/* * Copyright (c) 2014, 2015, 2016, 2018, 2022, 2023 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 #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)); display->redraw = true; } 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); display_schedule_redraw(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); display_schedule_redraw(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; display_schedule_redraw(display); 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_IGNORE: break; case MOUSE_STATE_BUTTON_PRESS: 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_schedule_redraw(struct display* display) { display->redraw = true; } void display_render(struct display* display) { if ( !display->redraw ) return; display->redraw = false; struct dispmsg_get_crtc_mode get_mode_msg = {0}; get_mode_msg.msgid = DISPMSG_GET_CRTC_MODE; get_mode_msg.device = display->display.device; get_mode_msg.connector = display->display.connector; if ( dispmsg_issue(&get_mode_msg, sizeof(get_mode_msg)) != 0 ) err(1, "dispmsg_issue: dispmsg_get_crtc_mode"); struct dispmsg_crtc_mode mode = get_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 write_memory_msg = {0}; write_memory_msg.msgid = DISPMSG_WRITE_MEMORY; write_memory_msg.device = display->display.device; write_memory_msg.size = framebuffer_size; write_memory_msg.src = (uint8_t*) display->fb.buffer; if ( dispmsg_issue(&write_memory_msg, sizeof(write_memory_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)*/ ) { window_quit(window); 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.id = EVENT_KEYBOARD; header.size = 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 ( xm || ym ) display_schedule_redraw(display); bool clipped_edge = false; if ( display->pointer_x < 0 ) { display->pointer_x = 0; clipped_edge = true; } if ( display->pointer_y < 0 ) { display->pointer_y = 0; clipped_edge = true; } if ( display->screen_width < (size_t) display->pointer_x ) { display->pointer_x = display->screen_width; clipped_edge = true; } if ( display->screen_height < (size_t) display->pointer_y ) { display->pointer_y = display->screen_height; clipped_edge = true; } 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 ( display->active_window != window ) { if ( bytes[0] & (MOUSE_BUTTON_LEFT | MOUSE_BUTTON_MIDDLE | MOUSE_BUTTON_RIGHT) ) { // TODO: Exit mouse from the current window. display_set_active_window(display, window); } else return; } bool maximized = window->window_state != WINDOW_STATE_REGULAR; int b2 = 2; int t0 = TITLE_HEIGHT; size_t border_width = maximized ? 0 : b2 + 1; size_t button_area_height = maximized ? t0 : t0 - (b2 + 1); size_t button_area_width = button_area_height; size_t button_area_top = maximized ? 0 : b2; ssize_t buttons_x = window->width - border_width - button_area_width*3 + 1; bool mouse_on_title = 0 <= window_pointer_x && window_pointer_x < (ssize_t) window->width && 0 <= window_pointer_y && window_pointer_y <= (ssize_t) TITLE_HEIGHT; for ( size_t n = 0; n < 3; n++ ) { ssize_t bottom = button_area_top + button_area_height; ssize_t left = button_area_width * n; ssize_t right = button_area_width * (n + 1); if ( (ssize_t) button_area_top <= window_pointer_y && window_pointer_y <= bottom && left <= window_pointer_x - buttons_x && window_pointer_x - buttons_x < right ) { if ( display->mouse_state == MOUSE_STATE_NONE && (bytes[0] & MOUSE_BUTTON_LEFT) ) { display->mouse_state = MOUSE_STATE_BUTTON_PRESS; window->button_states[n] = BUTTON_STATE_PRESSED; window_render_frame(window); } else if ( display->mouse_state == MOUSE_STATE_BUTTON_PRESS && window->button_states[n] == BUTTON_STATE_PRESSED && !(bytes[0] & MOUSE_BUTTON_LEFT) ) { window->button_states[n] = BUTTON_STATE_NORMAL; window_render_frame(window); switch ( n ) { case 0: /* TODO: Minimize. */ break; case 1: window_toggle_maximized(window); break; case 2: window_quit(window); break; } return; } else if ( display->mouse_state == MOUSE_STATE_NONE && window->button_states[n] != BUTTON_STATE_HOVER ) { window->button_states[n] = BUTTON_STATE_HOVER; window_render_frame(window); } } else if ( window->button_states[n] != BUTTON_STATE_NORMAL ) { window->button_states[n] = BUTTON_STATE_NORMAL; if ( display->mouse_state != MOUSE_STATE_NONE ) display->mouse_state = MOUSE_STATE_IGNORE; window_render_frame(window); } } struct timespec double_click = timespec_make(0, 500000000); if ( bytes[0] & MOUSE_BUTTON_LEFT ) { if ( (display->mouse_state == MOUSE_STATE_NONE) ) { // TODO: Stay in state until mouse release. if ( display->key_lalt ) display->mouse_state = MOUSE_STATE_TITLE_MOVE; else if ( mouse_on_title && window_pointer_x < buttons_x ) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); struct timespec elapsed = timespec_sub(now, window->title_click_time); if ( 0 <= window->title_click_time.tv_sec && timespec_le(elapsed, double_click) ) { display->mouse_state = MOUSE_STATE_IGNORE; window_toggle_maximized(window); } else { // TODO: Reset this if clicked anywhere else or if the // active window changes. window->title_click_time = now; 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 ( display->mouse_state != MOUSE_STATE_NONE && display->mouse_state != MOUSE_STATE_IGNORE ) display_schedule_redraw(display); } if ( xm || ym ) { bool floating = window->window_state == WINDOW_STATE_REGULAR; bool on_edge = display->pointer_x == 0 || display->pointer_y == 0 || display->pointer_x == (ssize_t) display->screen_width || display->pointer_y == (ssize_t) display->screen_height; switch ( display->mouse_state ) { case MOUSE_STATE_NONE: break; case MOUSE_STATE_IGNORE: break; case MOUSE_STATE_BUTTON_PRESS: break; case MOUSE_STATE_TITLE_MOVE: if ( clipped_edge ) { ssize_t x = display->pointer_x; ssize_t y = display->pointer_y; ssize_t sw = display->screen_width; ssize_t sh = display->screen_height; ssize_t corner_size = (sw < sh ? sw : sh) / 4; if ( x < corner_size && y < corner_size ) window_tile_top_left(window); else if ( sw - x < corner_size && y < corner_size ) window_tile_top_right(window); else if ( x < corner_size && sh - y < corner_size ) window_tile_bottom_left(window); else if ( sw - x < corner_size && sh - y < corner_size ) window_tile_bottom_right(window); else if ( x == 0 ) window_tile_left(window); else if ( x == sw ) window_tile_right(window); else if ( y == 0 ) window_tile_top(window); else if ( y == sh ) window_tile_bottom(window); } else if ( floating || !on_edge ) { if ( !floating ) { // The current behaviour of window_restore becomes // awkward with tiling gestures. I could change the // function itself, especially since this is currently // its only callsite, but the old behaviour could be // nice for a future untile hotkey. Thus, this hack. window_restore(window); window->top = display->pointer_y - TITLE_HEIGHT / 2; window->left = display->pointer_x - window->width / 2; } 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 if ( display->mouse_state != MOUSE_STATE_NONE ) { display->mouse_state = MOUSE_STATE_NONE; display_schedule_redraw(display); } } 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); }