sortix-mirror/display/connection.c
Jonas 'Sortie' Termansen 917722cf70 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>
2023-06-24 00:43:36 +02:00

536 lines
16 KiB
C

/*
* 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);
}