/* * Copyright (c) 2014, 2015, 2016, 2018, 2022 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 #include #include #include #include #include #include #include #include #include #include #include #include #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)); } 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); } 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); } 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; 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_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_render(struct display* display) { struct dispmsg_crtc_mode mode; { struct dispmsg_get_crtc_mode msg; memset(&msg, 0, sizeof(msg)); msg.msgid = DISPMSG_GET_CRTC_MODE; msg.device = 0; // TODO: Multi-screen support! msg.connector = 0; // TODO: Multi-screen support! if ( dispmsg_issue(&msg, sizeof(msg)) != 0 ) err(1, "dispmsg_issue: dispmsg_get_crtc_mode"); 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 msg; memset(&msg, 0, sizeof(msg)); msg.msgid = DISPMSG_WRITE_MEMORY; msg.device = 0; // TODO: Multi-screen support! msg.offset = 0; // TODO: mode.fb_location! msg.size = framebuffer_size; msg.src = (uint8_t*) display->fb.buffer; if ( dispmsg_issue(&msg, sizeof(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)*/ ) { struct event_keyboard event; event.window_id = display->active_window->window_id; struct display_packet_header header; header.message_id = EVENT_QUIT; header.message_length = sizeof(event); assert(window->connection); connection_schedule_transmit(window->connection, &header, sizeof(header)); connection_schedule_transmit(window->connection, &event, sizeof(event)); 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.message_id = EVENT_KEYBOARD; header.message_length = 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 ( display->pointer_x < 0 ) display->pointer_x = 0; if ( display->pointer_y < 0 ) display->pointer_y = 0; if ( display->screen_width <= (size_t) display->pointer_x ) display->pointer_x = display->screen_width; if ( display->screen_height <= (size_t) display->pointer_y ) display->pointer_y = display->screen_height; 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 ( bytes[0] & MOUSE_BUTTON_LEFT ) { display_set_active_window(display, window); if ( display->mouse_state == MOUSE_STATE_NONE ) { // TODO: Stay in state until mouse release. if ( display->key_lalt || (0 <= window_pointer_x && window_pointer_x < (ssize_t) window->width && 0 <= window_pointer_y && window_pointer_y <= (ssize_t) TITLE_HEIGHT) ) 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 ( xm || ym ) { switch ( display->mouse_state ) { case MOUSE_STATE_NONE: break; case MOUSE_STATE_TITLE_MOVE: if ( window->window_state != WINDOW_STATE_REGULAR ) window_restore(window); 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 { display->mouse_state = MOUSE_STATE_NONE; } } 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); }