mirror of
				https://gitlab.com/sortix/sortix.git
				synced 2023-02-13 20:55:38 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			1973 lines
		
	
	
	
		
			53 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1973 lines
		
	
	
	
		
			53 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*******************************************************************************
 | 
						|
 | 
						|
    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 <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
    trianglix.cpp
 | 
						|
    The Trianglix Desktop Environment.
 | 
						|
 | 
						|
*******************************************************************************/
 | 
						|
 | 
						|
#include <sys/display.h>
 | 
						|
#include <sys/keycodes.h>
 | 
						|
#include <sys/termmode.h>
 | 
						|
#include <sys/wait.h>
 | 
						|
 | 
						|
#include <assert.h>
 | 
						|
#include <ctype.h>
 | 
						|
#include <dirent.h>
 | 
						|
#include <dispd.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <error.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <ioleast.h>
 | 
						|
#include <libgen.h>
 | 
						|
#include <limits.h>
 | 
						|
#include <math.h>
 | 
						|
#include <poll.h>
 | 
						|
#include <pwd.h>
 | 
						|
#include <stddef.h>
 | 
						|
#include <stdint.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <time.h>
 | 
						|
#include <timespec.h>
 | 
						|
#include <unistd.h>
 | 
						|
 | 
						|
#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];
 | 
						|
 | 
						|
__attribute__((format(printf, 1, 2)))
 | 
						|
char* print_string(const char* format, ...)
 | 
						|
{
 | 
						|
	char* ret;
 | 
						|
	va_list ap;
 | 
						|
	va_start(ap, format);
 | 
						|
	if ( vasprintf(&ret, format, ap) < 0 )
 | 
						|
		ret = NULL;
 | 
						|
	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;
 | 
						|
	while ( char* component = strsep(&input, ":") )
 | 
						|
	{
 | 
						|
		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",
 | 
						|
			"<character> - 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, 0) )
 | 
						|
	{
 | 
						|
		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;
 | 
						|
}
 |