diff --git a/CHANGELOG.md b/CHANGELOG.md index 1013add5..96c7c8bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.17.0](#1-17-0) * [1.16.2](#1-16-2) * [1.16.1](#1-16-1) @@ -49,6 +50,39 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added + +* `cursor.unfocused-style=unchanged|hollow|none` to `foot.ini`. The + default is `hollow` ([#1582][1582]). +* New key binding: `quit` ([#1475][1475]). + +[1582]: https://codeberg.org/dnkl/foot/issues/1582 +[1475]: https://codeberg.org/dnkl/foot/issues/1475 + + +### Changed +### Deprecated +### Removed +### Fixed + +* Log-level not respected by syslog. +* Regression: terminal shutting down when the PTY is closed by the + client application, which may be earlier than when the client + application exits ([#1666][1666]). +* When closing the window, send `SIGHUP` to the client application, + before sending `SIGTERM`. The signal sequence is now `SIGHUP`, wait, + `SIGTERM`, wait `SIGKILL`. +* Crash when receiving a `DECRQSS` request with more than 2 bytes in + the `q` parameter. + +[1666]: https://codeberg.org/dnkl/foot/issues/1666 + + +### Security +### Contributors + + ## 1.17.0 ### Added diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..4b652df6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,83 @@ +# Foot Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +Participants in the foot community are expected to uphold the described +standards not only in official community spaces (issue trackers, IRC channels, +etc.) but in all public spaces. The Code of Conduct however does acknowledge +that people are fallible and that it is possible to truely correct a past +pattern of unacceptable behavior. That is to say, the scope of the Code of +Conduct does not necessarily extend into the distant past. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported to the community leaders responsible for enforcement +at [daniel@ekloef.se](mailto:daniel@ekloef.se). All complaints will +be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +The consequences for Code of Conduct violations will be decided upon and +enforced by community leaders. These may include a formal warning, a temporary +ban from community spaces, a permanent ban from community spaces, etc. + +There are no hard and fast rules for exactly what behavior in which space will +result in what consequences, it is up to the community leaders to enforce the +Code of Conduct in a way that they believe best promotes a healthy community. + +## Attribution + +This Code of Conduct is adapted from the +[Contributor Covenant](https://www.contributor-covenant.org/), +version 2.1, available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. diff --git a/README.md b/README.md index eb7fe81c..c188f76c 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator. 1. [Programmatically checking if running in foot](#programmatically-checking-if-running-in-foot) 1. [XTGETTCAP](#xtgettcap) 1. [Credits](#Credits) +1. [Code of Conduct](#code-of-conduct) 1. [Bugs](#bugs) 1. [Contact](#contact) 1. [IRC](#irc) @@ -304,7 +305,7 @@ when starting your Wayland compositor (i.e. logging in to your desktop), and then run `footclient` instead of `foot` whenever you want to launch a new terminal. -Foot support socket activation, which means `foot --server` will only be +Foot supports socket activation, which means `foot --server` will only be started the first time you'll run `footclient`. (systemd user units are included, but it can work with other supervision suites). @@ -644,6 +645,11 @@ queried capabilities are **always** sent. No queries are ever dropped. contributing foot's [logo](icons/hicolor/48x48/apps/foot.png). +# Code of Conduct + +See [Code of Conduct](CODE_OF_CONDUCT.md) + + # Bugs Please report bugs to https://codeberg.org/dnkl/foot/issues diff --git a/config.c b/config.c index d2b75180..d63934b3 100644 --- a/config.c +++ b/config.c @@ -119,6 +119,7 @@ static const char *const binding_action_map[] = { [BIND_ACTION_PROMPT_PREV] = "prompt-prev", [BIND_ACTION_PROMPT_NEXT] = "prompt-next", [BIND_ACTION_UNICODE_INPUT] = "unicode-input", + [BIND_ACTION_QUIT] = "quit", /* Mouse-specific actions */ [BIND_ACTION_SCROLLBACK_UP_MOUSE] = "scrollback-up-mouse", @@ -1383,6 +1384,16 @@ parse_section_cursor(struct context *ctx) (int *)&conf->cursor.style); } + else if (streq(key, "unfocused-style")) { + _Static_assert(sizeof(conf->cursor.unfocused_style) == sizeof(int), + "enum is not 32-bit"); + + return value_to_enum( + ctx, + (const char *[]){"unchanged", "hollow", "none", NULL}, + (int *)&conf->cursor.unfocused_style); + } + else if (streq(key, "blink")) return value_to_bool(ctx, &conf->cursor.blink); @@ -3090,6 +3101,7 @@ config_load(struct config *conf, const char *conf_path, .cursor = { .style = CURSOR_BLOCK, + .unfocused_style = CURSOR_UNFOCUSED_HOLLOW, .blink = false, .color = { .text = 0, diff --git a/config.h b/config.h index 30219ff0..df3d45f9 100644 --- a/config.h +++ b/config.h @@ -28,6 +28,11 @@ struct font_size_adjustment { }; enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM }; +enum cursor_unfocused_style { + CURSOR_UNFOCUSED_UNCHANGED, + CURSOR_UNFOCUSED_HOLLOW, + CURSOR_UNFOCUSED_NONE +}; enum conf_size_type {CONF_SIZE_PX, CONF_SIZE_CELLS}; @@ -256,6 +261,7 @@ struct config { struct { enum cursor_style style; + enum cursor_unfocused_style unfocused_style; bool blink; struct { uint32_t text; diff --git a/dcs.c b/dcs.c index c4309459..19cce3c2 100644 --- a/dcs.c +++ b/dcs.c @@ -239,7 +239,7 @@ decrqss_put(struct terminal *term, uint8_t c) return; struct vt *vt = &term->vt; - if (vt->dcs.idx > 2) + if (vt->dcs.idx >= 2) return; vt->dcs.data[vt->dcs.idx++] = c; } diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 2c50ca24..a3ddb7c3 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -500,6 +500,15 @@ applications can change these at runtime. *beam* or *underline*. Note that this can be overridden by applications. Default: _block_. +*unfocused-style* + Configures how the cursor is rendered when the terminal window is + unfocused. Possible values are: + + - unchanged: render cursor in exactly the same way as when the + window has focus. + - hollow: render a block cursor, but hollowed out. + - none: do not display any cursor at all. + *blink* Boolean. Enables blinking cursor. Note that this can be overridden by applications. Default: _no_. @@ -916,6 +925,9 @@ e.g. *search-start=none*. Default: _Control+Shift+u_. +*quit* + Quit foot. Default: _none_. + # SECTION: search-bindings This section lets you override the default key bindings used in diff --git a/foot.ini b/foot.ini index 9fd6c9db..a2c85e97 100644 --- a/foot.ini +++ b/foot.ini @@ -193,6 +193,7 @@ # clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste # primary-paste=Shift+Insert # unicode-input=none +# quit=none # scrollback-up-page=Shift+Page_Up # scrollback-up-half-page=none # scrollback-up-line=none diff --git a/input.c b/input.c index 1a42e5ee..26f62629 100644 --- a/input.c +++ b/input.c @@ -444,6 +444,10 @@ execute_binding(struct seat *seat, struct terminal *term, unicode_mode_activate(seat); return true; + case BIND_ACTION_QUIT: + term_shutdown(term); + return true; + case BIND_ACTION_SELECT_BEGIN: selection_start( term, seat->mouse.col, seat->mouse.row, SELECTION_CHAR_WISE, false); diff --git a/key-binding.h b/key-binding.h index ba841efa..f42dbc48 100644 --- a/key-binding.h +++ b/key-binding.h @@ -40,6 +40,7 @@ enum bind_action_normal { BIND_ACTION_PROMPT_PREV, BIND_ACTION_PROMPT_NEXT, BIND_ACTION_UNICODE_INPUT, + BIND_ACTION_QUIT, /* Mouse specific actions - i.e. they require a mouse coordinate */ BIND_ACTION_SCROLLBACK_UP_MOUSE, @@ -53,7 +54,7 @@ enum bind_action_normal { BIND_ACTION_SELECT_QUOTE, BIND_ACTION_SELECT_ROW, - BIND_ACTION_KEY_COUNT = BIND_ACTION_UNICODE_INPUT + 1, + BIND_ACTION_KEY_COUNT = BIND_ACTION_QUIT + 1, BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1, }; diff --git a/log.c b/log.c index c13b4179..d19adaca 100644 --- a/log.c +++ b/log.c @@ -105,6 +105,9 @@ _sys_log(enum log_class log_class, const char *module, if (!do_syslog) return; + if (log_class > log_level) + return; + /* Map our log level to syslog's level */ int level = log_level_map[log_class].syslog_equivalent; diff --git a/render.c b/render.c index bda43f1a..804ccdc3 100644 --- a/render.c +++ b/render.c @@ -305,8 +305,8 @@ color_brighten(const struct terminal *term, uint32_t color) } static void -draw_unfocused_block(const struct terminal *term, pixman_image_t *pix, - const pixman_color_t *color, int x, int y, int cell_cols) +draw_hollow_block(const struct terminal *term, pixman_image_t *pix, + const pixman_color_t *color, int x, int y, int cell_cols) { const int scale = (int)roundf(term->scale); const int width = min(min(scale, term->cell_width), term->cell_height); @@ -429,10 +429,23 @@ draw_cursor(const struct terminal *term, const struct cell *cell, switch (term->cursor_style) { case CURSOR_BLOCK: - if (unlikely(!term->kbd_focus)) - draw_unfocused_block(term, pix, &cursor_color, x, y, cols); + if (unlikely(!term->kbd_focus)) { + switch (term->conf->cursor.unfocused_style) { + case CURSOR_UNFOCUSED_UNCHANGED: + break; - else if (likely(term->cursor_blink.state == CURSOR_BLINK_ON)) { + case CURSOR_UNFOCUSED_HOLLOW: + draw_hollow_block(term, pix, fg, x, y, cols); + return; + + case CURSOR_UNFOCUSED_NONE: + return; + } + } + + if (likely(term->cursor_blink.state == CURSOR_BLINK_ON) || + !term->kbd_focus) + { *fg = text_color; pixman_image_fill_rectangles( PIXMAN_OP_SRC, pix, &cursor_color, 1, @@ -1513,7 +1526,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, /* Hollow cursor */ if (start >= 0 && end <= term->cols) { int cols = end - start; - draw_unfocused_block(term, buf->pix[0], &cursor_color, x, y, cols); + draw_hollow_block(term, buf->pix[0], &cursor_color, x, y, cols); } term_ime_set_cursor_rect( @@ -3373,7 +3386,7 @@ render_search_box(struct terminal *term) /* TODO: how do we handle a partially hidden rectangle? */ if (start >= 0 && end <= visible_cells) { - draw_unfocused_block( + draw_hollow_block( term, buf->pix[0], &fg, x + start * term->cell_width, y, end - start); } term_ime_set_cursor_rect(term, diff --git a/terminal.c b/terminal.c index e4396585..3fb918a4 100644 --- a/terminal.c +++ b/terminal.c @@ -364,8 +364,20 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) del_utmp_record(term->conf, term->reaper, term->ptmx); fdm_del(fdm, fd); term->ptmx = -1; - if (!term->conf->hold_at_exit) + + /* + * Normally, we do *not* want to shutdown when the PTY is + * closed. Instead, we want to wait for the client application + * to exit. + * + * However, when we're using a pre-existing PTY (the --pty + * option), there _is_ no client application. That is, foot + * does *not* fork+exec anything, and thus the only way to + * shutdown is to wait for the PTY to be closed. + */ + if (term->slave < 0 && !term->conf->hold_at_exit) { term_shutdown(term); + } } return true; @@ -1527,10 +1539,36 @@ fdm_terminate_timeout(struct fdm *fdm, int fd, int events, void *data) struct terminal *term = data; xassert(!term->shutdown.client_has_terminated); - LOG_DBG("slave (PID=%u) has not terminated, sending SIGKILL (%d)", - term->slave, SIGKILL); + LOG_DBG("slave (PID=%u) has not terminated, sending %s (%d)", + term->slave, + term->shutdown.next_signal == SIGTERM ? "SIGTERM" + : term->shutdown.next_signal == SIGKILL ? "SIGKILL" + : "", + term->shutdown.next_signal); + + kill(-term->slave, term->shutdown.next_signal); + + switch (term->shutdown.next_signal) { + case SIGTERM: + term->shutdown.next_signal = SIGKILL; + break; + + case SIGKILL: + /* Disarm. Shouldn't be necessary, as we should be able to + shutdown completely after sending SIGKILL, before the next + timeout occurs). But lets play it safe... */ + if (term->shutdown.terminate_timeout_fd >= 0) { + timerfd_settime( + term->shutdown.terminate_timeout_fd, 0, + &(const struct itimerspec){0}, NULL); + } + break; + + default: + BUG("can only handle SIGTERM and SIGKILL"); + return false; + } - kill(-term->slave, SIGKILL); return true; } @@ -1571,11 +1609,18 @@ term_shutdown(struct terminal *term) term->shutdown.client_has_terminated = true; } else { LOG_DBG("initiating asynchronous terminate of slave; " - "sending SIGTERM to PID=%u", term->slave); + "sending SIGHUP to PID=%u", term->slave); - kill(-term->slave, SIGTERM); + kill(-term->slave, SIGHUP); - const struct itimerspec timeout = {.it_value = {.tv_sec = 60}}; + /* + * Set up a timer, with an interval - on the first timeout + * we'll send SIGTERM. If the the client application still + * isn't terminating, we'll wait an additional interval, + * and then send SIGKILL. + */ + const struct itimerspec timeout = {.it_value = {.tv_sec = 30}, + .it_interval = {.tv_sec = 30}}; int timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); if (timeout_fd < 0 || @@ -1590,6 +1635,7 @@ term_shutdown(struct terminal *term) xassert(term->shutdown.terminate_timeout_fd < 0); term->shutdown.terminate_timeout_fd = timeout_fd; + term->shutdown.next_signal = SIGTERM; } } @@ -1772,9 +1818,9 @@ term_destroy(struct terminal *term) exit_status = term->shutdown.exit_status; else { LOG_DBG("initiating blocking terminate of slave; " - "sending SIGTERM to PID=%u", term->slave); + "sending SIGHUP to PID=%u", term->slave); - kill(-term->slave, SIGTERM); + kill(-term->slave, SIGHUP); /* * we've closed the ptxm, and sent SIGTERM to the client @@ -1795,7 +1841,12 @@ term_destroy(struct terminal *term) struct sigaction action = {.sa_handler = &sig_alarm}; sigemptyset(&action.sa_mask); sigaction(SIGALRM, &action, NULL); - alarm(60); + + /* Wait, then send SIGTERM, wait again, then send SIGKILL */ + int next_signal = SIGTERM; + + alarm_raised = 0; + alarm(30); while (true) { int r = waitpid(term->slave, &exit_status, 0); @@ -1807,11 +1858,16 @@ term_destroy(struct terminal *term) xassert(errno == EINTR); if (alarm_raised) { - LOG_DBG( - "slave (PID=%u) has not terminate yet, " - "sending: SIGKILL (%d)", term->slave, SIGKILL); + LOG_DBG("slave (PID=%u) has not terminated yet, " + "sending: %s (%d)", term->slave, + next_signal == SIGTERM ? "SIGTERM" : "SIGKILL", + next_signal); - kill(-term->slave, SIGKILL); + kill(-term->slave, next_signal); + next_signal = SIGKILL; + + alarm_raised = 0; + alarm(30); } } } @@ -2125,7 +2181,7 @@ term_fractional_scaling(const struct terminal *term) bool term_preferred_buffer_scale(const struct terminal *term) { - return term->wl->has_wl_compositor_v6; + return term->wl->has_wl_compositor_v6 && term->window->preferred_buffer_scale > 0; } bool diff --git a/terminal.h b/terminal.h index 953f3329..fa80e693 100644 --- a/terminal.h +++ b/terminal.h @@ -723,6 +723,7 @@ struct terminal { bool client_has_terminated; int terminate_timeout_fd; int exit_status; + int next_signal; void (*cb)(void *data, int exit_code); void *cb_data; diff --git a/tests/test-config.c b/tests/test-config.c index b54dd23e..3d4d345c 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -635,6 +635,12 @@ test_section_cursor(void) (const char *[]){"block", "beam", "underline"}, (int []){CURSOR_BLOCK, CURSOR_BEAM, CURSOR_UNDERLINE}, (int *)&conf.cursor.style); + test_enum( + &ctx, &parse_section_cursor, "unfocused-style", + 3, + (const char *[]){"unchanged", "hollow", "none"}, + (int []){CURSOR_UNFOCUSED_UNCHANGED, CURSOR_UNFOCUSED_HOLLOW, CURSOR_UNFOCUSED_NONE}, + (int *)&conf.cursor.unfocused_style); test_boolean(&ctx, &parse_section_cursor, "blink", &conf.cursor.blink); test_pt_or_px(&ctx, &parse_section_cursor, "beam-thickness", &conf.cursor.beam_thickness); diff --git a/themes/dracula-iterm b/themes/dracula-iterm new file mode 100644 index 00000000..8c2f66c3 --- /dev/null +++ b/themes/dracula-iterm @@ -0,0 +1,25 @@ +# -*- conf -*- +# Dracula iTerm2 variant + +[cursor] +color=ffffff bbbbbb + +[colors] +foreground=f8f8f2 +background=1e1f29 +regular0=000000 # black +regular1=ff5555 # red +regular2=50fa7b # green +regular3=f1fa8c # yellow +regular4=bd93f9 # blue +regular5=ff79c6 # magenta +regular6=8be9fd # cyan +regular7=bbbbbb # white +bright0=555555 # bright black +bright1=ff5555 # bright red +bright2=50fa7b # bright green +bright3=f1fa8c # bright yellow +bright4=bd93f9 # bright blue +bright5=ff79c6 # bright magenta +bright6=8be9fd # bright cyan +bright7=ffffff # bright white diff --git a/themes/noirblaze b/themes/noirblaze new file mode 100644 index 00000000..3cf452e6 --- /dev/null +++ b/themes/noirblaze @@ -0,0 +1,31 @@ +# -*- conf -*- +# noirblaze-kitty +# https://github.com/n1ghtmare/noirblaze-kitty + + +[cursor] +color=121212 ff0088 + +[colors] +foreground=d5d5d5 +background=121212 + +# selection-foreground=121212 +# selection-background=b0b0b0 + +regular0=121212 # black +regular1=ff0088 # red +regular2=00ff77 # green +regular3=ffffff # yellow +regular4=b0b0b0 # blue +regular5=7a7a7a # magenta +regular6=787878 # cyan +regular7=d5d5d5 # white +bright0=737373 # bright black +bright1=FD319E # bright red +bright2=FD319E # bright green +bright3=FDFDFD # bright yellow +bright4=BEBEBE # bright blue +bright5=939393 # bright magenta +bright6=919191 # bright cyan +bright7=f5f5f5 # bright white diff --git a/themes/xterm b/themes/xterm new file mode 100644 index 00000000..bf17f5e7 --- /dev/null +++ b/themes/xterm @@ -0,0 +1,22 @@ +# -*- conf -*- +# The default palette of xterm. + +[colors] +foreground=e5e5e5 +background=000000 +regular0=000000 # black +regular1=cd0000 # red +regular2=00cd00 # green +regular3=cdcd00 # yellow +regular4=0000ee # blue +regular5=cd00cd # magenta +regular6=00cdcd # cyan +regular7=e5e5e5 # white +bright0=7f7f7f # bright black +bright1=ff0000 # bright red +bright2=00ff00 # bright green +bright3=ffff00 # bright yellow +bright4=5c5cff # bright blue +bright5=ff00ff # bright magenta +bright6=00ffff # bright cyan +bright7=ffffff # bright white diff --git a/wayland.c b/wayland.c index 2645c4ab..1d9d3cdf 100644 --- a/wayland.c +++ b/wayland.c @@ -1738,10 +1738,6 @@ wayl_win_init(struct terminal *term, const char *token) win->fractional_scale, &fractional_scale_listener, win); } - if (wayl->has_wl_compositor_v6) { - win->preferred_buffer_scale = 1; - } - win->xdg_surface = xdg_wm_base_get_xdg_surface(wayl->shell, win->surface.surf); xdg_surface_add_listener(win->xdg_surface, &xdg_surface_listener, win);