Merge branch 'csd'

This implements client side decorations and have been tested on GNOME
and Weston.

They also render correctly in KWin, but cannot be used to move or
resize the window. I believe this is a bug in KWin's handling of
sub-surfaces positioned outside the parent surface. Luckily, KWin uses
server side decorations.

This merge also contains a lot of bug fixes related to resizing and
rendering of sub-surfaces in general (i.e. the scrollback search box).
This commit is contained in:
Daniel Eklöf 2020-03-06 19:21:57 +01:00
commit e4436e84df
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
22 changed files with 1597 additions and 268 deletions

View file

@ -61,18 +61,6 @@ This is a list of known, but probably not all, issues:
Examples: á (`LATIN SMALL LETTER A` + `COMBINING ACUTE ACCENT`)
* GNOME; might work, but without window decorations.
Strictly speaking, foot is at fault here; all Wayland applications
_must_ be able to draw their own window decorations (but foot is
not).
However, most people want a uniform look and feel on their
desktop, including the window decorations. For this reason, a
Wayland application can request _Server Side Decorations_
(SSD). GNOME will reply with a "_I hear you, but sorry, I wont do
that_".
## Fonts

View file

@ -6,7 +6,7 @@ _arguments \
'(-f --font)'{-f,--font}'[font name and style in fontconfig format (monospace)]:font:->fonts' \
'(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
'--login-shell[start shell as a login shell]' \
'(-g --geometry)'{-g,--geometry}'[window WIDTHxHEIGHT, in pixels (80x24 cells)]:geometry:()' \
'(-g --geometry)'{-g,--geometry}'[window WIDTHxHEIGHT, in pixels (700x50)]:geometry:()' \
'(-s --server)'{-s,--server}'[run as server; open terminals by running footclient]:server:_files' \
'--hold[remain open after child process exits]' \
'(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running (server mode only)]:pidfile:_files' \

150
config.c
View file

@ -44,15 +44,19 @@ static const uint32_t default_bright[] = {
static char *
get_shell(void)
{
struct passwd *passwd = getpwuid(getuid());
if (passwd == NULL) {
LOG_ERRNO("failed to lookup user");
return NULL;
const char *shell = getenv("SHELL");
if (shell == NULL) {
struct passwd *passwd = getpwuid(getuid());
if (passwd == NULL) {
LOG_ERRNO("failed to lookup user");
return NULL;
}
shell = passwd->pw_shell;
}
const char *shell = passwd->pw_shell;
LOG_DBG("user's shell: %s", shell);
return strdup(shell);
}
@ -133,7 +137,7 @@ str_to_double(const char *s, double *res)
}
static bool
str_to_color(const char *s, uint32_t *color, const char *path, int lineno)
str_to_color(const char *s, uint32_t *color, bool allow_alpha, const char *path, int lineno)
{
unsigned long value;
if (!str_to_ulong(s, 16, &value)) {
@ -141,7 +145,12 @@ str_to_color(const char *s, uint32_t *color, const char *path, int lineno)
return false;
}
*color = value & 0xffffff;
if (!allow_alpha && (value & 0xff000000) != 0) {
LOG_ERR("%s:%d: color value must not have an alpha component", path, lineno);
return false;
}
*color = value;
return true;
}
@ -273,7 +282,7 @@ parse_section_colors(const char *key, const char *value, struct config *conf,
}
uint32_t color_value;
if (!str_to_color(value, &color_value, path, lineno))
if (!str_to_color(value, &color_value, false, path, lineno))
return false;
*color = color_value;
@ -305,8 +314,8 @@ parse_section_cursor(const char *key, const char *value, struct config *conf,
uint32_t text_color, cursor_color;
if (text == NULL || cursor == NULL ||
!str_to_color(text, &text_color, path, lineno) ||
!str_to_color(cursor, &cursor_color, path, lineno))
!str_to_color(text, &text_color, false, path, lineno) ||
!str_to_color(cursor, &cursor_color, false, path, lineno))
{
LOG_ERR("%s:%d: invalid cursor colors: %s", path, lineno, value);
free(value_copy);
@ -326,6 +335,87 @@ parse_section_cursor(const char *key, const char *value, struct config *conf,
return true;
}
static bool
parse_section_csd(const char *key, const char *value, struct config *conf,
const char *path, unsigned lineno)
{
if (strcmp(key, "preferred") == 0) {
if (strcmp(value, "server") == 0)
conf->csd.preferred = CONF_CSD_PREFER_SERVER;
else if (strcmp(value, "client") == 0)
conf->csd.preferred = CONF_CSD_PREFER_CLIENT;
else {
LOG_ERR("%s:%d: expected either 'server' or 'client'", path, lineno);
return false;
}
}
else if (strcmp(key, "color") == 0) {
uint32_t color;
if (!str_to_color(value, &color, true, path, lineno)) {
LOG_ERR("%s:%d: invalid titlebar-color: %s", path, lineno, value);
return false;
}
conf->csd.color.title_set = true;
conf->csd.color.title = color;
}
else if (strcmp(key, "size") == 0) {
unsigned long pixels;
if (!str_to_ulong(value, 10, &pixels)) {
LOG_ERR("%s:%d: expected an integer: %s", path, lineno, value);
return false;
}
conf->csd.title_height = pixels;
}
else if (strcmp(key, "button-width") == 0) {
unsigned long pixels;
if (!str_to_ulong(value, 10, &pixels)) {
LOG_ERR("%s:%d: expected an integer: %s", path, lineno, value);
return false;
}
conf->csd.button_width = pixels;
}
else if (strcmp(key, "button-minimize-color") == 0) {
uint32_t color;
if (!str_to_color(value, &color, true, path, lineno)) {
LOG_ERR("%s:%d: invalid button-minimize-color: %s", path, lineno, value);
return false;
}
conf->csd.color.minimize_set = true;
conf->csd.color.minimize = color;
}
else if (strcmp(key, "button-maximize-color") == 0) {
uint32_t color;
if (!str_to_color(value, &color, true, path, lineno)) {
LOG_ERR("%s:%d: invalid button-maximize-color: %s", path, lineno, value);
return false;
}
conf->csd.color.maximize_set = true;
conf->csd.color.maximize = color;
}
else if (strcmp(key, "button-close-color") == 0) {
uint32_t color;
if (!str_to_color(value, &color, true, path, lineno)) {
LOG_ERR("%s:%d: invalid button-close-color: %s", path, lineno, value);
return false;
}
conf->csd.color.close_set = true;
conf->csd.color.close = color;
}
return true;
}
static bool
parse_config_file(FILE *f, struct config *conf, const char *path)
{
@ -333,6 +423,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path)
SECTION_MAIN,
SECTION_COLORS,
SECTION_CURSOR,
SECTION_CSD,
} section = SECTION_MAIN;
/* Function pointer, called for each key/value line */
@ -345,6 +436,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path)
[SECTION_MAIN] = &parse_section_main,
[SECTION_COLORS] = &parse_section_colors,
[SECTION_CURSOR] = &parse_section_cursor,
[SECTION_CSD] = &parse_section_csd,
};
#if defined(_DEBUG) && defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
@ -352,25 +444,25 @@ parse_config_file(FILE *f, struct config *conf, const char *path)
[SECTION_MAIN] = "main",
[SECTION_COLORS] = "colors",
[SECTION_CURSOR] = "cursor",
[SECTION_CSD] = "csd",
};
#endif
unsigned lineno = 0;
char *_line = NULL;
size_t count = 0;
while (true) {
errno = 0;
lineno++;
_line = NULL;
size_t count = 0;
ssize_t ret = getline(&_line, &count, f);
if (ret < 0) {
free(_line);
if (errno != 0) {
LOG_ERRNO("failed to read from configuration");
return false;
goto err;
}
break;
}
@ -389,10 +481,8 @@ parse_config_file(FILE *f, struct config *conf, const char *path)
}
/* Empty line, or comment */
if (line[0] == '\0' || line[0] == '#') {
free(_line);
if (line[0] == '\0' || line[0] == '#')
continue;
}
/* Check for new section */
if (line[0] == '[') {
@ -408,12 +498,13 @@ parse_config_file(FILE *f, struct config *conf, const char *path)
section = SECTION_COLORS;
else if (strcmp(&line[1], "cursor") == 0)
section = SECTION_CURSOR;
else if (strcmp(&line[1], "csd") == 0)
section = SECTION_CSD;
else {
LOG_ERR("%s:%d: invalid section name: %s", path, lineno, &line[1]);
goto err;
}
free(_line);
continue;
}
@ -436,7 +527,6 @@ parse_config_file(FILE *f, struct config *conf, const char *path)
goto err;
}
free(_line);
continue;
}
@ -447,10 +537,8 @@ parse_config_file(FILE *f, struct config *conf, const char *path)
assert(!isspace(*(value + strlen(value) - 1)));
}
if (key[0] == '#') {
free(_line);
if (key[0] == '#')
continue;
}
LOG_DBG("section=%s, key='%s', value='%s'",
section_names[section], key, value);
@ -460,10 +548,9 @@ parse_config_file(FILE *f, struct config *conf, const char *path)
if (!section_parser(key, value, conf, path, lineno))
goto err;
free(_line);
}
free(_line);
return true;
err:
@ -491,8 +578,8 @@ config_load(struct config *conf, const char *conf_path)
*conf = (struct config) {
.term = strdup("foot"),
.shell = get_shell(),
.width = -1,
.height = -1,
.width = 700,
.height = 500,
.pad_x = 2,
.pad_y = 2,
.fonts = tll_init(),
@ -532,6 +619,13 @@ config_load(struct config *conf, const char *conf_path)
},
},
.csd = {
.preferred = CONF_CSD_PREFER_SERVER,
.title_height = 26,
.border_width = 5,
.button_width = 26,
},
.render_worker_count = sysconf(_SC_NPROCESSORS_ONLN),
.server_socket_path = get_server_socket_path(),
.presentation_timings = false,
@ -562,10 +656,10 @@ config_load(struct config *conf, const char *conf_path)
ret = parse_config_file(f, conf, conf_path);
fclose(f);
out:
if (ret && tll_length(conf->fonts) == 0)
tll_push_back(conf->fonts, strdup("monospace"));
out:
free(default_path);
return ret;
}

View file

@ -36,6 +36,25 @@ struct config {
} color;
} cursor;
struct {
enum { CONF_CSD_PREFER_SERVER, CONF_CSD_PREFER_CLIENT } preferred;
int title_height;
int border_width;
int button_width;
struct {
bool title_set;
bool minimize_set;
bool maximize_set;
bool close_set;
uint32_t title;
uint32_t minimize;
uint32_t maximize;
uint32_t close;
} color;
} csd;
size_t render_worker_count;
char *server_socket_path;
bool presentation_timings;

