// 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. #include #include #define _GNU_SOURCE #include #include #include #include #define MSG_RECEIVED_OUTPUT ((EsMessageType) (ES_MSG_USER_START + 1)) EsInstance *instance; EsHandle commandEvent; char outputBuffer[262144]; uintptr_t outputBufferPosition; EsMutex mutex; volatile bool runningCommand; int stdinWritePipe; char *command; EsTextbox *textboxOutput, *textboxInput; const EsStyle styleMonospacedTextbox = { .inherit = ES_STYLE_TEXTBOX_NO_BORDER, .metrics = { .mask = ES_THEME_METRICS_FONT_FAMILY | ES_THEME_METRICS_TEXT_SIZE, .textSize = 12, .fontFamily = ES_FONT_MONOSPACED, }, }; char *ParseArgument(char **position) { char *start = *position; while (*start == ' ') { start++; } if (!(*start)) { return nullptr; } char *end = start; while ((*end != ' ' || (end != start && end[-1] == '\\')) && *end) { end++; } if (*end) { *end = 0; end++; } *position = end; return start; } void WriteToOutputTextbox(const char *string, ptrdiff_t stringBytes) { if (stringBytes == -1) { stringBytes = EsCRTstrlen(string); } bool done = false, postMessage = false; while (true) { EsMutexAcquire(&mutex); if (outputBufferPosition + stringBytes <= sizeof(outputBuffer)) { EsMemoryCopy(outputBuffer + outputBufferPosition, string, stringBytes); postMessage = outputBufferPosition == 0; outputBufferPosition += stringBytes; done = true; } EsMutexRelease(&mutex); if (!done) { // The main thread is busy. Wait a little bit before trying again. EsSleep(100); } else { break; } } if (postMessage) { EsMessage m = {}; m.type = MSG_RECEIVED_OUTPUT; EsMessagePost(nullptr, &m); } } void RunCommandThread() { char *argv[64]; int argc = 0; char executable[4096]; int status; int standardOutputPipe[2]; int standardInputPipe[2]; pid_t pid; struct timespec startTime, endTime; char *envp[5] = { (char *) "LANG=en_US.UTF-8", (char *) "HOME=/", (char *) "PATH=/Applications/POSIX/bin", (char *) "TMPDIR=/Applications/POSIX/tmp", nullptr }; char *commandPosition = command; while (argc < 63) { argv[argc] = ParseArgument(&commandPosition); if (!argv[argc]) break; argc++; } if (!argc) { goto done; } argv[argc] = nullptr; if (0 == EsCRTstrcmp(argv[0], "run")) { if (argc != 2) { WriteToOutputTextbox("\nUsage: run \n", -1); } else { EsApplicationRunTemporary(instance, argv[1], EsCStringLength(argv[1])); } WriteToOutputTextbox("\n----------------\n", -1); goto done; } else if (0 == EsCRTstrcmp(argv[0], "cd")) { if (argc != 2) { WriteToOutputTextbox("\nUsage: cd \n", -1); } else { chdir(argv[1]); WriteToOutputTextbox("\nNew working directory:\n", -1); WriteToOutputTextbox(getcwd(nullptr, 0), -1); WriteToOutputTextbox("\n", -1); } WriteToOutputTextbox("\n----------------\n", -1); goto done; } if (argv[0][0] == '/') { executable[EsStringFormat(executable, sizeof(executable) - 1, "%z", argv[0])] = 0; } else { executable[EsStringFormat(executable, sizeof(executable) - 1, "/Applications/POSIX/bin/%z", argv[0])] = 0; } clock_gettime(CLOCK_MONOTONIC, &startTime); pipe(standardOutputPipe); pipe(standardInputPipe); pid = vfork(); if (pid == 0) { dup2(standardInputPipe[0], 0); dup2(standardOutputPipe[1], 1); dup2(standardOutputPipe[1], 2); close(standardInputPipe[0]); close(standardOutputPipe[1]); execve(executable, argv, envp); WriteToOutputTextbox("\nThe executable failed to load.\n", -1); _exit(-1); } else if (pid == -1) { // TODO Report the error. } close(standardInputPipe[0]); close(standardOutputPipe[1]); EsMutexAcquire(&mutex); stdinWritePipe = standardInputPipe[1]; EsMutexRelease(&mutex); while (true) { char buffer[1024]; ssize_t bytesRead = read(standardOutputPipe[0], buffer, 1024); if (bytesRead <= 0) { break; } else { WriteToOutputTextbox(buffer, bytesRead); } } EsMutexAcquire(&mutex); stdinWritePipe = 0; EsMutexRelease(&mutex); close(standardInputPipe[1]); close(standardOutputPipe[0]); wait4(-1, &status, 0, NULL); clock_gettime(CLOCK_MONOTONIC, &endTime); { double startTimeS = startTime.tv_sec + startTime.tv_nsec / 1000000000.0; double endTimeS = endTime.tv_sec + endTime.tv_nsec / 1000000000.0; char buffer[256]; size_t bytes = EsStringFormat(buffer, sizeof(buffer), "\nProcess exited with status %d.\nExecution time: %Fs.\n", status >> 8, endTimeS - startTimeS); WriteToOutputTextbox(buffer, bytes); WriteToOutputTextbox("\n----------------\n", -1); } done:; EsHeapFree(command); __sync_synchronize(); runningCommand = false; } int ProcessTextboxInputMessage(EsElement *, EsMessage *message) { if (message->type == ES_MSG_KEY_DOWN) { if (message->keyboard.scancode == ES_SCANCODE_ENTER && !message->keyboard.modifiers && EsTextboxGetLineLength(textboxInput)) { char *data = EsTextboxGetContents(textboxInput); if (!runningCommand) { runningCommand = true; command = data; EsTextboxInsert(textboxOutput, "\n> ", -1, false); EsTextboxInsert(textboxOutput, command, -1, false); EsTextboxInsert(textboxOutput, "\n", -1, false); EsTextboxEnsureCaretVisible(textboxOutput, false); EsEventSet(commandEvent); } else { EsTextboxInsert(textboxOutput, data, -1, false); EsTextboxInsert(textboxOutput, "\n", -1, false); EsMutexAcquire(&mutex); if (stdinWritePipe) { write(stdinWritePipe, data, EsCStringLength(data)); write(stdinWritePipe, "\n", 1); } EsMutexRelease(&mutex); EsHeapFree(data); } EsTextboxClear(textboxInput, false); return ES_HANDLED; } } return 0; } void MessageLoopThread(EsGeneric) { EsMessageMutexAcquire(); while (true) { EsMessage *message = EsMessageReceive(); if (message->type == ES_MSG_INSTANCE_CREATE) { EsAssert(!instance); instance = EsInstanceCreate(message, "POSIX Launcher"); EsWindow *window = instance->window; EsWindowSetIcon(window, ES_ICON_UTILITIES_TERMINAL); EsPanel *panel = EsPanelCreate(window, ES_PANEL_VERTICAL | ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_BACKGROUND); 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); textboxInput->messageUser = ProcessTextboxInputMessage; EsElementFocus(textboxInput); } else if (message->type == MSG_RECEIVED_OUTPUT) { EsMutexAcquire(&mutex); if (outputBufferPosition) { // EsPrint("Inserting %d bytes...\n", outputBufferPosition); EsTextboxMoveCaretRelative(textboxOutput, ES_TEXTBOX_MOVE_CARET_ALL); EsTextboxInsert(textboxOutput, outputBuffer, outputBufferPosition, false); EsTextboxEnsureCaretVisible(textboxOutput, false); outputBufferPosition = 0; } EsMutexRelease(&mutex); } } } int main(int argc, char **argv) { (void) argc; (void) argv; commandEvent = EsEventCreate(true); EsMessageMutexRelease(); EsThreadCreate(MessageLoopThread, nullptr, 0); #if 0 runningCommand = true; command = (char *) EsHeapAllocate(128, true); EsStringFormat(command, 128, "gcc es/util/build_core.c -g"); EsEventSet(commandEvent); #endif while (true) { EsWaitSingle(commandEvent); RunCommandThread(); } return 0; }