mirror of https://gitlab.com/nakst/essence
317 lines
7.5 KiB
C++
317 lines
7.5 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.
|
|
|
|
#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;
|
|
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 <absolute path to esx>\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 <path>\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;
|
|
}
|