From 917722cf70b641703376bc2f1b888e589c064e97 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Sat, 24 Jun 2023 00:05:47 +0200 Subject: [PATCH] Add display server. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change adds the display(1) graphical user interface and desktop environment with basic windowing support and the graphical terminal(1) emulator along with integrations in chkblayout(1), chvideomode(1), sysinstall(8), sysupgrade(8), as well as the games and ports. Adopt the Aurora procedural wallpaper in display(1) and login(8). Remove the obsolete dispd. Juhani contributed keyboard and video mode APIs to the display protocol and other miscellaneous changes. dzwdz contributed the initial functioning window buttons, improved title bar, window tiling, and minor bug fixes Co-authored-by: Juhani Krekelä Co-authored-by: dzwdz --- Makefile | 5 +- build-aux/iso-grub-cfg.sh | 25 +- build-aux/smoketest.sh | 1 + chkblayout/Makefile | 2 +- chkblayout/chkblayout.c | 40 +- chvideomode/Makefile | 2 +- chvideomode/chvideomode.c | 201 ++- dispd/.gitignore | 3 - dispd/Makefile | 53 - dispd/client/session.c | 130 -- dispd/client/window.c | 150 -- dispd/include/dispd.h | 60 - display/.gitignore | 3 + display/Makefile | 51 + display/arrow.rgb | Bin 0 -> 9216 bytes display/connection.c | 535 +++++++ display/connection.h | 67 + .../client/window.h => display/damage-rect.c | 39 +- .../client/library.c => display/damage-rect.h | 30 +- display/display-code.c | 917 ++++++++++++ display/display.1 | 182 +++ display/display.c | 123 ++ display/display.h | 102 ++ display/displayrc.5 | 77 + display/server.c | 281 ++++ display/server.h | 51 + display/window.c | 554 +++++++ display/window.h | 119 ++ games/Makefile | 2 +- games/aquatinspitz.c | 180 +-- games/asteroids.cpp | 185 +-- libdisplay/.gitignore | 2 + libdisplay/Makefile | 34 + libdisplay/include/display-protocol.h | 180 +++ libdisplay/include/display.h | 116 ++ libdisplay/libdisplay.c | 441 ++++++ libui/.gitignore | 2 + libui/Makefile | 36 + libui/framebuffer.c | 71 + libui/include/framebuffer.h | 63 + libui/include/pixel.h | 55 + .../framebuffer.h => libui/include/vgafont.h | 38 +- dispd/client/session.h => libui/pixel.c | 42 +- libui/vgafont.c | 343 +++++ login/graphical.c | 50 + ports/libSDL/libSDL.patch | 378 +++++ share/init/single-user-gui | 9 + share/init/sysinstall-gui | 8 + share/init/sysupgrade-gui | 8 + share/man/man5/autoinstall.conf.5 | 6 + share/man/man5/init.5 | 30 + share/man/man5/session.5 | 14 + share/man/man7/installation.7 | 29 + share/man/man7/release-iso-modification.7 | 6 + share/man/man7/user-guide.7 | 42 +- sysinstall/Makefile | 4 +- sysinstall/interactive.c | 123 +- sysinstall/interactive.h | 4 +- sysinstall/sysinstall.c | 101 +- sysinstall/sysupgrade.c | 76 +- terminal/.gitignore | 2 + terminal/Makefile | 36 + terminal/palette.h | 283 ++++ terminal/terminal.1 | 44 + terminal/terminal.c | 1292 +++++++++++++++++ tix/tix-iso-bootconfig | 4 + tix/tix-iso-bootconfig.8 | 28 + utils/command-not-found.c | 5 +- 68 files changed, 7358 insertions(+), 817 deletions(-) delete mode 100644 dispd/.gitignore delete mode 100644 dispd/Makefile delete mode 100644 dispd/client/session.c delete mode 100644 dispd/client/window.c delete mode 100644 dispd/include/dispd.h create mode 100644 display/.gitignore create mode 100644 display/Makefile create mode 100644 display/arrow.rgb create mode 100644 display/connection.c create mode 100644 display/connection.h rename dispd/client/window.h => display/damage-rect.c (57%) rename dispd/client/library.c => display/damage-rect.h (70%) create mode 100644 display/display-code.c create mode 100644 display/display.1 create mode 100644 display/display.c create mode 100644 display/display.h create mode 100644 display/displayrc.5 create mode 100644 display/server.c create mode 100644 display/server.h create mode 100644 display/window.c create mode 100644 display/window.h create mode 100644 libdisplay/.gitignore create mode 100644 libdisplay/Makefile create mode 100644 libdisplay/include/display-protocol.h create mode 100644 libdisplay/include/display.h create mode 100644 libdisplay/libdisplay.c create mode 100644 libui/.gitignore create mode 100644 libui/Makefile create mode 100644 libui/framebuffer.c create mode 100644 libui/include/framebuffer.h create mode 100644 libui/include/pixel.h rename dispd/client/framebuffer.h => libui/include/vgafont.h (53%) rename dispd/client/session.h => libui/pixel.c (57%) create mode 100644 libui/vgafont.c create mode 100644 share/init/single-user-gui create mode 100644 share/init/sysinstall-gui create mode 100644 share/init/sysupgrade-gui create mode 100644 terminal/.gitignore create mode 100644 terminal/Makefile create mode 100644 terminal/palette.h create mode 100644 terminal/terminal.1 create mode 100644 terminal/terminal.c 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 0000000000000000000000000000000000000000..6d861ba5d984ce7b763e10b75782ef2f40339aee GIT binary patch literal 9216 zcmeI0J&O}j6o#{ox{JEXt{)mKz&cwtB@I$#~J4p;}Q0|9p+l}c@e`u_&*5xj+MI2BS3e79g11m43%2qpur>VFv| zm&<8}PjCr>$bhH%UjgZMyGe%6%!A0_`qTU1GPwSW#X=(lmBIC&&1Mo>2G@TwnMi0E zT>tTSETLs^{YRsbxEYqqrT6Q>^&bw0;%1o7=i++?*MBe=h?`+Lor>=nTz`Fkxfx2O zlD?1E@saN?==qdvV|#bkzuj)Hp8d^cQ}X$|^m;wXX0z&l1n=MtyoOis3~oT=i+`)t zl18H;>2zA1|5Pd!saC7{`8Pa>k`MY`hJH)&C&1oSNDSX2he?< z?zMH#v{CQmo*%oc_HUU?CUJks@kj8pTrNwkR+A`-Cd?ng+2mXwMtn~8pM3rXFW^30 zg#*0@olZxJ#iC}@b#M*5*<0hH{`>F+o`brdgVS&eeiRCYWF7xv{s6qW2e_#JdC;{P zgSxM}&8W}E18Bil&|ckx9Ur=rIN+meQlBMf;ACAMAHT`?6rR9c*adI;lVn&o;;NbU VKz|qilW*NQc6@X1^Z)hQ{{?%u8E^mq literal 0 HcmV?d00001 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