View file

@ -31,7 +31,7 @@ execute (instead of the shell).
Default: _monospace_.
*-g*,*--geometry*=_WIDTHxHEIGHT_
Set initial window width and height.
Set initial window width and height. Default: *700x500*.
*-t*,*--term*=_TERM_
Value to set the environment variable *TERM* to. Default: *foot*.

View file

@ -41,9 +41,10 @@ in this order:
_XxY_ (-padding).
*shell*
Executable to launch. Typically a shell. Default: the user's
default shell (as specified in _/etc/passwd_). You can also pass
arguments. For example "/bin/bash --norc".
Executable to launch. Typically a shell. Default: _$SHELL_ if set,
otherwise the user's default shell (as specified in
_/etc/passwd_). You can also pass arguments. For example
"/bin/bash --norc".
*login-shell*
Start a login shell, by prepending a '-' to argv[0]. Default: _no_.
@ -63,6 +64,9 @@ in this order:
# SECTION: cursor
This section controls the cursor style and color. Note that
applications can change these runtime.
*style*
Configures the default cursor style, and is one of: _block_, _bar_
or _underline_. Default: _block_.
@ -76,6 +80,13 @@ in this order:
# SECTION: colors
This section controls the 16 ANSI colors and the default foreground
and background colors. Note that applications can change these runtime.
The colors are in RRGGBB format. That is, they do *not* have an alpha
component. You can configure the background transparency with the
_alpha_ option.
*foreground*
Default RRGGBB foreground color. This is the color used when no
ANSI color is being used. Default: _dcdccc_.
@ -98,6 +109,44 @@ in this order:
Background translucency. A value in the range 0.0-1.0, where 0.0
means completely transparent, and 1.0 is opaque. Default: _1.0_.
# SECTION: csd
This section controls the look of the _CSDs_ (Client Side
Decorations). Note that the default is to *not* use CSDs, but instead
to use _SSDs_ (Server Side Decorations) when the compositor supports
it.
Note that unlike the colors defined in the _colors_ section, the color
values here are in AARRGGBB format. I.e. they contain an alpha
component.
*preferred*
Which type of window decorations to prefer: *client* (CSD) or
*server* (SSD). Note that this is only a hint to the
compositor. Depending on the compositor's configuration and
capabilities, it may not have any effect. Default: _server_.
*size*
Height, in pixels (subject to output scaling), of the
titlebar. Default: _26_.
*color*
Titlebar AARRGGBB color. Default: use the default _foreground_
color.
*button-width*
Width, in pixels (subject to output scaling), of the
minimize/maximize/close buttons. Default: _26_.
*button-minimize-color*
Minimize button's AARRGGBB color. Default: _ff1e90ff_.
*button-maximize-color*
Maximize button's AARRGGBB color. Default: _ff30ff30_.
*button-close-color*
Close button's AARRGGBB color. Default: _ffff3030_.
# FONT FORMAT
The font is specified in FontConfig syntax. That is, a colon-separated

15
footrc
View file

@ -2,9 +2,9 @@
# font=monospace
# scrollback=1000
# geometry=500x300
# geometry=700x500
# pad=2x2
# shell=<user's default shell (from /etc/passwd)> (you may need to override if you need a login shell)
# shell=$SHELL (if set, otherwise user's default shell from /etc/passwd)
# term=foot
# login-shell=no
# workers=<number of logical CPUs>
@ -14,6 +14,7 @@
# color=111111 dcdccc
[colors]
# alpha=1.0
# foreground=dcdccc
# background=111111
# regular0=222222
@ -32,4 +33,12 @@
# bright5=fcace3
# bright6=b3ffff
# bright7=ffffff
# alpha=1.0
[csd]
# preferred=server
# size=26
# color=<foreground color>
# button-width=26
# button-minimize-color=ff0000ff
# button-maximize-color=ff00ff00
# button-close-color=ffff0000

2
grid.c
View file

@ -60,7 +60,7 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols,
const int old_rows = grid->num_rows;
const int old_cols = grid->num_cols;
assert(old_rows != new_rows || old_cols != new_cols);
//assert(old_rows != new_rows || old_cols != new_cols);
int new_col_idx = 0;
int new_row_idx = 0;

417
input.c
View file

