mirror of https://gitlab.com/nakst/essence
2577 lines
97 KiB
C++
2577 lines
97 KiB
C++
// TODO Tabs:
|
|
// - Dragging out of the window.
|
|
// - Dragging onto other windows.
|
|
// - Keyboard shortcuts.
|
|
// - New tab page - search; recent files.
|
|
// - Right click menu.
|
|
// - Duplicate tabs.
|
|
|
|
// TODO Graphical issues:
|
|
// - Closing tabs isn't animating.
|
|
// - Inactivate windows don't dim outline around tabs.
|
|
// - Resizing windows doesn't redraw old shadow sometimes.
|
|
|
|
// TODO Task bar:
|
|
// - Right click menu.
|
|
// - Notification area.
|
|
|
|
// TODO Desktop experience:
|
|
// - Alt+tab.
|
|
// - Changing wallpaper.
|
|
//
|
|
// TODO Global shortcuts:
|
|
// - Restoring closed tabs.
|
|
// - Switch to window.
|
|
// - Print screen.
|
|
|
|
// TODO Restarting Desktop if it crashes.
|
|
// TODO Make sure applications can't delete |Fonts:.
|
|
// TODO Handle open document deletion.
|
|
// TODO Store an array of processes for each InstalledApplication.
|
|
|
|
#define MSG_SETUP_DESKTOP_UI ((EsMessageType) (ES_MSG_USER_START + 1))
|
|
|
|
#define APPLICATION_PERMISSION_ALL_FILES (1 << 0)
|
|
#define APPLICATION_PERMISSION_MANAGE_PROCESSES (1 << 1)
|
|
#define APPLICATION_PERMISSION_POSIX_SUBSYSTEM (1 << 2)
|
|
#define APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION (1 << 3)
|
|
#define APPLICATION_PERMISSION_SHUTDOWN (1 << 4)
|
|
#define APPLICATION_PERMISSION_VIEW_FILE_TYPES (1 << 5)
|
|
#define APPLICATION_PERMISSION_ALL_DEVICES (1 << 6)
|
|
|
|
#define APPLICATION_ID_DESKTOP_BLANK_TAB (-0x70000000)
|
|
#define APPLICATION_ID_DESKTOP_SETTINGS (-0x70000001)
|
|
#define APPLICATION_ID_DESKTOP_CRASHED (-0x70000002)
|
|
|
|
#define CRASHED_TAB_FATAL_ERROR (0)
|
|
#define CRASHED_TAB_PROGRAM_NOT_FOUND (1)
|
|
#define CRASHED_TAB_INVALID_EXECUTABLE (2)
|
|
#define CRASHED_TAB_NOT_RESPONDING (3)
|
|
|
|
#define INSTALLATION_STATE_NONE (0)
|
|
#define INSTALLATION_STATE_INSTALLER (1)
|
|
|
|
struct ReorderItem : EsElement {
|
|
double sizeProgress, sizeTarget;
|
|
double offsetProgress, offsetTarget;
|
|
bool dragging;
|
|
int dragOffset, dragPosition;
|
|
};
|
|
|
|
struct ReorderList : EsElement {
|
|
int targetWidth, extraWidth;
|
|
Array<ReorderItem *> items; // By index.
|
|
};
|
|
|
|
struct WindowTab : ReorderItem {
|
|
// NOTE Don't forget to update WindowTabMoveToNewContainer when modifying this.
|
|
struct ContainerWindow *container;
|
|
struct ApplicationInstance *applicationInstance;
|
|
struct ApplicationInstance *notRespondingInstance;
|
|
EsButton *closeButton;
|
|
};
|
|
|
|
struct WindowTabBand : ReorderList {
|
|
struct ContainerWindow *container;
|
|
bool preventNextTabSizeAnimation;
|
|
};
|
|
|
|
struct TaskBarButton : ReorderItem {
|
|
ContainerWindow *containerWindow;
|
|
};
|
|
|
|
struct TaskList : ReorderList {
|
|
};
|
|
|
|
struct TaskBar : EsElement {
|
|
double enterProgress;
|
|
EsRectangle targetBounds;
|
|
TaskList taskList;
|
|
};
|
|
|
|
struct ContainerWindow {
|
|
WindowTabBand *tabBand;
|
|
TaskBarButton *taskBarButton;
|
|
EsWindow *window;
|
|
WindowTab *active;
|
|
};
|
|
|
|
struct OpenDocument {
|
|
char *path;
|
|
size_t pathBytes;
|
|
char *temporarySavePath;
|
|
size_t temporarySavePathBytes;
|
|
EsHandle readHandle;
|
|
EsObjectID id;
|
|
EsObjectID currentWriter;
|
|
uintptr_t referenceCount;
|
|
};
|
|
|
|
struct InstalledApplication {
|
|
char *cName;
|
|
char *cExecutable;
|
|
char *settingsPath;
|
|
size_t settingsPathBytes;
|
|
void (*createInstance)(EsMessage *); // For applications provided by Desktop.
|
|
int64_t id;
|
|
uint32_t iconID;
|
|
bool hidden, useSingleProcess, temporary;
|
|
bool useSingleInstance;
|
|
struct ApplicationInstance *singleInstance;
|
|
uint64_t permissions;
|
|
size_t openInstanceCount; // Only used if useSingleProcess is true.
|
|
EsHandle singleProcessHandle;
|
|
bool notified; // Temporary flag.
|
|
EsFileOffset totalSize; // 0 if uncalculated.
|
|
};
|
|
|
|
struct CommonDesktopInstance : EsInstance {
|
|
void (*destroy)(EsInstance *);
|
|
};
|
|
|
|
struct CrashedTabInstance : CommonDesktopInstance {
|
|
};
|
|
|
|
struct BlankTabInstance : CommonDesktopInstance {
|
|
};
|
|
|
|
struct ApplicationInstance {
|
|
// User interface.
|
|
WindowTab *tab; // nullptr for notRespondingInstance and user tasks.
|
|
EsObjectID embeddedWindowID;
|
|
EsHandle embeddedWindowHandle;
|
|
|
|
// Currently loaded application.
|
|
InstalledApplication *application;
|
|
EsObjectID documentID, processID;
|
|
EsHandle processHandle;
|
|
bool isUserTask;
|
|
|
|
// Metadata.
|
|
char title[128];
|
|
size_t titleBytes;
|
|
uint32_t iconID;
|
|
double progress;
|
|
};
|
|
|
|
const EsStyle styleNewTabContent = {
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_MAJOR,
|
|
.insets = ES_RECT_4(50, 50, 50, 50),
|
|
.gapMajor = 25,
|
|
},
|
|
};
|
|
|
|
const EsStyle styleButtonGroupContainer = {
|
|
.inherit = ES_STYLE_BUTTON_GROUP_CONTAINER,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_PREFERRED_WIDTH,
|
|
.preferredWidth = 400,
|
|
},
|
|
};
|
|
|
|
struct {
|
|
Array<InstalledApplication *> installedApplications;
|
|
Array<ApplicationInstance *> allApplicationInstances;
|
|
Array<ContainerWindow *> allContainerWindows;
|
|
Array<EsMessageDevice> connectedDevices;
|
|
|
|
InstalledApplication *fileManager;
|
|
|
|
EsObjectID currentDocumentID;
|
|
HashStore<EsObjectID, OpenDocument> openDocuments;
|
|
|
|
TaskBar taskBar;
|
|
EsWindow *wallpaperWindow;
|
|
EsButton *tasksButton;
|
|
|
|
bool shutdownWindowOpen;
|
|
bool setupDesktopUIComplete;
|
|
int installationState;
|
|
|
|
EsHandle nextClipboardFile;
|
|
EsObjectID nextClipboardProcessID;
|
|
EsHandle clipboardFile;
|
|
ClipboardInformation clipboardInformation;
|
|
|
|
bool configurationModified;
|
|
|
|
Array<ApplicationInstance *> allOngoingUserTasks;
|
|
double totalUserTaskProgress;
|
|
} desktop;
|
|
|
|
int TaskBarButtonMessage(EsElement *element, EsMessage *message);
|
|
ApplicationInstance *ApplicationInstanceCreate(int64_t id, EsApplicationStartupInformation *startupInformation, ContainerWindow *container, bool hidden = false);
|
|
bool ApplicationInstanceStart(int64_t applicationID, EsApplicationStartupInformation *startupInformation, ApplicationInstance *instance);
|
|
void ApplicationInstanceClose(ApplicationInstance *instance);
|
|
ApplicationInstance *ApplicationInstanceFindByWindowID(EsObjectID windowID, bool remove = false);
|
|
void EmbeddedWindowDestroyed(EsObjectID id);
|
|
void ConfigurationWriteToFile();
|
|
void OpenDocumentOpenReference(EsObjectID id);
|
|
void OpenDocumentCloseReference(EsObjectID id);
|
|
void WallpaperLoad(EsGeneric);
|
|
WindowTab *WindowTabCreate(ContainerWindow *container);
|
|
ContainerWindow *ContainerWindowCreate(int32_t width, int32_t height);
|
|
|
|
#include "settings.cpp"
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Reorder lists:
|
|
//////////////////////////////////////////////////////
|
|
|
|
bool ReorderItemAnimate(ReorderItem *item, uint64_t deltaMs, const char *entranceDuration) {
|
|
item->sizeProgress += (item->sizeTarget - item->sizeProgress)
|
|
* (1 - EsCRTexp(deltaMs * -3.0f / GetConstantNumber(entranceDuration)));
|
|
item->offsetProgress += (item->offsetTarget - item->offsetProgress)
|
|
* (1 - EsCRTexp(deltaMs * -3.0f / GetConstantNumber("taskBarButtonMoveDuration")));
|
|
|
|
bool complete = true;
|
|
|
|
if (EsCRTfabs(item->sizeTarget - item->sizeProgress) < 1.5) {
|
|
item->sizeProgress = item->sizeTarget;
|
|
} else {
|
|
complete = false;
|
|
}
|
|
|
|
if (EsCRTfabs(item->offsetTarget - item->offsetProgress) < 1.5) {
|
|
item->offsetProgress = item->offsetTarget;
|
|
} else {
|
|
complete = false;
|
|
}
|
|
|
|
EsElementRelayout(item->parent);
|
|
return complete;
|
|
}
|
|
|
|
bool ReorderItemDragged(ReorderItem *item, int mouseX) {
|
|
ReorderList *list = (ReorderList *) item->parent;
|
|
size_t childCount = list->items.Length();
|
|
|
|
if (!item->dragging) {
|
|
item->dragOffset = gui.lastClickX - item->offsetX;
|
|
item->BringToFront();
|
|
}
|
|
|
|
item->dragPosition = mouseX + item->GetWindowBounds().l - item->dragOffset;
|
|
|
|
int draggedIndex = list->targetWidth ? ((item->dragPosition + list->targetWidth / 2) / list->targetWidth) : 0;
|
|
if (draggedIndex < 0) draggedIndex = 0;
|
|
if (draggedIndex >= (int) childCount) draggedIndex = childCount - 1;
|
|
int currentIndex = -1;
|
|
|
|
for (uintptr_t i = 0; i < childCount; i++) {
|
|
if (list->items[i] == item) {
|
|
currentIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
EsAssert(currentIndex != -1);
|
|
|
|
bool changed = false;
|
|
|
|
if (draggedIndex != currentIndex) {
|
|
list->items.Delete(currentIndex);
|
|
list->items.Insert(item, draggedIndex);
|
|
|
|
for (uintptr_t i = 0, x = list->currentStyle->insets.l; i < childCount; i++) {
|
|
ReorderItem *child = (ReorderItem *) list->items[i];
|
|
|
|
if ((int) i != draggedIndex) {
|
|
int oldPosition = child->offsetX, newPosition = x + child->offsetProgress;
|
|
|
|
if (child->offsetProgress != oldPosition - newPosition) {
|
|
child->offsetProgress = oldPosition - newPosition;
|
|
child->StartAnimating();
|
|
}
|
|
}
|
|
|
|
x += child->sizeProgress;
|
|
}
|
|
|
|
changed = true;
|
|
}
|
|
|
|
item->dragging = true;
|
|
EsElementRelayout(item->parent);
|
|
|
|
return changed;
|
|
}
|
|
|
|
void ReorderItemDragComplete(ReorderItem *item) {
|
|
if (!item->dragging) {
|
|
return;
|
|
}
|
|
|
|
ReorderList *list = (ReorderList *) item->parent;
|
|
|
|
for (uintptr_t i = 0, x = list->currentStyle->insets.l; i < list->items.Length(); i++) {
|
|
if (list->items[i] == item) {
|
|
int oldPosition = item->offsetX, newPosition = x + item->offsetProgress;
|
|
|
|
if (item->offsetProgress != oldPosition - newPosition) {
|
|
item->offsetProgress = oldPosition - newPosition;
|
|
item->StartAnimating();
|
|
}
|
|
}
|
|
|
|
x += item->sizeTarget;
|
|
}
|
|
|
|
item->dragging = false;
|
|
}
|
|
|
|
int ReorderListLayout(ReorderList *list, int additionalRightMargin, bool clampDraggedItem, bool preventTabSizeAnimation) {
|
|
EsRectangle bounds = list->GetBounds();
|
|
bounds.l += list->currentStyle->insets.l;
|
|
bounds.r -= list->currentStyle->insets.r;
|
|
bounds.r -= additionalRightMargin;
|
|
|
|
size_t childCount = list->items.Length();
|
|
|
|
if (!childCount) {
|
|
return 0;
|
|
}
|
|
|
|
int totalWidth = 0;
|
|
|
|
for (uintptr_t i = 0; i < childCount; i++) {
|
|
ReorderItem *child = list->items[i];
|
|
totalWidth += child->currentStyle->metrics->maximumWidth + list->currentStyle->metrics->gapMinor;
|
|
}
|
|
|
|
bool widthClamped = false;
|
|
|
|
if (totalWidth > Width(bounds)) {
|
|
totalWidth = Width(bounds);
|
|
widthClamped = true;
|
|
}
|
|
|
|
int targetWidth = totalWidth / childCount;
|
|
int extraWidth = totalWidth % childCount;
|
|
|
|
list->targetWidth = targetWidth;
|
|
list->extraWidth = extraWidth;
|
|
|
|
for (uintptr_t i = 0; i < childCount; i++) {
|
|
ReorderItem *child = list->items[i];
|
|
|
|
int sizeTarget = targetWidth;
|
|
if (extraWidth) sizeTarget++, extraWidth--;
|
|
|
|
if (preventTabSizeAnimation) {
|
|
child->sizeTarget = child->sizeProgress = sizeTarget;
|
|
}
|
|
|
|
if (child->sizeTarget != sizeTarget) {
|
|
child->sizeTarget = sizeTarget;
|
|
child->StartAnimating();
|
|
}
|
|
}
|
|
|
|
int x = bounds.l;
|
|
|
|
for (uintptr_t i = 0; i < childCount; i++) {
|
|
ReorderItem *child = list->items[i];
|
|
int width = (i == childCount - 1 && widthClamped) ? (totalWidth - x) : child->sizeProgress;
|
|
int gap = list->currentStyle->metrics->gapMinor;
|
|
|
|
if (child->dragging) {
|
|
int p = child->dragPosition;
|
|
|
|
if (clampDraggedItem) {
|
|
if (p + width > bounds.r) p = bounds.r - width;
|
|
if (p < bounds.l) p = bounds.l;
|
|
}
|
|
|
|
EsElementMove(child, p, 0, width - gap, Height(bounds));
|
|
} else {
|
|
EsElementMove(child, x + child->offsetProgress, 0, width - gap, Height(bounds));
|
|
}
|
|
|
|
x += width;
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
int ReorderListMessage(EsElement *_list, EsMessage *message) {
|
|
ReorderList *list = (ReorderList *) _list;
|
|
|
|
if (message->type == ES_MSG_LAYOUT) {
|
|
ReorderListLayout(list, 0, false, false);
|
|
} else if (message->type == ES_MSG_DESTROY) {
|
|
list->items.Free();
|
|
} else if (message->type == ES_MSG_ADD_CHILD) {
|
|
EsMessage m = { ES_MSG_REORDER_ITEM_TEST };
|
|
|
|
if (ES_HANDLED == EsMessageSend(message->child, &m)) {
|
|
list->items.Add((ReorderItem *) message->child);
|
|
}
|
|
} else if (message->type == ES_MSG_REMOVE_CHILD) {
|
|
EsMessage m = { ES_MSG_REORDER_ITEM_TEST };
|
|
|
|
if (ES_HANDLED == EsMessageSend(message->child, &m)) {
|
|
list->items.FindAndDelete((ReorderItem *) message->child, true);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Container windows:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void WindowTabClose(WindowTab *tab) {
|
|
if (tab->notRespondingInstance) {
|
|
// The application is not responding, so force quit the process.
|
|
EsProcessTerminate(tab->applicationInstance->processHandle, 1);
|
|
} else {
|
|
ApplicationInstanceClose(tab->applicationInstance);
|
|
}
|
|
}
|
|
|
|
void WindowTabActivate(WindowTab *tab, bool force = false) {
|
|
if (tab->container->active != tab || force) {
|
|
tab->container->active = tab;
|
|
EsElementRelayout(tab->container->tabBand);
|
|
tab->container->taskBarButton->Repaint(true);
|
|
EsHandle handle = tab->notRespondingInstance ? tab->notRespondingInstance->embeddedWindowHandle : tab->applicationInstance->embeddedWindowHandle;
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, tab->window->handle, handle, 0, ES_WINDOW_PROPERTY_EMBED);
|
|
}
|
|
}
|
|
|
|
void WindowTabDestroy(WindowTab *tab) {
|
|
ContainerWindow *container = tab->container;
|
|
|
|
if (container->tabBand->items.Length() == 1) {
|
|
EsElementDestroy(container->window);
|
|
EsElementDestroy(container->taskBarButton);
|
|
desktop.allContainerWindows.FindAndDeleteSwap(container, true);
|
|
} else {
|
|
if (container->active == tab) {
|
|
container->active = nullptr;
|
|
|
|
for (uintptr_t i = 0; i < container->tabBand->items.Length(); i++) {
|
|
if (container->tabBand->items[i] != tab) continue;
|
|
WindowTabActivate((WindowTab *) container->tabBand->items[i ? (i - 1) : 1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
EsElementDestroy(tab);
|
|
}
|
|
}
|
|
|
|
WindowTab *WindowTabMoveToNewContainer(WindowTab *tab, ContainerWindow *container, int32_t width, int32_t height) {
|
|
// Create the new tab and container window.
|
|
WindowTab *newTab = WindowTabCreate(container ?: ContainerWindowCreate(width, height));
|
|
if (!newTab) return nullptr;
|
|
|
|
// Move ownership of the instance to the new tab.
|
|
newTab->applicationInstance = tab->applicationInstance;
|
|
newTab->notRespondingInstance = tab->notRespondingInstance;
|
|
EsAssert(tab->applicationInstance->tab == tab);
|
|
tab->applicationInstance->tab = newTab;
|
|
tab->applicationInstance = nullptr;
|
|
tab->notRespondingInstance = nullptr;
|
|
|
|
// Destroy the old tab, and activate the new one.
|
|
WindowTabDestroy(tab); // Deplaces the embedded window from the old container.
|
|
WindowTabActivate(newTab);
|
|
|
|
// If this is an existing container window, make sure it's activated.
|
|
if (container) EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, newTab->window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED);
|
|
|
|
return newTab;
|
|
}
|
|
|
|
int ProcessGlobalKeyboardShortcuts(EsElement *, EsMessage *message) {
|
|
if (message->type == ES_MSG_KEY_DOWN) {
|
|
bool ctrlOnly = message->keyboard.modifiers == ES_MODIFIER_CTRL;
|
|
int scancode = message->keyboard.scancode;
|
|
|
|
if (ctrlOnly && scancode == ES_SCANCODE_N) {
|
|
ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, nullptr);
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ContainerWindowMessage(EsElement *element, EsMessage *message) {
|
|
ContainerWindow *container = (ContainerWindow *) element->userData.p;
|
|
|
|
if (message->type == ES_MSG_WINDOW_ACTIVATED) {
|
|
container->taskBarButton->customStyleState |= THEME_STATE_SELECTED;
|
|
container->taskBarButton->MaybeRefreshStyle();
|
|
} else if (message->type == ES_MSG_WINDOW_DEACTIVATED) {
|
|
container->taskBarButton->customStyleState &= ~THEME_STATE_SELECTED;
|
|
container->taskBarButton->MaybeRefreshStyle();
|
|
} else if (message->type == ES_MSG_KEY_DOWN) {
|
|
bool ctrlOnly = message->keyboard.modifiers == ES_MODIFIER_CTRL;
|
|
int scancode = message->keyboard.scancode;
|
|
|
|
if (((message->keyboard.modifiers & ~ES_MODIFIER_SHIFT) == ES_MODIFIER_CTRL) && message->keyboard.scancode == ES_SCANCODE_TAB) {
|
|
int tab = -1;
|
|
|
|
for (uintptr_t i = 0; i < container->tabBand->items.Length(); i++) {
|
|
if (container->tabBand->items[i] == container->active) {
|
|
tab = i;
|
|
}
|
|
}
|
|
|
|
EsAssert(tab != -1);
|
|
tab += ((message->keyboard.modifiers & ES_MODIFIER_SHIFT) ? -1 : 1);
|
|
if (tab == -1) tab = container->tabBand->items.Length() - 1;
|
|
if (tab == (int) container->tabBand->items.Length()) tab = 0;
|
|
WindowTabActivate((WindowTab *) container->tabBand->items[tab]);
|
|
} else if (ctrlOnly && scancode == ES_SCANCODE_T) {
|
|
ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, container);
|
|
} else if (ctrlOnly && scancode == ES_SCANCODE_W) {
|
|
WindowTabClose(container->active);
|
|
} else if (message->keyboard.modifiers == ES_MODIFIER_FLAG && scancode == ES_SCANCODE_UP_ARROW) {
|
|
WindowSnap(container->window, false, false, SNAP_EDGE_MAXIMIZE);
|
|
} else if (message->keyboard.modifiers == ES_MODIFIER_FLAG && scancode == ES_SCANCODE_DOWN_ARROW) {
|
|
if (container->window->isMaximised) {
|
|
WindowRestore(container->window);
|
|
} else {
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, container->window->handle, 0, 0, ES_WINDOW_MOVE_HIDDEN);
|
|
}
|
|
} else if (message->keyboard.modifiers == ES_MODIFIER_FLAG && scancode == ES_SCANCODE_LEFT_ARROW) {
|
|
if (container->window->restoreOnNextMove) {
|
|
WindowRestore(container->window);
|
|
} else {
|
|
WindowSnap(container->window, false, false, SNAP_EDGE_LEFT);
|
|
}
|
|
} else if (message->keyboard.modifiers == ES_MODIFIER_FLAG && scancode == ES_SCANCODE_RIGHT_ARROW) {
|
|
if (container->window->restoreOnNextMove) {
|
|
WindowRestore(container->window);
|
|
} else {
|
|
WindowSnap(container->window, false, false, SNAP_EDGE_RIGHT);
|
|
}
|
|
} else {
|
|
bool unhandled = true;
|
|
|
|
for (uintptr_t i = 0; i < 9; i++) {
|
|
if (ctrlOnly && scancode == (int) (ES_SCANCODE_1 + i) && container->tabBand->items.Length() > i) {
|
|
WindowTabActivate((WindowTab *) container->tabBand->items[i]);
|
|
unhandled = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (unhandled) {
|
|
ProcessGlobalKeyboardShortcuts(element, message);
|
|
}
|
|
}
|
|
} else if (message->type == ES_MSG_WINDOW_RESIZED) {
|
|
container->tabBand->preventNextTabSizeAnimation = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int WindowTabMessage(EsElement *element, EsMessage *message) {
|
|
WindowTab *tab = (WindowTab *) element;
|
|
WindowTabBand *band = (WindowTabBand *) tab->parent;
|
|
ApplicationInstance *instance = tab->applicationInstance;
|
|
|
|
if (message->type == ES_MSG_DESTROY) {
|
|
if (tab->notRespondingInstance) {
|
|
ApplicationInstanceClose(tab->notRespondingInstance);
|
|
tab->notRespondingInstance = nullptr;
|
|
}
|
|
} else if (message->type == ES_MSG_PRESSED_START) {
|
|
tab->BringToFront();
|
|
WindowTabActivate(tab);
|
|
} else if (message->type == ES_MSG_HIT_TEST) {
|
|
EsRectangle bounds = tab->GetBounds();
|
|
|
|
if (message->hitTest.x <= 12) {
|
|
message->hitTest.inside = (bounds.b - message->hitTest.y) * 14 < message->hitTest.x * bounds.b;
|
|
} else if (message->hitTest.x > bounds.r - 12) {
|
|
message->hitTest.inside = (bounds.b - message->hitTest.y) * 14 < (bounds.r - message->hitTest.x) * bounds.b;
|
|
}
|
|
} else if (message->type == ES_MSG_LAYOUT) {
|
|
int closeButtonWidth = tab->closeButton->currentStyle->preferredWidth;
|
|
Rectangle16 insets = tab->currentStyle->metrics->insets;
|
|
EsElementSetHidden(tab->closeButton, tab->currentStyle->gapWrap * 2 + closeButtonWidth >= tab->width);
|
|
EsElementMove(tab->closeButton, tab->width - tab->currentStyle->gapWrap - closeButtonWidth,
|
|
insets.t, closeButtonWidth, tab->height - insets.t - insets.b);
|
|
} else if (message->type == ES_MSG_PAINT) {
|
|
EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height),
|
|
instance->title, instance->titleBytes, instance->iconID);
|
|
} else if (message->type == ES_MSG_ANIMATE) {
|
|
message->animate.complete = ReorderItemAnimate(tab, message->animate.deltaMs, "windowTabEntranceDuration");
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) {
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) {
|
|
EsElementSetDisabled(band->GetChild(0), true);
|
|
|
|
if (band->items.Length() == 1) {
|
|
// Get the window we're hovering the tab over.
|
|
EsObjectID hoverWindowID;
|
|
EsPoint mousePositionOnScreen = EsMouseGetPosition();
|
|
EsSyscall(ES_SYSCALL_WINDOW_FIND_BY_POINT, (uintptr_t) &hoverWindowID, mousePositionOnScreen.x, mousePositionOnScreen.y, tab->window->id);
|
|
EsWindow *hoverWindow = WindowFromWindowID(hoverWindowID);
|
|
bool dragInto = false;
|
|
|
|
if (hoverWindow && hoverWindow->windowStyle == ES_WINDOW_CONTAINER) {
|
|
// Are we hovering over the tab band?
|
|
ContainerWindow *hoverContainer = (ContainerWindow *) hoverWindow->userData.p;
|
|
EsRectangle hoverTabBandBounds = hoverContainer->tabBand->GetScreenBounds();
|
|
dragInto = EsRectangleContains(hoverTabBandBounds, mousePositionOnScreen.x, mousePositionOnScreen.y);
|
|
}
|
|
|
|
if (!dragInto) {
|
|
// Move the current window.
|
|
WindowChangeBounds(RESIZE_MOVE, mousePositionOnScreen.x, mousePositionOnScreen.y, &gui.lastClickX, &gui.lastClickY, band->window);
|
|
} else {
|
|
ContainerWindow *hoverContainer = (ContainerWindow *) hoverWindow->userData.p;
|
|
int32_t dragOffset = mousePositionOnScreen.x - tab->GetScreenBounds().l;
|
|
|
|
// Move the tab into the new container.
|
|
EsSyscall(ES_SYSCALL_WINDOW_TRANSFER_PRESS, tab->window->handle, hoverWindow->handle, 0, 0);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, tab->window->handle, 0, 0, ES_WINDOW_PROPERTY_EMBED);
|
|
WindowTab *newTab = WindowTabMoveToNewContainer(tab, hoverContainer, 0, 0);
|
|
|
|
// Setup the drag in the new container.
|
|
// TODO Sometimes the tab ends up a few pixels off?
|
|
newTab->window->pressed = newTab;
|
|
newTab->window->dragged = newTab;
|
|
newTab->dragOffset = dragOffset + 2 * hoverContainer->tabBand->currentStyle->insets.l;
|
|
newTab->dragging = true;
|
|
}
|
|
} else {
|
|
EsPoint mousePosition = EsMouseGetPosition(tab->window);
|
|
int32_t dragOffThreshold = GetConstantNumber("tabDragOffThreshold");
|
|
int32_t previousTabOffsetX = tab->offsetX;
|
|
|
|
if (EsRectangleContains(EsRectangleAdd(band->GetWindowBounds(), ES_RECT_1I(-dragOffThreshold)), mousePosition.x, mousePosition.y)) {
|
|
ReorderItemDragged(tab, message->mouseDragged.newPositionX);
|
|
} else {
|
|
// TODO Moving a tab directly from one container to another.
|
|
|
|
// If we dragged the tab off the left or right side of the band, put it at the start of the new tab band.
|
|
bool putAtStart = tab->dragPosition < band->currentStyle->insets.l
|
|
|| tab->dragPosition + tab->width > band->width - band->currentStyle->insets.r;
|
|
int32_t putAtStartClickX = band->currentStyle->insets.l + tab->dragOffset;
|
|
|
|
// End the drag on this container.
|
|
EsMessage m = { .type = ES_MSG_MOUSE_LEFT_UP };
|
|
UIMouseUp(band->window, &m, false);
|
|
|
|
// Move the tab to a new container.
|
|
WindowTab *newTab = WindowTabMoveToNewContainer(tab, nullptr, band->window->width, band->window->height);
|
|
|
|
if (newTab) {
|
|
// Transfer the drag to the new container.
|
|
EsSyscall(ES_SYSCALL_WINDOW_TRANSFER_PRESS, band->window->handle, newTab->window->handle, 0, 0);
|
|
ReorderItemDragged(newTab, 0);
|
|
newTab->dragPosition = putAtStart ? band->currentStyle->insets.l : previousTabOffsetX;
|
|
newTab->window->pressed = newTab;
|
|
newTab->window->dragged = newTab;
|
|
gui.lastClickX = putAtStart ? putAtStartClickX : mousePosition.x;
|
|
gui.mouseButtonDown = true;
|
|
gui.draggingStarted = true;
|
|
|
|
// Update the bounds of the new container.
|
|
EsPoint mousePositionOnScreen = EsMouseGetPosition();
|
|
WindowChangeBounds(RESIZE_MOVE, mousePositionOnScreen.x, mousePositionOnScreen.y, &gui.lastClickX, &gui.lastClickY, newTab->window);
|
|
}
|
|
}
|
|
}
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_UP) {
|
|
ReorderItemDragComplete(tab);
|
|
EsElementSetDisabled(band->GetChild(0), false);
|
|
} else if (message->type == ES_MSG_MOUSE_RIGHT_CLICK) {
|
|
EsMenu *menu = EsMenuCreate(tab, ES_FLAGS_DEFAULT);
|
|
|
|
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(DesktopCloseTab), [] (EsMenu *, EsGeneric context) {
|
|
WindowTabClose((WindowTab *) context.p);
|
|
}, tab);
|
|
|
|
if (tab->container->tabBand->items.Length() > 1) {
|
|
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(DesktopMoveTabToNewWindow), [] (EsMenu *, EsGeneric context) {
|
|
WindowTabMoveToNewContainer((WindowTab *) context.p, nullptr, 0, 0);
|
|
}, tab);
|
|
}
|
|
|
|
if (EsKeyboardIsShiftHeld()) {
|
|
EsMenuAddSeparator(menu);
|
|
|
|
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(DesktopInspectUI), [] (EsMenu *, EsGeneric context) {
|
|
WindowTab *tab = (WindowTab *) context.p;
|
|
ApplicationInstance *instance = tab->applicationInstance;
|
|
EsMessage m = { ES_MSG_TAB_INSPECT_UI };
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
}, tab);
|
|
}
|
|
|
|
EsMenuShow(menu);
|
|
} else if (message->type == ES_MSG_MOUSE_MIDDLE_UP && (element->state & UI_STATE_HOVERED)) {
|
|
WindowTabClose(tab);
|
|
} else if (message->type == ES_MSG_REORDER_ITEM_TEST) {
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
}
|
|
|
|
WindowTab *WindowTabCreate(ContainerWindow *container) {
|
|
WindowTab *tab = (WindowTab *) EsHeapAllocate(sizeof(WindowTab), true);
|
|
tab->container = container;
|
|
tab->Initialise(container->tabBand, ES_CELL_H_SHRINK | ES_CELL_V_BOTTOM, WindowTabMessage, nullptr);
|
|
tab->cName = "window tab";
|
|
|
|
tab->closeButton = EsButtonCreate(tab, ES_FLAGS_DEFAULT, ES_STYLE_WINDOW_TAB_CLOSE_BUTTON);
|
|
tab->closeButton->userData = tab;
|
|
|
|
EsButtonOnCommand(tab->closeButton, [] (EsInstance *, EsElement *element, EsCommand *) {
|
|
WindowTabClose((WindowTab *) element->userData.p);
|
|
});
|
|
|
|
return tab;
|
|
}
|
|
|
|
int WindowTabBandMessage(EsElement *element, EsMessage *message) {
|
|
WindowTabBand *band = (WindowTabBand *) element;
|
|
|
|
if (message->type == ES_MSG_LAYOUT) {
|
|
for (uint16_t i = 0; i < band->items.Length(); i++) {
|
|
WindowTab *tab = (WindowTab *) band->items[i];
|
|
tab->SetStyle(tab == tab->container->active ? ES_STYLE_WINDOW_TAB_ACTIVE : ES_STYLE_WINDOW_TAB_INACTIVE);
|
|
|
|
if (tab == tab->container->active) {
|
|
tab->BringToFront();
|
|
}
|
|
}
|
|
|
|
int x = ReorderListLayout(band, band->GetChild(0)->currentStyle->preferredWidth + 10 * theming.scale,
|
|
true, band->preventNextTabSizeAnimation);
|
|
band->GetChild(0)->InternalMove(band->GetChild(0)->currentStyle->preferredWidth,
|
|
band->GetChild(0)->currentStyle->preferredHeight, x + 10 * theming.scale, 4 * theming.scale);
|
|
band->preventNextTabSizeAnimation = false;
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) {
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) {
|
|
EsPoint screenPosition = EsMouseGetPosition();
|
|
WindowChangeBounds(RESIZE_MOVE, screenPosition.x, screenPosition.y, &gui.lastClickX, &gui.lastClickY, band->window);
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_UP) {
|
|
if (band->window->restoreOnNextMove) {
|
|
band->window->resetPositionOnNextMove = true;
|
|
}
|
|
} else if (message->type == ES_MSG_MOUSE_RIGHT_CLICK) {
|
|
EsMenu *menu = EsMenuCreate(band, ES_MENU_AT_CURSOR);
|
|
|
|
EsMenuAddItem(menu, band->window->isMaximised ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT,
|
|
INTERFACE_STRING(DesktopCenterWindow), [] (EsMenu *, EsGeneric context) {
|
|
WindowTabBand *band = (WindowTabBand *) context.p;
|
|
EsRectangle workArea;
|
|
EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_GET, 0, (uintptr_t) &workArea, 0, 0);
|
|
EsRectangle newBounds = EsRectangleCenter(workArea, EsWindowGetBounds(band->window));
|
|
newBounds.t -= 8, newBounds.b -= 8; // Because of the shadow, it looks a little better to be slightly above center :)
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, band->window->handle, (uintptr_t) &newBounds, 0, ES_FLAGS_DEFAULT);
|
|
}, band);
|
|
|
|
EsMenuShow(menu);
|
|
} else {
|
|
return ReorderListMessage(band, message);
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
}
|
|
|
|
ContainerWindow *ContainerWindowCreate(int32_t width, int32_t height) {
|
|
ContainerWindow *container = (ContainerWindow *) EsHeapAllocate(sizeof(ContainerWindow), true);
|
|
EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_CONTAINER);
|
|
desktop.allContainerWindows.Add(container);
|
|
|
|
window->messageUser = ContainerWindowMessage;
|
|
window->userData = container;
|
|
window->windowWidth = width ?: GetConstantNumber("windowDefaultWidth");
|
|
window->windowHeight = height ?: GetConstantNumber("windowDefaultHeight");
|
|
|
|
static int cascadeX = -1, cascadeY = -1;
|
|
EsRectangle workArea;
|
|
EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_GET, 0, (uintptr_t) &workArea, 0, 0);
|
|
int cascadeMargin = GetConstantNumber("windowCascadeMargin");
|
|
int cascadeOffset = GetConstantNumber("windowCascadeOffset");
|
|
if (cascadeX == -1 || cascadeX + (int) window->windowWidth > workArea.r - cascadeMargin) cascadeX = workArea.l + cascadeMargin;
|
|
if (cascadeY == -1 || cascadeY + (int) window->windowHeight > workArea.b - cascadeMargin) cascadeY = workArea.t + cascadeMargin;
|
|
EsRectangle bounds = ES_RECT_4(cascadeX, cascadeX + window->windowWidth, cascadeY, cascadeY + window->windowHeight);
|
|
if (bounds.r > workArea.r - cascadeMargin) bounds.r = workArea.r - cascadeMargin;
|
|
if (bounds.b > workArea.b - cascadeMargin) bounds.b = workArea.b - cascadeMargin;
|
|
cascadeX += cascadeOffset, cascadeY += cascadeOffset;
|
|
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_FLAGS_DEFAULT);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED);
|
|
|
|
window->mainPanel = EsPanelCreate(window, ES_ELEMENT_NON_CLIENT | ES_CELL_FILL, ES_STYLE_PANEL_CONTAINER_WINDOW_ROOT);
|
|
window->SetStyle(ES_STYLE_CONTAINER_WINDOW);
|
|
|
|
EsMessage m = { .type = ES_MSG_UI_SCALE_CHANGED };
|
|
EsMessageSend(window, &m);
|
|
|
|
container->window = window;
|
|
|
|
container->tabBand = (WindowTabBand *) EsHeapAllocate(sizeof(WindowTabBand), true);
|
|
container->tabBand->container = container;
|
|
container->tabBand->Initialise(container->window, ES_CELL_FILL, WindowTabBandMessage, ES_STYLE_WINDOW_TAB_BAND);
|
|
container->tabBand->cName = "window tab band";
|
|
|
|
EsButton *newTabButton = EsButtonCreate(container->tabBand, ES_FLAGS_DEFAULT, ES_STYLE_WINDOW_TAB_BAND_NEW);
|
|
|
|
EsButtonOnCommand(newTabButton, [] (EsInstance *, EsElement *element, EsCommand *) {
|
|
ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, (ContainerWindow *) element->window->userData.p);
|
|
});
|
|
|
|
container->taskBarButton = (TaskBarButton *) EsHeapAllocate(sizeof(TaskBarButton), true);
|
|
container->taskBarButton->customStyleState = THEME_STATE_SELECTED;
|
|
container->taskBarButton->containerWindow = container;
|
|
container->taskBarButton->Initialise(&desktop.taskBar.taskList, ES_CELL_FILL,
|
|
TaskBarButtonMessage, ES_STYLE_TASK_BAR_BUTTON);
|
|
container->taskBarButton->cName = "task bar button";
|
|
|
|
return container;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Task bar and system modals:
|
|
//////////////////////////////////////////////////////
|
|
|
|
int TaskBarButtonMessage(EsElement *element, EsMessage *message) {
|
|
TaskBarButton *button = (TaskBarButton *) element;
|
|
|
|
if (message->type == ES_MSG_PAINT) {
|
|
ContainerWindow *containerWindow = button->containerWindow;
|
|
ApplicationInstance *instance = containerWindow->active->applicationInstance;
|
|
EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height),
|
|
instance->title, instance->titleBytes, instance->iconID);
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) {
|
|
if (button->customStyleState & THEME_STATE_SELECTED) {
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, button->containerWindow->window->handle, 0, 0, ES_WINDOW_MOVE_HIDDEN);
|
|
} else {
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, button->containerWindow->window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED);
|
|
}
|
|
} else if (message->type == ES_MSG_ANIMATE) {
|
|
message->animate.complete = ReorderItemAnimate(button, message->animate.deltaMs, "taskBarButtonEntranceDuration");
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) {
|
|
ReorderItemDragged(button, message->mouseDragged.newPositionX);
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_UP) {
|
|
ReorderItemDragComplete(button);
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) {
|
|
} else if (message->type == ES_MSG_REORDER_ITEM_TEST) {
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
}
|
|
|
|
int TaskBarWindowMessage(EsElement *, EsMessage *message) {
|
|
if (message->type == ES_MSG_ANIMATE) {
|
|
desktop.taskBar.enterProgress += (1 - desktop.taskBar.enterProgress)
|
|
* (1 - EsCRTexp(message->animate.deltaMs * -3.0 / GetConstantNumber("taskBarEntranceDuration")));
|
|
|
|
if (EsCRTfabs(1 - desktop.taskBar.enterProgress) < 0.001) {
|
|
desktop.taskBar.enterProgress = 1;
|
|
message->animate.complete = true;
|
|
} else {
|
|
message->animate.complete = false;
|
|
}
|
|
|
|
EsRectangle bounds = desktop.taskBar.targetBounds;
|
|
bounds = Translate(bounds, 0, Height(bounds) * (1 - desktop.taskBar.enterProgress));
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, desktop.taskBar.window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_UPDATE_SCREEN);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int TaskBarMessage(EsElement *element, EsMessage *message) {
|
|
if (message->type == ES_MSG_LAYOUT) {
|
|
EsRectangle bounds = element->GetBounds();
|
|
element->GetChild(0)->InternalMove(Width(bounds), Height(bounds), 0, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void TaskBarTasksButtonUpdate() {
|
|
if (desktop.allOngoingUserTasks.Length()) {
|
|
if (EsElementIsHidden(desktop.tasksButton)) {
|
|
EsPanelStartMovementAnimation((EsPanel *) EsElementGetLayoutParent(desktop.tasksButton), 1.5f /* duration scale */);
|
|
EsElementStartTransition(desktop.tasksButton, ES_TRANSITION_FADE_IN, ES_ELEMENT_TRANSITION_ENTRANCE, 1.5f);
|
|
EsElementSetHidden(desktop.tasksButton, false);
|
|
}
|
|
|
|
// TODO Maybe grow button.
|
|
} else {
|
|
// TODO Maybe shrink or hide button.
|
|
// Currently, this always hides it, but I don't think this is a good behaviour.
|
|
|
|
if (!EsElementIsHidden(desktop.tasksButton)) {
|
|
EsPanelStartMovementAnimation((EsPanel *) EsElementGetLayoutParent(desktop.tasksButton), 1.5f /* duration scale */);
|
|
EsElementStartTransition(desktop.tasksButton, ES_TRANSITION_FADE_OUT, ES_ELEMENT_TRANSITION_HIDE_AFTER_COMPLETE, 1.5f);
|
|
}
|
|
}
|
|
|
|
EsElementRepaint(desktop.tasksButton);
|
|
}
|
|
|
|
int TaskBarTasksButtonMessage(EsElement *element, EsMessage *message) {
|
|
if (message->type == ES_MSG_GET_WIDTH) {
|
|
message->measure.width = GetConstantNumber("taskBarTasksButtonWidth");
|
|
} else if (message->type == ES_MSG_PAINT) {
|
|
char title[256];
|
|
size_t titleBytes;
|
|
|
|
if (desktop.allOngoingUserTasks.Length() > 1) {
|
|
// TODO Localization.
|
|
titleBytes = EsStringFormat(title, sizeof(title), "%d tasks" ELLIPSIS, desktop.allOngoingUserTasks.Length());
|
|
} else if (desktop.allOngoingUserTasks.Length() == 1) {
|
|
titleBytes = EsStringFormat(title, sizeof(title), "%s", desktop.allOngoingUserTasks.First()->titleBytes, desktop.allOngoingUserTasks.First()->title);
|
|
} else {
|
|
titleBytes = 0;
|
|
}
|
|
|
|
EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height), title, titleBytes);
|
|
} else if (message->type == ES_MSG_PAINT_ICON) {
|
|
double progress = !desktop.allOngoingUserTasks.Length() ? 1.0 : desktop.totalUserTaskProgress / desktop.allOngoingUserTasks.Length();
|
|
|
|
if (progress > 0.0 && progress <= 0.05) {
|
|
progress = 0.05; // Really small angles look strange; avoid them.
|
|
} else if (progress >= 0.9 && progress < 1.0) {
|
|
progress = (progress - 0.9) * 0.5 + 0.9; // Approach 95% from 90% at half speed to account for bad progress calculations.
|
|
}
|
|
|
|
uint32_t color1 = GetConstantNumber("taskBarTasksButtonWheelColor1");
|
|
uint32_t color2 = GetConstantNumber("taskBarTasksButtonWheelColor2");
|
|
|
|
EsPainter *painter = message->painter;
|
|
|
|
EsRectangle destination = EsPainterBoundsClient(painter);
|
|
destination = EsRectangleFit(destination, ES_RECT_2S(1, 1), true); // Center with a 1:1 aspect ratio.
|
|
|
|
RastSurface surface = {};
|
|
surface.buffer = (uint32_t *) painter->target->bits;
|
|
surface.stride = painter->target->stride;
|
|
|
|
if (RastSurfaceInitialise(&surface, painter->target->width, painter->target->height, true)) {
|
|
RastVertex center = { (destination.l + destination.r) * 0.5f, (destination.t + destination.b) * 0.5f };
|
|
|
|
RastContourStyle style = {};
|
|
style.internalWidth = 5.0f * theming.scale;
|
|
style.capMode = RAST_LINE_CAP_FLAT;
|
|
|
|
RastPaint paint = {};
|
|
paint.type = RAST_PAINT_SOLID;
|
|
|
|
{
|
|
paint.solid.color = color1 & 0xFFFFFF;
|
|
paint.solid.alpha = (color1 >> 24) / 255.0f;
|
|
|
|
RastPath path = {};
|
|
RastPathAppendArc(&path, center, Width(destination) * 0.45f, ES_PI * 2.0f, 0.0f);
|
|
RastShape shape = RastShapeCreateContour(&path, style, true);
|
|
RastSurfaceFill(surface, shape, paint, false);
|
|
RastPathDestroy(&path);
|
|
}
|
|
|
|
{
|
|
paint.solid.color = color2 & 0xFFFFFF;
|
|
paint.solid.alpha = (color2 >> 24) / 255.0f;
|
|
|
|
RastPath path = {};
|
|
RastPathAppendArc(&path, center, Width(destination) * 0.45f, ES_PI * 1.5f + progress * ES_PI * 2.0f, ES_PI * 1.5f);
|
|
RastShape shape = RastShapeCreateContour(&path, style, true);
|
|
RastSurfaceFill(surface, shape, paint, false);
|
|
RastPathDestroy(&path);
|
|
}
|
|
}
|
|
|
|
RastSurfaceDestroy(&surface);
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
}
|
|
|
|
void ShutdownModalCreate() {
|
|
if (desktop.shutdownWindowOpen) {
|
|
return;
|
|
}
|
|
|
|
desktop.shutdownWindowOpen = true;
|
|
|
|
// Setup the window.
|
|
|
|
EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_PLAIN);
|
|
EsRectangle screen;
|
|
EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0);
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &screen, 0, ES_WINDOW_MOVE_ALWAYS_ON_TOP);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, (uintptr_t) &screen, 0, ES_WINDOW_PROPERTY_BLUR_BOUNDS);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE, 0, ES_WINDOW_PROPERTY_SOLID);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, BLEND_WINDOW_MATERIAL_LIGHT_BLUR, 0, ES_WINDOW_PROPERTY_MATERIAL);
|
|
|
|
// Setup the UI.
|
|
|
|
EsPanel *stack = EsPanelCreate(window, ES_CELL_FILL | ES_PANEL_Z_STACK, ES_STYLE_PANEL_NORMAL_WINDOW_ROOT);
|
|
stack->cName = "window stack";
|
|
EsPanelCreate(stack, ES_CELL_FILL, ES_STYLE_PANEL_SHUTDOWN_OVERLAY)->cName = "modal overlay";
|
|
EsPanel *dialog = EsPanelCreate(stack, ES_PANEL_VERTICAL | ES_CELL_CENTER, ES_STYLE_PANEL_DIALOG_ROOT);
|
|
dialog->cName = "dialog";
|
|
EsPanel *heading = EsPanelCreate(dialog, ES_PANEL_HORIZONTAL | ES_CELL_H_FILL, ES_STYLE_DIALOG_HEADING);
|
|
EsIconDisplayCreate(heading, ES_FLAGS_DEFAULT, {}, ES_ICON_SYSTEM_SHUTDOWN);
|
|
EsTextDisplayCreate(heading, ES_CELL_H_FILL | ES_CELL_V_CENTER, ES_STYLE_TEXT_HEADING2,
|
|
INTERFACE_STRING(DesktopShutdownTitle))->cName = "dialog heading";
|
|
EsTextDisplayCreate(EsPanelCreate(dialog, ES_PANEL_VERTICAL | ES_CELL_H_FILL, ES_STYLE_DIALOG_CONTENT),
|
|
ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopConfirmShutdown))->cName = "dialog contents";
|
|
EsPanel *buttonArea = EsPanelCreate(dialog, ES_PANEL_HORIZONTAL | ES_PANEL_REVERSE | ES_CELL_H_FILL, ES_STYLE_DIALOG_BUTTON_AREA);
|
|
EsButton *cancelButton = EsButtonCreate(buttonArea, ES_BUTTON_DEFAULT, 0, INTERFACE_STRING(CommonCancel));
|
|
EsButton *restartButton = EsButtonCreate(buttonArea, ES_FLAGS_DEFAULT, 0, INTERFACE_STRING(DesktopRestartAction));
|
|
EsButton *shutdownButton = EsButtonCreate(buttonArea, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_DANGEROUS, INTERFACE_STRING(DesktopShutdownAction));
|
|
EsElementFocus(cancelButton);
|
|
|
|
// Setup command callbacks when the buttons are pressed.
|
|
|
|
EsButtonOnCommand(shutdownButton, [] (EsInstance *, EsElement *, EsCommand *) {
|
|
EsSyscall(ES_SYSCALL_SHUTDOWN, SHUTDOWN_ACTION_POWER_OFF, 0, 0, 0);
|
|
});
|
|
|
|
EsButtonOnCommand(restartButton, [] (EsInstance *, EsElement *, EsCommand *) {
|
|
EsSyscall(ES_SYSCALL_SHUTDOWN, SHUTDOWN_ACTION_RESTART, 0, 0, 0);
|
|
});
|
|
|
|
EsButtonOnCommand(cancelButton, [] (EsInstance *, EsElement *element, EsCommand *) {
|
|
EsElementDestroy(element->window);
|
|
desktop.shutdownWindowOpen = false;
|
|
});
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Built-in tabs:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void InstanceForceQuit(EsInstance *, EsElement *element, EsCommand *) {
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->tab && instance->tab->notRespondingInstance && instance->tab->notRespondingInstance->embeddedWindowID == element->window->id) {
|
|
EsProcessTerminate(instance->processHandle, 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void InstanceCrashedTabCreate(EsMessage *message) {
|
|
CrashedTabInstance *instance = (CrashedTabInstance *) _EsInstanceCreate(sizeof(CrashedTabInstance), message, nullptr);
|
|
int32_t reason = ((APIInstance *) instance->_private)->startupInformation->data;
|
|
instance->window->toolbarFillMode = true;
|
|
if (reason != CRASHED_TAB_NOT_RESPONDING) EsWindowSetIcon(instance->window, ES_ICON_DIALOG_ERROR);
|
|
EsElement *toolbar = EsWindowGetToolbar(instance->window);
|
|
EsPanel *panel = EsPanelCreate(toolbar, ES_CELL_V_CENTER | ES_CELL_V_PUSH | ES_CELL_H_SHRINK | ES_CELL_H_PUSH | ES_PANEL_VERTICAL, ES_STYLE_PANEL_CRASH_INFO);
|
|
|
|
if (reason == CRASHED_TAB_FATAL_ERROR) {
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopCrashedApplication));
|
|
} else if (reason == CRASHED_TAB_PROGRAM_NOT_FOUND) {
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopNoSuchApplication));
|
|
EsWindowSetTitle(instance->window, INTERFACE_STRING(CommonErrorTitle));
|
|
} else if (reason == CRASHED_TAB_INVALID_EXECUTABLE) {
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopApplicationStartupError));
|
|
EsWindowSetTitle(instance->window, INTERFACE_STRING(CommonErrorTitle));
|
|
} else if (reason == CRASHED_TAB_NOT_RESPONDING) {
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_PARAGRAPH, INTERFACE_STRING(DesktopNotResponding));
|
|
EsButton *button = EsButtonCreate(panel, ES_CELL_H_RIGHT, ES_STYLE_PUSH_BUTTON_DANGEROUS, INTERFACE_STRING(DesktopForceQuit));
|
|
EsButtonOnCommand(button, InstanceForceQuit);
|
|
}
|
|
}
|
|
|
|
void InstanceBlankTabCreate(EsMessage *message) {
|
|
EsInstance *instance = _EsInstanceCreate(sizeof(BlankTabInstance), message, nullptr);
|
|
EsWindowSetTitle(instance->window, INTERFACE_STRING(DesktopNewTabTitle));
|
|
EsPanel *windowBackground = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND);
|
|
EsPanel *content = EsPanelCreate(windowBackground, ES_CELL_FILL | ES_PANEL_V_SCROLL_AUTO, &styleNewTabContent);
|
|
EsPanel *buttonGroup;
|
|
|
|
// Installed applications list.
|
|
|
|
buttonGroup = EsPanelCreate(content, ES_PANEL_VERTICAL | ES_CELL_H_SHRINK, &styleButtonGroupContainer);
|
|
buttonGroup->separatorStylePart = ES_STYLE_BUTTON_GROUP_SEPARATOR;
|
|
buttonGroup->separatorFlags = ES_CELL_H_FILL;
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
InstalledApplication *application = desktop.installedApplications[i];
|
|
if (application->hidden) continue;
|
|
|
|
EsButton *button = EsButtonCreate(buttonGroup, ES_CELL_H_FILL | ES_ELEMENT_NO_FOCUS_ON_CLICK, ES_STYLE_BUTTON_GROUP_ITEM, application->cName);
|
|
EsButtonSetIcon(button, (EsStandardIcon) application->iconID ?: ES_ICON_APPLICATION_DEFAULT_ICON);
|
|
button->userData = application;
|
|
|
|
EsButtonOnCommand(button, [] (EsInstance *, EsElement *element, EsCommand *) {
|
|
ApplicationInstance *instance = ApplicationInstanceFindByWindowID(element->window->id);
|
|
|
|
if (ApplicationInstanceStart(((InstalledApplication *) element->userData.p)->id, nullptr, instance)) {
|
|
WindowTabActivate(instance->tab, true);
|
|
EsInstanceDestroy(element->instance);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Application management:
|
|
//////////////////////////////////////////////////////
|
|
|
|
ApplicationInstance *ApplicationInstanceFindByWindowID(EsObjectID windowID, bool remove) {
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->embeddedWindowID == windowID) {
|
|
if (remove) {
|
|
desktop.allApplicationInstances.Delete(i);
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ApplicationInstanceClose(ApplicationInstance *instance) {
|
|
// TODO Force closing not responding instances.
|
|
EsMessage m = { ES_MSG_TAB_CLOSE_REQUEST };
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
}
|
|
|
|
bool ApplicationInstanceStart(int64_t applicationID, EsApplicationStartupInformation *startupInformation, ApplicationInstance *instance) {
|
|
InstalledApplication *application = nullptr;
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
if (desktop.installedApplications[i]->id == applicationID) {
|
|
application = desktop.installedApplications[i];
|
|
}
|
|
}
|
|
|
|
if (application && application->useSingleInstance && application->singleInstance) {
|
|
WindowTabActivate(application->singleInstance->tab);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, application->singleInstance->tab->window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED);
|
|
return false;
|
|
}
|
|
|
|
if (!application) {
|
|
EsApplicationStartupInformation s = {};
|
|
s.data = CRASHED_TAB_PROGRAM_NOT_FOUND;
|
|
return ApplicationInstanceStart(APPLICATION_ID_DESKTOP_CRASHED, &s, instance);
|
|
}
|
|
|
|
EsApplicationStartupInformation _startupInformation = {};
|
|
|
|
if (!startupInformation) {
|
|
startupInformation = &_startupInformation;
|
|
}
|
|
|
|
if (instance->tab && instance->tab->notRespondingInstance) {
|
|
ApplicationInstanceClose(instance->tab->notRespondingInstance);
|
|
instance->tab->notRespondingInstance = nullptr;
|
|
}
|
|
|
|
if (instance->processHandle) {
|
|
EsHandleClose(instance->processHandle);
|
|
instance->processID = 0;
|
|
instance->processHandle = ES_INVALID_HANDLE;
|
|
}
|
|
|
|
if (instance->documentID) {
|
|
OpenDocumentCloseReference(instance->documentID);
|
|
instance->documentID = 0;
|
|
}
|
|
|
|
instance->application = application;
|
|
|
|
if (application->useSingleProcess && application->singleProcessHandle) {
|
|
EsProcessState state;
|
|
EsProcessGetState(application->singleProcessHandle, &state);
|
|
|
|
if (state.flags & (ES_PROCESS_STATE_ALL_THREADS_TERMINATED | ES_PROCESS_STATE_TERMINATING | ES_PROCESS_STATE_CRASHED)) {
|
|
EsHandleClose(application->singleProcessHandle);
|
|
application->singleProcessHandle = ES_INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
EsHandle process = application->singleProcessHandle;
|
|
|
|
if (application->createInstance) {
|
|
process = ES_CURRENT_PROCESS;
|
|
} else if (!application->useSingleProcess || process == ES_INVALID_HANDLE) {
|
|
EsProcessInformation information;
|
|
EsProcessCreationArguments arguments = {};
|
|
|
|
_EsNodeInformation executableNode;
|
|
EsError error = NodeOpen(application->cExecutable, EsCStringLength(application->cExecutable),
|
|
ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND, &executableNode);
|
|
|
|
if (ES_CHECK_ERROR(error)) {
|
|
EsApplicationStartupInformation s = {};
|
|
s.data = CRASHED_TAB_INVALID_EXECUTABLE;
|
|
return ApplicationInstanceStart(APPLICATION_ID_DESKTOP_CRASHED, &s, instance);
|
|
}
|
|
|
|
arguments.executable = executableNode.handle;
|
|
arguments.permissions = ES_PERMISSION_WINDOW_MANAGER;
|
|
|
|
Array<EsMountPoint> initialMountPoints = {};
|
|
_EsNodeInformation settingsNode = {};
|
|
|
|
if (application->permissions & APPLICATION_PERMISSION_MANAGE_PROCESSES) {
|
|
arguments.permissions |= ES_PERMISSION_TAKE_SYSTEM_SNAPSHOT;
|
|
arguments.permissions |= ES_PERMISSION_PROCESS_CREATE;
|
|
arguments.permissions |= ES_PERMISSION_PROCESS_OPEN;
|
|
}
|
|
|
|
if (application->permissions & APPLICATION_PERMISSION_POSIX_SUBSYSTEM) {
|
|
arguments.permissions |= ES_PERMISSION_PROCESS_OPEN;
|
|
arguments.permissions |= ES_PERMISSION_POSIX_SUBSYSTEM;
|
|
|
|
MountPoint root = *NodeFindMountPoint(EsLiteral("0:"));
|
|
root.write = true;
|
|
root.prefixBytes = EsStringFormat(root.prefix, sizeof(root.prefix), "|POSIX:");
|
|
initialMountPoints.Add(root);
|
|
}
|
|
|
|
if (application->permissions & APPLICATION_PERMISSION_ALL_FILES) {
|
|
for (uintptr_t i = 0; i < api.mountPoints.Length(); i++) {
|
|
initialMountPoints.Add(api.mountPoints[i]);
|
|
initialMountPoints[i].write = true;
|
|
}
|
|
|
|
arguments.permissions |= ES_PERMISSION_GET_VOLUME_INFORMATION;
|
|
} else {
|
|
initialMountPoints.Add(*NodeFindMountPoint(EsLiteral("|Fonts:")));
|
|
}
|
|
|
|
{
|
|
error = NodeOpen(application->settingsPath, application->settingsPathBytes,
|
|
ES_NODE_DIRECTORY | ES_NODE_CREATE_DIRECTORIES | _ES_NODE_DIRECTORY_WRITE, &settingsNode);
|
|
|
|
if (error == ES_SUCCESS) {
|
|
EsMountPoint settings = {};
|
|
settings.prefixBytes = EsStringFormat(settings.prefix, sizeof(settings.prefix), "|Settings:");
|
|
settings.base = settingsNode.handle;
|
|
settings.write = true;
|
|
initialMountPoints.Add(settings);
|
|
} else {
|
|
settingsNode.handle = ES_INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
arguments.initialMountPoints = initialMountPoints.array;
|
|
arguments.initialMountPointCount = initialMountPoints.Length();
|
|
|
|
error = EsProcessCreate(&arguments, &information);
|
|
EsHandleClose(arguments.executable);
|
|
|
|
initialMountPoints.Free();
|
|
if (settingsNode.handle) EsHandleClose(settingsNode.handle);
|
|
|
|
if (!ES_CHECK_ERROR(error)) {
|
|
process = information.handle;
|
|
EsHandleClose(information.mainThread.handle);
|
|
} else {
|
|
EsApplicationStartupInformation s = {};
|
|
s.data = CRASHED_TAB_INVALID_EXECUTABLE;
|
|
return ApplicationInstanceStart(APPLICATION_ID_DESKTOP_CRASHED, &s, instance);
|
|
}
|
|
}
|
|
|
|
if (application->useSingleProcess) {
|
|
application->singleProcessHandle = process;
|
|
}
|
|
|
|
instance->processID = EsProcessGetID(process);
|
|
instance->processHandle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, process, ES_CURRENT_PROCESS, 0, 0);
|
|
|
|
if (startupInformation->documentID) {
|
|
instance->documentID = startupInformation->documentID;
|
|
OpenDocumentOpenReference(instance->documentID);
|
|
}
|
|
|
|
EsMessage m = { ES_MSG_INSTANCE_CREATE };
|
|
|
|
if (~startupInformation->flags & ES_APPLICATION_STARTUP_MANUAL_PATH) {
|
|
// Only tell the application the name of the file.
|
|
|
|
for (uintptr_t i = 0; i < (size_t) startupInformation->filePathBytes; i++) {
|
|
if (startupInformation->filePath[i] == '/') {
|
|
startupInformation->filePath += i + 1;
|
|
startupInformation->filePathBytes -= i + 1;
|
|
i = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Share handles to the file and the startup information buffer.
|
|
|
|
if (startupInformation->readHandle) {
|
|
startupInformation->readHandle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, startupInformation->readHandle, process, 0, 0);
|
|
}
|
|
|
|
if (!application->useSingleProcess && !application->createInstance) {
|
|
startupInformation->flags |= ES_APPLICATION_STARTUP_SINGLE_INSTANCE_IN_PROCESS;
|
|
}
|
|
|
|
uint8_t *createInstanceDataBuffer = ApplicationStartupInformationToBuffer(startupInformation, &m.createInstance.dataBytes);
|
|
m.createInstance.data = EsConstantBufferCreate(createInstanceDataBuffer, m.createInstance.dataBytes, process);
|
|
EsHeapFree(createInstanceDataBuffer);
|
|
|
|
EsHandle handle = EsSyscall(ES_SYSCALL_WINDOW_CREATE, ES_WINDOW_NORMAL, 0, 0, 0);
|
|
instance->embeddedWindowHandle = handle;
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, handle, 0xFF000000 | GetConstantNumber("windowFillColor"), 0, ES_WINDOW_PROPERTY_RESIZE_CLEAR_COLOR);
|
|
instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, handle, 0, 0, 0);
|
|
m.createInstance.window = EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, handle, process, 0, ES_WINDOW_PROPERTY_EMBED_OWNER);
|
|
|
|
if (application->createInstance) {
|
|
application->createInstance(&m);
|
|
} else {
|
|
EsMessagePostRemote(process, &m);
|
|
|
|
if (!application->useSingleProcess) {
|
|
EsHandleClose(process);
|
|
} else {
|
|
application->openInstanceCount++;
|
|
}
|
|
}
|
|
|
|
if (application->useSingleInstance) {
|
|
application->singleInstance = instance;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ApplicationInstance *ApplicationInstanceCreate(int64_t id, EsApplicationStartupInformation *startupInformation, ContainerWindow *container, bool hidden) {
|
|
ApplicationInstance *instance = (ApplicationInstance *) EsHeapAllocate(sizeof(ApplicationInstance), true);
|
|
WindowTab *tab = !hidden ? WindowTabCreate(container ?: ContainerWindowCreate(0, 0)) : nullptr;
|
|
if (tab) tab->applicationInstance = instance;
|
|
instance->title[0] = ' ';
|
|
instance->titleBytes = 1;
|
|
instance->tab = tab;
|
|
desktop.allApplicationInstances.Add(instance);
|
|
|
|
if (ApplicationInstanceStart(id, startupInformation, instance)) {
|
|
if (!hidden) WindowTabActivate(tab);
|
|
return instance;
|
|
} else {
|
|
// TODO Destroy the tab/container window.
|
|
// Or, we probably didn't want to create them in the first place.
|
|
EsHeapFree(instance);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void ApplicationTemporaryDestroy(InstalledApplication *application) {
|
|
if (!application->temporary) return;
|
|
EsAssert(!application->singleProcessHandle);
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
if (desktop.installedApplications[i] == application) {
|
|
desktop.installedApplications.Delete(i);
|
|
EsHeapFree(application->cName);
|
|
EsHeapFree(application->cExecutable);
|
|
EsHeapFree(application->settingsPath);
|
|
EsHeapFree(application);
|
|
// TODO Delete the settings folder.
|
|
return;
|
|
}
|
|
}
|
|
|
|
EsAssert(false);
|
|
}
|
|
|
|
void ApplicationInstanceCrashed(EsMessage *message) {
|
|
EsHandle processHandle = EsProcessOpen(message->crash.pid);
|
|
EsAssert(processHandle); // Since the process is paused, it cannot be removed.
|
|
|
|
EsProcessState state;
|
|
EsProcessGetState(processHandle, &state);
|
|
const char *fatalErrorString = state.crashReason.errorCode >= ES_FATAL_ERROR_COUNT ? "[unknown]"
|
|
: EnumLookupNameFromValue(enumStrings_EsFatalError, state.crashReason.errorCode);
|
|
const char *systemCallString = state.crashReason.duringSystemCall == -1 ? "[none]"
|
|
: EnumLookupNameFromValue(enumStrings_EsSyscallType, state.crashReason.duringSystemCall);
|
|
EsPrint("Process %d has crashed with error %z, during system call %z.\n", state.id, fatalErrorString, systemCallString);
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->processID == message->crash.pid) {
|
|
if (instance->tab) {
|
|
ApplicationInstanceStart(APPLICATION_ID_DESKTOP_CRASHED, nullptr, instance);
|
|
WindowTabActivate(instance->tab, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
if (desktop.installedApplications[i]->useSingleProcess && desktop.installedApplications[i]->singleProcessHandle
|
|
&& EsProcessGetID(desktop.installedApplications[i]->singleProcessHandle) == message->crash.pid) {
|
|
EsHandleClose(desktop.installedApplications[i]->singleProcessHandle);
|
|
desktop.installedApplications[i]->singleProcessHandle = ES_INVALID_HANDLE;
|
|
desktop.installedApplications[i]->singleInstance = nullptr;
|
|
desktop.installedApplications[i]->openInstanceCount = 0;
|
|
ApplicationTemporaryDestroy(desktop.installedApplications[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
EsProcessTerminate(processHandle, 1);
|
|
EsHandleClose(processHandle);
|
|
}
|
|
|
|
InstalledApplication *ApplicationFindByPID(EsObjectID pid) {
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->processID == pid) {
|
|
return instance->application;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ApplicationProcessTerminated(EsObjectID pid) {
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->processID != pid) {
|
|
continue;
|
|
}
|
|
|
|
EmbeddedWindowDestroyed(instance->embeddedWindowID);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
if (desktop.installedApplications[i]->useSingleProcess && desktop.installedApplications[i]->singleProcessHandle
|
|
&& EsProcessGetID(desktop.installedApplications[i]->singleProcessHandle) == pid) {
|
|
EsHandleClose(desktop.installedApplications[i]->singleProcessHandle);
|
|
desktop.installedApplications[i]->singleProcessHandle = ES_INVALID_HANDLE;
|
|
desktop.installedApplications[i]->singleInstance = nullptr;
|
|
desktop.installedApplications[i]->openInstanceCount = 0;
|
|
ApplicationTemporaryDestroy(desktop.installedApplications[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) {
|
|
OpenDocument *document = &desktop.openDocuments[i];
|
|
|
|
if (document->currentWriter == pid) {
|
|
document->currentWriter = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Document management:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void OpenDocumentCloseReference(EsObjectID id) {
|
|
OpenDocument *document = desktop.openDocuments.Get(&id);
|
|
EsAssert(document->referenceCount && document->referenceCount < 0x10000000 /* sanity check */);
|
|
document->referenceCount--;
|
|
if (document->referenceCount) return;
|
|
EsHeapFree(document->path);
|
|
EsHeapFree(document->temporarySavePath);
|
|
EsHandleClose(document->readHandle);
|
|
desktop.openDocuments.Delete(&id);
|
|
}
|
|
|
|
void OpenDocumentOpenReference(EsObjectID id) {
|
|
OpenDocument *document = desktop.openDocuments.Get(&id);
|
|
EsAssert(document->referenceCount && document->referenceCount < 0x10000000 /* sanity check */);
|
|
document->referenceCount++;
|
|
}
|
|
|
|
void OpenDocumentWithApplication(EsApplicationStartupInformation *startupInformation) {
|
|
bool foundDocument = false;
|
|
|
|
for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) {
|
|
OpenDocument *document = &desktop.openDocuments[i];
|
|
|
|
if (document->pathBytes == (size_t) startupInformation->filePathBytes
|
|
&& 0 == EsMemoryCompare(document->path, startupInformation->filePath, document->pathBytes)) {
|
|
foundDocument = true;
|
|
startupInformation->readHandle = document->readHandle;
|
|
startupInformation->documentID = document->id;
|
|
document->referenceCount++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundDocument) {
|
|
EsFileInformation file = EsFileOpen(startupInformation->filePath, startupInformation->filePathBytes,
|
|
ES_FILE_READ_SHARED | ES_NODE_FAIL_IF_NOT_FOUND);
|
|
|
|
if (file.error != ES_SUCCESS) {
|
|
// TODO Report error?
|
|
return;
|
|
}
|
|
|
|
OpenDocument document = {};
|
|
document.path = (char *) EsHeapAllocate(startupInformation->filePathBytes, false);
|
|
document.pathBytes = startupInformation->filePathBytes;
|
|
document.readHandle = file.handle;
|
|
document.id = ++desktop.currentDocumentID;
|
|
document.referenceCount = 1;
|
|
EsMemoryCopy(document.path, startupInformation->filePath, startupInformation->filePathBytes);
|
|
*desktop.openDocuments.Put(&document.id) = document;
|
|
|
|
startupInformation->readHandle = document.readHandle;
|
|
startupInformation->documentID = document.id;
|
|
}
|
|
|
|
ApplicationInstanceCreate(startupInformation->id, startupInformation, nullptr);
|
|
OpenDocumentCloseReference(startupInformation->documentID);
|
|
}
|
|
|
|
EsError TemporaryFileCreate(EsHandle *handle, char **path, size_t *pathBytes, uint32_t additionalFlags) {
|
|
char temporaryFileName[32];
|
|
|
|
for (uintptr_t i = 0; i < sizeof(temporaryFileName); i++) {
|
|
temporaryFileName[i] = (EsRandomU8() % 26) + 'a';
|
|
}
|
|
|
|
size_t temporaryFolderBytes;
|
|
char *temporaryFolder = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("temporary_path"), &temporaryFolderBytes);
|
|
char *temporaryFilePath = (char *) EsHeapAllocate(temporaryFolderBytes + 1 + sizeof(temporaryFileName), false);
|
|
size_t temporaryFilePathBytes = EsStringFormat(temporaryFilePath, ES_STRING_FORMAT_ENOUGH_SPACE, "%s/%s",
|
|
temporaryFolderBytes, temporaryFolder, sizeof(temporaryFileName), temporaryFileName);
|
|
|
|
EsFileInformation file = EsFileOpen(temporaryFilePath, temporaryFilePathBytes,
|
|
ES_NODE_FAIL_IF_FOUND | ES_NODE_CREATE_DIRECTORIES | additionalFlags);
|
|
|
|
EsHeapFree(temporaryFolder);
|
|
|
|
if (file.error != ES_SUCCESS) {
|
|
EsHeapFree(temporaryFilePath);
|
|
} else {
|
|
*path = temporaryFilePath;
|
|
*pathBytes = temporaryFilePathBytes;
|
|
*handle = file.handle;
|
|
}
|
|
|
|
return file.error;
|
|
}
|
|
|
|
void ApplicationInstanceRequestSave(ApplicationInstance *instance, const char *newName, size_t newNameBytes) {
|
|
if (!instance->processHandle) return;
|
|
|
|
EsMessage m = {};
|
|
m.type = ES_MSG_INSTANCE_SAVE_RESPONSE;
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
|
|
if (!instance->documentID) {
|
|
size_t folderBytes;
|
|
char *folder = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("default_user_documents_path"), &folderBytes);
|
|
char *name = (char *) EsHeapAllocate(folderBytes + newNameBytes + 32, false);
|
|
EsMemoryCopy(name, folder, folderBytes);
|
|
EsMemoryCopy(name + folderBytes, newName, newNameBytes);
|
|
EsHeapFree(folder);
|
|
size_t nameBytes = EsPathFindUniqueName(name, folderBytes + newNameBytes, folderBytes + newNameBytes + 32);
|
|
|
|
if (!nameBytes) {
|
|
EsHeapFree(name);
|
|
m.tabOperation.error = ES_ERROR_FILE_ALREADY_EXISTS;
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
return;
|
|
}
|
|
|
|
EsFileInformation file = EsFileOpen(name, nameBytes, ES_FILE_READ_SHARED | ES_NODE_FAIL_IF_FOUND);
|
|
|
|
if (file.error != ES_SUCCESS) {
|
|
EsHeapFree(name);
|
|
m.tabOperation.error = file.error;
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
return;
|
|
}
|
|
|
|
OpenDocument document = {};
|
|
document.path = name;
|
|
document.pathBytes = nameBytes;
|
|
document.readHandle = file.handle;
|
|
document.id = ++desktop.currentDocumentID;
|
|
*desktop.openDocuments.Put(&document.id) = document;
|
|
|
|
instance->documentID = document.id;
|
|
|
|
{
|
|
// Tell the instance the chosen name for the document.
|
|
|
|
uintptr_t nameOffset = 0;
|
|
|
|
for (uintptr_t i = 0; i < nameBytes; i++) {
|
|
if (name[i] == '/') {
|
|
nameOffset = i + 1;
|
|
}
|
|
}
|
|
|
|
EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_RENAMED };
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
m.tabOperation.handle = EsConstantBufferCreate(name + nameOffset, nameBytes - nameOffset, instance->processHandle);
|
|
m.tabOperation.bytes = nameBytes - nameOffset;
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
}
|
|
}
|
|
|
|
OpenDocument *document = desktop.openDocuments.Get(&instance->documentID);
|
|
|
|
if (!document) {
|
|
return;
|
|
}
|
|
|
|
if (document->currentWriter) {
|
|
m.tabOperation.error = ES_ERROR_FILE_CANNOT_GET_EXCLUSIVE_USE;
|
|
} else {
|
|
EsHeapFree(document->temporarySavePath);
|
|
document->temporarySavePath = nullptr;
|
|
|
|
EsHandle fileHandle;
|
|
m.tabOperation.error = TemporaryFileCreate(&fileHandle, &document->temporarySavePath, &document->temporarySavePathBytes, ES_FILE_WRITE);
|
|
|
|
if (m.tabOperation.error == ES_SUCCESS) {
|
|
document->currentWriter = instance->embeddedWindowID;
|
|
m.tabOperation.handle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, fileHandle, instance->processHandle, 0, 0);
|
|
EsHandleClose(fileHandle);
|
|
}
|
|
}
|
|
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
}
|
|
|
|
void InstanceAnnouncePathMoved(InstalledApplication *fromApplication, const uint8_t *buffer, size_t embedWindowMessageBytes) {
|
|
// TODO Update the location of installed applications and other things in the configuration.
|
|
// TODO Replace fromApplication with something better.
|
|
|
|
uintptr_t oldPathBytes, newPathBytes;
|
|
EsMemoryCopy(&oldPathBytes, buffer + 1, sizeof(uintptr_t));
|
|
EsMemoryCopy(&newPathBytes, buffer + 1 + sizeof(uintptr_t), sizeof(uintptr_t));
|
|
|
|
if (oldPathBytes >= 0x4000 || newPathBytes >= 0x4000
|
|
|| oldPathBytes + newPathBytes + sizeof(uintptr_t) * 2 + 1 != embedWindowMessageBytes) {
|
|
return;
|
|
}
|
|
|
|
const char *oldPath = (const char *) buffer + 1 + sizeof(uintptr_t) * 2;
|
|
const char *newPath = (const char *) buffer + 1 + sizeof(uintptr_t) * 2 + oldPathBytes;
|
|
|
|
EsObjectID documentID = 0;
|
|
|
|
for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) {
|
|
OpenDocument *document = &desktop.openDocuments[i];
|
|
|
|
if (document->pathBytes >= oldPathBytes
|
|
&& 0 == EsMemoryCompare(document->path, oldPath, oldPathBytes)
|
|
&& (oldPathBytes == document->pathBytes || document->path[oldPathBytes] == '/')) {
|
|
if (document->pathBytes == oldPathBytes) documentID = document->id;
|
|
char *newDocumentPath = (char *) EsHeapAllocate(document->pathBytes - oldPathBytes + newPathBytes, false);
|
|
EsMemoryCopy(newDocumentPath, newPath, newPathBytes);
|
|
EsMemoryCopy(newDocumentPath + newPathBytes, document->path + oldPathBytes, document->pathBytes - oldPathBytes);
|
|
document->pathBytes += newPathBytes - oldPathBytes;
|
|
EsHeapFree(document->path);
|
|
document->path = newDocumentPath;
|
|
}
|
|
}
|
|
|
|
if (!documentID) {
|
|
return;
|
|
}
|
|
|
|
uintptr_t newNameOffset = 0;
|
|
|
|
for (uintptr_t i = 0; i < newPathBytes; i++) {
|
|
if (newPath[i] == '/') {
|
|
newNameOffset = i + 1;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->documentID != documentID) continue;
|
|
if (instance->application == fromApplication) continue;
|
|
if (!instance->processHandle) continue;
|
|
|
|
EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_RENAMED };
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
m.tabOperation.handle = EsConstantBufferCreate(newPath + newNameOffset, newPathBytes - newNameOffset, instance->processHandle);
|
|
m.tabOperation.bytes = newPathBytes - newNameOffset;
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
}
|
|
}
|
|
|
|
void ApplicationInstanceCompleteSave(ApplicationInstance *fromInstance) {
|
|
OpenDocument *document = desktop.openDocuments.Get(&fromInstance->documentID);
|
|
|
|
if (!document || fromInstance->embeddedWindowID != document->currentWriter) {
|
|
return;
|
|
}
|
|
|
|
// Move the temporary file to its target destination.
|
|
// TODO Handling errors.
|
|
// TODO What should happen if the old file is deleted, but the new file isn't moved?
|
|
|
|
EsPathDelete(document->path, document->pathBytes);
|
|
EsPathMove(document->temporarySavePath, document->temporarySavePathBytes, document->path, document->pathBytes, ES_PATH_MOVE_ALLOW_COPY_AND_DELETE);
|
|
|
|
// Re-open the read handle.
|
|
|
|
EsFileInformation file = EsFileOpen(document->path, document->pathBytes, ES_FILE_READ_SHARED | ES_NODE_FAIL_IF_NOT_FOUND);
|
|
|
|
if (file.error != ES_SUCCESS) {
|
|
// TODO What now?
|
|
} else {
|
|
EsHandleClose(document->readHandle);
|
|
document->readHandle = file.handle;
|
|
}
|
|
|
|
document->currentWriter = 0;
|
|
|
|
if (desktop.fileManager->singleProcessHandle) {
|
|
EsMessage m = {};
|
|
m.type = ES_MSG_FILE_MANAGER_FILE_MODIFIED;
|
|
m.user.context1 = EsConstantBufferCreate(document->path, document->pathBytes, desktop.fileManager->singleProcessHandle);
|
|
m.user.context2 = document->pathBytes;
|
|
EsMessagePostRemote(desktop.fileManager->singleProcessHandle, &m);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->documentID != document->id) continue;
|
|
if (!instance->processHandle) continue;
|
|
|
|
EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_UPDATED };
|
|
m.tabOperation.isSource = instance == fromInstance;
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
m.tabOperation.handle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, document->readHandle, instance->processHandle, 0, 0);
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Configuration file management:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void ConfigurationLoadApplications() {
|
|
// Add applications provided by Desktop.
|
|
|
|
{
|
|
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
|
|
application->id = APPLICATION_ID_DESKTOP_BLANK_TAB;
|
|
application->hidden = true;
|
|
application->createInstance = InstanceBlankTabCreate;
|
|
desktop.installedApplications.Add(application);
|
|
}
|
|
|
|
{
|
|
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
|
|
application->cName = (char *) interfaceString_DesktopSettingsApplication;
|
|
application->id = APPLICATION_ID_DESKTOP_SETTINGS;
|
|
application->iconID = ES_ICON_PREFERENCES_DESKTOP;
|
|
application->createInstance = InstanceSettingsCreate;
|
|
application->useSingleInstance = true;
|
|
desktop.installedApplications.Add(application);
|
|
}
|
|
|
|
{
|
|
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
|
|
application->id = APPLICATION_ID_DESKTOP_CRASHED;
|
|
application->hidden = true;
|
|
application->createInstance = InstanceCrashedTabCreate;
|
|
desktop.installedApplications.Add(application);
|
|
}
|
|
|
|
EsMutexAcquire(&api.systemConfigurationMutex);
|
|
|
|
for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) {
|
|
// Load information about installed applications.
|
|
|
|
EsSystemConfigurationGroup *group = &api.systemConfigurationGroups[i];
|
|
|
|
if (0 != EsStringCompareRaw(group->sectionClass, group->sectionClassBytes, EsLiteral("application"))) {
|
|
continue;
|
|
}
|
|
|
|
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
|
|
|
|
application->cName = EsSystemConfigurationGroupReadString(group, EsLiteral("name"));
|
|
application->cExecutable = EsSystemConfigurationGroupReadString(group, EsLiteral("executable"));
|
|
application->settingsPath = EsSystemConfigurationGroupReadString(group, EsLiteral("settings_path"), &application->settingsPathBytes);
|
|
char *icon = EsSystemConfigurationGroupReadString(group, EsLiteral("icon"));
|
|
application->iconID = EsIconIDFromString(icon);
|
|
EsHeapFree(icon);
|
|
application->useSingleProcess = EsSystemConfigurationGroupReadInteger(group, EsLiteral("use_single_process"), false);
|
|
application->useSingleInstance = EsSystemConfigurationGroupReadInteger(group, EsLiteral("use_single_instance"), false);
|
|
application->hidden = EsSystemConfigurationGroupReadInteger(group, EsLiteral("hidden"), false);
|
|
application->id = EsIntegerParse(group->section, group->sectionBytes);
|
|
|
|
#define READ_PERMISSION(x, y) if (EsSystemConfigurationGroupReadInteger(group, EsLiteral(x), 0)) application->permissions |= y
|
|
READ_PERMISSION("permission_all_files", APPLICATION_PERMISSION_ALL_FILES);
|
|
READ_PERMISSION("permission_all_devices", APPLICATION_PERMISSION_ALL_DEVICES);
|
|
READ_PERMISSION("permission_manage_processes", APPLICATION_PERMISSION_MANAGE_PROCESSES);
|
|
READ_PERMISSION("permission_posix_subsystem", APPLICATION_PERMISSION_POSIX_SUBSYSTEM);
|
|
READ_PERMISSION("permission_run_temporary_application", APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION);
|
|
READ_PERMISSION("permission_shutdown", APPLICATION_PERMISSION_SHUTDOWN);
|
|
READ_PERMISSION("permission_view_file_types", APPLICATION_PERMISSION_VIEW_FILE_TYPES);
|
|
|
|
desktop.installedApplications.Add(application);
|
|
|
|
if (EsSystemConfigurationGroupReadInteger(group, EsLiteral("is_file_manager"))) {
|
|
desktop.fileManager = application;
|
|
}
|
|
}
|
|
|
|
EsMutexRelease(&api.systemConfigurationMutex);
|
|
|
|
EsSort(desktop.installedApplications.array, desktop.installedApplications.Length(),
|
|
sizeof(InstalledApplication *), [] (const void *_left, const void *_right, EsGeneric) {
|
|
InstalledApplication *left = *(InstalledApplication **) _left;
|
|
InstalledApplication *right = *(InstalledApplication **) _right;
|
|
return EsStringCompare(left->cName, EsCStringLength(left->cName), right->cName, EsCStringLength(right->cName));
|
|
}, 0);
|
|
}
|
|
|
|
void ConfigurationWriteSectionsToBuffer(const char *sectionClass, const char *section, bool includeComments, EsBuffer *pipe) {
|
|
char buffer[4096];
|
|
EsMutexAcquire(&api.systemConfigurationMutex);
|
|
|
|
for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) {
|
|
EsSystemConfigurationGroup *group = &api.systemConfigurationGroups[i];
|
|
|
|
if ((sectionClass && EsStringCompareRaw(group->sectionClass, group->sectionClassBytes, sectionClass, -1))
|
|
|| (section && EsStringCompareRaw(group->section, group->sectionBytes, section, -1))) {
|
|
continue;
|
|
}
|
|
|
|
EsINIState s = {};
|
|
s.sectionClass = group->sectionClass, s.sectionClassBytes = group->sectionClassBytes;
|
|
s.section = group->section, s.sectionBytes = group->sectionBytes;
|
|
size_t bytes = EsINIFormat(&s, buffer, sizeof(buffer));
|
|
EsBufferWrite(pipe, buffer, bytes);
|
|
|
|
for (uintptr_t i = 0; i < group->itemCount; i++) {
|
|
EsSystemConfigurationItem *item = group->items + i;
|
|
|
|
if ((!item->keyBytes || item->key[0] == ';') && !includeComments) {
|
|
continue;
|
|
}
|
|
|
|
s.key = item->key, s.keyBytes = item->keyBytes;
|
|
s.value = item->value, s.valueBytes = item->valueBytes;
|
|
size_t bytes = EsINIFormat(&s, buffer, sizeof(buffer));
|
|
EsBufferWrite(pipe, buffer, bytes);
|
|
}
|
|
}
|
|
|
|
EsMutexRelease(&api.systemConfigurationMutex);
|
|
}
|
|
|
|
void ConfigurationWriteToFile() {
|
|
if (!desktop.configurationModified) {
|
|
return;
|
|
}
|
|
|
|
EsBuffer buffer = { .canGrow = true };
|
|
ConfigurationWriteSectionsToBuffer(nullptr, nullptr, true /* include comments */, &buffer);
|
|
|
|
if (!buffer.error) {
|
|
if (ES_SUCCESS == EsFileWriteAll(EsLiteral(K_SYSTEM_CONFIGURATION "_"), buffer.out, buffer.position)) {
|
|
// TODO Atomic delete and move.
|
|
if (ES_SUCCESS == EsPathDelete(EsLiteral(K_SYSTEM_CONFIGURATION))) {
|
|
if (ES_SUCCESS == EsPathMove(EsLiteral(K_SYSTEM_CONFIGURATION "_"), EsLiteral(K_SYSTEM_CONFIGURATION))) {
|
|
EsPrint("ConfigurationWriteToFile - New configuration successfully written.\n");
|
|
desktop.configurationModified = true;
|
|
} else {
|
|
EsPrint("ConfigurationWriteToFile - Error while moving to final path.\n");
|
|
}
|
|
} else {
|
|
EsPrint("ConfigurationWriteToFile - Error while deleting old file.\n");
|
|
}
|
|
} else {
|
|
EsPrint("ConfigurationWriteToFile - Error while writing to file.\n");
|
|
}
|
|
} else {
|
|
EsPrint("ConfigurationWriteToFile - Error while writing to buffer.\n");
|
|
}
|
|
|
|
EsHeapFree(buffer.out);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Image utilities:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void WallpaperLoad(EsGeneric) {
|
|
size_t pathBytes;
|
|
char *path = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("wallpaper"), &pathBytes);
|
|
|
|
if (path) {
|
|
void *buffer = EsHeapAllocate(desktop.wallpaperWindow->windowWidth * desktop.wallpaperWindow->windowHeight * 4, false);
|
|
LoadImage(path, pathBytes, buffer, desktop.wallpaperWindow->windowWidth, desktop.wallpaperWindow->windowHeight, false);
|
|
EsHeapFree(path);
|
|
|
|
EsRectangle region = ES_RECT_2S(desktop.wallpaperWindow->windowWidth, desktop.wallpaperWindow->windowHeight);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_BITS, desktop.wallpaperWindow->handle, (uintptr_t) ®ion, (uintptr_t) buffer, 0);
|
|
EsSyscall(ES_SYSCALL_SCREEN_FORCE_UPDATE, true, 0, 0, 0);
|
|
|
|
// Work out the most common hue on the wallpaper, and set the system hue.
|
|
|
|
uint32_t hueBuckets[36] = {};
|
|
uint32_t hueSelected = 0;
|
|
|
|
for (uintptr_t i = 0; i < desktop.wallpaperWindow->windowWidth * desktop.wallpaperWindow->windowHeight; i += 7) {
|
|
float h, s, v;
|
|
EsColorConvertToHSV(((uint32_t *) buffer)[i], &h, &s, &v);
|
|
uintptr_t bucket = (uintptr_t) (h * 6);
|
|
hueBuckets[bucket] += 2;
|
|
hueBuckets[bucket == 35 ? 0 : (bucket + 1)] += 1;
|
|
hueBuckets[bucket == 0 ? 35 : (bucket - 1)] += 1;
|
|
}
|
|
|
|
for (uintptr_t i = 1; i < 36; i++) {
|
|
if (hueBuckets[i] > hueBuckets[hueSelected]) {
|
|
hueSelected = i;
|
|
}
|
|
}
|
|
|
|
theming.systemHue = hueSelected / 6.0f;
|
|
if (theming.systemHue < 0) theming.systemHue += 6.0f;
|
|
|
|
EsHeapFree(buffer);
|
|
|
|
// Tell all container windows to redraw with the new system hue.
|
|
|
|
EsMessageMutexAcquire();
|
|
|
|
for (uintptr_t i = 0; i < gui.allWindows.Length(); i++) {
|
|
if (gui.allWindows[i]->windowStyle == ES_WINDOW_CONTAINER) {
|
|
gui.allWindows[i]->Repaint(true);
|
|
UIWindowNeedsUpdate(gui.allWindows[i]);
|
|
}
|
|
}
|
|
|
|
EsMessageMutexRelease();
|
|
}
|
|
|
|
// TODO Fade wallpaper in.
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// General Desktop:
|
|
//////////////////////////////////////////////////////
|
|
|
|
ApplicationInstance *ApplicationInstanceFindForeground() {
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
WindowTab *tab = instance->tab;
|
|
|
|
if (tab && (tab->container->taskBarButton->customStyleState & THEME_STATE_SELECTED) && tab->container->active == instance->tab) {
|
|
return instance;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void CheckForegroundWindowResponding(EsGeneric) {
|
|
ApplicationInstance *instance = ApplicationInstanceFindForeground();
|
|
EsTimerSet(2500, CheckForegroundWindowResponding, 0);
|
|
if (!instance) return;
|
|
|
|
WindowTab *tab = instance->tab;
|
|
|
|
EsProcessState state;
|
|
EsProcessGetState(instance->processHandle, &state);
|
|
|
|
if (state.flags & ES_PROCESS_STATE_PINGED) {
|
|
if (tab->notRespondingInstance) {
|
|
// The tab is already not responding.
|
|
} else {
|
|
// The tab has just stopped not responding.
|
|
EsApplicationStartupInformation startupInformation = { .data = CRASHED_TAB_NOT_RESPONDING };
|
|
tab->notRespondingInstance = ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_CRASHED,
|
|
&startupInformation, tab->container, true /* hidden */);
|
|
WindowTabActivate(tab, true);
|
|
}
|
|
} else {
|
|
if (tab->notRespondingInstance) {
|
|
// The tab has started responding.
|
|
ApplicationInstanceClose(tab->notRespondingInstance);
|
|
tab->notRespondingInstance = nullptr;
|
|
WindowTabActivate(tab, true);
|
|
} else {
|
|
// Check if the tab is responding.
|
|
EsMessage m;
|
|
EsMemoryZero(&m, sizeof(EsMessage));
|
|
m.type = ES_MSG_PING;
|
|
EsMessagePostRemote(instance->processHandle, &m);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DesktopSetup() {
|
|
if (!desktop.setupDesktopUIComplete) {
|
|
// Get the installation state.
|
|
desktop.installationState = EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("installation_state"));
|
|
}
|
|
|
|
if (!desktop.setupDesktopUIComplete) {
|
|
// Load the theme bitmap.
|
|
|
|
EsHandle handle = EsMemoryOpen(ES_THEME_CURSORS_WIDTH * ES_THEME_CURSORS_HEIGHT * 4, EsLiteral(ES_THEME_CURSORS_NAME), ES_FLAGS_DEFAULT);
|
|
void *destination = EsObjectMap(handle, 0, ES_THEME_CURSORS_WIDTH * ES_THEME_CURSORS_HEIGHT * 4, ES_MAP_OBJECT_READ_WRITE);
|
|
LoadImage(theming.system.in + theming.system.bytes - theming.header->bitmapBytes, theming.header->bitmapBytes,
|
|
destination, ES_THEME_CURSORS_WIDTH, ES_THEME_CURSORS_HEIGHT, true);
|
|
EsObjectUnmap(destination);
|
|
EsHandleClose(handle);
|
|
}
|
|
|
|
{
|
|
// Create the wallpaper window.
|
|
|
|
if (!desktop.wallpaperWindow) desktop.wallpaperWindow = EsWindowCreate(nullptr, ES_WINDOW_PLAIN);
|
|
EsRectangle screen;
|
|
EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0);
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, desktop.wallpaperWindow->handle, (uintptr_t) &screen, 0, ES_WINDOW_MOVE_AT_BOTTOM);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, desktop.wallpaperWindow->handle, (uintptr_t) &screen, 0, ES_WINDOW_PROPERTY_OPAQUE_BOUNDS);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, desktop.wallpaperWindow->handle,
|
|
ES_WINDOW_SOLID_TRUE | ES_WINDOW_SOLID_NO_BRING_TO_FRONT, 0, ES_WINDOW_PROPERTY_SOLID);
|
|
desktop.wallpaperWindow->doNotPaint = true;
|
|
desktop.wallpaperWindow->messageUser = ProcessGlobalKeyboardShortcuts;
|
|
|
|
if ((int32_t) desktop.wallpaperWindow->windowWidth != Width(screen) || (int32_t) desktop.wallpaperWindow->windowHeight != Height(screen)) {
|
|
desktop.wallpaperWindow->windowWidth = Width(screen);
|
|
desktop.wallpaperWindow->windowHeight = Height(screen);
|
|
EsThreadCreate(WallpaperLoad, nullptr, 0);
|
|
}
|
|
}
|
|
|
|
if (desktop.installationState == INSTALLATION_STATE_NONE) {
|
|
// Create the taskbar.
|
|
|
|
EsWindow *window = desktop.setupDesktopUIComplete ? desktop.taskBar.window : EsWindowCreate(nullptr, ES_WINDOW_PLAIN);
|
|
window->messageUser = TaskBarWindowMessage;
|
|
window->appearActivated = true;
|
|
window->StartAnimating();
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE | ES_WINDOW_SOLID_NO_ACTIVATE, 0, ES_WINDOW_PROPERTY_SOLID);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, BLEND_WINDOW_MATERIAL_GLASS, 0, ES_WINDOW_PROPERTY_MATERIAL);
|
|
|
|
if (!desktop.setupDesktopUIComplete) desktop.taskBar.Initialise(window, ES_CELL_FILL, TaskBarMessage, ES_STYLE_TASK_BAR_BAR);
|
|
desktop.taskBar.cName = "task bar";
|
|
EsThemeMetrics metrics = EsElementGetMetrics(&desktop.taskBar);
|
|
window->userData = &desktop.taskBar;
|
|
|
|
EsRectangle screen;
|
|
EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0);
|
|
|
|
EsRectangle bounds = screen;
|
|
bounds.t = bounds.b - metrics.preferredHeight;
|
|
desktop.taskBar.targetBounds = bounds;
|
|
|
|
screen.b = bounds.t;
|
|
EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_SET, 0, (uintptr_t) &screen, 0, 0);
|
|
|
|
bounds.r -= bounds.l, bounds.b -= bounds.t;
|
|
bounds.l = bounds.t = 0;
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_PROPERTY_BLUR_BOUNDS);
|
|
|
|
if (!desktop.setupDesktopUIComplete) {
|
|
EsPanel *panel = EsPanelCreate(&desktop.taskBar, ES_PANEL_HORIZONTAL | ES_CELL_FILL, {});
|
|
|
|
EsButton *newWindowButton = EsButtonCreate(panel, ES_FLAGS_DEFAULT, ES_STYLE_TASK_BAR_NEW_WINDOW);
|
|
|
|
EsButtonOnCommand(newWindowButton, [] (EsInstance *, EsElement *, EsCommand *) {
|
|
ApplicationInstanceCreate(APPLICATION_ID_DESKTOP_BLANK_TAB, nullptr, nullptr);
|
|
});
|
|
|
|
desktop.taskBar.taskList.Initialise(panel, ES_CELL_FILL, ReorderListMessage, nullptr);
|
|
desktop.taskBar.taskList.cName = "task list";
|
|
|
|
desktop.tasksButton = EsButtonCreate(panel, ES_ELEMENT_HIDDEN, ES_STYLE_TASK_BAR_BUTTON);
|
|
desktop.tasksButton->messageUser = TaskBarTasksButtonMessage;
|
|
|
|
EsButton *shutdownButton = EsButtonCreate(panel, ES_FLAGS_DEFAULT, ES_STYLE_TASK_BAR_EXTRA);
|
|
EsButtonSetIcon(shutdownButton, ES_ICON_SYSTEM_SHUTDOWN_SYMBOLIC);
|
|
|
|
EsButtonOnCommand(shutdownButton, [] (EsInstance *, EsElement *, EsCommand *) {
|
|
ShutdownModalCreate();
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!desktop.setupDesktopUIComplete) {
|
|
// Launch the first application.
|
|
|
|
char *firstApplication = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("first_application"));
|
|
|
|
if (firstApplication && firstApplication[0]) {
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
if (desktop.installedApplications[i]->cName && 0 == EsCRTstrcmp(desktop.installedApplications[i]->cName, firstApplication)) {
|
|
ApplicationInstanceCreate(desktop.installedApplications[i]->id, nullptr, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
EsHeapFree(firstApplication);
|
|
}
|
|
|
|
#ifdef CHECK_FOR_NOT_RESPONDING
|
|
if (!desktop.setupDesktopUIComplete) {
|
|
// Setup the timer callback to check if the foreground window is responding.
|
|
EsTimerSet(2500, CheckForegroundWindowResponding, 0);
|
|
}
|
|
#endif
|
|
|
|
if (desktop.setupDesktopUIComplete) {
|
|
} else if (desktop.installationState == INSTALLATION_STATE_NONE) {
|
|
#if 0
|
|
// Play the startup sound.
|
|
|
|
EsThreadCreate([] (EsGeneric) {
|
|
size_t pathBytes;
|
|
char *path = EsSystemConfigurationReadString(EsLiteral("general"), EsLiteral("startup_sound"), &pathBytes);
|
|
|
|
if (path) {
|
|
PlaySound(path, pathBytes);
|
|
EsHeapFree(path);
|
|
}
|
|
}, nullptr, 0);
|
|
#endif
|
|
} else if (desktop.installationState == INSTALLATION_STATE_INSTALLER) {
|
|
// Start the installer.
|
|
|
|
EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_PLAIN);
|
|
|
|
EsRectangle screen;
|
|
EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0);
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &screen, 0, ES_WINDOW_MOVE_ALWAYS_ON_TOP);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, ES_WINDOW_SOLID_TRUE, 0, ES_WINDOW_PROPERTY_SOLID);
|
|
|
|
EsPanel *root = EsPanelCreate(window, ES_PANEL_VERTICAL | ES_CELL_PUSH | ES_CELL_CENTER, ES_STYLE_INSTALLER_ROOT);
|
|
EsTextDisplayCreate(root, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING0, EsLiteral("Essence Installation"));
|
|
|
|
// TODO.
|
|
}
|
|
|
|
desktop.setupDesktopUIComplete = true;
|
|
}
|
|
|
|
void DesktopSyscall(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) {
|
|
ApplicationInstance *instance = ApplicationInstanceFindByWindowID(message->desktop.windowID);
|
|
|
|
if (buffer[0] == DESKTOP_MSG_START_APPLICATION) {
|
|
EsApplicationStartupInformation *information = ApplicationStartupInformationParse(buffer + 1, message->desktop.bytes - 1);
|
|
if (information) OpenDocumentWithApplication(information);
|
|
} else if (buffer[0] == DESKTOP_MSG_CREATE_CLIPBOARD_FILE && pipe) {
|
|
EsHandle processHandle = EsProcessOpen(message->desktop.processID);
|
|
|
|
if (processHandle) {
|
|
EsHandle handle;
|
|
char *path;
|
|
size_t pathBytes;
|
|
EsError error = TemporaryFileCreate(&handle, &path, &pathBytes, ES_FILE_WRITE);
|
|
|
|
if (error == ES_SUCCESS) {
|
|
if (desktop.nextClipboardFile) {
|
|
EsHandleClose(desktop.nextClipboardFile);
|
|
}
|
|
|
|
desktop.nextClipboardFile = handle;
|
|
desktop.nextClipboardProcessID = message->desktop.processID;
|
|
|
|
handle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, handle, processHandle, 0, 0);
|
|
|
|
EsHeapFree(path);
|
|
} else {
|
|
handle = ES_INVALID_HANDLE;
|
|
}
|
|
|
|
EsBufferWrite(pipe, &handle, sizeof(handle));
|
|
EsBufferWrite(pipe, &error, sizeof(error));
|
|
|
|
EsHandleClose(processHandle);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_CLIPBOARD_PUT && message->desktop.bytes == sizeof(ClipboardInformation)
|
|
&& desktop.nextClipboardFile && desktop.nextClipboardProcessID == message->desktop.processID) {
|
|
ClipboardInformation *information = (ClipboardInformation *) buffer;
|
|
|
|
if (information->error == ES_SUCCESS) {
|
|
if (desktop.clipboardFile) {
|
|
EsFileDelete(desktop.clipboardFile);
|
|
EsHandleClose(desktop.clipboardFile);
|
|
}
|
|
|
|
desktop.clipboardFile = desktop.nextClipboardFile;
|
|
desktop.clipboardInformation = *information;
|
|
|
|
ApplicationInstance *foreground = ApplicationInstanceFindForeground();
|
|
|
|
if (foreground && foreground->processHandle) {
|
|
EsMessage m = { ES_MSG_PRIMARY_CLIPBOARD_UPDATED };
|
|
m.tabOperation.id = foreground->embeddedWindowID;
|
|
EsMessagePostRemote(foreground->processHandle, &m);
|
|
}
|
|
} else {
|
|
EsHandleClose(desktop.nextClipboardFile);
|
|
}
|
|
|
|
desktop.nextClipboardFile = ES_INVALID_HANDLE;
|
|
desktop.nextClipboardProcessID = 0;
|
|
} else if (buffer[0] == DESKTOP_MSG_CLIPBOARD_GET && pipe) {
|
|
EsHandle processHandle = EsProcessOpen(message->desktop.processID);
|
|
|
|
if (processHandle) {
|
|
EsHandle fileHandle = desktop.clipboardFile
|
|
? EsSyscall(ES_SYSCALL_HANDLE_SHARE, desktop.clipboardFile, processHandle, 1 /* ES_FILE_READ_SHARED */, 0) : ES_INVALID_HANDLE;
|
|
EsBufferWrite(pipe, &desktop.clipboardInformation, sizeof(desktop.clipboardInformation));
|
|
EsBufferWrite(pipe, &fileHandle, sizeof(fileHandle));
|
|
EsHandleClose(processHandle);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_SYSTEM_CONFIGURATION_GET && pipe) {
|
|
ConfigurationWriteSectionsToBuffer("font", nullptr, false, pipe);
|
|
ConfigurationWriteSectionsToBuffer(nullptr, "ui", false, pipe);
|
|
} else if (buffer[0] == DESKTOP_MSG_REQUEST_SHUTDOWN) {
|
|
InstalledApplication *application = ApplicationFindByPID(message->desktop.processID);
|
|
|
|
if (application && (application->permissions & APPLICATION_PERMISSION_SHUTDOWN)) {
|
|
ShutdownModalCreate();
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_FILE_TYPES_GET && pipe) {
|
|
InstalledApplication *application = ApplicationFindByPID(message->desktop.processID);
|
|
|
|
if (application && (application->permissions & APPLICATION_PERMISSION_VIEW_FILE_TYPES)) {
|
|
ConfigurationWriteSectionsToBuffer("file_type", nullptr, false, pipe);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_ANNOUNCE_PATH_MOVED && message->desktop.bytes > 1 + sizeof(uintptr_t) * 2) {
|
|
InstalledApplication *application = ApplicationFindByPID(message->desktop.processID);
|
|
|
|
if (application && (application->permissions & APPLICATION_PERMISSION_ALL_FILES)) {
|
|
InstanceAnnouncePathMoved(application, buffer, message->desktop.bytes);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_START_USER_TASK && pipe) {
|
|
InstalledApplication *application = ApplicationFindByPID(message->desktop.processID);
|
|
|
|
if (!application) {
|
|
return;
|
|
}
|
|
|
|
// HACK User tasks use an embedded window object for IPC.
|
|
// This allows us to basically treat them like other instances.
|
|
|
|
EsHandle processHandle = EsProcessOpen(message->desktop.processID);
|
|
EsHandle windowHandle = EsSyscall(ES_SYSCALL_WINDOW_CREATE, ES_WINDOW_NORMAL, 0, 0, 0);
|
|
ApplicationInstance *instance = (ApplicationInstance *) EsHeapAllocate(sizeof(ApplicationInstance), true);
|
|
bool added = false;
|
|
|
|
if (processHandle && windowHandle && instance) {
|
|
added = desktop.allApplicationInstances.Add(instance);
|
|
}
|
|
|
|
if (!processHandle || !windowHandle || !instance || !added) {
|
|
if (processHandle) EsHandleClose(processHandle);
|
|
if (windowHandle) EsHandleClose(windowHandle);
|
|
if (instance) EsHeapFree(instance);
|
|
|
|
EsHandle invalid = ES_INVALID_HANDLE;
|
|
EsBufferWrite(pipe, &invalid, sizeof(invalid));
|
|
return;
|
|
}
|
|
|
|
instance->title[0] = ' ';
|
|
instance->titleBytes = 1;
|
|
instance->isUserTask = true;
|
|
instance->embeddedWindowHandle = windowHandle;
|
|
instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, windowHandle, 0, 0, 0);
|
|
instance->processHandle = processHandle;
|
|
instance->processID = message->desktop.processID;
|
|
instance->application = application;
|
|
|
|
if (application->singleProcessHandle) {
|
|
EsAssert(application->openInstanceCount);
|
|
application->openInstanceCount++;
|
|
}
|
|
|
|
EsHandle targetWindowHandle = EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, windowHandle, processHandle, 0, ES_WINDOW_PROPERTY_EMBED_OWNER);
|
|
EsBufferWrite(pipe, &targetWindowHandle, sizeof(targetWindowHandle));
|
|
|
|
desktop.allOngoingUserTasks.Add(instance);
|
|
TaskBarTasksButtonUpdate();
|
|
} else if (!instance) {
|
|
// -------------------------------------------------
|
|
// | Messages below here require a valid instance. |
|
|
// -------------------------------------------------
|
|
EsPrint("DesktopSyscall - Received message %d without an instance.\n", buffer[0]);
|
|
} else if (buffer[0] == DESKTOP_MSG_SET_TITLE || buffer[0] == DESKTOP_MSG_SET_ICON) {
|
|
if (buffer[0] == DESKTOP_MSG_SET_TITLE) {
|
|
instance->titleBytes = EsStringFormat(instance->title, sizeof(instance->title), "%s",
|
|
message->desktop.bytes - 1, buffer + 1);
|
|
} else {
|
|
if (message->desktop.bytes == 5) {
|
|
EsMemoryCopy(&instance->iconID, buffer + 1, sizeof(uint32_t));
|
|
}
|
|
}
|
|
|
|
if (instance->tab) {
|
|
instance->tab->Repaint(true);
|
|
|
|
if (instance->tab == instance->tab->container->active) {
|
|
instance->tab->container->taskBarButton->Repaint(true);
|
|
}
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_SET_PROGRESS && message->desktop.bytes == 1 + sizeof(double) && instance->isUserTask) {
|
|
double progress;
|
|
EsMemoryCopy(&progress, buffer + 1, sizeof(double));
|
|
|
|
if (progress >= 0.0 && progress <= 1.0) {
|
|
desktop.totalUserTaskProgress += progress - instance->progress;
|
|
instance->progress = progress;
|
|
EsElementRepaint(desktop.tasksButton);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_REQUEST_SAVE) {
|
|
ApplicationInstanceRequestSave(instance, (const char *) buffer + 1, message->desktop.bytes - 1);
|
|
} else if (buffer[0] == DESKTOP_MSG_COMPLETE_SAVE) {
|
|
ApplicationInstanceCompleteSave(instance);
|
|
} else if (buffer[0] == DESKTOP_MSG_SHOW_IN_FILE_MANAGER) {
|
|
// TODO Don't open a new instance if the folder is already open?
|
|
OpenDocument *document = desktop.openDocuments.Get(&instance->documentID);
|
|
|
|
if (document) {
|
|
EsApplicationStartupInformation startupInformation = {};
|
|
startupInformation.flags = ES_APPLICATION_STARTUP_MANUAL_PATH;
|
|
startupInformation.filePath = document->path;
|
|
startupInformation.filePathBytes = document->pathBytes;
|
|
ApplicationInstanceCreate(desktop.fileManager->id, &startupInformation, instance->tab->container);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_RUN_TEMPORARY_APPLICATION) {
|
|
if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION)) {
|
|
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
|
|
application->temporary = true;
|
|
application->hidden = true;
|
|
application->useSingleProcess = true;
|
|
application->cExecutable = (char *) EsHeapAllocate(message->desktop.bytes, false);
|
|
EsMemoryCopy(application->cExecutable, buffer + 1, message->desktop.bytes - 1);
|
|
application->cExecutable[message->desktop.bytes - 1] = 0;
|
|
static int64_t nextTemporaryID = -1;
|
|
application->id = nextTemporaryID--;
|
|
application->cName = (char *) EsHeapAllocate(32, false);
|
|
for (int i = 1; i < 31; i++) application->cName[i] = (EsRandomU8() % 26) + 'a';
|
|
application->cName[0] = '_', application->cName[31] = 0;
|
|
EsHandle handle;
|
|
EsError error = TemporaryFileCreate(&handle, &application->settingsPath, &application->settingsPathBytes, ES_NODE_DIRECTORY);
|
|
if (error == ES_SUCCESS) EsHandleClose(handle);
|
|
desktop.installedApplications.Add(application);
|
|
ApplicationInstanceCreate(application->id, nullptr, nullptr);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_UNHANDLED_KEY_EVENT && instance->tab) {
|
|
_EsMessageWithObject message;
|
|
EsSyscall(ES_SYSCALL_WINDOW_GET_EMBED_KEYBOARD, instance->tab->window->handle, (uintptr_t) &message, 0, 0);
|
|
|
|
if (message.message.type != ES_MSG_INVALID) {
|
|
UIProcessWindowManagerMessage((EsWindow *) message.object, &message.message, nullptr);
|
|
}
|
|
} else {
|
|
EsPrint("DesktopSyscall - Received unhandled message %d.\n", buffer[0]);
|
|
}
|
|
}
|
|
|
|
void EmbeddedWindowDestroyed(EsObjectID id) {
|
|
EsMenuCloseAll(); // The tab will be destroyed, but menus might be keeping pointers to it.
|
|
ApplicationInstance *instance = ApplicationInstanceFindByWindowID(id, true /* remove if found */);
|
|
if (!instance) return;
|
|
|
|
EsHandleClose(instance->embeddedWindowHandle);
|
|
if (instance->processHandle) EsHandleClose(instance->processHandle);
|
|
|
|
if (instance->documentID) {
|
|
OpenDocumentCloseReference(instance->documentID);
|
|
}
|
|
|
|
InstalledApplication *application = instance->application;
|
|
|
|
if (application && application->singleInstance) {
|
|
application->singleInstance = nullptr;
|
|
}
|
|
|
|
if (application && application->singleProcessHandle) {
|
|
EsAssert(application->openInstanceCount);
|
|
application->openInstanceCount--;
|
|
|
|
if (!application->openInstanceCount && application->useSingleProcess) {
|
|
EsMessage m = { ES_MSG_APPLICATION_EXIT };
|
|
EsMessagePostRemote(application->singleProcessHandle, &m);
|
|
EsHandleClose(application->singleProcessHandle);
|
|
application->singleProcessHandle = ES_INVALID_HANDLE;
|
|
ApplicationTemporaryDestroy(application);
|
|
application = nullptr;
|
|
}
|
|
}
|
|
|
|
if (instance->tab) {
|
|
WindowTabDestroy(instance->tab);
|
|
} else if (instance->isUserTask) {
|
|
desktop.totalUserTaskProgress -= instance->progress;
|
|
EsElementRepaint(desktop.tasksButton);
|
|
desktop.allOngoingUserTasks.FindAndDeleteSwap(instance, false /* ignore if not found */);
|
|
TaskBarTasksButtonUpdate();
|
|
}
|
|
|
|
EsHeapFree(instance);
|
|
}
|
|
|
|
int CursorLocatorMessage(EsElement *element, EsMessage *message) {
|
|
EsWindow *window = element->window;
|
|
|
|
if (message->type == ES_MSG_ANIMATE) {
|
|
window->announcementTimeMs += message->animate.deltaMs;
|
|
double progress = window->announcementTimeMs / GetConstantNumber("cursorLocatorDuration");
|
|
|
|
if (progress > 1) {
|
|
EsElementDestroy(window);
|
|
} else {
|
|
EsElementRelayout(element);
|
|
message->animate.complete = false;
|
|
return ES_HANDLED;
|
|
}
|
|
} else if (message->type == ES_MSG_LAYOUT) {
|
|
EsElement *child = element->GetChild(0);
|
|
double progress = 1.0 - window->announcementTimeMs / GetConstantNumber("cursorLocatorDuration");
|
|
int width = progress * child->GetWidth(0), height = progress * child->GetHeight(0);
|
|
child->InternalMove(width, height, (element->width - width) / 2, (element->height - height) / 2);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void DesktopMessage(EsMessage *message) {
|
|
if (message->type == ES_MSG_POWER_BUTTON_PRESSED) {
|
|
ShutdownModalCreate();
|
|
} else if (message->type == ES_MSG_EMBEDDED_WINDOW_DESTROYED) {
|
|
EmbeddedWindowDestroyed(message->desktop.windowID);
|
|
} else if (message->type == ES_MSG_DESKTOP) {
|
|
uint8_t *buffer = (uint8_t *) EsHeapAllocate(message->desktop.bytes, false);
|
|
|
|
if (buffer) {
|
|
EsConstantBufferRead(message->desktop.buffer, buffer);
|
|
EsBuffer pipe = { .canGrow = true };
|
|
DesktopSyscall(message, buffer, &pipe);
|
|
if (message->desktop.pipe) EsPipeWrite(message->desktop.pipe, pipe.out, pipe.position);
|
|
EsHeapFree(pipe.out);
|
|
EsHeapFree(buffer);
|
|
}
|
|
|
|
EsHandleClose(message->desktop.buffer);
|
|
if (message->desktop.pipe) EsHandleClose(message->desktop.pipe);
|
|
} else if (message->type == ES_MSG_APPLICATION_CRASH) {
|
|
ApplicationInstanceCrashed(message);
|
|
} else if (message->type == ES_MSG_PROCESS_TERMINATED) {
|
|
ApplicationProcessTerminated(message->crash.pid);
|
|
} else if (message->type == ES_MSG_REGISTER_FILE_SYSTEM) {
|
|
EsHandle rootDirectory = message->registerFileSystem.rootDirectory;
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_ALL_FILES)
|
|
&& instance->processHandle && !instance->application->notified) {
|
|
message->registerFileSystem.rootDirectory = EsSyscall(ES_SYSCALL_HANDLE_SHARE, rootDirectory, instance->processHandle, 0, 0);
|
|
EsMessagePostRemote(instance->processHandle, message);
|
|
if (instance->application->useSingleProcess) instance->application->notified = true;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
desktop.installedApplications[i]->notified = false;
|
|
}
|
|
} else if (message->type == ES_MSG_UNREGISTER_FILE_SYSTEM) {
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_ALL_FILES)
|
|
&& instance->processHandle && !instance->application->notified) {
|
|
EsMessagePostRemote(instance->processHandle, message);
|
|
if (instance->application->useSingleProcess) instance->application->notified = true;
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
desktop.installedApplications[i]->notified = false;
|
|
}
|
|
} else if (message->type == ES_MSG_DEVICE_CONNECTED) {
|
|
desktop.connectedDevices.Add(message->device);
|
|
// TODO Propagate message.
|
|
} else if (message->type == ES_MSG_DEVICE_DISCONNECTED) {
|
|
for (uintptr_t i = 0; i < desktop.connectedDevices.Length(); i++) {
|
|
if (desktop.connectedDevices[i].id == message->device.id) {
|
|
EsHandleClose(desktop.connectedDevices[i].handle);
|
|
desktop.connectedDevices.Delete(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// TODO Propagate message.
|
|
} else if (message->type == ES_MSG_SET_SCREEN_RESOLUTION) {
|
|
if (desktop.setupDesktopUIComplete) {
|
|
DesktopSetup(); // Refresh desktop UI.
|
|
} else {
|
|
// The screen resolution will be correctly queried in DesktopSetup.
|
|
}
|
|
} else if (message->type == ES_MSG_SINGLE_CTRL_PRESS) {
|
|
if (EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("locate_cursor_on_ctrl"))) {
|
|
EsPoint position = EsMouseGetPosition();
|
|
EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_TIP);
|
|
EsElement *wrapper = EsCustomElementCreate(window, ES_CELL_FILL, ES_STYLE_CLEAR_BACKGROUND);
|
|
wrapper->messageUser = CursorLocatorMessage;
|
|
window->announcementBase = position;
|
|
EsElement *element = EsCustomElementCreate(wrapper, ES_CELL_FILL, ES_STYLE_CURSOR_LOCATOR);
|
|
int width = element->GetWidth(0), height = element->GetHeight(0);
|
|
EsRectangle bounds = ES_RECT_4PD(position.x - width / 2, position.y - height / 2, width, height);
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_ALWAYS_ON_TOP);
|
|
wrapper->StartAnimating();
|
|
}
|
|
} else if (message->type == ES_MSG_INSTANCE_DESTROY) {
|
|
CommonDesktopInstance *instance = (CommonDesktopInstance *) message->instanceDestroy.instance;
|
|
|
|
if (instance->destroy) {
|
|
instance->destroy(instance);
|
|
}
|
|
} else if (message->type == ES_MSG_KEY_DOWN) {
|
|
ProcessGlobalKeyboardShortcuts(nullptr, message);
|
|
} else if (message->type == MSG_SETUP_DESKTOP_UI || message->type == ES_MSG_UI_SCALE_CHANGED) {
|
|
DesktopSetup();
|
|
}
|
|
}
|
|
|
|
void DesktopEntry() {
|
|
ConfigurationLoadApplications();
|
|
|
|
EsMessage m = { MSG_SETUP_DESKTOP_UI };
|
|
EsMessagePost(nullptr, &m);
|
|
|
|
while (true) {
|
|
EsMessage *message = EsMessageReceive();
|
|
DesktopMessage(message);
|
|
}
|
|
}
|