native UI for build core

This commit is contained in:
nakst 2021-12-30 17:36:03 +00:00
parent d66fdf114c
commit 420bdf698a
32 changed files with 771 additions and 232 deletions

View File

@ -582,6 +582,14 @@ void FolderRefresh(Folder *folder) {
return;
}
if (!folder->attachedInstances.Length()) {
// The folder does not have any attached instances, so just destroy it.
loadedFolders.FindAndDeleteSwap(folder, true);
foldersWithNoAttachedInstances.FindAndDeleteSwap(folder, true);
FolderDestroy(folder);
return;
}
folder->refreshing = true;
Array<Instance *> instancesToRefresh = {};
@ -597,3 +605,21 @@ void FolderRefresh(Folder *folder) {
instancesToRefresh.Free();
}
void FolderRefreshAllFrom(String prefix) {
Array<Folder *> foldersToRefresh = {};
for (uintptr_t i = 0; i < loadedFolders.Length(); i++) {
if (!loadedFolders[i]->refreshing
&& loadedFolders[i]->itemHandler->type == NAMESPACE_HANDLER_FILE_SYSTEM
&& PathHasPrefix(loadedFolders[i]->path, prefix)) {
foldersToRefresh.Add(loadedFolders[i]);
}
}
for (uintptr_t i = 0; i < foldersToRefresh.Length(); i++) {
FolderRefresh(foldersToRefresh[i]);
}
foldersToRefresh.Free();
}

View File