@ -8,6 +8,7 @@
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <linux/input-event-codes.h>
@ -15,11 +16,15 @@
#include <xkbcommon/xkbcommon-keysyms.h>
#include <xkbcommon/xkbcommon-compose.h>
#include <xdg-shell.h>
#define LOG_MODULE "input"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "config.h"
#include "commands.h"
#include "keymap.h"
#include "quirks.h"
#include "render.h"
#include "search.h"
#include "selection.h"
@ -34,7 +39,6 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
char *map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
/* TODO: free old context + keymap */
if (wayl->kbd.xkb_compose_state != NULL) {
xkb_compose_state_unref(wayl->kbd.xkb_compose_state);
wayl->kbd.xkb_compose_state = NULL;
@ -86,12 +90,13 @@ keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
assert(surface != NULL);
struct wayland *wayl = data;
struct wl_window *win = wl_surface_get_user_data(surface);
struct terminal *term = win->term;
wayl->kbd_focus = term;
wayl->input_serial = serial;
wayl->kbd_focus = wayl_terminal_from_surface(wayl, surface);
assert(wayl->kbd_focus != NULL);
term_kbd_focus_in(wayl->kbd_focus);
term_xcursor_update(wayl->kbd_focus);
}
static bool
@ -145,7 +150,8 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
assert(
wayl->kbd_focus == NULL ||
surface == NULL || /* Seen on Sway 1.2 */
wayl_terminal_from_surface(wayl, surface) == wayl->kbd_focus);
((const struct wl_window *)wl_surface_get_user_data(surface))->term == wayl->kbd_focus
);
struct terminal *old_focused = wayl->kbd_focus;
wayl->kbd_focus = NULL;
@ -157,10 +163,9 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
wayl->kbd.meta = false;;
xkb_compose_state_reset(wayl->kbd.xkb_compose_state);
if (old_focused != NULL) {
if (old_focused != NULL)
term_kbd_focus_out(old_focused);
term_xcursor_update(old_focused);
} else {
else {
/*
* Sway bug - under certain conditions we get a
* keyboard_leave() (and keyboard_key()) without first having
@ -580,7 +585,7 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
wayl->kbd.meta = xkb_state_mod_index_is_active(
wayl->kbd.xkb_state, wayl->kbd.mod_meta, XKB_STATE_MODS_DEPRESSED);
if (wayl->kbd_focus)
if (wayl->kbd_focus && wayl->kbd_focus->active_surface == TERM_SURF_GRID)
term_xcursor_update(wayl->kbd_focus);
}
@ -609,6 +614,61 @@ input_repeat(struct wayland *wayl, uint32_t key)
keyboard_key(wayl, NULL, wayl->input_serial, 0, key, XKB_KEY_DOWN);
}
static bool
is_top_left(const struct terminal *term, int x, int y)
{
int csd_border_size = term->conf->csd.border_width;
return (
(term->active_surface == TERM_SURF_BORDER_LEFT && y < 10 * term->scale) ||
(term->active_surface == TERM_SURF_BORDER_TOP && x < (10 + csd_border_size) * term->scale));
}
static bool
is_top_right(const struct terminal *term, int x, int y)
{
int csd_border_size = term->conf->csd.border_width;
return (
(term->active_surface == TERM_SURF_BORDER_RIGHT && y < 10 * term->scale) ||
(term->active_surface == TERM_SURF_BORDER_TOP && x > term->width + 1 * csd_border_size * term->scale - 10 * term->scale));
}
static bool
is_bottom_left(const struct terminal *term, int x, int y)
{
int csd_title_size = term->conf->csd.title_height;
int csd_border_size = term->conf->csd.border_width;
return (
(term->active_surface == TERM_SURF_BORDER_LEFT && y > csd_title_size * term->scale + term->height) ||
(term->active_surface == TERM_SURF_BORDER_BOTTOM && x < (10 + csd_border_size) * term->scale));
}
static bool
is_bottom_right(const struct terminal *term, int x, int y)
{
int csd_title_size = term->conf->csd.title_height;
int csd_border_size = term->conf->csd.border_width;
return (
(term->active_surface == TERM_SURF_BORDER_RIGHT && y > csd_title_size * term->scale + term->height) ||
(term->active_surface == TERM_SURF_BORDER_BOTTOM && x > term->width + 1 * csd_border_size * term->scale - 10 * term->scale));
}
static const char *
xcursor_for_csd_border(struct terminal *term, int x, int y)
{
if (is_top_left(term, x, y)) return "top_left_corner";
else if (is_top_right(term, x, y)) return "top_right_corner";
else if (is_bottom_left(term, x, y)) return "bottom_left_corner";
else if (is_bottom_right(term, x, y)) return "bottom_right_corner";
else if (term->active_surface == TERM_SURF_BORDER_LEFT) return "left_side";
else if (term->active_surface == TERM_SURF_BORDER_RIGHT) return "right_side";
else if (term->active_surface == TERM_SURF_BORDER_TOP) return "top_side";
else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) return"bottom_side";
else {
assert(false);
return NULL;
}
}
static void
wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, struct wl_surface *surface,
@ -617,7 +677,8 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
assert(surface != NULL);
struct wayland *wayl = data;
struct terminal *term = wayl_terminal_from_surface(wayl, surface);
struct wl_window *win = wl_surface_get_user_data(surface);
struct terminal *term = win->term;
LOG_DBG("pointer-enter: surface = %p, new-moused = %p", surface, term);
@ -626,10 +687,39 @@ wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
int x = wl_fixed_to_int(surface_x) * term->scale;
int y = wl_fixed_to_int(surface_y) * term->scale;
wayl->mouse.col = x / term->cell_width;
wayl->mouse.row = y / term->cell_height;
switch ((term->active_surface = term_surface_kind(term, surface))) {
case TERM_SURF_GRID:
wayl->mouse.col = x / term->cell_width;
wayl->mouse.row = y / term->cell_height;
term_xcursor_update(term);
break;
term_xcursor_update(term);
case TERM_SURF_SEARCH:
case TERM_SURF_TITLE:
term->xcursor = "left_ptr";
render_xcursor_set(term);
break;
case TERM_SURF_BORDER_LEFT:
case TERM_SURF_BORDER_RIGHT:
case TERM_SURF_BORDER_TOP:
case TERM_SURF_BORDER_BOTTOM:
term->xcursor = xcursor_for_csd_border(term, x, y);
render_xcursor_set(term);
break;
case TERM_SURF_BUTTON_MINIMIZE:
case TERM_SURF_BUTTON_MAXIMIZE:
case TERM_SURF_BUTTON_CLOSE:
term->xcursor = "left_ptr";
render_xcursor_set(term);
render_refresh_csd(term);
break;
case TERM_SURF_NONE:
assert(false);
break;
}
}
static void
@ -649,13 +739,49 @@ wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
wayl->pointer.xcursor = NULL;
}
/* Reset mouse state */
memset(&wayl->mouse, 0, sizeof(wayl->mouse));
wayl->mouse_focus = NULL;
if (old_moused == NULL) {
LOG_WARN(
"compositor sent pointer_leave event without a pointer_enter "
"event: surface=%p", surface);
} else
} else {
if (surface != NULL) {
/* Sway 1.4 sends this event with a NULL surface when we destroy the window */
const struct wl_window *win __attribute__((unused))
= wl_surface_get_user_data(surface);
assert(old_moused == win->term);
}
enum term_surface active_surface = old_moused->active_surface;
old_moused->active_surface = TERM_SURF_NONE;
term_xcursor_update(old_moused);
switch (active_surface) {
case TERM_SURF_BUTTON_MINIMIZE:
case TERM_SURF_BUTTON_MAXIMIZE:
case TERM_SURF_BUTTON_CLOSE:
if (old_moused->is_shutting_down)
break;
render_refresh_csd(old_moused);
break;
case TERM_SURF_NONE:
case TERM_SURF_GRID:
case TERM_SURF_SEARCH:
case TERM_SURF_TITLE:
case TERM_SURF_BORDER_LEFT:
case TERM_SURF_BORDER_RIGHT:
case TERM_SURF_BORDER_TOP:
case TERM_SURF_BORDER_BOTTOM:
break;
}
}
}
static void
@ -664,6 +790,7 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
{
struct wayland *wayl = data;
struct terminal *term = wayl->mouse_focus;
struct wl_window *win = term->window;
/* Workaround buggy Sway 1.2 */
if (term == NULL) {
@ -687,29 +814,76 @@ wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
int x = wl_fixed_to_int(surface_x) * term->scale;
int y = wl_fixed_to_int(surface_y) * term->scale;
int col = (x - term->margins.left) / term->cell_width;
int row = (y - term->margins.top) / term->cell_height;
wayl->mouse.x = x;
wayl->mouse.y = y;
if (col < 0 || row < 0 || col >= term->cols || row >= term->rows)
return;
switch (term->active_surface) {
case TERM_SURF_NONE:
case TERM_SURF_SEARCH:
case TERM_SURF_BUTTON_MINIMIZE:
case TERM_SURF_BUTTON_MAXIMIZE:
case TERM_SURF_BUTTON_CLOSE:
break;
bool update_selection = wayl->mouse.button == BTN_LEFT;
bool update_selection_early = term->selection.end.row == -1;
case TERM_SURF_TITLE:
/* We've started a 'move' timer, but user started dragging
* right away - abort the timer and initiate the actual move
* right away */
if (wayl->mouse.button == BTN_LEFT && win->csd.move_timeout_fd != -1) {
fdm_del(wayl->fdm, win->csd.move_timeout_fd);
win->csd.move_timeout_fd = -1;
xdg_toplevel_move(win->xdg_toplevel, wayl->seat, win->csd.serial);
}
break;
if (update_selection && update_selection_early)
selection_update(term, col, row);
case TERM_SURF_BORDER_LEFT:
case TERM_SURF_BORDER_RIGHT:
case TERM_SURF_BORDER_TOP:
case TERM_SURF_BORDER_BOTTOM:
term->xcursor = xcursor_for_csd_border(term, x, y);
render_xcursor_set(term);
break;
if (col == wayl->mouse.col && row == wayl->mouse.row)
return;
case TERM_SURF_GRID: {
int col = (x - term->margins.left) / term->cell_width;
int row = (y - term->margins.top) / term->cell_height;
wayl->mouse.col = col;
wayl->mouse.row = row;
if (col < 0 || row < 0 || col >= term->cols || row >= term->rows)
return;
if (update_selection && !update_selection_early)
selection_update(term, col, row);
bool update_selection = wayl->mouse.button == BTN_LEFT;
bool update_selection_early = term->selection.end.row == -1;
term_mouse_motion(
if (update_selection && update_selection_early)
selection_update(term, col, row);
if (col == wayl->mouse.col && row == wayl->mouse.row)
break;
wayl->mouse.col = col;
wayl->mouse.row = row;
if (update_selection && !update_selection_early)
selection_update(term, col, row);
term_mouse_motion(
term, wayl->mouse.button, wayl->mouse.row, wayl->mouse.col);
break;
}
}
}
static bool
fdm_csd_move(struct fdm *fdm, int fd, int events, void *data)
{
struct wl_window *win = data;
struct wayland *wayl = win->term->wl;
fdm_del(fdm, fd);
win->csd.move_timeout_fd = -1;
xdg_toplevel_move(win->xdg_toplevel, wayl->seat, win->csd.serial);
return true;
}
static void
@ -739,10 +913,9 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
}
assert(term != NULL);
search_cancel(term);
switch (state) {
case WL_POINTER_BUTTON_STATE_PRESSED: {
/* Update double/triple click state */
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
/* Time since last click */
struct timeval now, since_last;
gettimeofday(&now, NULL);
@ -757,45 +930,161 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
} else
wayl->mouse.count = 1;
if (button == BTN_LEFT) {
switch (wayl->mouse.count) {
case 1:
selection_start(
term, wayl->mouse.col, wayl->mouse.row,
wayl->kbd.ctrl ? SELECTION_BLOCK : SELECTION_NORMAL);
break;
case 2:
selection_mark_word(term, wayl->mouse.col, wayl->mouse.row,
wayl->kbd.ctrl, serial);
break;
case 3:
selection_mark_row(term, wayl->mouse.row, serial);
break;
}
} else {
if (wayl->mouse.count == 1 && button == BTN_MIDDLE && selection_enabled(term))
selection_from_primary(term);
selection_cancel(term);
}
wayl->mouse.button = button; /* For motion events */
wayl->mouse.last_button = button;
wayl->mouse.last_time = now;
term_mouse_down(term, button, wayl->mouse.row, wayl->mouse.col);
} else
wayl->mouse.button = 0; /* For motion events */
switch (term->active_surface) {
case TERM_SURF_TITLE:
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
struct wl_window *win = term->window;
/* Toggle maximized state on double-click */
if (button == BTN_LEFT && wayl->mouse.count == 2) {
if (win->is_maximized)
xdg_toplevel_unset_maximized(win->xdg_toplevel);
else
xdg_toplevel_set_maximized(win->xdg_toplevel);
}
else if (button == BTN_LEFT && win->csd.move_timeout_fd == -1) {
const struct itimerspec timeout = {
.it_value = {.tv_nsec = 200000000},
};
int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK);
if (fd >= 0 &&
timerfd_settime(fd, 0, &timeout, NULL) == 0 &&
fdm_add(wayl->fdm, fd, EPOLLIN, &fdm_csd_move, win))
{
win->csd.move_timeout_fd = fd;
win->csd.serial = serial;
} else {
LOG_ERRNO("failed to configure XDG toplevel move timer FD");
close(fd);
}
}
}
else if (state == WL_POINTER_BUTTON_STATE_RELEASED) {
struct wl_window *win = term->window;
if (win->csd.move_timeout_fd != -1) {
fdm_del(wayl->fdm, win->csd.move_timeout_fd);
win->csd.move_timeout_fd = -1;
}
}
return;
case TERM_SURF_BORDER_LEFT:
case TERM_SURF_BORDER_RIGHT:
case TERM_SURF_BORDER_TOP:
case TERM_SURF_BORDER_BOTTOM: {
static const enum xdg_toplevel_resize_edge map[] = {
[TERM_SURF_BORDER_LEFT] = XDG_TOPLEVEL_RESIZE_EDGE_LEFT,
[TERM_SURF_BORDER_RIGHT] = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT,
[TERM_SURF_BORDER_TOP] = XDG_TOPLEVEL_RESIZE_EDGE_TOP,
[TERM_SURF_BORDER_BOTTOM] = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM,
};
if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) {
enum xdg_toplevel_resize_edge resize_type;
int x = wayl->mouse.x;
int y = wayl->mouse.y;
if (is_top_left(term, x, y))
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
else if (is_top_right(term, x, y))
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
else if (is_bottom_left(term, x, y))
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
else if (is_bottom_right(term, x, y))
resize_type = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
else
resize_type = map[term->active_surface];
xdg_toplevel_resize(
term->window->xdg_toplevel, term->wl->seat, serial, resize_type);
}
return;
}
case TERM_SURF_BUTTON_MINIMIZE:
if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED)
xdg_toplevel_set_minimized(term->window->xdg_toplevel);
break;
case TERM_SURF_BUTTON_MAXIMIZE:
if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED) {
if (term->window->is_maximized)
xdg_toplevel_unset_maximized(term->window->xdg_toplevel);
else
xdg_toplevel_set_maximized(term->window->xdg_toplevel);
}
break;
case TERM_SURF_BUTTON_CLOSE:
if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED)
term_shutdown(term);
break;
case TERM_SURF_SEARCH:
break;
case TERM_SURF_GRID: {
search_cancel(term);
switch (state) {
case WL_POINTER_BUTTON_STATE_PRESSED: {
if (button == BTN_LEFT) {
switch (wayl->mouse.count) {
case 1:
selection_start(
term, wayl->mouse.col, wayl->mouse.row,
wayl->kbd.ctrl ? SELECTION_BLOCK : SELECTION_NORMAL);
break;
case 2:
selection_mark_word(term, wayl->mouse.col, wayl->mouse.row,
wayl->kbd.ctrl, serial);
break;
case 3:
selection_mark_row(term, wayl->mouse.row, serial);
break;
}
} else {
if (wayl->mouse.count == 1 && button == BTN_MIDDLE &&
selection_enabled(term))
{
selection_from_primary(term);
}
selection_cancel(term);
}
term_mouse_down(term, button, wayl->mouse.row, wayl->mouse.col);
break;
}
case WL_POINTER_BUTTON_STATE_RELEASED:
if (button != BTN_LEFT || term->selection.end.col == -1)
selection_cancel(term);
else
selection_finalize(term, serial);
term_mouse_up(term, button, wayl->mouse.row, wayl->mouse.col);
break;
}
break;
}
case WL_POINTER_BUTTON_STATE_RELEASED:
if (button != BTN_LEFT || term->selection.end.col == -1)
selection_cancel(term);
else
selection_finalize(term, serial);
wayl->mouse.button = 0; /* For motion events */
term_mouse_up(term, button, wayl->mouse.row, wayl->mouse.col);
case TERM_SURF_NONE:
assert(false);
break;
}
}

