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