@ -608,9 +608,14 @@ void _start() {
EsConstantBufferRead(message->user.context1.u, data);
String oldPath = StringFromLiteralWithSize(paths, bytes[0]);
String newPath = StringFromLiteralWithSize(paths + bytes[0], bytes[1]);
FolderPathMoved(oldPath, newPath, false);
size_t pathSectionCount;
if (oldPath.bytes) {
FolderPathMoved(oldPath, newPath, false);
} else {
FolderRefreshAllFrom(newPath);
}
pathSectionCount = PathCountSections(oldPath);
for (uintptr_t i = 0; i < pathSectionCount; i++) {

View File

@ -834,7 +834,7 @@ int ListCallback(EsElement *element, EsMessage *message) {
INTERFACE_STRING(FileManagerCannotOpenSystemFile),
ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON);
} else {
EsApplicationRunTemporary(instance, STRING(path));
EsApplicationRunTemporary(STRING(path));
}
StringDestroy(&path);
@ -847,7 +847,8 @@ int ListCallback(EsElement *element, EsMessage *message) {
request.id = fileType->openHandler;
request.filePath = path.text;
request.filePathBytes = path.bytes;
request.flags = EsKeyboardIsCtrlHeld() ? ES_APPLICATION_STARTUP_IN_SAME_CONTAINER : ES_FLAGS_DEFAULT;
request.flags = EsKeyboardIsCtrlHeld() || message->chooseItem.source == ES_LIST_VIEW_CHOOSE_ITEM_MIDDLE_CLICK
? ES_APPLICATION_STARTUP_IN_SAME_CONTAINER : ES_FLAGS_DEFAULT;
EsApplicationStart(instance, &request);
StringDestroy(&path);
} else {
@ -1166,7 +1167,7 @@ void InstanceCreateUI(Instance *instance) {
EsApplicationStartupRequest startupRequest = EsInstanceGetStartupRequest(instance);
String path;
if (startupRequest.flags & ES_APPLICATION_STARTUP_MANUAL_PATH) {
if (startupRequest.filePathBytes) {
uintptr_t directoryEnd = startupRequest.filePathBytes;
for (uintptr_t i = 0; i < (uintptr_t) startupRequest.filePathBytes; i++) {

View File

@ -318,7 +318,7 @@ int InstanceCallback(Instance *instance, EsMessage *message) {
EsPanel *titleRow = EsPanelCreate(instance->fontPreview, ES_CELL_H_CENTER | ES_PANEL_HORIZONTAL, &styleFontInformationRow);
EsIconDisplayCreate(titleRow, ES_FLAGS_DEFAULT, ES_STYLE_ICON_DISPLAY, ES_ICON_FONT_X_GENERIC);
EsTextDisplayCreate(titleRow, ES_FLAGS_DEFAULT, ES_STYLE_TEXT_HEADING0, message->instanceOpen.name, message->instanceOpen.nameBytes);
EsTextDisplayCreate(titleRow, ES_FLAGS_DEFAULT, ES_STYLE_TEXT_HEADING0, message->instanceOpen.nameOrPath, message->instanceOpen.nameOrPathBytes);
EsSpacerCreate(instance->fontPreview, ES_FLAGS_DEFAULT, 0, 0, 20);
int sizes[] = { 12, 18, 24, 36, 48, 60, 72, 0 };

View File

@ -712,18 +712,18 @@ int InstanceCallback(Instance *instance, EsMessage *message) {
} else if (message->type == ES_MSG_INSTANCE_SAVE) {
// TODO Error handling.
uintptr_t extensionOffset = message->instanceSave.nameBytes;
uintptr_t extensionOffset = message->instanceSave.nameOrPathBytes;
while (extensionOffset) {
if (message->instanceSave.name[extensionOffset - 1] == '.') {
if (message->instanceSave.nameOrPath[extensionOffset - 1] == '.') {
break;
} else {
extensionOffset--;
}
}
const char *extension = extensionOffset ? message->instanceSave.name + extensionOffset : "png";
size_t extensionBytes = extensionOffset ? message->instanceSave.nameBytes - extensionOffset : 3;
const char *extension = extensionOffset ? message->instanceSave.nameOrPath + extensionOffset : "png";
size_t extensionBytes = extensionOffset ? message->instanceSave.nameOrPathBytes - extensionOffset : 3;
uint32_t *bits;
size_t width, height, stride;

View File

@ -173,7 +173,8 @@ void MessageLoopThread(EsGeneric) {
textboxOutput = EsTextboxCreate(panel, ES_TEXTBOX_MULTILINE | ES_CELL_FILL, &styleMonospacedTextbox);
EsSpacerCreate(panel, ES_CELL_H_FILL, ES_STYLE_SEPARATOR_HORIZONTAL);
textboxInput = EsTextboxCreate(panel, ES_CELL_H_FILL, &styleMonospacedTextbox);
EsTextboxEnableSmartQuotes(textboxInput, false);
EsTextboxEnableSmartReplacement(textboxInput, false);
EsTextboxSetReadOnly(textboxOutput, true);
textboxInput->messageUser = ProcessTextboxInputMessage;
EsElementFocus(textboxInput);
EsEventSet(commandEvent); // Ready to receive output.

View File

@ -280,6 +280,8 @@ void InitialiseInstance(EsInstance *instance) {
void _start() {
_init();
EsApplicationRunTemporary(EsLiteral("what"));
while (true) {
EsMessage *message = EsMessageReceive();

View File

@ -3,6 +3,7 @@ name=Test
icon=icon_system_software_install
use_single_process=1
permission_all_files=1
permission_run_temporary_application=1
[build]
source=apps/test.cpp

View File

@ -123,7 +123,7 @@ void SetLanguage(Instance *instance, uint32_t newLanguage) {
instance->syntaxHighlightingLanguage = newLanguage;
EsTextboxSetupSyntaxHighlighting(instance->textboxDocument, newLanguage);
EsTextboxEnableSmartQuotes(instance->textboxDocument, !newLanguage);
EsTextboxEnableSmartReplacement(instance->textboxDocument, !newLanguage);
}
void FormatPopupCreate(Instance *instance) {
@ -285,11 +285,11 @@ int InstanceCallback(Instance *instance, EsMessage *message) {
EsTextboxSetSelection(instance->textboxDocument, 0, 0, 0, 0);
EsElementRelayout(instance->textboxDocument);
if (StringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".c"), true)
|| StringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".cpp"), true)
|| StringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".h"), true)) {
if (StringEndsWith(message->instanceOpen.nameOrPath, message->instanceOpen.nameOrPathBytes, EsLiteral(".c"), true)
|| StringEndsWith(message->instanceOpen.nameOrPath, message->instanceOpen.nameOrPathBytes, EsLiteral(".cpp"), true)
|| StringEndsWith(message->instanceOpen.nameOrPath, message->instanceOpen.nameOrPathBytes, EsLiteral(".h"), true)) {
SetLanguage(instance, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_C);
} else if (StringEndsWith(message->instanceOpen.name, message->instanceOpen.nameBytes, EsLiteral(".ini"), true)) {
} else if (StringEndsWith(message->instanceOpen.nameOrPath, message->instanceOpen.nameOrPathBytes, EsLiteral(".ini"), true)) {
SetLanguage(instance, ES_SYNTAX_HIGHLIGHTING_LANGUAGE_INI);
} else {
SetLanguage(instance, 0);

View File

@ -177,6 +177,7 @@ char *SystemConfigurationGroupReadString(EsSystemConfigurationGroup *group, cons
int64_t SystemConfigurationGroupReadInteger(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes, int64_t defaultValue = 0);
MountPoint *NodeFindMountPoint(const char *prefix, size_t prefixBytes);
EsWindow *WindowFromWindowID(EsObjectID id);
void POSIXCleanup();
extern "C" void _init();
struct ProcessMessageTiming {
@ -626,14 +627,14 @@ void _EsOpenDocumentEnumerate(EsBuffer *outputBuffer) {
MessageDesktop(&m, 1, ES_INVALID_HANDLE, outputBuffer);
}
void EsApplicationRunTemporary(EsInstance *instance, const char *path, ptrdiff_t pathBytes) {
void EsApplicationRunTemporary(const char *path, ptrdiff_t pathBytes) {
if (pathBytes == -1) pathBytes = EsCStringLength(path);
char *buffer = (char *) EsHeapAllocate(pathBytes + 1, false);
if (buffer) {
buffer[0] = DESKTOP_MSG_RUN_TEMPORARY_APPLICATION;
EsMemoryCopy(buffer + 1, path, pathBytes);
MessageDesktop(buffer, pathBytes + 1, instance->window->handle);
MessageDesktop(buffer, pathBytes + 1);
EsHeapFree(buffer);
}
}
@ -682,8 +683,10 @@ void InstanceClose(EsInstance *instance) {
if (apiInstance->startupInformation->filePathBytes) {
cTitle = interfaceString_FileCloseWithModificationsTitle;
contentBytes = EsStringFormat(content, sizeof(content), interfaceString_FileCloseWithModificationsContent,
apiInstance->startupInformation->filePathBytes, apiInstance->startupInformation->filePath);
const char *name;
ptrdiff_t nameBytes;
PathGetName(apiInstance->startupInformation->filePath, apiInstance->startupInformation->filePathBytes, &name, &nameBytes);
contentBytes = EsStringFormat(content, sizeof(content), interfaceString_FileCloseWithModificationsContent, nameBytes, name);
} else {
cTitle = interfaceString_FileCloseNewTitle;
contentBytes = EsStringFormat(content, sizeof(content), interfaceString_FileCloseNewContent,
@ -870,8 +873,12 @@ EsInstance *_EsInstanceCreate(size_t bytes, EsMessage *message, const char *appl
EsWindowSetTitle(instance->window, nullptr, 0);
if (apiInstance->startupInformation && apiInstance->startupInformation->readHandle) {
const char *name;
ptrdiff_t nameBytes;
PathGetName(apiInstance->startupInformation->filePath, apiInstance->startupInformation->filePathBytes, &name, &nameBytes);
InstanceCreateFileStore(apiInstance, apiInstance->startupInformation->readHandle);
EsWindowSetTitle(instance->window, apiInstance->startupInformation->filePath, apiInstance->startupInformation->filePathBytes);
EsWindowSetTitle(instance->window, name, nameBytes);
EsCommandSetDisabled(&apiInstance->commandShowInFileManager, false);
// HACK Delay sending the instance open message so that application has a chance to initialise the instance.
@ -989,8 +996,8 @@ void InstanceSendOpenMessage(EsInstance *instance, bool update) {
APIInstance *apiInstance = (APIInstance *) instance->_private;
EsMessage m = { .type = ES_MSG_INSTANCE_OPEN };
m.instanceOpen.name = apiInstance->startupInformation->filePath;
m.instanceOpen.nameBytes = apiInstance->startupInformation->filePathBytes;
m.instanceOpen.nameOrPath = apiInstance->startupInformation->filePath;
m.instanceOpen.nameOrPathBytes = apiInstance->startupInformation->filePathBytes;
m.instanceOpen.file = apiInstance->fileStore;
m.instanceOpen.update = update;
@ -1061,6 +1068,9 @@ EsMessage *EsMessageReceive() {
EsAssert(!api.workQueue.Length());
api.workThreads.Free();
api.workQueue.Free();
#ifdef ENABLE_POSIX_SUBSYSTEM
POSIXCleanup();
#endif
MemoryLeakDetectorCheckpoint(&heap);
EsPrint("ES_MSG_APPLICATION_EXIT - Heap allocation count: %d (%d from malloc).\n", heap.allocationsCount, mallocCount);
#endif
@ -1147,8 +1157,8 @@ EsMessage *EsMessageReceive() {
m.instanceSave.file->type = FILE_STORE_HANDLE;
m.instanceSave.file->handles = 1;
m.instanceSave.name = instance->startupInformation->filePath;
m.instanceSave.nameBytes = instance->startupInformation->filePathBytes;
m.instanceSave.nameOrPath = instance->startupInformation->filePath;
m.instanceSave.nameOrPathBytes = instance->startupInformation->filePathBytes;
if (m.instanceSave.file->error == ES_SUCCESS && _instance->callback && _instance->callback(_instance, &m)) {
// The instance callback will have called EsInstanceSaveComplete.
@ -1227,7 +1237,10 @@ EsMessage *EsMessageReceive() {
instance->startupInformation->containingFolderBytes);
instance->startupInformation->filePath = filePath;
instance->startupInformation->containingFolder = containingFolder;
EsWindowSetTitle(_instance->window, filePath, instance->startupInformation->filePathBytes);
const char *name;
ptrdiff_t nameBytes;
PathGetName(filePath, instance->startupInformation->filePathBytes, &name, &nameBytes);
EsWindowSetTitle(_instance->window, name, nameBytes);
}
}
@ -1395,10 +1408,13 @@ void EsInstanceSaveComplete(EsInstance *instance, EsFileStore *file, bool succes
MessageDesktop(buffer, 1, instance->window->handle);
if (success) {
const char *name;
ptrdiff_t nameBytes;
PathGetName(apiInstance->startupInformation->filePath, apiInstance->startupInformation->filePathBytes, &name, &nameBytes);
EsInstanceSetModified(instance, false);
size_t messageBytes;
char *message = EsStringAllocateAndFormat(&messageBytes, "Saved to %s", // TODO Localization.
apiInstance->startupInformation->filePathBytes, apiInstance->startupInformation->filePath);
char *message = EsStringAllocateAndFormat(&messageBytes, interfaceString_FileSaveAnnouncement, nameBytes, name);
EsAnnouncementShow(instance->window, ES_FLAGS_DEFAULT, -1, -1, message, messageBytes);
EsHeapFree(message);
EsCommandSetDisabled(&apiInstance->commandShowInFileManager, false);
@ -1596,7 +1612,8 @@ void EsCommandAddButton(EsCommand *command, EsButton *button) {
button->state |= UI_STATE_COMMAND_BUTTON;
EsElementSetEnabled(button, command->enabled);
EsButtonSetCheck(button, command->check); // Set the check before setting the callback, so that it doesn't get called.
EsButtonOnCommand(button, command->callback, command);
EsButtonOnCommand(button, command->callback);
button->command = command;
}
EsCommand *EsCommandRegister(EsCommand *command, EsInstance *_instance,
@ -1654,7 +1671,9 @@ void EsCommandSetCallback(EsCommand *command, EsCommandCallback callback) {
for (uintptr_t i = 0; i < ArrayLength(command->elements); i++) {
if (command->elements[i]->state & UI_STATE_COMMAND_BUTTON) {
EsButtonOnCommand((EsButton *) command->elements[i], callback, command);
EsButton *button = (EsButton *) command->elements[i];
EsAssert(button->command == command);
button->onCommand = callback;
}
}
}
@ -2156,6 +2175,9 @@ char *EsPOSIXConvertPath(const char *, size_t *, bool) {
EsAssert(false);
return nullptr;
}
void POSIXCleanup() {
}
#else
EsProcessStartupInformation *ProcessGetStartupInformation() {
return api.startupInformation;

View File

@ -1586,10 +1586,11 @@ void ApplicationInstanceCleanup(ApplicationInstance *instance) {
instance->application = nullptr;
}
void PathGetNameAndContainingFolder(const char *path, ptrdiff_t pathBytes,
const char **name, ptrdiff_t *nameBytes,
const char **containingFolder, ptrdiff_t *containingFolderBytes,
EsVolumeInformation *volumeInformation /* needs to be allocated outside */) {
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);
}
@ -1604,8 +1605,11 @@ void PathGetNameAndContainingFolder(const char *path, ptrdiff_t pathBytes,
if (path[i - 1] == '/') {
if (!containingFolderEnd) {
containingFolderEnd = path + i - 1;
*name = path + i;
*nameBytes = pathBytes - i;
if (~application->permissions & APPLICATION_PERMISSION_ALL_FILES) {
*name = path + i;
*nameBytes = pathBytes - i;
}
} else {
*containingFolder = path + i;
*containingFolderBytes = containingFolderEnd - *containingFolder;
@ -1624,11 +1628,11 @@ void PathGetNameAndContainingFolder(const char *path, ptrdiff_t pathBytes,
}
}
void *OpenDocumentGetRenameMessageData(const char *path, size_t pathBytes, size_t *bytes) {
void *OpenDocumentGetRenameMessageData(InstalledApplication *application, const char *path, size_t pathBytes, size_t *bytes) {
EsVolumeInformation volumeInformation;
const char *name, *containingFolder;
ptrdiff_t nameBytes, containingFolderBytes;
PathGetNameAndContainingFolder(path, pathBytes, &name, &nameBytes, &containingFolder, &containingFolderBytes, &volumeInformation);
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));
@ -1638,6 +1642,24 @@ void *OpenDocumentGetRenameMessageData(const char *path, size_t pathBytes, size_
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);
}
bool ApplicationInstanceStart(int64_t applicationID, _EsApplicationStartupInformation *startupInformation, ApplicationInstance *instance) {
if (desktop.inShutdown) {
return false;
@ -1706,6 +1728,7 @@ bool ApplicationInstanceStart(int64_t applicationID, _EsApplicationStartupInform
ES_FILE_READ | ES_NODE_FAIL_IF_NOT_FOUND, &executableNode);
if (ES_CHECK_ERROR(error)) {
ApplicationTemporaryDestroy(application);
_EsApplicationStartupInformation s = {};
s.data = CRASHED_TAB_INVALID_EXECUTABLE;
return ApplicationInstanceStart(APPLICATION_ID_DESKTOP_CRASHED, &s, instance);
@ -1817,6 +1840,7 @@ bool ApplicationInstanceStart(int64_t applicationID, _EsApplicationStartupInform
process->application = application;
desktop.allApplicationProcesses.Add(process);
} else {
ApplicationTemporaryDestroy(application);
_EsApplicationStartupInformation s = {};
s.data = CRASHED_TAB_INVALID_EXECUTABLE;
return ApplicationInstanceStart(APPLICATION_ID_DESKTOP_CRASHED, &s, instance);
@ -1845,12 +1869,11 @@ bool ApplicationInstanceStart(int64_t applicationID, _EsApplicationStartupInform
EsMessage m = { ES_MSG_INSTANCE_CREATE };
if (~startupInformation->flags & ES_APPLICATION_STARTUP_MANUAL_PATH) {
PathGetNameAndContainingFolder(startupInformation->filePath, startupInformation->filePathBytes,
&startupInformation->filePath, &startupInformation->filePathBytes,
&startupInformation->containingFolder, &startupInformation->containingFolderBytes,
&volumeInformation);
}
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.
@ -1904,24 +1927,6 @@ ApplicationInstance *ApplicationInstanceCreate(int64_t id, _EsApplicationStartup
}
}
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);
}
ApplicationProcess *ApplicationProcessFindByPID(EsObjectID pid, bool removeIfFound = false) {
for (uintptr_t i = 0; i < desktop.allApplicationProcesses.Length(); i++) {
ApplicationProcess *process = desktop.allApplicationProcesses[i];
@ -2180,7 +2185,7 @@ void ApplicationInstanceRequestSave(ApplicationInstance *instance, const char *n
{
// Tell the instance the chosen name and new containing folder for the document.
EsMessage m = { ES_MSG_INSTANCE_DOCUMENT_RENAMED };
void *data = OpenDocumentGetRenameMessageData(name, nameBytes, &m.tabOperation.bytes);
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);
@ -2219,47 +2224,47 @@ void InstanceAnnouncePathMoved(InstalledApplication *fromApplication, const char
// TODO Update the location of installed applications and other things in the configuration.
// TODO Replace fromApplication with something better.
EsObjectID documentID = 0;
if (oldPathBytes) {
EsObjectID documentID = 0;
for (uintptr_t i = 0; i < desktop.openDocuments.Count(); i++) {
OpenDocument *document = &desktop.openDocuments[i];
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 (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 (!documentID) {
return;
}
size_t messageDataBytes;
void *messageData = OpenDocumentGetRenameMessageData(newPath, newPathBytes, &messageDataBytes);
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;
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));
@ -2905,6 +2910,28 @@ void DesktopSyscall(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) {
EsBufferWrite(pipe, document->path, document->pathBytes);
}
}
} else if (buffer[0] == DESKTOP_MSG_RUN_TEMPORARY_APPLICATION) {
InstalledApplication *requestingApplication = ApplicationFindByPID(message->desktop.processID);
if (requestingApplication && (requestingApplication->permissions & APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION)) {
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
application->temporary = true;
application->hidden = true;
application->useSingleProcess = true;
application->cExecutable = (char *) EsHeapAllocate(message->desktop.bytes, false);
EsMemoryCopy(application->cExecutable, buffer + 1, message->desktop.bytes - 1);
application->cExecutable[message->desktop.bytes - 1] = 0;
static int64_t nextTemporaryID = -1;
application->id = nextTemporaryID--;
application->cName = (char *) EsHeapAllocate(32, false);
for (int i = 1; i < 31; i++) application->cName[i] = (EsRandomU8() % 26) + 'a';
application->cName[0] = '_', application->cName[31] = 0;
EsHandle handle;
EsError error = TemporaryFileCreate(&handle, &application->settingsPath, &application->settingsPathBytes, ES_NODE_DIRECTORY);
if (error == ES_SUCCESS) EsHandleClose(handle);
desktop.installedApplications.Add(application);
ApplicationInstanceCreate(application->id, nullptr, nullptr);
}
} else if (!instance) {
// -------------------------------------------------
// | Messages below here require a valid instance. |
@ -2990,31 +3017,10 @@ void DesktopSyscall(EsMessage *message, uint8_t *buffer, EsBuffer *pipe) {
if (document) {
_EsApplicationStartupInformation startupInformation = {};
startupInformation.flags = ES_APPLICATION_STARTUP_MANUAL_PATH;
startupInformation.filePath = document->path;
startupInformation.filePathBytes = document->pathBytes;
ApplicationInstanceCreate(desktop.fileManager->id, &startupInformation, instance->tab->container);
}
} else if (buffer[0] == DESKTOP_MSG_RUN_TEMPORARY_APPLICATION) {
if (instance->application && (instance->application->permissions & APPLICATION_PERMISSION_RUN_TEMPORARY_APPLICATION)) {
InstalledApplication *application = (InstalledApplication *) EsHeapAllocate(sizeof(InstalledApplication), true);
application->temporary = true;
application->hidden = true;
application->useSingleProcess = true;
application->cExecutable = (char *) EsHeapAllocate(message->desktop.bytes, false);
EsMemoryCopy(application->cExecutable, buffer + 1, message->desktop.bytes - 1);
application->cExecutable[message->desktop.bytes - 1] = 0;
static int64_t nextTemporaryID = -1;
application->id = nextTemporaryID--;
application->cName = (char *) EsHeapAllocate(32, false);
for (int i = 1; i < 31; i++) application->cName[i] = (EsRandomU8() % 26) + 'a';
application->cName[0] = '_', application->cName[31] = 0;
EsHandle handle;
EsError error = TemporaryFileCreate(&handle, &application->settingsPath, &application->settingsPathBytes, ES_NODE_DIRECTORY);
if (error == ES_SUCCESS) EsHandleClose(handle);
desktop.installedApplications.Add(application);
ApplicationInstanceCreate(application->id, nullptr, nullptr);
}
} else {
EsPrint("DesktopSyscall - Received unhandled message %d.\n", buffer[0]);
}

View File

@ -4446,11 +4446,9 @@ void EsButtonSetIconFromBits(EsButton *button, const uint32_t *bits, size_t widt
}
}
void EsButtonOnCommand(EsButton *button, EsCommandCallback onCommand, EsCommand *command) {
void EsButtonOnCommand(EsButton *button, EsCommandCallback onCommand) {
EsMessageMutexCheck();
button->onCommand = onCommand;
button->command = command;
}
void EsButtonSetCheckBuddy(EsButton *button, EsElement *checkBuddy) {
@ -5949,8 +5947,7 @@ void FileMenuRename(EsInstance *_instance, EsElement *, EsCommand *) {
ptrdiff_t initialNameBytes = 0;
if (instance->startupInformation && instance->startupInformation->filePathBytes) {
initialName = instance->startupInformation->filePath;
initialNameBytes = instance->startupInformation->filePathBytes;
PathGetName(instance->startupInformation->filePath, instance->startupInformation->filePathBytes, &initialName, &initialNameBytes);
} else {
EsInstanceClassEditorSettings *editorSettings = &instance->editorSettings;
initialName = editorSettings->newDocumentFileName;
@ -6007,8 +6004,10 @@ void EsFileMenuCreate(EsInstance *_instance, EsElement *element, uint64_t menuFl
EsTextDisplayCreate(panel3, ES_CELL_H_FILL, ES_STYLE_TEXT_LABEL,
editorSettings->newDocumentTitle, editorSettings->newDocumentTitleBytes);
} else {
EsTextDisplayCreate(panel3, ES_CELL_H_FILL, ES_STYLE_TEXT_LABEL,
instance->startupInformation->filePath, instance->startupInformation->filePathBytes);
const char *name;
ptrdiff_t nameBytes;
PathGetName(instance->startupInformation->filePath, instance->startupInformation->filePathBytes, &name, &nameBytes);
EsTextDisplayCreate(panel3, ES_CELL_H_FILL, ES_STYLE_TEXT_LABEL, name, nameBytes);
}
EsButton *renameButton = EsButtonCreate(panel3, ES_BUTTON_TOOLBAR);
@ -6484,6 +6483,10 @@ void EsElementFocus(EsElement *element, uint32_t flags) {
if (window->focused == element) return;
// If an element is already focused and the request is only to focus the element if there is no focused element, ignore the request.
if ((flags & ES_ELEMENT_FOCUS_ONLY_IF_NO_FOCUSED_ELEMENT) && window->focused) return;
// Tell the previously focused element it's no longer focused.
EsElement *oldFocus = window->focused;
@ -6708,6 +6711,10 @@ bool EsElementIsHidden(EsElement *element) {
void EsElementSetDisabled(EsElement *element, bool disabled) {
EsMessageMutexCheck();
if (element->window->focused == element) {
UIRemoveFocusFromElement(element);
}
for (uintptr_t i = 0; i < element->GetChildCount(); i++) {
if (element->GetChild(i)->flags & ES_ELEMENT_NON_CLIENT) continue;
EsElementSetDisabled(element->GetChild(i), disabled);
@ -7808,6 +7815,8 @@ void UIProcessWindowManagerMessage(EsWindow *window, EsMessage *message, Process
gui.leftModifiers = message->windowActivated.leftModifiers;
gui.rightModifiers = message->windowActivated.rightModifiers;
UIMouseUp(window, nullptr, false);
if (!window->activated) {
AccessKeyModeExit();

View File

@ -1320,7 +1320,7 @@ struct EsListView : EsElement {
EsRectangle bounds = element->GetBounds();
child->InternalMove(bounds.r - bounds.l, bounds.b - bounds.t, bounds.l, bounds.t);
}
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) {
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN || message->type == ES_MSG_MOUSE_MIDDLE_CLICK) {
EsElementFocus(this);
if (hasFocusedItem) {
@ -1337,14 +1337,23 @@ struct EsListView : EsElement {
focusedItemIndex = item->index;
element->customStyleState |= THEME_STATE_FOCUSED_ITEM;
if (message->mouseDown.clickChainCount == 1 || (~element->customStyleState & THEME_STATE_SELECTED)) {
if (message->type == ES_MSG_MOUSE_MIDDLE_CLICK) {
Select(item->group, item->index, false, false, false);
EsMessage m = { ES_MSG_LIST_VIEW_CHOOSE_ITEM };
m.chooseItem.group = item->group;
m.chooseItem.index = item->index;
m.chooseItem.source = ES_LIST_VIEW_CHOOSE_ITEM_MIDDLE_CLICK;
EsMessageSend(this, &m);
} else if (message->mouseDown.clickChainCount == 1 || (~element->customStyleState & THEME_STATE_SELECTED)) {
Select(item->group, item->index, EsKeyboardIsShiftHeld(), EsKeyboardIsCtrlHeld(), false);
} else if (message->mouseDown.clickChainCount == 2) {
EsMessage m = { ES_MSG_LIST_VIEW_CHOOSE_ITEM };
m.chooseItem.group = item->group;
m.chooseItem.index = item->index;
m.chooseItem.source = ES_LIST_VIEW_CHOOSE_ITEM_DOUBLE_CLICK;
EsMessageSend(this, &m);
}
} else if (message->type == ES_MSG_MOUSE_MIDDLE_DOWN) {
} else if (message->type == ES_MSG_MOUSE_RIGHT_DOWN) {
EsMessage m = { ES_MSG_LIST_VIEW_IS_SELECTED };
m.selectItem.index = item->index;
@ -1602,6 +1611,7 @@ struct EsListView : EsElement {
EsMessage m = { ES_MSG_LIST_VIEW_CHOOSE_ITEM };
m.chooseItem.group = focusedItemGroup;
m.chooseItem.index = focusedItemIndex;
m.chooseItem.source = ES_LIST_VIEW_CHOOSE_ITEM_ENTER;
EsMessageSend(this, &m);
return true;
} else if (!ctrl && !alt) {

View File

@ -715,10 +715,10 @@ define ES_DRIVE_TYPE_SSD (2)
define ES_DRIVE_TYPE_CDROM (3)
define ES_DRIVE_TYPE_USB_MASS_STORAGE (4)
define ES_ELEMENT_FOCUS_ENSURE_VISIBLE (1 << 0)
define ES_ELEMENT_FOCUS_FROM_KEYBOARD (1 << 1)
define ES_ELEMENT_FOCUS_ENSURE_VISIBLE (1 << 0)
define ES_ELEMENT_FOCUS_FROM_KEYBOARD (1 << 1)
define ES_ELEMENT_FOCUS_ONLY_IF_NO_FOCUSED_ELEMENT (1 << 2)
define ES_APPLICATION_STARTUP_MANUAL_PATH (1 << 0)
define ES_APPLICATION_STARTUP_BACKGROUND_SERVICE (1 << 1)
define ES_APPLICATION_STARTUP_IN_SAME_CONTAINER (1 << 2)
@ -772,6 +772,11 @@ private define ES_SCROLL_MANUAL (1 << 2) // The parent is responsible for updati
define ES_SUBSYSTEM_ID_NATIVE (0)
define ES_SUBSYSTEM_ID_POSIX (1)
define ES_LIST_VIEW_CHOOSE_ITEM_OTHER (1)
define ES_LIST_VIEW_CHOOSE_ITEM_ENTER (2)
define ES_LIST_VIEW_CHOOSE_ITEM_DOUBLE_CLICK (3)
define ES_LIST_VIEW_CHOOSE_ITEM_MIDDLE_CLICK (4)
include desktop/icons.header
enum EsFatalError {
@ -1434,13 +1439,13 @@ struct EsCommand {
struct EsApplicationStartupRequest {
int64_t id;
STRING filePath;
STRING filePath; // Only contains the file name if the application does not have permission_all_files or the file is not on a file system.
uint32_t flags;
};
private struct _EsApplicationStartupInformation {
int64_t id;
STRING filePath;
STRING filePath; // See comment in EsApplicationStartupRequest.
EsWindow *targetWindow;
uint32_t flags;
int32_t data;
@ -1700,6 +1705,7 @@ struct EsMessageSelectItem {
struct EsMessageChooseItem {
EsListViewIndex group;
EsListViewIndex index;
uint8_t source;
};
struct EsMessageSearchItem {
@ -1759,13 +1765,13 @@ struct EsMessageEndEdit {
struct EsMessageInstanceOpen {
EsFileStore *file;
STRING name;
STRING nameOrPath; // See comment in EsApplicationStartupRequest.
bool update;
};
struct EsMessageInstanceSave {
EsFileStore *file;
STRING name;
STRING nameOrPath; // See comment in EsApplicationStartupRequest.
};
// Internal system messages.
@ -1990,8 +1996,8 @@ function_pointer void EsWorkCallback(EsGeneric context);
// System.
function void EsApplicationStart(ES_INSTANCE_TYPE *instance, const EsApplicationStartupRequest *request);
function void EsApplicationRunTemporary(ES_INSTANCE_TYPE *instance, STRING path);
function void EsApplicationStart(ES_INSTANCE_TYPE *instance, const EsApplicationStartupRequest *request); // The instance is optional, used only for ES_APPLICATION_STARTUP_IN_SAME_CONTAINER.
function void EsApplicationRunTemporary(STRING path);
function EsHandle EsTakeSystemSnapshot(int type, size_t *bufferSize);
function EsInstance *_EsInstanceCreate(size_t bytes, EsMessage *message, STRING name = BLANK_STRING);
function EsError EsHandleClose(EsHandle handle);
@ -1999,7 +2005,7 @@ function void EsSystemShowShutdownDialog();
function void EsPOSIXInitialise(int *argc, char ***argv);
function long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long a6);
function char *EsPOSIXConvertPath(const char *path, size_t *outNameLength, bool addPOSIXMountPointPrefix);
function char *EsPOSIXConvertPath(const char *path, size_t *outNameLength, bool addPOSIXMountPointPrefix); // Converts a POSIX path to a native path. Free with EsHeapFree.
private function void EsBatch(EsBatchCall *calls, size_t count);
private function uintptr_t _EsSyscall(uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e, uintptr_t f);
@ -2060,7 +2066,7 @@ function void *EsFileStoreMap(EsFileStore *file, size_t *fileSize, uint32_t flag
function bool EsMountPointGetVolumeInformation(const char *prefix, size_t prefixBytes, EsVolumeInformation *information); // Returns false if the mount point does not exist.
function void EsMountPointEnumerate(EsMountPointEnumerationCallback callback, EsGeneric context);
function void EsOpenDocumentQueryInformation(STRING filePath, EsOpenDocumentInformation *information);
function void _EsPathAnnouncePathMoved(STRING oldPath, STRING newPath);
function void _EsPathAnnouncePathMoved(STRING oldPath, STRING newPath); // Set oldPathBytes = 0 to make the file manager refresh the path.
function void _EsOpenDocumentEnumerate(EsBuffer *outputBuffer);
function void EsDeviceEnumerate(EsDeviceEnumerationCallback callback, EsGeneric context);
@ -2072,6 +2078,7 @@ function EsError EsProcessCreate(const EsProcessCreationArguments *arguments, Es
function int EsProcessGetExitStatus(EsHandle process);
function EsObjectID EsProcessGetID(EsHandle process);
function void EsProcessGetState(EsHandle process, EsProcessState *state);
function void EsProcessGetCreateData(EsProcessCreateData *data); // For the current process.
function EsHandle EsProcessOpen(EsObjectID pid);
function void EsProcessPause(EsHandle process, bool resume);
function void EsProcessTerminate(EsHandle process, int status);
@ -2498,7 +2505,7 @@ function void EsButtonSetIcon(EsButton *button, uint32_t iconID);
function void EsButtonSetIconFromBits(EsButton *button, const uint32_t *bits, size_t width, size_t height, size_t stride);
function void EsButtonSetCheck(EsButton *button, EsCheckState checkState = ES_CHECK_CHECKED, bool sendUpdatedMessage = true);
function EsCheckState EsButtonGetCheck(EsButton *button);
function void EsButtonOnCommand(EsButton *button, EsCommandCallback callback, EsCommand *command = ES_NULL); // TODO Public property?
function void EsButtonOnCommand(EsButton *button, EsCommandCallback callback); // TODO Public property?
function void EsButtonSetCheckBuddy(EsButton *button, EsElement *checkBuddy); // The buddy element is enabled/disabled when the button is checked/unchecked.
function EsElement *EsButtonGetCheckBuddy(EsButton *button); // TODO Public property?
@ -2524,7 +2531,8 @@ function void EsTextboxSetTextSize(EsTextbox *textbox, uint16_t size);
function void EsTextboxSetFont(EsTextbox *textbox, EsFont font);
function void EsTextboxSetupSyntaxHighlighting(EsTextbox *textbox, uint32_t language, uint32_t *customColors = ES_NULL, size_t customColorCount = 0);
function void EsTextboxStartEdit(EsTextbox *textbox);
function void EsTextboxEnableSmartQuotes(EsTextbox *textbox, bool enabled);
function void EsTextboxEnableSmartReplacement(EsTextbox *textbox, bool enabled); // e.g. smart quotes.
function void EsTextboxSetReadOnly(EsTextbox *textbox, bool readOnly); // Prevents the user from modifying the contents of the textbox; EsTextboxInsert will still work. Incompatible with ES_TEXTBOX_EDIT_BASED. Undo events will not be created.
// Sliders.

View File

@ -49,6 +49,11 @@ struct ChildProcess {
char *workingDirectory;
Array<ChildProcess> childProcesses;
Array<void *> _argv;
#ifdef ES_ARCH_X86_64
Elf64_Phdr *tlsHeader;
#endif
#ifdef DEBUG_BUILD
double syscallTimeSpent[1024];
@ -680,9 +685,11 @@ long EsPOSIXSystemCall(long n, long a1, long a2, long a3, long a4, long a5, long
}
#ifdef DEBUG_BUILD
double endTime = EsTimeStampMs();
syscallTimeSpent[n] += endTime - startTime;
syscallCallCount[n]++;
if ((uintptr_t) n < sizeof(syscallTimeSpent) / sizeof(syscallTimeSpent[0])) {
double endTime = EsTimeStampMs();
syscallTimeSpent[n] += endTime - startTime;
syscallCallCount[n]++;
}
#endif
// EsPrint(":: %z %x %x %x -> %x; %Fms\n", syscallNames[n], a1, a2, a3, returnValue, endTime - startTime);
@ -709,7 +716,6 @@ void EsPOSIXInitialise(int *argc, char ***argv) {
uintptr_t position = 0;
char *start = environmentBuffer;
Array<void *> _argv = {};
*argc = 0;
for (int i = 0; i < 2; i++) {
@ -750,7 +756,7 @@ void EsPOSIXInitialise(int *argc, char ***argv) {
// Add the auxillary vectors.
#ifdef ES_ARCH_X86_64
Elf64_Phdr *tlsHeader = (Elf64_Phdr *) EsHeapAllocate(sizeof(Elf64_Phdr), true);
tlsHeader = (Elf64_Phdr *) EsHeapAllocate(sizeof(Elf64_Phdr), true);
tlsHeader->p_type = PT_TLS;
tlsHeader->p_flags = 4 /* read */;
tlsHeader->p_vaddr = startupInformation->tlsImageStart;
@ -778,4 +784,11 @@ void EsPOSIXInitialise(int *argc, char ***argv) {
*argv = (char **) _argv.array;
}
void POSIXCleanup() {
_argv.Free();
childProcesses.Free();
EsHeapFree(workingDirectory);
EsHeapFree(tlsHeader);
}
#endif

View File

@ -121,3 +121,4 @@ define ES_STYLE_TOOLBAR_BUTTON_GROUP_SEPARATOR (ES_STYLE_CAST(7))
define ES_STYLE_TOOLBAR_SPACER_SMALL (ES_STYLE_CAST(3))
define ES_STYLE_LIST_CHOICE_ITEM_2X (ES_STYLE_CAST(9))
define ES_STYLE_TEXTBOX_BORDERED_SINGLE_MEDIUM (ES_STYLE_CAST(11))
define ES_STYLE_SEPARATOR_VERTICAL (ES_STYLE_CAST(13))

View File

@ -118,6 +118,10 @@ int EsProcessGetExitStatus(EsHandle process) {
return EsSyscall(ES_SYSCALL_PROCESS_GET_STATUS, process, 0, 0, 0);
}
void EsProcessGetCreateData(EsProcessCreateData *data) {
EsMemoryCopy(data, &api.startupInformation->data, sizeof(EsProcessCreateData));
}
void ThreadInitialise(ThreadLocalStorage *local);
__attribute__((no_instrument_function))

View File

@ -1,3 +1,7 @@
// 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 Caret blinking.
// TODO Wrapped lines.
// TODO Unicode grapheme/word boundaries.
@ -53,6 +57,8 @@ struct EsTextbox : EsElement {
int verticalMotionHorizontalDepth;
int oldHorizontalScroll;
bool inRightClickDrag;
bool colorUppercase;
EsUndoManager *undo;
EsUndoManager localUndo;
@ -67,12 +73,8 @@ struct EsTextbox : EsElement {
uint32_t syntaxHighlightingLanguage;
uint32_t syntaxHighlightingColors[8];
bool smartQuotes;
bool inRightClickDrag;
// For smart context menus:
bool colorUppercase;
bool smartReplacement;
bool readOnly;
};
#define MOVE_CARET_SINGLE (2)
@ -364,7 +366,7 @@ void TextboxUpdateCommands(EsTextbox *textbox, bool noClipboard) {
command = EsCommandByID(textbox->instance, ES_COMMAND_DELETE);
command->data = textbox;
EsCommandSetDisabled(command, selectionEmpty);
EsCommandSetDisabled(command, selectionEmpty || textbox->readOnly);
EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) {
EsTextbox *textbox = (EsTextbox *) command->data.p;
@ -397,7 +399,7 @@ void TextboxUpdateCommands(EsTextbox *textbox, bool noClipboard) {
command = EsCommandByID(textbox->instance, ES_COMMAND_CUT);
command->data = textbox;
EsCommandSetDisabled(command, selectionEmpty);
EsCommandSetDisabled(command, selectionEmpty || textbox->readOnly);
EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) {
EsTextbox *textbox = (EsTextbox *) command->data.p;
@ -422,7 +424,7 @@ void TextboxUpdateCommands(EsTextbox *textbox, bool noClipboard) {
if (!noClipboard) {
command = EsCommandByID(textbox->instance, ES_COMMAND_PASTE);
command->data = textbox;
EsCommandSetDisabled(command, !EsClipboardHasText(ES_CLIPBOARD_PRIMARY));
EsCommandSetDisabled(command, textbox->readOnly || !EsClipboardHasText(ES_CLIPBOARD_PRIMARY));
EsCommandSetCallback(command, [] (EsInstance *, EsElement *, EsCommand *command) {
EsTextbox *textbox = (EsTextbox *) command->data.p;
@ -790,14 +792,12 @@ void EsTextboxSelectAll(EsTextbox *textbox) {
void EsTextboxClear(EsTextbox *textbox, bool sendUpdatedMessage) {
EsMessageMutexCheck();
EsTextboxSelectAll(textbox);
EsTextboxInsert(textbox, "", 0, sendUpdatedMessage);
}
size_t EsTextboxGetLineLength(EsTextbox *textbox, uintptr_t line) {
EsMessageMutexCheck();
return textbox->lines[line].lengthBytes;
}
@ -872,7 +872,7 @@ void EsTextboxInsert(EsTextbox *textbox, const char *string, ptrdiff_t stringByt
}
}
if (textbox->undo) {
if (textbox->undo && !textbox->readOnly) {
// Step 3: Allocate space for an undo item.
undoItemBytes = sizeof(TextboxUndoItemHeader) - deltaBytes + deleteTo.line - deleteFrom.line;
@ -938,7 +938,7 @@ void EsTextboxInsert(EsTextbox *textbox, const char *string, ptrdiff_t stringByt
TextboxLineCountChangeCleanup(textbox, deltaBytes, deleteFrom.line + 1);
}
} else {
if (textbox->undo) {
if (textbox->undo && !textbox->readOnly) {
undoItemBytes = sizeof(TextboxUndoItemHeader);
undoItem = (TextboxUndoItemHeader *) EsHeapAllocate(undoItemBytes, false);
EsMemoryZero(undoItem, sizeof(TextboxUndoItemHeader));
@ -1476,6 +1476,40 @@ void TextboxStyleChanged(EsTextbox *textbox) {
EsElementRepaint(textbox);
}
void TextboxAddSmartContextMenu(EsTextbox *textbox, EsMenu *menu) {
if ((~textbox->flags & ES_TEXTBOX_NO_SMART_CONTEXT_MENUS) && textbox->carets[0].line == textbox->carets[1].line) {
int32_t selectionFrom = textbox->carets[0].byte, selectionTo = textbox->carets[1].byte;
if (selectionTo < selectionFrom) {
int32_t temporary = selectionFrom;
selectionFrom = selectionTo;
selectionTo = temporary;
}
if (selectionTo - selectionFrom == 7) {
char buffer[7];
EsMemoryCopy(buffer, GET_BUFFER(&textbox->lines[textbox->carets[0].line]) + selectionFrom, 7);
if (buffer[0] == '#' && EsCRTisxdigit(buffer[1]) && EsCRTisxdigit(buffer[2]) && EsCRTisxdigit(buffer[3])
&& EsCRTisxdigit(buffer[4]) && EsCRTisxdigit(buffer[5]) && EsCRTisxdigit(buffer[6])) {
// It's a color hex-code!
// TODO Versions with alpha.
EsMenuNextColumn(menu);
ColorPickerCreate(menu, { textbox }, EsColorParse(buffer, 7), false);
textbox->colorUppercase = true;
for (uintptr_t i = 1; i <= 6; i++) {
if (buffer[i] >= 'a' && buffer[i] <= 'f') {
textbox->colorUppercase = false;
break;
}
}
}
}
}
}
int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
EsTextbox *textbox = (EsTextbox *) element;
@ -1638,7 +1672,7 @@ int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
textbox->Repaint(true);
verticalMotion = true;
} else if (message->keyboard.scancode == ES_SCANCODE_BACKSPACE || message->keyboard.scancode == ES_SCANCODE_DELETE) {
} else if ((message->keyboard.scancode == ES_SCANCODE_BACKSPACE || message->keyboard.scancode == ES_SCANCODE_DELETE) && !textbox->readOnly) {
if (!textbox->editing) {
EsTextboxStartEdit(textbox);
}
@ -1659,7 +1693,7 @@ int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
TextboxEndEdit(textbox, true);
} else if (message->keyboard.scancode == ES_SCANCODE_TAB && (~textbox->flags & ES_TEXTBOX_ALLOW_TABS)) {
response = 0;
} else {
} else if (!textbox->readOnly) {
if (!textbox->editing) {
EsTextboxStartEdit(textbox);
}
@ -1669,7 +1703,7 @@ int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
true, textbox->flags & ES_TEXTBOX_MULTILINE);
if (inputString && (message->keyboard.modifiers & ~(ES_MODIFIER_SHIFT | ES_MODIFIER_ALT_GR)) == 0) {
if (textbox->smartQuotes && api.global->useSmartQuotes) {
if (textbox->smartReplacement && api.global->useSmartQuotes) {
DocumentLine *currentLine = &textbox->lines[textbox->carets[0].line];
const char *buffer = GET_BUFFER(currentLine);
bool left = !textbox->carets[0].byte || buffer[textbox->carets[0].byte - 1] == ' ';
@ -1701,6 +1735,8 @@ int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
} else {
response = 0;
}
} else {
response = 0;
}
if (!verticalMotion) {
@ -1757,52 +1793,26 @@ int ProcessTextboxMessage(EsElement *element, EsMessage *message) {
// TODO User customisation of menus.
if (textbox->editing) {
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonUndo), EsCommandByID(textbox->instance, ES_COMMAND_UNDO));
EsMenuAddSeparator(menu);
}
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardCut), EsCommandByID(textbox->instance, ES_COMMAND_CUT));
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardCopy), EsCommandByID(textbox->instance, ES_COMMAND_COPY));
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardPaste), EsCommandByID(textbox->instance, ES_COMMAND_PASTE));
if (textbox->editing) {
EsMenuAddSeparator(menu);
if (textbox->readOnly) {
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardCopy), EsCommandByID(textbox->instance, ES_COMMAND_COPY));
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonSelectionSelectAll), EsCommandByID(textbox->instance, ES_COMMAND_SELECT_ALL));
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonSelectionDelete), EsCommandByID(textbox->instance, ES_COMMAND_DELETE));
} else {
if (textbox->editing) {
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonUndo), EsCommandByID(textbox->instance, ES_COMMAND_UNDO));
EsMenuAddSeparator(menu);
}
// Add the smart context menu, if necessary.
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardCut), EsCommandByID(textbox->instance, ES_COMMAND_CUT));
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardCopy), EsCommandByID(textbox->instance, ES_COMMAND_COPY));
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonClipboardPaste), EsCommandByID(textbox->instance, ES_COMMAND_PASTE));
if ((~textbox->flags & ES_TEXTBOX_NO_SMART_CONTEXT_MENUS) && textbox->carets[0].line == textbox->carets[1].line) {
int32_t selectionFrom = textbox->carets[0].byte, selectionTo = textbox->carets[1].byte;
if (textbox->editing) {
EsMenuAddSeparator(menu);
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonSelectionSelectAll), EsCommandByID(textbox->instance, ES_COMMAND_SELECT_ALL));
EsMenuAddCommand(menu, 0, INTERFACE_STRING(CommonSelectionDelete), EsCommandByID(textbox->instance, ES_COMMAND_DELETE));
if (selectionTo < selectionFrom) {
int32_t temporary = selectionFrom;
selectionFrom = selectionTo;
selectionTo = temporary;
}
if (selectionTo - selectionFrom == 7) {
char buffer[7];
EsMemoryCopy(buffer, GET_BUFFER(&textbox->lines[textbox->carets[0].line]) + selectionFrom, 7);
if (buffer[0] == '#' && EsCRTisxdigit(buffer[1]) && EsCRTisxdigit(buffer[2]) && EsCRTisxdigit(buffer[3])
&& EsCRTisxdigit(buffer[4]) && EsCRTisxdigit(buffer[5]) && EsCRTisxdigit(buffer[6])) {
// It's a color hex-code!
// TODO Versions with alpha.
EsMenuNextColumn(menu);
ColorPickerCreate(menu, { textbox }, EsColorParse(buffer, 7), false);
textbox->colorUppercase = true;
for (uintptr_t i = 1; i <= 6; i++) {
if (buffer[i] >= 'a' && buffer[i] <= 'f') {
textbox->colorUppercase = false;
break;
}
}
}
}
// Add the smart context menu, if necessary.
TextboxAddSmartContextMenu(textbox, menu);
}
}
@ -1890,7 +1900,7 @@ EsTextbox *EsTextboxCreate(EsElement *parent, uint64_t flags, const EsStyle *sty
textbox->style->GetTextStyle(&textbox->textStyle);
textbox->smartQuotes = true;
textbox->smartReplacement = true;
DocumentLine firstLine = {};
firstLine.height = TextGetLineHeight(textbox, &textbox->textStyle);
@ -2133,8 +2143,14 @@ void EsTextboxSetupSyntaxHighlighting(EsTextbox *textbox, uint32_t language, uin
textbox->Repaint(true);
}
void EsTextboxEnableSmartQuotes(EsTextbox *textbox, bool enabled) {
textbox->smartQuotes = enabled;
void EsTextboxEnableSmartReplacement(EsTextbox *textbox, bool enabled) {
textbox->smartReplacement = enabled;
}
void EsTextboxSetReadOnly(EsTextbox *textbox, bool readOnly) {
textbox->readOnly = readOnly;
EsAssert(~textbox->flags & ES_TEXTBOX_EDIT_BASED);
TextboxUpdateCommands(textbox, false);
}
#undef GET_BUFFER

View File

@ -288,3 +288,62 @@ nakst
Yesterday at 8:44 AM
They are not linked to any libraries. They call into the system api via the api table, which is an array of function pointers at a fixed memory address. The api header wraps these function pointers with macros, as needed.
= On UI layouting =
nakst
Today at 5:22 PM
I really need to talk in depth about how layouting in Luigi (and by extension Essence) works, but let me try and describe the basic idea here:
- Each element has an associated callback function, called its message handler. "Sending a message" to an element just means calling into the message handler of that element.
- Elements are arranged in a layout tree. Deciding the position of each element is the responsibility of its parent.
- When an element need its layout updating it is sent a UI_MSG_LAYOUT message. The element must then position all its child elements with UIElementMove. If a child's bounding box changes, then it will be sent a UI_MSG_LAYOUT message. That is, the subtree that needs relayouting is iterated in recursive manner.
- During the layout of element, it can request sizing information from its children by sending them the UI_MSG_GET_WIDTH and UI_MSG_GET_HEIGHT messages. As an optional argument, the former takes a desired height of the element, and the latter takes a desired width of the element.
- On receiving a GET_WIDTH or GET_HEIGHT message, an element may also need to query its children with GET_WIDTH and GET_HEIGHT.
Example:
Consider a vertical stack X with a child element Y, which is a text display containing word-wrapped text. X is sent a UI_MSG_LAYOUT message. X sends UI_MSG_GET_HEIGHT to Y with the desired width argument as X.width. Y performs its word-wrapping algorithm, and returns the needed vertical space to display its contents at the given width. X calls UIElementMove on Y, giving it a bounding box with width X.width and the height returned by the UI_MSG_GET_HEIGHT call. Y receives a UI_MSG_LAYOUT message (because UIElementMove was called), but it does nothing because it has no children.
ryanfleury
Today at 5:26 PM
I see, okay. It seems sort of similar to what I do, actually, in some ways. In your case it's retained-mode, so you do message-passing-style "update propagation", whereas the immediate-mode equivalent would just do the full pass every frame. How do "upwards-dependent" sizes work? e.g. a widget that wants its width to be 50% of its parent's? Is that just decided by the parent?
nakst
Today at 5:37 PM
It depends on what exactly type of layout you're going for, but generally this sort of thing is done by setting the UI_ELEMENT_H_FILL flag on the child element.
For example,
UIPanel *panel = UIPanelCreate(parent, UI_PANEL_HORIZONTAL);
UIButtonCreate(panel, UI_ELEMENT_H_FILL, "Button 1", -1);
UIButtonCreate(panel, UI_ELEMENT_H_FILL, "Button 2", -1);
creates a panel using a horizontal stack layout algorithm, with 2 buttons as its children. Because each button has the UI_ELEMENT_H_FILL, they will stretch to each fill 50% of the available width of the parent.
Another example,
UIPanel *panel = UIPanelCreate(parent, UI_PANEL_HORIZONTAL);
UIButtonCreate(panel, 0, "Button 1", -1);
UISpacerCreate(panel, UI_ELEMENT_H_FILL, 0, 0);
UIButtonCreate(panel, 0, "Button 2", -1);
This time the 2 buttons have fixed width (determined by the length of their label) but the spacer element in the middle of the stack takes up 100% of the panel's width not used by the buttons. This has the effect of putting "Button 1" at the left side of the panel and "Button 2" at the right side.
ryanfleury
Today at 5:39 PM
I see—what about something like a 70/30 split?
nakst
Today at 5:43 PM
You can't in Luigi. (Luckily it doesn't matter because this is not very common in user interfaces.)
But in Essence you can do it, by setting it as a property on a panel using the TABLE layout algorithm:
EsPanelBand columns[2] = {
{ .push = 7 }, /* column 1 */
{ .push = 3 }, /* column 2 */
};
EsPanelSetBands(panel, 2 /* number of columns */, 0, &columns);

Binary file not shown.

Binary file not shown.

View File

@ -1187,6 +1187,23 @@ double EsDoubleParse(const char *nptr, ptrdiff_t maxBytes, char **endptr) {
return value;
}
void PathGetName(const char *path, ptrdiff_t pathBytes, const char **name, ptrdiff_t *nameBytes) {
if (pathBytes == -1) {
pathBytes = EsCStringLength(path);
}
*name = path;
*nameBytes = pathBytes;
for (uintptr_t i = pathBytes; i > 0; i--) {
if (path[i - 1] == '/') {
*name = path + i;
*nameBytes = pathBytes - i;
break;
}
}
}
#endif
/////////////////////////////////

View File

@ -132,8 +132,9 @@ static void MemoryLeakDetectorAdd(EsHeap *heap, void *address, size_t bytes) {
while (rbp && traceDepth < sizeof(entry->stack) / sizeof(entry->stack[0])) {
uint64_t value = *(uint64_t *) (rbp + 8);
entry->stack[traceDepth++] = value;
if (!value) break;
if (!value || (value & 0xFFFF000000000000) || (value < 0x100000)) break;
rbp = *(uint64_t *) rbp;
if ((rbp & 0xFFFF000000000000) || (rbp < 0x100000)) break;
}
break;

View File

@ -191,6 +191,7 @@ DEFINE_INTERFACE_STRING(FileCannotRename, "The file could not be renamed.");
DEFINE_INTERFACE_STRING(FileRenameSuccess, "Renamed");
DEFINE_INTERFACE_STRING(FileSaveAnnouncement, "Saved to %s");
DEFINE_INTERFACE_STRING(FileSaveErrorFileDeleted, "Another application deleted the file.");
DEFINE_INTERFACE_STRING(FileSaveErrorCorrupt, "The file has been corrupted, and it cannot be modified.");
DEFINE_INTERFACE_STRING(FileSaveErrorDrive, "The drive containing the file was unable to modify it.");
@ -379,6 +380,18 @@ DEFINE_INTERFACE_STRING(InstallerFailedGeneric, "The installation could not comp
DEFINE_INTERFACE_STRING(InstallerFailedResources, "The installation could not complete. Your computer does not have enough memory to install " SYSTEM_BRAND_SHORT);
DEFINE_INTERFACE_STRING(InstallerNotSupported, "Your computer does not meet the minimum system requirements to install " SYSTEM_BRAND_SHORT ". Remove the installer, and restart your computer.");
// Build Core.
DEFINE_INTERFACE_STRING(BuildCoreTitle, "Build Core");
DEFINE_INTERFACE_STRING(BuildCoreNoConfigFileLoaded, "No config file is loaded.");
DEFINE_INTERFACE_STRING(BuildCoreNoFilePath, "The config file is not in a real folder, so it can't be loaded.");
DEFINE_INTERFACE_STRING(BuildCorePathCannotBeAccessedByPOSIXSubsystem, "The config file is not located on the 0:/ drive, so it can't be accessed by the POSIX subsystem.");
DEFINE_INTERFACE_STRING(BuildCoreBuild, "Build");
DEFINE_INTERFACE_STRING(BuildCoreLaunch, "Launch");
DEFINE_INTERFACE_STRING(BuildCoreCannotCreateBuildThread, "The build thread could not be created.");
DEFINE_INTERFACE_STRING(BuildCoreBuildFailed, "\n--- The build failed. ---\n");
DEFINE_INTERFACE_STRING(BuildCoreBuildSuccess, "\n(success)\n");
// TODO System Monitor.
#pragma GCC diagnostic pop

View File

@ -70,6 +70,9 @@ EsThreadCreate=68
EsThreadGetID=69
EsCRTstrdup=70
EsThreadTerminate=71
EsProcessGetCreateData=72
EsTextboxEnableSmartReplacement=73
EsTextboxSetReadOnly=74
EsConstantBufferCreate=75
EsConstantBufferRead=76
EsConstantBufferShare=77
@ -445,7 +448,6 @@ _EsUISetFont=454
EsWorkQueue=455
EsWorkIsExiting=456
EsPanelRadioGroupGetChecked=457
EsTextboxEnableSmartQuotes=458
EsBufferWriteInt8=459
EsInstanceGetStartupRequest=460
EsImageDisplayPaint=461

View File

@ -92,6 +92,7 @@ bool IsStringEqual(const char *string, size_t stringBytes, const char *cLiteral)
bool CheckDependencies(const char *applicationName) {
#ifdef OS_ESSENCE
(void) applicationName;
// TODO.
return true;
#else
@ -128,6 +129,9 @@ bool CheckDependencies(const char *applicationName) {
void ParseDependencies(const char *dependencyFile, const char *applicationName, bool append) {
#ifdef OS_ESSENCE
(void) dependencyFile;
(void) applicationName;
(void) append;
// TODO.
#else
char *dependencies = (char *) LoadFile(dependencyFile, NULL);

View File

@ -1,5 +1,9 @@
// 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 Better configuration over what files are imported to the drive image.
// TODO Make build_core responsible for generating the header.
// TODO Resetting after system builds.
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
@ -11,6 +15,12 @@
#include <essence.h>
#include <bits/syscall.h>
#define MSG_BUILD_SUCCESS (ES_MSG_USER_START + 1)
#define MSG_BUILD_FAILED (ES_MSG_USER_START + 2)
#define MSG_LOG (ES_MSG_USER_START + 3)
bool logSendMessages;
typedef struct File {
bool error, ready;
EsHandle handle;
@ -27,10 +37,24 @@ void Log(const char *format, ...) {
va_list arguments;
va_start(arguments, format);
char buffer[4096];
int bytes = EsCRTvsnprintf(buffer, sizeof(buffer), format, arguments);
EsAssert(bytes < sizeof(buffer));
size_t bytes = EsCRTvsnprintf(buffer, sizeof(buffer), format, arguments);
va_end(arguments);
EsPOSIXSystemCall(SYS_write, (intptr_t) 1, (intptr_t) buffer, (intptr_t) bytes, 0, 0, 0);
if (bytes >= sizeof(buffer) - 1) {
buffer[sizeof(buffer) - 1] = 0;
bytes = sizeof(buffer) - 1;
}
if (logSendMessages) {
EsMessage m;
m.type = MSG_LOG;
char *copy = EsHeapAllocate(bytes + 1, false, NULL);
EsCRTstrcpy(copy, buffer);
m.user.context1.p = copy;
EsMessagePost(NULL, &m);
} else {
EsPOSIXSystemCall(SYS_write, (intptr_t) 1, (intptr_t) buffer, (intptr_t) bytes, 0, 0, 0);
}
}
File FileOpen(const char *path, char mode) {
@ -59,7 +83,7 @@ void _FilePrintFormat(File *file, const char *format, ...) {
va_list arguments;
va_start(arguments, format);
char buffer[4096];
int bytes = EsCRTvsnprintf(buffer, sizeof(buffer), format, arguments);
size_t bytes = EsCRTvsnprintf(buffer, sizeof(buffer), format, arguments);
EsAssert(bytes < sizeof(buffer));
va_end(arguments);
FileWrite((*file), bytes, buffer);
@ -81,7 +105,7 @@ void _FilePrintFormat(File *file, const char *format, ...) {
typedef uint64_t pid_t;
typedef uint64_t time_t;
time_t time(time_t *timer) { return 0; } // TODO.
time_t time(time_t *timer) { (void) timer; return 0; } // TODO.
#else
@ -170,6 +194,10 @@ char *builtinModules;
volatile uint8_t encounteredErrors;
volatile uint8_t encounteredErrorsInKernelModules;
size_t singleConfigFileBytes;
void *singleConfigFileData;
bool singleConfigFileUse;
//////////////////////////////////
#define COLOR_ERROR "\033[0;33m"
@ -202,6 +230,8 @@ char *executeEnvironment[3] = {
int _Execute(char **output, const char *executable, ...) {
char *argv[64];
char *copies[64];
size_t copyCount = 0;
va_list argList;
va_start(argList, executable);
@ -219,6 +249,8 @@ int _Execute(char **output, const char *executable, ...) {
}
char *copy = (char *) malloc(strlen(string) + 2);
assert(copyCount != 64);
copies[copyCount++] = copy;
strcpy(copy, string);
strcat(copy, " ");
uintptr_t start = 0;
@ -307,6 +339,10 @@ int _Execute(char **output, const char *executable, ...) {
Log("(status = %d)\n", status);
}
for (uintptr_t i = 0; i < copyCount; i++) {
free(copies[i]);
}
if (status) __sync_fetch_and_or(&encounteredErrors, 1);
return status;
}
@ -450,6 +486,7 @@ bool MakeBundle(const char *outputFile, BundleInput *inputFiles, size_t inputFil
if (output.error) {
Log("Error: Could not open output file '%s'.\n", outputFile);
__sync_fetch_and_or(&encounteredErrors, 1);
return false;
}
@ -486,11 +523,13 @@ bool MakeBundle(const char *outputFile, BundleInput *inputFiles, size_t inputFil
if (!buffer) {
Log("Error: Could not open input file '%s'.\n", inputFiles[i].path);
__sync_fetch_and_or(&encounteredErrors, 1);
return false;
}
if (size > 0xFFFFFFFF) {
Log("Error: Input file '%s' too large (max: 4GB).\n", inputFiles[i].path);
__sync_fetch_and_or(&encounteredErrors, 1);
return false;
}
@ -499,6 +538,7 @@ bool MakeBundle(const char *outputFile, BundleInput *inputFiles, size_t inputFil
FileSeek(output, outputPosition);
FileWrite(output, size, buffer);
outputPosition += size;
free(buffer);
}
FileClose(output);
@ -527,6 +567,8 @@ typedef struct DependencyFile {
} DependencyFile;
typedef struct Application {
char *manifest;
const char *name;
EsINIState *properties;
int id;
@ -607,7 +649,9 @@ void BuildDesktop(Application *application) {
ADD_BUNDLE_INPUT("res/Cursors.png", "Cursors.png", 16);
ADD_BUNDLE_INPUT("bin/Stripped Executables/Desktop", "$Executables/x86_64", 0x1000); // TODO Don't hardcode the target.
MakeBundle("root/" SYSTEM_FOLDER_NAME "/Desktop.esx", application->bundleInputFiles, arrlenu(application->bundleInputFiles), 0);
if (!application->error) {
application->error = !MakeBundle("root/" SYSTEM_FOLDER_NAME "/Desktop.esx", application->bundleInputFiles, arrlenu(application->bundleInputFiles), 0);
}
}
void BuildApplication(Application *application) {
@ -699,7 +743,9 @@ void BuildApplication(Application *application) {
}
}
MakeBundle(executable, application->bundleInputFiles, arrlenu(application->bundleInputFiles), 0);
if (!application->error) {
application->error = !MakeBundle(executable, application->bundleInputFiles, arrlenu(application->bundleInputFiles), 0);
}
}
}
@ -722,14 +768,22 @@ void *BuildApplicationThread(void *_unused) {
void ParseApplicationManifest(const char *manifestPath) {
EsINIState s = {};
char *manifest = (char *) LoadFile(manifestPath, &s.bytes);
s.buffer = manifest;
char *manifest;
if (singleConfigFileUse) {
manifest = s.buffer = malloc(singleConfigFileBytes);
s.bytes = singleConfigFileBytes;
memcpy(s.buffer, singleConfigFileData, singleConfigFileBytes);
} else {
manifest = s.buffer = (char *) LoadFile(manifestPath, &s.bytes);
}
const char *require = "";
bool needsNativeToolchain = false;
bool disabled = false;
Application application = {};
application.manifest = manifest;
application.id = nextID++;
application.manifestPath = manifestPath;
application.compileFlags = "";
@ -1226,7 +1280,7 @@ int BuildCore(int argc, char **argv) {
#else
int main(int argc, char **argv) {
#endif
if (argc < 2) {
if (argc < 2 && !singleConfigFileUse) {
Log("Usage: build_core <mode> <options...>\n");
return 1;
}
@ -1248,7 +1302,9 @@ int main(int argc, char **argv) {
char *driverSource = NULL, *driverName = NULL;
bool driverBuiltin = false;
if (0 == strcmp(argv[1], "standard")) {
if (singleConfigFileUse) {
arrput(applicationManifests, "$SingleConfigFile");
} else if (0 == strcmp(argv[1], "standard")) {
if (argc != 3) {
Log("Usage: standard <configuration>\n");
return 1;
@ -1520,13 +1576,6 @@ int main(int argc, char **argv) {
CopyFile("bin/Object Files/crt1.o", "cross/lib/gcc/x86_64-essence/" GCC_VERSION "/crt1.o", true); // TODO Don't hardcode the target.
CopyFile("bin/Object Files/crtglue.o", "cross/lib/gcc/x86_64-essence/" GCC_VERSION "/crtglue.o", true);
CopyFile("util/linker/userland64.ld", "root/Applications/POSIX/lib/linker/userland64.ld", false);
if (hasNativeToolchain) {
snprintf(buffer, sizeof(buffer), "%s/linker/userland64.ld", toolchainLinkerScripts); // TODO Don't hardcode the target.
Execute(toolchainCC, "util/build_core.c", "-o", "root/Applications/POSIX/bin/build_core", "-g",
"-nostdlib", "bin/Object Files/crti.o", "bin/Object Files/crtbegin.o",
"bin/Object Files/crtend.o", "bin/Object Files/crtn.o", "-T", buffer);
}
}
#define ADD_DEPENDENCY_FILE(application, _path, _name) \
@ -1696,15 +1745,252 @@ int main(int argc, char **argv) {
DependenciesListWrite();
}
return encounteredErrors;
for (uintptr_t i = 0; i < arrlenu(applications); i++) {
free(applications[i].manifest);
arrfree(applications[i].sources);
arrfree(applications[i].dependencyFiles);
arrfree(applications[i].bundleInputFiles);
arrfree(applications[i].fileTypes);
arrfree(applications[i].handlers);
}
arrfree(applicationManifests);
arrfree(applications);
shfree(applicationDependencies);
uint8_t _encounteredErrors = encounteredErrors;
encounteredErrors = false;
#ifdef PARALLEL_BUILD
applicationsIndex = 0;
#endif
return _encounteredErrors;
}
#ifdef OS_ESSENCE
#include <shared/strings.cpp>
EsCommand commandBuild;
EsCommand commandLaunch;
EsButton *buttonBuild;
EsButton *buttonLaunch;
EsTextbox *textboxLog;
char *workingDirectory;
size_t workingDirectoryBytes;
bool buildRunning;
const EsStyle stylePanelStack = {
.metrics = {
.mask = ES_THEME_METRICS_INSETS | ES_THEME_METRICS_GAP_MAJOR,
.insets = ES_RECT_1(20),
.gapMajor = 15,
},
};
const EsStyle stylePanelButtonStack = {
.metrics = {
.mask = ES_THEME_METRICS_GAP_MAJOR,
.gapMajor = 5,
},
};
const EsStyle styleTextboxLog = {
.inherit = ES_STYLE_TEXTBOX_BORDERED_MULTILINE,
.metrics = {
.mask = ES_THEME_METRICS_FONT_FAMILY | ES_THEME_METRICS_TEXT_SIZE | ES_THEME_METRICS_PREFERRED_WIDTH,
.textSize = 10,
.fontFamily = ES_FONT_MONOSPACED,
.preferredWidth = 400,
},
};
void ThreadBuild(EsGeneric argument) {
(void) argument;
logSendMessages = true;
int result = BuildCore(0, NULL);
EsMessage m;
m.type = result ? MSG_BUILD_FAILED : MSG_BUILD_SUCCESS;
EsMessagePost(NULL, &m);
}
void CommandBuild(EsInstance *instance, EsElement *element, EsCommand *command) {
(void) element;
EsAssert(command == &commandBuild);
if (!buildRunning) {
EsGeneric argument = { 0 };
if (ES_SUCCESS == EsThreadCreate(ThreadBuild, NULL, argument)) {
buildRunning = true;
EsCommandSetEnabled(&commandBuild, false);
EsCommandSetEnabled(&commandLaunch, false);
} else {
EsDialogShow(instance->window, INTERFACE_STRING(BuildCoreCannotCreateBuildThread), NULL, 0, ES_ICON_DIALOG_ERROR, ES_DIALOG_ALERT_OK_BUTTON);
}
}
}
void CommandLaunch(EsInstance *instance, EsElement *element, EsCommand *command) {
(void) instance;
(void) element;
EsAssert(command == &commandLaunch);
if (!singleConfigFileData || !workingDirectory) {
return;
}
EsINIState s = {};
s.buffer = singleConfigFileData;
s.bytes = singleConfigFileBytes;
char *executablePath = NULL;
size_t executablePathBytes = 0;
while (EsINIParse(&s)) {
if (s.sectionClassBytes == 0
&& 0 == EsStringCompareRaw(s.section, s.sectionBytes, EsLiteral("general"))
&& 0 == EsStringCompareRaw(s.key, s.keyBytes, EsLiteral("name"))) {
executablePath = EsStringAllocateAndFormat(&executablePathBytes, "%s/bin/%s.esx", workingDirectoryBytes, workingDirectory, s.valueBytes, s.value);
}
}
if (executablePath) {
EsApplicationRunTemporary(executablePath, executablePathBytes);
EsHeapFree(executablePath, 0, NULL);
}
}
int InstanceCallback(EsInstance *instance, EsMessage *message) {
if (message->type == ES_MSG_INSTANCE_OPEN) {
EsFileStore *fileStore = message->instanceOpen.file;
const char *path = message->instanceOpen.nameOrPath;
size_t pathBytes = message->instanceOpen.nameOrPathBytes;
bool pathIsReal = false;
for (uintptr_t i = 0; i < pathBytes; i++) if (path[i] == '/') pathIsReal = true;
// HACK Remove this limitation.
bool pathCanBeAccessedByPOSIXSubsystem = pathBytes > 3 && path[0] == '0' && path[1] == ':' && path[2] == '/';
if (!pathIsReal) {
// Not a real file, we cannot build here.
EsInstanceOpenComplete(instance, fileStore, false, INTERFACE_STRING(BuildCoreNoFilePath));
} else if (!pathCanBeAccessedByPOSIXSubsystem) {
EsInstanceOpenComplete(instance, fileStore, false, INTERFACE_STRING(BuildCorePathCannotBeAccessedByPOSIXSubsystem));
} else {
size_t newFileBytes;
void *newFileData = EsFileStoreReadAll(fileStore, &newFileBytes);
EsInstanceOpenComplete(instance, fileStore, newFileData != NULL, NULL, 0);
if (newFileData) {
EsHeapFree(singleConfigFileData, 0, NULL);
singleConfigFileBytes = newFileBytes;
singleConfigFileData = newFileData;
singleConfigFileUse = true;
// Change directory.
size_t directoryBytes = pathBytes;
for (uintptr_t i = 0; i < pathBytes; i++) {
if (path[i] == '/') {
directoryBytes = i;
}
}
char *directory = EsHeapAllocate(directoryBytes + 1, false, NULL);
directory[directoryBytes] = 0;
EsMemoryCopy(directory, path, directoryBytes);
EsPOSIXSystemCall(SYS_chdir, (intptr_t) (directory + 2 /* skip drive prefix */), 0, 0, 0, 0, 0);
EsHeapFree(workingDirectory, workingDirectoryBytes ? workingDirectoryBytes + 1 : 0, NULL);
workingDirectory = directory;
workingDirectoryBytes = directoryBytes;
if (!buildRunning) {
EsCommandSetEnabled(&commandBuild, true);
EsCommandSetEnabled(&commandLaunch, true);
}
}
}
}
return ES_HANDLED;
}
void InstanceCreate(EsMessage *message) {
EsInstance *instance = EsInstanceCreate(message, INTERFACE_STRING(BuildCoreTitle));
instance->callback = InstanceCallback;
EsCommandRegister(&commandBuild, instance, INTERFACE_STRING(BuildCoreBuild), CommandBuild, 1, "F5", false);
EsCommandRegister(&commandLaunch, instance, INTERFACE_STRING(BuildCoreLaunch), CommandLaunch, 2, "F6", false);
EsWindowSetIcon(instance->window, ES_ICON_TEXT_X_MAKEFILE);
EsPanel *panelRoot = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND);
EsPanel *panelStack = EsPanelCreate(panelRoot, ES_CELL_FILL | ES_PANEL_HORIZONTAL, &stylePanelStack);
EsPanel *panelButtonStack = EsPanelCreate(panelStack, ES_CELL_V_TOP | ES_PANEL_VERTICAL, &stylePanelButtonStack);
buttonBuild = EsButtonCreate(panelButtonStack, ES_BUTTON_DEFAULT, ES_NULL, INTERFACE_STRING(BuildCoreBuild));
EsCommandAddButton(&commandBuild, buttonBuild);
buttonBuild->accessKey = 'B';
buttonLaunch = EsButtonCreate(panelButtonStack, ES_FLAGS_DEFAULT, ES_NULL, INTERFACE_STRING(BuildCoreLaunch));
EsCommandAddButton(&commandLaunch, buttonLaunch);
buttonLaunch->accessKey = 'L';
EsSpacerCreate(panelStack, ES_CELL_V_FILL, ES_STYLE_SEPARATOR_VERTICAL, 0, 0);
textboxLog = EsTextboxCreate(panelStack, ES_TEXTBOX_MULTILINE | ES_CELL_FILL, &styleTextboxLog);
textboxLog->accessKey = 'O';
EsTextboxSetReadOnly(textboxLog, true);
}
void _start() {
_init();
int argc;
char **argv;
_init();
EsPOSIXInitialise(&argc, &argv);
exit(BuildCore(argc, argv));
EsProcessCreateData createData;
EsProcessGetCreateData(&createData);
if (createData.subsystemID == ES_SUBSYSTEM_ID_POSIX) {
exit(BuildCore(argc, argv));
}
while (true) {
EsMessage *message = EsMessageReceive();
if (message->type == ES_MSG_INSTANCE_CREATE) {
InstanceCreate(message);
} else if (message->type == ES_MSG_APPLICATION_EXIT) {
EsHeapFree(singleConfigFileData, 0, NULL);
singleConfigFileData = NULL;
} else if (message->type == MSG_BUILD_SUCCESS || message->type == MSG_BUILD_FAILED) {
buildRunning = false;
EsCommandSetEnabled(&commandBuild, true);
EsCommandSetEnabled(&commandLaunch, true);
EsTextboxMoveCaretRelative(textboxLog, ES_TEXTBOX_MOVE_CARET_ALL);
if (message->type == MSG_BUILD_FAILED) {
EsTextboxInsert(textboxLog, INTERFACE_STRING(BuildCoreBuildFailed), false);
EsElementFocus(buttonBuild, ES_ELEMENT_FOCUS_ONLY_IF_NO_FOCUSED_ELEMENT);
} else {
EsTextboxInsert(textboxLog, INTERFACE_STRING(BuildCoreBuildSuccess), false);
EsElementFocus(buttonLaunch, ES_ELEMENT_FOCUS_ONLY_IF_NO_FOCUSED_ELEMENT);
}
EsTextboxEnsureCaretVisible(textboxLog, false);
_EsPathAnnouncePathMoved(NULL, 0, workingDirectory, workingDirectoryBytes);
} else if (message->type == MSG_LOG) {
char *copy = message->user.context1.p;
EsTextboxMoveCaretRelative(textboxLog, ES_TEXTBOX_MOVE_CARET_ALL);
EsTextboxInsert(textboxLog, copy, -1, false);
EsTextboxEnsureCaretVisible(textboxLog, false);
EsHeapFree(copy, 0, NULL);
}
}
}
#endif

18
util/build_core.ini Normal file
View File

@ -0,0 +1,18 @@
[general]
name=Build Core
hidden=1
permission_all_files=1
permission_posix_subsystem=1
permission_run_temporary_application=1
[build]
source=util/build_core.c
[@file_type]
extension=build_core
name=Build config
icon=icon_text_x_makefile
[@handler]
extension=build_core
action=open

View File

@ -1,3 +1,7 @@
// 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 Searching for a specific option.
// TODO Option descriptions.

View File

@ -1,3 +1,7 @@
// 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.
#define UI_IMPLEMENTATION
#define ES_CRT_WITHOUT_PREFIX
#define EsPainter _EsPainter

View File

@ -1,3 +1,7 @@
// 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 Extensions: binary search, shifting glyphs in editor, undo/redo.
#define UI_IMPLEMENTATION

View File

@ -145,7 +145,7 @@ Token NextToken() {
position--;
#define COMPARE_KEYWORD(x, y) if (strlen(x) == token.value && 0 == memcmp(x, token.text, token.value)) token.type = y
#define COMPARE_KEYWORD(x, y) if (strlen(x) == (size_t) token.value && 0 == memcmp(x, token.text, token.value)) token.type = y
COMPARE_KEYWORD("define", TOKEN_DEFINE);
COMPARE_KEYWORD("enum", TOKEN_ENUM);
COMPARE_KEYWORD("struct", TOKEN_STRUCT);
@ -1005,6 +1005,8 @@ void OutputZigType(Entry *entry) {
}
void OutputZigVariable(Entry *entry, bool expandStrings, const char *nameSuffix) {
(void) expandStrings;
if (0 == strcmp(entry->name, "error")) {
entry->name[3] = ' ';
entry->name[4] = ' ';