View file

@ -112,6 +112,7 @@ executable(
'main.c',
'misc.c', 'misc.h',
'osc.c', 'osc.h',
'quirks.c', 'quirks.h',
'render.c', 'render.h',
'search.c', 'search.h',
'selection.c', 'selection.h',

77
quirks.c Normal file
View file

@ -0,0 +1,77 @@
#include "quirks.h"
#include <stdlib.h>
#include <stdbool.h>
#define LOG_MODULE "quirks"
#define LOG_ENABLE_DBG 0
#include "log.h"
#define ALEN(v) (sizeof(v) / sizeof(v[0]))
static bool
is_weston(void)
{
/*
* On weston (8.0), synchronized subsurfaces aren't updated
* correctly.
* They appear to render once, but after that, updates are
* sporadic. Sometimes they update, most of the time they
* don't.
*
* Adding explicit parent surface commits right after the
* subsurface commit doesn't help (and would be useless anyway,
* since it would defeat the purpose of having the subsurface
* synchronized in the first place).
*/
static bool is_weston = false;
static bool initialized = false;
if (!initialized) {
initialized = true;
is_weston = getenv("WESTON_CONFIG_FILE") != NULL;
if (is_weston)
LOG_WARN("applying wl_subsurface_set_desync() workaround for weston");
}
return is_weston;
}
void
quirk_weston_subsurface_desync_on(struct wl_subsurface *sub)
{
if (!is_weston())
return;
wl_subsurface_set_desync(sub);
}
void
quirk_weston_subsurface_desync_off(struct wl_subsurface *sub)
{
if (!is_weston())
return;
wl_subsurface_set_sync(sub);
}
void
quirk_weston_csd_on(struct terminal *term)
{
if (term->window->use_csd != CSD_YES)
return;
for (int i = 0; i < ALEN(term->window->csd.surface); i++)
quirk_weston_subsurface_desync_on(term->window->csd.sub_surface[i]);
}
void
quirk_weston_csd_off(struct terminal *term)
{
if (term->window->use_csd != CSD_YES)
return;
for (int i = 0; i < ALEN(term->window->csd.surface); i++)
quirk_weston_subsurface_desync_off(term->window->csd.sub_surface[i]);
}

12
quirks.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <wayland-client.h>
#include "terminal.h"
void quirk_weston_subsurface_desync_on(struct wl_subsurface *sub);
void quirk_weston_subsurface_desync_off(struct wl_subsurface *sub);
/* Shortcuts to call desync_{on,off} on all CSD subsurfaces */
void quirk_weston_csd_on(struct terminal *term);
void quirk_weston_csd_off(struct terminal *term);

661
render.c
View file

@ -18,6 +18,7 @@
#include "log.h"
#include "config.h"
#include "grid.h"
#include "quirks.h"
#include "selection.h"
#include "shm.h"
@ -38,6 +39,10 @@ static struct {
static void fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data);
#define shm_cookie_grid(term) ((unsigned long)((uintptr_t)term + 0))
#define shm_cookie_search(term) ((unsigned long)((uintptr_t)term + 1))
#define shm_cookie_csd(term, n) ((unsigned long)((uintptr_t)term + 2 + (n))) /* Should be placed last */
struct renderer *
render_init(struct fdm *fdm, struct wayland *wayl)
{
@ -211,6 +216,9 @@ color_hex_to_rgb(uint32_t color)
static inline pixman_color_t
color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha)
{
if (alpha == 0)
return (pixman_color_t){0, 0, 0, 0};
int alpha_div = 0xffff / alpha;
return (pixman_color_t){
.red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)) / alpha_div,
@ -655,6 +663,389 @@ render_worker_thread(void *_ctx)
return -1;
}
struct csd_data {
int x;
int y;
int width;
int height;
};
static struct csd_data
get_csd_data(const struct terminal *term, enum csd_surface surf_idx)
{
assert(term->window->use_csd == CSD_YES);
/* Only title bar is rendered in maximized mode */
const int border_width = !term->window->is_maximized
? term->conf->csd.border_width * term->scale : 0;
const int title_height = !term->window->is_fullscreen
? term->conf->csd.title_height * term->scale : 0;
const int button_width = !term->window->is_fullscreen
? term->conf->csd.button_width * term->scale : 0;
switch (surf_idx) {
case CSD_SURF_TITLE: return (struct csd_data){ 0, -title_height, term->width, title_height};
case CSD_SURF_LEFT: return (struct csd_data){-border_width, -title_height, border_width, title_height + term->height};
case CSD_SURF_RIGHT: return (struct csd_data){ term->width, -title_height, border_width, title_height + term->height};
case CSD_SURF_TOP: return (struct csd_data){-border_width, -title_height - border_width, term->width + 2 * border_width, border_width};
case CSD_SURF_BOTTOM: return (struct csd_data){-border_width, term->height, term->width + 2 * border_width, border_width};
/* Positioned relative to CSD_SURF_TITLE */
case CSD_SURF_MINIMIZE: return (struct csd_data){term->width - 3 * button_width, 0, button_width, title_height};
case CSD_SURF_MAXIMIZE: return (struct csd_data){term->width - 2 * button_width, 0, button_width, title_height};
case CSD_SURF_CLOSE: return (struct csd_data){term->width - 1 * button_width, 0, button_width, title_height};
case CSD_SURF_COUNT:
assert(false);
return (struct csd_data){0};
}
assert(false);
return (struct csd_data){0};
}
static void
csd_commit(struct terminal *term, struct wl_surface *surf, struct buffer *buf)
{
wl_surface_attach(surf, buf->wl_buf, 0, 0);
wl_surface_damage_buffer(surf, 0, 0, buf->width, buf->height);
wl_surface_set_buffer_scale(surf, term->scale);
wl_surface_commit(surf);
}
static void
render_csd_part(struct terminal *term,
struct wl_surface *surf, struct buffer *buf,
int width, int height, pixman_color_t *color)
{
assert(term->window->use_csd == CSD_YES);
pixman_image_t *src = pixman_image_create_solid_fill(color);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, color, 1,
&(pixman_rectangle16_t){0, 0, buf->width, buf->height});
pixman_image_unref(src);
}
static void
render_csd_title(struct terminal *term)
{
assert(term->window->use_csd == CSD_YES);
struct csd_data info = get_csd_data(term, CSD_SURF_TITLE);
struct wl_surface *surf = term->window->csd.surface[CSD_SURF_TITLE];
assert(info.width > 0 && info.height > 0);
unsigned long cookie = shm_cookie_csd(term, CSD_SURF_TITLE);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie);
uint32_t _color = term->colors.default_fg;
uint16_t alpha = 0xffff;
if (term->conf->csd.color.title_set) {
_color = term->conf->csd.color.title;
alpha = _color >> 24 | (_color >> 24 << 8);
}
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha);
if (!term->visual_focus)
pixman_color_dim(&color);
render_csd_part(term, surf, buf, info.width, info.height, &color);
csd_commit(term, surf, buf);
}
static void
render_csd_border(struct terminal *term, enum csd_surface surf_idx)
{
assert(term->window->use_csd == CSD_YES);
assert(surf_idx >= CSD_SURF_LEFT && surf_idx <= CSD_SURF_BOTTOM);
struct csd_data info = get_csd_data(term, surf_idx);
struct wl_surface *surf = term->window->csd.surface[surf_idx];
if (info.width == 0 || info.height == 0)
return;
unsigned long cookie = shm_cookie_csd(term, surf_idx);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie);
pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0);
render_csd_part(term, surf, buf, info.width, info.height, &color);
csd_commit(term, surf, buf);
}
static void
render_csd_button_minimize(struct terminal *term, struct buffer *buf)
{
pixman_color_t color = color_hex_to_pixman(term->colors.default_bg);
pixman_image_t *src = pixman_image_create_solid_fill(&color);
const int max_height = buf->height / 2;
const int max_width = buf->width / 2;
int width = max_width;
int height = max_width / 2;
if (height > max_height) {
height = max_height;
width = height * 2;
}
assert(width <= max_width);
assert(height <= max_height);
int x_margin = (buf->width - width) / 2.;
int y_margin = (buf->height - height) / 2.;
pixman_triangle_t tri = {
.p1 = {
.x = pixman_int_to_fixed(x_margin),
.y = pixman_int_to_fixed(y_margin),
},
.p2 = {
.x = pixman_int_to_fixed(x_margin + width),
.y = pixman_int_to_fixed(y_margin),
},
.p3 = {
.x = pixman_int_to_fixed(buf->width / 2),
.y = pixman_int_to_fixed(y_margin + height),
},
};
pixman_composite_triangles(
PIXMAN_OP_OVER, src, buf->pix, PIXMAN_a1,
0, 0, 0, 0, 1, &tri);
pixman_image_unref(src);
}
static void
render_csd_button_maximize_maximized(
struct terminal *term, struct buffer *buf)
{
pixman_color_t color = color_hex_to_pixman(term->colors.default_bg);
pixman_image_t *src = pixman_image_create_solid_fill(&color);
const int max_height = buf->height / 3;
const int max_width = buf->width / 3;
int width = min(max_height, max_width);
int thick = 1 * term->scale;
const int x_margin = (buf->width - width) / 2;
const int y_margin = (buf->height - width) / 2;
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &color, 4,
(pixman_rectangle16_t[]){
{x_margin, y_margin, width, thick},
{x_margin, y_margin + thick, thick, width - 2 * thick},
{x_margin + width - thick, y_margin + thick, thick, width - 2 * thick},
{x_margin, y_margin + width - thick, width, thick}});
pixman_image_unref(src);
}
static void
render_csd_button_maximize_window(
struct terminal *term, struct buffer *buf)
{
pixman_color_t color = color_hex_to_pixman(term->colors.default_bg);
pixman_image_t *src = pixman_image_create_solid_fill(&color);
const int max_height = buf->height / 2;
const int max_width = buf->width / 2;
int width = max_width;
int height = max_width / 2;
if (height > max_height) {
height = max_height;
width = height * 2;
}
assert(width <= max_width);
assert(height <= max_height);
int x_margin = (buf->width - width) / 2.;
int y_margin = (buf->height - height) / 2.;
pixman_triangle_t tri = {
.p1 = {
.x = pixman_int_to_fixed(buf->width / 2),
.y = pixman_int_to_fixed(y_margin),
},
.p2 = {
.x = pixman_int_to_fixed(x_margin),
.y = pixman_int_to_fixed(y_margin + height),
},
.p3 = {
.x = pixman_int_to_fixed(x_margin + width),
.y = pixman_int_to_fixed(y_margin + height),
},
};
pixman_composite_triangles(
PIXMAN_OP_OVER, src, buf->pix, PIXMAN_a1,
0, 0, 0, 0, 1, &tri);
pixman_image_unref(src);
}
static void
render_csd_button_maximize(struct terminal *term, struct buffer *buf)
{
if (term->window->is_maximized)
render_csd_button_maximize_maximized(term, buf);
else
render_csd_button_maximize_window(term, buf);
}
static void
render_csd_button_close(struct terminal *term, struct buffer *buf)
{
pixman_color_t color = color_hex_to_pixman(term->colors.default_bg);
pixman_image_t *src = pixman_image_create_solid_fill(&color);
const int max_height = buf->height / 3;
const int max_width = buf->width / 3;
int width = min(max_height, max_width);
const int x_margin = (buf->width - width) / 2;
const int y_margin = (buf->height - width) / 2;
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &color, 1,
&(pixman_rectangle16_t){x_margin, y_margin, width, width});
pixman_image_unref(src);
}
static void
render_csd_button(struct terminal *term, enum csd_surface surf_idx)
{
assert(term->window->use_csd == CSD_YES);
assert(surf_idx >= CSD_SURF_MINIMIZE && surf_idx <= CSD_SURF_CLOSE);
struct csd_data info = get_csd_data(term, surf_idx);
struct wl_surface *surf = term->window->csd.surface[surf_idx];
if (info.width == 0 || info.height == 0)
return;
unsigned long cookie = shm_cookie_csd(term, surf_idx);
struct buffer *buf = shm_get_buffer(
term->wl->shm, info.width, info.height, cookie);
uint32_t _color;
uint16_t alpha = 0xffff;
bool is_active = false;
const bool *is_set = NULL;
const uint32_t *conf_color = NULL;
switch (surf_idx) {
case CSD_SURF_MINIMIZE:
_color = 0xff1e90ff;
is_set = &term->conf->csd.color.minimize_set;
conf_color = &term->conf->csd.color.minimize;
is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE;
break;
case CSD_SURF_MAXIMIZE:
_color = 0xff30ff30;
is_set = &term->conf->csd.color.maximize_set;
conf_color = &term->conf->csd.color.maximize;
is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE;
break;
case CSD_SURF_CLOSE:
_color = 0xffff3030;
is_set = &term->conf->csd.color.close_set;
conf_color = &term->conf->csd.color.close;
is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE;
break;
default:
assert(false);
break;
}
if (is_active) {
if (*is_set) {
_color = *conf_color;
alpha = _color >> 24 | (_color >> 24 << 8);
}
} else {
_color = 0;
alpha = 0;
}
pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha);
if (!term->visual_focus)
pixman_color_dim(&color);
render_csd_part(term, surf, buf, info.width, info.height, &color);
switch (surf_idx) {
case CSD_SURF_MINIMIZE: render_csd_button_minimize(term, buf); break;
case CSD_SURF_MAXIMIZE: render_csd_button_maximize(term, buf); break;
case CSD_SURF_CLOSE: render_csd_button_close(term, buf); break;
break;
default:
assert(false);
break;
}
csd_commit(term, surf, buf);
}
static void
render_csd(struct terminal *term)
{
assert(term->window->use_csd == CSD_YES);
if (term->window->is_fullscreen)
return;
for (size_t i = 0; i < CSD_SURF_COUNT; i++) {
struct csd_data info = get_csd_data(term, i);
const int x = info.x;
const int y = info.y;
const int width = info.width;
const int height = info.height;
struct wl_surface *surf = term->window->csd.surface[i];
struct wl_subsurface *sub = term->window->csd.sub_surface[i];
assert(surf != NULL);
assert(sub != NULL);
if (width == 0 || height == 0) {
/* CSD borders aren't rendered in maximized mode */
assert(term->window->is_maximized || term->window->is_fullscreen);
wl_subsurface_set_position(sub, 0, 0);
wl_surface_attach(surf, NULL, 0, 0);
wl_surface_commit(surf);
continue;
}
wl_subsurface_set_position(sub, x / term->scale, y / term->scale);
}
for (size_t i = CSD_SURF_LEFT; i <= CSD_SURF_BOTTOM; i++)
render_csd_border(term, i);
for (size_t i = CSD_SURF_MINIMIZE; i <= CSD_SURF_CLOSE; i++)
render_csd_button(term, i);
render_csd_title(term);
}
static void frame_callback(
void *data, struct wl_callback *wl_callback, uint32_t callback_data);
@ -678,13 +1069,16 @@ grid_render(struct terminal *term)
assert(term->width > 0);
assert(term->height > 0);
unsigned long cookie = (uintptr_t)term;
unsigned long cookie = shm_cookie_grid(term);
struct buffer *buf = shm_get_buffer(
term->wl->shm, term->width, term->height, cookie);
wl_surface_attach(term->window->surface, buf->wl_buf, 0, 0);
pixman_image_t *pix = buf->pix;
pixman_region16_t clip;
pixman_region_init_rect(&clip, term->margins.left, term->margins.top, term->cols * term->cell_width, term->rows * term->cell_height);
pixman_image_set_clip_region(pix, &clip);
/* If we resized the window, or is flashing, or just stopped flashing */
if (term->render.last_buf != buf ||
@ -718,6 +1112,7 @@ grid_render(struct terminal *term)
if (term->is_searching)
pixman_color_dim(&bg);
pixman_image_set_clip_region(pix, NULL);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, pix, &bg, 4,
(pixman_rectangle16_t[]){
@ -725,6 +1120,7 @@ grid_render(struct terminal *term)
{0, 0, term->margins.left, term->height}, /* Left */
{rmargin, 0, term->margins.right, term->height}, /* Right */
{0, bmargin, term->width, term->margins.bottom}}); /* Bottom */
pixman_image_set_clip_region(pix, &clip);
wl_surface_damage_buffer(
term->window->surface, 0, 0, term->width, term->margins.top);
@ -954,41 +1350,29 @@ grid_render(struct terminal *term)
}
static void
frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
{
struct terminal *term = data;
assert(term->window->frame_callback == wl_callback);
wl_callback_destroy(wl_callback);
term->window->frame_callback = NULL;
if (term->render.pending) {
term->render.pending = false;
grid_render(term);
}
}
void
render_search_box(struct terminal *term)
{
assert(term->window->search_sub_surface != NULL);
const size_t wanted_visible_chars = max(20, term->search.len);
const int scale = term->scale >= 1 ? term->scale : 1;
const size_t margin = scale * 3;
assert(term->scale >= 1);
const int scale = term->scale;
const size_t width = min(
const size_t margin = 3 * scale;
const size_t width = term->width - 2 * margin;
const size_t visible_width = min(
term->width - 2 * margin,
2 * margin + wanted_visible_chars * term->cell_width);
const size_t height = min(
term->height - 2 * margin,
2 * margin + 1 * term->cell_height);
const size_t visible_chars = (width - 2 * margin) / term->cell_width;
const size_t visible_chars = (visible_width - 2 * margin) / term->cell_width;
size_t glyph_offset = term->render.search_glyph_offset;
unsigned long cookie = (uintptr_t)term + 1;
unsigned long cookie = shm_cookie_search(term);
struct buffer *buf = shm_get_buffer(term->wl->shm, width, height, cookie);
/* Background - yellow on empty/match, red on mismatch */
@ -998,15 +1382,20 @@ render_search_box(struct terminal *term)
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &color,
1, &(pixman_rectangle16_t){0, 0, width, height});
1, &(pixman_rectangle16_t){width - visible_width, 0, visible_width, height});
pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0);
pixman_image_fill_rectangles(
PIXMAN_OP_SRC, buf->pix, &transparent,
1, &(pixman_rectangle16_t){0, 0, width - visible_width, height});
struct font *font = term->fonts[0];
int x = margin;
int x = width - visible_width + margin;
int y = margin;
pixman_color_t fg = color_hex_to_pixman(term->colors.table[0]);
if (term->search.cursor < glyph_offset ||
term->search.cursor >= glyph_offset + visible_chars + 2)
term->search.cursor >= glyph_offset + visible_chars + 1)
{
/* Make sure cursor is always visible */
term->render.search_glyph_offset = glyph_offset = term->search.cursor;
@ -1014,7 +1403,7 @@ render_search_box(struct terminal *term)
/* Text (what the user entered - *not* match(es)) */
for (size_t i = glyph_offset;
i < term->search.len && i - glyph_offset < visible_chars + 1;
i < term->search.len && i - glyph_offset < visible_chars;
i++)
{
if (i == term->search.cursor)
@ -1037,23 +1426,70 @@ render_search_box(struct terminal *term)
if (term->search.cursor >= term->search.len)
draw_bar(term, buf->pix, font, &fg, x, y);
quirk_weston_subsurface_desync_on(term->window->search_sub_surface);
/* TODO: this is only necessary on a window resize */
wl_subsurface_set_position(
term->window->search_sub_surface,
max(0, (int32_t)term->width - width - margin),
max(0, (int32_t)term->height - height - margin));
margin / scale,
max(0, (int32_t)term->height - height - margin) / scale);
wl_surface_damage_buffer(term->window->search_surface, 0, 0, width, height);
wl_surface_attach(term->window->search_surface, buf->wl_buf, 0, 0);
wl_surface_damage_buffer(term->window->search_surface, 0, 0, width, height);
wl_surface_set_buffer_scale(term->window->search_surface, scale);
struct wl_region *region = wl_compositor_create_region(term->wl->compositor);
if (region != NULL) {
wl_region_add(region, width - visible_width, 0, visible_width, height);
wl_surface_set_opaque_region(term->window->search_surface, region);
wl_region_destroy(region);
}
wl_surface_commit(term->window->search_surface);
quirk_weston_subsurface_desync_off(term->window->search_sub_surface);
}
static void
frame_callback(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
{
struct terminal *term = data;
assert(term->window->frame_callback == wl_callback);
wl_callback_destroy(wl_callback);
term->window->frame_callback = NULL;
if (term->render.pending.csd) {
term->render.pending.csd = false;
if (term->window->use_csd == CSD_YES) {
quirk_weston_csd_on(term);
render_csd(term);
quirk_weston_csd_off(term);
}
}
if (term->render.pending.search) {
term->render.pending.search = false;
if (term->is_searching)
render_search_box(term);
}
if (term->render.pending.grid) {
term->render.pending.grid = false;
grid_render(term);
}
}
/* Move to terminal.c? */
static void
static bool
maybe_resize(struct terminal *term, int width, int height, bool force)
{
if (!force && (width == 0 || height == 0))
return;
if (!term->window->is_configured)
return false;
if (term->cell_width == 0 && term->cell_height == 0)
return false;
int scale = -1;
tll_foreach(term->window->on_outputs, it) {
@ -1069,13 +1505,50 @@ maybe_resize(struct terminal *term, int width, int height, bool force)
width *= scale;
height *= scale;
if (!force && width == 0 && height == 0) {
/* Assume we're not fully up and running yet */
return;
if (width == 0 && height == 0) {
/*
* The compositor is letting us choose the size
*
* If we have a "last" used size - use that. Otherwise, use
* the size from the user configuration.
*/
if (term->unmaximized_width != 0 && term->unmaximized_height != 0) {
width = term->unmaximized_width;
height = term->unmaximized_height;
} else {
width = term->conf->width;
height = term->conf->height;
if (term->window->use_csd == CSD_YES) {
/* Take CSD title bar into account */
assert(!term->window->is_fullscreen);
height -= term->conf->csd.title_height;
}
width *= scale;
height *= scale;
}
}
/* Don't shrink grid too much */
const int min_cols = 20;
const int min_rows = 4;
/* Minimum window size */
const int min_width = min_cols * term->cell_width;
const int min_height = min_rows * term->cell_height;
width = max(width, min_width);
height = max(height, min_height);
/* Padding */
const int max_pad_x = (width - min_width) / 2;
const int max_pad_y = (height - min_height) / 2;
const int pad_x = min(max_pad_x, scale * term->conf->pad_x);
const int pad_y = min(max_pad_y, scale * term->conf->pad_y);
if (!force && width == term->width && height == term->height && scale == term->scale)
return;
return false;
selection_cancel(term);
@ -1092,13 +1565,9 @@ maybe_resize(struct terminal *term, int width, int height, bool force)
const int old_cols = term->cols;
const int old_rows = term->rows;
/* Padding */
const int pad_x = term->width > 2 * scale * term->conf->pad_x ? scale * term->conf->pad_x : 0;
const int pad_y = term->height > 2 * scale * term->conf->pad_y ? scale * term->conf->pad_y : 0;
/* Screen rows/cols after resize */
const int new_cols = max((term->width - 2 * pad_x) / term->cell_width, 1);
const int new_rows = max((term->height - 2 * pad_y) / term->cell_height, 1);
const int new_cols = (term->width - 2 * pad_x) / term->cell_width;
const int new_rows = (term->height - 2 * pad_y) / term->cell_height;
/* Grid rows/cols after resize */
const int new_normal_grid_rows = 1 << (32 - __builtin_clz(new_rows + scrollback_lines - 1));
@ -1108,11 +1577,16 @@ maybe_resize(struct terminal *term, int width, int height, bool force)
assert(new_rows >= 1);
/* Margins */
term->margins.left = (term->width - new_cols * term->cell_width) / 2;
term->margins.top = (term->height - new_rows * term->cell_height) / 2;
term->margins.left = pad_x;
term->margins.top = pad_y;
term->margins.right = term->width - new_cols * term->cell_width - term->margins.left;
term->margins.bottom = term->height - new_rows * term->cell_height - term->margins.top;
assert(term->margins.left >= pad_x);
assert(term->margins.right >= pad_x);
assert(term->margins.top >= pad_y);
assert(term->margins.bottom >= pad_y);
if (new_cols == old_cols && new_rows == old_rows) {
LOG_DBG("grid layout unaffected; skipping reflow");
goto damage_view;
@ -1132,10 +1606,10 @@ maybe_resize(struct terminal *term, int width, int height, bool force)
term->cols = new_cols;
term->rows = new_rows;
LOG_INFO("resize: %dx%d, grid: cols=%d, rows=%d "
"(left-margin=%d, right-margin=%d, top-margin=%d, bottom-margin=%d)",
term->width, term->height, term->cols, term->rows,
term->margins.left, term->margins.right, term->margins.top, term->margins.bottom);
LOG_DBG("resize: %dx%d, grid: cols=%d, rows=%d "
"(left-margin=%d, right-margin=%d, top-margin=%d, bottom-margin=%d)",
term->width, term->height, term->cols, term->rows,
term->margins.left, term->margins.right, term->margins.top, term->margins.bottom);
/* Signal TIOCSWINSZ */
if (ioctl(term->ptmx, TIOCSWINSZ,
@ -1174,20 +1648,49 @@ maybe_resize(struct terminal *term, int width, int height, bool force)
term->render.last_cursor.cell = NULL;
damage_view:
if (!term->window->is_maximized && !term->window->is_fullscreen) {
term->unmaximized_width = term->width;
term->unmaximized_height = term->height;
}
#if 0
/* TODO: doesn't include CSD title bar */
xdg_toplevel_set_min_size(
term->window->xdg_toplevel, min_width / scale, min_height / scale);
#endif
{
bool title_shown = !term->window->is_fullscreen &&
term->window->use_csd == CSD_YES;
int title_height = title_shown ? term->conf->csd.title_height : 0;
xdg_surface_set_window_geometry(
term->window->xdg_surface,
0,
-title_height,
term->width / term->scale,
term->height / term->scale + title_height);
}
tll_free(term->normal.scroll_damage);
tll_free(term->alt.scroll_damage);
term->render.last_buf = NULL;
term_damage_view(term);
render_refresh_csd(term);
render_refresh_search(term);
render_refresh(term);
return true;
}
void
bool
render_resize(struct terminal *term, int width, int height)
{
return maybe_resize(term, width, height, false);
}
void
bool
render_resize_force(struct terminal *term, int width, int height)
{
return maybe_resize(term, width, height, true);
@ -1263,20 +1766,50 @@ fdm_hook_refresh_pending_terminals(struct fdm *fdm, void *data)
tll_foreach(renderer->wayl->terms, it) {
struct terminal *term = it->item;
if (!term->render.refresh_needed)
if (!term->render.refresh.grid &&
!term->render.refresh.csd &&
!term->render.refresh.search)
{
continue;
}
if (term->render.app_sync_updates.enabled)
if (term->render.app_sync_updates.enabled &&
!term->render.refresh.csd &&
!term->render.refresh.search)
{
continue;
}
if (term->render.refresh.csd || term->render.refresh.search) {
/* Force update of parent surface */
term->render.refresh.grid = true;
}
assert(term->window->is_configured);
term->render.refresh_needed = false;
if (term->window->frame_callback == NULL)
grid_render(term);
else {
bool grid = term->render.refresh.grid;
bool csd = term->render.refresh.csd;
bool search = term->render.refresh.search;
term->render.refresh.grid = false;
term->render.refresh.csd = false;
term->render.refresh.search = false;
if (term->window->frame_callback == NULL) {
if (csd && term->window->use_csd == CSD_YES) {
quirk_weston_csd_on(term);
render_csd(term);
quirk_weston_csd_off(term);
}
if (search)
render_search_box(term);
if (grid)
grid_render(term);
} else {
/* Tells the frame callback to render again */
term->render.pending = true;
term->render.pending.grid = grid;
term->render.pending.csd = csd;
term->render.pending.search = search;
}
}
@ -1311,7 +1844,21 @@ render_set_title(struct terminal *term, const char *_title)
void
render_refresh(struct terminal *term)
{
term->render.refresh_needed = true;
term->render.refresh.grid = true;
}
void
render_refresh_csd(struct terminal *term)
{
if (term->window->use_csd == CSD_YES)
term->render.refresh.csd = true;
}
void
render_refresh_search(struct terminal *term)
{
if (term->is_searching)
term->render.refresh.search = true;
}
bool

View file

@ -1,4 +1,5 @@
#pragma once
#include <stdbool.h>
#include "terminal.h"
#include "fdm.h"
@ -8,15 +9,15 @@ struct renderer;
struct renderer *render_init(struct fdm *fdm, struct wayland *wayl);
void render_destroy(struct renderer *renderer);
void render_resize(struct terminal *term, int width, int height);
void render_resize_force(struct terminal *term, int width, int height);
bool render_resize(struct terminal *term, int width, int height);
bool render_resize_force(struct terminal *term, int width, int height);
void render_set_title(struct terminal *term, const char *title);
void render_refresh(struct terminal *term);
void render_refresh_csd(struct terminal *term);
void render_refresh_search(struct terminal *term);
bool render_xcursor_set(struct terminal *term);
void render_search_box(struct terminal *term);
struct render_worker_context {
int my_id;
struct terminal *term;

View file

@ -40,8 +40,14 @@ search_ensure_size(struct terminal *term, size_t wanted_size)
static void
search_cancel_keep_selection(struct terminal *term)
{
wl_surface_attach(term->window->search_surface, NULL, 0, 0);
wl_surface_commit(term->window->search_surface);
struct wl_window *win = term->window;
if (win->search_sub_surface != NULL)
wl_subsurface_destroy(win->search_sub_surface);
if (win->search_surface != NULL)
wl_surface_destroy(win->search_surface);
win->search_surface = NULL;
win->search_sub_surface = NULL;
free(term->search.buf);
term->search.buf = NULL;
@ -65,13 +71,22 @@ search_begin(struct terminal *term)
search_cancel_keep_selection(term);
selection_cancel(term);
/* On-demand instantiate wayland surface */
struct wl_window *win = term->window;
struct wayland *wayl = term->wl;
win->search_surface = wl_compositor_create_surface(wayl->compositor);
wl_surface_set_user_data(win->search_surface, term->window);
win->search_sub_surface = wl_subcompositor_get_subsurface(
wayl->sub_compositor, win->search_surface, win->surface);
wl_subsurface_set_sync(win->search_sub_surface);
term->search.original_view = term->grid->view;
term->search.view_followed_offset = term->grid->view == term->grid->offset;
term->is_searching = true;
term_xcursor_update(term);
render_search_box(term);
render_refresh(term);
render_refresh_search(term);
}
void
@ -600,6 +615,5 @@ search_input(struct terminal *term, uint32_t key, xkb_keysym_t sym, xkb_mod_mask
LOG_DBG("search: buffer: %S", term->search.buf);
search_find_next(term);
render_refresh(term);
render_search_box(term);
render_refresh_search(term);
}

7
shm.c
View file

@ -2,6 +2,7 @@
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/mman.h>
@ -124,13 +125,15 @@ shm_get_buffer(struct wl_shm *shm, int width, int height, unsigned long cookie)
size = stride * height;
LOG_DBG("cookie=%lx: allocating new buffer: %zu KB", cookie, size / 1024);
int err = posix_fallocate(pool_fd, 0, size);
int err = EINTR;
while (err == EINTR)
err = posix_fallocate(pool_fd, 0, size);
if (err != 0) {
static bool failure_logged = false;
if (!failure_logged) {
failure_logged = true;
LOG_ERRNO_P("failed to fallocate", err);
LOG_ERRNO_P("failed to fallocate %zu bytes", err, size);
}
if (ftruncate(pool_fd, size) == -1) {

View file

@ -59,6 +59,11 @@ sixel_erase(struct terminal *term, struct sixel *sixel)
int r = (sixel->pos.row + i) & (term->grid->num_rows - 1);
struct row *row = term->grid->rows[r];
if (row == NULL) {
/* A resize/reflow may cause row to now be unallocated */
continue;
}
row->dirty = true;
for (int c = 0; c < term->grid->num_cols; c++)

51
slave.c
View file

@ -1,7 +1,9 @@
#include "slave.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
@ -16,6 +18,52 @@
#include "tokenize.h"
static bool
is_valid_shell(const char *shell)
{
FILE *f = fopen("/etc/shells", "r");
if (f == NULL)
goto err;
char *_line = NULL;
size_t count = 0;
while (true) {
errno = 0;
ssize_t ret = getline(&_line, &count, f);
if (ret < 0) {
free(_line);
break;
}
char *line = _line;
{
while (isspace(*line))
line++;
if (line[0] != '\0') {
char *end = line + strlen(line) - 1;
while (isspace(*end))
end--;
*(end + 1) = '\0';
}
}
if (line[0] == '#')
continue;
if (strcmp(line, shell) == 0) {
fclose(f);
return true;
}
}
err:
if (f != NULL)
fclose(f);
return false;
}
static void
slave_exec(int ptmx, char *argv[], int err_fd, bool login_shell)
{
@ -145,6 +193,9 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
shell_argv[count] = NULL;
}
if (is_valid_shell(shell_argv[0]))
setenv("SHELL", shell_argv[0], 1);
slave_exec(ptmx, shell_argv, fork_pipe[1], login_shell);
assert(false);
break;

View file

@ -21,12 +21,14 @@
#include "async.h"
#include "config.h"
#include "grid.h"
#include "quirks.h"
#include "render.h"
#include "selection.h"
#include "sixel.h"
#include "slave.h"
#include "vt.h"
#define ALEN(v) (sizeof(v) / sizeof(v[0]))
#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))
@ -197,7 +199,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
*/
if (term->window->frame_callback == NULL) {
if (term->render.app_sync_updates.enabled)
term->render.refresh_needed = true;
term->render.refresh.grid = true;
else {
/* First timeout - reset each time we receive input. */
@ -233,7 +235,7 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data)
}
}
} else
term->render.pending = true;
term->render.pending.grid = true;
if (hup) {
if (term->hold_at_exit) {
@ -519,7 +521,8 @@ term_set_fonts(struct terminal *term, struct font *fonts[static 4])
term->cell_width = term->fonts[0]->space_x_advance > 0
? term->fonts[0]->space_x_advance : term->fonts[0]->max_x_advance;
term->cell_height = term->fonts[0]->height;
term->cell_height = max(term->fonts[0]->height,
term->fonts[0]->ascent + term->fonts[0]->descent);
LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height);
render_resize_force(term, term->width, term->height);
@ -773,36 +776,13 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl,
term_set_window_title(term, "foot");
/* Load fonts */
#if 0
struct font *fonts[4];
if (!load_fonts_from_conf(term, conf, fonts))
if (!term_font_dpi_changed(term))
goto err;
term_set_fonts(term, fonts);
#endif
term_font_dpi_changed(term);
/* Start the slave/client */
if ((term->slave = slave_spawn(term->ptmx, argc, term->cwd, argv, term_env, conf->shell, login_shell)) == -1)
goto err;
if (term->width == 0 && term->height == 0) {
/* Try to use user-configured window dimentions */
unsigned width = conf->width;
unsigned height = conf->height;
if (width == -1) {
/* No user-configuration - use 80x24 cells */
assert(height == -1);
width = 80 * term->cell_width;
height = 24 * term->cell_height;
}
/* Don't go below a single cell */
width = max(width, term->cell_width);
height = max(height, term->cell_height);
render_resize(term, width, height);
}
return term;
@ -1657,6 +1637,7 @@ term_visual_focus_in(struct terminal *term)
if (term->cursor_blink.active)
cursor_blink_start_timer(term);
render_refresh_csd(term);
cursor_refresh(term);
}
@ -1670,6 +1651,7 @@ term_visual_focus_out(struct terminal *term)
if (term->cursor_blink.active)
cursor_blink_stop_timer(term);
render_refresh_csd(term);
cursor_refresh(term);
}
@ -2133,3 +2115,30 @@ term_print(struct terminal *term, wchar_t wc, int width)
else
term->cursor.lcf = true;
}
enum term_surface
term_surface_kind(const struct terminal *term, const struct wl_surface *surface)
{
if (surface == term->window->surface)
return TERM_SURF_GRID;
else if (surface == term->window->search_surface)
return TERM_SURF_SEARCH;
else if (surface == term->window->csd.surface[CSD_SURF_TITLE])
return TERM_SURF_TITLE;
else if (surface == term->window->csd.surface[CSD_SURF_LEFT])
return TERM_SURF_BORDER_LEFT;
else if (surface == term->window->csd.surface[CSD_SURF_RIGHT])
return TERM_SURF_BORDER_RIGHT;
else if (surface == term->window->csd.surface[CSD_SURF_TOP])
return TERM_SURF_BORDER_TOP;
else if (surface == term->window->csd.surface[CSD_SURF_BOTTOM])
return TERM_SURF_BORDER_BOTTOM;
else if (surface == term->window->csd.surface[CSD_SURF_MINIMIZE])
return TERM_SURF_BUTTON_MINIMIZE;
else if (surface == term->window->csd.surface[CSD_SURF_MAXIMIZE])
return TERM_SURF_BUTTON_MAXIMIZE;
else if (surface == term->window->csd.surface[CSD_SURF_CLOSE])
return TERM_SURF_BUTTON_CLOSE;
else
return TERM_SURF_NONE;
}

View file

@ -185,6 +185,20 @@ struct sixel {
struct coord pos;
};
enum term_surface {
TERM_SURF_NONE,
TERM_SURF_GRID,
TERM_SURF_SEARCH,
TERM_SURF_TITLE,
TERM_SURF_BORDER_LEFT,
TERM_SURF_BORDER_RIGHT,
TERM_SURF_BORDER_TOP,
TERM_SURF_BORDER_BOTTOM,
TERM_SURF_BUTTON_MINIMIZE,
TERM_SURF_BUTTON_MAXIMIZE,
TERM_SURF_BUTTON_CLOSE,
};
struct terminal {
struct fdm *fdm;
const struct config *conf;
@ -237,6 +251,8 @@ struct terminal {
int scale;
int width; /* pixels */
int height; /* pixels */
int unmaximized_width; /* last unmaximized size, pixels */
int unmaximized_height; /* last unmaximized size, pixels */
struct {
int left;
int right;
@ -312,9 +328,23 @@ struct terminal {
struct wayland *wl;
struct wl_window *window;
bool visual_focus;
enum term_surface active_surface;
struct {
bool refresh_needed; /* Terminal needs to be re-rendered, as soon-as-possible */
/* Scheduled for rendering, as soon-as-possible */
struct {
bool grid;
bool csd;
bool search;
} refresh;
/* Scheduled for rendering, in the next frame callback */
struct {
bool grid;
bool csd;
bool search;
} pending;
int scrollback_lines; /* Number of scrollback lines, from conf (TODO: move out from render struct?) */
struct {
@ -341,7 +371,6 @@ struct terminal {
struct cell *cell; /* For easy access to content */
} last_cursor;
bool pending; /* Need to re-render again, after next frame-callback */
struct buffer *last_buf; /* Buffer we rendered to last time */
bool was_flashing; /* Flash was active last time we rendered */
bool was_searching;
@ -477,3 +506,6 @@ bool term_spawn_new(const struct terminal *term);
void term_enable_app_sync_updates(struct terminal *term);
void term_disable_app_sync_updates(struct terminal *term);
enum term_surface term_surface_kind(
const struct terminal *term, const struct wl_surface *surface);

213
wayland.c
View file

@ -29,12 +29,51 @@
#include "render.h"
#include "selection.h"
#define ALEN(v) (sizeof(v) / sizeof(v[0]))
#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))
static bool wayl_reload_cursor_theme(
struct wayland *wayl, struct terminal *term);
static void
csd_instantiate(struct wl_window *win)
{
struct wayland *wayl = win->term->wl;
assert(wayl != NULL);
for (size_t i = 0; i < ALEN(win->csd.surface); i++) {
assert(win->csd.surface[i] == NULL);
assert(win->csd.sub_surface[i] == NULL);
win->csd.surface[i] = wl_compositor_create_surface(wayl->compositor);
struct wl_surface *parent = i < CSD_SURF_MINIMIZE
? win->surface : win->csd.surface[CSD_SURF_TITLE];
win->csd.sub_surface[i] = wl_subcompositor_get_subsurface(
wayl->sub_compositor, win->csd.surface[i], parent);
wl_subsurface_set_sync(win->csd.sub_surface[i]);
wl_surface_set_user_data(win->csd.surface[i], win);
wl_surface_commit(win->csd.surface[i]);
}
}
static void
csd_destroy(struct wl_window *win)
{
for (size_t i = 0; i < ALEN(win->csd.surface); i++) {
if (win->csd.sub_surface[i] != NULL)
wl_subsurface_destroy(win->csd.sub_surface[i]);
if (win->csd.surface[i] != NULL)
wl_surface_destroy(win->csd.surface[i]);
win->csd.surface[i] = NULL;
win->csd.sub_surface[i] = NULL;
}
}
static void
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
{
@ -64,24 +103,28 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
{
struct wayland *wayl = data;
if (wayl->keyboard != NULL) {
wl_keyboard_release(wayl->keyboard);
wayl->keyboard = NULL;
}
if (wayl->pointer.pointer != NULL) {
wl_pointer_release(wayl->pointer.pointer);
wayl->pointer.pointer = NULL;
}
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
wayl->keyboard = wl_seat_get_keyboard(wl_seat);
wl_keyboard_add_listener(wayl->keyboard, &keyboard_listener, wayl);
if (wayl->keyboard == NULL) {
wayl->keyboard = wl_seat_get_keyboard(wl_seat);
wl_keyboard_add_listener(wayl->keyboard, &keyboard_listener, wayl);
}
} else {
if (wayl->keyboard != NULL) {
wl_keyboard_release(wayl->keyboard);
wayl->keyboard = NULL;
}
}
if (caps & WL_SEAT_CAPABILITY_POINTER) {
wayl->pointer.pointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(wayl->pointer.pointer, &pointer_listener, wayl);
if (wayl->pointer.pointer == NULL) {
wayl->pointer.pointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(wayl->pointer.pointer, &pointer_listener, wayl);
}
} else {
if (wayl->pointer.pointer != NULL) {
wl_pointer_release(wayl->pointer.pointer);
wayl->pointer.pointer = NULL;
}
}
}
@ -98,6 +141,9 @@ static const struct wl_seat_listener seat_listener = {
static void
update_term_for_output_change(struct terminal *term)
{
if (tll_length(term->window->on_outputs) == 0)
return;
render_resize(term, term->width / term->scale, term->height / term->scale);
term_font_dpi_changed(term);
wayl_reload_cursor_theme(term->wl, term);
@ -322,7 +368,7 @@ handle_global(void *data, struct wl_registry *registry,
}
else if (strcmp(interface, wl_output_interface.name) == 0) {
const uint32_t required = 3;
const uint32_t required = 2;
if (!verify_iface_version(interface, version, required))
return;
@ -425,6 +471,8 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
int32_t width, int32_t height, struct wl_array *states)
{
bool is_activated = false;
bool is_fullscreen = false;
bool is_maximized = false;
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
char state_str[2048];
@ -445,12 +493,10 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
enum xdg_toplevel_state *state;
wl_array_for_each(state, states) {
switch (*state) {
case XDG_TOPLEVEL_STATE_ACTIVATED:
is_activated = true;
break;
case XDG_TOPLEVEL_STATE_ACTIVATED: is_activated = true; break;
case XDG_TOPLEVEL_STATE_FULLSCREEN: is_fullscreen = true; break;
case XDG_TOPLEVEL_STATE_MAXIMIZED: is_maximized = true; break;
case XDG_TOPLEVEL_STATE_MAXIMIZED:
case XDG_TOPLEVEL_STATE_FULLSCREEN:
case XDG_TOPLEVEL_STATE_RESIZING:
case XDG_TOPLEVEL_STATE_TILED_LEFT:
case XDG_TOPLEVEL_STATE_TILED_RIGHT:
@ -489,7 +535,18 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
* xdg_surface_configure() after we've ack:ed the event.
*/
struct wl_window *win = data;
if (!is_fullscreen && win->use_csd == CSD_YES && width > 0 && height > 0) {
/*
* We include the CSD title bar in our window geometry. Thus,
* the height we call render_resize() with must be adjusted,
* since it expects the size to refer to the main grid only.
*/
height -= win->term->conf->csd.title_height;
}
win->configure.is_activated = is_activated;
win->configure.is_fullscreen = is_fullscreen;
win->configure.is_maximized = is_maximized;
win->configure.width = width;
win->configure.height = height;
}
@ -513,19 +570,37 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface,
uint32_t serial)
{
LOG_DBG("xdg-surface: configure");
xdg_surface_ack_configure(xdg_surface, serial);
struct wl_window *win = data;
struct terminal *term = win->term;
win->is_configured = true;
win->is_maximized = win->configure.is_maximized;
render_resize(term, win->configure.width, win->configure.height);
if (win->is_fullscreen != win->configure.is_fullscreen && win->use_csd == CSD_YES) {
if (win->configure.is_fullscreen)
csd_destroy(win);
else
csd_instantiate(win);
}
win->is_fullscreen = win->configure.is_fullscreen;
xdg_surface_ack_configure(xdg_surface, serial);
bool resized = render_resize(term, win->configure.width, win->configure.height);
if (win->configure.is_activated)
term_visual_focus_in(term);
else
term_visual_focus_out(term);
if (!resized) {
/*
* If we didn't resize, we won't be commit a new surface
* anytime soon. Some compositors require a commit in
* combination with an ack - make them happy.
*/
wl_surface_commit(win->surface);
}
}
static const struct xdg_surface_listener xdg_surface_listener = {
@ -537,19 +612,37 @@ xdg_toplevel_decoration_configure(void *data,
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1,
uint32_t mode)
{
struct wl_window *win = data;
switch (mode) {
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
LOG_ERR("unimplemented: client-side decorations");
LOG_INFO("using CSD decorations");
win->use_csd = CSD_YES;
csd_instantiate(win);
break;
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
LOG_DBG("using server-side decorations");
LOG_INFO("using SSD decorations");
win->use_csd = CSD_NO;
csd_destroy(win);
break;
default:
LOG_ERR("unimplemented: unknown XDG toplevel decoration mode: %u", mode);
break;
}
if (win->is_configured && win->use_csd == CSD_YES) {
struct terminal *term = win->term;
int scale = term->scale;
int width = term->width / scale;
int height = term->height / scale;
/* Take CSD title bar into account */
height -= term->conf->csd.title_height;
render_resize_force(term, width, height);
}
}
static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration_listener = {
@ -879,9 +972,12 @@ struct wl_window *
wayl_win_init(struct terminal *term)
{
struct wayland *wayl = term->wl;
const struct config *conf = wayl->conf;
struct wl_window *win = calloc(1, sizeof(*win));
win->term = term;
win->use_csd = CSD_UNKNOWN;
win->csd.move_timeout_fd = -1;
win->surface = wl_compositor_create_surface(wayl->compositor);
if (win->surface == NULL) {
@ -911,18 +1007,27 @@ wayl_win_init(struct terminal *term)
xdg_toplevel_set_app_id(win->xdg_toplevel, "foot");
/* Request server-side decorations */
win->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
wayl->xdg_decoration_manager, win->xdg_toplevel);
zxdg_toplevel_decoration_v1_set_mode(
win->xdg_toplevel_decoration, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
zxdg_toplevel_decoration_v1_add_listener(
win->xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, win);
if (wayl->xdg_decoration_manager != NULL) {
win->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(
wayl->xdg_decoration_manager, win->xdg_toplevel);
/* Scrollback search box */
win->search_surface = wl_compositor_create_surface(wayl->compositor);
win->search_sub_surface = wl_subcompositor_get_subsurface(
wayl->sub_compositor, win->search_surface, win->surface);
wl_subsurface_set_desync(win->search_sub_surface);
LOG_INFO("requesting %s decorations",
conf->csd.preferred == CONF_CSD_PREFER_SERVER ? "SSD" : "CSD");
zxdg_toplevel_decoration_v1_set_mode(
win->xdg_toplevel_decoration,
(conf->csd.preferred == CONF_CSD_PREFER_SERVER
? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE
: ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE));
zxdg_toplevel_decoration_v1_add_listener(
win->xdg_toplevel_decoration, &xdg_toplevel_decoration_listener, win);
} else {
/* No decoration manager - thus we *must* draw our own decorations */
win->use_csd = CSD_YES;
csd_instantiate(win);
LOG_WARN("no decoration manager available - using CSDs unconditionally");
}
wl_surface_commit(win->surface);
return win;
@ -939,6 +1044,9 @@ wayl_win_destroy(struct wl_window *win)
if (win == NULL)
return;
if (win->csd.move_timeout_fd != -1)
close(win->csd.move_timeout_fd);
/*
* First, unmap all surfaces to trigger things like
* keyboard_leave() and wl_pointer_leave().
@ -949,16 +1057,29 @@ wayl_win_destroy(struct wl_window *win)
*/
/* Scrollback search */
wl_surface_attach(win->search_surface, NULL, 0, 0);
wl_surface_commit(win->search_surface);
if (win->search_surface != NULL) {
wl_surface_attach(win->search_surface, NULL, 0, 0);
wl_surface_commit(win->search_surface);
}
/* CSD */
for (size_t i = 0; i < ALEN(win->csd.surface); i++) {
if (win->csd.surface[i] != NULL) {
wl_surface_attach(win->csd.surface[i], NULL, 0, 0);
wl_surface_commit(win->csd.surface[i]);
}
}
wayl_roundtrip(win->term->wl);
/* Main window */
/* Main window */
wl_surface_attach(win->surface, NULL, 0, 0);
wl_surface_commit(win->surface);
wayl_roundtrip(win->term->wl);
tll_free(win->on_outputs);
csd_destroy(win);
if (win->search_sub_surface != NULL)
wl_subsurface_destroy(win->search_sub_surface);
if (win->search_surface != NULL)
@ -1004,22 +1125,6 @@ wayl_reload_cursor_theme(struct wayland *wayl, struct terminal *term)
return render_xcursor_set(term);
}
struct terminal *
wayl_terminal_from_surface(struct wayland *wayl, struct wl_surface *surface)
{
tll_foreach(wayl->terms, it) {
if (it->item->window->surface == surface ||
it->item->window->search_surface == surface)
{
return it->item;
}
}
assert(false);
LOG_WARN("surface %p doesn't map to a terminal", surface);
return NULL;
}
void
wayl_flush(struct wayland *wayl)
{

View file

@ -82,6 +82,18 @@ struct wl_primary {
uint32_t serial;
};
enum csd_surface {
CSD_SURF_TITLE,
CSD_SURF_LEFT,
CSD_SURF_RIGHT,
CSD_SURF_TOP,
CSD_SURF_BOTTOM,
CSD_SURF_MINIMIZE,
CSD_SURF_MAXIMIZE,
CSD_SURF_CLOSE,
CSD_SURF_COUNT,
};
struct wayland;
struct wl_window {
struct terminal *term;
@ -91,6 +103,15 @@ struct wl_window {
struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration;
enum {CSD_UNKNOWN, CSD_NO, CSD_YES } use_csd;
struct {
struct wl_surface *surface[CSD_SURF_COUNT];
struct wl_subsurface *sub_surface[CSD_SURF_COUNT];
int move_timeout_fd;
uint32_t serial;
} csd;
/* Scrollback search */
struct wl_surface *search_surface;
struct wl_subsurface *search_sub_surface;
@ -100,8 +121,12 @@ struct wl_window {
tll(const struct monitor *) on_outputs; /* Outputs we're mapped on */
bool is_configured;
bool is_fullscreen;
bool is_maximized;
struct {
bool is_activated;
bool is_fullscreen;
bool is_maximized;
int width;
int height;
} configure;
@ -158,6 +183,8 @@ struct wayland {
} pointer;
struct {
int x;
int y;
int col;
int row;
int button;
@ -184,8 +211,5 @@ void wayl_destroy(struct wayland *wayl);
void wayl_flush(struct wayland *wayl);
void wayl_roundtrip(struct wayland *wayl);
struct terminal *wayl_terminal_from_surface(
struct wayland *wayl, struct wl_surface *surface);
struct wl_window *wayl_win_init(struct terminal *term);
void wayl_win_destroy(struct wl_window *win);