mirror of https://gitlab.com/nakst/essence
3224 lines
122 KiB
C++
3224 lines
122 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
// TODO Tabs:
|
|
// - New tab page - search; recent files.
|
|
// - Duplicating tabs.
|
|
// TODO Task bar:
|
|
// - Right click menu.
|
|
// - Notification area?
|
|
// TODO Global shortcuts:
|
|
// - Restoring closed tabs.
|
|
// - Switch to window.
|
|
// - Print screen.
|
|
// TODO On inactivate windows dim outline around tabs.
|
|
// TODO Alt+tab.
|
|
// TODO Restarting Desktop if it crashes.
|
|
// TODO Make sure applications can't delete |Fonts:.
|
|
// TODO Handle open document deletion.
|
|
|
|
#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_PERMISSION_START_APPLICATION (1 << 7)
|
|
|
|
#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, exiting;
|
|
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;
|
|
|
|
// Copied from ApplicationInstance.
|
|
// This needs to store a copy so it can be used during the exit animation.
|
|
char title[128];
|
|
size_t titleBytes;
|
|
uint32_t iconID;
|
|
};
|
|
|
|
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 {
|
|
Array<WindowTab *> openTabs;
|
|
WindowTabBand *tabBand;
|
|
TaskBarButton *taskBarButton; // Might be null. For example, in the installer there is no task bar.
|
|
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;
|
|
struct ApplicationProcess *singleProcess;
|
|
EsFileOffset totalSize; // 0 if uncalculated.
|
|
};
|
|
|
|
struct CrashedTabInstance : EsInstance {
|
|
};
|
|
|
|
struct BlankTabInstance : EsInstance {
|
|
};
|
|
|
|
struct ApplicationProcess {
|
|
EsObjectID id;
|
|
EsHandle handle;
|
|
EsHandle desktopRequestPipe, desktopResponsePipe;
|
|
InstalledApplication *application;
|
|
size_t instanceCount;
|
|
};
|
|
|
|
struct ApplicationInstance {
|
|
// User interface.
|
|
WindowTab *tab; // nullptr for notRespondingInstance and user tasks.
|
|
EsObjectID embeddedWindowID;
|
|
EsHandle embeddedWindowHandle;
|
|
|
|
// Currently loaded application.
|
|
InstalledApplication *application;
|
|
EsObjectID documentID;
|
|
ApplicationProcess *process;
|
|
bool isUserTask;
|
|
|
|
// Metadata.
|
|
char title[128];
|
|
size_t titleBytes;
|
|
uint32_t iconID;
|
|
double progress;
|
|
};
|
|
|
|
const EsStyle styleSmallParagraph = {
|
|
.inherit = ES_STYLE_TEXT_PARAGRAPH,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_TEXT_SIZE,
|
|
.textSize = 8,
|
|
},
|
|
};
|
|
|
|
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,
|
|
},
|
|
};
|
|
|
|
const EsStyle styleClockButton = {
|
|
.inherit = ES_STYLE_TASK_BAR_EXTRA,
|
|
|
|
.metrics = {
|
|
.mask = ES_THEME_METRICS_TEXT_FIGURES,
|
|
.textFigures = ES_TEXT_FIGURE_TABULAR,
|
|
},
|
|
};
|
|
|
|
struct {
|
|
Array<InstalledApplication *> installedApplications;
|
|
Array<ApplicationInstance *> allApplicationInstances;
|
|
Array<ApplicationProcess *> allApplicationProcesses;
|
|
Array<ContainerWindow *> allContainerWindows;
|
|
|
|
InstalledApplication *fileManager;
|
|
InstalledApplication *installer;
|
|
|
|
EsObjectID currentDocumentID;
|
|
HashStore<EsObjectID, OpenDocument> openDocuments;
|
|
|
|
TaskBar taskBar;
|
|
EsWindow *wallpaperWindow;
|
|
EsButton *tasksButton;
|
|
|
|
bool setupDesktopUIComplete;
|
|
uint8_t installationState;
|
|
bool desktopInspectorOpen;
|
|
|
|
EsHandle nextClipboardFile;
|
|
EsObjectID nextClipboardProcessID;
|
|
EsHandle clipboardFile;
|
|
ClipboardInformation clipboardInformation;
|
|
|
|
bool configurationModified;
|
|
|
|
Array<ApplicationInstance *> allOngoingUserTasks;
|
|
double totalUserTaskProgress;
|
|
|
|
#define SHUTDOWN_TIMEOUT (3000) // Maximum time to wait for applications to exit.
|
|
EsHandle shutdownReadyEvent; // Set when all applications have exited.
|
|
bool inShutdown;
|
|
bool shutdownWindowOpen;
|
|
|
|
EsHandle clockReadyEvent;
|
|
} desktop;
|
|
|
|
int TaskBarButtonMessage(EsElement *element, EsMessage *message);
|
|
ApplicationInstance *ApplicationInstanceCreate(_EsApplicationStartupInformation *startupInformation, ContainerWindow *container, bool hidden = false);
|
|
bool ApplicationInstanceStart(_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();
|
|
void ContainerWindowShow(ContainerWindow *, int32_t width, int32_t height);
|
|
void ShutdownModalCreate();
|
|
void InstanceSettingsCreate(EsMessage *message);
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Reorder lists:
|
|
//////////////////////////////////////////////////////
|
|
|
|
bool ReorderItemAnimate(ReorderItem *item, uint64_t deltaMs, const char *entranceDuration) {
|
|
const float speed = -3.0f;
|
|
// const float speed = -0.3f;
|
|
|
|
item->sizeProgress += (item->sizeTarget - item->sizeProgress)
|
|
* (1 - EsCRTexp(deltaMs * speed / GetConstantNumber(entranceDuration)));
|
|
item->offsetProgress += (item->offsetTarget - item->offsetProgress)
|
|
* (1 - EsCRTexp(deltaMs * speed / 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;
|
|
}
|
|
|
|
if (item->sizeProgress <= 1 && item->exiting) {
|
|
EsElementDestroy(item);
|
|
}
|
|
|
|
EsElementRelayout(item->parent);
|
|
return complete;
|
|
}
|
|
|
|
void ReorderItemDragged(ReorderItem *item, int mouseX) {
|
|
if (item->exiting) {
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
if (draggedIndex != currentIndex) {
|
|
list->items.Delete(currentIndex);
|
|
list->items.Insert(item, draggedIndex);
|
|
|
|
EsMessage m = { .type = ES_MSG_REORDER_ITEM_MOVED, .child = item };
|
|
EsMessageSend(list, &m);
|
|
|
|
for (uintptr_t i = 0, x = list->style->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;
|
|
}
|
|
}
|
|
|
|
item->dragging = true;
|
|
EsElementRelayout(list);
|
|
}
|
|
|
|
void ReorderItemDragComplete(ReorderItem *item) {
|
|
if (!item->dragging || item->exiting) {
|
|
return;
|
|
}
|
|
|
|
ReorderList *list = (ReorderList *) item->parent;
|
|
|
|
for (uintptr_t i = 0, x = list->style->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->style->insets.l;
|
|
bounds.r -= list->style->insets.r;
|
|
bounds.r -= additionalRightMargin;
|
|
|
|
size_t childCount = list->items.Length();
|
|
|
|
int totalWidth = 0;
|
|
uintptr_t nonExitingChildCount = 0;
|
|
|
|
for (uintptr_t i = 0; i < childCount; i++) {
|
|
ReorderItem *child = list->items[i];
|
|
|
|
if (!child->exiting) {
|
|
totalWidth += child->style->metrics->maximumWidth + list->style->metrics->gapMinor;
|
|
nonExitingChildCount++;
|
|
}
|
|
}
|
|
|
|
bool widthClamped = false;
|
|
|
|
if (totalWidth > Width(bounds)) {
|
|
totalWidth = Width(bounds);
|
|
widthClamped = true;
|
|
}
|
|
|
|
int targetWidth = nonExitingChildCount ? totalWidth / nonExitingChildCount : -1;
|
|
int extraWidth = nonExitingChildCount ? totalWidth % nonExitingChildCount : 0;
|
|
|
|
list->targetWidth = targetWidth;
|
|
list->extraWidth = extraWidth;
|
|
|
|
for (uintptr_t i = 0; i < childCount; i++) {
|
|
ReorderItem *child = list->items[i];
|
|
int sizeTarget = 0;
|
|
|
|
if (!child->exiting) {
|
|
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 = child->sizeProgress;
|
|
int gap = list->style->metrics->gapMinor;
|
|
|
|
if (i == childCount - 1 && widthClamped && width > totalWidth - x) {
|
|
width = totalWidth - x;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Desktop Inspector:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void DesktopInspectorThread(EsGeneric) {
|
|
EsMessageMutexAcquire();
|
|
EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_PLAIN);
|
|
EsRectangle screen;
|
|
EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_GET, 0, (uintptr_t) &screen, 0, 0);
|
|
screen.l = screen.r - 400;
|
|
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_FLAGS_DEFAULT, 0, ES_WINDOW_PROPERTY_SOLID);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 180, 0, ES_WINDOW_PROPERTY_ALPHA);
|
|
EsPanel *panel = EsPanelCreate(window, ES_CELL_FILL, ES_STYLE_PANEL_FILLED);
|
|
EsMessageMutexRelease();
|
|
|
|
while (true) {
|
|
EsMessageMutexAcquire();
|
|
EsElementDestroyContents(panel);
|
|
char buffer[256];
|
|
size_t bytes;
|
|
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, ES_STYLE_TEXT_HEADING1, "Desktop Inspector");
|
|
EsSpacerCreate(panel, ES_CELL_H_FILL, 0, 0, 5);
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
bytes = EsStringFormat(buffer, sizeof(buffer), "inst: eid %d, title '%s', pid %d, docid %d, app '%z'%z",
|
|
instance->embeddedWindowID, instance->titleBytes, instance->title, instance->process->id,
|
|
instance->documentID, instance->application->cName, instance->isUserTask ? ", utask" : "");
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, EsStyleIntern(&styleSmallParagraph), buffer, bytes);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
|
|
ApplicationProcess *process = desktop.allApplicationProcesses[i];
|
|
bytes = EsStringFormat(buffer, sizeof(buffer), "proc: pid %d, app '%z', instances %d",
|
|
process->id, process->application ? process->application->cName : "??", process->instanceCount);
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, EsStyleIntern(&styleSmallParagraph), buffer, bytes);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
InstalledApplication *application = desktop.installedApplications[i];
|
|
bytes = EsStringFormat(buffer, sizeof(buffer), "app: '%z'%z%z%z, spid %d, seid %d",
|
|
application->cName, application->temporary ? ", temp" : "", application->useSingleProcess ? ", 1proc" : "",
|
|
application->useSingleInstance ? ", 1inst" : "",
|
|
application->singleProcess ? application->singleProcess->id : 0,
|
|
application->singleInstance ? application->singleInstance->embeddedWindowID : 0);
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, EsStyleIntern(&styleSmallParagraph), buffer, bytes);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) {
|
|
OpenDocument *document = &desktop.openDocuments[i];
|
|
bytes = EsStringFormat(buffer, sizeof(buffer), "doc: '%s', id %d, refs %d",
|
|
document->pathBytes, document->path, document->id, document->referenceCount);
|
|
EsTextDisplayCreate(panel, ES_CELL_H_FILL, EsStyleIntern(&styleSmallParagraph), buffer, bytes);
|
|
}
|
|
|
|
EsMessageMutexRelease();
|
|
EsSleep(500);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Container windows:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void WindowTabClose(WindowTab *tab) {
|
|
if (!tab->applicationInstance) {
|
|
return;
|
|
}
|
|
|
|
if (tab->notRespondingInstance) {
|
|
// The application is not responding, so force quit the process.
|
|
EsProcessTerminate(tab->applicationInstance->process->handle, 1);
|
|
} else {
|
|
ApplicationInstanceClose(tab->applicationInstance);
|
|
}
|
|
}
|
|
|
|
void WindowTabActivate(WindowTab *tab, bool force = false) {
|
|
if (!tab->applicationInstance) {
|
|
return;
|
|
}
|
|
|
|
if (tab->container->active != tab || force) {
|
|
tab->container->active = tab;
|
|
EsElementRelayout(tab->container->tabBand);
|
|
if (tab->container->taskBarButton) 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->openTabs.Length() == 1) {
|
|
EsElementDestroy(container->window);
|
|
|
|
if (container->taskBarButton) {
|
|
container->taskBarButton->exiting = true;
|
|
container->taskBarButton->containerWindow = nullptr;
|
|
EsElementStartTransition(container->taskBarButton, ES_TRANSITION_FADE_OUT, ES_ELEMENT_TRANSITION_HIDE_AFTER_COMPLETE);
|
|
EsElementRelayout(container->taskBarButton->parent);
|
|
// The button is destroyed by ReorderItemAnimate, once the exit animation completes.
|
|
}
|
|
|
|
desktop.allContainerWindows.FindAndDeleteSwap(container, true);
|
|
} else {
|
|
if (container->active == tab) {
|
|
container->active = nullptr;
|
|
|
|
for (uintptr_t i = 0; i < container->openTabs.Length(); i++) {
|
|
if (container->openTabs[i] != tab) continue;
|
|
WindowTabActivate(container->openTabs[i ? (i - 1) : 1]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
tab->applicationInstance = nullptr;
|
|
tab->exiting = true;
|
|
tab->SetStyle(ES_STYLE_WINDOW_TAB_INACTIVE);
|
|
EsElementStartTransition(tab, ES_TRANSITION_FADE_OUT, ES_ELEMENT_TRANSITION_HIDE_AFTER_COMPLETE);
|
|
container->openTabs.FindAndDelete(tab, true);
|
|
EsElementRelayout(container->tabBand);
|
|
// The tab is destroyed by ReorderItemAnimate, once the exit animation completes.
|
|
}
|
|
}
|
|
|
|
WindowTab *WindowTabMoveToNewContainer(WindowTab *tab, ContainerWindow *container, int32_t width, int32_t height) {
|
|
if (!tab->applicationInstance) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!container) {
|
|
// Create a new container.
|
|
container = ContainerWindowCreate();
|
|
if (!container) return nullptr;
|
|
ContainerWindowShow(container, width, height);
|
|
}
|
|
|
|
// Create the new tab.
|
|
WindowTab *newTab = WindowTabCreate(container);
|
|
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;
|
|
|
|
// Copy across metadata.
|
|
EsMemoryCopy(newTab->title, tab->title, tab->titleBytes);
|
|
newTab->titleBytes = tab->titleBytes;
|
|
newTab->iconID = tab->iconID;
|
|
|
|
// 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 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;
|
|
}
|
|
|
|
int ProcessGlobalKeyboardShortcuts(EsElement *, EsMessage *message) {
|
|
if (!desktop.setupDesktopUIComplete) {
|
|
return 0;
|
|
}
|
|
|
|
if (message->type == ES_MSG_KEY_DOWN) {
|
|
bool ctrlOnly = message->keyboard.modifiers == ES_MODIFIER_CTRL;
|
|
uint32_t scancode = ScancodeMapToLabel(message->keyboard.scancode);
|
|
|
|
if (ctrlOnly && scancode == ES_SCANCODE_N && !message->keyboard.repeat && !desktop.installationState) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = APPLICATION_ID_DESKTOP_BLANK_TAB };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr);
|
|
} else if (message->keyboard.modifiers == (ES_MODIFIER_CTRL | ES_MODIFIER_FLAG) && scancode == ES_SCANCODE_D) {
|
|
if (!desktop.desktopInspectorOpen) {
|
|
desktop.desktopInspectorOpen = true;
|
|
EsThreadCreate(DesktopInspectorThread, nullptr, 0);
|
|
} else {
|
|
// TODO Close the inspector.
|
|
}
|
|
} else if (message->keyboard.modifiers == (ES_MODIFIER_CTRL | ES_MODIFIER_FLAG) && scancode == ES_SCANCODE_DELETE) {
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
if (desktop.installedApplications[i]->cName && 0 == EsCRTstrcmp(desktop.installedApplications[i]->cName, "System Monitor")) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = desktop.installedApplications[i]->id };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr);
|
|
}
|
|
}
|
|
} else if (scancode == ES_SCANCODE_ACPI_POWER) {
|
|
ShutdownModalCreate();
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
} else if (message->type == ES_MSG_KEY_UP) {
|
|
if (message->keyboard.scancode == ES_SCANCODE_LEFT_CTRL && message->keyboard.single) {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ContainerWindowMessage(EsElement *element, EsMessage *message) {
|
|
ContainerWindow *container = (ContainerWindow *) element->userData.p;
|
|
|
|
if (message->type == ES_MSG_WINDOW_ACTIVATED) {
|
|
if (container->taskBarButton) {
|
|
container->taskBarButton->customStyleState |= THEME_STATE_SELECTED;
|
|
container->taskBarButton->MaybeRefreshStyle();
|
|
}
|
|
} else if (message->type == ES_MSG_WINDOW_DEACTIVATED) {
|
|
if (container->taskBarButton) {
|
|
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 = ScancodeMapToLabel(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->openTabs.Length(); i++) {
|
|
if (container->openTabs[i] == container->active) {
|
|
tab = i;
|
|
}
|
|
}
|
|
|
|
EsAssert(tab != -1);
|
|
tab += ((message->keyboard.modifiers & ES_MODIFIER_SHIFT) ? -1 : 1);
|
|
if (tab == -1) tab = container->openTabs.Length() - 1;
|
|
if (tab == (int) container->openTabs.Length()) tab = 0;
|
|
WindowTabActivate(container->openTabs[tab]);
|
|
} else if (ctrlOnly && scancode == ES_SCANCODE_T && !message->keyboard.repeat) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = APPLICATION_ID_DESKTOP_BLANK_TAB };
|
|
ApplicationInstanceCreate(&startupInformation, container);
|
|
} else if (ctrlOnly && scancode == ES_SCANCODE_W && !message->keyboard.repeat) {
|
|
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->openTabs.Length() > i) {
|
|
WindowTabActivate(container->openTabs[i]);
|
|
unhandled = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (unhandled) {
|
|
ProcessGlobalKeyboardShortcuts(element, message);
|
|
}
|
|
}
|
|
} else if (message->type == ES_MSG_KEY_UP) {
|
|
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;
|
|
|
|
if (message->type == ES_MSG_DESTROY) {
|
|
if (tab->dragging) {
|
|
EsElementSetDisabled(band->GetChild(0), false);
|
|
}
|
|
|
|
band->container->openTabs.FindAndDelete(tab, false);
|
|
|
|
if (tab->notRespondingInstance) {
|
|
ApplicationInstanceClose(tab->notRespondingInstance);
|
|
tab->notRespondingInstance = nullptr;
|
|
}
|
|
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) {
|
|
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->style->preferredWidth;
|
|
Rectangle16 insets = tab->style->metrics->insets;
|
|
EsElementSetHidden(tab->closeButton, tab->style->gapWrap * 2 + closeButtonWidth >= tab->width);
|
|
EsElementMove(tab->closeButton, tab->width - tab->style->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),
|
|
tab->title, tab->titleBytes, tab->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_DRAG) {
|
|
EsElementSetDisabled(band->GetChild(0), true);
|
|
|
|
if (band->container->openTabs.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->style->insets.l;
|
|
newTab->dragging = true;
|
|
}
|
|
} else {
|
|
EsPoint mousePosition = EsMouseGetPosition(tab->window);
|
|
int32_t dragOffThreshold = GetConstantNumber("tabDragOffThreshold");
|
|
// int32_t previousTabOffsetX = tab->offsetX;
|
|
int32_t previousTabOffsetX = tab->dragPosition;
|
|
|
|
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->style->insets.l
|
|
|| tab->dragPosition + tab->width > band->width - band->style->insets.r;
|
|
int32_t putAtStartClickX = band->style->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->style->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 && tab->applicationInstance) {
|
|
EsMenu *menu = EsMenuCreate(tab, ES_FLAGS_DEFAULT);
|
|
uint64_t disableIfOnlyTab = tab->container->openTabs.Length() == 1 ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT;
|
|
|
|
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, INTERFACE_STRING(DesktopCloseTab), [] (EsMenu *, EsGeneric context) {
|
|
WindowTabClose((WindowTab *) context.p);
|
|
}, tab);
|
|
|
|
EsMenuAddItem(menu, disableIfOnlyTab, INTERFACE_STRING(DesktopMoveTabToNewWindow), [] (EsMenu *, EsGeneric context) {
|
|
WindowTabMoveToNewContainer((WindowTab *) context.p, nullptr, 0, 0);
|
|
}, tab);
|
|
|
|
EsMenuAddItem(menu, disableIfOnlyTab, INTERFACE_STRING(DesktopMoveTabToNewWindowSplitLeft), [] (EsMenu *, EsGeneric context) {
|
|
WindowTab *oldTab = (WindowTab *) context.p;
|
|
EsWindow *oldWindow = oldTab->window;
|
|
WindowTab *newTab = WindowTabMoveToNewContainer(oldTab, nullptr, 0, 0);
|
|
if (!newTab) return;
|
|
if (oldWindow->isMaximised) WindowRestore(oldWindow, false);
|
|
WindowSnap(oldWindow, false, false, SNAP_EDGE_RIGHT);
|
|
WindowSnap(newTab->window, false, false, SNAP_EDGE_LEFT);
|
|
}, tab);
|
|
|
|
EsMenuAddItem(menu, disableIfOnlyTab, INTERFACE_STRING(DesktopMoveTabToNewWindowSplitRight), [] (EsMenu *, EsGeneric context) {
|
|
WindowTab *oldTab = (WindowTab *) context.p;
|
|
EsWindow *oldWindow = oldTab->window;
|
|
WindowTab *newTab = WindowTabMoveToNewContainer(oldTab, nullptr, 0, 0);
|
|
if (!newTab) return;
|
|
if (oldWindow->isMaximised) WindowRestore(oldWindow, false /* we immediately move the window after this */);
|
|
WindowSnap(oldWindow, false, false, SNAP_EDGE_LEFT);
|
|
WindowSnap(newTab->window, false, false, SNAP_EDGE_RIGHT);
|
|
}, 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;
|
|
if (!instance) return;
|
|
EsMessage m = { ES_MSG_TAB_INSPECT_UI };
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
EsMessagePostRemote(instance->process->handle, &m);
|
|
}, tab);
|
|
}
|
|
|
|
EsMenuShow(menu);
|
|
} else if (message->type == ES_MSG_MOUSE_MIDDLE_UP && element->window->hovered == element) {
|
|
if (EsButtonGetCheck(tab->closeButton) == ES_CHECK_CHECKED) {
|
|
// The tab contains a modified document, so it will probably popup a dialog after it receives the close request.
|
|
// Therefore, we should switch to that tab.
|
|
tab->BringToFront();
|
|
WindowTabActivate(tab);
|
|
}
|
|
|
|
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_FILL | ES_CELL_V_BOTTOM, WindowTabMessage, 0);
|
|
tab->cName = "window tab";
|
|
container->openTabs.Add(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->container->openTabs.Length(); i++) {
|
|
WindowTab *tab = band->container->openTabs[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)->style->preferredWidth + 10 * theming.scale,
|
|
true, band->preventNextTabSizeAnimation);
|
|
band->GetChild(0)->InternalMove(band->GetChild(0)->style->preferredWidth,
|
|
band->GetChild(0)->style->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);
|
|
const char *string = band->container->openTabs.Length() > 1 ? interfaceString_DesktopCloseAllTabs : interfaceString_DesktopCloseWindow;
|
|
|
|
EsMenuAddItem(menu, ES_FLAGS_DEFAULT, string, -1, [] (EsMenu *, EsGeneric context) {
|
|
ContainerWindow *container = (ContainerWindow *) context.p;
|
|
|
|
for (uintptr_t i = 0; i < container->openTabs.Length(); i++) {
|
|
WindowTabClose(container->openTabs[i]);
|
|
}
|
|
}, band->container);
|
|
|
|
EsMenuAddSeparator(menu);
|
|
|
|
EsMenuAddItem(menu, band->window->isMaximised ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT,
|
|
INTERFACE_STRING(DesktopMaximiseWindow), [] (EsMenu *, EsGeneric context) {
|
|
WindowSnap((EsWindow *) context.p, false, false, SNAP_EDGE_MAXIMIZE);
|
|
}, band->window);
|
|
|
|
EsMenuAddItem(menu, band->window->isMaximised ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT,
|
|
INTERFACE_STRING(DesktopMinimiseWindow), [] (EsMenu *, EsGeneric context) {
|
|
EsSyscall(ES_SYSCALL_WINDOW_MOVE, ((EsWindow *) context.p)->handle, 0, 0, ES_WINDOW_MOVE_HIDDEN);
|
|
}, band->window);
|
|
|
|
EsMenuAddItem(menu, !band->window->isMaximised ? ES_ELEMENT_DISABLED : ES_FLAGS_DEFAULT,
|
|
INTERFACE_STRING(DesktopRestoreWindow), [] (EsMenu *, EsGeneric context) {
|
|
WindowRestore((EsWindow *) context.p);
|
|
}, band->window);
|
|
|
|
EsMenuAddSeparator(menu);
|
|
|
|
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);
|
|
|
|
EsMenuAddItem(menu, ES_FLAGS_DEFAULT,
|
|
INTERFACE_STRING(DesktopSnapWindowLeft), [] (EsMenu *, EsGeneric context) {
|
|
EsWindow *window = (EsWindow *) context.p;
|
|
if (window->isMaximised) WindowRestore(window, false /* we immediately move the window after this */);
|
|
WindowSnap(window, false, false, SNAP_EDGE_LEFT);
|
|
}, band->window);
|
|
|
|
EsMenuAddItem(menu, ES_FLAGS_DEFAULT,
|
|
INTERFACE_STRING(DesktopSnapWindowRight), [] (EsMenu *, EsGeneric context) {
|
|
EsWindow *window = (EsWindow *) context.p;
|
|
if (window->isMaximised) WindowRestore(window, false /* we immediately move the window after this */);
|
|
WindowSnap(window, false, false, SNAP_EDGE_RIGHT);
|
|
}, band->window);
|
|
|
|
EsMenuShow(menu);
|
|
} else if (message->type == ES_MSG_REORDER_ITEM_MOVED) {
|
|
WindowTab *tab = (WindowTab *) message->child;
|
|
|
|
if (band->container->openTabs.FindAndDelete(tab, false)) {
|
|
uintptr_t openTabsBeforeTab = 0;
|
|
|
|
for (uintptr_t i = 0; i < band->items.Length(); i++) {
|
|
if (band->items[i] == tab) {
|
|
break;
|
|
} else if (!band->items[i]->exiting) {
|
|
openTabsBeforeTab++;
|
|
}
|
|
}
|
|
|
|
band->container->openTabs.Insert(tab, openTabsBeforeTab);
|
|
}
|
|
} else {
|
|
return ReorderListMessage(band, message);
|
|
}
|
|
|
|
return ES_HANDLED;
|
|
}
|
|
|
|
void ContainerWindowShow(ContainerWindow *container, int32_t width, int32_t height) {
|
|
EsWindow *window = container->window;
|
|
|
|
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_WINDOW_MOVE_DYNAMIC);
|
|
EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0, 0, ES_WINDOW_PROPERTY_FOCUSED);
|
|
}
|
|
|
|
ContainerWindow *ContainerWindowCreate() {
|
|
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->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 *) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = APPLICATION_ID_DESKTOP_BLANK_TAB };
|
|
ApplicationInstanceCreate(&startupInformation, (ContainerWindow *) element->window->userData.p);
|
|
});
|
|
|
|
if (!desktop.installationState) {
|
|
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 && button->containerWindow) {
|
|
ContainerWindow *containerWindow = button->containerWindow;
|
|
ApplicationInstance *instance = containerWindow->active->applicationInstance;
|
|
|
|
if (instance) {
|
|
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 && button->containerWindow) {
|
|
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_FLAGS_DEFAULT, 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 TaskBarClockUpdateThread(EsGeneric _clock) {
|
|
EsWaitSingle(desktop.clockReadyEvent);
|
|
|
|
EsButton *clock = (EsButton *) _clock.p;
|
|
static EsDateComponents previousTime = {};
|
|
|
|
while (true) {
|
|
// TODO Use the local time zone.
|
|
EsDateComponents currentTime;
|
|
EsDateNowUTC(¤tTime);
|
|
|
|
uint8_t secondsUntilNextMinuteChange = 60 - currentTime.second;
|
|
|
|
currentTime.second = 0;
|
|
currentTime.millisecond = 0;
|
|
|
|
if (0 == EsMemoryCompare(¤tTime, &previousTime, sizeof(EsDateComponents))) {
|
|
// The minute or hour hasn't changed. Wait for the next change.
|
|
EsSleep((secondsUntilNextMinuteChange + 1) * 1000);
|
|
} else {
|
|
// Update the clock's label.
|
|
// TODO Proper clock formatting.
|
|
char label[32];
|
|
size_t labelBytes = EsStringFormat(label, sizeof(label), "%d%d:%d%d",
|
|
currentTime.hour / 10, currentTime.hour % 10, currentTime.minute / 10, currentTime.minute % 10);
|
|
EsMessageMutexAcquire();
|
|
EsButtonSetLabel(clock, label, labelBytes);
|
|
EsMessageMutexRelease();
|
|
|
|
EsMemoryCopy(&previousTime, ¤tTime, sizeof(EsDateComponents));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Shutdown(uintptr_t action) {
|
|
// TODO This doesn't wait for Desktop instances.
|
|
|
|
if (desktop.inShutdown) {
|
|
return;
|
|
}
|
|
|
|
if (!desktop.allApplicationProcesses.Length()) {
|
|
// No applications are open, so we can shut down immediately.
|
|
EsSyscall(ES_SYSCALL_SHUTDOWN, action, 0, 0, 0);
|
|
}
|
|
|
|
desktop.inShutdown = true;
|
|
desktop.shutdownReadyEvent = EsEventCreate(true);
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
// Tell all applications to close.
|
|
ApplicationInstanceClose(desktop.allApplicationInstances[i]);
|
|
}
|
|
|
|
EsThreadCreate([] (EsGeneric action) {
|
|
// Shut down either when all applications exit, or after the timeout.
|
|
EsWait(&desktop.shutdownReadyEvent, 1, SHUTDOWN_TIMEOUT);
|
|
EsSyscall(ES_SYSCALL_SHUTDOWN, action.u, 0, 0, 0);
|
|
}, nullptr, action);
|
|
}
|
|
|
|
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);
|
|
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_ELEMENT_AUTO_GROUP, ES_STYLE_DIALOG_SHADOW);
|
|
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 | ES_BUTTON_CANCEL, 0, INTERFACE_STRING(CommonCancel));
|
|
EsButton *restartButton = EsButtonCreate(buttonArea, ES_FLAGS_DEFAULT, ES_STYLE_PUSH_BUTTON_DANGEROUS, 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 *) {
|
|
Shutdown(ES_SHUTDOWN_ACTION_POWER_OFF);
|
|
});
|
|
|
|
EsButtonOnCommand(restartButton, [] (EsInstance *, EsElement *, EsCommand *) {
|
|
Shutdown(ES_SHUTDOWN_ACTION_RESTART);
|
|
});
|
|
|
|
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->process->handle, 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, EsStyleIntern(&styleNewTabContent));
|
|
EsPanel *buttonGroup;
|
|
|
|
// Installed applications list.
|
|
|
|
buttonGroup = EsPanelCreate(content, ES_PANEL_VERTICAL | ES_CELL_H_SHRINK, EsStyleIntern(&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);
|
|
button->userData = application;
|
|
|
|
if (application->iconID) {
|
|
EsButtonSetIcon(button, (EsStandardIcon) application->iconID);
|
|
} else {
|
|
EsButtonSetIcon(button, ES_ICON_APPLICATION_DEFAULT_ICON);
|
|
|
|
// TODO Load the icon asynchronously.
|
|
// TODO Load the correct icon size.
|
|
// TODO Reload the icon if the UI scale factor changes.
|
|
// TODO Cache the icon bits.
|
|
// TODO Generic icon and thumbnail cache in the API, based off the one from File Manager?
|
|
|
|
size_t fileBytes;
|
|
void *file = EsFileMap(application->cExecutable, -1, &fileBytes, ES_MEMORY_MAP_OBJECT_READ_ONLY);
|
|
EsBundle bundle = { .base = (const BundleHeader *) file, .bytes = (ptrdiff_t) fileBytes };
|
|
|
|
if (file) {
|
|
size_t icon32Bytes;
|
|
const void *icon32 = EsBundleFind(&bundle, EsLiteral("$Icons/32"), &icon32Bytes);
|
|
|
|
if (icon32) {
|
|
uint32_t width, height;
|
|
uint32_t *bits = (uint32_t *) EsImageLoad(icon32, icon32Bytes, &width, &height, 4);
|
|
|
|
if (bits) {
|
|
EsButtonSetIconFromBits(button, bits, width, height, width * 4);
|
|
EsHeapFree(bits);
|
|
}
|
|
}
|
|
|
|
EsObjectUnmap(file);
|
|
}
|
|
}
|
|
|
|
EsButtonOnCommand(button, [] (EsInstance *, EsElement *element, EsCommand *) {
|
|
ApplicationInstance *instance = ApplicationInstanceFindByWindowID(element->window->id);
|
|
_EsApplicationStartupInformation startupInformation = {};
|
|
startupInformation.id = ((InstalledApplication *) element->userData.p)->id;
|
|
|
|
if (ApplicationInstanceStart(&startupInformation, instance)) {
|
|
WindowTabActivate(instance->tab, true);
|
|
EsInstanceClose(element->instance);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Application management:
|
|
//////////////////////////////////////////////////////
|
|
|
|
ApplicationInstance *ApplicationInstanceFindByWindowID(EsObjectID windowID, bool remove) {
|
|
if (!windowID) {
|
|
return nullptr;
|
|
}
|
|
|
|
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->process->handle, &m);
|
|
}
|
|
|
|
void ApplicationInstanceCleanup(ApplicationInstance *instance) {
|
|
if (instance->documentID) {
|
|
OpenDocumentCloseReference(instance->documentID);
|
|
instance->documentID = 0;
|
|
}
|
|
|
|
InstalledApplication *application = instance->application;
|
|
|
|
if (application && application->singleInstance == instance) {
|
|
application->singleInstance = nullptr;
|
|
}
|
|
|
|
if (instance->process) {
|
|
EsAssert(instance->process->instanceCount);
|
|
instance->process->instanceCount--;
|
|
|
|
if (!instance->process->instanceCount) {
|
|
EsMessage m = { ES_MSG_APPLICATION_EXIT };
|
|
EsMessagePostRemote(instance->process->handle, &m);
|
|
}
|
|
}
|
|
|
|
instance->process = nullptr;
|
|
instance->application = nullptr;
|
|
}
|
|
|
|
void PathGetNameAndContainingFolder(InstalledApplication *application,
|
|
const char *path, ptrdiff_t pathBytes,
|
|
const char **name, ptrdiff_t *nameBytes, /* the full path is returned if the application has permission_all_files */
|
|
const char **containingFolder, ptrdiff_t *containingFolderBytes,
|
|
EsVolumeInformation *volumeInformation /* needs to be allocated outside */) {
|
|
if (pathBytes == -1) {
|
|
pathBytes = EsCStringLength(path);
|
|
}
|
|
|
|
*name = path;
|
|
*nameBytes = pathBytes;
|
|
*containingFolderBytes = 0;
|
|
|
|
const char *containingFolderEnd = nullptr;
|
|
|
|
for (uintptr_t i = pathBytes; i > 0; i--) {
|
|
if (path[i - 1] == '/') {
|
|
if (!containingFolderEnd) {
|
|
containingFolderEnd = path + i - 1;
|
|
|
|
if (~application->permissions & APPLICATION_PERMISSION_ALL_FILES) {
|
|
*name = path + i;
|
|
*nameBytes = pathBytes - i;
|
|
}
|
|
} else {
|
|
*containingFolder = path + i;
|
|
*containingFolderBytes = containingFolderEnd - *containingFolder;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO Get the user name of the folder from File Manager?
|
|
// Maybe more of File Manager's code needs to be shared with Desktop..?
|
|
|
|
if (pathBytes && !(*containingFolderBytes)) {
|
|
if (EsMountPointGetVolumeInformation(path, pathBytes, volumeInformation)) {
|
|
*containingFolder = volumeInformation->label;
|
|
*containingFolderBytes = volumeInformation->labelBytes;
|
|
}
|
|
}
|
|
}
|
|
|
|
void *OpenDocumentGetRenameMessageData(InstalledApplication *application, const char *path, size_t pathBytes, size_t *bytes) {
|
|
EsVolumeInformation volumeInformation;
|
|
const char *name, *containingFolder;
|
|
ptrdiff_t nameBytes, containingFolderBytes;
|
|
PathGetNameAndContainingFolder(application, path, pathBytes, &name, &nameBytes, &containingFolder, &containingFolderBytes, &volumeInformation);
|
|
*bytes = sizeof(ptrdiff_t) * 2 + nameBytes + containingFolderBytes;
|
|
uint8_t *data = (uint8_t *) EsHeapAllocate(*bytes, false);
|
|
EsMemoryCopy(data, &nameBytes, sizeof(ptrdiff_t));
|
|
EsMemoryCopy(data + sizeof(ptrdiff_t), &containingFolderBytes, sizeof(ptrdiff_t));
|
|
EsMemoryCopy(data + sizeof(ptrdiff_t) * 2, name, nameBytes);
|
|
EsMemoryCopy(data + sizeof(ptrdiff_t) * 2 + nameBytes, containingFolder, containingFolderBytes);
|
|
return data;
|
|
}
|
|
|
|
void ApplicationTemporaryDestroy(InstalledApplication *application) {
|
|
if (!application->temporary) return;
|
|
|
|
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 ApplicationProcessTerminated(ApplicationProcess *process) {
|
|
EsMessageMutexCheck();
|
|
|
|
desktop.allApplicationProcesses.FindAndDeleteSwap(process, true /* assert found */);
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->process == process) {
|
|
EmbeddedWindowDestroyed(instance->embeddedWindowID);
|
|
i--; // EmbeddedWindowDestroyed removes it from the array.
|
|
}
|
|
}
|
|
|
|
InstalledApplication *application = process->application;
|
|
|
|
if (application) {
|
|
if (application->singleProcess == process) {
|
|
application->singleProcess = nullptr;
|
|
}
|
|
|
|
application->singleInstance = nullptr;
|
|
ApplicationTemporaryDestroy(application);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) {
|
|
OpenDocument *document = &desktop.openDocuments[i];
|
|
|
|
if (document->currentWriter == process->id) {
|
|
document->currentWriter = 0;
|
|
}
|
|
}
|
|
|
|
if (!desktop.allApplicationProcesses.Length() && desktop.inShutdown) {
|
|
EsEventSet(desktop.shutdownReadyEvent);
|
|
}
|
|
|
|
EsAssert(!process->instanceCount);
|
|
EsHandleClose(process->handle);
|
|
|
|
if (process->desktopRequestPipe) {
|
|
EsHandleClose(process->desktopRequestPipe);
|
|
EsHandleClose(process->desktopResponsePipe);
|
|
} else {
|
|
// This must be a Desktop-owned instance.
|
|
}
|
|
|
|
EsHeapFree(process);
|
|
}
|
|
|
|
void DesktopRequestThread(EsGeneric argument) {
|
|
ApplicationProcess *process = (ApplicationProcess *) argument.p;
|
|
|
|
uint32_t length;
|
|
size_t bytes;
|
|
EsObjectID embeddedWindowID;
|
|
|
|
while (true) {
|
|
bytes = EsPipeRead(process->desktopRequestPipe, &length, sizeof(length), false);
|
|
if (bytes != sizeof(length)) break; // Process has terminated or closed the pipe.
|
|
bytes = EsPipeRead(process->desktopRequestPipe, &embeddedWindowID, sizeof(embeddedWindowID), false);
|
|
if (bytes != sizeof(embeddedWindowID)) break; // Process has terminated or closed the pipe.
|
|
|
|
if (length < 1 || length > DESKTOP_MESSAGE_SIZE_LIMIT) {
|
|
// Discard the message.
|
|
// TODO Crash the process.
|
|
EsPipeRead(process->desktopRequestPipe, nullptr, length, false);
|
|
continue;
|
|
}
|
|
|
|
void *buffer = EsHeapAllocate(length, false);
|
|
bytes = EsPipeRead(process->desktopRequestPipe, buffer, length, false);
|
|
if (bytes != length) break; // Process has terminated or closed the pipe.
|
|
|
|
if (!buffer) {
|
|
// The buffer could not be allocated, and the data read from the pipe was discarded.
|
|
continue;
|
|
}
|
|
|
|
EsBuffer response = { .canGrow = true };
|
|
|
|
EsMessageMutexAcquire();
|
|
|
|
if (!DesktopSyscall(embeddedWindowID, process, (uint8_t *) buffer, length, &response)) {
|
|
// TODO Crash the process.
|
|
EsPrint("Process %d running application '%z' had a fatal error (this usually means a permission check failed) with desktop request #%d.\n",
|
|
process->id, process->application->cName, *(uint8_t *) buffer);
|
|
}
|
|
|
|
EsMessageMutexRelease();
|
|
|
|
if (!response.error) {
|
|
length = response.position;
|
|
EsPipeWrite(process->desktopResponsePipe, &length, sizeof(length));
|
|
EsPipeWrite(process->desktopResponsePipe, response.out, response.position);
|
|
} else {
|
|
length = 0;
|
|
EsPipeWrite(process->desktopResponsePipe, &length, sizeof(length));
|
|
}
|
|
|
|
EsHeapFree(response.out);
|
|
EsHeapFree(buffer);
|
|
}
|
|
|
|
EsMessageMutexAcquire();
|
|
ApplicationProcessTerminated(process);
|
|
EsMessageMutexRelease();
|
|
}
|
|
|
|
ApplicationProcess *DesktopGetApplicationProcessForDesktop() {
|
|
EsObjectID desktopProcessID = EsProcessGetID(ES_CURRENT_PROCESS);
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
|
|
if (desktop.allApplicationProcesses[i]->id == desktopProcessID) {
|
|
return desktop.allApplicationProcesses[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool ApplicationInstanceStart(_EsApplicationStartupInformation *startupInformation, ApplicationInstance *instance) {
|
|
if (desktop.inShutdown) {
|
|
return false;
|
|
}
|
|
|
|
InstalledApplication *application = nullptr;
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
if (desktop.installedApplications[i]->id == startupInformation->id) {
|
|
application = desktop.installedApplications[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
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.id = APPLICATION_ID_DESKTOP_CRASHED;
|
|
s.data = CRASHED_TAB_PROGRAM_NOT_FOUND;
|
|
return ApplicationInstanceStart(&s, instance);
|
|
}
|
|
|
|
if (instance->tab) {
|
|
EsButtonSetCheck(instance->tab->closeButton, ES_CHECK_UNCHECKED, false);
|
|
}
|
|
|
|
if (instance->tab && instance->tab->notRespondingInstance) {
|
|
ApplicationInstanceClose(instance->tab->notRespondingInstance);
|
|
instance->tab->notRespondingInstance = nullptr;
|
|
}
|
|
|
|
ApplicationProcess *process = application->singleProcess;
|
|
|
|
if (application->createInstance) {
|
|
process = DesktopGetApplicationProcessForDesktop();
|
|
|
|
if (!process) {
|
|
process = (ApplicationProcess *) EsHeapAllocate(sizeof(ApplicationProcess), true);
|
|
process->handle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, ES_CURRENT_PROCESS, ES_CURRENT_PROCESS, 0, 0);
|
|
process->id = EsProcessGetID(ES_CURRENT_PROCESS);
|
|
desktop.allApplicationProcesses.Add(process);
|
|
}
|
|
} else if (!process) {
|
|
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)) {
|
|
ApplicationTemporaryDestroy(application);
|
|
_EsApplicationStartupInformation s = {};
|
|
s.id = APPLICATION_ID_DESKTOP_CRASHED;
|
|
s.data = CRASHED_TAB_INVALID_EXECUTABLE;
|
|
return ApplicationInstanceStart(&s, instance);
|
|
}
|
|
|
|
arguments.executable = executableNode.handle;
|
|
arguments.permissions = ES_PERMISSION_WINDOW_MANAGER;
|
|
|
|
SystemStartupDataHeader header = {};
|
|
Array<EsMountPoint> initialMountPoints = {};
|
|
Array<EsMessageDevice> initialDevices = {};
|
|
Array<EsHandle> handleDuplicateList = {};
|
|
Array<uint32_t> handleModeDuplicateList = {};
|
|
_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;
|
|
|
|
EsMountPoint root;
|
|
EsAssert(NodeFindMountPoint(EsLiteral("0:"), &root, false));
|
|
root.prefixBytes = EsStringFormat(root.prefix, sizeof(root.prefix), "|POSIX:");
|
|
initialMountPoints.Add(root);
|
|
|
|
handleDuplicateList.Add(root.base);
|
|
handleModeDuplicateList.Add(0);
|
|
}
|
|
|
|
if (application->permissions & APPLICATION_PERMISSION_SHUTDOWN) {
|
|
arguments.permissions |= ES_PERMISSION_SHUTDOWN;
|
|
}
|
|
|
|
if (application->permissions & APPLICATION_PERMISSION_ALL_FILES) {
|
|
arguments.permissions |= ES_PERMISSION_GET_VOLUME_INFORMATION;
|
|
}
|
|
|
|
{
|
|
EsMountPoint fonts;
|
|
EsAssert(NodeFindMountPoint(EsLiteral("|Fonts:"), &fonts, false));
|
|
initialMountPoints.Add(fonts);
|
|
handleDuplicateList.Add(fonts.base);
|
|
handleModeDuplicateList.Add(2 /* prevent write */);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < api.connectedDevices.Length(); i++) {
|
|
if ((application->permissions & APPLICATION_PERMISSION_ALL_DEVICES)
|
|
|| ((application->permissions & APPLICATION_PERMISSION_ALL_FILES) && api.connectedDevices[i].type == ES_DEVICE_FILE_SYSTEM)) {
|
|
initialDevices.Add(api.connectedDevices[i]);
|
|
handleDuplicateList.Add(api.connectedDevices[i].handle);
|
|
handleModeDuplicateList.Add(0);
|
|
}
|
|
}
|
|
|
|
{
|
|
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;
|
|
initialMountPoints.Add(settings);
|
|
|
|
handleDuplicateList.Add(settings.base);
|
|
handleModeDuplicateList.Add(0);
|
|
} else {
|
|
settingsNode.handle = ES_INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
EsHandle desktopRequestReadEnd, desktopRequestWriteEnd, desktopResponseReadEnd, desktopResponseWriteEnd;
|
|
EsPipeCreate(&desktopRequestReadEnd, &desktopRequestWriteEnd);
|
|
EsPipeCreate(&desktopResponseReadEnd, &desktopResponseWriteEnd);
|
|
|
|
EsBuffer buffer = { .canGrow = true };
|
|
header.initialMountPointCount = initialMountPoints.Length();
|
|
header.initialDeviceCount = initialDevices.Length();
|
|
header.themeCursorData = theming.cursorData;
|
|
header.desktopRequestPipe = desktopRequestWriteEnd;
|
|
header.desktopResponsePipe = desktopResponseReadEnd;
|
|
EsBufferWrite(&buffer, &header, sizeof(header));
|
|
EsBufferWrite(&buffer, initialMountPoints.array, sizeof(EsMountPoint) * header.initialMountPointCount);
|
|
EsBufferWrite(&buffer, initialDevices.array, sizeof(EsMessageDevice) * header.initialDeviceCount);
|
|
arguments.data.systemData = EsConstantBufferCreate(buffer.out, buffer.position, ES_CURRENT_PROCESS);
|
|
handleDuplicateList.Add(arguments.data.systemData);
|
|
handleModeDuplicateList.Add(0);
|
|
handleDuplicateList.Add(header.themeCursorData);
|
|
handleModeDuplicateList.Add(0);
|
|
handleDuplicateList.Add(header.desktopRequestPipe);
|
|
handleModeDuplicateList.Add(0);
|
|
handleDuplicateList.Add(header.desktopResponsePipe);
|
|
handleModeDuplicateList.Add(0);
|
|
EsHeapFree(buffer.out);
|
|
|
|
arguments.handles = handleDuplicateList.array;
|
|
arguments.handleModes = handleModeDuplicateList.array;
|
|
arguments.handleCount = handleDuplicateList.Length();
|
|
EsAssert(handleDuplicateList.Length() == handleModeDuplicateList.Length());
|
|
|
|
error = EsProcessCreate(&arguments, &information);
|
|
EsHandleClose(arguments.executable);
|
|
|
|
if (settingsNode.handle) EsHandleClose(settingsNode.handle);
|
|
if (arguments.data.systemData) EsHandleClose(arguments.data.systemData);
|
|
|
|
EsHandleClose(desktopResponseReadEnd);
|
|
EsHandleClose(desktopRequestWriteEnd);
|
|
|
|
if (!ES_CHECK_ERROR(error)) {
|
|
EsHandleClose(information.mainThread.handle);
|
|
|
|
process = (ApplicationProcess *) EsHeapAllocate(sizeof(ApplicationProcess), true);
|
|
process->handle = information.handle;
|
|
process->id = information.pid;
|
|
process->application = application;
|
|
process->desktopResponsePipe = desktopResponseWriteEnd;
|
|
process->desktopRequestPipe = desktopRequestReadEnd;
|
|
desktop.allApplicationProcesses.Add(process);
|
|
|
|
EsThreadInformation information;
|
|
EsThreadCreate(DesktopRequestThread, &information, process);
|
|
EsHandleClose(information.handle);
|
|
} else {
|
|
EsHandleClose(desktopRequestReadEnd);
|
|
EsHandleClose(desktopResponseWriteEnd);
|
|
|
|
ApplicationTemporaryDestroy(application);
|
|
_EsApplicationStartupInformation s = {};
|
|
s.id = APPLICATION_ID_DESKTOP_CRASHED;
|
|
s.data = CRASHED_TAB_INVALID_EXECUTABLE;
|
|
return ApplicationInstanceStart(&s, instance);
|
|
}
|
|
}
|
|
|
|
if (application->useSingleProcess) {
|
|
application->singleProcess = process;
|
|
}
|
|
|
|
// Increment the instance count before cleaning up the old process,
|
|
// so that when going between 2 Desktop instances,
|
|
// the Desktop process doesn't exit.
|
|
process->instanceCount++;
|
|
|
|
ApplicationInstanceCleanup(instance);
|
|
instance->application = application;
|
|
instance->process = process;
|
|
|
|
if (startupInformation->documentID) {
|
|
instance->documentID = startupInformation->documentID;
|
|
OpenDocumentOpenReference(instance->documentID);
|
|
}
|
|
|
|
EsVolumeInformation volumeInformation;
|
|
|
|
EsMessage m = { ES_MSG_INSTANCE_CREATE };
|
|
|
|
PathGetNameAndContainingFolder(application,
|
|
startupInformation->filePath, startupInformation->filePathBytes,
|
|
&startupInformation->filePath, &startupInformation->filePathBytes,
|
|
&startupInformation->containingFolder, &startupInformation->containingFolderBytes,
|
|
&volumeInformation);
|
|
|
|
// Share handles to the file and the startup information buffer.
|
|
|
|
if (startupInformation->readHandle) {
|
|
startupInformation->readHandle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, startupInformation->readHandle, process->handle, 0, 0);
|
|
}
|
|
|
|
uint8_t *createInstanceDataBuffer = ApplicationStartupInformationToBuffer(startupInformation, &m.createInstance.dataBytes);
|
|
m.createInstance.data = EsConstantBufferCreate(createInstanceDataBuffer, m.createInstance.dataBytes, process->handle);
|
|
EsHeapFree(createInstanceDataBuffer);
|
|
|
|
EsHandle handle = EsSyscall(ES_SYSCALL_WINDOW_CREATE, ES_WINDOW_NORMAL, 0, 0, 0);
|
|
instance->embeddedWindowHandle = handle;
|
|
instance->embeddedWindowID = EsSyscall(ES_SYSCALL_WINDOW_GET_ID, handle, 0, 0, 0);
|
|
m.createInstance.window = EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, handle, process->handle, 0, ES_WINDOW_PROPERTY_EMBED_OWNER);
|
|
|
|
if (application->createInstance) {
|
|
application->createInstance(&m);
|
|
} else {
|
|
EsMessagePostRemote(process->handle, &m);
|
|
}
|
|
|
|
if (application->useSingleInstance) {
|
|
application->singleInstance = instance;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ApplicationInstance *ApplicationInstanceCreate(_EsApplicationStartupInformation *startupInformation, ContainerWindow *container, bool hidden) {
|
|
ApplicationInstance *instance = (ApplicationInstance *) EsHeapAllocate(sizeof(ApplicationInstance), true);
|
|
WindowTab *tab = !hidden ? WindowTabCreate(container ?: ContainerWindowCreate()) : nullptr;
|
|
if (tab) tab->applicationInstance = instance;
|
|
instance->title[0] = ' ';
|
|
instance->titleBytes = 1;
|
|
instance->tab = tab;
|
|
desktop.allApplicationInstances.Add(instance);
|
|
|
|
if (ApplicationInstanceStart(startupInformation, instance)) {
|
|
if (!hidden) {
|
|
WindowTabActivate(tab);
|
|
if (!container) ContainerWindowShow(tab->container, 0, 0);
|
|
}
|
|
|
|
return instance;
|
|
} else {
|
|
if (!hidden) WindowTabDestroy(tab); // TODO Test this.
|
|
desktop.allApplicationInstances.FindAndDeleteSwap(instance, true);
|
|
EsHeapFree(instance);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
ApplicationProcess *ApplicationProcessFindByPID(EsObjectID pid) {
|
|
for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
|
|
ApplicationProcess *process = desktop.allApplicationProcesses[i];
|
|
|
|
if (process->id == pid) {
|
|
return process;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
InstalledApplication *ApplicationFindByPID(EsObjectID pid) {
|
|
ApplicationProcess *process = ApplicationProcessFindByPID(pid);
|
|
return process ? process->application : nullptr;
|
|
}
|
|
|
|
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);
|
|
|
|
InstalledApplication *application = ApplicationFindByPID(message->crash.pid);
|
|
|
|
if (application) {
|
|
if (application->singleProcess && application->singleProcess->id == message->crash.pid) {
|
|
application->singleProcess = nullptr;
|
|
}
|
|
|
|
application->singleInstance = nullptr;
|
|
|
|
if (desktop.installationState == INSTALLATION_STATE_INSTALLER && desktop.installer == application) {
|
|
// Restart the installer.
|
|
_EsApplicationStartupInformation startupInformation = { .id = desktop.installer->id };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr, true /* hidden */);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->process->id == message->crash.pid) {
|
|
if (instance->tab) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = APPLICATION_ID_DESKTOP_CRASHED };
|
|
ApplicationInstanceStart(&startupInformation, instance);
|
|
WindowTabActivate(instance->tab, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
EsProcessTerminate(processHandle, 1);
|
|
EsHandleClose(processHandle);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Document management:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void OpenDocumentListUpdated() {
|
|
if (desktop.fileManager && desktop.fileManager->singleProcess) {
|
|
EsMessage m = { .type = ES_MSG_FILE_MANAGER_DOCUMENT_UPDATE };
|
|
EsMessagePostRemote(desktop.fileManager->singleProcess->handle, &m);
|
|
}
|
|
}
|
|
|
|
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);
|
|
OpenDocumentListUpdated();
|
|
}
|
|
|
|
void OpenDocumentOpenReference(EsObjectID id) {
|
|
OpenDocument *document = desktop.openDocuments.Get(&id);
|
|
EsAssert(document->referenceCount && document->referenceCount < 0x10000000 /* sanity check */);
|
|
document->referenceCount++;
|
|
}
|
|
|
|
void OpenDocumentWithApplication(EsApplicationStartupRequest *startupRequest, ContainerWindow *container) {
|
|
bool foundDocument = false;
|
|
|
|
_EsApplicationStartupInformation startupInformation = {};
|
|
startupInformation.id = startupRequest->id;
|
|
startupInformation.flags = startupRequest->flags;
|
|
startupInformation.filePath = startupRequest->filePath;
|
|
startupInformation.filePathBytes = startupRequest->filePathBytes;
|
|
|
|
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;
|
|
|
|
OpenDocumentListUpdated();
|
|
}
|
|
|
|
ApplicationInstanceCreate(&startupInformation, container);
|
|
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("paths"), EsLiteral("temporary"), &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, bool failIfAlreadyExists) {
|
|
if (!instance->process) 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("paths"), EsLiteral("default_user_documents"), &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 || (failIfAlreadyExists && nameBytes != folderBytes + newNameBytes)) {
|
|
EsHeapFree(name);
|
|
m.tabOperation.error = nameBytes ? ES_ERROR_ALREADY_EXISTS : ES_ERROR_TOO_MANY_FILES_WITH_NAME;
|
|
EsMessagePostRemote(instance->process->handle, &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->process->handle, &m);
|
|
return;
|
|
}
|
|
|
|
OpenDocument document = {};
|
|
document.path = name;
|
|
document.pathBytes = nameBytes;
|
|
document.readHandle = file.handle;
|
|
document.id = ++desktop.currentDocumentID;
|
|
document.referenceCount++;
|
|
*desktop.openDocuments.Put(&document.id) = document;
|
|
|
|
instance->documentID = document.id;
|
|
|
|
{
|
|
// Tell the instance the chosen name and new containing folder for the document.
|
|
EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_RENAMED };
|
|
void *data = OpenDocumentGetRenameMessageData(instance->application, name, nameBytes, &m.tabOperation.bytes);
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
m.tabOperation.handle = EsConstantBufferCreate(data, m.tabOperation.bytes, instance->process->handle);
|
|
EsMessagePostRemote(instance->process->handle, &m);
|
|
EsHeapFree(data);
|
|
}
|
|
|
|
OpenDocumentListUpdated();
|
|
}
|
|
|
|
OpenDocument *document = desktop.openDocuments.Get(&instance->documentID);
|
|
|
|
if (!document) {
|
|
return;
|
|
}
|
|
|
|
if (document->currentWriter) {
|
|
m.tabOperation.error = ES_ERROR_OPERATION_BLOCKED;
|
|
} 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->process->handle, 0, 0);
|
|
EsHandleClose(fileHandle);
|
|
}
|
|
}
|
|
|
|
EsMessagePostRemote(instance->process->handle, &m);
|
|
}
|
|
|
|
void InstanceAnnouncePathMoved(InstalledApplication *fromApplication, const char *oldPath, size_t oldPathBytes, const char *newPath, size_t newPathBytes) {
|
|
// TODO Update the location of installed applications and other things in the configuration.
|
|
// TODO Replace fromApplication with something better.
|
|
|
|
if (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) {
|
|
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->process) continue;
|
|
|
|
size_t messageDataBytes;
|
|
void *messageData = OpenDocumentGetRenameMessageData(instance->application, newPath, newPathBytes, &messageDataBytes);
|
|
|
|
EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_RENAMED };
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
m.tabOperation.handle = EsConstantBufferCreate(messageData, messageDataBytes, instance->process->handle);
|
|
m.tabOperation.bytes = messageDataBytes;
|
|
EsMessagePostRemote(instance->process->handle, &m);
|
|
|
|
EsHeapFree(messageData);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fromApplication != desktop.fileManager && desktop.fileManager && desktop.fileManager->singleProcess) {
|
|
char *data = (char *) EsHeapAllocate(sizeof(size_t) * 2 + oldPathBytes + newPathBytes, false);
|
|
EsMemoryCopy(data + 0, &oldPathBytes, sizeof(size_t));
|
|
EsMemoryCopy(data + sizeof(size_t), &newPathBytes, sizeof(size_t));
|
|
EsMemoryCopy(data + sizeof(size_t) * 2, oldPath, oldPathBytes);
|
|
EsMemoryCopy(data + sizeof(size_t) * 2 + oldPathBytes, newPath, newPathBytes);
|
|
EsMessage m = {};
|
|
m.type = ES_MSG_FILE_MANAGER_PATH_MOVED;
|
|
m.user.context2.u = sizeof(size_t) * 2 + oldPathBytes + newPathBytes;
|
|
m.user.context1 = EsConstantBufferCreate(data, m.user.context2.u, desktop.fileManager->singleProcess->handle);
|
|
EsMessagePostRemote(desktop.fileManager->singleProcess->handle, &m);
|
|
EsHeapFree(data);
|
|
}
|
|
}
|
|
|
|
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 && desktop.fileManager->singleProcess) {
|
|
EsMessage m = {};
|
|
m.type = ES_MSG_FILE_MANAGER_FILE_MODIFIED;
|
|
m.user.context1 = EsConstantBufferCreate(document->path, document->pathBytes, desktop.fileManager->singleProcess->handle);
|
|
m.user.context2 = document->pathBytes;
|
|
EsMessagePostRemote(desktop.fileManager->singleProcess->handle, &m);
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->documentID != document->id) continue;
|
|
if (!instance->process) 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->process->handle, 0, 0);
|
|
EsMessagePostRemote(instance->process->handle, &m);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// Configuration file management:
|
|
//////////////////////////////////////////////////////
|
|
|
|
void ConfigurationLoadApplications() {
|
|
// Add applications provided by Desktop.
|
|
|
|
{
|
|
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
|
|
application->cName = (char *) "(blank tab)";
|
|
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->cName = (char *) "(crashed tab)";
|
|
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.
|
|
|
|
SystemConfigurationGroup *group = &api.systemConfigurationGroups[i];
|
|
|
|
if (0 != EsStringCompareRaw(group->sectionClass, group->sectionClassBytes, EsLiteral("application"))) {
|
|
continue;
|
|
}
|
|
|
|
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
|
|
|
|
application->cName = SystemConfigurationGroupReadString(group, EsLiteral("name"));
|
|
application->cExecutable = SystemConfigurationGroupReadString(group, EsLiteral("executable"));
|
|
application->settingsPath = SystemConfigurationGroupReadString(group, EsLiteral("settings_path"), &application->settingsPathBytes);
|
|
char *icon = SystemConfigurationGroupReadString(group, EsLiteral("icon"));
|
|
application->iconID = EsIconIDFromString(icon);
|
|
EsHeapFree(icon);
|
|
application->useSingleProcess = SystemConfigurationGroupReadInteger(group, EsLiteral("use_single_process"), false);
|
|
application->useSingleInstance = SystemConfigurationGroupReadInteger(group, EsLiteral("use_single_instance"), false);
|
|
application->hidden = SystemConfigurationGroupReadInteger(group, EsLiteral("hidden"), false);
|
|
application->id = EsIntegerParse(group->section, group->sectionBytes);
|
|
|
|
#define READ_PERMISSION(x, y) if (SystemConfigurationGroupReadInteger(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);
|
|
READ_PERMISSION("permission_start_application", APPLICATION_PERMISSION_START_APPLICATION);
|
|
|
|
desktop.installedApplications.Add(application);
|
|
|
|
if (SystemConfigurationGroupReadInteger(group, EsLiteral("is_file_manager"))) {
|
|
desktop.fileManager = application;
|
|
} else if (SystemConfigurationGroupReadInteger(group, EsLiteral("is_installer"))) {
|
|
desktop.installer = application;
|
|
}
|
|
|
|
if (SystemConfigurationGroupReadInteger(group, EsLiteral("background_service"))) {
|
|
_EsApplicationStartupInformation startupInformation = {};
|
|
startupInformation.id = application->id;
|
|
startupInformation.flags = ES_APPLICATION_STARTUP_BACKGROUND_SERVICE;
|
|
ApplicationInstanceCreate(&startupInformation, nullptr, true /* hidden */);
|
|
}
|
|
}
|
|
|
|
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++) {
|
|
SystemConfigurationGroup *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++) {
|
|
SystemConfigurationItem *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 = false;
|
|
} 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);
|
|
}
|
|
|
|
// 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
|
|
&& (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 || !instance->process) return;
|
|
|
|
WindowTab *tab = instance->tab;
|
|
|
|
EsProcessState state;
|
|
EsProcessGetState(instance->process->handle, &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 = {};
|
|
startupInformation.id = APPLICATION_ID_DESKTOP_CRASHED;
|
|
startupInformation.data = CRASHED_TAB_NOT_RESPONDING;
|
|
tab->notRespondingInstance = ApplicationInstanceCreate(&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->process->handle, &m);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DesktopSetup() {
|
|
// Get the installation state.
|
|
|
|
if (!desktop.setupDesktopUIComplete) {
|
|
desktop.installationState = EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("installation_state"));
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
{
|
|
EsRectangle screen;
|
|
EsSyscall(ES_SYSCALL_SCREEN_BOUNDS_GET, 0, (uintptr_t) &screen, 0, 0);
|
|
EsSyscall(ES_SYSCALL_SCREEN_WORK_AREA_SET, 0, (uintptr_t) &screen, 0, 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 *) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = APPLICATION_ID_DESKTOP_BLANK_TAB };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr);
|
|
});
|
|
|
|
desktop.taskBar.taskList.Initialise(panel, ES_CELL_FILL, ReorderListMessage, ES_STYLE_TASK_BAR_LIST);
|
|
desktop.taskBar.taskList.cName = "task list";
|
|
|
|
desktop.tasksButton = EsButtonCreate(panel, ES_ELEMENT_HIDDEN, ES_STYLE_TASK_BAR_BUTTON);
|
|
desktop.tasksButton->messageUser = TaskBarTasksButtonMessage;
|
|
|
|
EsButton *clockButton = EsButtonCreate(panel, ES_BUTTON_COMPACT, EsStyleIntern(&styleClockButton));
|
|
clockButton->cName = "current time";
|
|
EsThreadCreate(TaskBarClockUpdateThread, nullptr, clockButton);
|
|
|
|
EsButtonOnCommand(clockButton, [] (EsInstance *, EsElement *, EsCommand *) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = APPLICATION_ID_DESKTOP_SETTINGS };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr);
|
|
});
|
|
|
|
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();
|
|
});
|
|
|
|
// 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)) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = desktop.installedApplications[i]->id };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
EsHeapFree(firstApplication);
|
|
}
|
|
} else if (desktop.installationState == INSTALLATION_STATE_INSTALLER) {
|
|
// Start the installer.
|
|
|
|
if (!desktop.setupDesktopUIComplete) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = desktop.installer->id };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr, true /* hidden */);
|
|
}
|
|
}
|
|
|
|
#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
|
|
|
|
desktop.setupDesktopUIComplete = true;
|
|
}
|
|
|
|
bool /* returns false on fatal error */ DesktopSyscall(EsObjectID windowID, ApplicationProcess *process, uint8_t *buffer, size_t bytes, EsBuffer *pipe) {
|
|
EsMessageMutexCheck();
|
|
EsAssert(process);
|
|
|
|
ApplicationInstance *instance = ApplicationInstanceFindByWindowID(windowID);
|
|
InstalledApplication *application = process->application;
|
|
|
|
if (instance && instance->process != process) {
|
|
EsPrint("DesktopSyscall - Process %d cannot operate on instance %d.\n", process->id, instance->embeddedWindowID);
|
|
return false;
|
|
}
|
|
|
|
if (buffer[0] == DESKTOP_MSG_START_APPLICATION) {
|
|
if (~application->permissions & APPLICATION_PERMISSION_START_APPLICATION) return false;
|
|
|
|
// TODO Restricting what flags can be requested?
|
|
EsBuffer b = { .in = buffer + 1, .bytes = bytes - 1 };
|
|
EsApplicationStartupRequest request = {};
|
|
EsBufferReadInto(&b, &request, sizeof(EsApplicationStartupRequest));
|
|
request.filePath = (const char *) EsBufferRead(&b, request.filePathBytes);
|
|
|
|
if (!b.error) {
|
|
ContainerWindow *container = nullptr /* new container */;
|
|
|
|
if ((request.flags & ES_APPLICATION_STARTUP_IN_SAME_CONTAINER) && instance && instance->tab) {
|
|
container = instance->tab->container;
|
|
}
|
|
|
|
if (request.flags & ES_APPLICATION_STARTUP_NO_DOCUMENT) {
|
|
_EsApplicationStartupInformation startupInformation = {};
|
|
startupInformation.id = request.id;
|
|
startupInformation.filePath = request.filePath;
|
|
startupInformation.filePathBytes = request.filePathBytes;
|
|
startupInformation.flags = request.flags;
|
|
ApplicationInstanceCreate(&startupInformation, container);
|
|
} else {
|
|
OpenDocumentWithApplication(&request, container);
|
|
}
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_CREATE_CLIPBOARD_FILE && pipe) {
|
|
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 = process->id;
|
|
|
|
handle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, handle, process->handle, 0, 0);
|
|
|
|
EsHeapFree(path);
|
|
} else {
|
|
handle = ES_INVALID_HANDLE;
|
|
}
|
|
|
|
EsBufferWrite(pipe, &handle, sizeof(handle));
|
|
EsBufferWrite(pipe, &error, sizeof(error));
|
|
} else if (buffer[0] == DESKTOP_MSG_CLIPBOARD_PUT && bytes == sizeof(ClipboardInformation)
|
|
&& desktop.nextClipboardFile && desktop.nextClipboardProcessID == process->id) {
|
|
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->process) {
|
|
EsMessage m = { ES_MSG_PRIMARY_CLIPBOARD_UPDATED };
|
|
m.tabOperation.id = foreground->embeddedWindowID;
|
|
EsMessagePostRemote(foreground->process->handle, &m);
|
|
}
|
|
} else {
|
|
EsHandleClose(desktop.nextClipboardFile);
|
|
}
|
|
|
|
desktop.nextClipboardFile = ES_INVALID_HANDLE;
|
|
desktop.nextClipboardProcessID = 0;
|
|
} else if (buffer[0] == DESKTOP_MSG_CLIPBOARD_GET && pipe) {
|
|
EsHandle fileHandle = desktop.clipboardFile
|
|
? EsSyscall(ES_SYSCALL_HANDLE_SHARE, desktop.clipboardFile, process->handle, 1 /* ES_FILE_READ_SHARED */, 0) : ES_INVALID_HANDLE;
|
|
EsBufferWrite(pipe, &desktop.clipboardInformation, sizeof(desktop.clipboardInformation));
|
|
EsBufferWrite(pipe, &fileHandle, sizeof(fileHandle));
|
|
} else if (buffer[0] == DESKTOP_MSG_SYSTEM_CONFIGURATION_GET && pipe) {
|
|
ConfigurationWriteSectionsToBuffer("font", nullptr, false, pipe);
|
|
ConfigurationWriteSectionsToBuffer(nullptr, "ui_fonts", false, pipe);
|
|
|
|
if (application->permissions & APPLICATION_PERMISSION_ALL_FILES) {
|
|
ConfigurationWriteSectionsToBuffer(nullptr, "paths", false, pipe);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_REQUEST_SHUTDOWN) {
|
|
if (~application->permissions & APPLICATION_PERMISSION_SHUTDOWN) return false;
|
|
ShutdownModalCreate();
|
|
} else if (buffer[0] == DESKTOP_MSG_FILE_TYPES_GET && pipe) {
|
|
if (~application->permissions & APPLICATION_PERMISSION_VIEW_FILE_TYPES) return false;
|
|
ConfigurationWriteSectionsToBuffer("file_type", nullptr, false, pipe);
|
|
} else if (buffer[0] == DESKTOP_MSG_ANNOUNCE_PATH_MOVED && bytes > 1 + sizeof(uintptr_t) * 2) {
|
|
if (~application->permissions & APPLICATION_PERMISSION_ALL_FILES) return false;
|
|
|
|
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 != bytes) {
|
|
return true;
|
|
}
|
|
|
|
const char *oldPath = (const char *) buffer + 1 + sizeof(uintptr_t) * 2;
|
|
const char *newPath = (const char *) buffer + 1 + sizeof(uintptr_t) * 2 + oldPathBytes;
|
|
|
|
InstanceAnnouncePathMoved(application, oldPath, oldPathBytes, newPath, newPathBytes);
|
|
} else if (buffer[0] == DESKTOP_MSG_START_USER_TASK && pipe) {
|
|
if (!process || !process->instanceCount) {
|
|
return false;
|
|
}
|
|
|
|
// HACK User tasks use an embedded window object for IPC.
|
|
// This allows us to basically treat them like other instances.
|
|
|
|
EsHandle windowHandle = EsSyscall(ES_SYSCALL_WINDOW_CREATE, ES_WINDOW_NORMAL, 0, 0, 0);
|
|
ApplicationInstance *instance = (ApplicationInstance *) EsHeapAllocate(sizeof(ApplicationInstance), true);
|
|
bool added = false;
|
|
|
|
if (windowHandle && instance) {
|
|
added = desktop.allApplicationInstances.Add(instance);
|
|
}
|
|
|
|
if (!windowHandle || !instance || !added) {
|
|
if (windowHandle) EsHandleClose(windowHandle);
|
|
if (instance) EsHeapFree(instance);
|
|
|
|
EsHandle invalid = ES_INVALID_HANDLE;
|
|
EsBufferWrite(pipe, &invalid, sizeof(invalid));
|
|
return true;
|
|
}
|
|
|
|
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->process = process;
|
|
instance->process->instanceCount++;
|
|
instance->application = application;
|
|
|
|
EsHandle targetWindowHandle = EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, windowHandle, process->handle, 0, ES_WINDOW_PROPERTY_EMBED_OWNER);
|
|
EsBufferWrite(pipe, &targetWindowHandle, sizeof(targetWindowHandle));
|
|
|
|
desktop.allOngoingUserTasks.Add(instance);
|
|
TaskBarTasksButtonUpdate();
|
|
} else if (buffer[0] == DESKTOP_MSG_QUERY_OPEN_DOCUMENT) {
|
|
if (~application->permissions & APPLICATION_PERMISSION_ALL_FILES) return false;
|
|
|
|
EsObjectID id = 0;
|
|
|
|
for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) {
|
|
OpenDocument *document = &desktop.openDocuments[i];
|
|
|
|
if (0 == EsStringCompare(document->path, document->pathBytes, (char *) buffer + 1, bytes - 1)) {
|
|
id = document->id;
|
|
break;
|
|
}
|
|
}
|
|
|
|
EsOpenDocumentInformation information;
|
|
EsMemoryZero(&information, sizeof(information));
|
|
|
|
if (id) {
|
|
information.isOpen = true;
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationInstances.Length(); i++) {
|
|
ApplicationInstance *instance = desktop.allApplicationInstances[i];
|
|
|
|
if (instance->documentID == id) {
|
|
information.isModified = instance->tab && (instance->tab->closeButton->customStyleState & THEME_STATE_CHECKED);
|
|
information.applicationNameBytes = MinimumInteger(EsCStringLength(instance->application->cName),
|
|
sizeof(information.applicationName));
|
|
EsMemoryCopy(information.applicationName, instance->application->cName, information.applicationNameBytes);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
EsBufferWrite(pipe, &information, sizeof(information));
|
|
} else if (buffer[0] == DESKTOP_MSG_LIST_OPEN_DOCUMENTS) {
|
|
if (~application->permissions & APPLICATION_PERMISSION_ALL_FILES) return false;
|
|
|
|
size_t count = desktop.openDocuments.Count();
|
|
EsBufferWrite(pipe, &count, sizeof(size_t));
|
|
|
|
for (uintptr_t i = 0; i < count; i++) {
|
|
OpenDocument *document = &desktop.openDocuments[i];
|
|
EsBufferWrite(pipe, &document->pathBytes, sizeof(size_t));
|
|
EsBufferWrite(pipe, document->path, document->pathBytes);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_RUN_TEMPORARY_APPLICATION) {
|
|
InstalledApplication *requestingApplication = application;
|
|
if (~requestingApplication->permissions & APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION) return false;
|
|
|
|
for (uintptr_t i = 0; i < desktop.installedApplications.Length(); i++) {
|
|
if (EsCStringLength(desktop.installedApplications[i]->cExecutable) == bytes - 1
|
|
&& 0 == EsMemoryCompare(desktop.installedApplications[i]->cExecutable, buffer + 1, bytes - 1)) {
|
|
_EsApplicationStartupInformation startupInformation = { .id = desktop.installedApplications[i]->id };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
InstalledApplication *temporaryApplication = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
|
|
if (!temporaryApplication) return true;
|
|
temporaryApplication->temporary = true;
|
|
temporaryApplication->hidden = true;
|
|
temporaryApplication->useSingleProcess = true;
|
|
temporaryApplication->cExecutable = (char *) EsHeapAllocate(bytes, false);
|
|
if (!temporaryApplication->cExecutable) { EsHeapFree(temporaryApplication); return true; }
|
|
EsMemoryCopy(temporaryApplication->cExecutable, buffer + 1, bytes - 1);
|
|
temporaryApplication->cExecutable[bytes - 1] = 0;
|
|
static int64_t nextTemporaryID = -1;
|
|
temporaryApplication->id = nextTemporaryID--;
|
|
temporaryApplication->cName = (char *) EsHeapAllocate(32, false);
|
|
if (!temporaryApplication->cName) { EsHeapFree(temporaryApplication->cExecutable); EsHeapFree(temporaryApplication); return true; }
|
|
for (int i = 1; i < 31; i++) temporaryApplication->cName[i] = (EsRandomU8() % 26) + 'a';
|
|
temporaryApplication->cName[0] = '_', temporaryApplication->cName[31] = 0;
|
|
EsHandle handle;
|
|
EsError error = TemporaryFileCreate(&handle, &temporaryApplication->settingsPath, &temporaryApplication->settingsPathBytes, ES_NODE_DIRECTORY);
|
|
if (error == ES_SUCCESS) EsHandleClose(handle);
|
|
desktop.installedApplications.Add(temporaryApplication);
|
|
_EsApplicationStartupInformation startupInformation = { .id = temporaryApplication->id };
|
|
ApplicationInstanceCreate(&startupInformation, nullptr);
|
|
} else if ((buffer[0] == DESKTOP_MSG_SET_TITLE || buffer[0] == DESKTOP_MSG_SET_ICON) && instance) {
|
|
if (buffer[0] == DESKTOP_MSG_SET_TITLE) {
|
|
instance->titleBytes = EsStringFormat(instance->title, sizeof(instance->title), "%s",
|
|
bytes - 1, buffer + 1);
|
|
} else {
|
|
if (bytes == 5) {
|
|
EsMemoryCopy(&instance->iconID, buffer + 1, sizeof(uint32_t));
|
|
}
|
|
}
|
|
|
|
if (instance->tab) {
|
|
EsMemoryCopy(instance->tab->title, instance->title, instance->titleBytes);
|
|
instance->tab->titleBytes = instance->titleBytes;
|
|
instance->tab->iconID = instance->iconID;
|
|
|
|
instance->tab->Repaint(true);
|
|
|
|
if (instance->tab == instance->tab->container->active && instance->tab->container->taskBarButton) {
|
|
instance->tab->container->taskBarButton->Repaint(true);
|
|
}
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_SET_MODIFIED && bytes == 2 && instance) {
|
|
if (instance->tab) {
|
|
EsButtonSetCheck(instance->tab->closeButton, buffer[1] ? ES_CHECK_CHECKED : ES_CHECK_UNCHECKED, false);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_SET_PROGRESS && bytes == 1 + sizeof(double) && instance->isUserTask && instance) {
|
|
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 && instance) {
|
|
ApplicationInstanceRequestSave(instance, (const char *) buffer + 1, bytes - 1, false);
|
|
} else if (buffer[0] == DESKTOP_MSG_RENAME && instance) {
|
|
const char *newName = (const char *) buffer + 1;
|
|
size_t newNameBytes = bytes - 1;
|
|
OpenDocument *document = desktop.openDocuments.Get(&instance->documentID);
|
|
|
|
if (!instance->documentID) {
|
|
ApplicationInstanceRequestSave(instance, newName, newNameBytes, true);
|
|
} else if (document) {
|
|
size_t folderBytes = 0, oldPathBytes, newPathBytes;
|
|
|
|
for (uintptr_t i = 0; i < document->pathBytes; i++) {
|
|
if (document->path[i] == '/') {
|
|
folderBytes = i;
|
|
}
|
|
}
|
|
|
|
char *oldPath = EsStringAllocateAndFormat(&oldPathBytes, "%s", document->pathBytes, document->path);
|
|
char *newPath = EsStringAllocateAndFormat(&newPathBytes, "%s/%s", folderBytes, document->path, newNameBytes, newName);
|
|
|
|
if (oldPathBytes == newPathBytes && 0 == EsMemoryCompare(oldPath, newPath, oldPathBytes)) {
|
|
// Same name.
|
|
} else {
|
|
EsMessage m = {};
|
|
m.type = ES_MSG_INSTANCE_RENAME_RESPONSE;
|
|
m.tabOperation.id = instance->embeddedWindowID;
|
|
m.tabOperation.error = EsPathMove(oldPath, oldPathBytes, newPath, newPathBytes);
|
|
EsMessagePostRemote(instance->process->handle, &m);
|
|
|
|
if (m.tabOperation.error == ES_SUCCESS) {
|
|
InstanceAnnouncePathMoved(nullptr, oldPath, oldPathBytes, newPath, newPathBytes);
|
|
}
|
|
}
|
|
|
|
EsHeapFree(oldPath);
|
|
EsHeapFree(newPath);
|
|
}
|
|
} else if (buffer[0] == DESKTOP_MSG_COMPLETE_SAVE && instance) {
|
|
ApplicationInstanceCompleteSave(instance);
|
|
} else if (buffer[0] == DESKTOP_MSG_SHOW_IN_FILE_MANAGER && instance) {
|
|
// 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.id = desktop.fileManager->id;
|
|
startupInformation.filePath = document->path;
|
|
startupInformation.filePathBytes = document->pathBytes;
|
|
ApplicationInstanceCreate(&startupInformation, instance->tab->container);
|
|
}
|
|
} else {
|
|
EsPrint("DesktopSyscall - Received unhandled message %d.\n", buffer[0]);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void EmbeddedWindowDestroyed(EsObjectID id) {
|
|
ApplicationInstance *instance = ApplicationInstanceFindByWindowID(id, true /* remove if found */);
|
|
if (!instance) return;
|
|
|
|
EsMenuCloseAll(); // The tab will be destroyed, but menus might be keeping pointers to it.
|
|
EsHandleClose(instance->embeddedWindowHandle);
|
|
|
|
ApplicationInstanceCleanup(instance);
|
|
|
|
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);
|
|
}
|
|
|
|
void DesktopSendMessage(EsMessage *message) {
|
|
if (!desktop.clockReadyEvent) {
|
|
desktop.clockReadyEvent = EsEventCreate(false);
|
|
}
|
|
|
|
if (message->type == ES_MSG_EMBEDDED_WINDOW_DESTROYED) {
|
|
EmbeddedWindowDestroyed(message->embeddedWindowDestroyedID);
|
|
} else if (message->type == ES_MSG_APPLICATION_CRASH) {
|
|
ApplicationInstanceCrashed(message);
|
|
} else if (message->type == ES_MSG_DEVICE_CONNECTED) {
|
|
EsHandle handle = message->device.handle;
|
|
|
|
EsPrint("Desktop received ES_MSG_DEVICE_CONNECTED with ID %d and type %z.\n",
|
|
message->device.id, EnumLookupNameFromValue(enumStrings_EsDeviceType, message->device.type));
|
|
|
|
for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
|
|
ApplicationProcess *process = desktop.allApplicationProcesses[i];
|
|
if (!process->application) continue;
|
|
|
|
if ((process->application->permissions & APPLICATION_PERMISSION_ALL_DEVICES)
|
|
|| ((process->application->permissions & APPLICATION_PERMISSION_ALL_FILES) && message->device.type == ES_DEVICE_FILE_SYSTEM)) {
|
|
message->device.handle = EsSyscall(ES_SYSCALL_HANDLE_SHARE, handle, process->handle, 0, 0);
|
|
EsMessagePostRemote(process->handle, message);
|
|
}
|
|
}
|
|
|
|
if (message->device.type == ES_DEVICE_CLOCK) {
|
|
EsDateComponents reading;
|
|
uint64_t linear;
|
|
|
|
if (ES_SUCCESS == EsDeviceControl(handle, ES_DEVICE_CONTROL_CLOCK_READ, &reading, &linear)) {
|
|
// TODO Scheduler timer is not particularly accurate, so we should periodically resynchronize with the clock.
|
|
api.global->schedulerTimeOffset = (linear ?: DateToLinear(&reading))
|
|
- api.global->schedulerTimeMs
|
|
+ EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("clock_offset_ms"), 0);
|
|
EsEventSet(desktop.clockReadyEvent);
|
|
}
|
|
} else if (message->device.type == ES_DEVICE_GRAPHICS_TARGET) {
|
|
if (desktop.setupDesktopUIComplete) {
|
|
DesktopSetup(); // Refresh desktop UI.
|
|
} else {
|
|
// The screen resolution will be correctly queried in DesktopSetup.
|
|
}
|
|
} else if (message->device.type == ES_DEVICE_FILE_SYSTEM) {
|
|
if (EsDeviceControl(message->device.handle, ES_DEVICE_CONTROL_FS_IS_BOOT, 0, 0) == 1) {
|
|
// Mount the boot file system at "0:".
|
|
MountPointAdd("0:", 2, message->device.handle, false);
|
|
api.foundBootFileSystem = true;
|
|
}
|
|
}
|
|
} else if (message->type == ES_MSG_DEVICE_DISCONNECTED) {
|
|
for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
|
|
ApplicationProcess *process = desktop.allApplicationProcesses[i];
|
|
if (!process->application) continue;
|
|
|
|
if ((process->application->permissions & APPLICATION_PERMISSION_ALL_DEVICES)
|
|
|| ((process->application->permissions & APPLICATION_PERMISSION_ALL_FILES) && message->device.type == ES_DEVICE_FILE_SYSTEM)) {
|
|
EsMessagePostRemote(process->handle, message);
|
|
}
|
|
}
|
|
} 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();
|
|
DesktopSendMessage(message);
|
|
}
|
|
}
|