essence-os/apps/posix_launcher.cpp

232 lines
6.0 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 Terminating the child process on exit.
// TODO Handle ES_MSG_INSTANCE_CLOSE, and ignore following MSG_RECEIVED_OUTPUTs.
#include <essence.h>
#include <shared/strings.cpp>
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <time.h>
#define MSG_RECEIVED_OUTPUT ((EsMessageType) (ES_MSG_USER_START + 1))
EsInstance *instance;
EsHandle commandEvent;
char outputBuffer[262144];
uintptr_t outputBufferPosition;
EsMutex mutex;
int stdinWritePipe;
EsTextbox *textboxOutput, *textboxInput;
const EsStyle styleOutputTextbox = {
.inherit = ES_STYLE_TEXTBOX_NO_BORDER,
.metrics = {
.mask = ES_THEME_METRICS_FONT_FAMILY | ES_THEME_METRICS_TEXT_SIZE,
.textSize = 12,
.fontFamily = ES_FONT_MONOSPACED,
},
};
const EsStyle styleInputTextbox = {
.inherit = ES_STYLE_TEXTBOX_BORDERED_SINGLE,
.metrics = {
.mask = ES_THEME_METRICS_FONT_FAMILY | ES_THEME_METRICS_TEXT_SIZE,
.textSize = 12,
.fontFamily = ES_FONT_MONOSPACED,
},
};
const EsStyle styleInputRow = {
.inherit = ES_STYLE_PANEL_FORM_TABLE,
.metrics = {
.mask = ES_THEME_METRICS_INSETS,
.insets = ES_RECT_1(7),
},
};
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() {
WriteToOutputTextbox("Starting busybox shell...\n", -1);
char *argv[3] = { (char *) "busybox", (char *) "sh", nullptr };
char executable[4096];
int status;
int standardOutputPipe[2];
int standardInputPipe[2];
pid_t pid;
char *envp[5] = {
(char *) "LANG=en_US.UTF-8",
(char *) "HOME=/",
(char *) "PATH=/Applications/POSIX/bin",
(char *) "TMPDIR=/Applications/POSIX/tmp",
nullptr
};
executable[EsStringFormat(executable, sizeof(executable) - 1, "/Applications/POSIX/bin/%z", argv[0])] = 0;
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) {
WriteToOutputTextbox("\nUnable to vfork().\n", -1);
}
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);
}
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);
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) {
// Cannot access the C standard library on this thread!
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, EsStyleIntern(&styleOutputTextbox));
EsSpacerCreate(panel, ES_CELL_H_FILL, ES_STYLE_SEPARATOR_HORIZONTAL);
EsPanel *row = EsPanelCreate(panel, ES_CELL_H_FILL | ES_PANEL_HORIZONTAL, EsStyleIntern(&styleInputRow));
EsTextDisplayCreate(row, ES_FLAGS_DEFAULT, ES_STYLE_TEXT_LABEL, EsLiteral("Input:"));
textboxInput = EsTextboxCreate(row, ES_CELL_H_FILL, EsStyleIntern(&styleInputTextbox));
EsTextboxEnableSmartReplacement(textboxInput, false);
EsTextboxSetReadOnly(textboxOutput, true);
textboxInput->messageUser = ProcessTextboxInputMessage;
EsElementFocus(textboxInput);
EsEventSet(commandEvent); // Ready to receive output.
} else if (message->type == MSG_RECEIVED_OUTPUT) {
EsMutexAcquire(&mutex);
if (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);
EsWaitSingle(commandEvent);
RunCommandThread();
return 0;
}