sortix-mirror/display/window.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

555 lines
18 KiB
C

/*
* Copyright (c) 2014, 2015, 2016, 2017, 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.
*
* window.c
* Window abstraction.
*/
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <timespec.h>
#include <display-protocol.h>
#include "connection.h"
#include "display.h"
#include "framebuffer.h"
#include "pixel.h"
#include "vgafont.h"
#include "window.h"
struct framebuffer window_client_buffer(struct window* window)
{
if ( window->window_state != WINDOW_STATE_REGULAR )
return framebuffer_crop(window->buffer, 0, TITLE_HEIGHT,
window->width, window->height - TITLE_HEIGHT);
return framebuffer_crop(window->buffer, BORDER_WIDTH, TITLE_HEIGHT,
window->width - 2 * BORDER_WIDTH,
window->height - TITLE_HEIGHT - BORDER_WIDTH);
}
void window_schedule_redraw(struct window* window)
{
if ( window->show )
display_schedule_redraw(window->display);
}
void window_render_frame(struct window* window)
{
if ( !window->width || !window->height )
return;
bool has_focus = window->display->tab_candidate ?
window->display->tab_candidate == window :
window->display->active_window == window;
uint32_t glass_color = has_focus ? make_color_a(200, 200, 255, 192)
: make_color_a(180, 180, 255, 128);
uint32_t title_color = has_focus ? make_color_a(16, 16, 16, 240)
: make_color_a(32, 32, 32, 200);
uint32_t button_hover_glass = make_color_a(220, 220, 255, 255);
uint32_t button_press_glass = make_color_a(180, 180, 255, 255);
size_t start_x = 0;
size_t start_y = 0;
size_t end_x = window->width - 1;
size_t end_y = window->height - 1;
bool maximized = window->window_state != WINDOW_STATE_REGULAR;
int b0 = 0;
int b1 = 1;
int b2 = 2;
int b3 = BORDER_WIDTH;
int t0 = TITLE_HEIGHT;
for ( size_t y = start_y; y <= end_y; y++ )
{
for ( size_t x = start_x; x <= end_x; x++ )
{
uint32_t color;
if ( maximized && y < start_y + t0 )
color = glass_color;
else if ( maximized )
continue;
else if ( x == start_x + b0 || x == end_x - b0 ||
y == start_y + b0 || y == end_y - b0 )
color = make_color_a(0, 0, 0, 32);
else if ( x == start_x + b1 || x == end_x - b1 ||
y == start_y + b1 || y == end_y - b1 )
color = make_color_a(0, 0, 0, 64);
else if ( x == start_x + b2 || x == end_x - b2 ||
y == start_y + b2 || y == end_y - b2 )
color = make_color(240, 240, 250);
else if ( x < start_x + (b3-1) || x > end_x - (b3-1) ||
y < start_y + (t0-1) || y > end_y - (b3-1) )
color = glass_color;
else if ( x == start_x + (b3-1) || x == end_x - (b3-1) ||
y == start_y + (t0-1) || y == end_y - (b3-1) )
color = make_color(64, 64, 64);
else
continue;
framebuffer_set_pixel(window->buffer, x, y, color);
}
}
const char* tt = window->title ? window->title : "";
ssize_t tt_width = render_text_width(tt); // Potentially adjusted later.
size_t tt_height = FONT_HEIGHT;
size_t tt_pos_y = (TITLE_HEIGHT - FONT_HEIGHT) / 2 + 2;
uint32_t tt_color = title_color;
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;
size_t button_size = FONT_WIDTH - 1;
size_t button_top = (button_area_height - button_size + 1) / 2;
size_t button_left = (button_area_width - button_size + 1) / 2;
ssize_t buttons_x = window->width - border_width - button_area_width*3 + 1;
struct framebuffer buttons_fb =
framebuffer_crop(window->buffer, buttons_x, button_area_top,
button_area_width * 3, button_area_height);
for ( size_t n = 0; n < 3; n++ )
{
uint32_t color = glass_color;
switch ( window->button_states[n] )
{
case BUTTON_STATE_NORMAL: continue;
case BUTTON_STATE_HOVER: color = button_hover_glass; break;
case BUTTON_STATE_PRESSED: color = button_press_glass; break;
}
size_t bx = button_area_width * n;
size_t by = 0;
for ( size_t y = 0; y < button_area_height; y++ )
for ( size_t x = 0; x < button_area_width; x++ )
framebuffer_set_pixel(buttons_fb, bx + x, by + y, color);
}
for ( size_t i = 0; i < button_size; i++ )
{
size_t bx = button_area_width * 0 + button_left;
size_t by = button_top;
framebuffer_set_pixel(buttons_fb, bx + i,
by + button_size - 1, tt_color);
framebuffer_set_pixel(buttons_fb, bx + i,
by + button_size - 2, tt_color);
}
for ( size_t i = 0; i < button_size; i++ )
{
size_t bx = button_area_width * 1 + button_left;
size_t by = button_top;
framebuffer_set_pixel(buttons_fb, bx + i,
by, tt_color);
framebuffer_set_pixel(buttons_fb, bx + i,
by + button_size - 1 , tt_color);
framebuffer_set_pixel(buttons_fb, bx,
by + i, tt_color);
framebuffer_set_pixel(buttons_fb, bx + button_size - 1,
by + i, tt_color);
framebuffer_set_pixel(buttons_fb, bx + i,
by + 1, tt_color);
framebuffer_set_pixel(buttons_fb, bx + i,
by + button_size - 2 , tt_color);
framebuffer_set_pixel(buttons_fb, bx + 1,
by + i, tt_color);
framebuffer_set_pixel(buttons_fb, bx + button_size - 2,
by + i, tt_color);
}
for ( size_t i = 0; i < button_size; i++ )
{
size_t bx = button_area_width * 2 + button_left;
size_t by = button_top;
framebuffer_set_pixel(buttons_fb, bx + i,
by + i, tt_color);
framebuffer_set_pixel(buttons_fb, bx + i,
by + button_size - 1 - i, tt_color);
framebuffer_set_pixel(buttons_fb, bx + i - 1,
by + i, tt_color);
framebuffer_set_pixel(buttons_fb, bx + i - 1,
by + button_size - 1 - i, tt_color);
}
ssize_t q = 500 - window->width;
ssize_t q_width = 200;
q = q < q_width ? q : q_width;
q = 0 < q ? q : 0;
ssize_t center_over = window->width - (button_area_width * 3 * q / q_width);
ssize_t tt_pos_x = (center_over - tt_width) / 2;
if ( tt_pos_x < (ssize_t)border_width )
{
tt_pos_x = border_width;
tt_width = buttons_x - border_width;
tt_width = 0 < tt_width ? tt_width : 0;
}
render_text(framebuffer_crop(window->buffer, tt_pos_x, tt_pos_y,
tt_width, tt_height), tt, tt_color);
window_schedule_redraw(window);
}
void window_move(struct window* window, size_t left, size_t top)
{
window->left = left;
window->top = top;
window_schedule_redraw(window);
}
void window_client_resize(struct window* window,
size_t client_width,
size_t client_height)
{
if ( window->window_state != WINDOW_STATE_MINIMIZED )
window->window_state = WINDOW_STATE_REGULAR;
struct framebuffer old_fb = window->buffer;
window->width = client_width + BORDER_WIDTH + BORDER_WIDTH;
window->height = client_height + TITLE_HEIGHT + BORDER_WIDTH;
window->buffer.xres = window->width;
window->buffer.yres = window->height;
window->buffer.pitch = window->width;
// TODO: Check malloc.
window->buffer.buffer =
malloc(sizeof(uint32_t) * window->width * window->height);
for ( size_t y = 0; y < window->height; y++ )
for ( size_t x = 0; x < window->width; x++ )
framebuffer_set_pixel(window->buffer, x, y,
framebuffer_get_pixel(old_fb, x, y));
free(old_fb.buffer);
window_render_frame(window);
window_notify_client_resize(window);
window_schedule_redraw(window);
}
void window_resize(struct window* window, size_t width, size_t height)
{
if ( width < BORDER_WIDTH + BORDER_WIDTH )
width = BORDER_WIDTH + BORDER_WIDTH;
if ( height < TITLE_HEIGHT + BORDER_WIDTH )
height = TITLE_HEIGHT + BORDER_WIDTH;
// TODO: Keep proper track of this for each state.
size_t client_width = width - (BORDER_WIDTH + BORDER_WIDTH);
size_t client_height = height - (TITLE_HEIGHT + BORDER_WIDTH);
window_client_resize(window, client_width, client_height);
}
void window_drag_resize(struct window* window, int ld, int td, int wd, int hd)
{
// TODO: Keep proper track of this for each state.
size_t client_width = window->width - (BORDER_WIDTH + BORDER_WIDTH);
size_t client_height = window->height - (TITLE_HEIGHT + BORDER_WIDTH);
if ( ld || td )
window_move(window, window->left + ld, window->top + td);
if ( wd || hd )
{
ssize_t new_width = (ssize_t) client_width + wd;
ssize_t new_height = (ssize_t) client_height + hd;
if ( new_width < 1 )
new_width = 1;
if ( new_height < 1 )
new_height = 1;
window_client_resize(window, new_width, new_height);
}
}
static size_t next_window_position = 25;
void window_initialize(struct window* window,
struct connection* connection,
struct display* display,
uint32_t window_id)
{
memset(window, 0, sizeof(*window));
window->created = true;
window->connection = connection;
window->display = display;
window->title_click_time = timespec_make(-1, 0);
window->window_id = window_id;
display_add_window(window->display, window);
window->top = next_window_position;
window->left = next_window_position;
size_t max_position = display->screen_width < display->screen_height ?
display->screen_width : display->screen_height;
max_position = (max_position * 6) / 10;
next_window_position += 30;
next_window_position %= max_position;
window_client_resize(window, 0, 0);
}
void window_quit(struct window* window)
{
struct event_quit event;
event.window_id = window->window_id;
struct display_packet_header header;
header.id = EVENT_QUIT;
header.size = sizeof(event);
assert(window->connection);
connection_schedule_transmit(window->connection, &header, sizeof(header));
connection_schedule_transmit(window->connection, &event, sizeof(event));
}
void window_destroy(struct window* window)
{
display_remove_window(window->display, window);
free(window->buffer.buffer);
free(window->title);
memset(window, 0, sizeof(*window));
window->created = false;
}
void window_on_display_resolution_change(struct window* window,
struct display* display)
{
// TODO: Move window back inside screen.
if ( window->window_state == WINDOW_STATE_MAXIMIZED )
{
// TODO: Change size of maximized window.
(void) display;
}
}
void window_tile(struct window* window, enum window_state state, size_t left,
size_t top, size_t width, size_t height)
{
if ( window->window_state == state )
return;
if ( window->window_state == WINDOW_STATE_REGULAR )
{
window->saved_left = window->left;
window->saved_top = window->top;
window->saved_width = window->width;
window->saved_height = window->height;
}
free(window->buffer.buffer);
window->left = left;
window->top = top;
window->width = width;
window->height = height;
// TODO: Share logic with window_client_resize.
window->buffer.xres = window->width;
window->buffer.yres = window->height;
window->buffer.pitch = window->width;
// TODO: Check malloc.
window->buffer.buffer =
calloc(1, sizeof(uint32_t) * window->width * window->height);
window->window_state = state;
window_render_frame(window);
window_notify_client_resize(window);
}
void window_maximize(struct window* window)
{
window_tile(window, WINDOW_STATE_MAXIMIZED,
0, 0,
window->display->screen_width, window->display->screen_height);
}
void window_restore(struct window* window)
{
if ( window->window_state == WINDOW_STATE_REGULAR )
return;
window->top = window->saved_top;
window->left = window->saved_left;
window_client_resize(window, window->saved_width - 2 * BORDER_WIDTH,
window->saved_height - TITLE_HEIGHT - BORDER_WIDTH);
window_notify_client_resize(window);
}
void window_toggle_maximized(struct window* window)
{
if ( window->window_state == WINDOW_STATE_MAXIMIZED )
window_restore(window);
else
window_maximize(window);
}
void window_tile_leftward(struct window* window)
{
switch ( window->window_state )
{
case WINDOW_STATE_REGULAR: window_tile_left(window); break;
case WINDOW_STATE_MAXIMIZED: window_tile_left(window); break;
case WINDOW_STATE_MINIMIZED: window_tile_right(window); break;
case WINDOW_STATE_TILE_LEFT: break;
case WINDOW_STATE_TILE_RIGHT: window_restore(window); break;
case WINDOW_STATE_TILE_TOP: window_tile_top_left(window); break;
case WINDOW_STATE_TILE_TOP_LEFT: break;
case WINDOW_STATE_TILE_TOP_RIGHT: window_tile_top(window); break;
case WINDOW_STATE_TILE_BOTTOM: window_tile_bottom_left(window); break;
case WINDOW_STATE_TILE_BOTTOM_LEFT: break;
case WINDOW_STATE_TILE_BOTTOM_RIGHT: window_tile_bottom(window); break;
}
}
void window_tile_rightward(struct window* window)
{
switch ( window->window_state )
{
case WINDOW_STATE_REGULAR: window_tile_right(window); break;
case WINDOW_STATE_MAXIMIZED: window_tile_right(window); break;
case WINDOW_STATE_MINIMIZED: window_tile_right(window); break;
case WINDOW_STATE_TILE_LEFT: window_restore(window); break;
case WINDOW_STATE_TILE_RIGHT: break;
case WINDOW_STATE_TILE_TOP: window_tile_top_right(window); break;
case WINDOW_STATE_TILE_TOP_LEFT: window_tile_top(window); break;
case WINDOW_STATE_TILE_TOP_RIGHT: break;
case WINDOW_STATE_TILE_BOTTOM: window_tile_bottom_right(window); break;
case WINDOW_STATE_TILE_BOTTOM_LEFT: window_tile_bottom(window); break;
case WINDOW_STATE_TILE_BOTTOM_RIGHT: break;
}
}
void window_tile_up(struct window* window)
{
switch ( window->window_state )
{
case WINDOW_STATE_REGULAR: window_tile_top(window); break;
case WINDOW_STATE_MAXIMIZED: window_restore(window); break;
case WINDOW_STATE_MINIMIZED: window_tile_top(window); break;
case WINDOW_STATE_TILE_LEFT: window_tile_top_left(window); break;
case WINDOW_STATE_TILE_RIGHT: window_tile_top_right(window); break;
case WINDOW_STATE_TILE_TOP: window_maximize(window); break;
case WINDOW_STATE_TILE_TOP_LEFT: break;
case WINDOW_STATE_TILE_TOP_RIGHT: break;
case WINDOW_STATE_TILE_BOTTOM: window_restore(window); break;
case WINDOW_STATE_TILE_BOTTOM_LEFT: window_tile_left(window); break;
case WINDOW_STATE_TILE_BOTTOM_RIGHT: window_tile_right(window); break;
}
}
void window_tile_down(struct window* window)
{
switch ( window->window_state )
{
case WINDOW_STATE_REGULAR: window_tile_bottom(window); break;
case WINDOW_STATE_MAXIMIZED: window_tile_top(window); break;
case WINDOW_STATE_MINIMIZED: window_tile_bottom(window); break;
case WINDOW_STATE_TILE_LEFT: window_tile_bottom_left(window); break;
case WINDOW_STATE_TILE_RIGHT: window_tile_bottom_right(window); break;
case WINDOW_STATE_TILE_TOP: window_restore(window); break;
case WINDOW_STATE_TILE_TOP_LEFT: window_tile_left(window); break;
case WINDOW_STATE_TILE_TOP_RIGHT: window_tile_right(window); break;
case WINDOW_STATE_TILE_BOTTOM: break;
case WINDOW_STATE_TILE_BOTTOM_LEFT: break;
case WINDOW_STATE_TILE_BOTTOM_RIGHT: break;
}
}
void window_tile_left(struct window* window)
{
window_tile(window, WINDOW_STATE_TILE_LEFT,
0,
0,
window->display->screen_width / 2,
window->display->screen_height);
}
void window_tile_right(struct window* window)
{
window_tile(window, WINDOW_STATE_TILE_RIGHT,
(window->display->screen_width + 1) / 2,
0,
(window->display->screen_width + 1) / 2,
window->display->screen_height);
}
void window_tile_top(struct window* window)
{
window_tile(window, WINDOW_STATE_TILE_TOP,
0,
0,
window->display->screen_width,
window->display->screen_height / 2);
}
void window_tile_top_left(struct window* window)
{
window_tile(window, WINDOW_STATE_TILE_TOP_LEFT,
0,
0,
window->display->screen_width / 2,
window->display->screen_height / 2);
}
void window_tile_top_right(struct window* window)
{
window_tile(window, WINDOW_STATE_TILE_TOP_RIGHT,
(window->display->screen_width + 1) / 2,
0,
(window->display->screen_width + 1) / 2,
window->display->screen_height / 2);
}
void window_tile_bottom(struct window* window)
{
window_tile(window, WINDOW_STATE_TILE_BOTTOM,
0,
(window->display->screen_height + 1) / 2,
window->display->screen_width,
(window->display->screen_height + 1) / 2);
}
void window_tile_bottom_left(struct window* window)
{
window_tile(window, WINDOW_STATE_TILE_BOTTOM_LEFT,
0,
(window->display->screen_height + 1) / 2,
window->display->screen_width / 2,
(window->display->screen_height + 1) / 2);
}
void window_tile_bottom_right(struct window* window)
{
window_tile(window, WINDOW_STATE_TILE_BOTTOM_RIGHT,
(window->display->screen_width + 1) / 2,
(window->display->screen_height + 1) / 2,
(window->display->screen_width + 1) / 2,
(window->display->screen_height + 1) / 2);
}
void window_notify_client_resize(struct window* window)
{
struct event_resize event;
event.window_id = window->window_id;
event.width = window_client_buffer(window).xres;
event.height = window_client_buffer(window).yres;
struct display_packet_header header;
header.id = EVENT_RESIZE;
header.size = sizeof(event);
assert(window->connection);
connection_schedule_transmit(window->connection, &header, sizeof(header));
connection_schedule_transmit(window->connection, &event, sizeof(event));
}