From b4a74a93adeea037a3b0b4bc3ccbe5f28248f083 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sat, 16 Mar 2013 22:40:02 +0100 Subject: [PATCH] Add the Trianglix Desktop Environment. --- Makefile | 1 + doc/user-guide | 5 + trianglix/.gitignore | 1 + trianglix/Makefile | 27 + trianglix/trianglix.cpp | 1973 +++++++++++++++++++++++++++++++++++++++ trianglix/vector.h | 132 +++ 6 files changed, 2139 insertions(+) create mode 100644 trianglix/.gitignore create mode 100644 trianglix/Makefile create mode 100644 trianglix/trianglix.cpp create mode 100644 trianglix/vector.h diff --git a/Makefile b/Makefile index 28193086..21714c72 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ mkinitrd \ regress \ sh \ tix \ +trianglix \ utils \ kernel diff --git a/doc/user-guide b/doc/user-guide index 8e67451b..f593d7e2 100644 --- a/doc/user-guide +++ b/doc/user-guide @@ -196,6 +196,7 @@ Sortix comes with a number of home-made programs. Here is an overview: * `sort` - sort lines of text files * `tail` - display end of file * `time` - measure program running time +* `trianglix` - triangle system * `tr` - translate, squeeze and/or delete characters * `true` - exit with a success status * `type` - type raw characters directly into the terminal @@ -371,6 +372,10 @@ Graphical User Interface The `dispd` display server is still under development. Sortix does not feature any documented graphical user interface at the moment. +Sortix comes with the orthogonal graphical multi-user revolutionary triangle +system Trianglix, an attempt at making the most foreign yet somehow usable user +interface. You just need to run `trianglix` to start it. + Network ------- diff --git a/trianglix/.gitignore b/trianglix/.gitignore new file mode 100644 index 00000000..017afedf --- /dev/null +++ b/trianglix/.gitignore @@ -0,0 +1 @@ +trianglix diff --git a/trianglix/Makefile b/trianglix/Makefile new file mode 100644 index 00000000..11d05bea --- /dev/null +++ b/trianglix/Makefile @@ -0,0 +1,27 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/compiler.mak +include ../build-aux/version.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CXXFLAGS?=$(OPTLEVEL) + +CXXFLAGS:=$(CXXFLAGS) -std=gnu++0x -Wall -Wextra -fno-exceptions -fno-rtti -msse -msse2 + +BINARY:=trianglix + +LIBS:=-ldispd + +all: $(BINARY) + +.PHONY: all install uninstall clean + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(BINARY) $(DESTDIR)$(BINDIR) + +%: %.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $< -o $@ $(LIBS) + +clean: + rm -f $(BINARY) *.o diff --git a/trianglix/trianglix.cpp b/trianglix/trianglix.cpp new file mode 100644 index 00000000..50faab65 --- /dev/null +++ b/trianglix/trianglix.cpp @@ -0,0 +1,1973 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013, 2014. + + This file is part of trianglix. + + trianglix is free software: you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License as published by the Free + Software Foundation, either version 3 of the License, or (at your option) + any later version. + + trianglix is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + details. + + You should have received a copy of the GNU Lesser General Public License + along with trianglix. If not, see . + + trianglix.cpp + The Trianglix Desktop Environment. + +*******************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vector.h" + +const int FONT_WIDTH = 8UL; +const int FONT_HEIGHT = 16UL; +const int FONT_NUMCHARS = 256UL; +const int FONT_CHARSIZE = FONT_WIDTH * FONT_HEIGHT / 8UL; + +bool use_runes = false; +bool configured_use_runes = false; +uint8_t font[FONT_CHARSIZE * FONT_NUMCHARS]; + +char* print_string(const char* format, ...) +{ + char* ret = NULL; + va_list ap; + va_start(ap, format); + vasprintf(&ret, format, ap); + va_end(ap); + return ret; +} + +char* gibberish() +{ + size_t length = 1 + arc4random_uniform(15); + char* result = (char*) malloc(length + 1); + for ( size_t i = 0; i < length; i++ ) + result[i] = 'A' + arc4random_uniform(26); + result[length] = '\0'; + return result; +} + +// TODO: This might be out of sync with execvpe's exact semantics. +bool has_path_executable(const char* name) +{ + const char* path = getenv("PATH"); + struct stat st; + if ( !path || strchr(name, '/') ) + return stat(name, &st) == 0; + char* path_copy = strdup(path); + if ( !path_copy ) + return false; + char* input = path_copy; + char* saved_ptr; + while ( char* component = strtok_r(input, ":", &saved_ptr) ) + { + input = NULL; + char* fullpath = print_string("%s/%s", component, name); + if ( !fullpath ) + return free(fullpath), free(path_copy), false; + if ( stat(fullpath, &st) == 0 ) + return free(fullpath), free(path_copy), true; + free(fullpath); + } + free(path_copy); + return errno = ENOENT, false; +} + +static float timespec_to_float(struct timespec ts) +{ + return (float) ts.tv_sec + (float) ts.tv_nsec / 1E9; +} + +DIR* open_home_directory() +{ + if ( struct passwd* user = getpwuid(getuid()) ) + if ( DIR* dir = opendir(user->pw_dir) ) + return dir; + return opendir("/"); +} + +static uint32_t MakeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) +{ + return a << 24U | r << 16U | g << 8U | b << 0U; +} + +static uint32_t BlendPixel(uint32_t bg, uint32_t fg) +{ + uint8_t fg_a = fg >> 24; + if ( fg_a == 255 ) + return fg; + if ( fg_a == 0 ) + return bg; + uint8_t fg_r = fg >> 16; + uint8_t fg_g = fg >> 8; + uint8_t fg_b = fg >> 0; + uint8_t bg_r = bg >> 16; + uint8_t bg_g = bg >> 8; + uint8_t bg_b = bg >> 0; + uint8_t ret_a = 255; + uint8_t ret_r = ((255-fg_a)*bg_r + fg_a*fg_r) / 256; + uint8_t ret_g = ((255-fg_a)*bg_g + fg_a*fg_g) / 256; + uint8_t ret_b = ((255-fg_a)*bg_b + fg_a*fg_b) / 256; + return ret_a << 24U | ret_r << 16U | ret_g << 8U | ret_b << 0U; +} + +static bool ForkAndWait() +{ + unsigned int old_termmode; + gettermmode(0, &old_termmode); + pid_t child_pid = fork(); + if ( child_pid < 0 ) + return false; + if ( child_pid ) + { + int status; + waitpid(child_pid, &status, 0); + tcsetpgrp(0, getpgid(0)); + settermmode(0, old_termmode); + return false; + } + close(0); + close(1); + close(2); + open("/dev/tty", O_RDONLY); + open("/dev/tty", O_WRONLY); + open("/dev/tty", O_WRONLY); + setpgid(0, 0); + tcsetpgrp(0, getpgid(0)); +#if 1 /* Magic to somehow fix a weird keyboard-related bug nortti has. */ + settermmode(0, TERMMODE_UNICODE | TERMMODE_SIGNAL | TERMMODE_UTF8 | TERMMODE_LINEBUFFER | TERMMODE_ECHO | TERMMODE_NONBLOCK); + char c; + while ( 0 <= read(0, &c, sizeof(c)) ); +#endif + settermmode(0, TERMMODE_UNICODE | TERMMODE_SIGNAL | TERMMODE_UTF8 | TERMMODE_LINEBUFFER | TERMMODE_ECHO); + printf("\e[m\e[2J\e[H"); + fflush(stdout); + fsync(0); + fsync(1); + fsync(2); + return true; +} + +__attribute__((unused)) +static void ExecuteFile(const char* path, int curdirfd) +{ + if ( ForkAndWait() ) + { + fchdir(curdirfd); + execl(path, path, (char*) NULL); + _exit(127); + } +} + +__attribute__((unused)) +static void DisplayFile(const char* path, int curdirfd) +{ + if ( ForkAndWait() ) + { + fchdir(curdirfd); + execlp("editor", "editor", path, (char*) NULL); + _exit(127); + } +} + +__attribute__((unused)) +static void ExecutePath(const char* path, int curdirfd) +{ + if ( ForkAndWait() ) + { + fchdir(curdirfd); + execlp(path, path, (char*) NULL); + _exit(127); + } +} + +void ExecuteShellCommand(const char* command, int curdirfd) +{ + if ( !strcmp(command, "exit") ) + exit(0); + unsigned int old_termmode; + gettermmode(0, &old_termmode); + if ( ForkAndWait() ) + { + fchdir(curdirfd); + execlp("sh", "sh", "-c", command, (char*) NULL); + error(127, errno, "`%s'", "sh"); + } + unsigned int cur_termmode; + gettermmode(0, &cur_termmode); + if ( old_termmode == cur_termmode ) + { + printf("\e[30;47m\e[J"); + printf("-- Press enter to return to Trianglix --\n"); + printf("\e[25l"); + fflush(stdout); + settermmode(0, TERMMODE_UNICODE | TERMMODE_UTF8 | TERMMODE_LINEBUFFER); + char line[256]; + read(0, line, sizeof(line)); + printf("\e[25h"); + fflush(stdout); + } +} + +enum object_type +{ + TYPE_DIRECTORY, + TYPE_FILE, +}; + +class action; +class object; + +class action +{ +public: + action(const char* name, class object* object) : name(strdup(name)), object(object) { } + ~action() { free(name); } + const char* get_name() { return name; } + class object* get_object() { return object; } + +private: + char* name; + class object* object; + +}; + +class object +{ +public: + object() : parent_object(NULL), error_string("") { } + virtual ~object() { } + virtual enum object_type type() = 0; + virtual class object* factory() { return NULL; } + virtual const char* title() { return ""; } + virtual const char* prompt() { return ""; } + virtual bool is_password_prompt() { return false; } + virtual bool is_core() { return false; } + virtual bool is_unstable_core() { return false; } + virtual const char* error_message() { return error_string; } + virtual void clear_error() { error_string = ""; } + virtual class action** list_actions(size_t* /*num_actions*/) { return NULL; } + virtual void invoke() { } + virtual class object* open(const char* /*path*/) { return NULL; } + virtual class object* command_line(const char* /*command*/) { return NULL; } + +public: + class object* parent_object; + const char* error_string; + +}; + +class directory : public object +{ +public: + directory(DIR* dir) + : dir(dir), path(canonicalize_file_name_at(dirfd(dir), ".")) { } + virtual ~directory() { closedir(dir); free(path); } + +public: + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual const char* title() { return path; } + virtual class action** list_actions(size_t* num_actions); + virtual class object* open(const char* path); + virtual class object* command_line(const char* command); + +private: + DIR* dir; + char* path; + +}; + +class file : public object +{ +public: + file(const char* path) : path(strdup(path)) { } + virtual ~file() { free(path); } + +public: + virtual enum object_type type() { return TYPE_FILE; } + virtual void invoke(); + +private: + char* path; + +}; + +class object* directory::open(const char* path) +{ + if ( !strcmp(path, ".") ) + return this; + int fd = openat(dirfd(dir), path, O_RDONLY | O_CLOEXEC); + if ( fd < 0 ) + return NULL; + if ( !strcmp(path, "..") ) + { + struct stat old_stat, new_stat; + if ( fstat(dirfd(dir), &old_stat) == 0 && fstat(fd, &new_stat) == 0 ) + { + if ( old_stat.st_dev == new_stat.st_dev && + old_stat.st_ino == new_stat.st_ino ) + return this; + } + } + struct stat st; + fstat(fd, &st); + if ( S_ISDIR(st.st_mode) ) + return new directory(fdopendir(fd)); + close(fd); + if ( char* file_path = canonicalize_file_name_at(dirfd(dir), path) ) + { + class object* result = new file(file_path); + free(file_path); + return result; + } + else + { + error(0, errno, "canonicalize_file_name_at: %s", path); + sleep(1); + } + return NULL; +} + +class action** directory::list_actions(size_t* num_actions) +{ + struct dirent** entries; + int num_entries = dscandir_r(dir, &entries, NULL, NULL, alphasort_r, NULL); + if ( num_entries < 0 ) + return NULL; + action** actions = new class action*[num_entries + 1]; + for ( int i = 0; i < num_entries; i++ ) + actions[i] = new action(entries[i]->d_name, open(entries[i]->d_name)); + for ( int i = 0; i < num_entries; i++ ) + free(entries[i]); + free(entries); + actions[num_entries] = new action("Back", parent_object); + return *num_actions = (size_t) num_entries + 1, actions; +} + +class object* directory::command_line(const char* command) +{ + if ( !strncmp(command, "cd ", 3) ) + { + class object* new_object = open(command + 3); + if ( new_object && new_object->type() == TYPE_DIRECTORY ) + return new_object; + delete new_object; + } + + ExecuteShellCommand(command, dirfd(dir)); + + return NULL; +} + +class object* directory_command_line(const char* command) +{ + if ( !strncmp(command, "cd ", 3) ) + { + class object* user_directory = new directory(open_home_directory()); + class object* new_object = user_directory->open(command + 3); + if ( new_object != user_directory ) + delete user_directory; + if ( new_object && new_object->type() == TYPE_DIRECTORY ) + return new_object; + delete new_object; + } + + DIR* dir = open_home_directory(); + ExecuteShellCommand(command, dirfd(dir)); + closedir(dir); + + return NULL; +} + +void file::invoke() +{ + struct stat st; + if ( stat(path, &st) == 0 ) + { + char* dir_path = strdup(path); + dirname(dir_path); + DIR* dir = opendir(dir_path); + if ( !dir ) + dir = opendir("/"); + if ( S_ISREG(st.st_mode) && (st.st_mode & 0111) ) + ExecuteFile(path, dirfd(dir)); + else if ( S_ISREG(st.st_mode) && !(st.st_mode & 0111) ) + DisplayFile(path, dirfd(dir)); + closedir(dir); + } +} + +class shell_command : public object +{ +public: + shell_command(const char* command) : command(strdup(command)) { } + virtual ~shell_command() { free(command); } + +public: + virtual enum object_type type() { return TYPE_FILE; } + virtual void invoke(); + +private: + char* command; + +}; + +void shell_command::invoke() +{ + DIR* dir = NULL; + if ( struct passwd* user = getpwuid(getuid()) ) + dir = opendir(user->pw_dir); + if ( !dir ) + dir = opendir("/"); + ExecuteShellCommand(command, dirfd(dir)); + closedir(dir); +} + +class path_program : public object +{ +public: + path_program(const char* filename) : filename(strdup(filename)) { } + virtual ~path_program() { free(filename); } + +public: + virtual enum object_type type() { return TYPE_FILE; } + virtual void invoke(); + +private: + char* filename; + +}; + +void path_program::invoke() +{ + DIR* dir = NULL; + if ( struct passwd* user = getpwuid(getuid()) ) + dir = opendir(user->pw_dir); + if ( !dir ) + dir = opendir("/"); + ExecutePath(filename, dirfd(dir)); + closedir(dir); +} + +class exec_program : public object +{ +public: + exec_program(const char* program) : program(strdup(program)) { } + virtual ~exec_program() { free(program); } + +public: + virtual enum object_type type() { return TYPE_FILE; } + virtual void invoke(); + +private: + char* program; + +}; + +void exec_program::invoke() +{ + execlp(program, program, (char*) NULL); +} + +class development : public object +{ +public: + development() { } + virtual ~development() { } + +public: + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual const char* title() { return "Development"; } + virtual class action** list_actions(size_t* num_actions); + virtual class object* command_line(const char* command); + +}; + +class action** development::list_actions(size_t* num_actions) +{ + struct stat st; + class action** actions = new action*[4 + 1]; + size_t index = 0; + if ( has_path_executable("editor") ) + actions[index++] = new action("Editor", new path_program("editor")); + if ( has_path_executable("sh") ) + actions[index++] = new action("Shell", new path_program("sh")); + if ( has_path_executable("make") && + has_path_executable("colormake") && + stat("/src/system", &st) == 0 ) + actions[index++] = new action("Recompile System", + new shell_command("colormake -C /src/system system")); + if ( stat("/src", &st) == 0 ) + actions[index++] = new action("Source Code", new directory(opendir("/src"))); + actions[index++] = new action("Reboot GUI", new exec_program("trianglix")); + actions[index++] = new action("Back", parent_object); + return *num_actions = index, actions; +} + +class object* development::command_line(const char* command) +{ + return directory_command_line(command); +} + +class games : public object +{ +public: + games() { } + virtual ~games() { } + +public: + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual const char* title() { return "Games"; } + virtual class action** list_actions(size_t* num_actions); + virtual class object* command_line(const char* command); + +}; + +class action** games::list_actions(size_t* num_actions) +{ + class action** actions = new action*[2 + 1]; + size_t index = 0; + if ( has_path_executable("asteroids") ) + actions[index++] = new action("Asteroids", new path_program("asteroids")); + if ( has_path_executable("quake") ) + actions[index++] = new action("Quake", new path_program("quake")); + actions[index++] = new action("Back", parent_object); + return *num_actions = index, actions; +} + +class object* games::command_line(const char* command) +{ + return directory_command_line(command); +} + +class core : public object +{ +public: + core(size_t depth = 0) : error(NULL), title_string(gibberish()), depth(depth) { } + virtual ~core() { free(error); free(title_string); } + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual bool is_core() { return true; } + virtual bool is_unstable_core() { return 2 <= depth; } + virtual const char* title() { return title_string; } + virtual const char* prompt() { return "|ROOT>"; } + virtual class action** list_actions(size_t* num_actions); + virtual class object* command_line(const char* command); + +private: + char* error; + char* title_string; + size_t depth; + +}; + +class action** core::list_actions(size_t* num_actions_ptr) +{ + if ( 2 <= depth && arc4random_uniform(10000) < 200 ) + write(1, NULL, 1); + + size_t num_actions = 1 + arc4random_uniform(31); + class action** actions = new action*[num_actions]; + for ( size_t i = 0; i < num_actions; i++ ) + { + char* name = gibberish(); + actions[i] = new action(name, new core(depth + 1)); + free(name); + } + return *num_actions_ptr = num_actions, actions; +} + +class object* core::command_line(const char* /*command*/) +{ + if ( arc4random_uniform(2) == 0 ) + return new core(); + else + { + free(error); + error_string = error = gibberish(); + return NULL; + } +} + +class decide_runes : public object +{ +public: + decide_runes(bool answer) : answer(answer) { } + virtual ~decide_runes() { } + virtual enum object_type type() { return TYPE_FILE; } + virtual void invoke(); + +private: + bool answer; + +}; + +void decide_runes::invoke() +{ + use_runes = answer; + configured_use_runes = true; + if ( answer ) + { + unlink("/etc/rune-disable"); + close(::open("/etc/rune-enable", O_WRONLY | O_CREAT, 0666)); + } + else + { + unlink("/etc/rune-enable"); + close(::open("/etc/rune-disable", O_WRONLY | O_CREAT, 0666)); + } +} + +struct create_user_prompt +{ + const char* prompt; + bool is_password_prompt; +}; + +struct create_user_prompt create_user_prompts[] = +{ + { "Enter username:", false }, + { "Enter password:", true }, +}; + +const size_t num_create_user_prompts = sizeof(create_user_prompts) / sizeof(create_user_prompts[0]); + +class create_user : public object +{ +public: + create_user(); + virtual ~create_user(); + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual const char* title() { return "Create user"; } + virtual const char* prompt(); + virtual bool is_password_prompt(); + virtual class object* command_line(const char* command); + +private: + size_t prompt_index; + char* answers[num_create_user_prompts]; + +}; + +create_user::create_user() +{ + prompt_index = 0; +} + +create_user::~create_user() +{ + for ( size_t i = 0; i < prompt_index; i++ ) + free(answers[i]); +} + +const char* create_user::prompt() +{ + return create_user_prompts[prompt_index].prompt; +} + +bool create_user::is_password_prompt() +{ + return create_user_prompts[prompt_index].is_password_prompt; +} + +class object* create_user::command_line(const char* command) +{ + if ( prompt_index == 0 && !command[0] ) + return error_string = "Username cannot be empty", (class object*) NULL; + if ( prompt_index == 0 && strchr(command, '/') ) + return error_string = "Username cannot contain slash characters", (class object*) NULL; + + answers[prompt_index] = strdup(command); + + if ( prompt_index + 1 < num_create_user_prompts ) + { + prompt_index++; + return NULL; + } + + uid_t highest_uid = 0; + FILE* fp = openpw(); + while ( struct passwd* user = fgetpwent(fp) ) + if ( highest_uid < user->pw_uid ) + highest_uid = user->pw_uid; + fclose(fp); + + struct passwd user; + memset(&user, 0, sizeof(user)); + user.pw_uid = highest_uid + 1; + user.pw_gid = (gid_t) user.pw_uid; + user.pw_dir = print_string("/home/%s", answers[0]); + user.pw_gecos = strdup(answers[0]); + user.pw_name = strdup(answers[0]); + user.pw_passwd = strdup(answers[1]); + user.pw_shell = strdup("sh"); + + mkdir(user.pw_dir, 777); + + fp = fopen("/etc/passwd", "a"); + fprintf(fp, "%s:%s:%ju:%ju:%s:%s:%s\n", + user.pw_name, + user.pw_passwd, + (uintmax_t) user.pw_uid, + (uintmax_t) user.pw_gid, + user.pw_gecos, + user.pw_dir, + user.pw_shell); + fclose(fp); + + return parent_object; +} + +class administration : public object +{ +public: + administration() { } + virtual ~administration() { } + +public: + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual const char* title() { return "Administration"; } + virtual class action** list_actions(size_t* num_actions); + +}; + +class action** administration::list_actions(size_t* num_actions) +{ + class action** actions = new action*[4 + 1]; + size_t index = 0; + actions[index++] = new action("Create user", new create_user()); + actions[index++] = new action("Enable Runes", new decide_runes(true)); + actions[index++] = new action("Disable Runes", new decide_runes(false)); + actions[index++] = new action("Trinit Core", new core()); + actions[index++] = new action("Back", parent_object); + return *num_actions = index, actions; +} + +class desktop : public object +{ +public: + desktop() { } + virtual ~desktop() { } + +public: + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual const char* title() { return "Desktop"; } + virtual class action** list_actions(size_t* num_actions); + virtual class object* command_line(const char* command); + +}; + +class action** desktop::list_actions(size_t* num_actions) +{ + class action** actions = new action*[*num_actions = 7]; + actions[0] = new action("Home", new directory(open_home_directory())); + actions[1] = new action("Filesystem", new directory(opendir("/"))); + actions[2] = new action("Games", new games()); + actions[3] = new action("Shell", new path_program("sh")); + actions[4] = new action("Development", new development()); + actions[5] = new action("Administration", new administration()); + actions[6] = new action("Logout", parent_object); + return actions; +} + +class object* desktop::command_line(const char* command) +{ + return directory_command_line(command); +} + +class object* log_user_in(struct passwd* user) +{ + setuid(user->pw_uid); + setenv("USERNAME", user->pw_name, 1); + setenv("HOME", user->pw_dir, 1); + setenv("SHELL", user->pw_shell, 1); + setenv("DEFAULT_STUFF", "NO", 1); + return new desktop(); +} + +class poweroff : public object +{ +public: + poweroff() { } + virtual ~poweroff() { } + +public: + virtual enum object_type type() { return TYPE_FILE; } + virtual void invoke(); + +}; + +void poweroff::invoke() +{ + exit(0); +} + +class login : public object +{ +public: + login(const char* username) : username(strdup(username)) { } + virtual ~login() { } + +public: + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual class object* factory(); + virtual const char* title() { return "Authentication required "; } + virtual const char* prompt() { return "Enter Password:"; } + virtual bool is_password_prompt() { return true; } + virtual class action** list_actions(size_t* num_actions); + virtual class object* command_line(const char* command); + +private: + char* username; + +}; + +class user_selection : public object +{ +public: + user_selection() { } + virtual ~user_selection() { } + +public: + virtual enum object_type type() { return TYPE_DIRECTORY; } + virtual const char* title() { return "User Selection"; } + virtual class action** list_actions(size_t* num_actions); + virtual class object* command_line(const char* command); + +}; + +class object* login::factory() +{ + if ( struct passwd* user = getpwnam(username) ) + if ( !user->pw_passwd[0] ) + return log_user_in(user); + return NULL; +} + +class action** login::list_actions(size_t* num_actions) +{ + return *num_actions = 0, new class action*[0]; +} + +class object* login::command_line(const char* password) +{ + error_string = ""; + if ( struct passwd* user = getpwnam(username) ) + { + if ( !strcmp(user->pw_passwd, password) ) + return log_user_in(user); + else + return error_string = "Invalid password", (class object*) NULL; + } + return error_string = "No such user", (class object*) NULL; +} + +class action** user_selection::list_actions(size_t* num_actions) +{ + size_t num_users = 0; + FILE* fp = openpw(); + while ( fgetpwent(fp) ) + num_users++; + fseeko(fp, 0, SEEK_SET); + action** actions = new class action*[num_users + 1]; + size_t which_user = 0; + while ( struct passwd* user = fgetpwent(fp) ) + actions[which_user++] = + new action(user->pw_gecos ? user->pw_gecos : user->pw_name, + new login(user->pw_name)); + fclose(fp); + actions[num_users] = new action("Poweroff", new poweroff); + return *num_actions = num_users + 1, actions; +} + +class object* user_selection::command_line(const char* command) +{ + error_string = ""; + if ( getpwnam(command) ) + return new login(command); + return error_string = "No such user", (class object*) NULL; +} + +class FrameBufferInfo; +struct Desktop; +struct RenderInfo; + +struct Desktop +{ + struct timespec init_time; + struct timespec since_init; + struct timespec last_update; + struct timespec experienced_time; + class object* object; + action** actions; + size_t num_actions; + size_t selected; + float rotate_angle; + float color_angle; + float text_angle; + float size_factor; + bool control; + bool shift; + bool lshift; + bool rshift; + bool show_help; + bool angry; + char buffer_overflow_fix_me; + char command[2048]; + char warning_buffer[64]; + const char* warning; + bool critical_warning; + bool flashing_warning; + bool rune_warning; +}; + +struct RenderInfo +{ + int frame; + struct Desktop desktop; +}; + +class FrameBufferInfo +{ +public: + FrameBufferInfo(uint8_t* buffer, size_t pitch, int xres, int yres) : + buffer(buffer), pitch(pitch), xres(xres), yres(yres) { } + FrameBufferInfo(const FrameBufferInfo& o) : + buffer(o.buffer), pitch(o.pitch), xres(o.xres), yres(o.yres) { } + FrameBufferInfo(struct dispd_framebuffer* fb) + { + buffer = dispd_get_framebuffer_data(fb); + pitch = dispd_get_framebuffer_pitch(fb); + xres = dispd_get_framebuffer_width(fb); + yres = dispd_get_framebuffer_height(fb); + } + ~FrameBufferInfo() { } + +public: + uint8_t* buffer; + size_t pitch; + int xres; + int yres; + +public: + uint32_t* GetLinePixels(int ypos) const + { + return (uint32_t*) (buffer + ypos * pitch); + } + +}; + +unsigned char char_to_rune(unsigned char c) +{ + switch ( tolower(c) ) + { + case 'a': return 0x89; + case 'b': return 0x8c; + case 'c': return 0x85; + case 'd': return 0x8b; + case 'e': return 0x88; + case 'f': return 0x80; + case 'g': return 0x85; + case 'h': return 0x86; + case 'i': return 0x88; + case 'j': return 0x88; + case 'k': return 0x85; + case 'l': return 0x8e; + case 'm': return 0x8d; + case 'n': return 0x87; + case 'o': return 0x83; + case 'p': return 0x8c; + case 'q': return 0x85; + case 'r': return 0x84; + case 's': return 0x8a; + case 't': return 0x8b; + case 'u': return 0x81; + case 'v': return 0x81; + case 'w': return 0x81; + case 'x': return 0x85; + case 'y': return 0x81; + case 'z': return 0x8a; + default: return c; + } +} + +static void RenderChar(FrameBufferInfo fb, char c, uint32_t color, + int pos_x, int pos_y, int max_w, int max_h) +{ + unsigned char uc = c; + if ( use_runes ) + uc = char_to_rune(uc); + int xres = fb.xres; + int yres = fb.yres; + if ( xres <= pos_x || yres <= pos_y ) + return; + int avai_x = xres - pos_x; + int avai_y = yres - pos_y; + if ( avai_x < max_w ) + max_w = avai_x; + if ( avai_y < max_h ) + max_h = avai_y; + int width = FONT_WIDTH + 1 < max_w ? FONT_WIDTH + 1 : max_w; + int height = FONT_HEIGHT < max_h ? FONT_HEIGHT : max_h; + for ( int y = 0; y < height; y++ ) + { + int pixely = pos_y + y; + if ( pixely < 0 ) + continue; + uint32_t* pixels = fb.GetLinePixels(pixely); + uint8_t linebitmap = font[uc * FONT_CHARSIZE + y]; + for ( int x = 0; x < width; x++ ) + { + int pixelx = pos_x + x; + if ( pixelx < 0 ) + continue; + if ( x != FONT_WIDTH && linebitmap & 1U << (7-x) ) + pixels[pixelx] = BlendPixel(pixels[pixelx], color); + } + } +} + +static void RenderCharShadow(FrameBufferInfo fb, char c, uint32_t color, + int pos_x, int pos_y, int max_w, int max_h) +{ + RenderChar(fb, c, MakeColor(0, 0, 0, 64), pos_x-1, pos_y-1, max_w, max_h); + RenderChar(fb, c, MakeColor(0, 0, 0, 64), pos_x+1, pos_y-1, max_w, max_h); + RenderChar(fb, c, MakeColor(0, 0, 0, 64), pos_x-1, pos_y+1, max_w, max_h); + RenderChar(fb, c, MakeColor(0, 0, 0, 64), pos_x+1, pos_y+1, max_w, max_h); + RenderChar(fb, c, color, pos_x+0, pos_y+0, max_w, max_h); +} + +static void RenderText(FrameBufferInfo fb, const char* str, uint32_t color, + int pos_x, int pos_y, int max_w, int max_h) +{ + int char_width = FONT_WIDTH+1; + int char_height = FONT_HEIGHT; + while ( *str && 0 < max_w ) + { + RenderChar(fb, *str++, color, pos_x, pos_y, max_w, max_h); + pos_x += char_width; + max_w -= char_width; + (void) char_height; + } +} + +static void RenderTextShadow(FrameBufferInfo fb, const char* str, uint32_t color, + int pos_x, int pos_y, int max_w, int max_h) +{ + int char_width = FONT_WIDTH+1; + int char_height = FONT_HEIGHT; + while ( *str && 0 < max_w ) + { + RenderCharShadow(fb, *str++, color, pos_x, pos_y, max_w, max_h); + pos_x += char_width; + max_w -= char_width; + (void) char_height; + } +} + +typedef struct +{ + float src; + float dst; +} fspan_t; + +size_t ClampFloatToSize(float val, size_t max) +{ + if ( val < 0.0f ) + return 0; + if ( (float) max <= val ) + return max; + size_t ret = (size_t) val; + if ( max < ret ) + ret = max; + return ret; +} + +int compare_triangle_points_by_height(const void* a, const void* b) +{ + Vector av = *(const Vector*) a; + Vector bv = *(const Vector*) b; + if ( av.y < bv.y ) + return -1; + else if ( bv.y < av.y ) + return 1; + return 0; +} + +static void SortTrianglePointsByHeight(Vector points[3]) +{ + qsort(points, 3, sizeof(Vector), compare_triangle_points_by_height); +} + +static float SafeDivide(float a, float b) +{ + if ( b == 0.0f ) + return a < 0.0f ? -INFINITY : INFINITY; + return a / b; +} + +static size_t TriangleHorizontalSpans(fspan_t* spans, size_t numspans, float from, + Vector points[3]) +{ + assert(points[0].y <= points[1].y && points[1].y <= points[2].y); + Vector vec01 = points[1] - points[0]; + Vector vec02 = points[2] - points[0]; + Vector vec20 = points[0] - points[2]; + Vector vec21 = points[1] - points[2]; + Vector vec01hat(-vec01.y, vec01.x); + bool flipped = vec02.Dot(vec01hat) < 0.0f; + float dirx_01 = SafeDivide(vec01.x, vec01.y); + float dirx_02 = SafeDivide(vec02.x, vec02.y); + float dirx_20 = -SafeDivide(vec20.x, vec20.y); + float dirx_21 = -SafeDivide(vec21.x, vec21.y); + for ( size_t i = 0; i < numspans; i++ ) + { + float y = from + (float) i; + if ( y < points[0].y ) { spans[i].src = spans[i].dst = 0.0f; continue; } + if ( points[2].y < y ) { return i; /* Nothing beyond here. */ } + float xpos01 = points[0].x + dirx_01 * (y - points[0].y); + float xpos02 = points[0].x + dirx_02 * (y - points[0].y); + float xpos20 = points[2].x + dirx_20 * (points[2].y - y); + float xpos21 = points[2].x + dirx_21 * (points[2].y - y); + float xsrc; + float xdst; + if ( flipped ) + xsrc = xpos01 > xpos21 ? xpos01 : xpos21, + xdst = xpos20; + else + xsrc = xpos02, + xdst = xpos01 < xpos21 ? xpos01 : xpos21; + spans[i].src = xsrc; + spans[i].dst = xdst; + } + return numspans; +} + +static void RenderTriangle(uint8_t* fb, size_t pitch, size_t xres, + size_t yres, float x1, float y1, float x2, + float y2, float x3, float y3, uint32_t color) +{ + Vector points[3] = { {x1, y1}, {x2, y2}, {x3, y3}, }; + SortTrianglePointsByHeight(points); + const size_t NUM_SPANS = 256UL; + fspan_t spans[NUM_SPANS]; + float y0 = points[0].y; + while ( true ) + { + size_t numspans = TriangleHorizontalSpans(spans, NUM_SPANS, y0, points); + if ( !numspans ) + break; + for ( size_t i = 0; i < numspans; i++ ) + { + float ypixelf = y0 + i * 1.0f; + if ( ypixelf < 0.f || (float) yres <= ypixelf ) + continue; + size_t ypixel = ypixelf; + if ( yres <= ypixel ) + continue; + size_t xsrc = ClampFloatToSize(spans[i].src, xres); + size_t xdst = ClampFloatToSize(spans[i].dst, xres); + uint32_t* line = (uint32_t*) (fb + ypixel * pitch); + for ( size_t xpixel = xsrc; xpixel < xdst; xpixel++ ) + line[xpixel] = color; + } + y0 += numspans * 1.0f; + } +} + +const int SENARY_FONT_WIDTH = FONT_HEIGHT-1; +const int SENARY_FONT_HEIGHT = FONT_HEIGHT; + +static void RenderSenaryDigit(FrameBufferInfo fb, unsigned int digit, + uint32_t pri_color, uint32_t sec_color, int top, + int left) +{ + int width = SENARY_FONT_WIDTH-2; + int height = SENARY_FONT_HEIGHT-2; + int bottom = top + height; + int right = left + width; + int x1, y1, x2, y2, x3, y3; + if ( digit % 3 == 0 ) + { + x3 = (left + right) / 2; + y3 = top; + x2 = left; + y2 = bottom-1; + x1 = right; + y1 = bottom; + } + else if ( digit % 3 == 1 ) + { + x1 = left+1; + y1 = top; + x2 = right-1; + y2 = (top + bottom) / 2; + x3 = left+1; + y3 = bottom; + } + else if ( digit % 3 == 2 ) + { + x1 = right-1; + y1 = top; + x2 = left+1; + y2 = (top + bottom) / 2; + x3 = right-1; + y3 = bottom; + } + uint32_t color = digit < 3 ? pri_color : sec_color; + RenderTriangle(fb.buffer, fb.pitch, fb.xres, fb.yres, x1, y1, x2, y2, x3, y3, color); +} + +static void RenderBackground(FrameBufferInfo fb, struct Desktop* desktop) +{ + bool was_runes = use_runes; + use_runes = use_runes || desktop->object->is_core(); + if ( desktop->object->is_unstable_core() ) + use_runes = arc4random_uniform(50) < 40; + + uint32_t background_color = desktop->object->is_core() ? MakeColor(0, 0, 0) : + MakeColor(0x89 * 2/3, 0xc7 * 2/3, 0xff * 2/3); + if ( desktop->since_init.tv_sec < 3 ) + { + float factor = timespec_to_float(desktop->since_init) / 3; + background_color = MakeColor(0x89 * 2/3 * factor, 0xc7 * 2/3 * factor, 0xff * 2/3 * factor); + } + + for ( int y = 0; y < fb.yres; y++ ) + { + uint32_t* pixels = fb.GetLinePixels(y); + for ( int x = 0; x < fb.xres; x++ ) + pixels[x] = background_color; + } + + if ( desktop->since_init.tv_sec < 3 ) + { + + const char* logo = "trianglix"; + size_t logo_length = strlen(logo); + int logo_width = (int) logo_length * (FONT_WIDTH+1); + int logo_height = FONT_HEIGHT; + int logo_pos_x = (fb.xres - logo_width) / 2; + int logo_pos_y = (fb.yres - logo_height) / 2; + uint32_t logo_color = MakeColor(255, 255, 255); + RenderTextShadow(fb, logo, logo_color, logo_pos_x, logo_pos_y, INT_MAX, INT_MAX); + use_runes = was_runes; + return; + } + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + + RenderSenaryDigit(fb, now.tv_sec/(6*6*6*6*6) % 6, MakeColor(255, 255, 255), MakeColor(255, 100, 100), 15, fb.xres-SENARY_FONT_WIDTH*(6+1)); + RenderSenaryDigit(fb, now.tv_sec/(6*6*6*6) % 6, MakeColor(255, 255, 255), MakeColor(255, 100, 100), 15, fb.xres-SENARY_FONT_WIDTH*(5+1)); + RenderSenaryDigit(fb, now.tv_sec/(6*6*6) % 6, MakeColor(255, 255, 255), MakeColor(255, 100, 100), 15, fb.xres-SENARY_FONT_WIDTH*(4+1)); + RenderSenaryDigit(fb, now.tv_sec/(6*6) % 6, MakeColor(255, 255, 255), MakeColor(255, 100, 100), 15, fb.xres-SENARY_FONT_WIDTH*(3+1)); + RenderSenaryDigit(fb, now.tv_sec/6 % 6, MakeColor(255, 255, 255), MakeColor(255, 100, 100), 15, fb.xres-SENARY_FONT_WIDTH*(2+1)); + RenderSenaryDigit(fb, now.tv_sec/1 % 6, MakeColor(255, 255, 255), MakeColor(255, 100, 100), 15, fb.xres-SENARY_FONT_WIDTH*(1+1)); + + struct timespec xtime = desktop->experienced_time; + + size_t index = (now.tv_sec / 10) % 5; + const char* slogans[] = + { + "trianglix", + "you can do anything with trianglix", + "welcome to trianglix", + "the only limit is yourself", + "the unattainable is unknown with trianglix", + }; + + const char* warnings[] = + { + "the triangle has been angered", + "the triangle does not forgive", + "the triangle never forgets", + "the triangle pays no attention to the empty mind", + "the triangle knows your limits", + "the triangle hates being anthropomorphized", + }; + + bool is_angry = desktop->angry || desktop->object->is_unstable_core(); + const char* slogan = (is_angry ? warnings : slogans)[index]; + size_t slogan_length = strlen(slogan); + int slogan_width = (int) slogan_length * (FONT_WIDTH+1); + int slogan_pos_x = (fb.xres - slogan_width) / 2; + int slogan_pos_y = 15; + uint32_t slogan_color = desktop->angry ? + MakeColor(255, 0, 0) : + MakeColor(255, 255, 255); + RenderText(fb, slogan, slogan_color, slogan_pos_x, slogan_pos_y, INT_MAX, INT_MAX); + + bool show_warning = !desktop->flashing_warning || xtime.tv_nsec < 500000000; + if ( desktop->warning && show_warning && !desktop->show_help ) + { + const char* warning = desktop->warning; + size_t warning_length = strlen(warning); + int warning_width = (int) warning_length * (FONT_WIDTH+1); + int warning_pos_x = (fb.xres - warning_width) / 2; + int warning_pos_y = 15 + FONT_HEIGHT * 2; + uint32_t warning_color = desktop->critical_warning ? + MakeColor(255, 0, 0) : + MakeColor(255, 255, 255); + bool used_runes = use_runes; + use_runes = use_runes || desktop->rune_warning; + RenderText(fb, warning, warning_color, warning_pos_x, warning_pos_y, INT_MAX, INT_MAX); + use_runes = used_runes; + } + + const char* title = desktop->object->title(); + if ( desktop->show_help ) + title = "Help"; + size_t title_length = strlen(title); + int title_width = (int) title_length * (FONT_WIDTH+1); + int title_pos_x = (fb.xres - title_width) / 2; + int title_pos_y = fb.yres - 15 - FONT_HEIGHT; + uint32_t title_color = MakeColor(255, 255, 255); + RenderText(fb, title, title_color, title_pos_x, title_pos_y, INT_MAX, INT_MAX); + + int alpha = (uint8_t) ((sin(desktop->color_angle) + 1.0f) * 128.f); + if ( alpha < 0 ) + alpha = 0; + if ( 255 < alpha ) + alpha = 255; + + uint32_t pri_color = desktop->object->is_core() && !desktop->object->is_unstable_core() ? + MakeColor(255, 255, 255, alpha) : + is_angry ? + MakeColor(114, 21, 36, alpha) : + MakeColor(255, 222, 0, alpha); + uint32_t sec_color = desktop->object->is_core() && !desktop->object->is_unstable_core() ? + MakeColor(255, 255, 255, 255) : + is_angry ? + MakeColor(255, 0, 0, 255) : + MakeColor(114, 255, 36, 255); + uint32_t triangle_color = BlendPixel(sec_color, pri_color); + + float size = fb.xres / 10.0 * desktop->size_factor; + + float center_x = fb.xres / 2.0; + float center_y = fb.yres / 2.0; + + float x1 = center_x + size * cos(desktop->rotate_angle + 2.0*M_PI * 0.0 / 3.0); + float y1 = center_y + size * sin(desktop->rotate_angle + 2.0*M_PI * 0.0 / 3.0); + float x2 = center_x + size * cos(desktop->rotate_angle + 2.0*M_PI * 1.0 / 3.0); + float y2 = center_y + size * sin(desktop->rotate_angle + 2.0*M_PI * 1.0 / 3.0); + float x3 = center_x + size * cos(desktop->rotate_angle + 2.0*M_PI * 2.0 / 3.0); + float y3 = center_y + size * sin(desktop->rotate_angle + 2.0*M_PI * 2.0 / 3.0); + + RenderTriangle(fb.buffer, fb.pitch, fb.xres, fb.yres, x1, y1, x2, y2, x3, y3, triangle_color); + + if ( desktop->show_help ) + { + const char* help_messages[] = + { + "Trianglix Basic Usage", + "", + "F1 - Basic Usage", + "Escape - Return to parent object", + "Arrow Keys - Select action in direction", + "Enter (empty prompt) - Invoke action", + "Enter (non-empty prompt) - Execute command", + "Tab - Next object", + "Shift-Tab - Previous object", + "Control-D - Exit shell", + "Control-Q - Exit editor", + " - Append character to command prompt", + NULL, + }; + + for ( size_t i = 0; help_messages[i]; i++ ) + { + const char* help = help_messages[i]; + int help_length = strlen(help); + int help_width = (int) help_length * (FONT_WIDTH+1); + int help_height = FONT_HEIGHT; + int help_pos_x = (fb.xres - help_width) / 2; + int help_pos_y = 15 + (3 + i) * (help_height); + RenderTextShadow(fb, help, MakeColor(255, 255, 255), help_pos_x, help_pos_y, INT_MAX, INT_MAX); + } + + use_runes = was_runes; + return; + } + + float normal_dist = size * 2.0; + + uint32_t unselected_color = MakeColor(255, 255, 255); + uint32_t selected_color = MakeColor(255, 222, 0); + for ( size_t i = 0; i < desktop->num_actions; i++ ) + { + float dist = normal_dist; + if ( desktop->object->is_unstable_core() ) + dist *= 0.5 + (sin(desktop->text_angle / 50.0 * i) + 1.0) / 2.0; + const char* file_name = desktop->actions[i]->get_name(); + char* text = new char[1+strlen(file_name)+1+1]; + stpcpy(stpcpy(stpcpy(text, "|"), file_name), ">"); + float text_x = center_x + dist * cos(desktop->text_angle + 2.0*M_PI * (float) i / (float) desktop->num_actions); + float text_y = center_y + dist * sin(desktop->text_angle + 2.0*M_PI * (float) i / (float) desktop->num_actions); + int text_width = (int) strlen(text) * (FONT_WIDTH+1); + int text_height = FONT_HEIGHT; + float x = text_x - text_width / 2.0f; + float y = text_y - text_height / 2.0f; + RenderTextShadow(fb, text, i == desktop->selected ? selected_color : unselected_color, x, y, text_width, text_height); + delete[] text; + } + + bool is_password_prompt = desktop->object->is_password_prompt(); + char* password_command = NULL; + const char* command = desktop->command; + if ( is_password_prompt ) + { + password_command = strdup(command); + for ( size_t i = 0; command[i]; i++ ) + password_command[i] = '*'; + command = password_command; + } + int command_length = strlen(command); + int command_width = (int) command_length * (FONT_WIDTH+1); + int command_height = FONT_HEIGHT; + int command_pos_x = (fb.xres - command_width) / 2; + int command_pos_y = (fb.yres - command_height) / 2; + RenderTextShadow(fb, command, MakeColor(255, 255, 255), command_pos_x, command_pos_y, INT_MAX, INT_MAX); + if ( is_password_prompt ) + free(password_command); + + const char* prompt = desktop->object->prompt(); + int prompt_length = strlen(prompt); + int prompt_width = (int) prompt_length * (FONT_WIDTH+1); + int prompt_height = FONT_HEIGHT; + int prompt_pos_x = (fb.xres - prompt_width) / 2; + int prompt_pos_y = (fb.yres - prompt_height) / 2 - FONT_HEIGHT - 2; + RenderTextShadow(fb, prompt, MakeColor(255, 255, 255), prompt_pos_x, prompt_pos_y, INT_MAX, INT_MAX); + + const char* error = desktop->object->error_message(); + int error_length = strlen(error); + int error_width = (int) error_length * (FONT_WIDTH+1); + int error_height = FONT_HEIGHT; + int error_pos_x = (fb.xres - error_width) / 2; + int error_pos_y = (fb.yres - error_height) / 2 + FONT_HEIGHT - 2; + RenderText(fb, error, MakeColor(255, 0, 0), error_pos_x, error_pos_y, INT_MAX, INT_MAX); + + use_runes = was_runes; +} + +static void Render(FrameBufferInfo fb, struct Desktop* desktop) +{ + RenderBackground(fb, desktop); +} + +static bool Render(struct dispd_window* window, struct RenderInfo* info) +{ + struct dispd_framebuffer* fb = dispd_begin_render(window); + if ( !fb ) + return false; + FrameBufferInfo fbinfo(fb); + Render(fbinfo, &info->desktop); + dispd_finish_render(fb); + return true; +} + +void ClearActions(struct Desktop* desktop, class object* no_delete = NULL) +{ + for ( size_t i = 0; i < desktop->num_actions; i++ ) + { + class object* object = desktop->actions[i]->get_object(); + if ( object != no_delete && + object == desktop->object && + object == desktop->object->parent_object ) + delete object; + delete desktop->actions[i]; + } + delete[] desktop->actions; + desktop->actions = NULL; + desktop->num_actions = 0; + desktop->selected = 0; +} + +static void UpdateActionList(struct Desktop* desktop) +{ + size_t old_selected = desktop->selected; + ClearActions(desktop); + desktop->actions = desktop->object->list_actions(&desktop->num_actions); + if ( desktop->num_actions ) + desktop->selected = old_selected % desktop->num_actions; + else + desktop->selected = 0; +} + +void PopObject(struct Desktop* desktop) +{ + if ( desktop->object->parent_object ) + { + ClearActions(desktop); + class object* poped_object = desktop->object; + desktop->object = desktop->object->parent_object; + delete poped_object; + } + UpdateActionList(desktop); +} + +void PushObject(struct Desktop* desktop, class object* object) +{ + desktop->object->clear_error(); + + while ( class object* product = object->factory() ) + { + if ( object != desktop->object && + object != desktop->object->parent_object && + object != product ) + delete object; + object = product; + } + + if ( object == desktop->object ) + UpdateActionList(desktop); + else if ( object == desktop->object->parent_object ) + PopObject(desktop); + else + { + ClearActions(desktop, object); + object->parent_object = desktop->object; + desktop->object = object; + UpdateActionList(desktop); + } +} + +static void HandleKeystroke(int kbkey, struct Desktop* desktop) +{ + int abskbkey = kbkey < 0 ? -kbkey : kbkey; + + switch ( abskbkey ) + { + case KBKEY_LCTRL: desktop->control = kbkey > 0; break; + case KBKEY_LSHIFT: desktop->lshift = kbkey > 0; break; + case KBKEY_RSHIFT: desktop->rshift = kbkey > 0; break; + } + desktop->shift = desktop->lshift || desktop->rshift; + + if ( desktop->show_help ) + { + if ( kbkey == KBKEY_F1 || kbkey == KBKEY_ESC ) + desktop->show_help = false; + return; + } + + if ( kbkey == KBKEY_F1 ) + { + desktop->show_help = true; + return; + } + + if ( kbkey == KBKEY_ESC ) + PopObject(desktop); + + size_t& num_actions = desktop->num_actions; + if ( !num_actions ) + return; + + float selection_x = cos(desktop->text_angle + 2.0*M_PI * (float) desktop->selected / (float) num_actions); + float selection_y = sin(desktop->text_angle + 2.0*M_PI * (float) desktop->selected / (float) num_actions); + + if ( kbkey == KBKEY_RIGHT ) + { + float best_diff = 2.0f; + size_t best = num_actions; + for ( size_t i = 0; i < num_actions; i++ ) + { + float x = cos(desktop->text_angle + 2.0*M_PI * (float) i / (float) num_actions); + float y = sin(desktop->text_angle + 2.0*M_PI * (float) i / (float) num_actions); + if ( x <= selection_x ) + continue; + float diff = fabsf(y - selection_y); + if ( best_diff < diff ) + continue; + best_diff = diff; + best = i; + } + if ( best != num_actions ) + desktop->selected = best; + } + + if ( kbkey == KBKEY_LEFT ) + { + float best_diff = 2.0f; + size_t best = num_actions; + for ( size_t i = 0; i < num_actions; i++ ) + { + float x = cos(desktop->text_angle + 2.0*M_PI * (float) i / (float) num_actions); + float y = sin(desktop->text_angle + 2.0*M_PI * (float) i / (float) num_actions); + if ( selection_x <= x ) + continue; + float diff = fabsf(y - selection_y); + if ( best_diff < diff ) + continue; + best_diff = diff; + best = i; + } + if ( best != num_actions ) + desktop->selected = best; + } + + if ( kbkey == KBKEY_DOWN ) + { + float best_diff = 2.0f; + size_t best = num_actions; + for ( size_t i = 0; i < num_actions; i++ ) + { + float x = cos(desktop->text_angle + 2.0*M_PI * (float) i / (float) num_actions); + float y = sin(desktop->text_angle + 2.0*M_PI * (float) i / (float) num_actions); + if ( y <= selection_y ) + continue; + float diff = fabsf(x - selection_x); + if ( best_diff < diff ) + continue; + best_diff = diff; + best = i; + } + if ( best != num_actions ) + desktop->selected = best; + } + + if ( kbkey == KBKEY_UP ) + { + float best_diff = 2.0f; + size_t best = num_actions; + for ( size_t i = 0; i < num_actions; i++ ) + { + float x = cos(desktop->text_angle + 2.0*M_PI * (float) i / (float) num_actions); + float y = sin(desktop->text_angle + 2.0*M_PI * (float) i / (float) num_actions); + if ( selection_y <= y ) + continue; + float diff = fabsf(x - selection_x); + if ( best_diff < diff ) + continue; + best_diff = diff; + best = i; + } + if ( best != num_actions ) + desktop->selected = best; + } + + if ( num_actions && !desktop->shift && kbkey == KBKEY_TAB ) + desktop->selected = (desktop->selected + num_actions - 1) % num_actions; + + if ( num_actions && desktop->shift && kbkey == KBKEY_TAB ) + desktop->selected = (desktop->selected + num_actions + 1) % num_actions; + + if ( kbkey == KBKEY_ENTER && !desktop->command[0] ) + { + class action* action = desktop->actions[desktop->selected]; + class object* object = action->get_object(); + if ( object->type() == TYPE_DIRECTORY ) + PushObject(desktop, object); + else if ( object->type() == TYPE_FILE ) + { + object->clear_error(); + object->invoke(); + UpdateActionList(desktop); + } + } +} + +void HandleCodepoint(uint32_t codepoint, struct Desktop* desktop) +{ + if ( 128 <= codepoint ) + return; + char c = (char) codepoint; + if ( c == '\t' ) + return; + if ( desktop->show_help ) + { + if ( c == '\n' ) + desktop->show_help = false; + return; + } + size_t column = 0; + while ( desktop->command[column] ) + column++; + if ( c == '\b' ) + desktop->command[column-1] = '\0'; + else if ( c == '\n' ) + { + if ( !desktop->command[0] && desktop->num_actions != 0 ) + return; + if ( class object* new_object = desktop->object->command_line(desktop->command) ) + PushObject(desktop, new_object); + memset(&desktop->command, 0, sizeof(desktop->command)); + return; + } + else + desktop->command[column] = c; +} + +static void InitializeDesktop(struct Desktop* desktop) +{ + memset(desktop, 0, sizeof(*desktop)); + clock_gettime(CLOCK_MONOTONIC, &desktop->init_time); + desktop->last_update = desktop->init_time; + desktop->angry = false; + desktop->rotate_angle = M_PI_2; + desktop->selected = 0; + desktop->color_angle = 0.f; + desktop->text_angle = 0.f; + desktop->size_factor = 1.f; + desktop->control = false; + desktop->lshift = false; + desktop->rshift = false; + desktop->actions = NULL; + desktop->num_actions = 0; + desktop->object = new user_selection(); + UpdateActionList(desktop); +} + +static void HandleKeyboardEvents(int kbfd, struct Desktop* desktop) +{ + if ( desktop->since_init.tv_sec < 3 ) + return; + + const size_t BUFFER_LENGTH = 64; + uint32_t input[BUFFER_LENGTH]; + while ( true ) + { + settermmode(kbfd, TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_NONBLOCK); + ssize_t num_bytes = read(kbfd, input, sizeof(input)); + if ( num_bytes < 0 ) + return; + size_t num_kbkeys = num_bytes / sizeof(uint32_t); + for ( size_t i = 0; i < num_kbkeys; i++ ) + { + int kbkey = KBKEY_DECODE(input[i]); + if ( kbkey ) + HandleKeystroke(kbkey, desktop); + else + HandleCodepoint(input[i], desktop); + } + } +} + +static void HandleEvents(int kbfd, struct Desktop* desktop) +{ + const nfds_t NFDS = 1; + struct pollfd fds[NFDS]; + fds[0].fd = kbfd; + fds[0].events = POLLIN; + fds[0].revents = 0; + if ( 0 < poll(fds, NFDS, -1) ) + { + if ( fds[0].revents ) + HandleKeyboardEvents(kbfd, desktop); + } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct timespec delta_ts = timespec_sub(now, desktop->last_update); + desktop->last_update = now; + float delta = delta_ts.tv_sec * 1E0f + delta_ts.tv_nsec * 1E-9f; + + time_t xtime_sec = desktop->experienced_time.tv_sec; + if ( !desktop->show_help && delta_ts.tv_sec == 0 ) + desktop->experienced_time = timespec_add(desktop->experienced_time, delta_ts); + + desktop->warning = NULL; + desktop->critical_warning = false; + desktop->flashing_warning = false; + desktop->rune_warning = false; + desktop->angry = false; + if ( desktop->object->is_unstable_core() ) + { + char* stuff = gibberish(); + snprintf(desktop->warning_buffer, sizeof(desktop->warning_buffer), + "!! TRINIT CORE UNSTABLE: %s !!", stuff); + desktop->warning = desktop->warning_buffer; + desktop->critical_warning = true; + desktop->flashing_warning = true; + free(stuff); + } + else if ( desktop->object->is_core() ) + { + desktop->warning = "TRINIT CORE"; + desktop->critical_warning = true; + desktop->flashing_warning = true; + } + else if ( !configured_use_runes ) + { + if ( 0 <= xtime_sec && xtime_sec < 15 ) + desktop->warning = "Press F1 for basic usage help"; + if ( 20 <= xtime_sec && xtime_sec < 30 ) + desktop->warning = "Runes has not been configured"; + if ( 45 <= xtime_sec && xtime_sec < 60 ) + desktop->warning = "Note: Runes has not been configured"; + if ( 80 <= xtime_sec && xtime_sec < 100 ) + desktop->warning = "Warning: Runes has not been configured", + desktop->critical_warning = true; + if ( 110 <= xtime_sec && xtime_sec < 130 ) + { + snprintf(desktop->warning_buffer, sizeof(desktop->warning_buffer), + "WARNING: UNDEPLOYED RUNES WILL BE DEPLOYED IN: %u", + 130 - (unsigned int) xtime_sec); + desktop->warning = desktop->warning_buffer; + desktop->critical_warning = true; + desktop->flashing_warning = true; + desktop->rune_warning = true; + desktop->angry = true; + } + if ( 130 <= xtime_sec ) + { + use_runes = true; + configured_use_runes = true; + unlink("/etc/rune-disable"); + close(::open("/etc/rune-enable", O_WRONLY | O_CREAT, 0666)); + } + } + + desktop->since_init = timespec_sub(now, desktop->init_time); + bool init_boosted = timespec_lt(desktop->since_init, timespec_make(5, 0)); + float since_init = desktop->since_init.tv_sec * 1E0f + desktop->since_init.tv_nsec * 1E-9f; + + float rotate_speed = 2*M_PI / (desktop->angry ? 2 : 60); + float color_speed = 2*M_PI / (desktop->angry ? 1 : 20); + float text_speed = 2*M_PI / (desktop->angry ? 10 : 20); + + if ( desktop->object->is_core() ) + { + rotate_speed *= 10; + text_speed *= 5; + } + + if ( desktop->object->is_unstable_core() ) + { + rotate_speed *= 1.5; + text_speed *= -1.0; + color_speed *= 20; + } + + if ( init_boosted ) + { + rotate_speed *= 1.f + 100.0 * cosf(M_PI_2 * (since_init - 3.0) / 2.0); + desktop->size_factor = sinf(M_PI_2 * (since_init - 3.0) / 2.0); + } + else + desktop->size_factor = 1.0f; + + desktop->rotate_angle += rotate_speed * delta; + desktop->color_angle += color_speed * delta; + desktop->text_angle += text_speed * delta; +} + +static int MainLoop(int argc, char* argv[], int kbfd, struct dispd_window* window) +{ + (void) argc; + (void) argv; + struct RenderInfo info; + InitializeDesktop(&info.desktop); + for ( info.frame = 0; true; info.frame++ ) + { + HandleEvents(kbfd, &info.desktop); + if ( !Render(window, &info) ) + return 1; + } + return 0; +} + +static int CreateKeyboardConnection() +{ + int fd = open("/dev/tty", O_RDONLY | O_CLOEXEC); + if ( fd < 0 ) + return -1; + if ( settermmode(fd, TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_NONBLOCK) ) + return close(fd), -1; + return fd; +} + +int main(int argc, char* argv[]) +{ + setpgid(0, 0); + tcsetpgrp(0, getpgid(0)); + + struct stat st; + if ( stat("/etc/rune-enable", &st) == 0 ) + use_runes = true, configured_use_runes = true; + else if ( stat("/etc/rune-disable", &st) == 0 ) + use_runes = false, configured_use_runes = true; + + int fontfd = open("/dev/vgafont", O_RDONLY); + readall(fontfd, font, sizeof(font)); + close(fontfd); + uint8_t rune_font[26][16] = + { + { 0x00, 0x4a, 0x4a, 0x52, 0x64, 0x48, 0x70, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, + { 0x00, 0x40, 0x40, 0x60, 0x60, 0x50, 0x50, 0x50, 0x48, 0x48, 0x48, 0x44, 0x44, 0x42, 0x42, 0x00 }, + { 0x00, 0x40, 0x40, 0x40, 0x60, 0x50, 0x48, 0x44, 0x44, 0x48, 0x50, 0x60, 0x40, 0x40, 0x40, 0x00 }, + { 0x00, 0x40, 0x60, 0x50, 0x48, 0x44, 0x60, 0x50, 0x48, 0x44, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, + { 0x00, 0x60, 0x50, 0x48, 0x44, 0x48, 0x50, 0x60, 0x60, 0x50, 0x48, 0x44, 0x40, 0x40, 0x40, 0x00 }, + { 0x00, 0x42, 0x44, 0x48, 0x50, 0x60, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, + { 0x00, 0x10, 0x10, 0x10, 0x92, 0x54, 0x38, 0x10, 0x38, 0x54, 0x92, 0x10, 0x10, 0x10, 0x10, 0x00 }, + { 0x00, 0x10, 0x10, 0x10, 0x90, 0x50, 0x30, 0x10, 0x18, 0x14, 0x12, 0x10, 0x10, 0x10, 0x10, 0x00 }, + { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, + { 0x00, 0x10, 0x10, 0x10, 0x10, 0x12, 0x14, 0x18, 0x10, 0x30, 0x50, 0x90, 0x10, 0x10, 0x10, 0x00 }, + { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x44, 0x4c, 0x54, 0x64, 0x44, 0x04, 0x04, 0x04, 0x04, 0x00 }, + { 0x00, 0x10, 0x38, 0x54, 0x92, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, + { 0x00, 0x60, 0x50, 0x48, 0x44, 0x48, 0x50, 0x60, 0x60, 0x50, 0x48, 0x44, 0x48, 0x50, 0x60, 0x00 }, + { 0x00, 0x92, 0x92, 0x54, 0x38, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00 }, + { 0x00, 0x40, 0x60, 0x50, 0x48, 0x44, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, + { 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x54, 0x92, 0x92, 0x00 }, + }; + + memcpy(font + 128 * 16, rune_font, sizeof(rune_font)); + + if ( !dispd_initialize(&argc, &argv) ) + error(1, 0, "couldn't initialize dispd library"); + struct dispd_session* session = dispd_attach_default_session(); + if ( !session ) + error(1, 0, "couldn't attach to dispd default session"); + if ( !dispd_session_setup_game_rgba(session) ) + error(1, 0, "couldn't setup dispd rgba session"); + struct dispd_window* window = dispd_create_window_game_rgba(session); + if ( !window ) + error(1, 0, "couldn't create dispd rgba window"); + + int kbfd = CreateKeyboardConnection(); + if ( kbfd < 0 ) + error(1, 0, "couldn't create keyboard connection"); + + int ret = MainLoop(argc, argv, kbfd, window); + + close(kbfd); + + dispd_destroy_window(window); + dispd_detach_session(session); + + return ret; +} diff --git a/trianglix/vector.h b/trianglix/vector.h new file mode 100644 index 00000000..c8177fd8 --- /dev/null +++ b/trianglix/vector.h @@ -0,0 +1,132 @@ +#ifndef VECTOR_H +#define VECTOR_H + +class Vector +{ +public: + float x; + float y; + float z; + +public: + Vector(float x = 0.f, float y = 0.f, float z = 0.f) : x(x), y(y), z(z) { } + + Vector& operator=(const Vector& rhs) + { + if ( this != &rhs ) { x = rhs.x; y = rhs.y; z = rhs.z; } + return *this; + } + + Vector& operator+=(const Vector& rhs) + { + x += rhs.x; + y += rhs.y; + z += rhs.z; + return *this; + } + + Vector& operator-=(const Vector& rhs) + { + x -= rhs.x; + y -= rhs.y; + z -= rhs.z; + return *this; + } + + Vector& operator*=(float scalar) + { + x *= scalar; + y *= scalar; + z *= scalar; + return *this; + } + + Vector& operator/=(float scalar) + { + x /= scalar; + y /= scalar; + z /= scalar; + return *this; + } + + const Vector operator+(const Vector& other) const + { + Vector ret(*this); ret += other; return ret; + } + + const Vector operator-(const Vector& other) const + { + Vector ret(*this); ret -= other; return ret; + } + + const Vector operator*(float scalar) const + { + Vector ret(*this); ret *= scalar; return ret; + } + + const Vector operator/(float scalar) const + { + Vector ret(*this); ret /= scalar; return ret; + } + + bool operator==(const Vector& other) const + { + return x == other.x && y == other.y && z == other.z; + } + + bool operator!=(const Vector& other) const + { + return !(*this == other); + } + + float Dot(const Vector& other) const + { + return x * other.x + y * other.y + z * other.z; + } + + const Vector Cross(const Vector& other) const + { + Vector ret(y * other.z - z * other.y, + z * other.x - x * other.z, + x * other.y - y * other.x); + return ret; + } + + float SquaredSize() const + { + return x*x + y*y + z*z; + } + + float Size() const + { + return sqrtf(SquaredSize()); + } + + float DistanceTo(const Vector& other) const + { + return (other - *this).Size(); + } + + const Vector Normalize() const + { + float size = Size(); + if ( size == 0.0 ) { size = 1.0f; } + return *this / size; + } + + const Vector Rotate2D(float radians) const + { + float sinr = sinf(radians); + float cosr = cosf(radians); + float newx = x * cosr - y * sinr; + float newy = x * sinr + y * cosr; + return Vector(newx, newy); + } + + const Vector Rotate2DAround(float radians, const Vector& off) const + { + return Vector(*this - off).Rotate2D(radians) + off; + } +}; + +#endif