diff --git a/Makefile b/Makefile index 38ae4c1a..dc0ec390 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,9 @@ include build-aux/version.mak MODULES=\ libc \ libm \ -dispd \ +libdisplay \ libmount \ +libui \ bench \ carray \ checksum \ @@ -15,6 +16,7 @@ chkblayout \ chvideomode \ dhclient \ disked \ +display \ dnsconfig \ editor \ ext \ @@ -32,6 +34,7 @@ rw \ sf \ sh \ sysinstall \ +terminal \ tix \ trianglix \ update-initrd \ diff --git a/build-aux/iso-grub-cfg.sh b/build-aux/iso-grub-cfg.sh index 9d5becb6..66637e3e 100755 --- a/build-aux/iso-grub-cfg.sh +++ b/build-aux/iso-grub-cfg.sh @@ -198,6 +198,7 @@ fi set enable_src=true set enable_network_drivers= set enable_dhclient=true +set enable_gui=true set enable_ntpd=false set enable_sshd=false @@ -214,6 +215,7 @@ export no_random_seed export enable_src export enable_network_drivers export enable_dhclient +export enable_gui export enable_ntpd export enable_sshd EOF @@ -402,10 +404,11 @@ menuentry() { echo printf "menuentry \"Sortix (%s)\" {\n" "$1" if [ -n "$2" ]; then - printf " load_sortix %s\n" "$2" - #printf " load_sortix '" - #printf '%s' "$2" | sed "s,','\\'',g" - #printf "'\n" + printf " if \$enable_gui; then\n" + printf " load_sortix %s-gui\n" "$2" + printf " else\n" + printf " load_sortix %s\n" "$2" + printf " fi\n" else printf " load_sortix\n" fi @@ -418,7 +421,7 @@ menu_title="\$base_menu_title" hook_menu_pre EOF -menuentry "\$title_single_user" '-- /sbin/init' +menuentry "\$title_single_user" '-- /sbin/init --target=single-user' menuentry "\$title_sysinstall" '-- /sbin/init --target=sysinstall' menuentry "\$title_sysupgrade" '-- /sbin/init --target=sysupgrade' @@ -446,6 +449,18 @@ menu_title="\$base_menu_title - Advanced Options" hook_advanced_menu_pre +if "\$enable_gui"; then + menuentry "Disable GUI" { + enable_gui=false + configfile /boot/grub/advanced.cfg + } +else + menuentry "Enable GUI" { + enable_gui=true + configfile /boot/grub/advanced.cfg + } +fi + if "\$enable_src"; then menuentry "Disable loading source code" { enable_src=false diff --git a/build-aux/smoketest.sh b/build-aux/smoketest.sh index c9887cff..e58aad87 100755 --- a/build-aux/smoketest.sh +++ b/build-aux/smoketest.sh @@ -151,6 +151,7 @@ EOF tix-iso-bootconfig \ --random-seed \ --timeout=0 \ + --disable-gui \ --liveconfig=liveconfig \ bootconfig mkdir -p bootconfig/boot/grub diff --git a/chkblayout/Makefile b/chkblayout/Makefile index 3e8e780a..f45ae07e 100644 --- a/chkblayout/Makefile +++ b/chkblayout/Makefile @@ -11,7 +11,7 @@ CFLAGS += -Wall -Wextra BINARIES = chkblayout MANPAGES1 = chkblayout.1 -LIBS = +LIBS = -ldisplay all: $(BINARIES) diff --git a/chkblayout/chkblayout.c b/chkblayout/chkblayout.c index 1bbf900b..8493406e 100644 --- a/chkblayout/chkblayout.c +++ b/chkblayout/chkblayout.c @@ -20,6 +20,7 @@ #include +#include #include #include #include @@ -34,6 +35,20 @@ #include #include +#define CHKBLAYOUT_ID 0 + +static bool chkblayout_ack_received = false; +static int chkblayout_error; + +static void on_ack(void* ctx, uint32_t id, int32_t error) +{ + (void) ctx; + if ( id != CHKBLAYOUT_ID ) + return; + chkblayout_error = error; + chkblayout_ack_received = true; +} + int main(int argc, char* argv[]) { bool list = false; @@ -103,8 +118,29 @@ int main(int argc, char* argv[]) err(1, "read: %s", kblayout_path); close(kblayout_fd); - if ( tcsetblob(tty_fd, "kblayout", kblayout, kblayout_size) < 0 ) - err(1, "tcsetblob: kblayout: %s:", kblayout_path); + if ( getenv("DISPLAY_SOCKET") ) + { + struct display_connection* connection = display_connect_default(); + if ( !connection ) + err(1, "Could not connect to display server"); + + display_chkblayout(connection, CHKBLAYOUT_ID, kblayout, kblayout_size); + + struct display_event_handlers handlers = {0}; + handlers.ack_handler = on_ack; + while ( !chkblayout_ack_received ) + display_wait_event(connection, &handlers); + + if ( chkblayout_error ) + { + errno = chkblayout_error; + err(1, "tcsetblob: kblayout: %s", kblayout_path); + } + + display_disconnect(connection); + } + else if ( tcsetblob(tty_fd, "kblayout", kblayout, kblayout_size) < 0 ) + err(1, "tcsetblob: kblayout: %s", kblayout_path); free(kblayout); diff --git a/chvideomode/Makefile b/chvideomode/Makefile index 249ce2ea..77a15caa 100644 --- a/chvideomode/Makefile +++ b/chvideomode/Makefile @@ -11,7 +11,7 @@ CFLAGS += -Wall -Wextra BINARIES = chvideomode MANPAGES1 = chvideomode.1 -LIBS = +LIBS = -ldisplay all: $(BINARIES) diff --git a/chvideomode/chvideomode.c b/chvideomode/chvideomode.c index 2d7bbab1..b6a96500 100644 --- a/chvideomode/chvideomode.c +++ b/chvideomode/chvideomode.c @@ -22,6 +22,8 @@ #include #include +#include +#include #include #include #include @@ -37,7 +39,22 @@ #include #include -struct termios saved; +#define REQUEST_DISPLAYS_ID 0 +#define REQUEST_DISPLAY_MODES_ID 1 +#define SET_DISPLAY_MODE_ID 2 + +static uint32_t display_id; +static bool displays_received = false; + +static size_t modes_count = 0; +static struct dispmsg_crtc_mode* modes; +static int request_display_modes_error = 0; +static bool modes_received = false; + +static int set_display_mode_error = 0; +static bool set_display_mode_ack_received; + +static struct termios saved; static void restore_terminal(int sig) { @@ -48,6 +65,88 @@ static void restore_terminal(int sig) raise(sig); } +static void on_displays(void* ctx, uint32_t id, uint32_t displays) +{ + (void) ctx; + if ( id != REQUEST_DISPLAYS_ID ) + return; + if ( displays < 1 ) + errx(1, "No displays available"); + display_id = 0; // TODO: Multimonitor support. + displays_received = true; +} + +static void on_display_modes(void* ctx, uint32_t id, + uint32_t display_modes_count, + void* aux, size_t aux_size) +{ + (void) ctx; + assert(display_modes_count * sizeof(struct dispmsg_crtc_mode) == aux_size); + if ( id != REQUEST_DISPLAY_MODES_ID ) + return; + modes = malloc(aux_size); + if ( !modes ) + err(1, "malloc"); + memcpy(modes, aux, aux_size); + modes_count = display_modes_count; + modes_received = true; +} + +static void on_ack(void* ctx, uint32_t id, int32_t error) +{ + (void) ctx; + switch ( id ) + { + case REQUEST_DISPLAY_MODES_ID: + if ( error ) + { + modes = NULL; + request_display_modes_error = error; + modes_received = true; + } + break; + case SET_DISPLAY_MODE_ID: + set_display_mode_error = error; + set_display_mode_ack_received = true; + break; + } +} + +static void request_displays(struct display_connection* connection) +{ + display_request_displays(connection, REQUEST_DISPLAYS_ID); + struct display_event_handlers handlers = {0}; + handlers.displays_handler = on_displays; + while ( !displays_received ) + display_wait_event(connection, &handlers); +} + +static void request_display_modes(struct display_connection* connection, + uint32_t display_id) +{ + display_request_display_modes(connection, REQUEST_DISPLAY_MODES_ID, + display_id); + struct display_event_handlers handlers = {0}; + handlers.display_modes_handler = on_display_modes; + handlers.ack_handler = on_ack; + while ( !modes_received ) + display_wait_event(connection, &handlers); + errno = request_display_modes_error; +} + +static bool request_set_display_mode(struct display_connection* connection, + uint32_t display_id, + struct dispmsg_crtc_mode mode) +{ + display_set_display_mode(connection, SET_DISPLAY_MODE_ID, display_id, mode); + struct display_event_handlers handlers = {0}; + handlers.ack_handler = on_ack; + set_display_mode_ack_received = false; + while ( !set_display_mode_ack_received ) + display_wait_event(connection, &handlers); + return !(errno = set_display_mode_error); +} + static bool set_current_mode(const struct tiocgdisplay* display, struct dispmsg_crtc_mode mode) { @@ -61,7 +160,7 @@ static bool set_current_mode(const struct tiocgdisplay* display, static struct dispmsg_crtc_mode* get_available_modes(const struct tiocgdisplay* display, - size_t* num_modes_ptr) + size_t* modes_count_ptr) { struct dispmsg_get_crtc_modes msg; msg.msgid = DISPMSG_GET_CRTC_MODES; @@ -70,15 +169,15 @@ get_available_modes(const struct tiocgdisplay* display, size_t guess = 1; while ( true ) { - struct dispmsg_crtc_mode* ret = (struct dispmsg_crtc_mode*) - malloc(sizeof(struct dispmsg_crtc_mode) * guess); + struct dispmsg_crtc_mode* ret = + calloc(guess, sizeof(struct dispmsg_crtc_mode)); if ( !ret ) return NULL; msg.modes_length = guess; msg.modes = ret; if ( dispmsg_issue(&msg, sizeof(msg)) == 0 ) { - *num_modes_ptr = guess; + *modes_count_ptr = guess; return ret; } free(ret); @@ -144,21 +243,21 @@ static bool mode_passes_filter(struct dispmsg_crtc_mode mode, } static void filter_modes(struct dispmsg_crtc_mode* modes, - size_t* num_modes_ptr, + size_t* modes_count_ptr, struct filter* filter) { - size_t in_num = *num_modes_ptr; - size_t out_num = 0; - for ( size_t i = 0; i < in_num; i++ ) + size_t in_count = *modes_count_ptr; + size_t out_count = 0; + for ( size_t i = 0; i < in_count; i++ ) { if ( mode_passes_filter(modes[i], filter) ) - modes[out_num++] = modes[i]; + modes[out_count++] = modes[i]; } - *num_modes_ptr = out_num; + *modes_count_ptr = out_count; } static bool get_mode(struct dispmsg_crtc_mode* modes, - size_t num_modes, + size_t modes_count, unsigned int xres, unsigned int yres, unsigned int bpp, @@ -168,7 +267,7 @@ static bool get_mode(struct dispmsg_crtc_mode* modes, bool found_other = false; size_t index; size_t other_index = 0; - for ( size_t i = 0; i < num_modes; i++ ) + for ( size_t i = 0; i < modes_count; i++ ) { if ( modes[i].view_xres == xres && modes[i].view_yres == yres && @@ -208,16 +307,16 @@ static bool get_mode(struct dispmsg_crtc_mode* modes, } static bool select_mode(struct dispmsg_crtc_mode* modes, - size_t num_modes, + size_t modes_count, int mode_set_error, struct dispmsg_crtc_mode* mode) { if ( !isatty(0) ) errx(1, "Interactive menu requires stdin to be a terminal"); - int num_modes_display_length = 1; - for ( size_t i = num_modes; 10 <= i; i /= 10 ) - num_modes_display_length++; + int modes_count_display_length = 1; + for ( size_t i = modes_count; 10 <= i; i /= 10 ) + modes_count_display_length++; size_t selection = 0; bool decided = false; @@ -239,7 +338,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, size_t entries_per_page = ws.ws_row - off; size_t page = selection / entries_per_page; size_t from = page * entries_per_page; - size_t how_many_available = num_modes - from; + size_t how_many_available = modes_count - from; size_t how_many = entries_per_page; if ( how_many_available < how_many ) how_many = how_many_available; @@ -259,7 +358,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, const char* color = index == selection ? "\e[31m" : "\e[m"; printf("%s", color); printf("\e[2K"); - printf(" [%-*zu] ", num_modes_display_length, index); + printf(" [%-*zu] ", modes_count_display_length, index); if ( modes[index].control & DISPMSG_CONTROL_VALID ) printf("%ux%ux%u", modes[index].view_xres, @@ -325,12 +424,12 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, if ( selection ) selection--; else - selection = num_modes - 1; + selection = modes_count - 1; redraw = true; } else if ( length == 1 && byte == 'B' ) // Down key { - if ( selection + 1 == num_modes ) + if ( selection + 1 == modes_count ) selection = 0; else selection++; @@ -343,7 +442,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, else if ( '0' <= byte && byte <= '9' ) { uint32_t requested = byte - '0'; - if ( requested < num_modes ) + if ( requested < modes_count ) { selection = requested; redraw = true; @@ -510,22 +609,37 @@ int main(int argc, char* argv[]) } } + bool use_display = getenv("DISPLAY_SOCKET"); + + struct display_connection* connection = NULL; struct tiocgdisplay display; - struct tiocgdisplays gdisplays = {0}; - gdisplays.count = 1; - gdisplays.displays = &display; - if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0 ) + if ( use_display ) { - fprintf(stderr, "No video devices are associated with this terminal.\n"); - exit(13); + connection = display_connect_default(); + if ( !connection ) + err(1, "Could not connect to display server"); + request_displays(connection); + request_display_modes(connection, display_id); + } + else + { + struct tiocgdisplays gdisplays = {0}; + // TODO: Multimonitor support. + gdisplays.count = 1; + gdisplays.displays = &display; + if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0 ) + { + fprintf(stderr, "No displays associated with this terminal.\n"); + exit(13); + } + + modes = get_available_modes(&display, &modes_count); } - size_t num_modes = 0; - struct dispmsg_crtc_mode* modes = get_available_modes(&display, &num_modes); if ( !modes ) err(1, "Unable to detect available video modes"); - if ( !num_modes ) + if ( !modes_count ) { fprintf(stderr, "No video modes are currently available.\n"); fprintf(stderr, "Try make sure a device driver exists and is " @@ -533,8 +647,8 @@ int main(int argc, char* argv[]) exit(11); } - filter_modes(modes, &num_modes, &filter); - if ( !num_modes ) + filter_modes(modes, &modes_count, &filter); + if ( !modes_count ) { fprintf(stderr, "No video mode remains after filtering away unwanted " "modes.\n"); @@ -552,10 +666,15 @@ int main(int argc, char* argv[]) errx(1, "Invalid video mode: %s", argv[optind]); struct dispmsg_crtc_mode mode; - if ( !get_mode(modes, num_modes, xres, yres, bpp, &mode) ) + if ( !get_mode(modes, modes_count, xres, yres, bpp, &mode) ) errx(1, "No such available resolution: %s", argv[optind]); - if ( !set_current_mode(&display, mode) ) + bool mode_set; + if ( use_display ) + mode_set = request_set_display_mode(connection, display_id, mode); + else + mode_set = set_current_mode(&display, mode); + if ( !mode_set ) err(1, "Failed to set video mode %jux%jux%ju", (uintmax_t) mode.view_xres, (uintmax_t) mode.view_yres, @@ -568,10 +687,15 @@ int main(int argc, char* argv[]) while ( !mode_set ) { struct dispmsg_crtc_mode mode; - if ( !select_mode(modes, num_modes, mode_set_error, &mode) ) + if ( !select_mode(modes, modes_count, mode_set_error, &mode) ) exit(10); - if ( !(mode_set = set_current_mode(&display, mode)) ) + if ( use_display ) + mode_set = request_set_display_mode(connection, display_id, + mode); + else + mode_set = set_current_mode(&display, mode); + if ( !mode_set ) { mode_set_error = errno; warn("Failed to set video mode %jux%jux%ju", @@ -582,5 +706,8 @@ int main(int argc, char* argv[]) } } + if ( use_display ) + display_disconnect(connection); + return 0; } diff --git a/dispd/.gitignore b/dispd/.gitignore deleted file mode 100644 index 20586633..00000000 --- a/dispd/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.a -*.o -server/dispd diff --git a/dispd/Makefile b/dispd/Makefile deleted file mode 100644 index 441b6afc..00000000 --- a/dispd/Makefile +++ /dev/null @@ -1,53 +0,0 @@ -SOFTWARE_MEANT_FOR_SORTIX=1 -include ../build-aux/platform.mak -include ../build-aux/compiler.mak -include ../build-aux/version.mak -include ../build-aux/dirs.mak - -OPTLEVEL?=$(DEFAULT_OPTLEVEL) -CFLAGS?=$(OPTLEVEL) - -CFLAGS:=$(CFLAGS) -Wall -Wextra -CPPFLAGS:=$(CPPFLAGS) -I include - -CLIENT_OBJS:=\ -client/library.o \ -client/session.o \ -client/window.o \ - -BINS:=client/libdispd.a - -all: $(BINS) - -.PHONY: all headers client clean install install-include-dirs \ - install-headers install-client-dirs install-client - -headers: - -client: client/libdispd.a - -client/libdispd.a: $(CLIENT_OBJS) - $(AR) rcs $@ $(CLIENT_OBJS) - -clean: - rm -f $(CLIENT_OBJS) - rm -f $(BINS) - rm -f *.o client/*.o - -%.o: %.c - $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ - -# Installation into sysroot -install: install-headers install-client - -install-include-dirs: headers - mkdir -p $(DESTDIR)$(INCLUDEDIR) - -install-headers: install-include-dirs headers - cp -RTv include $(DESTDIR)$(INCLUDEDIR) - -install-client-dirs: - mkdir -p $(DESTDIR)$(LIBDIR) - -install-client: install-client-dirs client - cp -P client/libdispd.a $(DESTDIR)$(LIBDIR) diff --git a/dispd/client/session.c b/dispd/client/session.c deleted file mode 100644 index 90ccad78..00000000 --- a/dispd/client/session.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2012, 2013, 2014, 2016 Jonas 'Sortie' Termansen. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * session.c - * Handles session management. - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "session.h" - -struct dispd_session* global_session = NULL; - -bool dispd__session_initialize(int* argc, char*** argv) -{ - (void) argc; - (void) argv; - size_t size = sizeof(struct dispd_session); - global_session = (struct dispd_session*) malloc(size); - if ( !global_session ) - return false; - memset(global_session, 0, sizeof(*global_session)); - int tty_fd = open("/dev/tty", O_RDWR); - if ( tty_fd < 0 ) - return free(global_session), false; - struct tiocgdisplay display; - struct tiocgdisplays gdisplays; - memset(&gdisplays, 0, sizeof(gdisplays)); - gdisplays.count = 1; - gdisplays.displays = &display; - bool fail = ioctl(tty_fd, TIOCGDISPLAYS, &gdisplays) < 0 || - gdisplays.count == 0; - close(tty_fd); - if ( fail ) - return free(global_session), false; - global_session->device = display.device; - global_session->connector = display.connector; - return true; -} - -struct dispd_session* dispd_attach_default_session() -{ - global_session->refcount++; - return global_session; -} - -bool dispd_detach_session(struct dispd_session* session) -{ - session->refcount--; - return true; -} - -bool dispd_session_setup_game_rgba(struct dispd_session* session) -{ - if ( session->is_rgba ) - return true; - if ( session->current_window ) - return false; - struct dispmsg_get_crtc_mode msg; - msg.msgid = DISPMSG_GET_CRTC_MODE; - msg.device = session->device; - msg.connector = session->connector; - if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) - return false; - if ( !(msg.mode.control & 1) || msg.mode.fb_format != 32 ) - { - pid_t childpid = fork(); - if ( childpid < 0 ) - return false; - if ( childpid ) - { - int status; - waitpid(childpid, &status, 0); - return session->is_rgba = (WIFEXITED(status) && !WEXITSTATUS(status)); - } - const char* chvideomode = "chvideomode"; -#if 1 - // TODO chvideomode currently launches --bpp 32 as a program... - execlp(chvideomode, chvideomode, (const char*) NULL); -#else - execlp(chvideomode, chvideomode, - "--bpp", "32", - "--show-graphics", "true", - "--show-text", "false", - (const char*) NULL); -#endif - err(127, "%s", chvideomode); - } - - // HACK: The console may be rendered asynchronously and it might still be - // rendering to the framebuffer, however this process is about to do - // bitmapped graphics to the framebuffer as well. Since there is no - // synchronization with the terminal except not writing to it, there - // is a small window where both are fighting for the framebuffer. - // We can resolve this issue by simply fsync()ing the terminal, which - // causes the scheduled console rendering to finish before returning. - int tty_fd = open("/dev/tty", O_WRONLY); - if ( 0 <= tty_fd ) - { - fsync(tty_fd); - close(tty_fd); - } - - return session->is_rgba = true; -} diff --git a/dispd/client/window.c b/dispd/client/window.c deleted file mode 100644 index 2a7dfb94..00000000 --- a/dispd/client/window.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2012, 2013, 2014, 2016 Jonas 'Sortie' Termansen. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * window.c - * Handles windows. - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "framebuffer.h" -#include "session.h" -#include "window.h" - -struct dispd_window* dispd_create_window_game_rgba(struct dispd_session* session) -{ - if ( session->current_window ) - return NULL; - if ( !session->is_rgba ) - return NULL; - size_t size = sizeof(struct dispd_window); - struct dispd_window* window = (struct dispd_window*) malloc(size); - if ( !window ) - return NULL; - memset(window, 0, sizeof(*window)); - window->session = session; - return session->current_window = window; -} - -bool dispd_destroy_window(struct dispd_window* window) -{ - free(window->cached_buffer); - free(window); - return true; -} - -static uint8_t* request_buffer(struct dispd_window* window, size_t size) -{ - if ( window->cached_buffer ) - { - if ( window->cached_buffer_size == size ) - { - uint8_t* ret = window->cached_buffer; - window->cached_buffer = NULL; - window->cached_buffer_size = 0; - return ret; - } - free(window->cached_buffer); - window->cached_buffer = NULL; - window->cached_buffer_size = 0; - } - return (uint8_t*) malloc(size); -} - -static void return_buffer(struct dispd_window* window, uint8_t* b, size_t size) -{ - free(window->cached_buffer); - window->cached_buffer = b; - window->cached_buffer_size = size; -} - -struct dispd_framebuffer* dispd_begin_render(struct dispd_window* window) -{ - struct dispmsg_get_crtc_mode msg; - msg.msgid = DISPMSG_GET_CRTC_MODE; - msg.device = window->session->device; - msg.connector = window->session->connector; - if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) - return NULL; - size_t size = sizeof(struct dispd_framebuffer); - struct dispd_framebuffer* fb = (struct dispd_framebuffer*) malloc(size); - if ( !fb ) - return NULL; - memset(fb, 0, sizeof(*fb)); - fb->window = window; - fb->width = msg.mode.view_xres; - fb->height = msg.mode.view_yres; - fb->bpp = 32; - fb->pitch = fb->width * fb->bpp / 8; - fb->datasize = fb->pitch * fb->height; - fb->data = (uint8_t*) request_buffer(window, fb->datasize); - fb->fb_location = msg.mode.fb_location; - if ( !fb->data ) { free(fb); return NULL; } - return fb; -} - -bool dispd_finish_render(struct dispd_framebuffer* fb) -{ - struct dispd_window* window = fb->window; - bool ret = false; - struct dispmsg_write_memory msg; - msg.msgid = DISPMSG_WRITE_MEMORY; - msg.device = window->session->device; - msg.offset = fb->fb_location; - msg.size = fb->datasize; - msg.src = fb->data; - if ( dispmsg_issue(&msg, sizeof(msg)) == 0 ) - ret = true; - return_buffer(window, fb->data, fb->datasize); - free(fb); - return ret; -} - -uint8_t* dispd_get_framebuffer_data(struct dispd_framebuffer* fb) -{ - return fb->data; -} - -size_t dispd_get_framebuffer_pitch(struct dispd_framebuffer* fb) -{ - return fb->pitch; -} - -int dispd_get_framebuffer_format(struct dispd_framebuffer* fb) -{ - return fb->bpp; -} - -int dispd_get_framebuffer_height(struct dispd_framebuffer* fb) -{ - return fb->height; -} - -int dispd_get_framebuffer_width(struct dispd_framebuffer* fb) -{ - return fb->width; -} diff --git a/dispd/include/dispd.h b/dispd/include/dispd.h deleted file mode 100644 index 4e34fdc4..00000000 --- a/dispd/include/dispd.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2012 Jonas 'Sortie' Termansen. - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * dispd.h - * Interface to the Sortix Display Daemon. - */ - -#ifndef INCLUDE_DISPD_H -#define INCLUDE_DISPD_H - -#if !defined(__cplusplus) -#include -#endif -#include -#include - -#if defined(__cplusplus) -extern "C" { -#endif - -struct dispd_session; -struct dispd_window; -struct dispd_framebuffer; - -bool dispd_initialize(int* argc, char*** argv); - -struct dispd_session* dispd_attach_default_session(void); -bool dispd_detach_session(struct dispd_session* session); - -bool dispd_session_setup_game_rgba(struct dispd_session* session); - -struct dispd_window* dispd_create_window_game_rgba(struct dispd_session* session); -bool dispd_destroy_window(struct dispd_window* window); - -struct dispd_framebuffer* dispd_begin_render(struct dispd_window* window); -bool dispd_finish_render(struct dispd_framebuffer* framebuffer); - -uint8_t* dispd_get_framebuffer_data(struct dispd_framebuffer* framebuffer); -size_t dispd_get_framebuffer_pitch(struct dispd_framebuffer* framebuffer); -int dispd_get_framebuffer_format(struct dispd_framebuffer* framebuffer); -int dispd_get_framebuffer_height(struct dispd_framebuffer* framebuffer); -int dispd_get_framebuffer_width(struct dispd_framebuffer* framebuffer); - -#if defined(__cplusplus) -} /* extern "C" */ -#endif - -#endif diff --git a/display/.gitignore b/display/.gitignore new file mode 100644 index 00000000..c6938b89 --- /dev/null +++ b/display/.gitignore @@ -0,0 +1,3 @@ +display +*.o +*.inc diff --git a/display/Makefile b/display/Makefile new file mode 100644 index 00000000..8eb7ea2b --- /dev/null +++ b/display/Makefile @@ -0,0 +1,51 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra + +PROGRAM=display +MANPAGES1 = display.1 +MANPAGES5 = displayrc.5 + +OBJS=\ +connection.o \ +display-code.o \ +display.o \ +server.o \ +window.o \ + +LIBS:=-lui + +all: $(PROGRAM) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(PROGRAM) $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(SYSCONFDIR)/default + printf '#!sh\nexec terminal\n' > $(DESTDIR)$(SYSCONFDIR)/default/displayrc + chmod +x $(DESTDIR)$(SYSCONFDIR)/default/displayrc + mkdir -p $(DESTDIR)$(MANDIR)/man1 + install $(MANPAGES1) $(DESTDIR)$(MANDIR)/man1 + mkdir -p $(DESTDIR)$(MANDIR)/man5 + install $(MANPAGES5) $(DESTDIR)$(MANDIR)/man5 + +$(PROGRAM): $(OBJS) + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $(OBJS) -o $@ $(LIBS) + +display.o: arrow.inc + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +%.inc: %.rgb + carray -cs -o $@ $< + +clean: + rm -f $(PROGRAM) *.o *.inc diff --git a/display/arrow.rgb b/display/arrow.rgb new file mode 100644 index 00000000..6d861ba5 Binary files /dev/null and b/display/arrow.rgb differ diff --git a/display/connection.c b/display/connection.c new file mode 100644 index 00000000..57e2ccfb --- /dev/null +++ b/display/connection.c @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen. + * Copyright (c) 2023 Juhani 'nortti' Krekelä. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * connection.c + * Display protocol implementation. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "connection.h" +#include "display.h" + +void connection_schedule_transmit(struct connection* connection, + const void* buffer, + size_t count) +{ + assert(connection); + size_t available = connection->outgoing_size - connection->outgoing_used; + if ( available < count ) + { + // TODO: Overflow. + size_t required_size = connection->outgoing_used + count; + // TODO: Check allocation. + unsigned char* new_outgoing = malloc(required_size); + size_t first_available = + connection->outgoing_size - connection->outgoing_offset; + size_t first = connection->outgoing_used < first_available ? + connection->outgoing_used : + first_available; + if ( connection->outgoing ) + { + memcpy(new_outgoing, connection->outgoing + + connection->outgoing_offset, first); + size_t second = connection->outgoing_used - first; + memcpy(new_outgoing + first, connection->outgoing, second); + free(connection->outgoing); + } + connection->outgoing_offset = 0; + connection->outgoing_size = required_size; + connection->outgoing = new_outgoing; + } + + size_t used_offset = + (connection->outgoing_offset + connection->outgoing_used) % + connection->outgoing_size; + size_t first_available = connection->outgoing_size - used_offset; + size_t first = count < first_available ? count : first_available; + memcpy(connection->outgoing + used_offset, buffer, first); + size_t second = count - first; + memcpy(connection->outgoing, (const unsigned char*) buffer + first, second); + connection->outgoing_used += count; +} + +void connection_schedule_ack_event(struct connection* connection, + uint32_t id, + int32_t error) +{ + struct event_ack event = { .id = id, .error = error }; + struct display_packet_header header = { .id = EVENT_ACK, + .size = sizeof(event) }; + connection_schedule_transmit(connection, &header, sizeof(header)); + connection_schedule_transmit(connection, &event, sizeof(event)); +} + +void connection_initialize(struct connection* connection, + struct display* display, + int fd) +{ + memset(connection, 0, sizeof(*connection)); + connection->display = display; + connection->fd = fd; +} + +struct window* connection_find_window_raw(struct connection* connection, + uint32_t window_id) +{ + if ( MAX_WINDOWS_PER_CONNECTION <= window_id ) + return NULL; + return &connection->windows[window_id]; +} + +struct window* connection_find_window(struct connection* connection, + uint32_t window_id) +{ + struct window* result = connection_find_window_raw(connection, window_id); + if ( !result->created ) + return NULL; + return result; +} + +#define CONNECTION_MESSAGE_HANDLER_NO_AUX(message_name) \ +void connection_handler_##message_name( \ + struct connection* connection, \ + struct display_##message_name* msg, \ + void* auxiliary __attribute__((unused)), \ + size_t auxiliary_size __attribute__((unused)), \ + const struct server* server __attribute__((unused))) + +#define CONNECTION_MESSAGE_HANDLER(message_name) \ +void connection_handler_##message_name( \ + struct connection* connection, \ + struct display_##message_name* msg, \ + unsigned char* auxiliary, \ + size_t auxiliary_size, \ + const struct server* server __attribute__((unused))) + +#define CONNECTION_MESSAGE_HANDLER_NO_AUX_SERVER(message_name) \ +void connection_handler_##message_name( \ + struct connection* connection, \ + struct display_##message_name* msg, \ + void* auxiliary __attribute__((unused)), \ + size_t auxiliary_size __attribute__((unused)), \ + const struct server* server) + +#define CONNECTION_MESSAGE_HANDLER_SERVER(message_name) \ +void connection_handler_##message_name( \ + struct connection* connection, \ + struct display_##message_name* msg, \ + unsigned char* auxiliary, \ + size_t auxiliary_size, \ + const struct server* server) + +CONNECTION_MESSAGE_HANDLER_NO_AUX(shutdown) +{ + (void) connection; + if ( msg->code == 0 ) + exit(0); + else if ( msg->code == 1 ) + exit(1); + else if ( msg->code == 2 ) + exit(2); + else if ( msg->code == 3 ) + exit(3); + else + exit(0); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(create_window) +{ + struct window* window = + connection_find_window_raw(connection, msg->window_id); + if ( !window ) + return; + if ( window->created ) + return; + window_initialize(window, connection, connection->display, msg->window_id); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(destroy_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + window_destroy(window); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(resize_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + window_client_resize(window, msg->width, msg->height); +} + +CONNECTION_MESSAGE_HANDLER(render_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + + struct framebuffer src; + src.xres = msg->width; + src.yres = msg->height; + src.pitch = msg->width; + src.buffer = (uint32_t*) auxiliary; + + if ( auxiliary_size < sizeof(uint32_t) * src.xres * src.yres ) + return; + + struct framebuffer dst = + framebuffer_crop(window_client_buffer(window), + msg->left, msg->top, msg->width, msg->height); + + framebuffer_copy_to_framebuffer(dst, src); + + window_schedule_redraw(window); +} + +CONNECTION_MESSAGE_HANDLER(title_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + + const char* title = (char*) auxiliary; + free(window->title); + window->title = strndup(title, auxiliary_size); + + window_render_frame(window); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(show_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + if ( !window->show ) + display_schedule_redraw(window->display); + window->show = true; +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(hide_window) +{ + struct window* window = connection_find_window(connection, msg->window_id); + if ( !window ) + return; + if ( window->show ) + display_schedule_redraw(window->display); + window->show = false; +} + +CONNECTION_MESSAGE_HANDLER_SERVER(chkblayout) +{ + if ( tcsetblob(server->tty_fd, "kblayout", auxiliary, auxiliary_size) < 0 ) + connection_schedule_ack_event(connection, msg->id, errno); + else + connection_schedule_ack_event(connection, msg->id, 0); +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX(request_displays) +{ + struct event_displays event; + event.id = msg->id; + event.displays = 1; // TODO: Multimonitor support. + + struct display_packet_header header; + header.id = EVENT_DISPLAYS; + header.size = sizeof(event); + + connection_schedule_transmit(connection, &header, sizeof(header)); + connection_schedule_transmit(connection, &event, sizeof(event)); +} + +static struct dispmsg_crtc_mode* +get_available_modes(const struct tiocgdisplay* display, + size_t* modes_count_ptr) +{ + struct dispmsg_get_crtc_modes msg; + msg.msgid = DISPMSG_GET_CRTC_MODES; + msg.device = display->device; + msg.connector = display->connector; + size_t guess = 1; + while ( true ) + { + struct dispmsg_crtc_mode* ret = + calloc(guess, sizeof(struct dispmsg_crtc_mode)); + if ( !ret ) + return NULL; + msg.modes_length = guess; + msg.modes = ret; + if ( dispmsg_issue(&msg, sizeof(msg)) == 0 ) + { + *modes_count_ptr = guess; + return ret; + } + free(ret); + if ( errno == ERANGE && guess < msg.modes_length ) + { + guess = msg.modes_length; + continue; + } + return NULL; + } +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX_SERVER(request_display_modes) +{ + struct event_display_modes event; + event.id = msg->id; + event.modes_count = 0; + + struct dispmsg_crtc_mode* modes = NULL; + // TODO: Multimonitor support. + if ( msg->display_id != 0 ) + { + connection_schedule_ack_event(connection, msg->id, EINVAL); + return; + } + else + { + size_t modes_count; + modes = get_available_modes(&server->display->display, &modes_count); + if ( !modes ) + { + connection_schedule_ack_event(connection, msg->id, errno); + return; + } + else if ( (uint32_t) modes_count != modes_count ) + { + free(modes); + connection_schedule_ack_event(connection, msg->id, EOVERFLOW); + return; + } + event.modes_count = modes_count; + } + + size_t modes_size = event.modes_count * sizeof(struct dispmsg_crtc_mode); + + struct display_packet_header header; + header.id = EVENT_DISPLAY_MODES; + header.size = sizeof(event) + modes_size; + + connection_schedule_transmit(connection, &header, sizeof(header)); + connection_schedule_transmit(connection, &event, sizeof(event)); + connection_schedule_transmit(connection, modes, modes_size); +} + +static bool get_current_mode(const struct tiocgdisplay* display, + struct dispmsg_crtc_mode* mode) +{ + struct dispmsg_set_crtc_mode msg; + msg.msgid = DISPMSG_GET_CRTC_MODE; + msg.device = display->device; + msg.connector = display->connector; + if ( dispmsg_issue(&msg, sizeof(msg)) ) + return false; + *mode = msg.mode; + return true; +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX_SERVER(request_display_mode) +{ + struct event_display_mode event; + event.id = msg->id; + + // TODO: Multimonitor support. + if ( msg->display_id != 0 ) + { + connection_schedule_ack_event(connection, msg->id, EINVAL); + return; + } + else if ( !get_current_mode(&server->display->display, &event.mode) ) + { + connection_schedule_ack_event(connection, msg->id, EINVAL); + return; + } + + struct display_packet_header header; + header.id = EVENT_DISPLAY_MODE; + header.size = sizeof(event); + + connection_schedule_transmit(connection, &header, sizeof(header)); + connection_schedule_transmit(connection, &event, sizeof(event)); +} + +static bool set_current_mode(const struct tiocgdisplay* display, + struct dispmsg_crtc_mode mode) +{ + struct dispmsg_set_crtc_mode msg; + msg.msgid = DISPMSG_SET_CRTC_MODE; + msg.device = display->device; + msg.connector = display->connector; + msg.mode = mode; + return dispmsg_issue(&msg, sizeof(msg)) == 0; +} + +CONNECTION_MESSAGE_HANDLER_NO_AUX_SERVER(set_display_mode) +{ + // TODO: Multimonitor support. + if ( msg->display_id != 0 ) + connection_schedule_ack_event(connection, msg->id, EINVAL); + else if ( !set_current_mode(&server->display->display, msg->mode) ) + connection_schedule_ack_event(connection, msg->id, errno); + else + connection_schedule_ack_event(connection, msg->id, 0); +} + +typedef void (*connection_message_handler)(struct connection* connection, + void* msg, + void* auxiliary, + size_t auxiliary_size, + const struct server* server); + +struct connection_message_handler_registration +{ + connection_message_handler handler; + size_t message_size; +}; + +#define REGISTER_CONNECTION_MESSAGE_HANDLER(message_name) \ + { (connection_message_handler) connection_handler_##message_name, \ + sizeof(struct display_##message_name) } + +struct connection_message_handler_registration connection_message_handlers[] = +{ + REGISTER_CONNECTION_MESSAGE_HANDLER(create_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(destroy_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(resize_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(render_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(title_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(show_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(hide_window), + REGISTER_CONNECTION_MESSAGE_HANDLER(shutdown), + REGISTER_CONNECTION_MESSAGE_HANDLER(chkblayout), + REGISTER_CONNECTION_MESSAGE_HANDLER(request_displays), + REGISTER_CONNECTION_MESSAGE_HANDLER(request_display_modes), + REGISTER_CONNECTION_MESSAGE_HANDLER(request_display_mode), + REGISTER_CONNECTION_MESSAGE_HANDLER(set_display_mode), +}; + +size_t num_connection_message_handlers = sizeof(connection_message_handlers) / + sizeof(connection_message_handlers[0]); + +short connection_interested_poll_events(struct connection* connection) +{ + return POLLIN | (connection->outgoing_used ? POLLOUT : 0); +} + +void connection_can_read(struct connection* connection, + const struct server* server) +{ + while ( connection->packet_header_received < + sizeof(connection->packet_header) ) + { + ssize_t amount = read(connection->fd, + (unsigned char*) &connection->packet_header + + connection->packet_header_received, + sizeof(connection->packet_header) - + connection->packet_header_received); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + return; + if ( amount < 0 || amount == 0 ) + return; // TODO: No longer signal interest in reading + disconnect. + connection->packet_header_received += amount; + } + + size_t packet_size = connection->packet_header.size; + + // TODO: Check allocation and protect against too big buffers. + if ( !connection->packet ) + connection->packet = malloc(packet_size); + + while ( connection->packet_received < packet_size ) + { + ssize_t amount = read(connection->fd, + connection->packet + connection->packet_received, + packet_size - connection->packet_received); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + return; + if ( amount < 0 || amount == 0 ) + return; // TODO: No longer signal interest in reading + disconnect. + connection->packet_received += amount; + } + + size_t packet_id = connection->packet_header.id; + + if ( packet_id < num_connection_message_handlers ) + { + struct connection_message_handler_registration* handler = + &connection_message_handlers[packet_id]; + unsigned char* auxiliary = connection->packet + handler->message_size; + size_t auxiliary_size = packet_size - handler->message_size; + handler->handler(connection, connection->packet, + auxiliary, auxiliary_size, server); + } + + connection->packet_header_received = 0; + free(connection->packet); + connection->packet = NULL; + connection->packet_received = 0; + + // TODO: Check if we can receive another packet, but only if we haven't + // done so much work that the display server is starved. +} + +void connection_can_write(struct connection* connection) +{ + while ( connection->outgoing_used ) + { + size_t available = + connection->outgoing_size - connection->outgoing_offset; + size_t count = connection->outgoing_used < available ? + connection->outgoing_used : available; + unsigned char* buf = connection->outgoing + connection->outgoing_offset; + ssize_t amount = write(connection->fd, buf, count); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + return; + if ( amount < 0 || amount == 0 ) + return; // TODO: No longer signal interest in writing + disconnect. + connection->outgoing_offset = (connection->outgoing_offset + amount) % + connection->outgoing_size; + connection->outgoing_used -= amount; + } + + free(connection->outgoing); + connection->outgoing = NULL; + connection->outgoing_used = 0; + connection->outgoing_size = 0; +} + +void connection_destroy(struct connection* connection) +{ + for ( size_t i = 0; i < MAX_WINDOWS_PER_CONNECTION; i++ ) + { + if ( !connection->windows[i].created ) + continue; + window_destroy(&connection->windows[i]); + } + close(connection->fd); +} diff --git a/display/connection.h b/display/connection.h new file mode 100644 index 00000000..2fb1898c --- /dev/null +++ b/display/connection.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * connection.h + * Display protocol implementation. + */ + +#ifndef CONNECTION_H +#define CONNECTION_H + +#include +#include + +#include + +#include "server.h" +#include "window.h" + +struct display; +struct window; + +#define MAX_WINDOWS_PER_CONNECTION 256 + +struct connection +{ + struct display* display; + struct window windows[MAX_WINDOWS_PER_CONNECTION]; + struct display_packet_header packet_header; + size_t packet_header_received; + unsigned char* packet; + size_t packet_received; + unsigned char* outgoing; + size_t outgoing_offset; + size_t outgoing_used; + size_t outgoing_size; + int fd; +}; + +void connection_schedule_transmit(struct connection* connection, + const void* buffer, + size_t count); +void connection_initialize(struct connection* connection, + struct display* display, + int fd); +struct window* connection_find_window_raw(struct connection* connection, + uint32_t window_id); +struct window* connection_find_window(struct connection* connection, + uint32_t window_id); +short connection_interested_poll_events(struct connection* connection); +void connection_can_read(struct connection* connection, + const struct server* server); +void connection_can_write(struct connection* connection); +void connection_destroy(struct connection* connection); + +#endif diff --git a/dispd/client/window.h b/display/damage-rect.c similarity index 57% rename from dispd/client/window.h rename to display/damage-rect.c index 96caf160..88769290 100644 --- a/dispd/client/window.h +++ b/display/damage-rect.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Jonas 'Sortie' Termansen. + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,26 +13,27 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * window.h - * Handles windows. + * damage-rect.c + * Damage rectangles. */ -#ifndef INCLUDE_DISPD_WINDOW_H -#define INCLUDE_DISPD_WINDOW_H +#include -#if defined(__cplusplus) -extern "C" { -#endif +#include "damage-rect.h" -struct dispd_window +struct damage_rect damage_rect_add(struct damage_rect a, struct damage_rect b) { - struct dispd_session* session; - uint8_t* cached_buffer; - size_t cached_buffer_size; -}; - -#if defined(__cplusplus) -} /* extern "C" */ -#endif - -#endif + if ( !a.width || !a.height ) + return b; + if ( !b.width || !b.height ) + return a; + if ( a.left < b.left ) + b.width += b.left - a.left, b.left = a.left; + if ( b.width < a.width ) + b.width = a.width; + if ( a.top < b.top ) + b.height += b.top - a.top, b.top = a.top; + if ( b.height < a.height ) + b.height = a.height; + return b; +} diff --git a/dispd/client/library.c b/display/damage-rect.h similarity index 70% rename from dispd/client/library.c rename to display/damage-rect.h index 6dc858f9..fa469845 100644 --- a/dispd/client/library.c +++ b/display/damage-rect.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Jonas 'Sortie' Termansen. + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,21 +13,23 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * library.c - * Main entry point of the Sortix Display Daemon. + * damage-rect.h + * Damage rectangles. */ -#include +#ifndef DAMAGE_RECT_H +#define DAMAGE_RECT_H + #include -#include -#include - -#include "session.h" - -bool dispd_initialize(int* argc, char*** argv) +struct damage_rect { - if ( !dispd__session_initialize(argc, argv) ) - return false; - return true; -} + size_t left; + size_t top; + size_t width; + size_t height; +}; + +struct damage_rect damage_rect_add(struct damage_rect a, struct damage_rect b); + +#endif diff --git a/display/display-code.c b/display/display-code.c new file mode 100644 index 00000000..ddd149fc --- /dev/null +++ b/display/display-code.c @@ -0,0 +1,917 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2018, 2022, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * display-code.c + * Display server logic. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "connection.h" +#include "display.h" +#include "pixel.h" +#include "vgafont.h" +#include "window.h" + +extern struct framebuffer arrow_framebuffer; + +void display_initialize(struct display* display) +{ + memset(display, 0, sizeof(*display)); + display->redraw = true; +} + +void assert_is_well_formed_display_list(struct display* display) +{ + struct window* last_window = NULL; + struct window* iterator = display->bottom_window; + while ( iterator ) + { + assert(iterator->below_window == last_window); + last_window = iterator; + iterator = iterator->above_window; + } + assert(last_window == display->top_window); +} + +void assert_is_well_formed_display(struct display* display) +{ + assert_is_well_formed_display_list(display); + + bool found_active_window = display->active_window == NULL; + bool found_tab_candidate = display->tab_candidate == NULL; + struct window* iterator = display->bottom_window; + while ( iterator ) + { + if ( iterator == display->active_window ) + found_active_window = true; + if ( iterator == display->tab_candidate ) + found_tab_candidate = true; + iterator = iterator->above_window; + } + assert(found_active_window); + assert(found_tab_candidate); +} + +void display_link_window_at_top(struct display* display, struct window* window) +{ + assert_is_well_formed_display_list(display); + + assert(!window->above_window); + assert(!window->below_window); + assert(display->top_window != window); + assert(display->bottom_window != window); + + if ( (window->below_window = display->top_window) ) + window->below_window->above_window = window; + window->above_window = NULL; + + display->top_window = window; + if ( !display->bottom_window ) + display->bottom_window = window; + + assert_is_well_formed_display_list(display); + + display_schedule_redraw(display); +} + +void display_unlink_window(struct display* display, struct window* window) +{ + assert_is_well_formed_display_list(display); + + assert(window->below_window || display->bottom_window == window); + assert(window->above_window || display->top_window == window); + + if ( window->below_window ) + window->below_window->above_window = window->above_window; + else + display->bottom_window = window->above_window; + if ( window->above_window ) + window->above_window->below_window = window->below_window; + else + display->top_window = window->below_window; + + assert(display->bottom_window != window); + assert(display->top_window != window); + + window->above_window = NULL; + window->below_window = NULL; + + assert_is_well_formed_display_list(display); + + display_schedule_redraw(display); +} + +void display_unlink_window_removal(struct display* display, + struct window* window) +{ + assert_is_well_formed_display_list(display); + + if ( display->tab_candidate == window ) + if ( !(display->tab_candidate = window->below_window) ) + if ( (display->tab_candidate = display->top_window) == window ) + display->tab_candidate = NULL; + + if ( display->active_window == window ) + display->active_window = NULL; + + window->focus = false; + display_schedule_redraw(display); + + assert_is_well_formed_display_list(display); + + display_unlink_window(display, window); + + assert_is_well_formed_display_list(display); +} + +void display_unmark_active_window(struct display* display, + struct window* window) +{ + assert(display->active_window == window); + window->focus = false; + display->active_window = NULL; + window_render_frame(window); +} + +void display_mark_active_window(struct display* display, struct window* window) +{ + assert(!display->active_window); + window->focus = true; + display->active_window = window; + window_render_frame(window); +} + +void display_update_active_window(struct display* display) +{ + if ( !display->active_window && display->top_window ) + display_mark_active_window(display, display->top_window); +} + +void display_move_window_to_top(struct display* display, struct window* window) +{ + display_unlink_window(display, window); + display_link_window_at_top(display, window); +} + +void display_change_active_window(struct display* display, + struct window* window) +{ + if ( display->active_window == window ) + { + display_move_window_to_top(display, window); + return; + } + + display_unmark_active_window(display, display->active_window); + display_mark_active_window(display, window); +} + +void display_set_active_window(struct display* display, struct window* window) +{ + display_change_active_window(display, window); + display_move_window_to_top(display, window); +} + +void display_add_window(struct display* display, struct window* window) +{ + display_link_window_at_top(display, window); + display_update_active_window(display); +} + +void display_remove_window(struct display* display, struct window* window) +{ + display_unlink_window_removal(display, window); + display_update_active_window(display); + + assert(display->top_window != window); + assert(display->bottom_window != window); + struct window* last_window = NULL; + struct window* iterator = display->bottom_window; + while ( iterator ) + { + assert(iterator != window); + assert(iterator->below_window == last_window); + last_window = iterator; + iterator = iterator->above_window; + } + assert(last_window == display->top_window); + + assert(!display->top_window || display->active_window); +} + +union c { struct { uint8_t b; uint8_t g; uint8_t r; }; uint32_t v; }; + +static void wallpaper(struct framebuffer fb) +{ + static uint32_t s; + static uint32_t t; + static bool seeded = false; + if ( !seeded ) + { + s = arc4random(); + t = arc4random(); + seeded = true; + } + for ( size_t y = 0; y < fb.yres; y++ ) + { + for ( size_t x = 0; x < fb.xres; x++ ) + { + uint32_t r = 3793 * x + 6959 * y + 1889 * t + 7901 * s; + r ^= (5717 * x * 2953 * y) ^ s ^ t; + r = (r >> 24) ^ (r >> 16) ^ (r >> 8) ^ r; + union c c; + if ( x && (r & 0x3) == 2 ) + c.v = framebuffer_get_pixel(fb, x - 1, y); + else if ( y && (r & 0x3) == 1 ) + c.v = framebuffer_get_pixel(fb, x, y - 1); + else if ( x && y ) + c.v = framebuffer_get_pixel(fb, x - 1, y - 1); + else + { + c.v = t; + c.r = (c.r & 0xc0) | (r >> 0 & 0x3f); + c.g = (c.g & 0xc0) | (r >> 4 & 0x3f); + c.b = (c.b & 0xc0) | (r >> 8 & 0x3f); + } + if ( (r & 0xf0) == 0x10 && c.r ) c.r--; + if ( (r & 0xf0) == 0x20 && c.g ) c.g--; + if ( (r & 0xf0) == 0x30 && c.b ) c.b--; + if ( (r & 0xf0) == 0x40 && c.r != 255 ) c.r++; + if ( (r & 0xf0) == 0x50 && c.g != 255 ) c.g++; + if ( (r & 0xf0) == 0x60 && c.b != 255 ) c.b++; + union c tc = {.v = t}; + if ( c.r && c.r - tc.r > (int8_t) (r >> 0) + 64 ) c.r--; + if ( c.r != 255 && tc.r - c.r > (int8_t) (r >> 4) + 240 ) c.r++; + if ( c.g && c.g - tc.g > (int8_t) (r >> 8) + 64) c.g--; + if ( c.g != 255 && tc.g - c.g > (int8_t) (r >> 12) + 240 ) c.g++; + if ( c.b && c.b - tc.b > (int8_t) (r >> 16) + 64 ) c.b--; + if ( c.b != 255 && tc.b - c.b > (int8_t) (r >> 20) + 240 ) c.b++; + framebuffer_set_pixel(fb, x, y, c.v); + } + } +} + +void display_composit(struct display* display, struct framebuffer fb) +{ + struct damage_rect damage_rect = display->damage_rect; + damage_rect.left = 0; + damage_rect.top = 0; + damage_rect.width = fb.xres; + damage_rect.height = fb.yres; + if ( !damage_rect.width || !damage_rect.height ) + return; + +#if 0 + uint32_t bg_color = make_color(0x89 * 2/3, 0xc7 * 2/3, 0xff * 2/3); + for ( size_t y = 0; y < damage_rect.height; y++ ) + for ( size_t x = 0; x < damage_rect.width; x++ ) + framebuffer_set_pixel(fb, damage_rect.left + x, damage_rect.top + y, + bg_color); +#endif + + framebuffer_copy_to_framebuffer(fb, display->wallpaper); + + for ( struct window* window = display->bottom_window; + window; + window = window->above_window ) + { + if ( !window->show ) + continue; + + size_t winfb_left; + size_t winfb_top; + struct framebuffer winfb = window->buffer; + + if ( window->left < 0 ) + { + winfb_left = 0; + winfb = framebuffer_crop(winfb, -window->left, 0, + winfb.xres, winfb.yres); + } + else + winfb_left = window->left; + + if ( window->top < 0 ) + { + winfb_top = 0; + winfb = framebuffer_crop(winfb, 0, -window->top, + winfb.xres, winfb.yres); + } + else + winfb_top = window->top; + + size_t winfb_width = winfb.xres; + size_t winfb_height = winfb.yres; + +#if 0 + if ( winfb_left < damage_rect.left && + winfb_width < damage_rect.left - winfb_left ) + continue; + if ( winfb_left < damage_rect.left ) + { + winfb_left = damage_rect.left; + winfb_width -= damage_rect.left - winfb_left; + } +#endif + + struct framebuffer fb_dst = + framebuffer_crop(fb, winfb_left, winfb_top, + winfb_width, winfb_height); + + framebuffer_copy_to_framebuffer_blend(fb_dst, winfb); + } + + const char* cursor_text = NULL; + switch ( display->mouse_state ) + { + case MOUSE_STATE_NONE: break; + case MOUSE_STATE_IGNORE: break; + case MOUSE_STATE_BUTTON_PRESS: break; + case MOUSE_STATE_TITLE_MOVE: break; + case MOUSE_STATE_RESIZE_BOTTOM: cursor_text = "↓"; break; + case MOUSE_STATE_RESIZE_BOTTOM_LEFT: cursor_text = "└"; break; + case MOUSE_STATE_RESIZE_BOTTOM_RIGHT: cursor_text = "┘"; break; + case MOUSE_STATE_RESIZE_LEFT: cursor_text = "←"; break; + case MOUSE_STATE_RESIZE_RIGHT: cursor_text = "→"; break; + case MOUSE_STATE_RESIZE_TOP: cursor_text = "↑"; break; + case MOUSE_STATE_RESIZE_TOP_LEFT: cursor_text = "┌"; break; + case MOUSE_STATE_RESIZE_TOP_RIGHT: cursor_text = "┐"; break; + } + + int pointer_hwidth = arrow_framebuffer.xres / 2; + int pointer_hheight = arrow_framebuffer.yres / 2; + + int pointer_x = display->pointer_x - (cursor_text ? 0 : pointer_hwidth); + int pointer_y = display->pointer_y - (cursor_text ? 0 : pointer_hheight); + + struct framebuffer arrow_render = arrow_framebuffer; + if ( pointer_x < 0 ) + { + arrow_render = framebuffer_crop(arrow_render, -pointer_x, 0, + arrow_render.xres, arrow_render.yres); + pointer_x = 0; + } + if ( pointer_y < 0 ) + { + arrow_render = framebuffer_crop(arrow_render, 0, -pointer_y, + arrow_render.xres, arrow_render.yres); + pointer_y = 0; + } + + struct framebuffer fb_dst = + framebuffer_crop(fb, pointer_x, pointer_y, fb.xres, fb.yres); + + if ( cursor_text != NULL ) + render_text(fb_dst, cursor_text, make_color(0, 0, 0)); + else + framebuffer_copy_to_framebuffer_blend(fb_dst, arrow_render); + + memset(&damage_rect, 0, sizeof(damage_rect)); +} + +void display_schedule_redraw(struct display* display) +{ + display->redraw = true; +} + +void display_render(struct display* display) +{ + if ( !display->redraw ) + return; + display->redraw = false; + + struct dispmsg_get_crtc_mode get_mode_msg = {0}; + get_mode_msg.msgid = DISPMSG_GET_CRTC_MODE; + get_mode_msg.device = display->display.device; + get_mode_msg.connector = display->display.connector; + if ( dispmsg_issue(&get_mode_msg, sizeof(get_mode_msg)) != 0 ) + err(1, "dispmsg_issue: dispmsg_get_crtc_mode"); + struct dispmsg_crtc_mode mode = get_mode_msg.mode; + + if ( !(mode.control & DISPMSG_CONTROL_VALID) ) + errx(1, "No valid video mode was set"); + if ( mode.control & DISPMSG_CONTROL_VGA ) + errx(1, "A VGA text mode was set"); + if ( mode.fb_format != 32 ) + errx(1, "A 32-bit video mode wasn't set"); + + size_t framebuffer_length = mode.view_xres * mode.view_yres; + size_t framebuffer_size = sizeof(uint32_t) * framebuffer_length; + + if ( display->fb_size != framebuffer_size ) + { + display->fb.buffer = realloc(display->fb.buffer, framebuffer_size); + display->fb.xres = mode.view_xres; + display->fb.yres = mode.view_yres; + display->fb.pitch = mode.view_xres; + display->fb_size = framebuffer_size; + } + + if ( display->wallpaper_size != framebuffer_size ) + { + display->wallpaper.buffer = + realloc(display->wallpaper.buffer, framebuffer_size); + display->wallpaper.xres = mode.view_xres; + display->wallpaper.yres = mode.view_yres; + display->wallpaper.pitch = mode.view_xres; + display->wallpaper_size = framebuffer_size; + } + + display_on_resolution_change(display, mode.view_xres, mode.view_yres); + + display_composit(display, display->fb); + + struct dispmsg_write_memory write_memory_msg = {0}; + write_memory_msg.msgid = DISPMSG_WRITE_MEMORY; + write_memory_msg.device = display->display.device; + write_memory_msg.size = framebuffer_size; + write_memory_msg.src = (uint8_t*) display->fb.buffer; + if ( dispmsg_issue(&write_memory_msg, sizeof(write_memory_msg)) != 0 ) + err(1, "dispmsg_issue: dispmsg_write_memory"); +} + +void display_keyboard_event(struct display* display, uint32_t codepoint) +{ + struct window* window = display->active_window; + + int kbkey = KBKEY_DECODE(codepoint); + int abskbkey = kbkey < 0 ? -kbkey : kbkey; + + if ( kbkey && (!window || !window->grab_input) ) + { + switch ( abskbkey ) + { + case KBKEY_LCTRL: display->key_lctrl = kbkey > 0; break; + case KBKEY_LALT: display->key_lalt = kbkey > 0; break; + case KBKEY_LSUPER: display->key_lsuper = kbkey > 0; break; + case KBKEY_RSUPER: display->key_rsuper = kbkey > 0; break; + } + if ( display->key_lctrl && display->key_lalt && kbkey == KBKEY_DELETE ) + exit(0); + if ( display->key_lctrl && display->key_lalt && kbkey == KBKEY_T ) + { + if ( !fork() ) + { + execlp("terminal", "terminal", (char*) NULL); + _exit(127); + } + return; + } + else if ( display->key_lctrl && display->key_lalt && kbkey == -KBKEY_T ) + return; + } + + if ( kbkey && window && !window->grab_input ) + { + // TODO: Ctrl+Q when termninal has a way of handling it or not. + if ( (display->key_lalt && kbkey == KBKEY_F4) /* || + (display->key_lctrl && kbkey == KBKEY_Q)*/ ) + { + window_quit(window); + return; + } + + if ( display->key_lalt && kbkey == KBKEY_F10 ) + { + window_toggle_maximized(display->active_window); + return; + } + + if ( display->key_lalt && kbkey == KBKEY_TAB ) + { + if ( !display->tab_candidate ) + display->tab_candidate = display->active_window; + struct window* old_candidate = display->tab_candidate; + if ( !(display->tab_candidate = old_candidate->below_window) ) + display->tab_candidate = display->top_window; + window_render_frame(old_candidate); + window_render_frame(display->tab_candidate); + return; + } + + if ( kbkey == -KBKEY_LALT && display->tab_candidate ) + { + if ( display->tab_candidate != display->active_window ) + display_set_active_window(display, display->tab_candidate); + display->tab_candidate = NULL; + window = display->active_window; + return; + } + if ( display->key_lsuper || display->key_lsuper ) + { + struct window* window = display->active_window; + if ( kbkey == KBKEY_LEFT ) + { + window_tile_leftward(window); + return; + } + if ( kbkey == KBKEY_RIGHT ) + { + window_tile_rightward(window); + return; + } + if ( kbkey == KBKEY_UP ) + { + window_tile_up(window); + return; + } + if ( kbkey == KBKEY_DOWN ) + { + window_tile_down(window); + return; + } + } + } + + const char* grab_inputbed_string = " - Input Grabbed"; + if ( kbkey == KBKEY_F11 && window && !window->grab_input ) + { + // TODO: window->title can be null. + char* new_title; + if ( 0 <= asprintf(&new_title, "%s%s", window->title, + grab_inputbed_string) ) + { + window->grab_input = true; + free(window->title); + window->title = new_title; + window_render_frame(window); + } + return; + } + if ( kbkey == KBKEY_F12 && window && window->grab_input ) + { + // TODO: Only remove from title if there. + size_t grab_inputbed_string_len = strlen(grab_inputbed_string); + window->title[strlen(window->title) - grab_inputbed_string_len] = '\0'; + window->grab_input = false; + window_render_frame(window); + return; + } + + if ( !window ) + return; + + struct event_keyboard event; + event.window_id = display->active_window->window_id; + event.codepoint = codepoint; + + struct display_packet_header header; + header.id = EVENT_KEYBOARD; + header.size = sizeof(event); + + assert(window->connection); + + connection_schedule_transmit(window->connection, &header, sizeof(header)); + connection_schedule_transmit(window->connection, &event, sizeof(event)); +} + +void display_mouse_event(struct display* display, uint8_t byte) +{ + if ( display->mouse_byte_count == 0 && !(byte & MOUSE_ALWAYS_1) ) + return; + if ( display->mouse_byte_count < MOUSE_PACKET_SIZE ) + display->mouse_bytes[display->mouse_byte_count++] = byte; + if ( display->mouse_byte_count < MOUSE_PACKET_SIZE ) + return; + display->mouse_byte_count = 0; + uint8_t* bytes = display->mouse_bytes; + + int xm = MOUSE_X(bytes); + int ym = MOUSE_Y(bytes); + + int old_pointer_x = display->pointer_x; + int old_pointer_y = display->pointer_y; + + if ( xm*xm + ym*ym >= 2*2 ) + { + xm *= 2; + ym *= 2; + } + else if ( xm*xm + ym*ym >= 5*5 ) + { + xm *= 3; + ym *= 3; + } + display->pointer_x += xm; + display->pointer_y += ym; + if ( xm || ym ) + display_schedule_redraw(display); + + bool clipped_edge = false; + if ( display->pointer_x < 0 ) + { + display->pointer_x = 0; + clipped_edge = true; + } + if ( display->pointer_y < 0 ) + { + display->pointer_y = 0; + clipped_edge = true; + } + if ( display->screen_width < (size_t) display->pointer_x ) + { + display->pointer_x = display->screen_width; + clipped_edge = true; + } + if ( display->screen_height < (size_t) display->pointer_y ) + { + display->pointer_y = display->screen_height; + clipped_edge = true; + } + xm = display->pointer_x - old_pointer_x; + ym = display->pointer_y - old_pointer_y; + + struct window* window; + for ( window = display->top_window; window; window = window->below_window ) + { + if ( display->mouse_state != MOUSE_STATE_NONE ) + break; + int grace = RESIZE_GRACE; + if ( window->window_state == WINDOW_STATE_MAXIMIZED ) + grace = 0; + if ( old_pointer_x < window->left - grace ) + continue; + if ( old_pointer_y < window->top - grace ) + continue; + if ( old_pointer_x > window->left + (ssize_t) window->width + grace ) + continue; + if ( old_pointer_y > window->top + (ssize_t) window->height + grace) + continue; + break; + } + if ( !window ) + return; + + ssize_t window_pointer_x = display->pointer_x - window->left; + ssize_t window_pointer_y = display->pointer_y - window->top; + + if ( display->active_window != window ) + { + if ( bytes[0] & (MOUSE_BUTTON_LEFT | MOUSE_BUTTON_MIDDLE | + MOUSE_BUTTON_RIGHT) ) + { + // TODO: Exit mouse from the current window. + display_set_active_window(display, window); + } + else + return; + } + + bool maximized = window->window_state != WINDOW_STATE_REGULAR; + + int b2 = 2; + int t0 = TITLE_HEIGHT; + + size_t border_width = maximized ? 0 : b2 + 1; + size_t button_area_height = maximized ? t0 : t0 - (b2 + 1); + size_t button_area_width = button_area_height; + size_t button_area_top = maximized ? 0 : b2; + ssize_t buttons_x = window->width - border_width - button_area_width*3 + 1; + + bool mouse_on_title = 0 <= window_pointer_x && + window_pointer_x < (ssize_t) window->width && + 0 <= window_pointer_y && + window_pointer_y <= (ssize_t) TITLE_HEIGHT; + + for ( size_t n = 0; n < 3; n++ ) + { + ssize_t bottom = button_area_top + button_area_height; + ssize_t left = button_area_width * n; + ssize_t right = button_area_width * (n + 1); + if ( (ssize_t) button_area_top <= window_pointer_y && + window_pointer_y <= bottom && + left <= window_pointer_x - buttons_x && + window_pointer_x - buttons_x < right ) + { + if ( display->mouse_state == MOUSE_STATE_NONE && + (bytes[0] & MOUSE_BUTTON_LEFT) ) + { + display->mouse_state = MOUSE_STATE_BUTTON_PRESS; + window->button_states[n] = BUTTON_STATE_PRESSED; + window_render_frame(window); + } + else if ( display->mouse_state == MOUSE_STATE_BUTTON_PRESS && + window->button_states[n] == BUTTON_STATE_PRESSED && + !(bytes[0] & MOUSE_BUTTON_LEFT) ) + { + window->button_states[n] = BUTTON_STATE_NORMAL; + window_render_frame(window); + switch ( n ) + { + case 0: /* TODO: Minimize. */ break; + case 1: window_toggle_maximized(window); break; + case 2: window_quit(window); break; + } + return; + } + else if ( display->mouse_state == MOUSE_STATE_NONE && + window->button_states[n] != BUTTON_STATE_HOVER ) + { + window->button_states[n] = BUTTON_STATE_HOVER; + window_render_frame(window); + } + } + else if ( window->button_states[n] != BUTTON_STATE_NORMAL ) + { + window->button_states[n] = BUTTON_STATE_NORMAL; + if ( display->mouse_state != MOUSE_STATE_NONE ) + display->mouse_state = MOUSE_STATE_IGNORE; + window_render_frame(window); + } + } + + struct timespec double_click = timespec_make(0, 500000000); + + if ( bytes[0] & MOUSE_BUTTON_LEFT ) + { + if ( (display->mouse_state == MOUSE_STATE_NONE) ) + { + // TODO: Stay in state until mouse release. + if ( display->key_lalt ) + display->mouse_state = MOUSE_STATE_TITLE_MOVE; + else if ( mouse_on_title && window_pointer_x < buttons_x ) + { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + struct timespec elapsed = + timespec_sub(now, window->title_click_time); + if ( 0 <= window->title_click_time.tv_sec && + timespec_le(elapsed, double_click) ) + { + display->mouse_state = MOUSE_STATE_IGNORE; + window_toggle_maximized(window); + } + else + { + // TODO: Reset this if clicked anywhere else or if the + // active window changes. + window->title_click_time = now; + display->mouse_state = MOUSE_STATE_TITLE_MOVE; + } + } + else if ( window_pointer_x < 0 && window_pointer_y < 0 ) + display->mouse_state = MOUSE_STATE_RESIZE_TOP_LEFT; + else if ( window_pointer_x < 0 && + 0 <= window_pointer_y && + window_pointer_y < (ssize_t) window->height ) + display->mouse_state = MOUSE_STATE_RESIZE_LEFT; + else if ( window_pointer_x < 0 && + (ssize_t) window->height <= window_pointer_y ) + display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM_LEFT; + else if ( 0 <= window_pointer_x && + window_pointer_x < (ssize_t) window->width && + window_pointer_y < 0 ) + display->mouse_state = MOUSE_STATE_RESIZE_TOP; + else if ( 0 <= window_pointer_x && + window_pointer_x < (ssize_t) window->width && + (ssize_t) window->height < window_pointer_y ) + display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM; + else if ( (ssize_t) window->width <= window_pointer_x && + window_pointer_y < 0 ) + display->mouse_state = MOUSE_STATE_RESIZE_TOP_RIGHT; + else if ( (ssize_t) window->width < window_pointer_x && + 0 <= window_pointer_y && + window_pointer_y < (ssize_t) window->height ) + display->mouse_state = MOUSE_STATE_RESIZE_RIGHT; + else if ( (ssize_t) window->width <= window_pointer_x && + (ssize_t) window->height <= window_pointer_y ) + display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM_RIGHT; + if ( display->mouse_state != MOUSE_STATE_NONE && + display->mouse_state != MOUSE_STATE_IGNORE ) + display_schedule_redraw(display); + } + if ( xm || ym ) + { + bool floating = window->window_state == WINDOW_STATE_REGULAR; + bool on_edge = + display->pointer_x == 0 || + display->pointer_y == 0 || + display->pointer_x == (ssize_t) display->screen_width || + display->pointer_y == (ssize_t) display->screen_height; + switch ( display->mouse_state ) + { + case MOUSE_STATE_NONE: break; + case MOUSE_STATE_IGNORE: break; + case MOUSE_STATE_BUTTON_PRESS: break; + case MOUSE_STATE_TITLE_MOVE: + if ( clipped_edge ) + { + ssize_t x = display->pointer_x; + ssize_t y = display->pointer_y; + ssize_t sw = display->screen_width; + ssize_t sh = display->screen_height; + ssize_t corner_size = (sw < sh ? sw : sh) / 4; + if ( x < corner_size && y < corner_size ) + window_tile_top_left(window); + else if ( sw - x < corner_size && y < corner_size ) + window_tile_top_right(window); + else if ( x < corner_size && sh - y < corner_size ) + window_tile_bottom_left(window); + else if ( sw - x < corner_size && sh - y < corner_size ) + window_tile_bottom_right(window); + else if ( x == 0 ) + window_tile_left(window); + else if ( x == sw ) + window_tile_right(window); + else if ( y == 0 ) + window_tile_top(window); + else if ( y == sh ) + window_tile_bottom(window); + } + else if ( floating || !on_edge ) + { + if ( !floating ) + { + // The current behaviour of window_restore becomes + // awkward with tiling gestures. I could change the + // function itself, especially since this is currently + // its only callsite, but the old behaviour could be + // nice for a future untile hotkey. Thus, this hack. + window_restore(window); + window->top = display->pointer_y - TITLE_HEIGHT / 2; + window->left = display->pointer_x - window->width / 2; + } + window_move(window, window->left + xm, window->top + ym); + } + break; + case MOUSE_STATE_RESIZE_TOP_LEFT: + window_drag_resize(window, xm, ym, -xm, -ym); + break; + case MOUSE_STATE_RESIZE_LEFT: + window_drag_resize(window, xm, 0, -xm, 0); + break; + case MOUSE_STATE_RESIZE_BOTTOM_LEFT: + window_drag_resize(window, xm, 0, -xm, ym); + break; + case MOUSE_STATE_RESIZE_TOP: + window_drag_resize(window, 0, ym, 0, -ym); + break; + case MOUSE_STATE_RESIZE_BOTTOM: + window_drag_resize(window, 0, 0, 0, ym); + break; + case MOUSE_STATE_RESIZE_TOP_RIGHT: + window_drag_resize(window, 0, ym, xm, -ym); + break; + case MOUSE_STATE_RESIZE_RIGHT: + window_drag_resize(window, 0, 0, xm, 0); + break; + case MOUSE_STATE_RESIZE_BOTTOM_RIGHT: + window_drag_resize(window, 0, 0, xm, ym); + break; + } + } + + // TODO: Leave mouse state if the top window closes. + // TODO: Leave mouse state if the top window is switched. + } + else if ( display->mouse_state != MOUSE_STATE_NONE ) + { + display->mouse_state = MOUSE_STATE_NONE; + display_schedule_redraw(display); + } +} + +void display_on_resolution_change(struct display* display, size_t width, + size_t height) +{ + if ( display->screen_width == width && display->screen_height == height ) + return; + display->screen_width = width; + display->screen_height = height; + display->pointer_x = width / 2; + display->pointer_y = height / 2; + for ( struct window* window = display->bottom_window; + window; + window = window->above_window ) + window_on_display_resolution_change(window, display); + wallpaper(display->wallpaper); +} diff --git a/display/display.1 b/display/display.1 new file mode 100644 index 00000000..bd82cc63 --- /dev/null +++ b/display/display.1 @@ -0,0 +1,182 @@ +.Dd June 11, 2023 +.Dt DISPLAY 1 +.Os +.Sh NAME +.Nm display +.Nd desktop environment +.Sh SYNOPSIS +.Nm +.Op Ar session ... +.Sh DESCRIPTION +.Nm +is a desktop environment and windowing system compositor. +Applications talk to the +.Nm +server process to receive user input and show their graphical user interfaces +in windows. +.Pp +The user's preferred startup applications are launched on startup by launching +the +.Xr session +program (if set) or otherwise the +.Xr displayrc 5 +script in the background. +.Pp +.Nm +exits when Control + Alt + Delete is pressed. +.Pp +The options are as follows: +.Bl -tag -width "12345678" +.It Fl m Ar mouse +Use +.Pa mouse +device instead of +.Pa /dev/mouse . +.It Fl t Ar tty +Use +.Pa tty +device instead of +.Pa /dev/tty . +.It Fl s Ar socket +Listen on +.Pa socket +instead of +.Pa /var/run/display . +.El +.Pp +The keyboard shortcuts are as follows: +.Bl -tag -width "Control + Alt + Delete" +.It Alt + F4 +Quit the current window. +.It Alt + F10 +Maximize (or restore) the current window. +.It Alt + Tab +Switch to the next window. +.It Alt + Drag +Drag the current window. +.It Control + Alt + Delete +Exit the desktop environment. +.It Control + Alt + T +Launch the +.Xr terminal 1 +application. +.It Super + Left +Tile the current window leftwards. +.It Super + Right +Tile the current window rightwards. +.It Super + Up +Tile the current window upwards. +.It Super + Down +Tile the current window downwards. +.It F11 +Grab input for the current window. +.It F12 +Release the input grab on the current window. +.El +.Pp +The mouse gestures are as follow: +.Bl -bullet +.It +Clicking on a window brings it to the foreground. +.It +Dragging the window title bar moves the window. +.It +Double clicking on the window title bar maximizes (or restores) the window. +.It +Clicking on the rectangle icon in the title bar maximizes (or restores) the +window. +.It +Clicking on the X icon in the title bar closes the window. +.It +Dragging the edges of a window resizes it. +.It +Windows can be tiled by moving them when the cursor meets the left, right, top, +and bottom edges or any corner. +.El +.Pp +The keyboard layout can be changed with the +.Xr chkblayout 1 +program. +The display resolution can be changed with the +.Xr chvideomode 1 +program. +.Sh ENVIRONMENT +.Bl -tag -width "DISPLAY_SOCKET" +.It Ev DISPLAY_SOCKET +.Nm +sets +.Ev DISPLAY_SOCKET +to the path of the +.Xr unix 4 +socket where it listens for connections from applications. +Applications use +.Ev DISPLAY_SOCKET +to connect to +.Nm +or +.Pa /var/run/display +by default. +.El +.Sh FILES +.Bl -tag -width 12345678 -compact +.It Pa ~/.displayrc , /etc/displayrc , /etc/default/displayrc +.Xr displayrc 5 +script that spawns the user's preferred startup applications. +.It Pa /var/run/display +.Xr unix 4 +socket where +.Nm +listens for connections from applications, as advertised in the +.Ev DISPLAY_SOCKET +environment variable. +.El +.Sh ASYNCHRONOUS EVENTS +.Bl -tag -width "SIGTERM" +.It Dv SIGTERM +Request daemon termination. +.El +.Sh EXIT STATUS +.Nm +runs as a +.Xr daemon 7 +until stopped by +.Dv SIGTERM , +the user explicitly exits the desktop environment, or an application asks +it to exit. +.Nm +signals readiness on the +.Ev READYFD +file descriptor when the display server is ready to receive connections from +applications. +.Nm +will exit non-zero on any fatal startup error. +.Sh EXAMPLES +.Nm +can be selected as the user's graphical user interface with this executable +.Pa ~/.session +script: +.Bd -literal -offset indent +#!/bin/sh +exec display +.Ed +.Pp +.Xr chkblayout 1 , +.Xr chvideomode 1 , +.Xr display 1 +will run the +.Xr displayrc 5 +script on startup, which can be used to start applications. +.Sh SEE ALSO +.Xr terminal 1 , +.Xr displayrc 5 , +.Xr session 5 +.Sh BUGS +The following features are not yet implemented: +.Bl -bullet -compact +.It +Windows cannot be minimized. +.It +Applications cannot receive mouse events. +.It +The wallpaper is random and cannot be controlled. +.El diff --git a/display/display.c b/display/display.c new file mode 100644 index 00000000..2c01975c --- /dev/null +++ b/display/display.c @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017, 2022, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * display.c + * Display server. + */ + +#include +#include +#include +#include +#include +#include + +#include "arrow.inc" + +#include "display.h" +#include "framebuffer.h" +#include "server.h" + +uint32_t arrow_buffer[48 * 48]; +struct framebuffer arrow_framebuffer = { 48, arrow_buffer, 48, 48 }; + +static void ready(void) +{ + const char* readyfd_env = getenv("READYFD"); + if ( !readyfd_env ) + return; + int readyfd = atoi(readyfd_env); + char c = '\n'; + write(readyfd, &c, 1); + close(readyfd); + unsetenv("READYFD"); +} + +int main(int argc, char* argv[]) +{ + const char* mouse = "/dev/mouse"; + const char* socket = "/var/run/display"; + const char* tty = NULL; + + int opt; + while ( (opt = getopt(argc, argv, "m:s:t:")) != -1 ) + { + switch ( opt ) + { + case 'm': mouse = optarg; break; + case 's': socket = optarg; break; + case 't': tty = optarg; break; + default: return 1; + } + } + + memcpy(arrow_buffer, arrow, sizeof(arrow)); + + setlocale(LC_ALL, ""); + setvbuf(stdout, NULL, _IOLBF, 0); + + if ( getpgid(0) != getpid() ) + errx(1, "This program must be run in its own process group"); + + struct display display; + display_initialize(&display); + + struct server server; + server_initialize(&server, &display, tty, mouse, socket); + + if ( setenv("DISPLAY_SOCKET", server.server_path, 1) < 0 ) + err(1, "setenv"); + + ready(); + + char* home_session = NULL; + char** session_argv = NULL; + if ( optind < argc ) + session_argv = argv + optind; + else + { + const char* home = getenv("HOME"); + if ( home && asprintf(&home_session, "%s/.displayrc", home) < 0 ) + err(1, "malloc"); + const char* session_path = NULL; + if ( !access(home_session, F_OK) ) + session_path = home_session; + else if ( !access("/etc/displayrc", F_OK) ) + session_path = "/etc/displayrc"; + else if ( !access("/etc/default/displayrc", F_OK) ) + session_path = "/etc/default/displayrc"; + if ( session_path ) + session_argv = (char**) (const char*[]) {session_path, NULL}; + } + + if ( session_argv ) + { + pid_t pid = fork(); + if ( pid < 0 ) + warn("fork"); + else if ( pid == 0 ) + { + execvp(session_argv[0], session_argv); + warn("%s", session_argv[0]); + _exit(127); + } + } + + free(home_session); + + server_mainloop(&server); + + return 0; +} diff --git a/display/display.h b/display/display.h new file mode 100644 index 00000000..bac429d6 --- /dev/null +++ b/display/display.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2022, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * display.h + * Display server. + */ + +#ifndef DISPLAY_H +#define DISPLAY_H + +#include +#include + +#include +#include + +#include "damage-rect.h" +#include "framebuffer.h" + +enum mouse_state +{ + MOUSE_STATE_NONE, + MOUSE_STATE_IGNORE, + MOUSE_STATE_BUTTON_PRESS, + MOUSE_STATE_TITLE_MOVE, + MOUSE_STATE_RESIZE_BOTTOM, + MOUSE_STATE_RESIZE_BOTTOM_LEFT, + MOUSE_STATE_RESIZE_BOTTOM_RIGHT, + MOUSE_STATE_RESIZE_LEFT, + MOUSE_STATE_RESIZE_RIGHT, + MOUSE_STATE_RESIZE_TOP, + MOUSE_STATE_RESIZE_TOP_LEFT, + MOUSE_STATE_RESIZE_TOP_RIGHT, +}; + +struct window; + +struct display +{ + struct tiocgdisplay display; + struct framebuffer fb; + struct framebuffer wallpaper; + size_t fb_size; + size_t wallpaper_size; + struct damage_rect damage_rect; + struct window* top_window; + struct window* bottom_window; + struct window* active_window; + struct window* tab_candidate; + size_t screen_width; + size_t screen_height; + size_t num_tabs; + bool key_lctrl; + bool key_lalt; + bool key_lsuper; + bool key_rsuper; + bool redraw; + int pointer_x; + int pointer_y; + enum mouse_state mouse_state; + size_t mouse_byte_count; + uint8_t mouse_bytes[MOUSE_PACKET_SIZE]; +}; + +void display_initialize(struct display* display); +void assert_is_well_formed_display_list(struct display* display); +void assert_is_well_formed_display(struct display* display); +void display_link_window_at_top(struct display* display, struct window* window); +void display_unlink_window(struct display* display, struct window* window); +void display_unlink_window_removal(struct display* display, + struct window* window); +void display_unmark_active_window(struct display* display, + struct window* window); +void display_mark_active_window(struct display* display, struct window* window); +void display_update_active_window(struct display* display); +void display_move_window_to_top(struct display* display, struct window* window); +void display_change_active_window(struct display* display, + struct window* window); +void display_set_active_window(struct display* display, struct window* window); +void display_add_window(struct display* display, struct window* window); +void display_remove_window(struct display* display, struct window* window); +void display_composit(struct display* display, struct framebuffer fb); +void display_schedule_redraw(struct display* display); +void display_render(struct display* display); +void display_keyboard_event(struct display* display, uint32_t codepoint); +void display_on_resolution_change(struct display* display, size_t width, + size_t height); +void display_mouse_event(struct display* display, uint8_t byte); + +#endif diff --git a/display/displayrc.5 b/display/displayrc.5 new file mode 100644 index 00000000..e110f223 --- /dev/null +++ b/display/displayrc.5 @@ -0,0 +1,77 @@ +.Dd June 11, 2023 +.Dt DISPLAYRC 5 +.Os +.Sh NAME +.Nm displayrc +.Nd startup graphical applications +.Sh SYNOPSIS +.Nm ~/.displayrc +.Nm /etc/displayrc +.Nm /etc/default/displayrc +.Sh DESCRIPTION +.Xr display 1 +runs the +.Nm +script to launch the user's startup applications and prepare the desktop +environment according to the user's preferences. +.Pp +.Xr display 1 +continues running after +.Nm +finishes running and any launched applications should be run as background +processes. +.Pp +The +.Nm +script is found by searching for an executable script in the following paths: +.Bl -bullet -compact +.It +.Pa ~/.displayrc +.It +.Pa /etc/displayrc +.It +.Pa /etc/default/displayrc +.El +.Pp +.Nm +is not executed if no script is found. +.Sh ENVIRONMENT +.Nm +is executed with the following environment: +.Bl -tag -width "DISPLAY_SOCKET" +.It Ev DISPLAY_SOCKET +The path of the +.Xr unix 4 socket +where +.Xr display 1 +is listening for connections from applications. +.El +.Sh FILES +.Bl -tag -width "/etc/default/displayrc" -compact +.It Pa ~/.displayrc +The user's +.Nm +script. +.It Pa /etc/displayrc +The system administor provided +.Nm +script. +.It Pa /etc/default/displayrc +The operating system provided +.Nm +script. +.El +.Sh EXAMPLES +Launch a terminal with a text editor, another terminal with the user's default +shell, and launch the asteroids game. +.Bd -literal -offset indent +terminal editor & +terminal & +asteroids & +.Ed +.Sh SEE ALSO +.Xr display 1 , +.Xr terminal 1 , +.Xr profile 5 , +.Xr session 5 , +.Xr shrc 5 diff --git a/display/server.c b/display/server.c new file mode 100644 index 00000000..253ee595 --- /dev/null +++ b/display/server.c @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * server.c + * Display server main loop. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "connection.h" +#include "display.h" +#include "server.h" +#include "vgafont.h" + +static int open_local_server_socket(const char* path, int flags) +{ + size_t path_length = strlen(path); + size_t addr_size = offsetof(struct sockaddr_un, sun_path) + path_length + 1; + struct sockaddr_un* sockaddr = malloc(addr_size); + if ( !sockaddr ) + return -1; + sockaddr->sun_family = AF_LOCAL; + strcpy(sockaddr->sun_path, path); + int fd = socket(AF_LOCAL, SOCK_STREAM | flags, 0); + if ( fd < 0 ) + return free(sockaddr), -1; + if ( bind(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 ) + return close(fd), free(sockaddr), -1; + if ( listen(fd, SOMAXCONN) < 0 ) + return close(fd), free(sockaddr), -1; + free(sockaddr); + return fd; +} + +void server_initialize(struct server* server, struct display* display, + const char* tty, const char* mouse, const char* socket) +{ + memset(server, 0, sizeof(*server)); + + server->display = display; + + load_font(); + + server->tty_fd = 0; + if ( tty || !isatty(server->tty_fd) ) + { + tty = tty ? tty : "/dev/tty"; + server->tty_fd = open(tty, O_RDONLY); + if ( server->tty_fd < 0 ) + err(1, tty); + } + + // TODO: Support for multiple displays. + struct tiocgdisplays gdisplays = {0}; + gdisplays.count = 1; + gdisplays.displays = &display->display; + if ( ioctl(server->tty_fd, TIOCGDISPLAYS, &gdisplays) < 0 || + gdisplays.count == 0 ) + errx(1, "%s: No video devices are associated with this terminal", tty); + + server->mouse_fd = open(mouse, O_RDONLY | O_CLOEXEC); + if ( server->mouse_fd < 0 ) + err(1, "%s", mouse); + + server->server_path = socket; + server->server_fd = open_local_server_socket(server->server_path, + SOCK_NONBLOCK | SOCK_CLOEXEC); + if ( server->server_fd < 0 ) + err(1, "open_local_server_socket: %s", server->server_path); + + unsigned int termmode = + TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_NONBLOCK; + if ( settermmode(server->tty_fd, termmode) < 0 ) + err(1, "settermmode"); + + server->pfds_count = server_pfds_count(server); + server->pfds = + reallocarray(NULL, sizeof(struct pollfd), server->pfds_count); + if ( !server->pfds ) + err(1, "malloc"); +} + +bool server_accept(struct server* server) +{ + int client_fd = accept4(server->server_fd, NULL, NULL, SOCK_NONBLOCK); + if ( client_fd < 0 ) + { + warn("accept: %s", server->server_path); + return false; + } + + if ( server->connections_used == server->connections_length ) + { + size_t new_length = server->connections_length * 2; + if ( !new_length ) + new_length = 16; + struct connection** new_connections = + reallocarray(server->connections, new_length, + sizeof(struct connection*)); + if ( !new_connections ) + { + warn("dropped connection: %s: malloc", server->server_path); + close(client_fd); + return false; + } + server->connections = new_connections; + server->connections_length = new_length; + } + + size_t new_pfds_count = server_pfds_count(server) + 1; + struct pollfd* new_pfds = + reallocarray(server->pfds, sizeof(struct pollfd), new_pfds_count); + if ( !new_pfds ) + { + warn("dropped connection: %s: malloc", server->server_path); + close(client_fd); + return false; + } + server->pfds = new_pfds; + server->pfds_count = new_pfds_count; + + struct connection* connection = malloc(sizeof(struct connection)); + if ( !connection ) + { + warn("dropped connection: %s: malloc", server->server_path); + close(client_fd); + return false; + } + server->connections[server->connections_used++] = connection; + connection_initialize(connection, server->display, client_fd); + + return true; +} + +size_t server_pfds_count(const struct server* server) +{ + return 3 + server->connections_used; +} + +void server_poll(struct server* server) +{ + int code; + while ( 0 < waitpid(-1, &code, WNOHANG) ) + { + } + + struct pollfd* pfds = server->pfds; + + pfds[0].fd = server->server_fd; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + pfds[1].fd = server->tty_fd; + pfds[1].events = POLLIN; + pfds[1].revents = 0; + pfds[2].fd = server->mouse_fd; + pfds[2].events = POLLIN; + pfds[2].revents = 0; + size_t cpfd_off = 3; + + size_t connections_polled = server->connections_used; + for ( size_t i = 0; i < connections_polled; i++ ) + { + struct pollfd* pfd = &pfds[cpfd_off + i]; + struct connection* connection = server->connections[i]; + pfd->fd = connection->fd; + pfd->events = connection_interested_poll_events(connection); + pfd->revents = 0; + } + size_t pfds_used = cpfd_off + connections_polled; + + int num_events = ppoll(pfds, pfds_used, NULL, NULL); + if ( num_events < 0 ) + err(1, "poll"); + + if ( pfds[0].revents ) + { + // TODO: Handle if this can actually happen. + assert(!(pfds[0].revents & POLLERR)); + assert(!(pfds[0].revents & POLLHUP)); + assert(!(pfds[0].revents & POLLNVAL)); + + server_accept(server); + } + + if ( pfds[1].revents ) + { + // TODO: Handle if this can actually happen. + assert(!(pfds[1].revents & POLLERR)); + assert(!(pfds[1].revents & POLLHUP)); + assert(!(pfds[1].revents & POLLNVAL)); + + uint32_t codepoint; + ssize_t size = sizeof(codepoint); + while ( read(server->tty_fd, &codepoint, size) == size ) + display_keyboard_event(server->display, codepoint); + } + + if ( pfds[2].revents ) + { + // TODO: Handle if this can actually happen. + assert(!(pfds[2].revents & POLLERR)); + assert(!(pfds[2].revents & POLLHUP)); + assert(!(pfds[2].revents & POLLNVAL)); + + unsigned char events[64]; + ssize_t amount = read(server->mouse_fd, events, sizeof(events)); + for ( ssize_t i = 0; i < amount; i++ ) + display_mouse_event(server->display, events[i]); + } + + bool any_disconnect = false; + for ( size_t i = 0; i < connections_polled; i++ ) + { + struct pollfd* pfd = &pfds[cpfd_off + i]; + if ( !pfd->revents ) + continue; + struct connection* connection = server->connections[i]; + if ( pfd->revents & (POLLERR | POLLHUP | POLLNVAL) && + !(pfd->revents & POLLIN) ) + { + connection_destroy(connection); + free(connection); + server->connections[i] = NULL; + any_disconnect = true; + continue; + } + if ( pfd->revents & POLLOUT ) + connection_can_write(connection); + if ( pfd->revents & POLLIN ) + connection_can_read(connection, server); + } + + // Compact the array down here so the pfds match the connections above. + if ( any_disconnect ) + { + size_t new_used = 0; + for ( size_t i = 0; i < server->connections_used; i++ ) + { + if ( server->connections[i] ) + server->connections[new_used++] = server->connections[i]; + } + server->connections_used = new_used; + } +} + +void server_mainloop(struct server* server) +{ + while ( true ) + { + display_render(server->display); + server_poll(server); + } +} diff --git a/display/server.h b/display/server.h new file mode 100644 index 00000000..f50e7027 --- /dev/null +++ b/display/server.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * connection.h + * Display server main loop. + */ + +#ifndef SERVER_H +#define SERVER_H + +#include +#include + +struct connection; +struct display; +struct pollfd; + +struct server +{ + struct display* display; + const char* server_path; + int server_fd; + int tty_fd; + int mouse_fd; + struct connection** connections; + size_t connections_used; + size_t connections_length; + struct pollfd* pfds; + size_t pfds_count; +}; + +void server_initialize(struct server* server, struct display* display, + const char* tty, const char* mouse, const char* socket); +bool server_accept(struct server* server); +size_t server_pfds_count(const struct server* server); +void server_poll(struct server* server); +void server_mainloop(struct server* server); + +#endif diff --git a/display/window.c b/display/window.c new file mode 100644 index 00000000..7b70a51f --- /dev/null +++ b/display/window.c @@ -0,0 +1,554 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017, 2022, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * window.c + * Window abstraction. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "connection.h" +#include "display.h" +#include "framebuffer.h" +#include "pixel.h" +#include "vgafont.h" +#include "window.h" + +struct framebuffer window_client_buffer(struct window* window) +{ + if ( window->window_state != WINDOW_STATE_REGULAR ) + return framebuffer_crop(window->buffer, 0, TITLE_HEIGHT, + window->width, window->height - TITLE_HEIGHT); + return framebuffer_crop(window->buffer, BORDER_WIDTH, TITLE_HEIGHT, + window->width - 2 * BORDER_WIDTH, + window->height - TITLE_HEIGHT - BORDER_WIDTH); +} + +void window_schedule_redraw(struct window* window) +{ + if ( window->show ) + display_schedule_redraw(window->display); +} + +void window_render_frame(struct window* window) +{ + if ( !window->width || !window->height ) + return; + + bool has_focus = window->display->tab_candidate ? + window->display->tab_candidate == window : + window->display->active_window == window; + + uint32_t glass_color = has_focus ? make_color_a(200, 200, 255, 192) + : make_color_a(180, 180, 255, 128); + uint32_t title_color = has_focus ? make_color_a(16, 16, 16, 240) + : make_color_a(32, 32, 32, 200); + uint32_t button_hover_glass = make_color_a(220, 220, 255, 255); + uint32_t button_press_glass = make_color_a(180, 180, 255, 255); + + size_t start_x = 0; + size_t start_y = 0; + size_t end_x = window->width - 1; + size_t end_y = window->height - 1; + + bool maximized = window->window_state != WINDOW_STATE_REGULAR; + + int b0 = 0; + int b1 = 1; + int b2 = 2; + int b3 = BORDER_WIDTH; + int t0 = TITLE_HEIGHT; + + for ( size_t y = start_y; y <= end_y; y++ ) + { + for ( size_t x = start_x; x <= end_x; x++ ) + { + uint32_t color; + if ( maximized && y < start_y + t0 ) + color = glass_color; + else if ( maximized ) + continue; + else if ( x == start_x + b0 || x == end_x - b0 || + y == start_y + b0 || y == end_y - b0 ) + color = make_color_a(0, 0, 0, 32); + else if ( x == start_x + b1 || x == end_x - b1 || + y == start_y + b1 || y == end_y - b1 ) + color = make_color_a(0, 0, 0, 64); + else if ( x == start_x + b2 || x == end_x - b2 || + y == start_y + b2 || y == end_y - b2 ) + color = make_color(240, 240, 250); + else if ( x < start_x + (b3-1) || x > end_x - (b3-1) || + y < start_y + (t0-1) || y > end_y - (b3-1) ) + color = glass_color; + else if ( x == start_x + (b3-1) || x == end_x - (b3-1) || + y == start_y + (t0-1) || y == end_y - (b3-1) ) + color = make_color(64, 64, 64); + else + continue; + framebuffer_set_pixel(window->buffer, x, y, color); + } + } + + const char* tt = window->title ? window->title : ""; + ssize_t tt_width = render_text_width(tt); // Potentially adjusted later. + size_t tt_height = FONT_HEIGHT; + size_t tt_pos_y = (TITLE_HEIGHT - FONT_HEIGHT) / 2 + 2; + uint32_t tt_color = title_color; + + size_t border_width = maximized ? 0 : b2 + 1; + size_t button_area_height = maximized ? t0 : t0 - (b2 + 1); + size_t button_area_width = button_area_height; + size_t button_area_top = maximized ? 0 : b2; + size_t button_size = FONT_WIDTH - 1; + size_t button_top = (button_area_height - button_size + 1) / 2; + size_t button_left = (button_area_width - button_size + 1) / 2; + ssize_t buttons_x = window->width - border_width - button_area_width*3 + 1; + struct framebuffer buttons_fb = + framebuffer_crop(window->buffer, buttons_x, button_area_top, + button_area_width * 3, button_area_height); + for ( size_t n = 0; n < 3; n++ ) + { + uint32_t color = glass_color; + switch ( window->button_states[n] ) + { + case BUTTON_STATE_NORMAL: continue; + case BUTTON_STATE_HOVER: color = button_hover_glass; break; + case BUTTON_STATE_PRESSED: color = button_press_glass; break; + } + size_t bx = button_area_width * n; + size_t by = 0; + for ( size_t y = 0; y < button_area_height; y++ ) + for ( size_t x = 0; x < button_area_width; x++ ) + framebuffer_set_pixel(buttons_fb, bx + x, by + y, color); + } + for ( size_t i = 0; i < button_size; i++ ) + { + size_t bx = button_area_width * 0 + button_left; + size_t by = button_top; + framebuffer_set_pixel(buttons_fb, bx + i, + by + button_size - 1, tt_color); + framebuffer_set_pixel(buttons_fb, bx + i, + by + button_size - 2, tt_color); + } + for ( size_t i = 0; i < button_size; i++ ) + { + size_t bx = button_area_width * 1 + button_left; + size_t by = button_top; + framebuffer_set_pixel(buttons_fb, bx + i, + by, tt_color); + framebuffer_set_pixel(buttons_fb, bx + i, + by + button_size - 1 , tt_color); + framebuffer_set_pixel(buttons_fb, bx, + by + i, tt_color); + framebuffer_set_pixel(buttons_fb, bx + button_size - 1, + by + i, tt_color); + + framebuffer_set_pixel(buttons_fb, bx + i, + by + 1, tt_color); + framebuffer_set_pixel(buttons_fb, bx + i, + by + button_size - 2 , tt_color); + framebuffer_set_pixel(buttons_fb, bx + 1, + by + i, tt_color); + framebuffer_set_pixel(buttons_fb, bx + button_size - 2, + by + i, tt_color); + } + for ( size_t i = 0; i < button_size; i++ ) + { + size_t bx = button_area_width * 2 + button_left; + size_t by = button_top; + framebuffer_set_pixel(buttons_fb, bx + i, + by + i, tt_color); + framebuffer_set_pixel(buttons_fb, bx + i, + by + button_size - 1 - i, tt_color); + + framebuffer_set_pixel(buttons_fb, bx + i - 1, + by + i, tt_color); + framebuffer_set_pixel(buttons_fb, bx + i - 1, + by + button_size - 1 - i, tt_color); + } + + ssize_t q = 500 - window->width; + ssize_t q_width = 200; + q = q < q_width ? q : q_width; + q = 0 < q ? q : 0; + ssize_t center_over = window->width - (button_area_width * 3 * q / q_width); + ssize_t tt_pos_x = (center_over - tt_width) / 2; + if ( tt_pos_x < (ssize_t)border_width ) + { + tt_pos_x = border_width; + tt_width = buttons_x - border_width; + tt_width = 0 < tt_width ? tt_width : 0; + } + render_text(framebuffer_crop(window->buffer, tt_pos_x, tt_pos_y, + tt_width, tt_height), tt, tt_color); + window_schedule_redraw(window); +} + +void window_move(struct window* window, size_t left, size_t top) +{ + window->left = left; + window->top = top; + window_schedule_redraw(window); +} + +void window_client_resize(struct window* window, + size_t client_width, + size_t client_height) +{ + if ( window->window_state != WINDOW_STATE_MINIMIZED ) + window->window_state = WINDOW_STATE_REGULAR; + + struct framebuffer old_fb = window->buffer; + + window->width = client_width + BORDER_WIDTH + BORDER_WIDTH; + window->height = client_height + TITLE_HEIGHT + BORDER_WIDTH; + + window->buffer.xres = window->width; + window->buffer.yres = window->height; + window->buffer.pitch = window->width; + // TODO: Check malloc. + window->buffer.buffer = + malloc(sizeof(uint32_t) * window->width * window->height); + for ( size_t y = 0; y < window->height; y++ ) + for ( size_t x = 0; x < window->width; x++ ) + framebuffer_set_pixel(window->buffer, x, y, + framebuffer_get_pixel(old_fb, x, y)); + + free(old_fb.buffer); + + window_render_frame(window); + window_notify_client_resize(window); + window_schedule_redraw(window); +} + +void window_resize(struct window* window, size_t width, size_t height) +{ + if ( width < BORDER_WIDTH + BORDER_WIDTH ) + width = BORDER_WIDTH + BORDER_WIDTH; + if ( height < TITLE_HEIGHT + BORDER_WIDTH ) + height = TITLE_HEIGHT + BORDER_WIDTH; + // TODO: Keep proper track of this for each state. + size_t client_width = width - (BORDER_WIDTH + BORDER_WIDTH); + size_t client_height = height - (TITLE_HEIGHT + BORDER_WIDTH); + window_client_resize(window, client_width, client_height); +} + +void window_drag_resize(struct window* window, int ld, int td, int wd, int hd) +{ + // TODO: Keep proper track of this for each state. + size_t client_width = window->width - (BORDER_WIDTH + BORDER_WIDTH); + size_t client_height = window->height - (TITLE_HEIGHT + BORDER_WIDTH); + if ( ld || td ) + window_move(window, window->left + ld, window->top + td); + if ( wd || hd ) + { + ssize_t new_width = (ssize_t) client_width + wd; + ssize_t new_height = (ssize_t) client_height + hd; + if ( new_width < 1 ) + new_width = 1; + if ( new_height < 1 ) + new_height = 1; + window_client_resize(window, new_width, new_height); + } +} + +static size_t next_window_position = 25; + +void window_initialize(struct window* window, + struct connection* connection, + struct display* display, + uint32_t window_id) +{ + memset(window, 0, sizeof(*window)); + window->created = true; + window->connection = connection; + window->display = display; + window->title_click_time = timespec_make(-1, 0); + window->window_id = window_id; + display_add_window(window->display, window); + window->top = next_window_position; + window->left = next_window_position; + size_t max_position = display->screen_width < display->screen_height ? + display->screen_width : display->screen_height; + max_position = (max_position * 6) / 10; + next_window_position += 30; + next_window_position %= max_position; + window_client_resize(window, 0, 0); +} + +void window_quit(struct window* window) +{ + struct event_quit event; + event.window_id = window->window_id; + + struct display_packet_header header; + header.id = EVENT_QUIT; + header.size = sizeof(event); + + assert(window->connection); + + connection_schedule_transmit(window->connection, &header, sizeof(header)); + connection_schedule_transmit(window->connection, &event, sizeof(event)); +} + +void window_destroy(struct window* window) +{ + display_remove_window(window->display, window); + free(window->buffer.buffer); + free(window->title); + memset(window, 0, sizeof(*window)); + window->created = false; +} + +void window_on_display_resolution_change(struct window* window, + struct display* display) +{ + // TODO: Move window back inside screen. + if ( window->window_state == WINDOW_STATE_MAXIMIZED ) + { + // TODO: Change size of maximized window. + (void) display; + } +} + +void window_tile(struct window* window, enum window_state state, size_t left, + size_t top, size_t width, size_t height) +{ + if ( window->window_state == state ) + return; + + if ( window->window_state == WINDOW_STATE_REGULAR ) + { + window->saved_left = window->left; + window->saved_top = window->top; + window->saved_width = window->width; + window->saved_height = window->height; + } + + free(window->buffer.buffer); + + window->left = left; + window->top = top; + window->width = width; + window->height = height; + + // TODO: Share logic with window_client_resize. + window->buffer.xres = window->width; + window->buffer.yres = window->height; + window->buffer.pitch = window->width; + // TODO: Check malloc. + window->buffer.buffer = + calloc(1, sizeof(uint32_t) * window->width * window->height); + + window->window_state = state; + + window_render_frame(window); + window_notify_client_resize(window); +} + +void window_maximize(struct window* window) +{ + window_tile(window, WINDOW_STATE_MAXIMIZED, + 0, 0, + window->display->screen_width, window->display->screen_height); +} + +void window_restore(struct window* window) +{ + if ( window->window_state == WINDOW_STATE_REGULAR ) + return; + window->top = window->saved_top; + window->left = window->saved_left; + window_client_resize(window, window->saved_width - 2 * BORDER_WIDTH, + window->saved_height - TITLE_HEIGHT - BORDER_WIDTH); + window_notify_client_resize(window); +} + +void window_toggle_maximized(struct window* window) +{ + if ( window->window_state == WINDOW_STATE_MAXIMIZED ) + window_restore(window); + else + window_maximize(window); +} + +void window_tile_leftward(struct window* window) +{ + switch ( window->window_state ) + { + case WINDOW_STATE_REGULAR: window_tile_left(window); break; + case WINDOW_STATE_MAXIMIZED: window_tile_left(window); break; + case WINDOW_STATE_MINIMIZED: window_tile_right(window); break; + case WINDOW_STATE_TILE_LEFT: break; + case WINDOW_STATE_TILE_RIGHT: window_restore(window); break; + case WINDOW_STATE_TILE_TOP: window_tile_top_left(window); break; + case WINDOW_STATE_TILE_TOP_LEFT: break; + case WINDOW_STATE_TILE_TOP_RIGHT: window_tile_top(window); break; + case WINDOW_STATE_TILE_BOTTOM: window_tile_bottom_left(window); break; + case WINDOW_STATE_TILE_BOTTOM_LEFT: break; + case WINDOW_STATE_TILE_BOTTOM_RIGHT: window_tile_bottom(window); break; + } +} + +void window_tile_rightward(struct window* window) +{ + switch ( window->window_state ) + { + case WINDOW_STATE_REGULAR: window_tile_right(window); break; + case WINDOW_STATE_MAXIMIZED: window_tile_right(window); break; + case WINDOW_STATE_MINIMIZED: window_tile_right(window); break; + case WINDOW_STATE_TILE_LEFT: window_restore(window); break; + case WINDOW_STATE_TILE_RIGHT: break; + case WINDOW_STATE_TILE_TOP: window_tile_top_right(window); break; + case WINDOW_STATE_TILE_TOP_LEFT: window_tile_top(window); break; + case WINDOW_STATE_TILE_TOP_RIGHT: break; + case WINDOW_STATE_TILE_BOTTOM: window_tile_bottom_right(window); break; + case WINDOW_STATE_TILE_BOTTOM_LEFT: window_tile_bottom(window); break; + case WINDOW_STATE_TILE_BOTTOM_RIGHT: break; + } +} + +void window_tile_up(struct window* window) +{ + switch ( window->window_state ) + { + case WINDOW_STATE_REGULAR: window_tile_top(window); break; + case WINDOW_STATE_MAXIMIZED: window_restore(window); break; + case WINDOW_STATE_MINIMIZED: window_tile_top(window); break; + case WINDOW_STATE_TILE_LEFT: window_tile_top_left(window); break; + case WINDOW_STATE_TILE_RIGHT: window_tile_top_right(window); break; + case WINDOW_STATE_TILE_TOP: window_maximize(window); break; + case WINDOW_STATE_TILE_TOP_LEFT: break; + case WINDOW_STATE_TILE_TOP_RIGHT: break; + case WINDOW_STATE_TILE_BOTTOM: window_restore(window); break; + case WINDOW_STATE_TILE_BOTTOM_LEFT: window_tile_left(window); break; + case WINDOW_STATE_TILE_BOTTOM_RIGHT: window_tile_right(window); break; + } +} + +void window_tile_down(struct window* window) +{ + switch ( window->window_state ) + { + case WINDOW_STATE_REGULAR: window_tile_bottom(window); break; + case WINDOW_STATE_MAXIMIZED: window_tile_top(window); break; + case WINDOW_STATE_MINIMIZED: window_tile_bottom(window); break; + case WINDOW_STATE_TILE_LEFT: window_tile_bottom_left(window); break; + case WINDOW_STATE_TILE_RIGHT: window_tile_bottom_right(window); break; + case WINDOW_STATE_TILE_TOP: window_restore(window); break; + case WINDOW_STATE_TILE_TOP_LEFT: window_tile_left(window); break; + case WINDOW_STATE_TILE_TOP_RIGHT: window_tile_right(window); break; + case WINDOW_STATE_TILE_BOTTOM: break; + case WINDOW_STATE_TILE_BOTTOM_LEFT: break; + case WINDOW_STATE_TILE_BOTTOM_RIGHT: break; + } +} + +void window_tile_left(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_LEFT, + 0, + 0, + window->display->screen_width / 2, + window->display->screen_height); +} + +void window_tile_right(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_RIGHT, + (window->display->screen_width + 1) / 2, + 0, + (window->display->screen_width + 1) / 2, + window->display->screen_height); +} + +void window_tile_top(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_TOP, + 0, + 0, + window->display->screen_width, + window->display->screen_height / 2); +} + +void window_tile_top_left(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_TOP_LEFT, + 0, + 0, + window->display->screen_width / 2, + window->display->screen_height / 2); +} + +void window_tile_top_right(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_TOP_RIGHT, + (window->display->screen_width + 1) / 2, + 0, + (window->display->screen_width + 1) / 2, + window->display->screen_height / 2); +} + +void window_tile_bottom(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_BOTTOM, + 0, + (window->display->screen_height + 1) / 2, + window->display->screen_width, + (window->display->screen_height + 1) / 2); +} + +void window_tile_bottom_left(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_BOTTOM_LEFT, + 0, + (window->display->screen_height + 1) / 2, + window->display->screen_width / 2, + (window->display->screen_height + 1) / 2); +} + +void window_tile_bottom_right(struct window* window) +{ + window_tile(window, WINDOW_STATE_TILE_BOTTOM_RIGHT, + (window->display->screen_width + 1) / 2, + (window->display->screen_height + 1) / 2, + (window->display->screen_width + 1) / 2, + (window->display->screen_height + 1) / 2); +} + +void window_notify_client_resize(struct window* window) +{ + struct event_resize event; + event.window_id = window->window_id; + event.width = window_client_buffer(window).xres; + event.height = window_client_buffer(window).yres; + + struct display_packet_header header; + header.id = EVENT_RESIZE; + header.size = sizeof(event); + + assert(window->connection); + + connection_schedule_transmit(window->connection, &header, sizeof(header)); + connection_schedule_transmit(window->connection, &event, sizeof(event)); +} diff --git a/display/window.h b/display/window.h new file mode 100644 index 00000000..58f93e84 --- /dev/null +++ b/display/window.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2018, 2022, 2023 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * window.h + * Window abstraction. + */ + +#ifndef WINDOW_H +#define WINDOW_H + +#include + +#include +#include +#include + +#include "framebuffer.h" + +struct connection; +struct display; + +static const size_t BORDER_WIDTH = 8; +static const size_t TITLE_HEIGHT = 28; +static const size_t RESIZE_GRACE = 16; + +enum button_state +{ + BUTTON_STATE_NORMAL, + BUTTON_STATE_HOVER, + BUTTON_STATE_PRESSED, +}; + +enum window_state +{ + WINDOW_STATE_REGULAR, + WINDOW_STATE_MAXIMIZED, + WINDOW_STATE_MINIMIZED, + WINDOW_STATE_TILE_LEFT, + WINDOW_STATE_TILE_RIGHT, + WINDOW_STATE_TILE_TOP, + WINDOW_STATE_TILE_TOP_LEFT, + WINDOW_STATE_TILE_TOP_RIGHT, + WINDOW_STATE_TILE_BOTTOM, + WINDOW_STATE_TILE_BOTTOM_LEFT, + WINDOW_STATE_TILE_BOTTOM_RIGHT, +}; + +struct window +{ + struct display* display; + struct connection* connection; + struct window* above_window; + struct window* below_window; + struct framebuffer buffer; + struct timespec title_click_time; + char* title; + ssize_t left; + ssize_t top; + size_t width; + size_t height; + ssize_t saved_left; + ssize_t saved_top; + size_t saved_width; + size_t saved_height; + uint32_t window_id; + enum window_state window_state; + enum button_state button_states[3]; + bool created; + bool show; + bool focus; + bool grab_input; +}; + +struct framebuffer window_client_buffer(struct window* window); +void window_schedule_redraw(struct window* window); +void window_render_frame(struct window* window); +void window_move(struct window* window, size_t left, size_t top); +void window_resize(struct window* window, size_t width, size_t height); +void window_client_resize(struct window* window, size_t client_width, + size_t client_height); +void window_initialize(struct window* window, struct connection* connection, + struct display* display, uint32_t window_id); +void window_quit(struct window* window); +void window_destroy(struct window* window); +void window_drag_resize(struct window* window, int ld, int td, int wd, int hd); +void window_on_display_resolution_change(struct window* window, + struct display* display); +void window_maximize(struct window* window); +void window_restore(struct window* window); +void window_toggle_maximized(struct window* window); +void window_tile(struct window* window, enum window_state state, size_t left, + size_t top, size_t width, size_t height); +void window_tile_leftward(struct window* window); +void window_tile_rightward(struct window* window); +void window_tile_up(struct window* window); +void window_tile_down(struct window* window); +void window_tile_left(struct window* window); +void window_tile_right(struct window* window); +void window_tile_top(struct window* window); +void window_tile_top_left(struct window* window); +void window_tile_top_right(struct window* window); +void window_tile_bottom(struct window* window); +void window_tile_bottom_left(struct window* window); +void window_tile_bottom_right(struct window* window); +void window_notify_client_resize(struct window* window); + +#endif diff --git a/games/Makefile b/games/Makefile index d522a6c3..2acce0fb 100644 --- a/games/Makefile +++ b/games/Makefile @@ -15,7 +15,7 @@ BINARIES:=\ asteroids \ aquatinspitz \ -LIBS:=-ldispd +LIBS:=-ldisplay all: $(BINARIES) diff --git a/games/aquatinspitz.c b/games/aquatinspitz.c index c216fe95..67ed82a4 100644 --- a/games/aquatinspitz.c +++ b/games/aquatinspitz.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -34,12 +35,15 @@ #include #include -#include +#include // Utility global variables every game will need. +uint32_t window_id = 0; +static size_t framesize; +static uint32_t* fb; static bool game_running = true; -static size_t game_width = 1280; -static size_t game_height = 720; +static size_t game_width = 800; +static size_t game_height = 512; #define MAX_KEY_NUMBER 512 static bool keys_down[MAX_KEY_NUMBER]; static bool keys_pending[MAX_KEY_NUMBER]; @@ -200,20 +204,16 @@ void update(float deltatime) } // Render the game into the framebuffer. -void render(struct dispd_window* window) +void render(struct display_connection* connection) { - struct dispd_framebuffer* window_fb = dispd_begin_render(window); - if ( !window_fb ) - { - error(0, 0, "unable to begin rendering dispd window"); - game_running = false; - return; - } + size_t old_framesize = framesize; - uint32_t* fb = (uint32_t*) dispd_get_framebuffer_data(window_fb); - size_t xres = dispd_get_framebuffer_width(window_fb); - size_t yres = dispd_get_framebuffer_height(window_fb); - size_t pitch = dispd_get_framebuffer_pitch(window_fb) / sizeof(uint32_t); + size_t xres = game_width; + size_t yres = game_height; + size_t pitch = xres; + framesize = xres * yres * sizeof(uint32_t); + if ( old_framesize != framesize && !(fb = realloc(fb, framesize)) ) + err(1, "malloc"); // Render a colorful background. for ( size_t y = 0; y < yres; y++ ) @@ -273,7 +273,9 @@ void render(struct dispd_window* window) } } - dispd_finish_render(window_fb); + display_render_window(connection, window_id, 0, 0, + game_width, game_height, fb); + display_show_window(connection, window_id); } // ... to here. No need to edit stuff below. @@ -310,50 +312,68 @@ bool pop_is_key_just_down(int abskbkey) return true; } -// Read input from the keyboard. -void input(void) +// When the connection to the display server has disconnected. +void on_disconnect(void* ctx) { - // Read the keyboard input from the user. - unsigned termmode = TERMMODE_KBKEY | TERMMODE_SIGNAL | TERMMODE_NONBLOCK; - if ( settermmode(0, termmode) ) - error(1, errno, "settermmode"); - uint32_t codepoint; - ssize_t numbytes; - while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) ) - { - int kbkey = KBKEY_DECODE(codepoint); - if( !kbkey ) - continue; - int abskbkey = (kbkey < 0) ? -kbkey : kbkey; - if ( MAX_KEY_NUMBER <= (size_t) abskbkey ) - continue; - bool is_key_down_event = 0 < kbkey; - if ( !keys_down[abskbkey] && is_key_down_event ) - keys_pending[abskbkey] = true; - keys_down[abskbkey] = is_key_down_event; - } + (void) ctx; + exit(0); +} + +// When the window is asked to quit. +void on_quit(void* ctx, uint32_t window_id) +{ + (void) ctx; + (void) window_id; + exit(0); +} + +// When the window has been resized. +void on_resize(void* ctx, uint32_t window_id, uint32_t width, uint32_t height) +{ + (void) ctx; + if ( window_id != window_id ) + return; + game_width = width; + game_height = height; +} + +// When a key has been pressed. +void on_keyboard(void* ctx, uint32_t window_id, uint32_t codepoint) +{ + (void) ctx; + if ( window_id != window_id ) + return; + int kbkey = KBKEY_DECODE(codepoint); + if ( !kbkey ) + return; + int abskbkey = (kbkey < 0) ? -kbkey : kbkey; + if ( MAX_KEY_NUMBER <= (size_t) abskbkey ) + return; + bool is_key_down_event = 0 < kbkey; + if ( !keys_down[abskbkey] && is_key_down_event ) + keys_pending[abskbkey] = true; + keys_down[abskbkey] = is_key_down_event; } // Run the game until no longer needed. -void mainloop(struct dispd_window* window) +void mainloop(struct display_connection* connection) { - struct dispd_framebuffer* window_fb = dispd_begin_render(window); - if ( window_fb ) - { - game_width = dispd_get_framebuffer_width(window_fb); - game_height = dispd_get_framebuffer_height(window_fb); - dispd_finish_render(window_fb); - } + struct display_event_handlers handlers = {0}; + handlers.disconnect_handler = on_disconnect; + handlers.quit_handler = on_quit; + handlers.resize_handler = on_resize; + handlers.keyboard_handler = on_keyboard; init(); struct timespec last_frame_time; clock_gettime(CLOCK_MONOTONIC, &last_frame_time); - render(window); + render(connection); while ( game_running ) { + struct timespec current_frame_time; clock_gettime(CLOCK_MONOTONIC, ¤t_frame_time); @@ -361,71 +381,31 @@ void mainloop(struct dispd_window* window) timespec_sub(current_frame_time, last_frame_time); float deltatime = deltatime_ts.tv_sec + deltatime_ts.tv_nsec / 1E9f; - input(); + while ( display_poll_event(connection, &handlers) == 0 ); + update(deltatime); - render(window); + render(connection); last_frame_time = current_frame_time; } } -// Reset the terminal state when the process terminates. -static struct termios saved_tio; - -static void restore_terminal_on_exit(void) -{ - tcsetattr(0, TCSAFLUSH, &saved_tio); -} - -static void restore_terminal_on_signal(int signum) -{ - if ( signum == SIGTSTP ) - { - struct termios tio; - tcgetattr(0, &tio); - tcsetattr(0, TCSAFLUSH, &saved_tio); - raise(SIGSTOP); - tcgetattr(0, &saved_tio); - tcsetattr(0, TCSAFLUSH, &tio); - return; - } - tcsetattr(0, TCSAFLUSH, &saved_tio); - raise(signum); -} - // Create a display context, run the game, and then cleanly exit. int main(int argc, char* argv[]) { - if ( !isatty(0) ) - error(1, errno, "standard input"); - if ( tcgetattr(0, &saved_tio) < 0 ) - error(1, errno, "tcsetattr: standard input"); - if ( atexit(restore_terminal_on_exit) != 0 ) - error(1, errno, "atexit"); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = restore_terminal_on_signal; - sigaction(SIGTSTP, &sa, NULL); - sa.sa_flags = SA_RESETHAND; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); + struct display_connection* connection = display_connect_default(); + if ( !connection && errno == ECONNREFUSED ) + display_spawn(argc, argv); + if ( !connection ) + error(1, errno, "Could not connect to display server"); - 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"); + display_create_window(connection, window_id); + display_resize_window(connection, window_id, game_width, game_height); + display_title_window(connection, window_id, "Aquatinspitz"); - mainloop(window); + mainloop(connection); - dispd_destroy_window(window); - dispd_detach_session(session); + display_disconnect(connection); return 0; } diff --git a/games/asteroids.cpp b/games/asteroids.cpp index 40ef3cbe..d84eec10 100644 --- a/games/asteroids.cpp +++ b/games/asteroids.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -36,7 +37,11 @@ #include #include -#include +#include + +uint32_t WINDOW_ID = 0; +uint32_t WINDOW_WIDTH = 800; +uint32_t WINDOW_HEIGHT = 512; static inline float RandomFloat() { @@ -60,7 +65,7 @@ static inline float RandomAngle() static inline uint32_t MakeColor(uint8_t r, uint8_t g, uint8_t b) { - return b << 0UL | g << 8UL | r << 16UL; + return b << 0UL | g << 8UL | r << 16UL | 0xFF << 24UL; } static const size_t STARFIELD_WIDTH = 512UL; @@ -110,31 +115,6 @@ bool pop_is_key_just_down(int abskbkey) return true; } -void FetchKeyboardInput() -{ - // Read the keyboard input from the user. - const unsigned termmode = TERMMODE_KBKEY - | TERMMODE_SIGNAL - | TERMMODE_NONBLOCK; - if ( settermmode(0, termmode) ) - error(1, errno, "settermmode"); - uint32_t codepoint; - ssize_t numbytes; - while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) ) - { - int kbkey = KBKEY_DECODE(codepoint); - if( !kbkey ) - continue; - int abskbkey = (kbkey < 0) ? -kbkey : kbkey; - if ( MAXKEYNUM <= (size_t) abskbkey ) - continue; - bool is_key_down_event = 0 < kbkey; - if ( !keysdown[abskbkey] && is_key_down_event ) - keyspending[abskbkey] = true; - keysdown[abskbkey] = is_key_down_event; - } -} - static size_t xres; static size_t yres; static size_t bpp; @@ -1143,25 +1123,88 @@ void Render() obj->Render(); } } -void RunFrame(struct dispd_window* window) + +void on_disconnect(void*) { - struct dispd_framebuffer* fb = dispd_begin_render(window); - if ( !fb ) - { - error(0, 0, "unable to begin rendering dispd window"); - gamerunning = false; + exit(0); +} + +void on_quit(void*, uint32_t) +{ + exit(0); +} + +void on_resize(void*, uint32_t window_id, uint32_t width, uint32_t height) +{ + if ( window_id != WINDOW_ID ) return; + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; +} + +void on_keyboard(void*, uint32_t window_id, uint32_t codepoint) +{ + if ( window_id != WINDOW_ID ) + return; + int kbkey = KBKEY_DECODE(codepoint); + if( !kbkey ) + return; + int abskbkey = (kbkey < 0) ? -kbkey : kbkey; + if ( MAXKEYNUM <= (size_t) abskbkey ) + return; + bool is_key_down_event = 0 < kbkey; + if ( !keysdown[abskbkey] && is_key_down_event ) + keyspending[abskbkey] = true; + keysdown[abskbkey] = is_key_down_event; +} + +void RunFrame(struct display_connection* connection) +{ + struct display_event_handlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.disconnect_handler = on_disconnect; + handlers.quit_handler = on_quit; + handlers.resize_handler = on_resize; + handlers.keyboard_handler = on_keyboard; + + while ( display_poll_event(connection, &handlers) == 0 ); + + size_t old_framesize = framesize; + + xres = WINDOW_WIDTH; + yres = WINDOW_HEIGHT; + bpp = 32; + linesize = WINDOW_WIDTH; + framesize = WINDOW_WIDTH * sizeof(uint32_t) * WINDOW_HEIGHT; + if ( old_framesize != framesize ) + { + free(buf); + buf = (uint32_t*) malloc(framesize); + if ( !buf ) + err(1, "malloc"); } - xres = dispd_get_framebuffer_width(fb); - yres = dispd_get_framebuffer_height(fb); - bpp = dispd_get_framebuffer_format(fb); - linesize = dispd_get_framebuffer_pitch(fb) / (bpp / 8); - framesize = dispd_get_framebuffer_pitch(fb) * yres; - buf = (uint32_t*) dispd_get_framebuffer_data(fb); - FetchKeyboardInput(); + GameLogic(); Render(); - dispd_finish_render(fb); + + static int fps_counter = 0; + fps_counter++; + static time_t last_frame_sec = 0; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if ( now.tv_sec != last_frame_sec ) + { + char* title = NULL; + asprintf(&title, "Asteroids (fps %i)", fps_counter); + display_title_window(connection, WINDOW_ID, title); + free(title); + fps_counter = 0; + last_frame_sec = now.tv_sec; + } + + display_render_window(connection, WINDOW_ID, 0, 0, + WINDOW_WIDTH, WINDOW_HEIGHT, buf); + display_show_window(connection, WINDOW_ID); } void InitGame() @@ -1172,64 +1215,24 @@ void InitGame() new AsteroidField; } -static struct termios saved_tio; - -static void restore_terminal_on_exit(void) -{ - tcsetattr(0, TCSAFLUSH, &saved_tio); -} - -static void restore_terminal_on_signal(int signum) -{ - if ( signum == SIGTSTP ) - { - struct termios tio; - tcgetattr(0, &tio); - tcsetattr(0, TCSAFLUSH, &saved_tio); - raise(SIGSTOP); - tcgetattr(0, &saved_tio); - tcsetattr(0, TCSAFLUSH, &tio); - return; - } - tcsetattr(0, TCSAFLUSH, &saved_tio); - raise(signum); -} - int main(int argc, char* argv[]) { - if ( !isatty(0) ) - error(1, errno, "standard input"); - if ( tcgetattr(0, &saved_tio) < 0 ) - error(1, errno, "tcsetattr: standard input"); - if ( atexit(restore_terminal_on_exit) != 0 ) - error(1, errno, "atexit"); - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = restore_terminal_on_signal; - sigaction(SIGTSTP, &sa, NULL); - sa.sa_flags = SA_RESETHAND; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGQUIT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); + struct display_connection* connection = display_connect_default(); + if ( !connection && errno == ECONNREFUSED ) + display_spawn(argc, argv); + if ( !connection ) + error(1, errno, "Could not connect to display server"); - 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"); + display_create_window(connection, WINDOW_ID); + display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT); + display_title_window(connection, WINDOW_ID, "Asteroids"); InitGame(); gamerunning = true; for ( framenum = 0; gamerunning; framenum++ ) - RunFrame(window); + RunFrame(connection); - dispd_destroy_window(window); - dispd_detach_session(session); + display_disconnect(connection); return 0; } diff --git a/libdisplay/.gitignore b/libdisplay/.gitignore new file mode 100644 index 00000000..9eca6c88 --- /dev/null +++ b/libdisplay/.gitignore @@ -0,0 +1,2 @@ +*.a +*.o diff --git a/libdisplay/Makefile b/libdisplay/Makefile new file mode 100644 index 00000000..4a564e16 --- /dev/null +++ b/libdisplay/Makefile @@ -0,0 +1,34 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra +CPPFLAGS:=$(CPPFLAGS) -Iinclude + +LIBRARY=libdisplay.a + +OBJS=\ +libdisplay.o \ + +all: $(LIBRARY) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(LIBDIR) + cp $(LIBRARY) $(DESTDIR)$(LIBDIR) + mkdir -p $(DESTDIR)$(INCLUDEDIR) + cp -RTv include $(DESTDIR)$(INCLUDEDIR) + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +$(LIBRARY): $(OBJS) + $(AR) rcs $@ $(OBJS) + +clean: + rm -f $(LIBRARY) *.o *.a diff --git a/libdisplay/include/display-protocol.h b/libdisplay/include/display-protocol.h new file mode 100644 index 00000000..99a68db6 --- /dev/null +++ b/libdisplay/include/display-protocol.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen. + * Copyright (c) 2023 Juhani 'nortti' Krekelä. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * display-protocol.h + * Display protocol. + */ + +#ifndef DISPLAY_PROTOCOL_H +#define DISPLAY_PROTOCOL_H + +#include + +#include + +struct display_packet_header +{ + uint32_t id; + uint32_t size; +}; + +#define DISPLAY_CREATE_WINDOW 0 +struct display_create_window +{ + uint32_t window_id; +}; + +#define DISPLAY_DESTROY_WINDOW 1 +struct display_destroy_window +{ + uint32_t window_id; +}; + +#define DISPLAY_RESIZE_WINDOW 2 +struct display_resize_window +{ + uint32_t window_id; + uint32_t width; + uint32_t height; +}; + +#define DISPLAY_RENDER_WINDOW 3 +struct display_render_window +{ + uint32_t window_id; + uint32_t left; + uint32_t top; + uint32_t width; + uint32_t height; + /* width * height * sizeof(uint32_t) image bytes follows */ +}; + +#define DISPLAY_TITLE_WINDOW 4 +struct display_title_window +{ + uint32_t window_id; + /* A non-terminated UTF-8 string follows */ +}; + +#define DISPLAY_SHOW_WINDOW 5 +struct display_show_window +{ + uint32_t window_id; +}; + +#define DISPLAY_HIDE_WINDOW 6 +struct display_hide_window +{ + uint32_t window_id; +}; + +#define DISPLAY_SHUTDOWN 7 +struct display_shutdown +{ + uint32_t code; +}; + +#define DISPLAY_CHKBLAYOUT 8 +struct display_chkblayout +{ + uint32_t id; + /* keyboard layout data bytes follow */ +}; + +#define DISPLAY_REQUEST_DISPLAYS 9 +struct display_request_displays +{ + uint32_t id; +}; + +#define DISPLAY_REQUEST_DISPLAY_MODES 10 +struct display_request_display_modes +{ + uint32_t id; + uint32_t display_id; +}; + +#define DISPLAY_REQUEST_DISPLAY_MODE 11 +struct display_request_display_mode +{ + uint32_t id; + uint32_t display_id; +}; + +#define DISPLAY_SET_DISPLAY_MODE 12 +struct display_set_display_mode +{ + uint32_t id; + uint32_t display_id; + struct dispmsg_crtc_mode mode; +}; + +#define EVENT_DISCONNECT 0 +struct event_disconnect +{ +}; + +#define EVENT_QUIT 1 +struct event_quit +{ + uint32_t window_id; +}; + +#define EVENT_RESIZE 2 +struct event_resize +{ + uint32_t window_id; + uint32_t width; + uint32_t height; +}; + +#define EVENT_KEYBOARD 3 +struct event_keyboard +{ + uint32_t window_id; + uint32_t codepoint; +}; + +#define EVENT_ACK 4 +struct event_ack +{ + uint32_t id; + int32_t error; +}; + +#define EVENT_DISPLAYS 5 +struct event_displays +{ + uint32_t id; + uint32_t displays; +}; + +#define EVENT_DISPLAY_MODES 6 +struct event_display_modes +{ + uint32_t id; + uint32_t modes_count; + /* modes_count * sizeof(struct dispmsg_crtc_mode) video mode bytes follow */ +}; + +#define EVENT_DISPLAY_MODE 7 +struct event_display_mode +{ + uint32_t id; + struct dispmsg_crtc_mode mode; +}; + +#endif diff --git a/libdisplay/include/display.h b/libdisplay/include/display.h new file mode 100644 index 00000000..1153b6e0 --- /dev/null +++ b/libdisplay/include/display.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017, 2023 Jonas 'Sortie' Termansen. + * Copyright (c) 2023 Juhani 'nortti' Krekelä. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * display.h + * Display client library. + */ + +#ifndef INCLUDE_DISPLAY_H +#define INCLUDE_DISPLAY_H + +#include + +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +struct display_connection; + +int display_spawn(int argc, char** argv); + +struct display_connection* display_connect(const char* socket_path); +struct display_connection* display_connect_default(void); +void display_disconnect(struct display_connection* connection); +int display_connection_fd(struct display_connection* connection); +void display_shutdown(struct display_connection* connection, uint32_t code); +void display_create_window(struct display_connection* connection, + uint32_t window_id); +void display_destroy_window(struct display_connection* connection, + uint32_t window_id); +void display_resize_window(struct display_connection* connection, + uint32_t window_id, + uint32_t width, + uint32_t height); +void display_render_window(struct display_connection* connection, + uint32_t window_id, + uint32_t left, + uint32_t top, + uint32_t width, + uint32_t height, + uint32_t* data); +void display_title_window(struct display_connection* connection, + uint32_t window_id, + const char* title); +void display_show_window(struct display_connection* connection, + uint32_t window_id); +void display_hide_window(struct display_connection* connection, + uint32_t window_id); +void display_chkblayout(struct display_connection* connection, + uint32_t id, + void* data, + uint32_t kblayout_bytes); +void display_request_displays(struct display_connection* connection, + uint32_t id); +void display_request_display_modes(struct display_connection* connection, + uint32_t id, + uint32_t display_id); +void display_request_display_mode(struct display_connection* connection, + uint32_t id, + uint32_t display_id); +void display_set_display_mode(struct display_connection* connection, + uint32_t id, + uint32_t display_id, + struct dispmsg_crtc_mode mode); + +typedef void (*display_event_disconnect_handler_t)(void*); +typedef void (*display_event_quit_handler_t)(void*, uint32_t); +typedef void (*display_event_resize_handler_t)(void*, uint32_t, uint32_t, + uint32_t); +typedef void (*display_event_keyboard_handler_t)(void*, uint32_t, uint32_t); +typedef void (*display_event_ack_handler_t)(void*, uint32_t, int32_t); +typedef void (*display_event_displays_handler_t)(void*, uint32_t, uint32_t); +typedef void (*display_event_display_modes_handler_t)(void*, uint32_t, uint32_t, + void*, size_t); +typedef void (*display_event_display_mode_handler_t)(void*, uint32_t, + struct dispmsg_crtc_mode); + +struct display_event_handlers +{ + void* context; + display_event_disconnect_handler_t disconnect_handler; + display_event_quit_handler_t quit_handler; + display_event_resize_handler_t resize_handler; + display_event_keyboard_handler_t keyboard_handler; + display_event_ack_handler_t ack_handler; + display_event_displays_handler_t displays_handler; + display_event_display_modes_handler_t display_modes_handler; + display_event_display_mode_handler_t display_mode_handler; +}; + +int display_poll_event(struct display_connection* connection, + struct display_event_handlers* handlers); +int display_wait_event(struct display_connection* connection, + struct display_event_handlers* handlers); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif diff --git a/libdisplay/libdisplay.c b/libdisplay/libdisplay.c new file mode 100644 index 00000000..732e095b --- /dev/null +++ b/libdisplay/libdisplay.c @@ -0,0 +1,441 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017, 2023 Jonas 'Sortie' Termansen. + * Copyright (c) 2023 Juhani 'nortti' Krekelä. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * libdisplay.c + * Display client library. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "display-protocol.h" + +int display_spawn(int argc, char** argv) +{ + int length = 2 + 1; + if ( __builtin_add_overflow(length, argc, &length) ) + return errno = EOVERFLOW, -1; + char** new_argv = reallocarray(NULL, length, sizeof(char*)); + if ( !new_argv ) + return -1; + new_argv[0] = (char*) "display"; + // TODO: Start the compositor in a special close-after-program-exists mode? + // And maybe go fullscreen / maximized by default? + new_argv[1] = (char*) "--"; + for ( int i = 0; i < argc; i++ ) + new_argv[2 + i] = argv[i]; + new_argv[2 + argc] = NULL; + execvp(new_argv[0], new_argv); + free(new_argv); + return -1; +} + +static int open_local_client_socket(const char* path, int flags) +{ + size_t path_length = strlen(path); + size_t addr_size = offsetof(struct sockaddr_un, sun_path) + path_length + 1; + struct sockaddr_un* sockaddr = malloc(addr_size); + if ( !sockaddr ) + return -1; + sockaddr->sun_family = AF_LOCAL; + strcpy(sockaddr->sun_path, path); + int fd = socket(AF_LOCAL, SOCK_STREAM | flags, 0); + if ( fd < 0 ) + return free(sockaddr), -1; + if ( connect(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 ) + return close(fd), free(sockaddr), -1; + free(sockaddr); + return fd; +} + +struct display_connection +{ + int fd; + struct display_packet_header header; + size_t header_got; + uint8_t* payload; + size_t payload_got; +}; + +struct display_connection* display_connect(const char* socket_path) +{ + struct display_connection* connection = + calloc(1, sizeof(struct display_connection)); + if ( !connection ) + return NULL; + if ( (connection->fd = open_local_client_socket(socket_path, 0)) < 0 ) + return free(connection), (struct display_connection*) NULL; + size_t send_buffer_size = 2 * 1024 * 1024; + setsockopt(connection->fd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size, + sizeof(send_buffer_size)); + return connection; +} + +struct display_connection* display_connect_default(void) +{ + return display_connect(getenv("DISPLAY_SOCKET") ? + getenv("DISPLAY_SOCKET") : + "/var/run/display"); +} + +void display_disconnect(struct display_connection* connection) +{ + free(connection->payload); + close(connection->fd); + free(connection); +} + +int display_connection_fd(struct display_connection* connection) +{ + return connection->fd; +} + +static void send_message(struct display_connection* connection, + uint32_t id, + const void* message, + size_t message_size, + const void* auxiliary, + size_t auxiliary_size) +{ + struct display_packet_header header; + header.id = id; + header.size = message_size + auxiliary_size; + writeall(connection->fd, &header, sizeof(header)); + writeall(connection->fd, message, message_size); + writeall(connection->fd, auxiliary, auxiliary_size); +} + +static void send_message_no_aux(struct display_connection* connection, + uint32_t id, + const void* message, + size_t message_size) +{ + send_message(connection, id, message, message_size, 0, 0); +} + +void display_shutdown(struct display_connection* connection, uint32_t code) +{ + struct display_shutdown msg; + msg.code = code; + send_message_no_aux(connection, DISPLAY_SHUTDOWN, &msg, sizeof(msg)); +} + +void display_create_window(struct display_connection* connection, + uint32_t window_id) +{ + struct display_create_window msg; + msg.window_id = window_id; + send_message_no_aux(connection, DISPLAY_CREATE_WINDOW, &msg, sizeof(msg)); +} + +void display_destroy_window(struct display_connection* connection, + uint32_t window_id) +{ + struct display_destroy_window msg; + msg.window_id = window_id; + send_message_no_aux(connection, DISPLAY_DESTROY_WINDOW, &msg, sizeof(msg)); +} + +void display_resize_window(struct display_connection* connection, + uint32_t window_id, + uint32_t width, + uint32_t height) +{ + struct display_resize_window msg; + msg.window_id = window_id; + msg.width = width; + msg.height = height; + send_message_no_aux(connection, DISPLAY_RESIZE_WINDOW, &msg, sizeof(msg)); +} + +void display_render_window(struct display_connection* connection, + uint32_t window_id, + uint32_t left, + uint32_t top, + uint32_t width, + uint32_t height, + uint32_t* data) +{ + struct display_render_window msg; + msg.window_id = window_id; + msg.left = left; + msg.top = top; + msg.width = width; + msg.height = height; + send_message(connection, DISPLAY_RENDER_WINDOW, &msg, sizeof(msg), + data, sizeof(uint32_t) * width * height); +} + +void display_title_window(struct display_connection* connection, + uint32_t window_id, + const char* title) +{ + struct display_title_window msg; + msg.window_id = window_id; + send_message(connection, DISPLAY_TITLE_WINDOW, &msg, sizeof(msg), title, + strlen(title)); +} + +void display_show_window(struct display_connection* connection, + uint32_t window_id) +{ + struct display_show_window msg; + msg.window_id = window_id; + send_message_no_aux(connection, DISPLAY_SHOW_WINDOW, &msg, sizeof(msg)); +} + +void display_hide_window(struct display_connection* connection, + uint32_t window_id) +{ + struct display_hide_window msg; + msg.window_id = window_id; + send_message_no_aux(connection, DISPLAY_HIDE_WINDOW, &msg, sizeof(msg)); +} + +void display_chkblayout(struct display_connection* connection, + uint32_t id, + void* data, + uint32_t kblayout_bytes) +{ + struct display_chkblayout msg; + msg.id = id; + send_message(connection, DISPLAY_CHKBLAYOUT, &msg, sizeof(msg), + data, kblayout_bytes); +} + +void display_request_displays(struct display_connection* connection, + uint32_t id) +{ + struct display_request_displays msg; + msg.id = id; + send_message_no_aux(connection, DISPLAY_REQUEST_DISPLAYS, &msg, + sizeof(msg)); +} + +void display_request_display_modes(struct display_connection* connection, + uint32_t id, + uint32_t display_id) +{ + struct display_request_display_modes msg; + msg.id = id; + msg.display_id = display_id; + send_message_no_aux(connection, DISPLAY_REQUEST_DISPLAY_MODES, &msg, + sizeof(msg)); +} + +void display_request_display_mode(struct display_connection* connection, + uint32_t id, + uint32_t display_id) +{ + struct display_request_display_mode msg; + msg.id = id; + msg.display_id = display_id; + send_message_no_aux(connection, DISPLAY_REQUEST_DISPLAY_MODE, &msg, + sizeof(msg)); +} + +void display_set_display_mode(struct display_connection* connection, + uint32_t id, + uint32_t display_id, + struct dispmsg_crtc_mode mode) +{ + struct display_set_display_mode msg; + msg.id = id; + msg.display_id = display_id; + msg.mode = mode; + send_message_no_aux(connection, DISPLAY_SET_DISPLAY_MODE, &msg, + sizeof(msg)); +} + +static bool display_read_event(struct display_connection* connection) +{ + while ( connection->header_got < sizeof(connection->header) ) + { + errno = 0; + uint8_t* data = (uint8_t*) &connection->header + connection->header_got; + size_t left = sizeof(connection->header) - connection->header_got; + ssize_t amount = read(connection->fd, data, left); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + break; + if ( amount <= 0 ) + return false; + connection->header_got += amount; + } + if ( connection->header_got == sizeof(connection->header) && + !connection->payload ) + { + connection->payload = malloc(connection->header.size); + if ( !connection->payload ) + return false; + connection->payload_got = 0; + } + while ( connection->header_got == sizeof(connection->header) && + connection->payload && + connection->payload_got < connection->header.size ) + { + errno = 0; + uint8_t* data = connection->payload + connection->payload_got; + size_t left = connection->header.size - connection->payload_got; + ssize_t amount = read(connection->fd, data, left); + if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) ) + break; + if ( amount <= 0 ) + return false; + connection->payload_got += amount; + } + + return true; +} + +static int display_dispatch_event(struct display_connection* connection, + struct display_event_handlers* handlers) +{ + if ( connection->header_got == sizeof(connection->header) && + connection->payload && + connection->payload_got == connection->header.size ) + { + void* payload = connection->payload; + + if ( connection->header.id == EVENT_DISCONNECT && + connection->header.size == sizeof(struct event_disconnect) ) + { + struct event_disconnect* event = payload; + (void) event; + if ( handlers->disconnect_handler ) + handlers->disconnect_handler(handlers->context); + else + exit(0); + } + + if ( connection->header.id == EVENT_QUIT && + connection->header.size == sizeof(struct event_quit) ) + { + struct event_quit* event = payload; + if ( handlers->quit_handler ) + handlers->quit_handler(handlers->context, event->window_id); + else + exit(0); + } + + if ( connection->header.id == EVENT_RESIZE && + connection->header.size == sizeof(struct event_resize) ) + { + struct event_resize* event = payload; + if ( handlers->resize_handler ) + handlers->resize_handler(handlers->context, event->window_id, + event->width, event->height); + } + + if ( connection->header.id == EVENT_KEYBOARD && + connection->header.size == sizeof(struct event_keyboard) ) + { + struct event_keyboard* event = payload; + if ( handlers->keyboard_handler ) + handlers->keyboard_handler(handlers->context, event->window_id, + event->codepoint); + } + + if ( connection->header.id == EVENT_ACK && + connection->header.size == sizeof(struct event_ack) ) + { + struct event_ack* event = payload; + if ( handlers->ack_handler ) + handlers->ack_handler(handlers->context, event->id, + event->error); + } + + if ( connection->header.id == EVENT_DISPLAYS && + connection->header.size == sizeof(struct event_displays) ) + { + struct event_displays* event = payload; + if ( handlers->displays_handler ) + handlers->displays_handler(handlers->context, event->id, + event->displays); + } + + if ( connection->header.id == EVENT_DISPLAY_MODES && + connection->header.size >= sizeof(struct event_display_modes) ) + { + size_t aux_size = connection->header.size - + sizeof(struct event_display_modes); + void* aux = (char*) payload + sizeof(struct event_display_modes); + struct event_display_modes* event = payload; + if ( handlers->display_modes_handler ) + handlers->display_modes_handler(handlers->context, event->id, + event->modes_count, + aux, aux_size); + } + + if ( connection->header.id == EVENT_DISPLAY_MODE && + connection->header.size == sizeof(struct event_display_mode) ) + { + struct event_display_mode* event = payload; + if ( handlers->display_mode_handler ) + handlers->display_mode_handler(handlers->context, event->id, + event->mode); + } + + connection->header_got = 0; + free(connection->payload); + connection->payload = NULL; + connection->payload_got = 0; + + return 0; + } + + return -1; +} + +static int display_event_read_hangup(struct display_event_handlers* handlers) +{ + if ( handlers->disconnect_handler ) + handlers->disconnect_handler(handlers->context); + else + exit(1); + return -1; +} + +int display_poll_event(struct display_connection* connection, + struct display_event_handlers* handlers) +{ + fcntl(connection->fd, F_SETFL, fcntl(connection->fd, F_GETFL) | O_NONBLOCK); + bool read_success = display_read_event(connection); + fcntl(connection->fd, F_SETFL, fcntl(connection->fd, F_GETFL) & ~O_NONBLOCK); + if ( !read_success ) + return display_event_read_hangup(handlers); + return display_dispatch_event(connection, handlers); +} + +int display_wait_event(struct display_connection* connection, + struct display_event_handlers* handlers) +{ + if ( !display_read_event(connection) ) + return display_event_read_hangup(handlers); + return display_dispatch_event(connection, handlers); +} diff --git a/libui/.gitignore b/libui/.gitignore new file mode 100644 index 00000000..9eca6c88 --- /dev/null +++ b/libui/.gitignore @@ -0,0 +1,2 @@ +*.a +*.o diff --git a/libui/Makefile b/libui/Makefile new file mode 100644 index 00000000..37c0892c --- /dev/null +++ b/libui/Makefile @@ -0,0 +1,36 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra +CPPFLAGS:=$(CPPFLAGS) -Iinclude + +LIBRARY=libui.a + +OBJS=\ +framebuffer.o \ +pixel.o \ +vgafont.o \ + +all: $(LIBRARY) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(LIBDIR) + cp $(LIBRARY) $(DESTDIR)$(LIBDIR) + mkdir -p $(DESTDIR)$(INCLUDEDIR) + cp -RTv include $(DESTDIR)$(INCLUDEDIR) + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +$(LIBRARY): $(OBJS) + $(AR) rcs $@ $(OBJS) + +clean: + rm -f $(LIBRARY) *.o *.a diff --git a/libui/framebuffer.c b/libui/framebuffer.c new file mode 100644 index 00000000..68328d08 --- /dev/null +++ b/libui/framebuffer.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * framebuffer.c + * Framebuffer functions. + */ + +#include +#include + +#include "framebuffer.h" +#include "pixel.h" + +struct framebuffer framebuffer_crop(struct framebuffer fb, + size_t left, + size_t top, + size_t width, + size_t height) +{ + // Crop the framebuffer horizontally. + if ( fb.xres < left ) + left = fb.xres; + fb.buffer += left; + fb.xres -= left; + if ( width < fb.xres ) + fb.xres = width; + + // Crop the framebuffer vertically. + if ( fb.yres < top ) + top = fb.yres; + fb.buffer += top * fb.pitch; + fb.yres -= top; + if ( height < fb.yres ) + fb.yres = height; + + return fb; +} + +void framebuffer_copy_to_framebuffer(const struct framebuffer dst, + const struct framebuffer src) +{ + for ( size_t y = 0; y < src.yres; y++ ) + for ( size_t x = 0; x < src.xres; x++ ) + framebuffer_set_pixel(dst, x, y, framebuffer_get_pixel(src, x, y)); +} + +void framebuffer_copy_to_framebuffer_blend(const struct framebuffer dst, + const struct framebuffer src) +{ + for ( size_t y = 0; y < src.yres; y++ ) + { + for ( size_t x = 0; x < src.xres; x++ ) + { + uint32_t bg = framebuffer_get_pixel(dst, x, y); + uint32_t fg = framebuffer_get_pixel(src, x, y); + framebuffer_set_pixel(dst, x, y, blend_pixel(bg, fg)); + } + } +} diff --git a/libui/include/framebuffer.h b/libui/include/framebuffer.h new file mode 100644 index 00000000..bd81a331 --- /dev/null +++ b/libui/include/framebuffer.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * framebuffer.h + * Framebuffer functions. + */ + +#ifndef FRAMEBUFFER_H +#define FRAMEBUFFER_H + +#include +#include + +struct framebuffer +{ + size_t pitch; + uint32_t* buffer; + size_t xres; + size_t yres; +}; + +static inline uint32_t framebuffer_get_pixel(const struct framebuffer fb, + size_t x, + size_t y) +{ + if ( fb.xres <= x || fb.yres <= y ) + return 0; + return fb.buffer[y * fb.pitch + x]; +} + +static inline void framebuffer_set_pixel(const struct framebuffer fb, + size_t x, + size_t y, + uint32_t value) +{ + if ( fb.xres <= x || fb.yres <= y ) + return; + fb.buffer[y * fb.pitch + x] = value; +} + +struct framebuffer framebuffer_crop(struct framebuffer fb, + size_t left, + size_t top, + size_t width, + size_t height); +void framebuffer_copy_to_framebuffer(const struct framebuffer dst, + const struct framebuffer src); +void framebuffer_copy_to_framebuffer_blend(const struct framebuffer dst, + const struct framebuffer src); + +#endif diff --git a/libui/include/pixel.h b/libui/include/pixel.h new file mode 100644 index 00000000..201fc2f7 --- /dev/null +++ b/libui/include/pixel.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * pixel.h + * Pixel functions. + */ + +#ifndef PIXEL_H +#define PIXEL_H + +#include + +// TODO: This isn't the only pixel format in the world! +union color_rgba8 +{ + struct + { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t a; + }; + uint32_t value; +}; + +static inline uint32_t make_color_a(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + union color_rgba8 color; + color.r = r; + color.g = g; + color.b = b; + color.a = a; + return color.value; +} + +static inline uint32_t make_color(uint8_t r, uint8_t g, uint8_t b) +{ + return make_color_a(r, g, b, 255); +} + +uint32_t blend_pixel(uint32_t bg_value, uint32_t fg_value); + +#endif diff --git a/dispd/client/framebuffer.h b/libui/include/vgafont.h similarity index 53% rename from dispd/client/framebuffer.h rename to libui/include/vgafont.h index 4bdf6ed8..8296427b 100644 --- a/dispd/client/framebuffer.h +++ b/libui/include/vgafont.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2016 Jonas 'Sortie' Termansen. + * Copyright (c) 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,23 +13,29 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * framebuffer.h - * Keeps track of framebuffers. + * vgafont.h + * VGA font rendering. */ -#ifndef INCLUDE_DISPD_FRAMEBUFFER_H -#define INCLUDE_DISPD_FRAMEBUFFER_H +#ifndef VGAFONT_H +#define VGAFONT_H -struct dispd_framebuffer -{ - struct dispd_window* window; - uint8_t* data; - size_t datasize; - size_t pitch; - int bpp; - int width; - int height; - uint64_t fb_location; -}; +#include + +#include "framebuffer.h" + +#define FONT_REALWIDTH 8 +#define FONT_WIDTH 9 +#define FONT_HEIGHT 16 +#define FONT_CHARSIZE (FONT_REALWIDTH * FONT_HEIGHT / 8) +#define FONT_NUMCHARS 256 + +extern uint8_t font[FONT_CHARSIZE * FONT_NUMCHARS]; + +void load_font(void); +void render_char(struct framebuffer fb, wchar_t c, uint32_t color); +void render_text(struct framebuffer fb, const char* str, uint32_t color); +size_t render_text_columns(const char* str); +size_t render_text_width(const char* str); #endif diff --git a/dispd/client/session.h b/libui/pixel.c similarity index 57% rename from dispd/client/session.h rename to libui/pixel.c index 821f82cf..cf449d10 100644 --- a/dispd/client/session.h +++ b/libui/pixel.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Jonas 'Sortie' Termansen. + * Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -13,30 +13,26 @@ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - * session.h - * Handles session management. + * pixel.c + * Pixel functions. */ -#ifndef INCLUDE_DISPD_SESSION_H -#define INCLUDE_DISPD_SESSION_H +#include -#if defined(__cplusplus) -extern "C" { -#endif +#include "pixel.h" -struct dispd_session +uint32_t blend_pixel(uint32_t bg_value, uint32_t fg_value) { - size_t refcount; - uint64_t device; - uint64_t connector; - struct dispd_window* current_window; - bool is_rgba; -}; - -bool dispd__session_initialize(int* argc, char*** argv); - -#if defined(__cplusplus) -} /* extern "C" */ -#endif - -#endif + union color_rgba8 fg; fg.value = fg_value; + union color_rgba8 bg; bg.value = bg_value; + if ( fg.a == 255 ) + return fg.value; + if ( fg.a == 0 ) + return bg.value; + union color_rgba8 ret; + ret.a = 255; + ret.r = ((255-fg.a)*bg.r + fg.a*fg.r) / 256; + ret.g = ((255-fg.a)*bg.g + fg.a*fg.g) / 256; + ret.b = ((255-fg.a)*bg.b + fg.a*fg.b) / 256; + return ret.value; +} diff --git a/libui/vgafont.c b/libui/vgafont.c new file mode 100644 index 00000000..9540fc44 --- /dev/null +++ b/libui/vgafont.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2014, 2015, 2016, 2017 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * vgafont.c + * VGA font rendering. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "framebuffer.h" +#include "vgafont.h" + +static const wchar_t REPLACEMENT_CHARACTER = 0xFFFD; + +uint8_t font[FONT_CHARSIZE * FONT_NUMCHARS]; + +void load_font(void) +{ + int fd = open("/dev/vgafont", O_RDONLY); + if ( fd < 0 ) + err(1, "/dev/vgafont"); + if ( readall(fd, font, sizeof(font)) != sizeof(font) ) + err(1, "/dev/vgafont"); + close(fd); +} + +// https://en.wikipedia.org/wiki/Code_page_437 +static inline int map_wide_to_vga_font(wchar_t c) +{ + if ( 32 <= c && c < 127 ) + return (int) c; + switch ( c ) + { + case L'☺': return 1; + case L'☻': return 2; + case L'♥': return 3; + case L'♦': return 4; + case L'♣': return 5; + case L'♠': return 6; + case L'•': return 7; + case L'◘': return 8; + case L'○': return 9; + case L'◙': return 10; + case L'♂': return 11; + case L'♀': return 12; + case L'♪': return 13; + case L'♬': return 14; + case L'☼': return 15; + case L'►': return 16; + case L'◄': return 17; + case L'↕': return 18; + case L'‼': return 19; + case L'¶': return 20; + case L'§': return 21; + case L'▬': return 22; + case L'↨': return 23; + case L'↑': return 24; + case L'↓': return 25; + case L'→': return 26; + case L'←': return 27; + case L'∟': return 28; + case L'↔': return 29; + case L'▲': return 30; + case L'▼': return 31; + case L'⌂': return 127; + case L'Ç': return 128; + case L'ü': return 129; + case L'é': return 130; + case L'â': return 131; + case L'ä': return 132; + case L'à': return 133; + case L'å': return 134; + case L'ç': return 135; + case L'ê': return 136; + case L'ë': return 137; + case L'è': return 138; + case L'ï': return 139; + case L'î': return 140; + case L'ì': return 141; + case L'Ä': return 142; + case L'Å': return 143; + case L'É': return 144; + case L'æ': return 145; + case L'Æ': return 146; + case L'ô': return 147; + case L'ö': return 148; + case L'ò': return 149; + case L'û': return 150; + case L'ù': return 151; + case L'ÿ': return 152; + case L'Ö': return 153; + case L'Ü': return 154; + case L'¢': return 155; + case L'£': return 156; + case L'¥': return 157; + case L'₧': return 158; + case L'ƒ': return 159; + case L'á': return 160; + case L'í': return 161; + case L'ó': return 162; + case L'ú': return 163; + case L'ñ': return 164; + case L'Ñ': return 165; + case L'ª': return 166; + case L'º': return 167; + case L'¿': return 168; + case L'⌐': return 169; + case L'¬': return 170; + case L'½': return 171; + case L'¼': return 172; + case L'¡': return 173; + case L'«': return 174; + case L'»': return 175; + case L'░': return 176; + case L'▒': return 177; + case L'▓': return 178; + case L'│': return 179; + case L'┤': return 180; + case L'╡': return 181; + case L'╢': return 182; + case L'╖': return 183; + case L'╕': return 184; + case L'╣': return 185; + case L'║': return 186; + case L'╗': return 187; + case L'╝': return 188; + case L'╜': return 189; + case L'╛': return 190; + case L'┐': return 191; + case L'└': return 192; + case L'┴': return 193; + case L'┬': return 194; + case L'├': return 195; + case L'─': return 196; + case L'┼': return 197; + case L'╞': return 198; + case L'╟': return 199; + case L'╚': return 200; + case L'╔': return 201; + case L'╩': return 202; + case L'╦': return 203; + case L'╠': return 204; + case L'═': return 205; + case L'╬': return 206; + case L'╧': return 207; + case L'╨': return 208; + case L'╤': return 209; + case L'╥': return 210; + case L'╙': return 211; + case L'╘': return 212; + case L'╒': return 213; + case L'╓': return 214; + case L'╫': return 215; + case L'╪': return 216; + case L'┘': return 217; + case L'┌': return 218; + case L'█': return 219; + case L'▄': return 220; + case L'▌': return 221; + case L'▐': return 222; + case L'▀': return 223; + case L'α': return 224; + case L'ß': return 225; /* German sharp S U+00DF */ + case L'β': return 225; /* Greek lowercase beta U+03B2 */ + case L'Γ': return 226; + case L'π': return 227; + case L'Σ': return 228; /* Greek uppercase sigma U+03A3 */ + case L'∑': return 228; /* n-ary summation sign U+2211 (replacement) */ + case L'σ': return 229; + case L'µ': return 230; + case L'τ': return 231; + case L'Φ': return 232; + case L'Θ': return 233; + case L'Ω': return 234; + case L'δ': return 235; /* Greek lowercase delta U+03B4 */ + case L'ð': return 235; /* Icelandic lowercase eth U+00F0 (replacement) */ + case L'∂': return 235; /* Partial derivative sign U+2202 (replacement) */ + case L'∞': return 236; + case L'φ': return 237; /* Greek lowercase phi U+03C6 */ + case L'∅': return 237; /* Empty set sign U+2205 (replacement) */ + case L'ϕ': return 237; /* Greek phi symbol in italics U+03D5 (replacement) */ + case L'⌀': return 237; /* Diameter sign U+2300 (replacement) */ + case L'ø': return 237; /* Latin lowercase O with stroke U+00F8 (replacement) */ + case L'Ø': return 237; /* Latin uppercase O with stroke U+00D8 (replacement) */ + case L'ε': return 238; /* Greek lowercase epsilon U+03B5 */ + case L'∈': return 238; /* Element-of sign U+2208 */ + case L'€': return 238; /* Euro sign U+20AC */ + case L'∩': return 239; + case L'≡': return 240; + case L'±': return 241; + case L'≥': return 242; + case L'≤': return 243; + case L'⌠': return 244; + case L'⌡': return 245; + case L'÷': return 246; + case L'≈': return 247; + case L'°': return 248; + case L'∙': return 249; + case L'·': return 250; + case L'√': return 251; + case L'ⁿ': return 252; + case L'²': return 253; + case L'■': return 254; + default: return 0 <= c && c < 256 ? c : -1; + } +} + +static const uint8_t font_replacement_character[16] = +{ + 0b00000000, + 0b00010000, + 0b00111000, + 0b01000100, + 0b10111010, + 0b10111010, + 0b11110110, + 0b11101110, + 0b11101110, + 0b11111110, + 0b01101100, + 0b00101000, + 0b00010000, + 0b00000000, + 0b00000000, + 0b00000000, +}; + +static inline const uint8_t* get_character_font(const uint8_t* font, int remap) +{ + if ( remap < 0 ) + return font_replacement_character; + return font + 16 * remap; +} + +void render_char(struct framebuffer fb, wchar_t wc, uint32_t color) +{ + // TODO: Special case the rendering of some block drawing characters like in + // the kernel so pstree looks nice. + int remap = map_wide_to_vga_font(wc); + const uint8_t* charfont = get_character_font(font, remap); + uint32_t buffer[FONT_HEIGHT * (FONT_REALWIDTH+1)]; + for ( size_t y = 0; y < FONT_HEIGHT; y++ ) + { + uint8_t line_bitmap = charfont[y]; + for ( size_t x = 0; x < FONT_REALWIDTH; x++ ) + buffer[y * (FONT_REALWIDTH+1) + x] = line_bitmap & 1U << (7 - x) ? color : 0; + uint32_t last_color = 0; + if ( 0xB0 <= remap && remap <= 0xDF && (line_bitmap & 1) ) + last_color = color; + buffer[y * (FONT_REALWIDTH+1) + 8] = last_color; + } + + struct framebuffer character_fb; + character_fb.xres = FONT_WIDTH; + character_fb.yres = FONT_HEIGHT; + character_fb.pitch = character_fb.xres; + character_fb.buffer = buffer; + + framebuffer_copy_to_framebuffer_blend(fb, character_fb); +} + +void render_text(struct framebuffer fb, const char* str, uint32_t color) +{ + mbstate_t ps; + memset(&ps, 0, sizeof(ps)); + size_t column = 0; + for ( size_t i = 0; true; i++ ) + { + wchar_t wc; + size_t amount = mbrtowc(&wc, str + i, 1, &ps); + if ( amount == (size_t) -2 ) + continue; + if ( amount == (size_t) -1 ) + { + wc = REPLACEMENT_CHARACTER; + memset(&ps, 0, sizeof(ps)); + } + if ( amount == (size_t) 0 ) + break; + int width = wcwidth(wc); + if ( 0 < width ) + { + render_char(framebuffer_crop(fb, FONT_REALWIDTH * column, 0, + fb.xres, fb.yres), wc, color); + column += width; // TODO: Overflow. + } + if ( amount == (size_t) -1 && str[i] == '\0' ) + break; + } +} + +size_t render_text_columns(const char* str) +{ + mbstate_t ps; + memset(&ps, 0, sizeof(ps)); + size_t column = 0; + for ( size_t i = 0; true; i++ ) + { + wchar_t wc; + size_t amount = mbrtowc(&wc, str + i, 1, &ps); + if ( amount == (size_t) -2 ) + continue; + if ( amount == (size_t) -1 ) + { + wc = REPLACEMENT_CHARACTER; + memset(&ps, 0, sizeof(ps)); + } + if ( amount == (size_t) 0 ) + break; + int width = wcwidth(wc); + if ( 0 < width ) + column += width; // TODO: Overflow. + if ( amount == (size_t) -1 && str[i] == '\0' ) + break; + } + return column; +} + +size_t render_text_width(const char* str) +{ + // TODO: Overflow. + return FONT_WIDTH * render_text_columns(str); +} diff --git a/login/graphical.c b/login/graphical.c index 10e677e5..6ebe464a 100644 --- a/login/graphical.c +++ b/login/graphical.c @@ -198,12 +198,62 @@ void render_right_text_if_needed(struct framebuffer fb, const char* str, uint32_ render_right_text(fb, str, color); } +union c { struct { uint8_t b; uint8_t g; uint8_t r; }; uint32_t v; }; + static void render_background(struct framebuffer fb) { +#if 0 uint32_t bg_color = make_color(0x89 * 2/3, 0xc7 * 2/3, 0xff * 2/3); for ( size_t y = 0; y < fb.yres; y++ ) for ( size_t x = 0; x < fb.xres; x++ ) framebuffer_set_pixel(fb, x, y, bg_color); +#endif + static uint32_t s; + static uint32_t t; + static bool seeded = false; + if ( !seeded ) + { + s = arc4random(); + t = arc4random(); + seeded = true; + } + for ( size_t y = 0; y < fb.yres; y++ ) + { + for ( size_t x = 0; x < fb.xres; x++ ) + { + uint32_t r = 3793 * x + 6959 * y + 1889 * t + 7901 * s; + r ^= (5717 * x * 2953 * y) ^ s ^ t; + r = (r >> 24) ^ (r >> 16) ^ (r >> 8) ^ r; + union c c; + if ( x && (r & 0x3) == 2 ) + c.v = framebuffer_get_pixel(fb, x - 1, y); + else if ( y && (r & 0x3) == 1 ) + c.v = framebuffer_get_pixel(fb, x, y - 1); + else if ( x && y ) + c.v = framebuffer_get_pixel(fb, x - 1, y - 1); + else + { + c.v = t; + c.r = (c.r & 0xc0) | (r >> 0 & 0x3f); + c.g = (c.g & 0xc0) | (r >> 4 & 0x3f); + c.b = (c.b & 0xc0) | (r >> 8 & 0x3f); + } + if ( (r & 0xf0) == 0x10 && c.r ) c.r--; + if ( (r & 0xf0) == 0x20 && c.g ) c.g--; + if ( (r & 0xf0) == 0x30 && c.b ) c.b--; + if ( (r & 0xf0) == 0x40 && c.r != 255 ) c.r++; + if ( (r & 0xf0) == 0x50 && c.g != 255 ) c.g++; + if ( (r & 0xf0) == 0x60 && c.b != 255 ) c.b++; + union c tc = {.v = t}; + if ( c.r && c.r - tc.r > (int8_t) (r >> 0) + 64 ) c.r--; + if ( c.r != 255 && tc.r - c.r > (int8_t) (r >> 4) + 240 ) c.r++; + if ( c.g && c.g - tc.g > (int8_t) (r >> 8) + 64) c.g--; + if ( c.g != 255 && tc.g - c.g > (int8_t) (r >> 12) + 240 ) c.g++; + if ( c.b && c.b - tc.b > (int8_t) (r >> 16) + 64 ) c.b--; + if ( c.b != 255 && tc.b - c.b > (int8_t) (r >> 20) + 240 ) c.b++; + framebuffer_set_pixel(fb, x, y, c.v); + } + } } static void render_pointer(struct framebuffer fb) diff --git a/ports/libSDL/libSDL.patch b/ports/libSDL/libSDL.patch index 9fe8d10d..5b94df47 100644 --- a/ports/libSDL/libSDL.patch +++ b/ports/libSDL/libSDL.patch @@ -1,3 +1,26 @@ +diff -Paur --no-dereference -- libSDL.upstream/configure libSDL/configure +--- libSDL.upstream/configure ++++ libSDL/configure +@@ -19338,7 +19338,7 @@ + + #include + #include +- #include ++ #include + + int + main () +@@ -19362,8 +19362,8 @@ + + SOURCES="$SOURCES $srcdir/src/video/sortix/*.c" + have_video=yes +- EXTRA_LDFLAGS="$EXTRA_LDFLAGS -ldispd" +- SDL_LIBS="$SDL_LIBS -ldispd" ++ EXTRA_LDFLAGS="$EXTRA_LDFLAGS -ldisplay" ++ SDL_LIBS="$SDL_LIBS -ldisplay" + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for display support" >&5 + $as_echo_n "checking for display support... " >&6; } diff -Paur --no-dereference -- libSDL.upstream/make.sortix libSDL/make.sortix --- libSDL.upstream/make.sortix +++ libSDL/make.sortix @@ -10,3 +33,358 @@ diff -Paur --no-dereference -- libSDL.upstream/make.sortix libSDL/make.sortix +if [ "$1" = "install" ]; then +rm -fv "$DESTDIR/$EXEC_PREFIX/bin/sdl-config" +fi +diff -Paur --no-dereference -- libSDL.upstream/sdl.pc libSDL/sdl.pc +--- libSDL.upstream/sdl.pc ++++ libSDL/sdl.pc +@@ -10,6 +10,6 @@ + Version: 1.2.15 + Requires: + Conflicts: +-Libs: -L${libdir} -lSDL -ldispd -ldisplay +-Libs.private: -lSDL -ldispd -ldisplay -liconv -lm -ldispd -ldisplay +-Cflags: -I${includedir}/SDL ++Libs: -L${libdir} -lSDL -ldisplay -ldisplay ++Libs.private: -lSDL -ldisplay -ldisplay -liconv -lm -ldisplay -ldisplay ++Cflags: -I${includedir}/SDL -D_GNU_SOURCE=1 +diff -Paur --no-dereference -- libSDL.upstream/src/video/sortix/SDL_dispd.c libSDL/src/video/sortix/SDL_dispd.c +--- libSDL.upstream/src/video/sortix/SDL_dispd.c ++++ libSDL/src/video/sortix/SDL_dispd.c +@@ -28,14 +28,11 @@ + #include + #include + #include +-#include +-#if __has_include() +-#define DISPLAY +-#include +-#endif + #include + #include + ++#include ++ + #include "SDL_video.h" + #include "../SDL_sysvideo.h" + #include "../../events/SDL_sysevents.h" +@@ -64,30 +61,12 @@ + + static void DispD_DeleteDevice(SDL_VideoDevice *device) + { +-#ifdef DISPLAY + if ( device->hidden->connection ) { + display_destroy_window(device->hidden->connection, + device->hidden->window_id); + display_disconnect(device->hidden->connection); + device->hidden->connection = NULL; + } +-#endif +- if ( device->hidden->fbinfo ) { +- dispd_finish_render(device->hidden->fbinfo); +- device->hidden->fbinfo = NULL; +- } +- if ( device->hidden->window ) { +- dispd_destroy_window(device->hidden->window); +- device->hidden->window = NULL; +- } +- if ( device->hidden->session ) { +- dispd_detach_session(device->hidden->session); +- device->hidden->session = NULL; +- } +- if ( 0 < device->hidden->tty_fd ) { +- close(device->hidden->tty_fd); +- device->hidden->tty_fd = -1; +- } + SDL_free(device->hidden); + SDL_free(device); + } +@@ -112,68 +91,20 @@ + } + SDL_memset(device->hidden, 0, (sizeof *device->hidden)); + +-#ifdef DISPLAY +- if ( (device->hidden->connection = display_connect_default()) ) { +- device->hidden->disconnected = 0; +- device->hidden->window_id = 0; +- device->hidden->window_width = 0; +- device->hidden->window_height = 0; +- display_create_window(device->hidden->connection, +- device->hidden->window_id); +- } else { +-#endif +- static int has_initialized_dispd = 0; +- if ( !has_initialized_dispd ) { +- if ( !dispd_initialize(NULL, NULL) ) { +- return(0); +- } +- has_initialized_dispd = 1; +- } +- if ( (device->hidden->tty_fd = open("/dev/tty", O_RDONLY)) < 0 ) { +- DispD_DeleteDevice(device); +- return(0); +- } +- +- if ( (device->hidden->session = dispd_attach_default_session()) == NULL ) { +- DispD_DeleteDevice(device); +- return(0); +- } +- +- if ( !(dispd_session_setup_game_rgba(device->hidden->session)) ) { +- DispD_DeleteDevice(device); +- return(0); +- } ++ if ( !(device->hidden->connection = display_connect_default()) ) { ++ return(0); ++ } + +- if ( (device->hidden->window = +- dispd_create_window_game_rgba(device->hidden->session)) == NULL ) { +- DispD_DeleteDevice(device); +- return(0); +- } ++ device->hidden->disconnected = 0; ++ device->hidden->window_id = 0; ++ device->hidden->window_width = 0; ++ device->hidden->window_height = 0; ++ display_create_window(device->hidden->connection, ++ device->hidden->window_id); + +- if ( (device->hidden->fbinfo = +- dispd_begin_render(device->hidden->window)) == NULL ) { +- DispD_DeleteDevice(device); +- return(0); +- } +-#ifdef DISPLAY +- } +-#endif ++ device->hidden->current_mode.w = 800; ++ device->hidden->current_mode.h = 600; + +- device->hidden->current_mode.x = 0; +- device->hidden->current_mode.y = 0; +-#ifdef DISPLAY +- if ( device->hidden->connection ) { +- device->hidden->current_mode.w = 800; +- device->hidden->current_mode.h = 600; +- } else { +-#endif +- device->hidden->current_mode.w = +- dispd_get_framebuffer_width(device->hidden->fbinfo); +- device->hidden->current_mode.h = +- dispd_get_framebuffer_height(device->hidden->fbinfo); +-#ifdef DISPLAY +- } +-#endif + device->hidden->mode_list[0] = &device->hidden->current_mode; + device->hidden->mode_list[1] = NULL; + +@@ -217,13 +148,8 @@ + SDL_Rect **DispD_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags) + { + // TODO: Return NULL if the format isn't 32-bit supported. +-#ifdef DISPLAY +- if ( this->hidden->connection ) { +- // TODO: qemu seems to pick too little a resolution due to this. +- return((SDL_Rect **)-1); +- } +-#endif +- return(this->hidden->mode_list); ++ // TODO: qemu seems to pick too little a resolution due to this. ++ return((SDL_Rect **)-1); + } + + SDL_Surface *DispD_SetVideoMode(_THIS, SDL_Surface *current, +@@ -234,28 +160,16 @@ + + bpp = 32; + +-#ifdef DISPLAY +- if ( this->hidden->connection ) { +- current->flags = SDL_RESIZABLE; +- size_t size = (size_t)width * (size_t)width * (bpp / 8); +- data = SDL_malloc(size); +- if ( !data ) +- return(NULL); +- this->hidden->window_width = width; +- this->hidden->window_height = height; +- display_resize_window(this->hidden->connection, +- this->hidden->window_id, width, height); +- pitch = (size_t) width * (bpp / 8); +- } else { +-#endif +- data = dispd_get_framebuffer_data(this->hidden->fbinfo); +- width = dispd_get_framebuffer_width(this->hidden->fbinfo); +- height = dispd_get_framebuffer_height(this->hidden->fbinfo); +- pitch = dispd_get_framebuffer_pitch(this->hidden->fbinfo); +- current->flags = SDL_FULLSCREEN; +-#ifdef DISPLAY +- } +-#endif ++ current->flags = SDL_RESIZABLE; ++ size_t size = (size_t)width * (size_t)width * (bpp / 8); ++ data = SDL_malloc(size); ++ if ( !data ) ++ return(NULL); ++ this->hidden->window_width = width; ++ this->hidden->window_height = height; ++ display_resize_window(this->hidden->connection, ++ this->hidden->window_id, width, height); ++ pitch = (size_t) width * (bpp / 8); + + int y; + for ( y = 0; y < height; y++ ) +@@ -270,7 +184,7 @@ + assert(current->format); + assert(current->format->BitsPerPixel == 32); + current->pitch = pitch; +- // TODO: Memory leak of old buffer? ++ free(current->pixels); + current->pixels = data; + current->w = width; + current->h = height; +@@ -291,12 +205,8 @@ + + static void DispD_SetCaption(_THIS, const char *title, const char *icon) + { +-#ifdef DISPLAY +- if ( this->hidden->connection) { +- display_title_window(this->hidden->connection, +- this->hidden->window_id, title); +- } +-#endif ++ display_title_window(this->hidden->connection, ++ this->hidden->window_id, title); + } + + /* We need to wait for vertical retrace on page flipped displays */ +@@ -312,26 +222,13 @@ + + static void DispD_UpdateRects(_THIS, int numrects, SDL_Rect *rects) + { +-#ifdef DISPLAY +- if ( this->hidden->connection) { +- for ( size_t i = 3; i < (size_t)SDL_VideoSurface->w * (size_t)SDL_VideoSurface->h * 4; i += 4 ) +- ((unsigned char*)SDL_VideoSurface->pixels)[i] = 255; +- display_render_window(this->hidden->connection, this->hidden->window_id, +- 0, 0, SDL_VideoSurface->w, SDL_VideoSurface->h, +- SDL_VideoSurface->pixels); +- display_show_window(this->hidden->connection, this->hidden->window_id); +- return; +- } +-#endif +- uint8_t* old_data = dispd_get_framebuffer_data(this->hidden->fbinfo); +- if ( !dispd_finish_render(this->hidden->fbinfo) ) { +- abort(); +- } +- if ( !(this->hidden->fbinfo = dispd_begin_render(this->hidden->window)) ) { +- abort(); +- } +- uint8_t* new_data = dispd_get_framebuffer_data(this->hidden->fbinfo); +- assert(old_data == new_data); ++ size_t size = (size_t)SDL_VideoSurface->w * (size_t)SDL_VideoSurface->h * 4; ++ for ( size_t i = 3; i < size; i += 4 ) ++ ((unsigned char*)SDL_VideoSurface->pixels)[i] = 255; ++ display_render_window(this->hidden->connection, this->hidden->window_id, ++ 0, 0, SDL_VideoSurface->w, SDL_VideoSurface->h, ++ SDL_VideoSurface->pixels); ++ display_show_window(this->hidden->connection, this->hidden->window_id); + } + + int DispD_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors) +@@ -345,6 +242,7 @@ + */ + void DispD_VideoQuit(_THIS) + { ++ free(this->screen->pixels); + this->screen->pixels = NULL; + } + +@@ -424,7 +322,6 @@ + return 0; + } + +-#ifdef DISPLAY + static void on_disconnect(void* ctx) + { + struct SDL_PrivateVideoData* hidden = (struct SDL_PrivateVideoData*) ctx; +@@ -463,48 +360,19 @@ + keysym.unicode = 0; + SDL_PrivateKeyboard(kbkey < 0 ? SDL_RELEASED : SDL_PRESSED, &keysym); + } +-#endif + + void DispD_PumpEvents(_THIS) + { +-#ifdef DISPLAY +- if ( this->hidden->connection) { +- struct display_event_handlers handlers; +- memset(&handlers, 0, sizeof(handlers)); +- handlers.context = this->hidden; +- handlers.disconnect_handler = on_disconnect; +- handlers.quit_handler = on_quit; +- handlers.resize_handler = on_resize; +- handlers.keyboard_handler = on_keyboard; +- while ( !this->hidden->disconnected ) { +- if ( display_poll_event(this->hidden->connection, &handlers) < 0 ) +- break; +- } +- return; +- } +-#endif +- +- // Read the keyboard input from the user. +- const unsigned termmode = TERMMODE_KBKEY +- | TERMMODE_UNICODE +- | TERMMODE_SIGNAL +- | TERMMODE_NONBLOCK; +- if ( settermmode(0, termmode) ) { +- return; +- } +- uint32_t codepoint; +- ssize_t numbytes; +- while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) ) +- { +- int kbkey = KBKEY_DECODE(codepoint); +- int abskbkey = kbkey < 0 ? -kbkey : kbkey; +- int key = TranslateKey(abskbkey); +- SDL_keysym keysym; +- keysym.scancode = abskbkey; +- keysym.sym = key; +- keysym.mod = 0; +- keysym.unicode = 0; +- SDL_PrivateKeyboard(kbkey < 0 ? SDL_RELEASED : SDL_PRESSED, &keysym); ++ struct display_event_handlers handlers; ++ memset(&handlers, 0, sizeof(handlers)); ++ handlers.context = this->hidden; ++ handlers.disconnect_handler = on_disconnect; ++ handlers.quit_handler = on_quit; ++ handlers.resize_handler = on_resize; ++ handlers.keyboard_handler = on_keyboard; ++ while ( !this->hidden->disconnected ) { ++ if ( display_poll_event(this->hidden->connection, &handlers) < 0 ) ++ break; + } + } + +diff -Paur --no-dereference -- libSDL.upstream/src/video/sortix/SDL_dispd.h libSDL/src/video/sortix/SDL_dispd.h +--- libSDL.upstream/src/video/sortix/SDL_dispd.h ++++ libSDL/src/video/sortix/SDL_dispd.h +@@ -33,19 +33,13 @@ + /* Private display data */ + + struct SDL_PrivateVideoData { +-#ifdef DISPLAY + struct display_connection *connection; + uint32_t window_id; + uint32_t window_width; + uint32_t window_height; + int disconnected; +-#endif +- struct dispd_session *session; +- struct dispd_window *window; +- struct dispd_framebuffer *fbinfo; + SDL_Rect current_mode; + SDL_Rect *mode_list[2]; +- int tty_fd; + }; + + #endif /* _SDL_nullvideo_h */ diff --git a/share/init/single-user-gui b/share/init/single-user-gui new file mode 100644 index 00000000..8e0d11f2 --- /dev/null +++ b/share/init/single-user-gui @@ -0,0 +1,9 @@ +require base no-await +require local no-await + +tty tty1 +need tty + +cd "$HOME" +exit-code-meaning poweroff-reboot +exec display diff --git a/share/init/sysinstall-gui b/share/init/sysinstall-gui new file mode 100644 index 00000000..3d9b1a96 --- /dev/null +++ b/share/init/sysinstall-gui @@ -0,0 +1,8 @@ +require base no-await +require local no-await + +tty tty1 +need tty + +exec display terminal sysinstall +exit-code-meaning poweroff-reboot diff --git a/share/init/sysupgrade-gui b/share/init/sysupgrade-gui new file mode 100644 index 00000000..6fafaf5e --- /dev/null +++ b/share/init/sysupgrade-gui @@ -0,0 +1,8 @@ +require base no-await +require local no-await + +tty tty1 +need tty + +exec display terminal sysupgrade +exit-code-meaning poweroff-reboot diff --git a/share/man/man5/autoinstall.conf.5 b/share/man/man5/autoinstall.conf.5 index 2bc3a499..1fa0ca50 100644 --- a/share/man/man5/autoinstall.conf.5 +++ b/share/man/man5/autoinstall.conf.5 @@ -241,6 +241,12 @@ Copy the file (if it exists) into the installation? .It Sy empty_password Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) Allow insecure empty passwords for regular users? +.It Sy enable_gui Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy yes ) +Enable the +.Xr display 1 +graphical user interface? +The choice is remembered in +.Xr session 5 . .It Sy enable_ntpd Ns "=" Ns Oo Sy no "|" Sy yes Oc ( default Sy no ) Automatically get time from the network using .Xr ntpd 8 ? diff --git a/share/man/man5/init.5 b/share/man/man5/init.5 index ef159d76..2e4c3688 100644 --- a/share/man/man5/init.5 +++ b/share/man/man5/init.5 @@ -116,6 +116,16 @@ It depends on the and .Sy local daemons. +.It Sy single-user-gui +Like +.Sy single-user , +but runs the root shell in +.Xr terminal 1 +inside the +.Xr display 1 +graphical user interface environment. +This operating system mode is insecure because it boots straight to root access +without a password. .It Sy sysinstall Starts the operating system installer. This foreground daemon starts the @@ -129,6 +139,16 @@ It depends on the and .Sy local daemons. +.It Sy sysinstall-gui +Like +.Sy sysinstall , +but runs it in +.Xr terminal 1 +inside the +.Xr display 1 +graphical user interface environment. +This operating system mode is insecure because it boots straight to root access +without a password. .It Sy sysupgrade Starts the operating system upgrader. This foreground daemon starts the @@ -142,6 +162,16 @@ It depends on the and .Sy local daemons. +.It Sy sysupgrade-gui +Like +.Sy sysupgrade , +but runs it in +.Xr terminal 1 +inside the +.Xr display 8 +graphical user interface environment. +This operating system mode is insecure because it boots straight to root access +without a password. .El .Pp The following daemons are provided by the system: diff --git a/share/man/man5/session.5 b/share/man/man5/session.5 index 249c4ff4..3488f662 100644 --- a/share/man/man5/session.5 +++ b/share/man/man5/session.5 @@ -37,6 +37,20 @@ file can be created in any text editor and then made executable: editor ~/.session chmod +x ~/.session .Ed +.Ss Graphical User Interface +.Xr display 1 +can be selected as the user's graphical user interface with this executable +.Pa ~/.session +script: +.Bd -literal -offset indent +#!/bin/sh +exec display +.Ed +.Pp +.Xr display 1 +will run the +.Xr displayrc 5 +script on startup, which can be used to start applications. .Ss Trianglix .Xr trianglix 1 can be selected as the user's triangle environment with this executable diff --git a/share/man/man7/installation.7 b/share/man/man7/installation.7 index 078700d1..b4f36f1b 100644 --- a/share/man/man7/installation.7 +++ b/share/man/man7/installation.7 @@ -156,6 +156,15 @@ Ports can additionally be loaded as binary packages in the directory by navigating to the advanced menu and then the select binary packages submenu and then selecting which ports. .Pp +The +.Xr display 1 +graphical user interface and desktop environment can be disabled by navigating +to the advanced menu and selecting +.Sy Disable GUI , +which will instead boot to a plain +.Pa /dev/tty1 +terminal. +.Pp The network drivers can be disabled by navigating to the advanced menu and selecting .Sy Disable network drivers . @@ -180,6 +189,17 @@ If not, you can run the installer by running the .Xr sysinstall 8 command. .Pp +You will boot into the +.Xr display 1 +graphical user interface and desktop environment by default. +A single +.Xr terminal 1 +window will open by default. +More terminals can be opened by pressing Control + Alt + T. +See +.Xr display 1 +for the available shortcuts. +.Pp The installer is an interactive command line program that asks you questions and you answer them. It provides useful information you shouldn't accidentally overlook. @@ -386,6 +406,14 @@ and .Pp Please note that Sortix is not currently secure as a multi-user system and filesystem permissions are not enforced. +.Ss Graphical User Interface +You will be asked if you want to enable the graphical user interface. +If you answer yes, then the system-wide default +.Xr session 5 +is configured to run +.Xr display 1 +upon login. +Otherwise the user's preferred shell will be run upon login. .Ss Network Time You will be asked if you want to enable the Network Time Protocol client .Xr ntpd 8 , @@ -519,6 +547,7 @@ fragment instead. .Sh SEE ALSO .Xr chkblayout 1 , .Xr chvideomode 1 , +.Xr display 1 , .Xr man 1 , .Xr fstab 5 , .Xr group 5 , diff --git a/share/man/man7/release-iso-modification.7 b/share/man/man7/release-iso-modification.7 index ac6e0d8e..057865b3 100644 --- a/share/man/man7/release-iso-modification.7 +++ b/share/man/man7/release-iso-modification.7 @@ -514,6 +514,12 @@ ssh-keygen -t rsa -f liveconfig/root/.ssh/id_rsa -N "" -C "root@$hostname" Consider omitting the .Fl N option and password protect the private key to protect it in the case of a leak. +.Ss Boot to Console Instead of GUI By Default +To customize a release so it boots to a console instead of the GUI: +.Bd -literal +tix-iso-bootconfig --disable-gui bootconfig +tix-iso-add sortix.iso bootconfig +.Ed .Ss Automatic Installation To customize a release so it automatically installs itself per the .Xr autoinstall.conf 5 : diff --git a/share/man/man7/user-guide.7 b/share/man/man7/user-guide.7 index fcf4357c..eaf8536c 100644 --- a/share/man/man7/user-guide.7 +++ b/share/man/man7/user-guide.7 @@ -14,26 +14,34 @@ The installation process is covered in Bootable cdrom releases will offer the options of running a live environment, installing the operating system, or upgrading an existing installation. .Pp -You will be presented a with standard Unix command line environment upon login or -booting the live environment. -.Ss Shutdown -.Xr init 8 -spawns a session after boot. -This is -.Xr login 8 -if the system is booted in multi-user mode. -This is a root shell if booted in single-user mode. +You will be presented with a graphical Unix-like command line environment upon +login or booting the live environment. +.Ss Desktop Environment +The +.Xr display 1 +desktop environment is automatically started when booting the live environment +or after logging into an installation. .Pp -To power off the computer login as user -.Sy poweroff -or run +A new +.Xr terminal 1 +can be launched by pressing Control + Alt + T. +.Pp +The desktop environment can be exited by pressing Control + Alt + Delete, +which will return to the login screen (in installations) or power off the +computer (in the live environment). +.Pp +See +.Xr display 1 +for all the available keyboard shortcuts. +.Ss Shutdown +To power off the computer, run .Xr poweroff 8 -after logging in. -To reboot the computer login as user -.Sy reboot -or run +or login as +.Sy poweroff . +To reboot the computter, run .Xr reboot 8 -after logging in. +or login as +.Sy reboot . .Ss Keyboard Layout The kernel has a default US keyboard layout compiled into it. .Pp diff --git a/sysinstall/Makefile b/sysinstall/Makefile index ad11b1df..e18e71ae 100644 --- a/sysinstall/Makefile +++ b/sysinstall/Makefile @@ -60,13 +60,13 @@ install: all touch $(DESTDIR)$(DATADIR)/sysinstall/hooks/sortix-1.1-group sysinstall: $(SYSINSTALL_OBJS) - $(CC) $(SYSINSTALL_OBJS) -o $@ -lmount + $(CC) $(SYSINSTALL_OBJS) -o $@ -lmount -ldisplay sysmerge: $(SYSMERGE_OBJS) $(CC) $(SYSMERGE_OBJS) -o $@ -lmount sysupgrade: $(SYSUPGRADE_OBJS) - $(CC) $(SYSUPGRADE_OBJS) -o $@ -lmount + $(CC) $(SYSUPGRADE_OBJS) -o $@ -lmount -ldisplay %.o: %.c $(CC) $(CFLAGS) $(CPPFLAGS) -std=gnu11 -c $< -o $@ diff --git a/sysinstall/interactive.c b/sysinstall/interactive.c index bb82ed4b..43974e58 100644 --- a/sysinstall/interactive.c +++ b/sysinstall/interactive.c @@ -1,5 +1,6 @@ /* - * Copyright (c) 2015, 2016, 2017 Jonas 'Sortie' Termansen. + * Copyright (c) 2015, 2016, 2017, 2023 Jonas 'Sortie' Termansen. + * Copyright (c) 2023 Juhani 'nortti' Krekelä. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,11 +18,15 @@ * Interactive utility functions. */ +#include +#include #include #include #include +#include #include +#include #include #include #include @@ -31,10 +36,22 @@ #include #include +#include + #include "autoconf.h" #include "execute.h" #include "interactive.h" +#define REQUEST_DISPLAYS_ID 0 +#define REQUEST_DISPLAY_MODE_ID 1 + +static uint32_t displays_count; +static bool displays_count_received; + +static struct dispmsg_crtc_mode display_mode; +static int request_display_mode_error; +static bool display_mode_received; + void shlvl(void) { long shlvl = 0; @@ -259,3 +276,107 @@ bool missing_program(const char* program) warnx("%s: Program is absent", program); return true; } + +static void on_displays(void* ctx, uint32_t id, uint32_t displays) +{ + (void) ctx; + if ( id != REQUEST_DISPLAYS_ID ) + return; + displays_count = displays; + displays_count_received = true; +} + +static void on_display_mode(void* ctx, uint32_t id, + struct dispmsg_crtc_mode mode) +{ + (void) ctx; + if ( id != REQUEST_DISPLAY_MODE_ID ) + return; + display_mode = mode; + request_display_mode_error = 0; + display_mode_received = true; +} + +static void on_ack(void* ctx, uint32_t id, int32_t error) +{ + (void) ctx; + if ( id != REQUEST_DISPLAY_MODE_ID ) + return; + if ( error ) + { + request_display_mode_error = error; + display_mode_received = true; + } +} + +bool get_video_mode(struct dispmsg_crtc_mode* mode) +{ + if ( getenv("DISPLAY_SOCKET") ) + { + struct display_connection* connection = display_connect_default(); + if ( !connection ) + return false; + struct display_event_handlers handlers = {0}; + handlers.displays_handler = on_displays; + handlers.display_mode_handler = on_display_mode; + handlers.ack_handler = on_ack; + display_request_displays(connection, REQUEST_DISPLAYS_ID); + displays_count_received = false; + while ( !displays_count_received ) + display_wait_event(connection, &handlers); + if ( displays_count < 1 ) + { + display_disconnect(connection); + return false; + } + // TODO: Multimonitor support. + display_request_display_mode(connection, REQUEST_DISPLAY_MODE_ID, 0); + display_mode_received = false; + while ( !display_mode_received ) + display_wait_event(connection, &handlers); + display_disconnect(connection); + if ( request_display_mode_error ) + return false; + *mode = display_mode; + return true; + } + + struct tiocgdisplay display; + struct tiocgdisplays gdisplays; + memset(&gdisplays, 0, sizeof(gdisplays)); + gdisplays.count = 1; + gdisplays.displays = &display; + struct dispmsg_get_driver_name dgdn = { 0 }; + if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0 ) + return false; + dgdn.device = display.device; + dgdn.msgid = DISPMSG_GET_DRIVER_NAME; + dgdn.device = display.device; + dgdn.driver_index = 0; + dgdn.name.byte_size = 0; + dgdn.name.str = NULL; + if ( dispmsg_issue(&dgdn, sizeof(dgdn)) < 0 && errno == ENODEV ) + return false; + struct dispmsg_get_crtc_mode get_mode; + memset(&get_mode, 0, sizeof(get_mode)); + get_mode.msgid = DISPMSG_GET_CRTC_MODE; + get_mode.device = display.device; + get_mode.connector = display.connector; + // TODO: Still allow setting the video mode if none was already set. + if ( dispmsg_issue(&get_mode, sizeof(get_mode)) < 0 ) + return false; + *mode = get_mode.mode; + return true; +} + +void gui_shutdown(int code) +{ + if ( getenv("DISPLAY_SOCKET") ) + { + struct display_connection* connection = display_connect_default(); + if ( connection ) + display_shutdown(connection, code); + else + warn("display_connect_default"); + } +} diff --git a/sysinstall/interactive.h b/sysinstall/interactive.h index 0971bed6..2319f0b4 100644 --- a/sysinstall/interactive.h +++ b/sysinstall/interactive.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016 Jonas 'Sortie' Termansen. + * Copyright (c) 2015, 2016, 2023 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -42,5 +42,7 @@ void password(char* buffer, size_t buffer_size, const char* question); bool missing_program(const char* program); +bool get_video_mode(struct dispmsg_crtc_mode* mode); +void gui_shutdown(int code); #endif diff --git a/sysinstall/sysinstall.c b/sysinstall/sysinstall.c index cedc771a..832c9c1b 100644 --- a/sysinstall/sysinstall.c +++ b/sysinstall/sysinstall.c @@ -348,6 +348,7 @@ static bool etc_made = false; static char etc[] = "/tmp/etc.XXXXXX"; static bool fs_made = false; static char fs[] = "/tmp/fs.XXXXXX"; +static int exit_gui_code = -1; static void unmount_all_but_root(void) { @@ -376,6 +377,14 @@ void exit_handler(void) rmdir(fs); if ( etc_made ) execute((const char*[]) { "rm", "-rf", etc, NULL }, ""); + if ( 0 <= exit_gui_code ) + gui_shutdown(exit_gui_code); +} + +void exit_gui(int code) +{ + exit_gui_code = code; + exit(code); } static void cancel_on_sigint(int signum) @@ -544,7 +553,8 @@ int main(void) install_configurationf("upgrade.conf", "a", "src = yes\n"); - bool kblayout_setable = 0 <= tcgetblob(0, "kblayout", NULL, 0); + bool kblayout_setable = 0 <= tcgetblob(0, "kblayout", NULL, 0) || + getenv("DISPLAY_SOCKET"); while ( kblayout_setable ) { // TODO: Detect the name of the current keyboard layout. @@ -598,38 +608,16 @@ int main(void) text("\n"); } - struct tiocgdisplay display; - struct tiocgdisplays gdisplays; - memset(&gdisplays, 0, sizeof(gdisplays)); - gdisplays.count = 1; - gdisplays.displays = &display; - struct dispmsg_get_driver_name dgdn = { 0 }; - dgdn.msgid = DISPMSG_GET_DRIVER_NAME; - dgdn.device = 0; - dgdn.driver_index = 0; - dgdn.name.byte_size = 0; - dgdn.name.str = NULL; - if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) == 0 && - 0 < gdisplays.count && - (dgdn.device = display.device, true) && - (dispmsg_issue(&dgdn, sizeof(dgdn)) == 0 || errno != ENODEV) ) + struct dispmsg_crtc_mode mode; + if ( get_video_mode(&mode) ) { - struct dispmsg_get_crtc_mode get_mode; - memset(&get_mode, 0, sizeof(get_mode)); - get_mode.msgid = DISPMSG_GET_CRTC_MODE; - get_mode.device = 0; - get_mode.connector = 0; - bool good = false; - if ( dispmsg_issue(&get_mode, sizeof(get_mode)) == 0 ) + bool good = (mode.control & DISPMSG_CONTROL_VALID) && + (mode.control & DISPMSG_CONTROL_GOOD_DEFAULT); + if ( mode.control & DISPMSG_CONTROL_VM_AUTO_SCALE ) { - good = (get_mode.mode.control & DISPMSG_CONTROL_VALID) && - (get_mode.mode.control & DISPMSG_CONTROL_GOOD_DEFAULT); - if ( get_mode.mode.control & DISPMSG_CONTROL_VM_AUTO_SCALE ) - { - text("The display resolution will automatically change to " - "match the size of the virtual machine window.\n\n"); - good = true; - } + text("The display resolution will automatically change to " + "match the size of the virtual machine window.\n\n"); + good = true; } const char* def = non_interactive || good ? "no" : "yes"; while ( true ) @@ -648,14 +636,12 @@ int main(void) if ( execute((const char*[]) { "chvideomode", r, NULL }, "f") != 0 ) continue; input[0] = '\0'; - if ( dispmsg_issue(&get_mode, sizeof(get_mode)) < 0 || - !(get_mode.mode.control & DISPMSG_CONTROL_VALID) || - get_mode.mode.control & DISPMSG_CONTROL_VGA ) - break; + if ( !get_video_mode(&mode) || + !(mode.control & DISPMSG_CONTROL_VALID) || + mode.control & DISPMSG_CONTROL_VGA ) + continue; snprintf(input, sizeof(input), "%ux%ux%u", - get_mode.mode.view_xres, - get_mode.mode.view_yres, - get_mode.mode.fb_format); + mode.view_xres, mode.view_yres, mode.fb_format); break; } @@ -918,7 +904,7 @@ int main(void) { prompt(input, sizeof(input), "confirm_install", "Install " BRAND_DISTRIBUTION_NAME "? " - "(yes/no/poweroff/reboot/halt)", "yes"); + "(yes/no/exit/poweroff/reboot/halt)", "yes"); if ( !strcasecmp(input, "yes") ) break; else if ( !strcasecmp(input, "no") ) @@ -929,12 +915,14 @@ int main(void) "'halt' to cancel the installation.\n"); continue; } - else if ( !strcasecmp(input, "poweroff") ) + else if ( !strcasecmp(input, "exit") ) exit(0); + else if ( !strcasecmp(input, "poweroff") ) + exit_gui(0); else if ( !strcasecmp(input, "reboot") ) - exit(1); + exit_gui(1); else if ( !strcasecmp(input, "halt") ) - exit(2); + exit_gui(2); else continue; } @@ -1293,6 +1281,27 @@ int main(void) // TODO: Ask if networking should be disabled / enabled. + while ( true ) + { + prompt(input, sizeof(input), "enable_gui", + "Enable graphical user interface?", + getenv("DISPLAY_SOCKET") ? "yes" : "no"); + if ( strcasecmp(input, "no") == 0 ) + break; + if ( strcasecmp(input, "yes") != 0 ) + continue; + if ( !install_configurationf("etc/session", "w", + "#!sh\nexec display\n") || + chmod("etc/session", 0755) < 0 ) + { + warn("etc/session"); + continue; + } + text("Added 'exec display' to /etc/session\n"); + break; + } + text("\n"); + if ( !access_or_die("/tix/tixinfo/ntpd", F_OK) ) { text("A Network Time Protocol client (ntpd) has been installed that " @@ -1538,11 +1547,11 @@ int main(void) if ( !strcasecmp(input, "exit") ) exit(0); else if ( !strcasecmp(input, "poweroff") ) - exit(0); + exit_gui(0); else if ( !strcasecmp(input, "reboot") ) - exit(1); + exit_gui(1); else if ( !strcasecmp(input, "halt") ) - exit(2); + exit_gui(2); else if ( !strcasecmp(input, "boot") ) { if ( !access("/etc/fstab", F_OK) ) @@ -1557,7 +1566,7 @@ int main(void) "echo 'require chain exit-code' > " "/etc/init/default", NULL }, "ef"); - exit(3); + exit_gui(3); } else if ( !strcasecmp(input, "chroot") ) { diff --git a/sysinstall/sysupgrade.c b/sysinstall/sysupgrade.c index d55c2270..9a0f4251 100644 --- a/sysinstall/sysupgrade.c +++ b/sysinstall/sysupgrade.c @@ -73,6 +73,7 @@ static struct mountpoint* mountpoints; static size_t mountpoints_used; static bool fs_made = false; static char fs[] = "/tmp/fs.XXXXXX"; +static int exit_gui_code = -1; static bool add_installation(struct blockdevice* bdev, struct release* release, @@ -329,6 +330,14 @@ void exit_handler(void) } if ( fs_made ) rmdir(fs); + if ( 0 <= exit_gui_code ) + gui_shutdown(exit_gui_code); +} + +void exit_gui(int code) +{ + exit_gui_code = code; + exit(code); } static void cancel_on_sigint(int signum) @@ -457,7 +466,8 @@ int main(void) prompt(input, sizeof(input), "ready", "Ready?", ready); text("\n"); - bool kblayout_setable = 0 <= tcgetblob(0, "kblayout", NULL, 0); + bool kblayout_setable = 0 <= tcgetblob(0, "kblayout", NULL, 0) || + getenv("DISPLAY_SOCKET"); while ( kblayout_setable ) { // TODO: Detect the name of the current keyboard layout. @@ -499,38 +509,16 @@ int main(void) if ( kblayout_setable ) text("\n"); - struct tiocgdisplay display; - struct tiocgdisplays gdisplays; - memset(&gdisplays, 0, sizeof(gdisplays)); - gdisplays.count = 1; - gdisplays.displays = &display; - struct dispmsg_get_driver_name dgdn = { 0 }; - dgdn.msgid = DISPMSG_GET_DRIVER_NAME; - dgdn.device = 0; - dgdn.driver_index = 0; - dgdn.name.byte_size = 0; - dgdn.name.str = NULL; - if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) == 0 && - 0 < gdisplays.count && - (dgdn.device = display.device, true) && - (dispmsg_issue(&dgdn, sizeof(dgdn)) == 0 || errno != ENODEV) ) + struct dispmsg_crtc_mode mode; + if ( get_video_mode(&mode) ) { - struct dispmsg_get_crtc_mode get_mode; - memset(&get_mode, 0, sizeof(get_mode)); - get_mode.msgid = DISPMSG_GET_CRTC_MODE; - get_mode.device = 0; - get_mode.connector = 0; - bool good = false; - if ( dispmsg_issue(&get_mode, sizeof(get_mode)) == 0 ) + bool good = (mode.control & DISPMSG_CONTROL_VALID) && + (mode.control & DISPMSG_CONTROL_GOOD_DEFAULT); + if ( mode.control & DISPMSG_CONTROL_VM_AUTO_SCALE ) { - good = (get_mode.mode.control & DISPMSG_CONTROL_VALID) && - (get_mode.mode.control & DISPMSG_CONTROL_GOOD_DEFAULT); - if ( get_mode.mode.control & DISPMSG_CONTROL_VM_AUTO_SCALE ) - { - text("The display resolution will automatically change to " - "match the size of the virtual machine window.\n\n"); - good = true; - } + text("The display resolution will automatically change to " + "match the size of the virtual machine window.\n\n"); + good = true; } const char* def = non_interactive || good ? "no" : "yes"; while ( true ) @@ -835,7 +823,7 @@ int main(void) while ( true ) { promptx(input, sizeof(input), "confirm_upgrade", - "Upgrade? (yes/no/poweroff/reboot/halt)", "yes", true); + "Upgrade? (yes/no/exit/poweroff/reboot/halt)", "yes", true); if ( !strcasecmp(input, "yes") ) break; else if ( !strcasecmp(input, "no") ) @@ -848,12 +836,14 @@ int main(void) "'halt' or cancel the upgrade.\n"); continue; } - else if ( !strcasecmp(input, "poweroff") ) + else if ( !strcasecmp(input, "exit") ) exit(0); + else if ( !strcasecmp(input, "poweroff") ) + exit_gui(0); else if ( !strcasecmp(input, "reboot") ) - exit(1); + exit_gui(1); else if ( !strcasecmp(input, "halt") ) - exit(2); + exit_gui(2); else if ( !strcasecmp(input, "!") ) break; else @@ -991,12 +981,14 @@ int main(void) while ( true ) { prompt(input, sizeof(input), "finally", - "What now? (poweroff/reboot/halt)", "reboot"); - if ( !strcasecmp(input, "poweroff") ) - return 0; - if ( !strcasecmp(input, "reboot") ) - return 1; - if ( !strcasecmp(input, "halt") ) - return 2; + "What now? (exit/poweroff/reboot/halt)", "reboot"); + if ( !strcasecmp(input, "exit") ) + exit(0); + else if ( !strcasecmp(input, "poweroff") ) + exit_gui(0); + else if ( !strcasecmp(input, "reboot") ) + exit_gui(1); + else if ( !strcasecmp(input, "halt") ) + exit_gui(2); } } diff --git a/terminal/.gitignore b/terminal/.gitignore new file mode 100644 index 00000000..6cc474ae --- /dev/null +++ b/terminal/.gitignore @@ -0,0 +1,2 @@ +terminal +*.o diff --git a/terminal/Makefile b/terminal/Makefile new file mode 100644 index 00000000..b84ffdf5 --- /dev/null +++ b/terminal/Makefile @@ -0,0 +1,36 @@ +SOFTWARE_MEANT_FOR_SORTIX=1 +include ../build-aux/platform.mak +include ../build-aux/compiler.mak +include ../build-aux/dirs.mak + +OPTLEVEL?=-g -O2 +CFLAGS?=$(OPTLEVEL) + +CFLAGS:=$(CFLAGS) -Wall -Wextra + +PROGRAM=terminal +MANPAGES1 = terminal.1 + +OBJS=\ +terminal.o \ + +LIBS:=-lui -ldisplay + +all: $(PROGRAM) + +.PHONY: all install clean + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(PROGRAM) $(DESTDIR)$(BINDIR) + mkdir -p $(DESTDIR)$(MANDIR)/man1 + install $(MANPAGES1) $(DESTDIR)$(MANDIR)/man1 + +$(PROGRAM): $(OBJS) + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $(OBJS) -o $@ $(LIBS) + +%.o: %.c + $(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@ + +clean: + rm -f $(PROGRAM) *.o diff --git a/terminal/palette.h b/terminal/palette.h new file mode 100644 index 00000000..44c8f87e --- /dev/null +++ b/terminal/palette.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2016 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * palette.h + * Console color palette, matches the xterm palette with tango colors. + */ + +#ifndef PALETTE_H +#define PALETTE_H + +static const uint32_t palette[256] = +{ + 0x000000, + 0xcc0000, + 0x3e9a06, + 0xc4a000, + 0x3465a4, + 0x75507b, + 0x06989a, + 0xbfbfbf, + 0x555753, + 0xef2929, + 0x8ae234, + 0xfce94f, + 0x729fcf, + 0xad7fa8, + 0x34e2e2, + 0xffffff, + 0x000000, + 0x00005f, + 0x000087, + 0x0000af, + 0x0000d7, + 0x0000ff, + 0x005f00, + 0x005f5f, + 0x005f87, + 0x005faf, + 0x005fd7, + 0x005fff, + 0x008700, + 0x00875f, + 0x008787, + 0x0087af, + 0x0087d7, + 0x0087ff, + 0x00af00, + 0x00af5f, + 0x00af87, + 0x00afaf, + 0x00afd7, + 0x00afff, + 0x00d700, + 0x00d75f, + 0x00d787, + 0x00d7af, + 0x00d7d7, + 0x00d7ff, + 0x00ff00, + 0x00ff5f, + 0x00ff87, + 0x00ffaf, + 0x00ffd7, + 0x00ffff, + 0x5f0000, + 0x5f005f, + 0x5f0087, + 0x5f00af, + 0x5f00d7, + 0x5f00ff, + 0x5f5f00, + 0x5f5f5f, + 0x5f5f87, + 0x5f5faf, + 0x5f5fd7, + 0x5f5fff, + 0x5f8700, + 0x5f875f, + 0x5f8787, + 0x5f87af, + 0x5f87d7, + 0x5f87ff, + 0x5faf00, + 0x5faf5f, + 0x5faf87, + 0x5fafaf, + 0x5fafd7, + 0x5fafff, + 0x5fd700, + 0x5fd75f, + 0x5fd787, + 0x5fd7af, + 0x5fd7d7, + 0x5fd7ff, + 0x5fff00, + 0x5fff5f, + 0x5fff87, + 0x5fffaf, + 0x5fffd7, + 0x5fffff, + 0x870000, + 0x87005f, + 0x870087, + 0x8700af, + 0x8700d7, + 0x8700ff, + 0x875f00, + 0x875f5f, + 0x875f87, + 0x875faf, + 0x875fd7, + 0x875fff, + 0x878700, + 0x87875f, + 0x878787, + 0x8787af, + 0x8787d7, + 0x8787ff, + 0x87af00, + 0x87af5f, + 0x87af87, + 0x87afaf, + 0x87afd7, + 0x87afff, + 0x87d700, + 0x87d75f, + 0x87d787, + 0x87d7af, + 0x87d7d7, + 0x87d7ff, + 0x87ff00, + 0x87ff5f, + 0x87ff87, + 0x87ffaf, + 0x87ffd7, + 0x87ffff, + 0xaf0000, + 0xaf005f, + 0xaf0087, + 0xaf00af, + 0xaf00d7, + 0xaf00ff, + 0xaf5f00, + 0xaf5f5f, + 0xaf5f87, + 0xaf5faf, + 0xaf5fd7, + 0xaf5fff, + 0xaf8700, + 0xaf875f, + 0xaf8787, + 0xaf87af, + 0xaf87d7, + 0xaf87ff, + 0xafaf00, + 0xafaf5f, + 0xafaf87, + 0xafafaf, + 0xafafd7, + 0xafafff, + 0xafd700, + 0xafd75f, + 0xafd787, + 0xafd7af, + 0xafd7d7, + 0xafd7ff, + 0xafff00, + 0xafff5f, + 0xafff87, + 0xafffaf, + 0xafffd7, + 0xafffff, + 0xd70000, + 0xd7005f, + 0xd70087, + 0xd700af, + 0xd700d7, + 0xd700ff, + 0xd75f00, + 0xd75f5f, + 0xd75f87, + 0xd75faf, + 0xd75fd7, + 0xd75fff, + 0xd78700, + 0xd7875f, + 0xd78787, + 0xd787af, + 0xd787d7, + 0xd787ff, + 0xd7af00, + 0xd7af5f, + 0xd7af87, + 0xd7afaf, + 0xd7afd7, + 0xd7afff, + 0xd7d700, + 0xd7d75f, + 0xd7d787, + 0xd7d7af, + 0xd7d7d7, + 0xd7d7ff, + 0xd7ff00, + 0xd7ff5f, + 0xd7ff87, + 0xd7ffaf, + 0xd7ffd7, + 0xd7ffff, + 0xff0000, + 0xff005f, + 0xff0087, + 0xff00af, + 0xff00d7, + 0xff00ff, + 0xff5f00, + 0xff5f5f, + 0xff5f87, + 0xff5faf, + 0xff5fd7, + 0xff5fff, + 0xff8700, + 0xff875f, + 0xff8787, + 0xff87af, + 0xff87d7, + 0xff87ff, + 0xffaf00, + 0xffaf5f, + 0xffaf87, + 0xffafaf, + 0xffafd7, + 0xffafff, + 0xffd700, + 0xffd75f, + 0xffd787, + 0xffd7af, + 0xffd7d7, + 0xffd7ff, + 0xffff00, + 0xffff5f, + 0xffff87, + 0xffffaf, + 0xffffd7, + 0xffffff, + 0x080808, + 0x121212, + 0x1c1c1c, + 0x262626, + 0x303030, + 0x3a3a3a, + 0x444444, + 0x4e4e4e, + 0x585858, + 0x626262, + 0x6c6c6c, + 0x767676, + 0x808080, + 0x8a8a8a, + 0x949494, + 0x9e9e9e, + 0xa8a8a8, + 0xb2b2b2, + 0xbcbcbc, + 0xc6c6c6, + 0xd0d0d0, + 0xdadada, + 0xe4e4e4, + 0xeeeeee, +}; + +#endif diff --git a/terminal/terminal.1 b/terminal/terminal.1 new file mode 100644 index 00000000..3b83cfd6 --- /dev/null +++ b/terminal/terminal.1 @@ -0,0 +1,44 @@ +.Dd June 17, 2023 +.Dt TERMINAL 1 +.Os +.Sh NAME +.Nm terminal +.Nd graphical terminal emulator +.Sh SYNOPSIS +.Nm +.Op Ar command ... +.Sh DESCRIPTION +.Nm +is a graphical terminal emulator for the +.Xr display 1 +desktop environment. +.Nm +has essentially the same features as the +.Xr kernel 7 +console. +.Pp +The +.Ar command +is executed inside a +.Xr pts 4 +psuedoterminal. +A login shell can be requested with a leading hyphen +.Sq - . +If no command was specified, the user's shell per +.Xr passwd 5 +is run as a login shell with +.Xr sh 1 +as a fallback. +.Sh ENVIRONMENT +.Bl -tag -width "TERM" +.It TERM Ns = Ns Sy sortix +The terminal type. +.El +.Sh EXIT STATUS +.Nm +will exit 0 on success and non-zero otherwise. +.Sh SEE ALSO +.Xr display 1 , +.Xr sh 1 , +.Xr pts 4 , +.Xr tty 4 diff --git a/terminal/terminal.c b/terminal/terminal.c new file mode 100644 index 00000000..47f515c1 --- /dev/null +++ b/terminal/terminal.c @@ -0,0 +1,1292 @@ +/* + * Copyright (c) 2017, 2022 Jonas 'Sortie' Termansen. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF7 + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * terminal.c + * Terminal emulator. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(TTY_NAME_MAX) +#include +#endif + +#include +#include +#include +#include + +#include "palette.h" + +struct kbkey_sequence +{ + const char* sequence; + int kbkey; + int flags; +}; + +#define MODIFIER_ALT (1 << 0) +#define MODIFIER_LSHIFT (1 << 1) +#define MODIFIER_RSHIFT (1 << 2) +#define MODIFIER_LCONTROL (1 << 3) +#define MODIFIER_RCONTROL (1 << 4) + +#define SEQUENCE_1IFMOD (1 << 0) +#define SEQUENCE_OSHORT (1 << 1) + +static const struct kbkey_sequence kbkey_sequences[] = +{ + { "\e[A", KBKEY_UP, SEQUENCE_1IFMOD }, + { "\e[B", KBKEY_DOWN, SEQUENCE_1IFMOD}, + { "\e[C", KBKEY_RIGHT, SEQUENCE_1IFMOD }, + { "\e[D", KBKEY_LEFT, SEQUENCE_1IFMOD }, + { "\e[F", KBKEY_END, SEQUENCE_1IFMOD }, + { "\e[H", KBKEY_HOME, SEQUENCE_1IFMOD }, + { "\e[2~", KBKEY_INSERT, 0 }, + { "\e[3~", KBKEY_DELETE, 0 }, + { "\e[5~", KBKEY_PGUP, 0 }, + { "\e[6~", KBKEY_PGDOWN, 0 }, + { "\e[1P", KBKEY_F1, SEQUENCE_OSHORT }, + { "\e[1Q", KBKEY_F2, SEQUENCE_OSHORT }, + { "\e[1R", KBKEY_F3, SEQUENCE_OSHORT }, + { "\e[1S", KBKEY_F4, SEQUENCE_OSHORT }, + { "\e[15~", KBKEY_F5, 0 }, + { "\e[17~", KBKEY_F6, 0 }, + { "\e[18~", KBKEY_F7, 0 }, + { "\e[19~", KBKEY_F8, 0 }, + { "\e[20~", KBKEY_F9, 0 }, + { "\e[21~", KBKEY_F10, 0 }, + { "\e[23~", KBKEY_F11, 0 }, + { "\e[24~", KBKEY_F12, 0 }, +}; + +static inline const struct kbkey_sequence* lookup_keystroke_sequence(int kbkey) +{ + size_t count = sizeof(kbkey_sequences) / sizeof(kbkey_sequences[0]); + for ( size_t i = 0; i < count; i++ ) + if ( kbkey_sequences[i].kbkey == kbkey ) + return &kbkey_sequences[i]; + return NULL; +} + +static uint32_t WINDOW_ID = 0; +static uint32_t WINDOW_WIDTH = 0; +static uint32_t WINDOW_HEIGHT = 0; + +static bool need_redraw = true; +static bool need_show = true; +static bool need_exit = false; +static bool redraw_pipe_written = false; +static int redraw_pipe[2]; + +struct entry +{ + uint32_t attr; + uint32_t fgcolor; + uint32_t bgcolor; + wchar_t wc; +}; + +#define ATTR_INVERSE (1 << 0) +#define ATTR_BOLD (1 << 1) +#define ATTR_UNDERLINE (1 << 2) + +static struct entry* scrollback; +static size_t column = 0; +static size_t row = 0; +static size_t columns = 0; +static size_t rows = 0; +static int modifiers = 0; +static mbstate_t in_ps; +static mbstate_t out_ps; +static uint32_t default_fgcolor; +static uint32_t default_bgcolor; +static uint32_t current_fgcolor; +static uint32_t current_bgcolor; +static uint32_t attr; +static uint32_t next_attr; +static unsigned ansisavedposx; +static unsigned ansisavedposy; +static enum { NONE = 0, CSI, CHARSET, COMMAND, GREATERTHAN, } ansimode; +#define ANSI_NUM_PARAMS 16 +static unsigned ansiusedparams; +static unsigned ansiparams[ANSI_NUM_PARAMS]; +static bool ignore_sequence; +static bool draw_cursor = true; + +static void scrollback_resize(size_t new_rows, size_t new_columns) +{ + // TODO: Recover gracefully if the scrollback fails. + // TODO: Overflow. + struct entry* new_scrollback = + calloc(sizeof(struct entry), new_rows * new_columns); + if ( !new_scrollback ) + err(1, "malloc"); + size_t src_y_after_cursor = rows ? row + 1 : 0; + size_t src_y_count = + new_rows < src_y_after_cursor ? new_rows : src_y_after_cursor; + size_t src_y_from = src_y_after_cursor - src_y_count; + size_t new_row = row; + size_t new_column = column; + new_row += src_y_from; + for ( size_t dst_y = 0; dst_y < new_rows; dst_y++ ) + { + size_t src_y = src_y_from + dst_y; + for ( size_t dst_x = 0; dst_x < new_columns; dst_x++ ) + { + size_t src_x = dst_x; + struct entry tc; + if ( src_x < columns && src_y < rows ) + tc = scrollback[src_y * columns + src_x]; + else if ( columns && rows ) + { + size_t templ_x = src_y < columns ? src_y : src_x - 1; + size_t templ_y = src_y < rows ? src_y : rows - 1; + tc = scrollback[templ_y * columns + templ_x]; + tc.wc = 0; + tc.attr = 0; + } + else + tc = (struct entry) { 0 }; + new_scrollback[dst_y * new_columns + dst_x] = tc; + if ( src_x == column && src_y == row ) + { + new_row = dst_y; + new_column = dst_x; + } + } + } + if ( new_columns <= new_column ) + new_column = new_columns ? new_column - 1 : 0; + if ( new_rows <= new_row ) + new_row = new_rows ? new_row - 1 : 0; + free(scrollback); + scrollback = new_scrollback; + rows = new_rows; + columns = new_columns; + row = new_row; + column = new_column; +} + +static void fill(size_t from_x, size_t from_y, size_t to_x, size_t to_y, + struct entry with) +{ + // TODO: Assert within bounds? + size_t from = from_y * columns + from_x; + size_t to = to_y * columns + to_x; + for ( size_t i = from; i <= to; i++ ) + scrollback[i] = with; +} + +static void scroll(ssize_t offsigned, struct entry with) +{ + if ( 0 < offsigned ) + { + size_t off = offsigned; + if ( rows < off ) + off = rows; + size_t dist = off * columns; + size_t end = rows * columns - dist; + for ( size_t i = 0; i < end; i++ ) + scrollback[i] = scrollback[i + dist]; + for ( size_t i = end; i < end + dist; i++ ) + scrollback[i] = with; + } + else if ( offsigned < 0 ) + { + size_t off = -offsigned; // TODO: Negation overflow. + if ( rows < off ) + off = rows; + size_t dist = off * columns; + size_t end = rows * columns; + for ( size_t i = end-1; dist <= i; i-- ) + scrollback[i] = scrollback[i - dist]; + for ( size_t i = 0; i < dist; i++ ) + scrollback[i] = with; + } +} + +static void newline(void) +{ + if ( row + 1 < rows ) + { + row++; + return; + } + struct entry entry; + entry.attr = 0; + entry.fgcolor = current_fgcolor; + entry.bgcolor = current_bgcolor; + entry.wc = 0; + scroll(1, entry); +} + +static void run_ansi_command(char c) +{ + switch ( c ) + { + case 'A': // Cursor up + { + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( row < dist ) + row = 0; + else + row -= dist; + } break; + case 'B': // Cursor down + { + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( rows <= row + dist ) + row = rows-1; + else + row += dist; + } break; + case 'C': // Cursor forward + { + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( columns <= column + dist ) + column = columns-1; + else + column += dist; + } break; + case 'D': // Cursor backward + { + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( column < dist ) + column = 0; + else + column -= dist; + } break; + case 'E': // Move to beginning of line N lines down. + { + column = 0; + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( rows <= row + dist ) + row = rows-1; + else + row += dist; + } break; + case 'F': // Move to beginning of line N lines up. + { + column = 0; + unsigned dist = 0 < ansiusedparams ? ansiparams[0] : 1; + if ( row < dist ) + row = 0; + else + row -= dist; + } break; + case 'G': // Move the cursor to column N. + { + unsigned pos = 0 < ansiusedparams ? ansiparams[0]-1 : 0; + if ( columns <= pos ) + pos = columns-1; + column = pos; + } break; + case 'H': // Move the cursor to line Y, column X. + case 'f': + { + unsigned posy = 0 < ansiusedparams ? ansiparams[0]-1 : 0; + unsigned posx = 1 < ansiusedparams ? ansiparams[1]-1 : 0; + if ( columns <= posx ) + posx = columns-1; + if ( rows <= posy ) + posy = rows-1; + column = posx; + row = posy; + } break; + case 'J': // Erase parts of the screen. + { + unsigned mode = 0 < ansiusedparams ? ansiparams[0] : 0; + size_t from_x = 0, from_y = 0; + size_t to_x = 0, to_y = 0; + + if ( mode == 0 ) // From cursor to end. + { + from_x = column; + from_y = row; + // TODO: Ensure the number of rows and columns are always non-zero. + to_x = columns - 1; + to_y = rows - 1; + } + + if ( mode == 1 ) // From start to cursor. + { + from_x = 0; + from_y = 0; + to_x = columns - 1; + to_y = rows - 1; + } + + if ( mode == 2 ) // Everything. + { + from_x = 0; + from_y = 0; + to_x = columns - 1; + to_y = rows - 1; + } + + struct entry with; + with.attr = 0; + with.fgcolor = attr & ATTR_INVERSE ? current_bgcolor : current_fgcolor; + with.bgcolor = attr & ATTR_INVERSE ? current_fgcolor : current_bgcolor; + with.wc = 0; + fill(from_x, from_y, to_x, to_y, with); + } break; + case 'K': // Erase parts of the current line. + { + unsigned mode = 0 < ansiusedparams ? ansiparams[0] : 0; + size_t from_x = 0, from_y = row; + size_t to_x = 0, to_y = row; + + if ( mode == 0 ) // From cursor to end. + { + from_x = column; + to_x = columns - 1; + } + + if ( mode == 1 ) // From start to cursor. + { + from_x = 0; + to_x = column; + } + + if ( mode == 2 ) // Everything. + { + from_x = 0; + to_x = columns - 1; + } + + struct entry with; + with.attr = 0; + with.fgcolor = attr & ATTR_INVERSE ? current_bgcolor : current_fgcolor; + with.bgcolor = attr & ATTR_INVERSE ? current_fgcolor : current_bgcolor; + with.wc = 0; + fill(from_x, from_y, to_x, to_y, with); + } break; + // TODO: CSI Ps M Delete Ps Line(s) (default = 1) (DL). + // (delete those lines and move the rest of the lines upwards). + // TODO: CSI Ps P Delete Ps Character(s) (default = 1) (DCH). + // (delete those characters and move the rest of the line leftward). + case 'S': // Scroll a line up and place a new line at the buttom. + { + struct entry with; + with.attr = 0; + with.fgcolor = attr & ATTR_INVERSE ? current_bgcolor : current_fgcolor; + with.bgcolor = attr & ATTR_INVERSE ? current_fgcolor : current_bgcolor; + with.wc = 0; + scroll(1, with); + row = rows - 1; + } break; + case 'T': // Scroll a line up and place a new line at the top. + { + struct entry with; + with.attr = 0; + with.fgcolor = attr & ATTR_INVERSE ? current_bgcolor : current_fgcolor; + with.bgcolor = attr & ATTR_INVERSE ? current_fgcolor : current_bgcolor; + with.wc = 0; + scroll(-1, with); + row = 0; + } break; + case 'd': // Move the cursor to line N. + { + unsigned posy = 0 < ansiusedparams ? ansiparams[0]-1 : 0; + if ( rows <= posy ) + posy = rows-1; + row = posy; + } break; + case 'm': // Change how the text is rendered. + { + if ( ansiusedparams == 0 ) + { + ansiparams[0] = 0; + ansiusedparams++; + } + + for ( size_t i = 0; i < ansiusedparams; i++ ) + { + unsigned cmd = ansiparams[i]; + // Turn all attributes off. + if ( cmd == 0 ) + { + attr = 0; + current_fgcolor = default_fgcolor; + current_bgcolor = default_bgcolor; + } + // Boldness. + else if ( cmd == 1 ) + attr |= ATTR_BOLD; + // TODO: 2, Faint + // TODO: 3, Italicized + // Underline. + else if ( cmd == 4 ) + attr |= ATTR_UNDERLINE; + // TODO: 5, Blink (appears as Bold) + // Inverse. + else if ( cmd == 7 ) + attr |= ATTR_INVERSE; + // TODO: 8, Invisible + // TODO: 9, Crossed-out + // TODO: 21, Doubly-underlined + // Normal (neither bold nor faint). + else if ( cmd == 22 ) + attr &= ~ATTR_BOLD; + // TODO: 23, Not italicized + // Not underlined. + else if ( cmd == 24 ) + attr &= ~ATTR_UNDERLINE; + // TODO: 25, Steady (not blinking) + // Positive (not inverse). + else if ( cmd == 27 ) + attr &= ~ATTR_INVERSE; + // TODO: 28, Visible (not hidden) + // Set text color. + else if ( 30 <= cmd && cmd <= 37 ) + { + unsigned val = cmd - 30; + current_fgcolor = palette[val] | 0xFF000000; + } + // Set text color. + else if ( cmd == 38 ) + { + if ( 5 <= ansiusedparams - i && ansiparams[i+1] == 2 ) + { + uint8_t r = ansiparams[i+2]; + uint8_t g = ansiparams[i+3]; + uint8_t b = ansiparams[i+4]; + i += 5 - 1; + current_fgcolor = make_color(r, g, b); + } + else if ( 3 <= ansiusedparams - i && ansiparams[i+1] == 5 ) + { + uint8_t index = ansiparams[i+2]; + i += 3 - 1; + current_fgcolor = palette[index] | 0xFF000000; + } + } + // Set default text color. + else if ( cmd == 39 ) + { + current_fgcolor = default_fgcolor; + } + // Set background color. + else if ( 40 <= cmd && cmd <= 47 ) + { + unsigned val = cmd - 40; + current_bgcolor = palette[val] | 0xFF000000; + } + // Set background color. + else if ( cmd == 48 ) + { + if ( 5 <= ansiusedparams - i && ansiparams[i+1] == 2 ) + { + uint8_t r = ansiparams[i+2]; + uint8_t g = ansiparams[i+3]; + uint8_t b = ansiparams[i+4]; + i += 5 - 1; + current_bgcolor = make_color(r, g, b); + } + else if ( 3 <= ansiusedparams - i && ansiparams[i+1] == 5 ) + { + uint8_t index = ansiparams[i+2]; + i += 3 - 1; + current_bgcolor = palette[index] | 0xFF000000; + } + } + // Set default background color. + else if ( cmd == 49 ) + { + current_bgcolor = default_bgcolor; + } + // Set text color. + else if ( 90 <= cmd && cmd <= 97 ) + { + unsigned val = cmd - 90 + 8; + current_fgcolor = palette[val] | 0xFF000000; + } + // Set background color. + else if ( 100 <= cmd && cmd <= 107 ) + { + unsigned val = cmd - 100 + 8; + current_bgcolor = palette[val] | 0xFF000000; + } + else + { + ansimode = NONE; + } + } + } break; + case 'n': // Request special information from terminal. + { + ansimode = NONE; + // TODO: Handle this code. + } break; + case 's': // Save cursor position. + { + ansisavedposx = column; + ansisavedposy = row; + } break; + case 'u': // Restore cursor position. + { + column = ansisavedposx; + row = ansisavedposy; + if ( columns <= column ) + column = columns-1; + if ( rows <= row ) + row = rows-1; + } break; + case 'l': // Hide cursor. + { + // TODO: This is somehow related to the special char '?'. + if ( 0 < ansiusedparams && ansiparams[0] == 25 ) + draw_cursor = false; + if ( 0 < ansiusedparams && ansiparams[0] == 1049 ) + {}; // TODO: Save scrollback. + } break; + case 'h': // Show cursor. + { + // TODO: This is somehow related to the special char '?'. + if ( 0 < ansiusedparams && ansiparams[0] == 25 ) + draw_cursor = true; + if ( 0 < ansiusedparams && ansiparams[0] == 1049 ) + {}; // TODO: Restore scrollback. + } break; + default: + { + ansimode = NONE; + } + // TODO: Handle other cases. + } + + ansimode = NONE; +} + +static void put_ansi_escaped(char c) +{ + // Check the proper prefixes are used. + if ( ansimode == CSI ) + { + if ( c == '[' ) + ansimode = COMMAND; + else if ( c == '(' || c == ')' || c == '*' || c == '+' || + c == '-' || c == '.' || c == '/' ) + ansimode = CHARSET; + // TODO: Enter and exit alternatve keypad mode. + else if ( c == '=' || c == '>' ) + ansimode = NONE; + else + { + ansimode = NONE; + } + return; + } + + if ( ansimode == CHARSET ) + { + ansimode = NONE; + return; + } + + // Read part of a parameter. + if ( '0' <= c && c <= '9' ) + { + if ( ansiusedparams == 0 ) + ansiusedparams++; + unsigned val = c - '0'; + ansiparams[ansiusedparams-1] *= 10; + ansiparams[ansiusedparams-1] += val; + } + + // Parameter delimiter. + else if ( c == ';' ) + { + if ( ansiusedparams == ANSI_NUM_PARAMS ) + { + ansimode = NONE; + return; + } + ansiparams[ansiusedparams++] = 0; + } + + // Left for future standardization, so discard this sequence. + else if ( c == ':' ) + { + ignore_sequence = true; + } + + else if ( c == '>' ) + { + ansimode = GREATERTHAN; + } + + // Run a command. + else if ( 64 <= c && c <= 126 ) + { + if ( !ignore_sequence ) + { + if ( ansimode == COMMAND ) + run_ansi_command(c); + else if ( ansimode == GREATERTHAN ) + { + // Send Device Attributes + if ( c == 'c' ) + { + // TODO: Send an appropriate response through the terminal. + } + else + { + ansimode = NONE; + return; + } + ansimode = NONE; + } + } + else + ansimode = NONE; + } + + // Something I don't understand, and ignore intentionally. + else if ( c == '?' ) + { + //ansimode = NONE; + } + + // TODO: There are some rare things that should be supported here. + + // Ignore unknown input. + else + { + ansimode = NONE; + } +} + +static uint32_t boldify(uint32_t color) +{ + int b = color >> 0 & 0xFF; + int g = color >> 8 & 0xFF; + int r = color >> 16 & 0xFF; + int a = color >> 24 & 0xFF; + b += 63; + if ( 255 < b ) + b = 255; + g += 63; + if ( 255 < g ) + g = 255; + r += 63; + if ( 255 < r ) + r = 255; + return make_color_a(r, g, b, a); +} + +static void outwc(wchar_t wc) +{ + if ( wc == L'\a' ) + { + } + else if ( wc == L'\n' ) + { + newline(); + } + else if ( wc == L'\r' ) + { + column = 0; + } + else if ( wc == L'\b' ) + { + if ( column ) + { + column--; + struct entry* entry = &scrollback[row * columns + column]; + next_attr = entry->attr & (ATTR_BOLD | ATTR_UNDERLINE); + if ( entry->wc == L'_' ) + next_attr |= ATTR_UNDERLINE; + else if ( entry->wc == L' ' ) + next_attr &= ~ATTR_BOLD; + else + next_attr |= ATTR_BOLD; + } + } + else if ( wc == L'\t' ) + { + if ( column == columns ) + { + newline(); + column = 0; + } + column++; + column = -(-column & ~((size_t)0x7)); + if ( columns <= column ) + column = columns; + } + else if ( wc == L'\e' ) + { + next_attr = 0; + ansiusedparams = 0; + ansiparams[0] = 0; + ignore_sequence = false; + ansimode = CSI; + } + else + { + if ( column == columns ) + { + newline(); + column = 0; + } + struct entry* entry = &scrollback[row * columns + column++]; + entry->attr = attr | next_attr; + if ( !(entry->attr & ATTR_INVERSE) ) + { + entry->fgcolor = current_fgcolor; + entry->bgcolor = current_bgcolor; + } + else + { + entry->fgcolor = current_bgcolor; + entry->bgcolor = current_fgcolor; + } + if ( entry->attr & ATTR_BOLD ) + entry->fgcolor = boldify(entry->fgcolor); + entry->wc = wc; + next_attr = 0; + } +} + +static void outc(char c) +{ + if ( ansimode != NONE ) + { + put_ansi_escaped(c); + return; + } + wchar_t wc; + size_t amount = mbrtowc(&wc, &c, 1, &out_ps); + if ( amount == (size_t) -2 ) + return; + if ( amount == (size_t) -1 ) + { + memset(&out_ps, 0, sizeof(out_ps)); + wc = 0xFFFD; /* REPLACEMENT CHARACTER */; + } + if ( amount == (size_t) 0 ) + wc = L' '; + outwc(wc); +} + +static pthread_mutex_t scrollback_mutex = PTHREAD_MUTEX_INITIALIZER; +static int master_fd; + +static void* outgoing_thread(void* ctx) +{ + (void) ctx; + const char* getcursor = "\e[6n"; + size_t i = 0; + char c; + ssize_t amount = 0; + while ( 0 < (amount = read(master_fd, &c, 1)) ) + { + // TODO: Do escape code handling in the escape code parsing. + if ( c == getcursor[i] ) + { + i++; + if ( !getcursor[i] ) + { + i = 0; + char buf[64]; + snprintf(buf, sizeof(buf), "\e[%zu;%zuR", + (size_t) (row + 1), (size_t) (column + 1)); + for ( size_t n = 0; buf[n]; n++ ) + { + if ( write(master_fd, &buf[n], 1) <= 0 ) + { + warn("incoming write"); + break; // TODO: This break is incorrect. + } + } + } + continue; + } + pthread_mutex_lock(&scrollback_mutex); + for ( size_t j = 0; j < i; j++ ) + outc(getcursor[j]); + i = 0; + outc(c); + if ( !redraw_pipe_written ) + { + char c = 'X'; + if ( write(redraw_pipe[1], &c, 1) < 0 ) + warn("write: redraw_pipe"); + else + redraw_pipe_written = true; + } + pthread_mutex_unlock(&scrollback_mutex); + } + if ( amount < 0 ) + warn("outgoing read"); + return NULL; +} + +void on_disconnect(void* ctx) +{ + (void) ctx; + need_exit = true; +} + +void on_quit(void* ctx, uint32_t window_id) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + need_exit = true; +} + +void on_resize(void* ctx, uint32_t window_id, uint32_t width, uint32_t height) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + // TODO: The resolution 0x0 gets sent for newly created windows that hasn't + // been shown yet, fix the compositor to not do this. + if ( width == 0 && height == 0 ) + return; + pthread_mutex_lock(&scrollback_mutex); + size_t new_rows = height / FONT_HEIGHT; + size_t new_columns = width / FONT_WIDTH; + scrollback_resize(new_rows, new_columns); + struct winsize ws; + ws.ws_row = rows; + ws.ws_col = columns; + if ( ioctl(master_fd, TIOCSWINSZ, &ws) < 0 ) + warn("TIOCSWINSZ"); + pthread_mutex_unlock(&scrollback_mutex); + need_redraw = true; + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; +} + +void inuc(unsigned char uc) +{ + write(master_fd, &uc, 1); +} + +void on_keycode(int kbkey) +{ + if ( kbkey < 0 ) + return; + + if ( kbkey == KBKEY_ESC ) + { + inuc('\e'); + return; + } + + const struct kbkey_sequence* seq = lookup_keystroke_sequence(kbkey); + if ( !seq ) + return; + + const char* str = seq->sequence; + size_t len = strlen(str); + + int mods = 0; + if ( modifiers & (MODIFIER_LSHIFT | MODIFIER_RSHIFT) ) + mods |= 1; + if ( modifiers & MODIFIER_ALT ) + mods |= 2; + if ( modifiers & (MODIFIER_LCONTROL | MODIFIER_RCONTROL) ) + mods |= 4; + + if ( (seq->flags & SEQUENCE_OSHORT) && mods == 0 ) + { + inuc('\e'); + inuc('O'); + inuc((unsigned char) str[len-1]); + return; + } + + for ( size_t i = 0; i < len - 1; i++ ) + inuc((unsigned char) str[i]); + if ( seq->flags & SEQUENCE_1IFMOD && mods != 0 ) + inuc('1'); + if ( mods ) + { + inuc(';'); + inuc('1' + mods); + } + inuc(str[len-1]); +} + +void on_keyboard(void* ctx, uint32_t window_id, uint32_t codepoint) +{ + (void) ctx; + if ( window_id != WINDOW_ID ) + return; + int kbkey = KBKEY_DECODE(codepoint); + if ( kbkey != 0 ) + { + // TODO: Don't do this here, let the compositor do this. + if ( kbkey == KBKEY_LALT ) + modifiers |= MODIFIER_ALT; + else if ( kbkey == -KBKEY_LALT ) + modifiers &= ~MODIFIER_ALT; + else if ( kbkey == KBKEY_LSHIFT ) + modifiers |= MODIFIER_LSHIFT; + else if ( kbkey == -KBKEY_LSHIFT ) + modifiers &= ~MODIFIER_LSHIFT; + else if ( kbkey == KBKEY_RSHIFT ) + modifiers |= MODIFIER_RSHIFT; + else if ( kbkey == -KBKEY_RSHIFT ) + modifiers &= ~MODIFIER_RSHIFT; + else if ( kbkey == KBKEY_LCTRL ) + modifiers |= MODIFIER_LCONTROL; + else if ( kbkey == -KBKEY_LCTRL ) + modifiers &= ~MODIFIER_LCONTROL; + else if ( kbkey == KBKEY_RCTRL ) + modifiers |= MODIFIER_RCONTROL; + else if ( kbkey == -KBKEY_RCTRL ) + modifiers &= ~MODIFIER_RCONTROL; + on_keycode(kbkey); + return; + } + if ( codepoint == '\n' ) + codepoint = '\r'; + bool control = modifiers & (MODIFIER_LCONTROL | MODIFIER_RCONTROL); + if ( codepoint == '\b' ) + codepoint = 127; + if ( modifiers & MODIFIER_ALT ) + inuc('\e'); + if ( control && codepoint == L' ' ) + inuc(0); + else if ( control && (L'`' <= codepoint && codepoint <= L'}') ) + inuc(codepoint - L'`'); + else if ( control && (L'@' <= codepoint && codepoint <= L'_') ) + inuc(codepoint - L'@'); + else if ( control && codepoint == L'?' ) + inuc(127); + else + { + wchar_t wc = codepoint; + char mb[MB_CUR_MAX]; + size_t amount = wcrtomb(mb, wc, &in_ps); + if ( amount == (size_t) -1 ) + memset(&in_ps, 0, sizeof(in_ps)); + else + { + for ( size_t i = 0; i < amount; i++ ) + inuc((unsigned char) mb[i]); + } + } +} + +void draw(struct display_connection* connection) +{ + uint32_t* framebuffer = (uint32_t*) + calloc(sizeof(uint32_t), WINDOW_WIDTH * WINDOW_HEIGHT); + assert(framebuffer); + + struct framebuffer fb; + fb.pitch = WINDOW_WIDTH; + fb.buffer = framebuffer; + fb.xres = WINDOW_WIDTH; + fb.yres = WINDOW_HEIGHT; + + pthread_mutex_lock(&scrollback_mutex); + size_t draw_rows = WINDOW_HEIGHT / FONT_HEIGHT; + size_t draw_columns = WINDOW_WIDTH / FONT_WIDTH; + if ( rows < draw_rows ) + draw_rows = rows; + if ( columns < draw_columns ) + draw_rows = columns; + for ( size_t y = 0; y < rows; y++ ) + { + size_t yoff = FONT_HEIGHT * y; + for ( size_t x = 0; x < columns; x++ ) + { + size_t xoff = FONT_WIDTH * x; + size_t cell_width = FONT_WIDTH; + size_t cell_height = FONT_HEIGHT; + if ( x + 1 == columns ) + cell_width = WINDOW_WIDTH - xoff; + if ( y + 1 == rows ) + cell_height = WINDOW_HEIGHT - yoff; + struct framebuffer charfb = + framebuffer_crop(fb, xoff, yoff, cell_width, cell_height); + struct entry* entry = &scrollback[y * columns + x]; + for ( size_t py = 0; py < charfb.yres; py++ ) + { + uint32_t* line = charfb.buffer + py * charfb.pitch; + for ( size_t px = 0; px < charfb.xres; px++ ) + line[px] = entry->bgcolor; + } + render_char(charfb, entry->wc, entry->fgcolor); + size_t entry_width = FONT_WIDTH; + size_t entry_height = FONT_HEIGHT; + if ( charfb.xres < entry_width ) + entry_width = charfb.xres; + if ( charfb.yres < entry_height ) + entry_height = charfb.yres; + size_t underlines = 0; + if ( draw_cursor && y == row && x == column ) + underlines = 2; + else if ( entry->attr & ATTR_UNDERLINE ) + underlines = 1; + size_t start_underlines = FONT_HEIGHT - underlines; + for ( size_t py = start_underlines; py < entry_height; py++ ) + { + uint32_t* line = charfb.buffer + py * charfb.pitch; + for ( size_t x = 0; x < entry_width; x++ ) + line[x] = blend_pixel(line[x], entry->fgcolor); + } + } + } + pthread_mutex_unlock(&scrollback_mutex); + + display_render_window(connection, WINDOW_ID, 0, 0, WINDOW_WIDTH, + WINDOW_HEIGHT, framebuffer); + + free(framebuffer); +} + +static void signal_handler(int signum) +{ + (void) signum; +} + +int main(int argc, char* argv[]) +{ + struct display_connection* connection = display_connect_default(); + if ( !connection && errno == ECONNREFUSED ) + display_spawn(argc, argv); + if ( !connection ) + err(1, "Could not connect to display server"); + + int opt; + while ( (opt = getopt(argc, argv, "")) != -1 ) + { + switch ( opt ) + { + default: return 1; + } + } + + load_font(); + + if ( pipe(redraw_pipe) < 0 ) + err(1, "pipe"); + + rows = 25; + columns = 80; + + // TODO: Overflow. + scrollback = calloc(sizeof(struct entry), rows * columns); + if ( !scrollback ) + err(1, "malloc"); + + WINDOW_WIDTH = columns * FONT_WIDTH; + WINDOW_HEIGHT = rows * FONT_HEIGHT; + + default_bgcolor = make_color_a(0, 0, 0, 220); + default_fgcolor = palette[7] | 0xFF000000; + current_bgcolor = default_bgcolor; + current_fgcolor = default_fgcolor; + for ( size_t y = 0; y < rows; y++ ) + { + for ( size_t x = 0; x < columns; x++ ) + { + struct entry* entry = &scrollback[y * columns + x]; + entry->attr = 0; + entry->fgcolor = current_fgcolor; + entry->bgcolor = current_bgcolor; + entry->wc = 0; + } + } + + display_create_window(connection, WINDOW_ID); + display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT); + display_title_window(connection, WINDOW_ID, "Terminal"); + + struct winsize ws; + ws.ws_row = rows; + ws.ws_col = columns; + char path[TTY_NAME_MAX + 1]; + int slave_fd; + if ( openpty(&master_fd, &slave_fd, path, NULL, &ws) < 0 ) + err(1, "openpty"); + + sigset_t saved_mask, sigchld_mask; + sigemptyset(&sigchld_mask); + sigaddset(&sigchld_mask, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigchld_mask, &saved_mask); + struct sigaction sa = { .sa_handler = signal_handler }; + struct sigaction old_sa; + sigaction(SIGCHLD, &sa, &old_sa); + + pid_t child_pid = fork(); + if ( child_pid < 0 ) + err(1, "fork"); + + if ( !child_pid ) + { + sigprocmask(SIG_SETMASK, &saved_mask, NULL); + if ( setsid() < 0 ) + { + warn("setsid"); + _exit(1); + } + if ( ioctl(slave_fd, TIOCSCTTY) < 0 ) + { + warn("ioctl: TIOCSCTTY"); + _exit(1); + } + if ( close(0) < 0 || close(1) < 0 || close(2) < 0 ) + { + warn("close"); + _exit(1); + } + if ( dup2(slave_fd, 0) != 0 || + dup2(slave_fd, 1) != 1 || + dup2(slave_fd, 2) != 2 ) + { + warn("dup"); + _exit(1); + } + if ( closefrom(3) < 0 ) + { + warn("closefrom"); + _exit(1); + } + const char* program; + if ( argc <= optind ) + { + program = "sh"; + uid_t uid = getuid(); + struct passwd* pwd = getpwuid(uid); + if ( !pwd ) + warn("getpwuid: %ju", (uintmax_t) uid); + else + program = pwd->pw_shell; + char* login_program; + if ( asprintf(&login_program, "-%s", program) < 0 ) + { + warn("malloc"); + _exit(1); + } + execlp(program, login_program, (const char*) NULL); + } + else + { + // Support explicit login shells using a leading dash. + program = argv[optind]; + if ( program[0] == '-' ) + program++; + execvp(program, argv + optind); + } + warn("%s", program); + _exit(127); + } + + int errnum; + pthread_t outthread; + if ( (errnum = pthread_create(&outthread, NULL, outgoing_thread, NULL)) ) + { + errno = errnum; + err(1, "pthread_create"); + } + + struct display_event_handlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.disconnect_handler = on_disconnect; + handlers.quit_handler = on_quit; + handlers.resize_handler = on_resize; + handlers.keyboard_handler = on_keyboard; + + const nfds_t nfds = 2; + struct pollfd pfds[nfds]; + pfds[0].fd = redraw_pipe[0]; + pfds[0].events = POLLIN; + pfds[0].revents = 0; + pfds[1].fd = display_connection_fd(connection); + pfds[1].events = POLLIN; + pfds[1].revents = 0; + + while ( !need_exit ) + { + int status; + if ( 0 < waitpid(child_pid, &status, WNOHANG) ) + break; + + if ( need_redraw ) + { + draw(connection); + need_redraw = false; + } + + if ( need_show ) + { + display_show_window(connection, WINDOW_ID); + need_show = false; + } + + if ( ppoll(pfds, nfds, NULL, &saved_mask) < 0 ) + { + if ( errno == EINTR ) + continue; + err(1, "poll"); + } + + if ( pfds[0].revents ) + { + pthread_mutex_lock(&scrollback_mutex); + char c; + ssize_t amount = read(redraw_pipe[0], &c, 1); + if ( amount < 0 ) + err(1, "read: redraw_pipe"); + if ( 0 < amount ) + { + redraw_pipe_written = false; + need_redraw = true; + } + pthread_mutex_unlock(&scrollback_mutex); + } + + if ( pfds[1].revents ) + { + while ( display_poll_event(connection, &handlers) == 0 ); + } + } + + display_disconnect(connection); + + return 0; +} diff --git a/tix/tix-iso-bootconfig b/tix/tix-iso-bootconfig index 467a7e34..9b9b7cac 100755 --- a/tix/tix-iso-bootconfig +++ b/tix/tix-iso-bootconfig @@ -23,6 +23,7 @@ default= directory= enable_append_title=true enable_dhclient= +enable_gui= enable_network_drivers= enable_ntpd= enable_src= @@ -56,12 +57,14 @@ for argument do --default) previous_option=default ;; --disable-append-title) enable_append_title=false ;; --disable-dhclient) enable_dhclient=false ;; + --disable-gui) enable_gui=false ;; --disable-network-drivers) enable_network_drivers=false ;; --disable-ntpd) enable_ntpd=false ;; --disable-src) enable_src=false ;; --disable-sshd) enable_sshd=false ;; --enable-append-title) enable_append_title=true ;; --enable-dhclient) enable_dhclient=true ;; + --enable-gui) enable_gui=true ;; --enable-network-drivers) enable_network_drivers=true ;; --enable-ntpd) enable_ntpd=true ;; --enable-src) enable_src=true ;; @@ -162,6 +165,7 @@ mkdir -p -- "$directory/boot/grub" echo "title_sysupgrade='***AUTOMATIC UPGRADE***'" fi print_enable_default_bool "$enable_dhclient" dhclient dhclient + print_enable_default_bool "$enable_gui" gui gui print_enable_default "$enable_network_drivers" network_drivers network-drivers print_enable_default_bool "$enable_src" src src print_enable_default_bool "$enable_sshd" sshd sshd diff --git a/tix/tix-iso-bootconfig.8 b/tix/tix-iso-bootconfig.8 index bd6859cc..ba075f41 100644 --- a/tix/tix-iso-bootconfig.8 +++ b/tix/tix-iso-bootconfig.8 @@ -10,12 +10,14 @@ .Op Fl \-default Ns = Ns Ar default-boot-menu-option .Op Fl \-disable-append-title .Op Fl \-disable-dhclient +.Op Fl \-disable-gui .Op Fl \-disable-network-drivers .Op Fl \-disable-ntpd .Op Fl \-disable-src .Op Fl \-disable-sshd .Op Fl \-enable-append-title .Op Fl \-enable-dhclient +.Op Fl \-enable-gui .Op Fl \-enable-network-drivers .Op Fl \-enable-ntpd .Op Fl \-enable-src @@ -105,6 +107,16 @@ GRUB variable to causing the bootloader to load additional configuration that turns off the .Xr dhclient 8 daemon on boot. +.It Fl \-disable-gui +Disable the GUI by setting the +.Sy enable_gui +GRUB variable to +.Sy false , +which makes the bootloader configuration not append +.Sy -gui +to the requested +.Xr init 8 +target. .It Fl \-disable-network-drivers Disable network drivers by setting the .Sy enable_network_drivers @@ -152,6 +164,16 @@ GRUB variable to selecting the default behavior of starting the .Xr dhclient 8 daemon. +.It Fl \-enable-gui +Enable the GUI by setting the +.Sy enable_gui +GRUB variable to +.Sy true , +which makes the bootloader configuration append +.Sy -gui +to the requested +.Xr init 8 +target. .It Fl \-enable-network-drivers Enable network drivers by setting the .Sy enable_network_drivers @@ -350,6 +372,12 @@ automatically using the SSH configuration found in the liveconfig directory: tix-iso-bootconfig --liveconfig=liveconfig --enable-sshd bootconfig tix-iso-add sortix.iso bootconfig .Ed +.Ss Boot to Console Instead of GUI By Default +To customize a release so it boots to a console instead of the GUI: +.Bd -literal +tix-iso-bootconfig --disable-gui bootconfig +tix-iso-add sortix.iso bootconfig +.Ed .Sh SEE ALSO .Xr xorriso 1 , .Xr kernel 7 , diff --git a/utils/command-not-found.c b/utils/command-not-found.c index 510a4563..f0fda838 100644 --- a/utils/command-not-found.c +++ b/utils/command-not-found.c @@ -29,7 +29,10 @@ static void suggest_logout(void) { - fprintf(stderr, " Exiting your shell normally to logout.\n"); + if ( getenv("DISPLAY_SOCKET") ) + fprintf(stderr, " Pressing Ctrl-Alt-Del to exit desktop.\n"); + else + fprintf(stderr, " Exiting your shell normally to logout.\n"); } enum category