diff --git a/chkblayout/chkblayout.c b/chkblayout/chkblayout.c index da3f2eee..8493406e 100644 --- a/chkblayout/chkblayout.c +++ b/chkblayout/chkblayout.c @@ -40,7 +40,7 @@ static bool chkblayout_ack_received = false; static int chkblayout_error; -static void on_ack(void* ctx, uint32_t id, int error) +static void on_ack(void* ctx, uint32_t id, int32_t error) { (void) ctx; if ( id != CHKBLAYOUT_ID ) diff --git a/chvideomode/Makefile b/chvideomode/Makefile index 249ce2ea..77a15caa 100644 --- a/chvideomode/Makefile +++ b/chvideomode/Makefile @@ -11,7 +11,7 @@ CFLAGS += -Wall -Wextra BINARIES = chvideomode MANPAGES1 = chvideomode.1 -LIBS = +LIBS = -ldisplay all: $(BINARIES) diff --git a/chvideomode/chvideomode.c b/chvideomode/chvideomode.c index 2d7bbab1..6657612c 100644 --- a/chvideomode/chvideomode.c +++ b/chvideomode/chvideomode.c @@ -22,6 +22,8 @@ #include #include +#include +#include #include #include #include @@ -37,7 +39,22 @@ #include #include -struct termios saved; +#define REQUEST_DISPLAYS_ID 0 +#define REQUEST_DISPLAY_MODES_ID 1 +#define SET_DISPLAY_MODE_ID 2 + +static uint32_t display_id; +static bool displays_received = false; + +static size_t modes_count = 0; +static struct dispmsg_crtc_mode* modes; +static int request_display_modes_error = 0; +static bool modes_received = false; + +static int set_display_mode_error = 0; +static bool set_display_mode_ack_received; + +static struct termios saved; static void restore_terminal(int sig) { @@ -48,6 +65,88 @@ static void restore_terminal(int sig) raise(sig); } +static void on_displays(void* ctx, uint32_t id, uint32_t displays) +{ + (void) ctx; + if ( id != REQUEST_DISPLAYS_ID ) + return; + if ( displays < 1 ) + errx(1, "No displays available"); + display_id = 0; // TODO: Multimonitor support. + displays_received = true; +} + +static void on_display_modes(void* ctx, uint32_t id, + uint32_t display_modes_count, + void* aux, size_t aux_size) +{ + (void) ctx; + assert(display_modes_count * sizeof(struct dispmsg_crtc_mode) == aux_size); + if ( id != REQUEST_DISPLAY_MODES_ID ) + return; + modes = malloc(aux_size); + if ( !modes ) + err(1, "malloc"); + memcpy(modes, aux, aux_size); + modes_count = display_modes_count; + modes_received = true; +} + +static void on_ack(void* ctx, uint32_t id, int32_t error) +{ + (void) ctx; + switch ( id ) + { + case REQUEST_DISPLAY_MODES_ID: + if ( error ) + { + modes = NULL; + request_display_modes_error = error; + modes_received = true; + } + break; + case SET_DISPLAY_MODE_ID: + set_display_mode_error = error; + set_display_mode_ack_received = true; + break; + } +} + +static void request_displays(struct display_connection* connection) +{ + display_request_displays(connection, REQUEST_DISPLAYS_ID); + struct display_event_handlers handlers = {0}; + handlers.displays_handler = on_displays; + while ( !displays_received ) + display_wait_event(connection, &handlers); +} + +static void request_display_modes(struct display_connection* connection, + uint32_t display_id) +{ + display_request_display_modes(connection, REQUEST_DISPLAY_MODES_ID, + display_id); + struct display_event_handlers handlers = {0}; + handlers.display_modes_handler = on_display_modes; + handlers.ack_handler = on_ack; + while ( !modes_received ) + display_wait_event(connection, &handlers); + errno = request_display_modes_error; +} + +static bool request_set_display_mode(struct display_connection* connection, + uint32_t display_id, + struct dispmsg_crtc_mode mode) +{ + display_set_display_mode(connection, SET_DISPLAY_MODE_ID, display_id, mode); + struct display_event_handlers handlers = {0}; + handlers.ack_handler = on_ack; + set_display_mode_ack_received = false; + while ( !set_display_mode_ack_received ) + display_wait_event(connection, &handlers); + return !(errno = set_display_mode_error); +} + static bool set_current_mode(const struct tiocgdisplay* display, struct dispmsg_crtc_mode mode) { @@ -61,7 +160,7 @@ static bool set_current_mode(const struct tiocgdisplay* display, static struct dispmsg_crtc_mode* get_available_modes(const struct tiocgdisplay* display, - size_t* num_modes_ptr) + size_t* modes_count_ptr) { struct dispmsg_get_crtc_modes msg; msg.msgid = DISPMSG_GET_CRTC_MODES; @@ -70,15 +169,15 @@ get_available_modes(const struct tiocgdisplay* display, size_t guess = 1; while ( true ) { - struct dispmsg_crtc_mode* ret = (struct dispmsg_crtc_mode*) - malloc(sizeof(struct dispmsg_crtc_mode) * guess); + struct dispmsg_crtc_mode* ret = + calloc(guess, sizeof(struct dispmsg_crtc_mode)); if ( !ret ) return NULL; msg.modes_length = guess; msg.modes = ret; if ( dispmsg_issue(&msg, sizeof(msg)) == 0 ) { - *num_modes_ptr = guess; + *modes_count_ptr = guess; return ret; } free(ret); @@ -144,21 +243,21 @@ static bool mode_passes_filter(struct dispmsg_crtc_mode mode, } static void filter_modes(struct dispmsg_crtc_mode* modes, - size_t* num_modes_ptr, + size_t* modes_count_ptr, struct filter* filter) { - size_t in_num = *num_modes_ptr; - size_t out_num = 0; - for ( size_t i = 0; i < in_num; i++ ) + size_t in_count = *modes_count_ptr; + size_t out_count = 0; + for ( size_t i = 0; i < in_count; i++ ) { if ( mode_passes_filter(modes[i], filter) ) - modes[out_num++] = modes[i]; + modes[out_count++] = modes[i]; } - *num_modes_ptr = out_num; + *modes_count_ptr = out_count; } static bool get_mode(struct dispmsg_crtc_mode* modes, - size_t num_modes, + size_t modes_count, unsigned int xres, unsigned int yres, unsigned int bpp, @@ -168,7 +267,7 @@ static bool get_mode(struct dispmsg_crtc_mode* modes, bool found_other = false; size_t index; size_t other_index = 0; - for ( size_t i = 0; i < num_modes; i++ ) + for ( size_t i = 0; i < modes_count; i++ ) { if ( modes[i].view_xres == xres && modes[i].view_yres == yres && @@ -208,16 +307,16 @@ static bool get_mode(struct dispmsg_crtc_mode* modes, } static bool select_mode(struct dispmsg_crtc_mode* modes, - size_t num_modes, + size_t modes_count, int mode_set_error, struct dispmsg_crtc_mode* mode) { if ( !isatty(0) ) errx(1, "Interactive menu requires stdin to be a terminal"); - int num_modes_display_length = 1; - for ( size_t i = num_modes; 10 <= i; i /= 10 ) - num_modes_display_length++; + int modes_count_display_length = 1; + for ( size_t i = modes_count; 10 <= i; i /= 10 ) + modes_count_display_length++; size_t selection = 0; bool decided = false; @@ -239,7 +338,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, size_t entries_per_page = ws.ws_row - off; size_t page = selection / entries_per_page; size_t from = page * entries_per_page; - size_t how_many_available = num_modes - from; + size_t how_many_available = modes_count - from; size_t how_many = entries_per_page; if ( how_many_available < how_many ) how_many = how_many_available; @@ -259,7 +358,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, const char* color = index == selection ? "\e[31m" : "\e[m"; printf("%s", color); printf("\e[2K"); - printf(" [%-*zu] ", num_modes_display_length, index); + printf(" [%-*zu] ", modes_count_display_length, index); if ( modes[index].control & DISPMSG_CONTROL_VALID ) printf("%ux%ux%u", modes[index].view_xres, @@ -325,12 +424,12 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, if ( selection ) selection--; else - selection = num_modes - 1; + selection = modes_count - 1; redraw = true; } else if ( length == 1 && byte == 'B' ) // Down key { - if ( selection + 1 == num_modes ) + if ( selection + 1 == modes_count ) selection = 0; else selection++; @@ -343,7 +442,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, else if ( '0' <= byte && byte <= '9' ) { uint32_t requested = byte - '0'; - if ( requested < num_modes ) + if ( requested < modes_count ) { selection = requested; redraw = true; @@ -510,22 +609,37 @@ int main(int argc, char* argv[]) } } + bool use_display = getenv("DISPLAY_SOCKET"); + + struct display_connection* connection; 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", diff --git a/display/connection.c b/display/connection.c index 33dbd090..cef4832c 100644 --- a/display/connection.c +++ b/display/connection.c @@ -18,11 +18,11 @@ * Display protocol implementation. */ +#include #include #include #include -#include #include #include #include @@ -33,6 +33,7 @@ #include #include "connection.h" +#include "display.h" void connection_schedule_transmit(struct connection* connection, const void* buffer, @@ -117,6 +118,14 @@ void connection_handler_##message_name( \ 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, \ @@ -234,6 +243,131 @@ CONNECTION_MESSAGE_HANDLER_SERVER(chkblayout) connection_schedule_transmit(connection, &event, sizeof(event)); } +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 success; + success.id = msg->id; + success.modes_count = 0; + struct event_ack failure; + failure.id = msg->id; + failure.error = 0; + + struct dispmsg_crtc_mode* modes = NULL; + // TODO: Multimonitor support. + if ( msg->display_id != 0 ) + failure.error = EINVAL; + else + { + size_t modes_count; + modes = get_available_modes(&server->display->display, &modes_count); + if ( !modes ) + failure.error = errno; + if ( (uint32_t) modes_count != modes_count ) + { + failure.error = EOVERFLOW; + free(modes); + } + success.modes_count = modes_count; + } + + if ( failure.error ) + { + struct display_packet_header header; + header.id = EVENT_ACK; + header.size = sizeof(failure); + + connection_schedule_transmit(connection, &header, sizeof(header)); + connection_schedule_transmit(connection, &failure, sizeof(failure)); + return; + } + + size_t modes_size = success.modes_count * sizeof(struct dispmsg_crtc_mode); + + struct display_packet_header header; + header.id = EVENT_DISPLAY_MODES; + header.size = sizeof(success) + modes_size; + + connection_schedule_transmit(connection, &header, sizeof(header)); + connection_schedule_transmit(connection, &success, sizeof(success)); + connection_schedule_transmit(connection, modes, modes_size); +} + +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) +{ + struct event_ack event; + event.id = msg->id; + event.error = 0; + // TODO: Multimonitor support. + if ( msg->display_id != 0 ) + event.error = EINVAL; + else if ( !set_current_mode(&server->display->display, msg->mode) ) + event.error = errno; + + struct display_packet_header header; + header.id = EVENT_ACK; + header.size = sizeof(event); + + connection_schedule_transmit(connection, &header, sizeof(header)); + connection_schedule_transmit(connection, &event, sizeof(event)); +} + typedef void (*connection_message_handler)(struct connection* connection, void* msg, void* auxiliary, @@ -261,6 +395,9 @@ struct connection_message_handler_registration connection_message_handlers[] = 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(set_display_mode), }; size_t num_connection_message_handlers = sizeof(connection_message_handlers) / diff --git a/libdisplay/include/display-protocol.h b/libdisplay/include/display-protocol.h index 4d4185be..eb196682 100644 --- a/libdisplay/include/display-protocol.h +++ b/libdisplay/include/display-protocol.h @@ -21,6 +21,8 @@ #ifndef DISPLAY_PROTOCOL_H #define DISPLAY_PROTOCOL_H +#include + #include struct display_packet_header @@ -92,6 +94,27 @@ struct display_chkblayout /* 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_SET_DISPLAY_MODE 11 +struct display_set_display_mode +{ + uint32_t id; + uint32_t display_id; + struct dispmsg_crtc_mode mode; +}; + #define EVENT_DISCONNECT 0 struct event_disconnect { @@ -125,4 +148,19 @@ struct event_ack 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 */ +}; + #endif diff --git a/libdisplay/include/display.h b/libdisplay/include/display.h index 435d50f0..5a617658 100644 --- a/libdisplay/include/display.h +++ b/libdisplay/include/display.h @@ -21,6 +21,8 @@ #ifndef INCLUDE_DISPLAY_H #define INCLUDE_DISPLAY_H +#include + #include #include #include @@ -64,13 +66,25 @@ 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_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, int); +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); struct display_event_handlers { @@ -80,6 +94,8 @@ struct display_event_handlers 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; }; int display_poll_event(struct display_connection* connection, diff --git a/libdisplay/libdisplay.c b/libdisplay/libdisplay.c index a8be68da..867e876f 100644 --- a/libdisplay/libdisplay.c +++ b/libdisplay/libdisplay.c @@ -229,6 +229,39 @@ void display_chkblayout(struct display_connection* connection, 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_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) ) @@ -326,6 +359,28 @@ static int display_dispatch_event(struct display_connection* connection, 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); + } + connection->header_got = 0; free(connection->payload); connection->payload = NULL;