Add display server.

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ä <juhani@krekelä.fi>
Co-authored-by: dzwdz <kg67199@gmail.com>
This commit is contained in:
Jonas 'Sortie' Termansen 2023-06-24 00:05:47 +02:00
parent b384bce28c
commit 917722cf70
68 changed files with 7358 additions and 817 deletions

View File

@ -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 \

View File

@ -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

View File

@ -151,6 +151,7 @@ EOF
tix-iso-bootconfig \
--random-seed \
--timeout=0 \
--disable-gui \
--liveconfig=liveconfig \
bootconfig
mkdir -p bootconfig/boot/grub

View File

@ -11,7 +11,7 @@ CFLAGS += -Wall -Wextra
BINARIES = chkblayout
MANPAGES1 = chkblayout.1
LIBS =
LIBS = -ldisplay
all: $(BINARIES)

View File

@ -20,6 +20,7 @@
#include <sys/stat.h>
#include <display.h>
#include <err.h>
#include <errno.h>
#include <error.h>
@ -34,6 +35,20 @@
#include <unistd.h>
#include <termios.h>
#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);

View File

@ -11,7 +11,7 @@ CFLAGS += -Wall -Wextra
BINARIES = chvideomode
MANPAGES1 = chvideomode.1
LIBS =
LIBS = -ldisplay
all: $(BINARIES)

View File

@ -22,6 +22,8 @@
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <assert.h>
#include <display.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
@ -37,7 +39,22 @@
#include <termios.h>
#include <unistd.h>
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;
}

3
dispd/.gitignore vendored
View File

@ -1,3 +0,0 @@
*.a
*.o
server/dispd

View File

@ -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)

View File

@ -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 <sys/ioctl.h>
#include <sys/display.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <err.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dispd.h>
#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;
}

View File

@ -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 <sys/display.h>
#include <sys/types.h>
#include <fcntl.h>
#include <ioleast.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dispd.h>
#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;
}

View File

@ -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 <stdbool.h>
#endif
#include <stddef.h>
#include <stdint.h>
#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

3
display/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
display
*.o
*.inc

51
display/Makefile Normal file
View File

@ -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

BIN
display/arrow.rgb Normal file

Binary file not shown.

535
display/connection.c Normal file
View File

@ -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 <sys/display.h>
#include <sys/socket.h>
#include <sys/termmode.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#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);
}

67
display/connection.h Normal file
View File

@ -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 <stddef.h>
#include <stdint.h>
#include <display-protocol.h>
#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

View File

@ -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 <stddef.h>
#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;
}

View File

@ -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 <stdbool.h>
#ifndef DAMAGE_RECT_H
#define DAMAGE_RECT_H
#include <stddef.h>
#include <stdint.h>
#include <dispd.h>
#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

917
display/display-code.c Normal file
View File

@ -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 <sys/display.h>
#include <sys/keycodes.h>
#include <sys/ps2mouse.h>
#include <assert.h>
#include <err.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <timespec.h>
#include <display-protocol.h>
#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);
}

182
display/display.1 Normal file
View File

@ -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

123
display/display.c Normal file
View File

@ -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 <err.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}

102
display/display.h Normal file
View File

@ -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 <sys/display.h>
#include <sys/ps2mouse.h>
#include <stdbool.h>
#include <stddef.h>
#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

77
display/displayrc.5 Normal file
View File

@ -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

281
display/server.c Normal file
View File

@ -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 <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/termmode.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <display-protocol.h>
#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);
}
}

51
display/server.h Normal file
View File

@ -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 <stdbool.h>
#include <stddef.h>
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

554
display/window.c Normal file
View File

@ -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 <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <timespec.h>
#include <display-protocol.h>
#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));
}

119
display/window.h Normal file
View File

@ -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 <sys/types.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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

View File

@ -15,7 +15,7 @@ BINARIES:=\
asteroids \
aquatinspitz \
LIBS:=-ldispd
LIBS:=-ldisplay
all: $(BINARIES)

View File

@ -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 <sys/types.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <error.h>
#include <math.h>
@ -34,12 +35,15 @@
#include <timespec.h>
#include <unistd.h>
#include <dispd.h>
#include <display.h>
// 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, &current_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;
}

View File

@ -21,6 +21,7 @@
#include <sys/termmode.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
@ -36,7 +37,11 @@
#include <timespec.h>
#include <unistd.h>
#include <dispd.h>
#include <display.h>
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;
}

2
libdisplay/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.a
*.o

34
libdisplay/Makefile Normal file
View File

@ -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

View File

@ -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 <sys/display.h>
#include <stdint.h>
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

View File

@ -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 <sys/display.h>
#include <poll.h>
#include <stdint.h>
#include <time.h>
#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

441
libdisplay/libdisplay.c Normal file
View File

@ -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 <sys/socket.h>
#include <sys/un.h>
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <ioleast.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <display.h>
#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);
}

2
libui/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.a
*.o

36
libui/Makefile Normal file
View File

@ -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

71
libui/framebuffer.c Normal file
View File

@ -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 <stddef.h>
#include <stdint.h>
#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));
}
}
}

View File

@ -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 <stddef.h>
#include <stdint.h>
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

55
libui/include/pixel.h Normal file
View File

@ -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 <stdint.h>
// 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

View File

@ -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 <stdint.h>
#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

View File

@ -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 <stdint.h>
#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;
}

343
libui/vgafont.c Normal file
View File

@ -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 <err.h>
#include <fcntl.h>
#include <ioleast.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wchar.h>
#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);
}

View File

@ -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)

View File

@ -1,3 +1,26 @@
diff -Paur --no-dereference -- libSDL.upstream/configure libSDL/configure
--- libSDL.upstream/configure
+++ libSDL/configure
@@ -19338,7 +19338,7 @@
#include <stddef.h>
#include <stdint.h>
- #include <dispd.h>
+ #include <display.h>
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 <stddef.h>
#include <stdint.h>
#include <stdio.h>
-#include <dispd.h>
-#if __has_include(<display.h>)
-#define DISPLAY
-#include <display.h>
-#endif
#include <fcntl.h>
#include <unistd.h>
+#include <display.h>
+
#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 */

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ?

View File

@ -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:

View File

@ -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

View File

@ -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 ,

View File

@ -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 :

View File

@ -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

View File

@ -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 $@

View File

@ -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 <sys/display.h>
#include <sys/ioctl.h>
#include <sys/termmode.h>
#include <sys/types.h>
#include <ctype.h>
#include <display.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
@ -31,10 +36,22 @@
#include <termios.h>
#include <wchar.h>
#include <display.h>
#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");
}
}

View File

@ -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

View File

@ -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") )
{

View File

@ -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);
}
}

2
terminal/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
terminal
*.o

36
terminal/Makefile Normal file
View File

@ -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

283
terminal/palette.h Normal file
View File

@ -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

44
terminal/terminal.1 Normal file
View File

@ -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

1292
terminal/terminal.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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 ,

View File

@ -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