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