442 lines
14 KiB
C
442 lines
14 KiB
C
|
/*
|
||
|
* 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);
|
||
|
}
|