From 8414966013ad0b88694697fab2df2d1766366a14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 3 Jan 2025 08:03:06 +0100 Subject: [PATCH 001/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be32f4d6..a7e1b169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.20.1](#1-20-1) * [1.20.0](#1-20-0) * [1.19.0](#1-19-0) @@ -56,6 +57,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.20.1 ### Changed From 9667fe2b263871ea510039178e75b75d247c9e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 3 Jan 2025 08:08:52 +0100 Subject: [PATCH 002/353] changelog: add missing issue ref --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e1b169..4b5e4b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,7 +83,7 @@ ### Fixed * Regression: trying to print a Unicode _"Legacy Computing symbol"_, - in the range U+1FB00 - U+1FB9B would crash foot ([#][]). + in the range U+1FB00 - U+1FB9B would crash foot ([#1901][1901]). [1901]: https://codeberg.org/dnkl/foot/issues/1901 From 77e30c8b4501e719bc18b4afe06d5fc0eb13d699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2025 09:50:06 +0100 Subject: [PATCH 003/353] ci: sr.ht: disable x64 (rely on codeberg only) --- .builds/{alpine-x64.yml => alpine-x64.yml.disabled} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .builds/{alpine-x64.yml => alpine-x64.yml.disabled} (100%) diff --git a/.builds/alpine-x64.yml b/.builds/alpine-x64.yml.disabled similarity index 100% rename from .builds/alpine-x64.yml rename to .builds/alpine-x64.yml.disabled From 169471cf23ccba1e82f6d5a216c0ca774052c365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2025 09:50:30 +0100 Subject: [PATCH 004/353] ci: sr.ht: try to bring up to date, and pull from codeberg --- .builds/freebsd-x64.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.builds/freebsd-x64.yml b/.builds/freebsd-x64.yml index 9642f96d..497db929 100644 --- a/.builds/freebsd-x64.yml +++ b/.builds/freebsd-x64.yml @@ -19,7 +19,7 @@ packages: - noto-emoji sources: - - https://git.sr.ht/~dnkl/foot + - https://codeberg.org/dnkl/foot.git # triggers: # - action: email @@ -29,6 +29,7 @@ sources: tasks: - fcft: | cd foot/subprojects + git clone https://codeberg.org/dnkl/tllist.git git clone https://codeberg.org/dnkl/fcft.git cd ../.. - debug: | From a2960aa457d867746789ca227259b80e8fe7d1df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2025 10:06:45 +0100 Subject: [PATCH 005/353] meson: fix dependencies (utf8proc missing in lots of places) --- meson.build | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 359a8ce0..ee5af2f3 100644 --- a/meson.build +++ b/meson.build @@ -226,7 +226,8 @@ common = static_library( 'debug.c', 'debug.h', 'macros.h', 'xmalloc.c', 'xmalloc.h', - 'xsnprintf.c', 'xsnprintf.h' + 'xsnprintf.c', 'xsnprintf.h', + dependencies: [utf8proc] ) misc = static_library( @@ -234,7 +235,9 @@ misc = static_library( 'hsl.c', 'hsl.h', 'macros.h', 'misc.c', 'misc.h', - 'uri.c', 'uri.h' + 'uri.c', 'uri.h', + dependencies: [utf8proc], + link_with: [common] ) vtlib = static_library( @@ -268,6 +271,7 @@ pgolib = static_library( tokenize = static_library( 'tokenizelib', 'tokenize.c', + dependencies: [utf8proc], link_with: [common], ) @@ -321,7 +325,7 @@ executable( 'macros.h', 'util.h', version, - dependencies: [tllist], + dependencies: [tllist, utf8proc], link_with: common, install: true) From 6999968ee5ba111946b237bb1bb186a8526bbb23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2025 10:33:23 +0100 Subject: [PATCH 006/353] changelog: utf8proc.h not found --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5e4b61..f98908fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,8 +84,11 @@ * Regression: trying to print a Unicode _"Legacy Computing symbol"_, in the range U+1FB00 - U+1FB9B would crash foot ([#1901][1901]). +* Build failures (`utf8proc.h` not found) on at least FreeBSD, but + most likely other BSDs, as well as some Linuxes ([#1903][1903]). [1901]: https://codeberg.org/dnkl/foot/issues/1901 +[1903]: https://codeberg.org/dnkl/foot/issues/1903 ## 1.20.0 From 42f78b7f9c755d5fa7e04f0cbbbf88c58dabd44d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Jan 2025 12:06:49 +0100 Subject: [PATCH 007/353] ci: "meson [options]" is deprecated (do "meson setup [options]" instead) --- .builds/freebsd-x64.yml | 4 ++-- .woodpecker.yaml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.builds/freebsd-x64.yml b/.builds/freebsd-x64.yml index 497db929..77775ac3 100644 --- a/.builds/freebsd-x64.yml +++ b/.builds/freebsd-x64.yml @@ -34,7 +34,7 @@ tasks: cd ../.. - debug: | mkdir -p bld/debug - meson --buildtype=debug -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug + meson setup --buildtype=debug -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug ninja -C bld/debug -k0 meson test -C bld/debug --print-errorlogs bld/debug/foot --version @@ -42,7 +42,7 @@ tasks: - release: | mkdir -p bld/release - meson --buildtype=minsize -Db_pgo=generate -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release + meson setup --buildtype=minsize -Db_pgo=generate -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release ninja -C bld/release -k0 meson test -C bld/release --print-errorlogs bld/release/foot --version diff --git a/.woodpecker.yaml b/.woodpecker.yaml index 9b121f2a..340ba241 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -49,7 +49,7 @@ steps: # Debug - mkdir -p bld/debug-x64 - cd bld/debug-x64 - - meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - meson setup --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. - ninja -v -k0 - ninja -v test - ./foot --version @@ -59,7 +59,7 @@ steps: # Release (gcc) - mkdir -p bld/release-x64 - cd bld/release-x64 - - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - meson setup --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. - ninja -v -k0 - ninja -v test - ./foot --version @@ -69,7 +69,7 @@ steps: # Release (clang) - mkdir -p bld/release-x64-clang - cd bld/release-x64-clang - - CC=clang meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - CC=clang meson setup --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. - ninja -v -k0 - ninja -v test - ./foot --version @@ -80,7 +80,7 @@ steps: - apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev - mkdir -p bld/debug - cd bld/debug - - meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../.. + - meson setup --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../.. - ninja -v -k0 - ninja -v test - ./foot --version @@ -106,7 +106,7 @@ steps: # Debug - mkdir -p bld/debug-x86 - cd bld/debug-x86 - - meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - meson setup --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. - ninja -v -k0 - ninja -v test - ./foot --version @@ -116,7 +116,7 @@ steps: # Release (gcc) - mkdir -p bld/release-x86 - cd bld/release-x86 - - meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - meson setup --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. - ninja -v -k0 - ninja -v test - ./foot --version @@ -126,7 +126,7 @@ steps: # Release (clang) - mkdir -p bld/release-x86-clang - cd bld/release-x86-clang - - CC=clang meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. + - CC=clang meson setup --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../.. - ninja -v -k0 - ninja -v test - ./foot --version From 8e425c4e976017c09ed2a9b4402067b3595501cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 7 Jan 2025 12:58:44 +0100 Subject: [PATCH 008/353] csi: ignore 'CSI 21 t' - report window title It's not widely used (don't know _any_ application that uses it), and can be used to trick users to run unwanted commands. --- csi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/csi.c b/csi.c index 35a39f82..61cbdced 100644 --- a/csi.c +++ b/csi.c @@ -1354,10 +1354,14 @@ csi_dispatch(struct terminal *term, uint8_t final) } case 21: { +#if 0 /* Disabled for now, see #1894 */ char reply[3 + strlen(term->window_title) + 2 + 1]; int chars = xsnprintf( reply, sizeof(reply), "\033]l%s\033\\", term->window_title); term_to_slave(term, reply, chars); +#else + LOG_WARN("CSI 21 t (report window title) ignored"); +#endif break; } From 06a32d553e8d2613e3b4582199a317219f5cfc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 7 Jan 2025 13:00:10 +0100 Subject: [PATCH 009/353] osc: ignore 'OSC 176 ?' - report app ID It's not widely used (don't know _any_ application that uses it), and can be used to trick users to run unwanted commands. --- osc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osc.c b/osc.c index 17639c19..e335dc61 100644 --- a/osc.c +++ b/osc.c @@ -1498,6 +1498,7 @@ osc_dispatch(struct terminal *term) case 176: if (string[0] == '?' && string[1] == '\0') { +#if 0 /* Disabled for now, see #1894 */ const char *terminator = term->vt.osc.bel ? "\a" : "\033\\"; char *reply = xasprintf( "\033]176;%s%s", @@ -1506,6 +1507,9 @@ osc_dispatch(struct terminal *term) term_to_slave(term, reply, strlen(reply)); free(reply); +#else + LOG_WARN("OSC-176 app-id query ignored"); +#endif break; } From d9bd9b7ffaf682e66629839bcfe6a8e668cf0822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 7 Jan 2025 13:00:38 +0100 Subject: [PATCH 010/353] doc: ctlseqs: remove 'CSI 21 t' --- doc/foot-ctlseqs.7.scd | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index 60f78d83..f8eb1222 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -391,9 +391,6 @@ manipulation sequences. The generic format is: | 20 : - : Report icon label. -| 21 -: - -: Report window title. | 22 : - : Push window title+icon. From bcc176cdf181b6b3a5b84edea8ce62b5f596b9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 7 Jan 2025 13:00:50 +0100 Subject: [PATCH 011/353] changelog: 'CSI 21 t' and 'OSC 176 ?' disabled --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f98908fe..bc1d3bc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,13 @@ ## Unreleased ### Added ### Changed + +* The `CSI 21 t` (report window title) and `OSC 176 ?` (report app-id) + escape sequences are now ignored ([#1894][1894]). + +[1894]: https://codeberg.org/dnkl/foot/issues/1894 + + ### Deprecated ### Removed ### Fixed From f0253633d3cdde994b48c25692707e1df2f1e1e7 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Sat, 4 Jan 2025 22:26:00 -0500 Subject: [PATCH 012/353] render: Expose render_overlay This function updates the overlay that foot uses. It will be used to update the overlay when the flash effect ends. --- pgo/pgo.c | 2 ++ render.c | 2 +- render.h | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pgo/pgo.c b/pgo/pgo.c index aab18847..24154277 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -72,6 +72,8 @@ void render_refresh_title(struct terminal *term) {} void render_refresh_app_id(struct terminal *term) {} void render_refresh_icon(struct terminal *term) {} +void render_overlay(struct terminal *term) {} + bool render_xcursor_is_valid(const struct seat *seat, const char *cursor) { diff --git a/render.c b/render.c index 5a924743..020dee79 100644 --- a/render.c +++ b/render.c @@ -1862,7 +1862,7 @@ render_overlay_single_pixel(struct terminal *term, enum overlay_style style, } } -static void +void render_overlay(struct terminal *term) { struct wayl_sub_surface *overlay = &term->window->overlay; diff --git a/render.h b/render.h index 1898351c..81d2a905 100644 --- a/render.h +++ b/render.h @@ -31,6 +31,8 @@ bool render_xcursor_set( struct seat *seat, struct terminal *term, enum cursor_shape shape); bool render_xcursor_is_valid(const struct seat *seat, const char *cursor); +void render_overlay(struct terminal *term); + struct render_worker_context { int my_id; struct terminal *term; From c2add346ad5c1673ba0334264300e86eb50b2293 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Sat, 4 Jan 2025 21:59:19 -0500 Subject: [PATCH 013/353] terminal: Refresh only overlay when flash expires If we call render_refresh, that will wait for a callback to the main surface. In the case of a flash, the main surface might not get callbacks if the compositor implements fancy culling optimizations like wlroots wlr_scene compositors such as sway version >=1.10. --- terminal.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/terminal.c b/terminal.c index 5a74631a..7b5a4d2d 100644 --- a/terminal.c +++ b/terminal.c @@ -419,7 +419,12 @@ fdm_flash(struct fdm *fdm, int fd, int events, void *data) (unsigned long long)expiration_count); term->flash.active = false; - render_refresh(term); + render_overlay(term); + + // since the overlay surface is synced with the main window surface, we have + // to commit the main surface for the compositor to acknowledge the new + // overlay state. + wl_surface_commit(term->window->surface.surf); return true; } From 301101e7d9d9675b48b874128a36ecf3005b727e Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Sat, 4 Jan 2025 21:36:33 -0500 Subject: [PATCH 014/353] Revert "config: don't allow colors.flash-alpha to be 1.0" This reverts commit 56d2c3e990509d18de31e6009e0d0c6254f99b67. --- CHANGELOG.md | 3 --- config.c | 4 ++-- render.c | 21 --------------------- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc1d3bc3..508174b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,9 +80,6 @@ * Runtime changes to the app-id (OSC-176) now limits the app-id string to 2048 characters ([#1897][1897]). -* `colors.flash-alpha` can no longer be set to 1.0 (i.e. fully - opaque). This fixes an issue where the window would be stuck in the - flash state. [1897]: https://codeberg.org/dnkl/foot/issues/1897 diff --git a/config.c b/config.c index 3b8ce7e8..7f1ce055 100644 --- a/config.c +++ b/config.c @@ -1445,8 +1445,8 @@ parse_section_colors(struct context *ctx) if (!value_to_float(ctx, &alpha)) return false; - if (alpha < 0. || alpha >= 1.) { - LOG_CONTEXTUAL_ERR("not in range 0.0-0.999"); + if (alpha < 0. || alpha > 1.) { + LOG_CONTEXTUAL_ERR("not in range 0.0-1.0"); return false; } diff --git a/render.c b/render.c index 020dee79..b1791a90 100644 --- a/render.c +++ b/render.c @@ -1898,27 +1898,6 @@ render_overlay(struct terminal *term) break; case OVERLAY_FLASH: - /* - * A compositor will not send a frame callback for our main - * window if it is fully occluded (for example, by a fully - * opaque overlay...). This causes the overlay to stuck. - * - * For regular buffers, it _should_ be enough to *not* hint - * the compositor it's opaque. But at least some compositor - * special cases single-pixel buffers, and actually look at - * their pixel value. - * - * Thus, we have two options: implement frame callback - * handling for the overlay sub-surface, or ensure we don't - * use a fully opaque surface. Since no overlays are fully - * opaque by default, and the flash surface is the only one - * that can be configured to be opaque (colors.flash-alpha), - * and since adding frame callback handling adds a lot of - * boilerplate code... let's go with the simpler solution of - * not allowing colors.flash-alpha to be 1.0. - */ - xassert(term->conf->colors.flash_alpha != 0xffff); - color = color_hex_to_pixman_with_alpha( term->conf->colors.flash, term->conf->colors.flash_alpha); From e136abf1ef394aaa2bc86f9fc4b96d61c0bffbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 9 Jan 2025 07:56:10 +0100 Subject: [PATCH 015/353] changelog: colors.flash-alpha=1.0 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 508174b2..87694630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,12 @@ ### Deprecated ### Removed ### Fixed + +* 'flash' overlay (triggered by either `tput flash`, or enabling + `bell.visual` and then sending `BEL` to the terminal) stuck when + `colors.flash-alpha=1.0`. + + ### Security ### Contributors From 2c309227f1c20c23d75055935b4d705c275c3c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 9 Jan 2025 07:49:29 +0100 Subject: [PATCH 016/353] term: cursor_refresh(): don't try to dirty the grid if we don't have one If the compositor sends a keyboard enter event before our window has been mapped, foot crashes; the enter event triggers a cursor refresh (hollow -> non-hollow block cursor), which crashes since we haven't yet allocated a grid. Fix by no-op:ing the refresh if the window hasn't been configured yet. Closes #1910 --- CHANGELOG.md | 4 ++++ terminal.c | 3 +++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87694630..be321556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,10 @@ * 'flash' overlay (triggered by either `tput flash`, or enabling `bell.visual` and then sending `BEL` to the terminal) stuck when `colors.flash-alpha=1.0`. +* Crash when compositor sends a keyboard enter event before the foot + window has been mapped ([#1910][1910]). + +[1910]: https://codeberg.org/dnkl/foot/issues/1910 ### Security diff --git a/terminal.c b/terminal.c index 7b5a4d2d..e392c36d 100644 --- a/terminal.c +++ b/terminal.c @@ -515,6 +515,9 @@ term_arm_blink_timer(struct terminal *term) static void cursor_refresh(struct terminal *term) { + if (!term->window->is_configured) + return; + term->grid->cur_row->cells[term->grid->cursor.point.col].attrs.clean = 0; term->grid->cur_row->dirty = true; render_refresh(term); From feb4dd102b606b3b5ccd2d8955d9ac370d9ae9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 10 Jan 2025 13:05:35 +0100 Subject: [PATCH 017/353] forgejo: bugs: add required field 'distro' --- .forgejo/issue_template/issue_template.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index cca40dd5..fa602200 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -34,6 +34,14 @@ body: placeholder: "sway version 1.9" validations: required: true + - type: input + id: distro + attributes: + label: Distribution + description: "The name of the Linux distribution, or BSD flavor, you are running" + placeholder: "Arch Linux" + validations: + required: true - type: textarea id: repro attributes: From b5835cbd589aed06469d4761ad3c8f9214a2bb86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 10 Jan 2025 13:13:05 +0100 Subject: [PATCH 018/353] forgejo: bugs: add required field 'config' Require all bug submitters to include their foot configs. --- .forgejo/issue_template/issue_template.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index fa602200..e650d8d0 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -42,6 +42,13 @@ body: placeholder: "Arch Linux" validations: required: true + - type: textarea + id: config + attributes: + label: Foot config + description: paste your entire `foot.ini` here + validations: + required: true - type: textarea id: repro attributes: From 3b96de2aa4c86a968b9239f7351d0eb7d8e60ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 10 Jan 2025 13:14:02 +0100 Subject: [PATCH 019/353] forgejo: bugs: config: uppercase description's first letter --- .forgejo/issue_template/issue_template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index e650d8d0..ca12c4a3 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -46,7 +46,7 @@ body: id: config attributes: label: Foot config - description: paste your entire `foot.ini` here + description: Paste your entire `foot.ini` here validations: required: true - type: textarea From 7e7fd0468d860274c46030dcd43b2eadfb189f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 10 Jan 2025 13:15:02 +0100 Subject: [PATCH 020/353] forgejo: bugs: short explanation of what an IME is --- .forgejo/issue_template/issue_template.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index ca12c4a3..eae2e492 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -74,8 +74,8 @@ body: other terminal multiplexer? Does the bug happen in a plain foot instance? - **IME** do you use an IME? Which one? Does the bug happen if - you disable the IME? + **IME** do you use an IME (e.g. fcitx5, ibus etc)? Which one? + Does the bug happen if you disable the IME? Obtaining logs and stacktraces ------------------------------ From 2a07a2e6b9a9565287996c51ceb2824b37d3fba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Jan 2025 10:10:10 +0100 Subject: [PATCH 021/353] Add support for the new Wayland protocol xdg-system-bell From the release notes: system bell - allowing e.g. terminal emulators to hand off system bell alerts to the compositor for among other things accessibility purposes The new protocol is used when the new config option bell.system=yes (and the compositor implements the protocol, obviously). The system bell is rung independent of whether the foot window has keyboard focus or not (thus relying on compositor configuration to determine whether anything should be done or not in response to the bell). The new option is enabled by default. --- CHANGELOG.md | 7 +++++++ config.c | 3 +++ config.h | 1 + doc/foot.ini.5.scd | 29 ++++++++++++++++++----------- foot.ini | 1 + meson.build | 10 ++++++++++ pgo/pgo.c | 1 + terminal.c | 3 +++ tests/test-config.c | 1 + wayland.c | 37 +++++++++++++++++++++++++++++++++++++ wayland.h | 9 +++++++++ 11 files changed, 91 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be321556..3c01193a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,14 @@ ## Unreleased + ### Added + +* Support for the new Wayland protocol `xdg-system-bell-v1` protocol + (added in wayland-protocols 1.38), via the new config option + `bell.system=no|yes` (defaults to `yes`). + + ### Changed * The `CSI 21 t` (report window title) and `OSC 176 ?` (report app-id) diff --git a/config.c b/config.c index 7f1ce055..68d49207 100644 --- a/config.c +++ b/config.c @@ -1139,6 +1139,8 @@ parse_section_bell(struct context *ctx) return value_to_bool(ctx, &conf->bell.urgent); else if (streq(key, "notify")) return value_to_bool(ctx, &conf->bell.notify); + else if (streq(key, "system")) + return value_to_bool(ctx, &conf->bell.system_bell); else if (streq(key, "visual")) return value_to_bool(ctx, &conf->bell.flash); else if (streq(key, "command")) @@ -3182,6 +3184,7 @@ config_load(struct config *conf, const char *conf_path, .urgent = false, .notify = false, .flash = false, + .system_bell = true, .command = { .argv = {.args = NULL}, }, diff --git a/config.h b/config.h index d7192970..7d9f88c3 100644 --- a/config.h +++ b/config.h @@ -186,6 +186,7 @@ struct config { bool urgent; bool notify; bool flash; + bool system_bell; struct config_spawn_template command; bool command_focused; } bell; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index ba59050e..733168bf 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -445,10 +445,17 @@ Note: do not set *TERM* here; use the *term* option in the main # SECTION: bell +*system* + Boolean, when set to _yes_, ring the system bell. The bell is rung + independent of whether the foot window has keyboard focus or + not. Exact behavior is compositor dependent. + + Default: _yes_ + *urgent* - When set to _yes_, foot will signal urgency to the compositor - through the XDG activation protocol whenever *BEL* is received, - and the window does NOT have keyboard focus. + Boolean, when set to _yes_, foot will signal urgency to the + compositor through the XDG activation protocol whenever *BEL* is + received, and the window does NOT have keyboard focus. If the compositor does not implement this protocol, the margins will be painted in red instead. @@ -459,25 +466,25 @@ Note: do not set *TERM* here; use the *term* option in the main Default: _no_ *notify* - When set to _yes_, foot will emit a desktop notification using the - command specified in the *notify* option whenever *BEL* is - received. By default, bell notifications are shown only when the - window does *not* have keyboard focus. See + Boolean, when set to _yes_, foot will emit a desktop notification + using the command specified in the *notify* option whenever *BEL* + is received. By default, bell notifications are shown only when + the window does *not* have keyboard focus. See _desktop-notifications.inhibit-when-focused_. Default: _no_ *visual* - When set to _yes_, foot will flash the terminal window. Default: - _no_ + Boolean, when set to _yes_, foot will flash the terminal + window. Default: _no_ *command* When set, foot will execute this command when *BEL* is received. Default: none *command-focused* - Whether to run the command on *BEL* even while focused. Default: - _no_ + Boolean, whether to run the command on *BEL* even while + focused. Default: _no_ # SECTION: desktop-notifications diff --git a/foot.ini b/foot.ini index bd4ac082..580178af 100644 --- a/foot.ini +++ b/foot.ini @@ -45,6 +45,7 @@ # osc52=enabled # disabled|copy-enabled|paste-enabled|enabled [bell] +# system=yes # urgent=no # notify=no # visual=no diff --git a/meson.build b/meson.build index ee5af2f3..f39dbc6c 100644 --- a/meson.build +++ b/meson.build @@ -179,6 +179,15 @@ else xdg_toplevel_icon = false endif +if wayland_protocols.version().version_compare('>=1.38') + add_project_arguments('-DHAVE_XDG_SYSTEM_BELL', language: 'c') + wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml'] + xdg_system_bell = true +else + xdg_system_bell = false +endif + + foreach prot : wl_proto_xml wl_proto_headers += custom_target( prot.underscorify() + '-client-header', @@ -414,6 +423,7 @@ summary( 'IME': get_option('ime'), 'Grapheme clustering': utf8proc.found(), 'Wayland: xdg-toplevel-icon-v1': xdg_toplevel_icon, + 'Wayland: xdg-system-bell-v1': xdg_system_bell, 'utmp backend': utmp_backend, 'utmp helper default path': utmp_default_helper_path, 'Build terminfo': tic.found(), diff --git a/pgo/pgo.c b/pgo/pgo.c index 24154277..88e862b8 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -101,6 +101,7 @@ wayl_win_init(struct terminal *term, const char *token) void wayl_win_destroy(struct wl_window *win) {} void wayl_win_alpha_changed(struct wl_window *win) {} bool wayl_win_set_urgent(struct wl_window *win) { return true; } +bool wayl_win_ring_bell(const struct wl_window *win) { return true; } bool wayl_fractional_scaling(const struct wayland *wayl) { return true; } pid_t diff --git a/terminal.c b/terminal.c index e392c36d..6936ff29 100644 --- a/terminal.c +++ b/terminal.c @@ -3683,6 +3683,9 @@ term_bell(struct terminal *term) } } + if (term->conf->bell.system_bell) + wayl_win_ring_bell(term->window); + if (term->conf->bell.notify) { notify_notify(term, &(struct notification){ .title = xstrdup("Bell"), diff --git a/tests/test-config.c b/tests/test-config.c index a189d440..303ddd6f 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -579,6 +579,7 @@ test_section_bell(void) test_boolean(&ctx, &parse_section_bell, "urgent", &conf.bell.urgent); test_boolean(&ctx, &parse_section_bell, "notify", &conf.bell.notify); + test_boolean(&ctx, &parse_section_bell, "system", &conf.bell.system_bell); test_boolean(&ctx, &parse_section_bell, "command-focused", &conf.bell.command_focused); test_spawn_template(&ctx, &parse_section_bell, "command", diff --git a/wayland.c b/wayland.c index 9c184adc..3a46133f 100644 --- a/wayland.c +++ b/wayland.c @@ -1374,6 +1374,17 @@ handle_global(void *data, struct wl_registry *registry, } #endif +#if defined(HAVE_XDG_SYSTEM_BELL) + else if (streq(interface, xdg_system_bell_v1_interface.name)) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->system_bell = wl_registry_bind( + wayl->registry, name, &xdg_system_bell_v1_interface, required); + } +#endif + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (streq(interface, zwp_text_input_manager_v3_interface.name)) { const uint32_t required = 1; @@ -1696,6 +1707,10 @@ wayl_destroy(struct wayland *wayl) zwp_text_input_manager_v3_destroy(wayl->text_input_manager); #endif +#if defined(HAVE_XDG_SYSTEM_BELL) + if (wayl->system_bell != NULL) + xdg_system_bell_v1_destroy(wayl->system_bell); +#endif #if defined(HAVE_XDG_TOPLEVEL_ICON) if (wayl->toplevel_icon_manager != NULL) xdg_toplevel_icon_manager_v1_destroy(wayl->toplevel_icon_manager); @@ -2247,6 +2262,28 @@ wayl_win_set_urgent(struct wl_window *win) return false; } +bool +wayl_win_ring_bell(const struct wl_window *win) +{ +#if defined(HAVE_XDG_SYSTEM_BELL) + if (win->term->wl->system_bell == NULL) { + static bool have_warned = false; + + if (!have_warned) { + LOG_WARN("compositor does not implement the XDG system bell protocol"); + have_warned = true; + } + + return false; + } + + xdg_system_bell_v1_ring(win->term->wl->system_bell, win->surface.surf); + return true; +#else + return false; +#endif +} + bool wayl_win_csd_titlebar_visible(const struct wl_window *win) { diff --git a/wayland.h b/wayland.h index 227e2a68..b3ef5a2b 100644 --- a/wayland.h +++ b/wayland.h @@ -24,6 +24,10 @@ #include #endif +#if defined(HAVE_XDG_SYSTEM_BELL) + #include +#endif + #include #include @@ -451,6 +455,10 @@ struct wayland { struct xdg_toplevel_icon_manager_v1 *toplevel_icon_manager; #endif +#if defined(HAVE_XDG_SYSTEM_BELL) + struct xdg_system_bell_v1 *system_bell; +#endif + bool presentation_timings; struct wp_presentation *presentation; uint32_t presentation_clock_id; @@ -492,6 +500,7 @@ void wayl_win_destroy(struct wl_window *win); void wayl_win_scale(struct wl_window *win, const struct buffer *buf); void wayl_win_alpha_changed(struct wl_window *win); bool wayl_win_set_urgent(struct wl_window *win); +bool wayl_win_ring_bell(const struct wl_window *win); bool wayl_win_csd_titlebar_visible(const struct wl_window *win); bool wayl_win_csd_borders_visible(const struct wl_window *win); From aeb28e33fa774e9d00ffbc1054c6152ebe54babd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 17 Jan 2025 11:22:23 +0100 Subject: [PATCH 022/353] features: add +/-system-bell to version output --- client.c | 6 ++++-- foot-features.h | 9 +++++++++ main.c | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/client.c b/client.c index 8576531f..757f4d41 100644 --- a/client.c +++ b/client.c @@ -67,12 +67,14 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %cassertions", + "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', - feature_assertions() ? '+' : '-'); + feature_xdg_toplevel_icon() ? '+' : '-', + feature_xdg_system_bell() ? '+' : '-', + feature_assertions() ? '+' : '-'); return buf; } diff --git a/foot-features.h b/foot-features.h index 674c1056..0eef5eac 100644 --- a/foot-features.h +++ b/foot-features.h @@ -46,3 +46,12 @@ static inline bool feature_xdg_toplevel_icon(void) return false; #endif } + +static inline bool feature_xdg_system_bell(void) +{ +#if defined(HAVE_XDG_SYSTEM_BELL) + return true; +#else + return false; +#endif +} diff --git a/main.c b/main.c index 973cbae4..c5c11080 100644 --- a/main.c +++ b/main.c @@ -51,12 +51,13 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %cassertions", + "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', feature_xdg_toplevel_icon() ? '+' : '-', + feature_xdg_system_bell() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } From 22e1b1610f622ca9e2a7e64593025a8d44bc7b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 18 Jan 2025 10:22:24 +0100 Subject: [PATCH 023/353] vt: combining chars: ensure 'key' is within range When there's a key collision, we increment the key and check again. When doing this, we need to ensure the key is withing range, and wrap around to 0 if the key value is too large. --- vt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vt.c b/vt.c index 95cfdd2e..2b5eb27d 100644 --- a/vt.c +++ b/vt.c @@ -845,6 +845,7 @@ action_utf8_print(struct terminal *term, char32_t wc) cc->chars[0], base, cc->count, wanted_count, cc->chars[wanted_count - 1], wc); #endif key++; + key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO; collision_count++; continue; } @@ -856,6 +857,7 @@ action_utf8_print(struct terminal *term, char32_t wc) if (!match) { key++; + key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO; collision_count++; continue; } From 2ff38e86a7fabd8fd90caa31c7eb0aea76fc981b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 20 Jan 2025 09:08:47 +0100 Subject: [PATCH 024/353] input: kitty: fix alternate codepoint sometimes not being reported When alternate key reporting is enabled (i.e. when we're supposed to report the shifted key along with the unshifted key), we try to figure out whether the key really is shifted or not (and thus which xkb keysym to use for the unshifted and shifted keys in the escape). This was done by getting the layout's *all* modifier combinations that produce the shifted keysym, and if any of of them contained a modifier that isn't supported by the kitty protocol, the shifted and unshifted keys are derived from the same keysym. This is to ensure we handle things like AltGr-combos correctly. The issue is, since there may be more than one modifier combination generating the shifted keysym, we may end up using the wrong keysym just because _another_ combination set contains modifiers not supported by the kitty protocol. What we're interrested in is whether the *pressed* set of modifiers contains such modifiers. Closes #1918 --- CHANGELOG.md | 7 +++++++ input.c | 25 ++----------------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cb18305..d4212c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,13 @@ ### Deprecated ### Removed ### Fixed + +* Kitty keyboard protocol: alternate key reporting failing to report + the alternate codepoint in some corner cases ([#1918][1918]). + +[1918]: https://codeberg.org/dnkl/foot/issues/1918 + + ### Security ### Contributors diff --git a/input.c b/input.c index 51425a36..7b4cb667 100644 --- a/input.c +++ b/input.c @@ -1315,29 +1315,8 @@ emit_escapes: * (yes, this matches what kitty does, as of 0.23.1) */ - /* Get the key's shift level */ - xkb_level_index_t lvl = xkb_state_key_get_level( - seat->kbd.xkb_state, ctx->key, ctx->layout); - - /* And get all modifier combinations that, combined with - * the pressed key, results in the current shift level */ - xkb_mod_mask_t masks[32]; - size_t mask_count = xkb_keymap_key_get_mods_for_level( - seat->kbd.xkb_keymap, ctx->key, ctx->layout, lvl, - masks, ALEN(masks)); - - /* Check modifier combinations - if a combination has - * modifiers not in our set of 'significant' modifiers, - * use key sym as-is */ - bool use_level0_sym = true; - for (size_t i = 0; i < mask_count; i++) { - if ((masks[i] & ~seat->kbd.kitty_significant) > 0) { - use_level0_sym = false; - break; - } - } - - xkb_keysym_t sym_to_use = use_level0_sym && ctx->level0_syms.count > 0 + const bool use_level0_sym = (ctx->mods & ~seat->kbd.kitty_significant) == 0; + const xkb_keysym_t sym_to_use = use_level0_sym && ctx->level0_syms.count > 0 ? ctx->level0_syms.syms[0] : sym; From 786037791c2e775c98287d794277b6ef0752aab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 20 Jan 2025 10:34:45 +0100 Subject: [PATCH 025/353] input: kitty: improve handling of alternate+base keys even more * Always do a base key lookup. Before this, we didn't do that if we matched the XKB sym to a lookup table entry (i.e. keypads, cursor keys etc.) * Try to retrieve the unshifted symbol also when we matched the symbol to a lookup table entry. When successful, we now report an alternate key for keypad and cursor keys; before this patch, we only did that for keys that didn't have an entry in the lookup table (i.e. ASCII chars etc). This improves compatibility with kitty (and the kitty keyboard protocol) in more corner cases. One particular example is the neo keyboard layout, where part of the regular keys act as keypad keys when num-lock is active. --- input.c | 74 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/input.c b/input.c index 7b4cb667..615eb00d 100644 --- a/input.c +++ b/input.c @@ -1279,75 +1279,83 @@ emit_escapes: int key = -1, alternate = -1, base = -1; char final; + const bool use_level0_sym = + (ctx->mods & ~seat->kbd.kitty_significant) == 0 && ctx->level0_syms.count > 0; + if (info != NULL) { if (!info->is_modifier || report_all_as_escapes) { key = info->key; final = info->final; + + if (use_level0_sym) { + xkb_keysym_t unshifted = xkb_keysym_to_utf32(ctx->level0_syms.syms[0]); + if (unshifted > 0) { + alternate = key; + key = unshifted; + } + } } } else { /* * Use keysym (typically its Unicode codepoint value). * * If the keysym is shifted, use its unshifted codepoint - * instead. In other words, ctrl+a and ctrl+shift+a should - * both use the same value for 'key' (97 - i.a. 'a'). + * instead. In other words, ctrl+a and ctrl+shift+a should both + * use the same value for 'key' (97 - i.a. 'a'). * - * However, don't do this if a non-significant modifier was - * used to generate the symbol. This is needed since we cannot - * encode non-significant modifiers, and thus the "extra" - * modifier(s) would get lost. + * However, don't do this if a non-significant modifier was used + * to generate the symbol. This is needed since we cannot encode + * non-significant modifiers, and thus the "extra" modifier(s) + * would get lost. * * Example: * - * the Swedish layout has '2', QUOTATION MARK ("double - * quote"), '@', and '²' on the same key. '2' is the base - * symbol. + * the Swedish layout has '2', QUOTATION MARK ("double quote"), + * '@', and '²' on the same key. '2' is the base symbol. * * Shift+2 results in QUOTATION MARK * AltGr+2 results in '@' * AltGr+Shift+2 results in '²' * - * The kitty kbd protocol can't encode AltGr. So, if we - * always used the base symbol ('2'), Alt+Shift+2 would - * result in the same escape sequence as - * AltGr+Alt+Shift+2. + * The kitty kbd protocol can't encode AltGr. So, if we always + * used the base symbol ('2'), Alt+Shift+2 would result in the + * same escape sequence as AltGr+Alt+Shift+2. * * (yes, this matches what kitty does, as of 0.23.1) */ - const bool use_level0_sym = (ctx->mods & ~seat->kbd.kitty_significant) == 0; - const xkb_keysym_t sym_to_use = use_level0_sym && ctx->level0_syms.count > 0 - ? ctx->level0_syms.syms[0] - : sym; - if (composed) key = utf32[0]; /* TODO: what if there are multiple codepoints? */ else { - key = xkb_keysym_to_utf32(sym_to_use); + key = xkb_keysym_to_utf32( + use_level0_sym ? ctx->level0_syms.syms[0] : sym); + if (key == 0) return false; - - /* The *shifted* key. May be the same as the unshifted - * key - if so, this is filtered out below, when - * emitting the CSI */ - alternate = xkb_keysym_to_utf32(sym); } - /* Base layout key. I.e the symbol the pressed key produces in - * the base/default layout (layout idx 0) */ - const xkb_keysym_t *base_syms; - int base_sym_count = xkb_keymap_key_get_syms_by_level( - seat->kbd.xkb_keymap, ctx->key, 0, 0, &base_syms); - - if (base_sym_count > 0) - base = xkb_keysym_to_utf32(base_syms[0]); - + /* + * The *shifted* key. May be the same as the unshifted key - + * if so, this is filtered out below, when emitting the CSI. + * + * Note that normally, only the *unshifted* key is emitted - see below + */ + alternate = xkb_keysym_to_utf32(sym); final = 'u'; } if (key < 0) return false; + /* Base layout key. I.e the symbol the pressed key produces in + * the base/default layout (layout idx 0) */ + const xkb_keysym_t *base_syms; + int base_sym_count = xkb_keymap_key_get_syms_by_level( + seat->kbd.xkb_keymap, ctx->key, 0, 0, &base_syms); + + if (base_sym_count > 0) + base = xkb_keysym_to_utf32(base_syms[0]); + xassert(encoded_mods >= 1); char event[4]; From 09f718878f8afa54cb38d046db0af12e1ff679a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 21 Jan 2025 08:37:30 +0100 Subject: [PATCH 026/353] input: kitty: add initial unit test Test a couple of key combos from the se and de(neo) layouts. This unit test isn't intended to test _all_ key combinations, for all kitty flag combinations, but to be more of a regression test - whenever we discover a buggy, weird combo, add it here. --- input.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/input.c b/input.c index 615eb00d..4f136181 100644 --- a/input.c +++ b/input.c @@ -1752,6 +1752,235 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, term_xcursor_update_for_seat(seat->kbd_focus, seat); } +UNITTEST +{ + int chan[2]; + pipe2(chan, O_CLOEXEC); + + xassert(chan[0] >= 0); + xassert(chan[1] >= 0); + + struct config conf = {0}; + struct grid grid = {0}; + + struct terminal term = { + .conf = &conf, + .grid = &grid, + .ptmx = chan[1], + .selection = { + .coords = { + .start = {-1, -1}, + .end = {-1, -1}, + }, + .auto_scroll = { + .fd = -1, + }, + }, + }; + + struct key_binding_manager *key_binding_manager = key_binding_manager_new(); + + struct wayland wayl = { + .key_binding_manager = key_binding_manager, + .terms = tll_init(), + }; + + struct seat seat = { + .wayl = &wayl, + .name = "unittest", + }; + + tll_push_back(wayl.terms, &term); + term.wl = &wayl; + + seat.kbd.xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + xassert(seat.kbd.xkb != NULL); + + grid.kitty_kbd.flags[0] = KITTY_KBD_DISAMBIGUATE | KITTY_KBD_REPORT_ALTERNATE; + + /* Swedish keymap */ + { + seat.kbd.xkb_keymap = xkb_keymap_new_from_names( + seat.kbd.xkb, &(struct xkb_rule_names){.layout = "se"}, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (seat.kbd.xkb_keymap == NULL) { + /* Skip test */ + goto no_keymap; + } + + seat.kbd.xkb_state = xkb_state_new(seat.kbd.xkb_keymap); + xassert(seat.kbd.xkb_state != NULL); + + seat.kbd.mod_shift = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_SHIFT); + seat.kbd.mod_alt = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_ALT) ; + seat.kbd.mod_ctrl = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CTRL); + seat.kbd.mod_super = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_LOGO); + seat.kbd.mod_caps = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CAPS); + seat.kbd.mod_num = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_NUM); + + /* Significant modifiers in the legacy keyboard protocol */ + seat.kbd.legacy_significant = 0; + if (seat.kbd.mod_shift != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_shift; + if (seat.kbd.mod_alt != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_alt; + if (seat.kbd.mod_ctrl != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_ctrl; + if (seat.kbd.mod_super != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_super; + + /* Significant modifiers in the kitty keyboard protocol */ + seat.kbd.kitty_significant = seat.kbd.legacy_significant; + if (seat.kbd.mod_caps != XKB_MOD_INVALID) + seat.kbd.kitty_significant |= 1 << seat.kbd.mod_caps; + if (seat.kbd.mod_num != XKB_MOD_INVALID) + seat.kbd.kitty_significant |= 1 << seat.kbd.mod_num; + + key_binding_new_for_seat(key_binding_manager, &seat); + key_binding_load_keymap(key_binding_manager, &seat); + + { + xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_ctrl; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0); + key_press_release(&seat, &term, 1337, KEY_A + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + /* key: 97 = 'a', alternate: 65 = 'A', base: N/A, mods: 6 = ctrl+shift */ + const char expected_ctrl_shift_a[] = "\033[97:65;6u"; + xassert(count == strlen(expected_ctrl_shift_a)); + xassert(streq(escape, expected_ctrl_shift_a)); + + key_press_release(&seat, &term, 1337, KEY_A + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + } + + { + xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_alt; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0); + key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + /* key;. 50 = '2', alternate: 34 = '"', base: N/A, 4 = alt+shift */ + const char expected_alt_shift_2[] = "\033[50:34;4u"; + xassert(count == strlen(expected_alt_shift_2)); + xassert(streq(escape, expected_alt_shift_2)); + + key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + } + + { + xkb_mod_index_t alt_gr = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, "Mod5"); + xassert(alt_gr != XKB_MOD_INVALID); + + xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_alt | 1u << alt_gr; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0); + key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + /* key; 178 = '²', alternate: N/A, base: 50 = '2', 4 = alt+shift (AltGr not part of the protocol) */ + const char expected_altgr_alt_shift_2[] = "\033[178::50;4u"; + xassert(count == strlen(expected_altgr_alt_shift_2)); + xassert(streq(escape, expected_altgr_alt_shift_2)); + + key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + } + + key_binding_unload_keymap(key_binding_manager, &seat); + key_binding_remove_seat(key_binding_manager, &seat); + + xkb_state_unref(seat.kbd.xkb_state); + xkb_keymap_unref(seat.kbd.xkb_keymap); + + seat.kbd.xkb_state = NULL; + seat.kbd.xkb_keymap = NULL; + } + + /* de(neo) keymap */ + { + seat.kbd.xkb_keymap = xkb_keymap_new_from_names( + seat.kbd.xkb, &(struct xkb_rule_names){.layout = "us,de(neo)"}, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + if (seat.kbd.xkb_keymap == NULL) { + /* Skip test */ + goto no_keymap; + } + + seat.kbd.xkb_state = xkb_state_new(seat.kbd.xkb_keymap); + xassert(seat.kbd.xkb_state != NULL); + + seat.kbd.mod_shift = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_SHIFT); + seat.kbd.mod_alt = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_ALT) ; + seat.kbd.mod_ctrl = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CTRL); + seat.kbd.mod_super = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_LOGO); + seat.kbd.mod_caps = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CAPS); + seat.kbd.mod_num = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_NUM); + + /* Significant modifiers in the legacy keyboard protocol */ + seat.kbd.legacy_significant = 0; + if (seat.kbd.mod_shift != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_shift; + if (seat.kbd.mod_alt != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_alt; + if (seat.kbd.mod_ctrl != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_ctrl; + if (seat.kbd.mod_super != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_super; + + /* Significant modifiers in the kitty keyboard protocol */ + seat.kbd.kitty_significant = seat.kbd.legacy_significant; + if (seat.kbd.mod_caps != XKB_MOD_INVALID) + seat.kbd.kitty_significant |= 1 << seat.kbd.mod_caps; + if (seat.kbd.mod_num != XKB_MOD_INVALID) + seat.kbd.kitty_significant |= 1 << seat.kbd.mod_num; + + key_binding_new_for_seat(key_binding_manager, &seat); + key_binding_load_keymap(key_binding_manager, &seat); + + { + /* + * In the de(neo) layout, the Y key generates 'k'. This + * means we should get a key+alternate that indiciates + * 'k', but a base key that is 'y'. + */ + xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_alt; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 1); + key_press_release(&seat, &term, 1337, KEY_Y + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + /* key: 107 = 'k', alternate: 75 = 'K', base: 121 = 'y', mods: 4 = alt+shift */ + const char expected_alt_shift_y[] = "\033[107:75:121;4u"; + xassert(count == strlen(expected_alt_shift_y)); + xassert(streq(escape, expected_alt_shift_y)); + + key_press_release(&seat, &term, 1337, KEY_Y + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + } + + key_binding_unload_keymap(key_binding_manager, &seat); + key_binding_remove_seat(key_binding_manager, &seat); + + xkb_state_unref(seat.kbd.xkb_state); + xkb_keymap_unref(seat.kbd.xkb_keymap); + + seat.kbd.xkb_state = NULL; + seat.kbd.xkb_keymap = NULL; + } + +no_keymap: + xkb_context_unref(seat.kbd.xkb); + key_binding_manager_destroy(key_binding_manager); + + tll_free(wayl.terms); + close(chan[0]); + close(chan[1]); +} + static void keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) From 5e65f3f07e41065a6fbd1af74e43acd553e1d5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 22 Jan 2025 07:50:49 +0100 Subject: [PATCH 027/353] shm: codespell: re-using -> reusing --- shm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shm.c b/shm.c index bbf6f933..9a745f6c 100644 --- a/shm.c +++ b/shm.c @@ -608,7 +608,7 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph } if (cached != NULL) { - LOG_DBG("re-using buffer %p from cache", (void *)cached); + LOG_DBG("reusing buffer %p from cache", (void *)cached); cached->busy = true; for (size_t i = 0; i < cached->public.pix_instances; i++) pixman_region32_clear(&cached->public.dirty[i]); From f62a5ed1ff139b70d6c12ee3033cc956c3d93723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 22 Jan 2025 07:51:00 +0100 Subject: [PATCH 028/353] input: codespell: indiciates -> indicates --- input.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index 4f136181..7e0f3e58 100644 --- a/input.c +++ b/input.c @@ -1944,8 +1944,8 @@ UNITTEST { /* * In the de(neo) layout, the Y key generates 'k'. This - * means we should get a key+alternate that indiciates - * 'k', but a base key that is 'y'. + * means we should get a key+alternate that indicates 'k', + * but a base key that is 'y'. */ xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift | 1u << seat.kbd.mod_alt; keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 1); From 6ca1a2c2dcdcbd3432a953e19f55829b6f07e7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 22 Jan 2025 10:26:44 +0100 Subject: [PATCH 029/353] input: kitty: only set 'alternate' if the "unshifted" code is printable Fixes a regression where alt+backspace was reported as ^[[8:127;3u instead of ^[[127;3u. --- input.c | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/input.c b/input.c index 7e0f3e58..79c5d8a0 100644 --- a/input.c +++ b/input.c @@ -1213,7 +1213,7 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, bool is_text = count > 0 && utf32 != NULL && (mods & ~locked & ~consumed) == 0; for (size_t i = 0; utf32[i] != U'\0'; i++) { - if (!iswprint(utf32[i])) { + if (!isc32print(utf32[i])) { is_text = false; break; } @@ -1282,18 +1282,12 @@ emit_escapes: const bool use_level0_sym = (ctx->mods & ~seat->kbd.kitty_significant) == 0 && ctx->level0_syms.count > 0; + int unshifted = use_level0_sym ? xkb_keysym_to_utf32(ctx->level0_syms.syms[0]) : 0; + if (info != NULL) { if (!info->is_modifier || report_all_as_escapes) { key = info->key; final = info->final; - - if (use_level0_sym) { - xkb_keysym_t unshifted = xkb_keysym_to_utf32(ctx->level0_syms.syms[0]); - if (unshifted > 0) { - alternate = key; - key = unshifted; - } - } } } else { /* @@ -1327,26 +1321,23 @@ emit_escapes: if (composed) key = utf32[0]; /* TODO: what if there are multiple codepoints? */ else { - key = xkb_keysym_to_utf32( - use_level0_sym ? ctx->level0_syms.syms[0] : sym); + key = xkb_keysym_to_utf32(sym); if (key == 0) return false; } - /* - * The *shifted* key. May be the same as the unshifted key - - * if so, this is filtered out below, when emitting the CSI. - * - * Note that normally, only the *unshifted* key is emitted - see below - */ - alternate = xkb_keysym_to_utf32(sym); final = 'u'; } if (key < 0) return false; + if (unshifted > 0 && isc32print(unshifted)) { + alternate = key; + key = unshifted; + } + /* Base layout key. I.e the symbol the pressed key produces in * the base/default layout (layout idx 0) */ const xkb_keysym_t *base_syms; @@ -1378,7 +1369,7 @@ emit_escapes: if (report_alternate) { bool emit_alternate = alternate > 0 && alternate != key; - bool emit_base = base > 0 && base != key && base != alternate; + bool emit_base = base > 0 && base != key && base != alternate && isc32print(base); if (emit_alternate) { bytes = snprintf(p, left, ":%u", alternate); @@ -1889,6 +1880,22 @@ UNITTEST key_press_release(&seat, &term, 1337, KEY_2 + 8, WL_KEYBOARD_KEY_STATE_RELEASED); } + { + xkb_mod_mask_t mods = 1u << seat.kbd.mod_alt; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0); + key_press_release(&seat, &term, 1337, KEY_BACKSPACE + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + /* key; 127 = , alternate: N/A, base: N/A, 3 = alt */ + const char expected_alt_backspace[] = "\033[127;3u"; + xassert(count == strlen(expected_alt_backspace)); + xassert(streq(escape, expected_alt_backspace)); + + key_press_release(&seat, &term, 1337, KEY_BACKSPACE + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + } + key_binding_unload_keymap(key_binding_manager, &seat); key_binding_remove_seat(key_binding_manager, &seat); From f301f6ecccc4a6be70da793c56814e50d13e93db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 22 Jan 2025 12:24:06 +0100 Subject: [PATCH 030/353] input: kitty: add more test cases --- input.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/input.c b/input.c index 79c5d8a0..fc1270c7 100644 --- a/input.c +++ b/input.c @@ -1896,6 +1896,52 @@ UNITTEST key_press_release(&seat, &term, 1337, KEY_BACKSPACE + 8, WL_KEYBOARD_KEY_STATE_RELEASED); } + { + xkb_mod_mask_t mods = 1u << seat.kbd.mod_ctrl; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0); + key_press_release(&seat, &term, 1337, KEY_ENTER + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + /* key; 13 = , alternate: N/A, base: N/A, 5 = ctrl */ + const char expected_ctrl_enter[] = "\033[13;5u"; + xassert(count == strlen(expected_ctrl_enter)); + xassert(streq(escape, expected_ctrl_enter)); + + key_press_release(&seat, &term, 1337, KEY_ENTER + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + } + + { + xkb_mod_mask_t mods = 1u << seat.kbd.mod_ctrl; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0); + key_press_release(&seat, &term, 1337, KEY_TAB + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + /* key; 9 = , alternate: N/A, base: N/A, 5 = ctrl */ + const char expected_ctrl_tab[] = "\033[9;5u"; + xassert(count == strlen(expected_ctrl_tab)); + xassert(streq(escape, expected_ctrl_tab)); + + key_press_release(&seat, &term, 1337, KEY_TAB + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + } + + { + xkb_mod_mask_t mods = 1u << seat.kbd.mod_ctrl | 1u << seat.kbd.mod_shift; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 0); + key_press_release(&seat, &term, 1337, KEY_LEFT + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + const char expected_ctrl_shift_left[] = "\033[1;6D"; + xassert(count == strlen(expected_ctrl_shift_left)); + xassert(streq(escape, expected_ctrl_shift_left)); + + key_press_release(&seat, &term, 1337, KEY_LEFT + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + } key_binding_unload_keymap(key_binding_manager, &seat); key_binding_remove_seat(key_binding_manager, &seat); From ba7ecc4669ab0bef850318478cf739016a2fc6a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 22 Jan 2025 12:37:36 +0100 Subject: [PATCH 031/353] input: kitty: refactor, try to simplify and be less confusing Use better named variables while juggling the shifted vs. unshifted key codes. Switch to variable names appropriate for the kitty keyboard protocol once we have all the unshifted, shifted and base key codes done. It's not until then we can decide which key code to use as the main key, and whether or not to report the alternate key code. --- input.c | 107 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/input.c b/input.c index fc1270c7..f5daf6b4 100644 --- a/input.c +++ b/input.c @@ -1276,68 +1276,48 @@ emit_escapes: encoded_mods |= mods & (1 << seat->kbd.mod_num) ? (1 << 7) : 0; encoded_mods++; - int key = -1, alternate = -1, base = -1; + /* + * Figure out the main, alternate and base key codes. + * + * The main key is the unshifted version of the generated symbol, + * the alternate key is the shifted version, and base is the + * (unshifted) key assuming the default layout. + * + * For example, the user presses shift+a, then: + * - unshifted = 'a' + * - shifted = 'A' + * - base = 'a' + * + * Base will in many cases be the same as the unshifted key, but + * may differ if the active keyboard layout is non-ASCII (examples + * would be russian, or alternative layouts like neo etc). + * + * The shifted key is what we get from XKB, i.e. the resulting key + * from all active modifiers, plus the pressed key. + */ + int unshifted = -1, shifted = -1, base = -1; char final; - const bool use_level0_sym = - (ctx->mods & ~seat->kbd.kitty_significant) == 0 && ctx->level0_syms.count > 0; - - int unshifted = use_level0_sym ? xkb_keysym_to_utf32(ctx->level0_syms.syms[0]) : 0; - if (info != NULL) { + /* Use code from lookup table (cursor keys, enter, tab etc)*/ if (!info->is_modifier || report_all_as_escapes) { - key = info->key; + shifted = info->key; final = info->final; } } else { - /* - * Use keysym (typically its Unicode codepoint value). - * - * If the keysym is shifted, use its unshifted codepoint - * instead. In other words, ctrl+a and ctrl+shift+a should both - * use the same value for 'key' (97 - i.a. 'a'). - * - * However, don't do this if a non-significant modifier was used - * to generate the symbol. This is needed since we cannot encode - * non-significant modifiers, and thus the "extra" modifier(s) - * would get lost. - * - * Example: - * - * the Swedish layout has '2', QUOTATION MARK ("double quote"), - * '@', and '²' on the same key. '2' is the base symbol. - * - * Shift+2 results in QUOTATION MARK - * AltGr+2 results in '@' - * AltGr+Shift+2 results in '²' - * - * The kitty kbd protocol can't encode AltGr. So, if we always - * used the base symbol ('2'), Alt+Shift+2 would result in the - * same escape sequence as AltGr+Alt+Shift+2. - * - * (yes, this matches what kitty does, as of 0.23.1) - */ + /* Use keysym (typically its Unicode codepoint value) */ if (composed) - key = utf32[0]; /* TODO: what if there are multiple codepoints? */ - else { - key = xkb_keysym_to_utf32(sym); - - if (key == 0) - return false; - } + shifted = utf32[0]; /* TODO: what if there are multiple codepoints? */ + else + shifted = xkb_keysym_to_utf32(sym); final = 'u'; } - if (key < 0) + if (shifted <= 0) return false; - if (unshifted > 0 && isc32print(unshifted)) { - alternate = key; - key = unshifted; - } - /* Base layout key. I.e the symbol the pressed key produces in * the base/default layout (layout idx 0) */ const xkb_keysym_t *base_syms; @@ -1347,6 +1327,36 @@ emit_escapes: if (base_sym_count > 0) base = xkb_keysym_to_utf32(base_syms[0]); + /* + * If the keysym is shifted, use its unshifted codepoint + * instead. In other words, ctrl+a and ctrl+shift+a should both + * use the same value for 'key' (97 - i.a. 'a'). + * + * However, don't do this if a non-significant modifier was used + * to generate the symbol. This is needed since we cannot encode + * non-significant modifiers, and thus the "extra" modifier(s) + * would get lost. + * + * Example: + * + * the Swedish layout has '2', QUOTATION MARK ("double quote"), + * '@', and '²' on the same key. '2' is the base symbol. + * + * Shift+2 results in QUOTATION MARK + * AltGr+2 results in '@' + * AltGr+Shift+2 results in '²' + * + * The kitty kbd protocol can't encode AltGr. So, if we always + * used the base symbol ('2'), Alt+Shift+2 would result in the + * same escape sequence as AltGr+Alt+Shift+2. + * + * (yes, this matches what kitty does, as of 0.23.1) + */ + const bool use_level0_sym = + (ctx->mods & ~seat->kbd.kitty_significant) == 0 && ctx->level0_syms.count > 0; + + unshifted = use_level0_sym ? xkb_keysym_to_utf32(ctx->level0_syms.syms[0]) : 0; + xassert(encoded_mods >= 1); char event[4]; @@ -1363,6 +1373,9 @@ emit_escapes: size_t left = sizeof(buf); size_t bytes; + const int key = unshifted > 0 && isc32print(unshifted) ? unshifted : shifted; + const int alternate = shifted; + if (final == 'u' || final == '~') { bytes = snprintf(p, left, "\x1b[%u", key); p += bytes; left -= bytes; From 736328ab6b1d2a8cc90745e6003f4031e1fef5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 24 Jan 2025 06:38:02 +0100 Subject: [PATCH 032/353] config: check for FcNameUnparse() failure --- config.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.c b/config.c index 68d49207..a0d32869 100644 --- a/config.c +++ b/config.c @@ -3729,6 +3729,11 @@ config_font_parse(const char *pattern, struct config_font *font) LOG_DBG("%s: pt-size=%.2f, px-size=%d", stripped_pattern, pt_size, px_size); + if (stripped_pattern == NULL) { + LOG_ERR("failed to convert font pattern to string"); + return false; + } + *font = (struct config_font){ .pattern = stripped_pattern, .pt_size = pt_size, From bfabc5450b3a01c7d701809b3cf6158220ea289e Mon Sep 17 00:00:00 2001 From: camel-cdr Date: Wed, 22 Jan 2025 19:38:11 +0000 Subject: [PATCH 033/353] fix infinite loop/oom when cwd longer then 1024 The code reads cwd into a buffer, which is expanded while errno is ERANGE, with the intent of growing the buffer until the path fits. While getcwd will set errno on error, it will not reset it once the path fits into the buffer. So to not get an infinite loop once errno is ERANGE, we need to make sure to reset errno, such that the loop behaves as expected. --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index c5c11080..0ba574c9 100644 --- a/main.c +++ b/main.c @@ -552,10 +552,10 @@ main(int argc, char *const *argv) char *_cwd = NULL; if (cwd == NULL) { - errno = 0; size_t buf_len = 1024; do { _cwd = xrealloc(_cwd, buf_len); + errno = 0; if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) { LOG_ERRNO("failed to get current working directory"); goto out; From 787e886ff0281e53432366cca8456c210d3a919c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 24 Jan 2025 06:51:13 +0100 Subject: [PATCH 034/353] client: port bfabc5450b3a01c7d701809b3cf6158220ea289e to footclient --- client.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.c b/client.c index 757f4d41..dabcc327 100644 --- a/client.c +++ b/client.c @@ -401,10 +401,10 @@ main(int argc, char *const *argv) const char *cwd = custom_cwd; if (cwd == NULL) { - errno = 0; size_t buf_len = 1024; do { _cwd = xrealloc(_cwd, buf_len); + errno = 0; if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) { LOG_ERRNO("failed to get current working directory"); goto err; From f39b75f2960abf370c9ed455f8f0ca8b87055027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 24 Jan 2025 06:52:52 +0100 Subject: [PATCH 035/353] changelog: cwd > 1024 chars --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4212c68..b45aa01f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,9 @@ * Kitty keyboard protocol: alternate key reporting failing to report the alternate codepoint in some corner cases ([#1918][1918]). +* `foot` and `footclient` hanging, or terminating with `SIGABRT`, when + starting inside a directory whose total length is more than 1024 + characters. [1918]: https://codeberg.org/dnkl/foot/issues/1918 From 97385b007f2bb9cd54e701e5745d6e8d607513c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 25 Jan 2025 08:46:21 +0100 Subject: [PATCH 036/353] grid: reflow: regression: remove (truncate) SPACER cells at the end of line When printing a double-width glyph at the end of the line, it will get pushed to the next line if there's only one cell left on the current line. That last cell on the current line is filled with a SPACER value. When reflowing the text, the SPACER cell should be "removed", so that the double-width glyph continues directly after the text on the previous line. 9567694bab7149afbb4e4fcc4c754272a8d25aba fixed an issue where reflowing e.g. neofetch output incorrectly removed spaces between the logo, and the system info. But also introduced a regression where SPACER values no longer are removed. This patch tries to fix it, by adding back empty cells, but NOT SPACER cells. --- CHANGELOG.md | 4 ++++ grid.c | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b45aa01f..a249ef2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,10 @@ * `foot` and `footclient` hanging, or terminating with `SIGABRT`, when starting inside a directory whose total length is more than 1024 characters. +* Regression: reflowing (resizing the window) a line that ends with a + double-width glyph that was pushed to the next line due to there + being only one cell left on current line, did not remove the virtual + space inserted at the end of the current line. [1918]: https://codeberg.org/dnkl/foot/issues/1918 diff --git a/grid.c b/grid.c index 2f65a1dd..3f5c617d 100644 --- a/grid.c +++ b/grid.c @@ -930,7 +930,8 @@ grid_resize_and_reflow( if (!old_row->linebreak && col_count > 0) { /* Don't truncate logical lines */ - col_count = old_cols; + while (col_count < old_cols && old_row->cells[col_count].wc == 0) + col_count++; } xassert(col_count >= 0 && col_count <= old_cols); From 846271e8d3056ef3ecd3d2f4b7df6151e17c3c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 26 Jan 2025 09:28:54 +0100 Subject: [PATCH 037/353] render: resize: configure with only one dimension being zero The protocol states: If the width or height arguments are zero, it means the client should decide its own window dimension. This may happen when the compositor needs to configure the state of the surface but doesn't have any information about any previous or expected dimension. The wording is a bit ambiguous; does it mean we should set *both* width and height to values we choose, even if only one dimension is zero in the configure event? Or does it mean that we should choose the value for the dimension that is zero in the configure event? Regardless, it's pretty clear that it does *not* mean we should *only* choose width and height if *both* dimensions are zero in the configure event. This is foot's behavior before this patch, meaning if only one of them is zero, foot assumed the compositor wanted us to set the width (or height) to zero... Change this, so that we now choose value for the "missing" dimension, but do use the compositor provided value for the other dimension. Closes #1925 Relevant issues: * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/155 * https://github.com/YaLTeR/niri/issues/1050 --- CHANGELOG.md | 8 ++++++++ render.c | 57 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a249ef2d..97becf79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,14 @@ ### Changed + +* Do not try to set a zero width, or height, if the compositor sends a + _configure_ event with only one dimension being zero + ([#1925][1925]). + +[1925]: https://codeberg.org/dnkl/foot/issues/1925 + + ### Deprecated ### Removed ### Fixed diff --git a/render.c b/render.c index b1791a90..0cca0643 100644 --- a/render.c +++ b/render.c @@ -4295,17 +4295,24 @@ send_dimensions_to_client(struct terminal *term) static void set_size_from_grid(struct terminal *term, int *width, int *height, int cols, int rows) { + int new_width, new_height; + /* Nominal grid dimensions */ - *width = cols * term->cell_width; - *height = rows * term->cell_height; + new_width = cols * term->cell_width; + new_height = rows * term->cell_height; /* Include any configured padding */ - *width += 2 * term->conf->pad_x * term->scale; - *height += 2 * term->conf->pad_y * term->scale; + new_width += 2 * term->conf->pad_x * term->scale; + new_height += 2 * term->conf->pad_y * term->scale; /* Round to multiples of scale */ - *width = round(term->scale * round(*width / term->scale)); - *height = round(term->scale * round(*height / term->scale)); + new_width = round(term->scale * round(new_width / term->scale)); + new_height = round(term->scale * round(new_height / term->scale)); + + if (width != NULL) + *width = new_width; + if (height != NULL) + *height = new_height; } /* Move to terminal.c? */ @@ -4336,34 +4343,54 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) set_size_from_grid(term, &width, &height, term->cols, term->rows); } - if (width == 0 && height == 0) { - /* The compositor is letting us choose the size */ - if (term->stashed_width != 0 && term->stashed_height != 0) { + if (width == 0) { + /* The compositor is letting us choose the width */ + if (term->stashed_width != 0) { /* If a default size is requested, prefer the "last used" size */ width = term->stashed_width; - height = term->stashed_height; } else { /* Otherwise, use a user-configured size */ switch (term->conf->size.type) { case CONF_SIZE_PX: width = term->conf->size.width; + + if (wayl_win_csd_borders_visible(term->window)) + width -= 2 * term->conf->csd.border_width_visible; + + width *= scale; + break; + + case CONF_SIZE_CELLS: + set_size_from_grid(term, &width, NULL, + term->conf->size.width, term->conf->size.height); + break; + } + } + } + + if (height == 0) { + /* The compositor is letting us choose the height */ + if (term->stashed_height != 0) { + /* If a default size is requested, prefer the "last used" size */ + height = term->stashed_height; + } else { + /* Otherwise, use a user-configured size */ + switch (term->conf->size.type) { + case CONF_SIZE_PX: height = term->conf->size.height; /* Take CSDs into account */ if (wayl_win_csd_titlebar_visible(term->window)) height -= term->conf->csd.title_height; - if (wayl_win_csd_borders_visible(term->window)) { + if (wayl_win_csd_borders_visible(term->window)) height -= 2 * term->conf->csd.border_width_visible; - width -= 2 * term->conf->csd.border_width_visible; - } - width *= scale; height *= scale; break; case CONF_SIZE_CELLS: - set_size_from_grid(term, &width, &height, + set_size_from_grid(term, NULL, &height, term->conf->size.width, term->conf->size.height); break; } From 43206e66016175407abd11c415c7135c293a0c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 06:34:20 +0100 Subject: [PATCH 038/353] config: fix memory leak on e.g. "not a valid XKB key name" errors --- config.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/config.c b/config.c index a0d32869..12336e3d 100644 --- a/config.c +++ b/config.c @@ -1928,7 +1928,8 @@ value_to_key_combos(struct context *ctx, int action, if (idx == 0) { LOG_CONTEXTUAL_ERR( "empty binding not allowed (set to 'none' to unmap)"); - goto err; + free(copy); + return false; } remove_from_key_bindings_list(bindings, action, aux); @@ -1946,10 +1947,8 @@ value_to_key_combos(struct context *ctx, int action, return true; err: - if (idx > 0) { - for (size_t i = 0; i < used_combos; i++) - free_key_binding(&new_combos[i]); - } + for (size_t i = 0; i < used_combos; i++) + free_key_binding(&new_combos[i]); free(copy); return false; } From fda9638edd5e87c52c6a62c781a8f0e6df9deb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 06:36:54 +0100 Subject: [PATCH 039/353] forgejo: add optional field for shell/TUI --- .forgejo/issue_template/issue_template.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index eae2e492..e4ad7944 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -42,6 +42,12 @@ body: placeholder: "Arch Linux" validations: required: true + - type: input + id: application + attributes: + label: Shell, TUI, application + description: "Application in which the problem occurs (list all known)" + placeholder: "e.g. bash, neovim" - type: textarea id: config attributes: From 7a5353d18adcca26ab70ccb97873751d90c51694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 06:38:14 +0100 Subject: [PATCH 040/353] forgejo: application -> application(s) --- .forgejo/issue_template/issue_template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index e4ad7944..98b9c919 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -46,7 +46,7 @@ body: id: application attributes: label: Shell, TUI, application - description: "Application in which the problem occurs (list all known)" + description: "Application(s) in which the problem occurs (list all known)" placeholder: "e.g. bash, neovim" - type: textarea id: config From 8d6f0d0583a1cb7c245ff54ec472a03ebcdb6949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 10:51:03 +0100 Subject: [PATCH 041/353] key-bindings: try all bindings in translated mode before matching untranslated, and then finally raw When trying to match key bindings, we do three types of matching: * Match the _translated_ symbol (e.g. Control+C) * Match the _untranslated_ symbol (e.g. Control+Shift+c) * Match raw keyboard codes This was done for *each* key binding. This meant we sometimes matched a keybinding in raw mode, even though there was a translated/untranslated binding that would match it too. All depending on the internal order of the key binding list. This patch changes it, so that we first try all bindings in translated mode, then all bindings in untranslated mode, and finally all bindings in raw mode. Closes #1929 --- CHANGELOG.md | 3 +++ input.c | 17 ++++++++++++++--- search.c | 22 ++++++++++++++++++---- url-mode.c | 19 +++++++++++++++++-- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97becf79..61559359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,8 +89,11 @@ double-width glyph that was pushed to the next line due to there being only one cell left on current line, did not remove the virtual space inserted at the end of the current line. +* Wrong key bindings executed when using alternative keyboard layouts + ([#1929][1929]). [1918]: https://codeberg.org/dnkl/foot/issues/1918 +[1929]: https://codeberg.org/dnkl/foot/issues/1929 ### Security diff --git a/input.c b/input.c index f5daf6b4..2db696c1 100644 --- a/input.c +++ b/input.c @@ -1582,21 +1582,25 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, * User configurable bindings */ if (pressed) { + /* Match translated symbol */ tll_foreach(bindings->key, it) { const struct key_binding *bind = &it->item; - /* Match translated symbol */ if (bind->k.sym == sym && bind->mods == (mods & ~consumed) && execute_binding(seat, term, bind, serial, 1)) { goto maybe_repeat; } + } + + /* Match untranslated symbols */ + tll_foreach(bindings->key, it) { + const struct key_binding *bind = &it->item; if (bind->mods != mods) continue; - /* Match untranslated symbols */ for (size_t i = 0; i < raw_count; i++) { if (bind->k.sym == raw_syms[i] && execute_binding(seat, term, bind, serial, 1)) @@ -1604,8 +1608,15 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, goto maybe_repeat; } } + } + + /* Match raw key code */ + tll_foreach(bindings->key, it) { + const struct key_binding *bind = &it->item; + + if (bind->mods != mods) + continue; - /* Match raw key code */ tll_foreach(bind->k.key_codes, code) { if (code->item == key && execute_binding(seat, term, bind, serial, 1)) diff --git a/search.c b/search.c index eaf8c34e..bcb354d6 100644 --- a/search.c +++ b/search.c @@ -1388,11 +1388,14 @@ search_input(struct seat *seat, struct terminal *term, bool update_search_result = false; bool redraw = false; - /* Key bindings */ + /* + * Key bindings + */ + + /* Match translated symbol */ tll_foreach(bindings->search, it) { const struct key_binding *bind = &it->item; - /* Match translated symbol */ if (bind->k.sym == sym && bind->mods == (mods & ~consumed)) { @@ -1404,11 +1407,15 @@ search_input(struct seat *seat, struct terminal *term, } return; } + } + + /* Match untranslated symbols */ + tll_foreach(bindings->search, it) { + const struct key_binding *bind = &it->item; if (bind->mods != mods) continue; - /* Match untranslated symbols */ for (size_t i = 0; i < raw_count; i++) { if (bind->k.sym == raw_syms[i]) { if (execute_binding(seat, term, bind, serial, @@ -1420,8 +1427,15 @@ search_input(struct seat *seat, struct terminal *term, return; } } + } + + /* Match raw key code */ + tll_foreach(bindings->search, it) { + const struct key_binding *bind = &it->item; + + if (bind->mods != mods) + continue; - /* Match raw key code */ tll_foreach(bind->k.key_codes, code) { if (code->item == key) { if (execute_binding(seat, term, bind, serial, diff --git a/url-mode.c b/url-mode.c index 20c9820b..cca7bd22 100644 --- a/url-mode.c +++ b/url-mode.c @@ -178,11 +178,14 @@ urls_input(struct seat *seat, struct terminal *term, const xkb_keysym_t *raw_syms, size_t raw_count, uint32_t serial) { - /* Key bindings */ + /* + * Key bindings + */ + + /* Match translated symbol */ tll_foreach(bindings->url, it) { const struct key_binding *bind = &it->item; - /* Match translated symbol */ if (bind->k.sym == sym && bind->mods == (mods & ~consumed)) { @@ -190,6 +193,11 @@ urls_input(struct seat *seat, struct terminal *term, return; } + } + + /* Match untranslated symbols */ + tll_foreach(bindings->url, it) { + const struct key_binding *bind = &it->item; if (bind->mods != mods) continue; @@ -199,6 +207,13 @@ urls_input(struct seat *seat, struct terminal *term, return; } } + } + + /* Match raw key code */ + tll_foreach(bindings->url, it) { + const struct key_binding *bind = &it->item; + if (bind->mods != mods) + continue; /* Match raw key code */ tll_foreach(bind->k.key_codes, code) { From 1c7c9f6c16a55c47ea8f2740cd9d7619c4e0f9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 12:31:50 +0100 Subject: [PATCH 042/353] doc: foot.ini: describe key binding match logic --- doc/foot.ini.5.scd | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 733168bf..35f78674 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1096,7 +1096,36 @@ Note that *Alt* is usually called *Mod1*. *xkbcli interactive-wayland* can be useful for finding keysym names. -A key combination can only be mapped to *one* action. Lets say you +When matching key presses to key bindings, foot uses a couple of +different approaches. + +As an example, let's say you press ctrl+shift+c (assume plain us ASCII +layout). XKB will tell foot *Control+C* was pressed. Note the lack of +the shift modifier, and the upper case 'C'. Internally, this is called +the "translated" form, and is what foot tries to match first. + +If no "translated" key bindings can be found, foot proceeds to +checking the "untranslated" variant. Using the same example as above, +this will match *Control+Shift+c* (shift modifier present, lower case +'c'). + +This means you can use either form in your foot configuration, and +that *Control+C* (and similar) has higher priority than +*Control+Shift+c*. Also note that while foot normally detects when the +same combination is assigned to multiple actions, it will not detect +*Control+C* vs. *Control+Shift+c* collisions. Call it a known bug... + +Finally, foot tries to match the raw key code. Here, the primary +layout is queried for all key codes that generate a particular XKB +symbol, and the pressed key's code is matched against this. For +example, if you use the layouts *"us,de(neo)"*, the 'r' key generates +the symbol 'c' in the neo layout. I.e. to get a 'c', you press +'r'. The match logic described above will only match 'c' key bindings +(e.g. *Control+Shift+c*). The raw mode however, will match 'r' key +bindings (e.g. *Control+Shift+r*). This is useful for non-latin +layouts, where you would otherwise have to customize all key bindings. + +A key combination can only be mapped to *one* action. Let's say you want to bind *Control+Shift+R* to *fullscreen*. Since this is the default shortcut for *search-start*, you first need to unmap the default binding. This can be done by setting _action=none_; @@ -1453,7 +1482,7 @@ events never generate a *COUNT* larger than 1. That is, Foot also recognizes tiltable wheels; to map these, use *BTN_WHEEL_LEFT* and *BTN_WHEEL_RIGHT*. -A modifier+button combination can only be mapped to *one* action. Lets +A modifier+button combination can only be mapped to *one* action. Let's say you want to bind *BTN\_MIDDLE* to *fullscreen*. Since *BTN\_MIDDLE* is the default binding for *primary-paste*, you first need to unmap the default binding. This can be done by setting From 8b408f00397b11cbd47ddf050a23027245d81949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 13:15:59 +0100 Subject: [PATCH 043/353] forgejo: add optional field for terminal multiplexers --- .forgejo/issue_template/issue_template.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index 98b9c919..1545fe85 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -42,12 +42,18 @@ body: placeholder: "Arch Linux" validations: required: true + - type: input + id: multiplexer + attributes: + label: Terminal multiplexer + description: "Terminal multiplexers are terminal emulators themselves, therefore the issue may be in the multiplexer, not foot. Please list which multiplexer(s) you use here (and mention in the problem description below if the issue only occurs in the multiplexer, but not in bare metal foot)" + placeholder: "tmux, zellij" - type: input id: application attributes: - label: Shell, TUI, application - description: "Application(s) in which the problem occurs (list all known)" - placeholder: "e.g. bash, neovim" + label: Shell, TUI, application + description: "Application(s) in which the problem occurs (list all known)" + placeholder: "e.g. bash, neovim" - type: textarea id: config attributes: From c2c8d29272f81ef8c468b687f247574a9b62f5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 13:16:43 +0100 Subject: [PATCH 044/353] forgejo: remove "e.g." from placeholder text --- .forgejo/issue_template/issue_template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index 1545fe85..805d15ce 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -53,7 +53,7 @@ body: attributes: label: Shell, TUI, application description: "Application(s) in which the problem occurs (list all known)" - placeholder: "e.g. bash, neovim" + placeholder: "bash, neovim" - type: textarea id: config attributes: From 6e2bdd663aa69176d670e3c8fd8e9a213f5e3e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 13:18:09 +0100 Subject: [PATCH 045/353] forgejo: config: render as .ini, instead of the default markdown --- .forgejo/issue_template/issue_template.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index 805d15ce..fd2e62e5 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -59,6 +59,7 @@ body: attributes: label: Foot config description: Paste your entire `foot.ini` here + render: ini validations: required: true - type: textarea From 5286808b6cd439c0442a76169d7a58b1b7e71e8c Mon Sep 17 00:00:00 2001 From: Attila Fidan Date: Thu, 30 Jan 2025 09:39:31 +0000 Subject: [PATCH 046/353] input: close fd on no/unrecognized keymap format --- CHANGELOG.md | 2 ++ input.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61559359..a14b0fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,8 @@ space inserted at the end of the current line. * Wrong key bindings executed when using alternative keyboard layouts ([#1929][1929]). +* Foot not closing file descriptors for unrecognized or `no_keymap` + keymaps. [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 diff --git a/input.c b/input.c index 2db696c1..a2f30a0c 100644 --- a/input.c +++ b/input.c @@ -527,6 +527,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, /* Verify keymap is in a format we understand */ switch ((enum wl_keyboard_keymap_format)format) { case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP: + close(fd); return; case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1: @@ -534,6 +535,7 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, default: LOG_WARN("unrecognized keymap format: %u", format); + close(fd); return; } From d24f700256240ae38505a4ab501e25814133c011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 31 Jan 2025 07:29:16 +0100 Subject: [PATCH 047/353] key-bindings: add keypad variants to existing default key-bindings --- config.c | 5 +++++ doc/foot.ini.5.scd | 13 ++++++++----- foot.ini | 10 +++++----- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/config.c b/config.c index 12336e3d..a9db87d5 100644 --- a/config.c +++ b/config.c @@ -3006,7 +3006,9 @@ add_default_key_bindings(struct config *conf) { const struct config_key_binding bindings[] = { {BIND_ACTION_SCROLLBACK_UP_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Prior}}}, + {BIND_ACTION_SCROLLBACK_UP_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_KP_Prior}}}, {BIND_ACTION_SCROLLBACK_DOWN_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Next}}}, + {BIND_ACTION_SCROLLBACK_DOWN_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_KP_Next}}}, {BIND_ACTION_CLIPBOARD_COPY, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_c}}}, {BIND_ACTION_CLIPBOARD_COPY, m("none"), {{XKB_KEY_XF86Copy}}}, {BIND_ACTION_CLIPBOARD_PASTE, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_v}}}, @@ -3037,11 +3039,14 @@ add_default_search_bindings(struct config *conf) { const struct config_key_binding bindings[] = { {BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Prior}}}, + {BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_KP_Prior}}}, {BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Next}}}, + {BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_KP_Next}}}, {BIND_ACTION_SEARCH_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_c}}}, {BIND_ACTION_SEARCH_CANCEL, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_g}}}, {BIND_ACTION_SEARCH_CANCEL, m("none"), {{XKB_KEY_Escape}}}, {BIND_ACTION_SEARCH_COMMIT, m("none"), {{XKB_KEY_Return}}}, + {BIND_ACTION_SEARCH_COMMIT, m("none"), {{XKB_KEY_KP_Enter}}}, {BIND_ACTION_SEARCH_FIND_PREV, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_r}}}, {BIND_ACTION_SEARCH_FIND_NEXT, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_s}}}, {BIND_ACTION_SEARCH_EDIT_LEFT, m("none"), {{XKB_KEY_Left}}}, diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 35f78674..903d3375 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1136,7 +1136,8 @@ e.g. *search-start=none*. application. Default: _none_. *scrollback-up-page* - Scrolls up/back one page in history. Default: _Shift+Page\_Up_. + Scrolls up/back one page in history. Default: _Shift+Page\_Up + Shift+KP\_Page\_Up_. *scrollback-up-half-page* Scrolls up/back half of a page in history. Default: _none_. @@ -1146,7 +1147,7 @@ e.g. *search-start=none*. *scrollback-down-page* Scroll down/forward one page in history. Default: - _Shift+Page\_Down_. + _Shift+Page\_Down Shift+KP\_Page\_Down_. *scrollback-down-half-page* Scroll down/forward half of a page in history. Default: _none_. @@ -1288,7 +1289,8 @@ scrollback search mode. The syntax is exactly the same as the regular *commit* Exit search mode and copy current selection into the _primary selection_. Viewport is **not** restored. To copy the selection to - the regular _clipboard_, use *Control+Shift+c*. Default: _Return_. + the regular _clipboard_, use *Control+Shift+c*. Default: _Return + KP_Enter_. *find-prev* Search **backwards** in the scrollback history for the next @@ -1379,7 +1381,8 @@ scrollback search mode. The syntax is exactly the same as the regular details. Default: _none_. *scrollback-up-page* - Scrolls up/back one page in history. Default: _Shift+Page\_Up_. + Scrolls up/back one page in history. Default: _Shift+Page\_Up + Shift+KP\_Page\_Up_. *scrollback-up-half-page* Scrolls up/back half of a page in history. Default: _none_. @@ -1389,7 +1392,7 @@ scrollback search mode. The syntax is exactly the same as the regular *scrollback-down-page* Scroll down/forward one page in history. Default: - _Shift+Page\_Down_. + _Shift+Page\_Down Shift+KP\_Page\_Down_. *scrollback-down-half-page* Scroll down/forward half of a page in history. Default: _none_. diff --git a/foot.ini b/foot.ini index 580178af..17fabd3d 100644 --- a/foot.ini +++ b/foot.ini @@ -167,10 +167,10 @@ # button-close-color= [key-bindings] -# scrollback-up-page=Shift+Page_Up +# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up # scrollback-up-half-page=none # scrollback-up-line=none -# scrollback-down-page=Shift+Page_Down +# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down # scrollback-down-half-page=none # scrollback-down-line=none # scrollback-home=none @@ -201,7 +201,7 @@ [search-bindings] # cancel=Control+g Control+c Escape -# commit=Return +# commit=Return KP_Enter # find-prev=Control+r # find-next=Control+s # cursor-left=Left Control+b @@ -225,10 +225,10 @@ # clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste # primary-paste=Shift+Insert # unicode-input=none -# scrollback-up-page=Shift+Page_Up +# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up # scrollback-up-half-page=none # scrollback-up-line=none -# scrollback-down-page=Shift+Page_Down +# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down # scrollback-down-half-page=none # scrollback-down-line=none # scrollback-home=none From bee17a95b86a640522c25e650e13aebc82c7353d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 31 Jan 2025 07:35:54 +0100 Subject: [PATCH 048/353] input: ignore key-bindings without modifiers when matching untranslated/raw When matching the unshifted symbol, or the raw key code, ignore all key bindings that don't have any modifiers. This fixes an issue where it was impossible to enter (some of the) numbers on the keypad, **if** there was a key-binding for e.g. KP_Page_Up, or KP_Page_Down. --- input.c | 4 ++-- search.c | 4 ++-- url-mode.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/input.c b/input.c index a2f30a0c..22566411 100644 --- a/input.c +++ b/input.c @@ -1600,7 +1600,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, tll_foreach(bindings->key, it) { const struct key_binding *bind = &it->item; - if (bind->mods != mods) + if (bind->mods != mods || bind->mods == 0) continue; for (size_t i = 0; i < raw_count; i++) { @@ -1616,7 +1616,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, tll_foreach(bindings->key, it) { const struct key_binding *bind = &it->item; - if (bind->mods != mods) + if (bind->mods != mods || bind->mods == 0) continue; tll_foreach(bind->k.key_codes, code) { diff --git a/search.c b/search.c index bcb354d6..75f12b4a 100644 --- a/search.c +++ b/search.c @@ -1413,7 +1413,7 @@ search_input(struct seat *seat, struct terminal *term, tll_foreach(bindings->search, it) { const struct key_binding *bind = &it->item; - if (bind->mods != mods) + if (bind->mods != mods || bind->mods == 0) continue; for (size_t i = 0; i < raw_count; i++) { @@ -1433,7 +1433,7 @@ search_input(struct seat *seat, struct terminal *term, tll_foreach(bindings->search, it) { const struct key_binding *bind = &it->item; - if (bind->mods != mods) + if (bind->mods != mods || bind->mods == 0) continue; tll_foreach(bind->k.key_codes, code) { diff --git a/url-mode.c b/url-mode.c index cca7bd22..83dbfa70 100644 --- a/url-mode.c +++ b/url-mode.c @@ -198,7 +198,7 @@ urls_input(struct seat *seat, struct terminal *term, /* Match untranslated symbols */ tll_foreach(bindings->url, it) { const struct key_binding *bind = &it->item; - if (bind->mods != mods) + if (bind->mods != mods || bind->mods == 0) continue; for (size_t i = 0; i < raw_count; i++) { @@ -212,7 +212,7 @@ urls_input(struct seat *seat, struct terminal *term, /* Match raw key code */ tll_foreach(bindings->url, it) { const struct key_binding *bind = &it->item; - if (bind->mods != mods) + if (bind->mods != mods || bind->mods == 0) continue; /* Match raw key code */ From 51128a3484049e5f9b9b74276222977d12554d76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 31 Jan 2025 09:07:42 +0100 Subject: [PATCH 049/353] input: match unshifted key-bindings before shifted That is, try to match e.g. Control+shift+a, before trying to match Control+A. In most cases, order doesn't matter. There are however a couple of symbols where the layout consumes the shift-modifier, and the generated symbol is the same in both the shifted and unshifted form. One such example is backspace. Before this patch, key-bindings with shift-backspace would be ignored, if there were another key-binding with backspace. So, for example, if we had one key-binding with Control+Backspace, and another with Control+Shift+Backspace, the latter would never trigger, as we would always match the first one. By checking for unshifted matches first, we ensure Control+Shift+Backspace does match. --- input.c | 24 ++++++++++++------------ search.c | 34 +++++++++++++++++----------------- url-mode.c | 26 +++++++++++++------------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/input.c b/input.c index 22566411..c3ddbf13 100644 --- a/input.c +++ b/input.c @@ -1584,18 +1584,6 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, * User configurable bindings */ if (pressed) { - /* Match translated symbol */ - tll_foreach(bindings->key, it) { - const struct key_binding *bind = &it->item; - - if (bind->k.sym == sym && - bind->mods == (mods & ~consumed) && - execute_binding(seat, term, bind, serial, 1)) - { - goto maybe_repeat; - } - } - /* Match untranslated symbols */ tll_foreach(bindings->key, it) { const struct key_binding *bind = &it->item; @@ -1612,6 +1600,18 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, } } + /* Match translated symbol */ + tll_foreach(bindings->key, it) { + const struct key_binding *bind = &it->item; + + if (bind->k.sym == sym && + bind->mods == (mods & ~consumed) && + execute_binding(seat, term, bind, serial, 1)) + { + goto maybe_repeat; + } + } + /* Match raw key code */ tll_foreach(bindings->key, it) { const struct key_binding *bind = &it->item; diff --git a/search.c b/search.c index 75f12b4a..20990c87 100644 --- a/search.c +++ b/search.c @@ -1392,23 +1392,6 @@ search_input(struct seat *seat, struct terminal *term, * Key bindings */ - /* Match translated symbol */ - tll_foreach(bindings->search, it) { - const struct key_binding *bind = &it->item; - - if (bind->k.sym == sym && - bind->mods == (mods & ~consumed)) { - - if (execute_binding(seat, term, bind, serial, - &update_search_result, &search_direction, - &redraw)) - { - goto update_search; - } - return; - } - } - /* Match untranslated symbols */ tll_foreach(bindings->search, it) { const struct key_binding *bind = &it->item; @@ -1429,6 +1412,23 @@ search_input(struct seat *seat, struct terminal *term, } } + /* Match translated symbol */ + tll_foreach(bindings->search, it) { + const struct key_binding *bind = &it->item; + + if (bind->k.sym == sym && + bind->mods == (mods & ~consumed)) { + + if (execute_binding(seat, term, bind, serial, + &update_search_result, &search_direction, + &redraw)) + { + goto update_search; + } + return; + } + } + /* Match raw key code */ tll_foreach(bindings->search, it) { const struct key_binding *bind = &it->item; diff --git a/url-mode.c b/url-mode.c index 83dbfa70..986860af 100644 --- a/url-mode.c +++ b/url-mode.c @@ -182,19 +182,6 @@ urls_input(struct seat *seat, struct terminal *term, * Key bindings */ - /* Match translated symbol */ - tll_foreach(bindings->url, it) { - const struct key_binding *bind = &it->item; - - if (bind->k.sym == sym && - bind->mods == (mods & ~consumed)) - { - execute_binding(seat, term, bind, serial); - return; - } - - } - /* Match untranslated symbols */ tll_foreach(bindings->url, it) { const struct key_binding *bind = &it->item; @@ -209,6 +196,19 @@ urls_input(struct seat *seat, struct terminal *term, } } + /* Match translated symbol */ + tll_foreach(bindings->url, it) { + const struct key_binding *bind = &it->item; + + if (bind->k.sym == sym && + bind->mods == (mods & ~consumed)) + { + execute_binding(seat, term, bind, serial); + return; + } + + } + /* Match raw key code */ tll_foreach(bindings->url, it) { const struct key_binding *bind = &it->item; From dc4e9fc25b541cb19c6e0db297764d3117e39c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Feb 2025 14:48:02 +0100 Subject: [PATCH 050/353] forgejo: ask user to provide distro *version*, when applicable --- .forgejo/issue_template/issue_template.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index fd2e62e5..8ac9edbc 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -38,8 +38,8 @@ body: id: distro attributes: label: Distribution - description: "The name of the Linux distribution, or BSD flavor, you are running" - placeholder: "Arch Linux" + description: "The name of the Linux distribution, or BSD flavor, you are running. And, if applicable, the version" + placeholder: "Fedora Workstation 41" validations: required: true - type: input From 9c882cfdabdd951ff11b5ae0dc4eb3da6e268f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Feb 2025 14:52:52 +0100 Subject: [PATCH 051/353] forgejo: issue happens in foot --server, standalone, or both? --- .forgejo/issue_template/issue_template.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index 8ac9edbc..a52d97b3 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -54,6 +54,14 @@ body: label: Shell, TUI, application description: "Application(s) in which the problem occurs (list all known)" placeholder: "bash, neovim" + - type: checkboxes + id: server + attributes: + label: Server/standalone mode + description: The issue occurs in foot server, or standalone mode, or both. Note that you **cannot** test standalone mode by manually running `foot` from a `footclient` instance, since then the standalone foot will simply inherit the server process' context. + options: + - label: standalone + - label: server - type: textarea id: config attributes: From 230d8b6f70804bb656e6989a868a2db2a6a868a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Feb 2025 14:54:02 +0100 Subject: [PATCH 052/353] forgejo: server/standalone: tweak wording --- .forgejo/issue_template/issue_template.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index a52d97b3..51c85c3f 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -58,10 +58,10 @@ body: id: server attributes: label: Server/standalone mode - description: The issue occurs in foot server, or standalone mode, or both. Note that you **cannot** test standalone mode by manually running `foot` from a `footclient` instance, since then the standalone foot will simply inherit the server process' context. + description: Does the issue occur in foot server, or standalone mode, or both? Note that you **cannot** test standalone mode by manually running `foot` from a `footclient` instance, since then the standalone foot will simply inherit the server process' context. options: - - label: standalone - - label: server + - label: Standalone + - label: Server - type: textarea id: config attributes: From fcfdbeebcf17506634b75f763f54388148983a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Feb 2025 14:55:09 +0100 Subject: [PATCH 053/353] forgejo: remind user to sanitize pasted config --- .forgejo/issue_template/issue_template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index 51c85c3f..a5000090 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -66,7 +66,7 @@ body: id: config attributes: label: Foot config - description: Paste your entire `foot.ini` here + description: Paste your entire `foot.ini` here (do not forget to sanitize it!) render: ini validations: required: true From 70aa033d7912b978264d035b095b25090ff00f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Feb 2025 14:55:49 +0100 Subject: [PATCH 054/353] forgejo: server/standalone: what happens when we set required=true? --- .forgejo/issue_template/issue_template.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index a5000090..3c6dee90 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -61,7 +61,9 @@ body: description: Does the issue occur in foot server, or standalone mode, or both? Note that you **cannot** test standalone mode by manually running `foot` from a `footclient` instance, since then the standalone foot will simply inherit the server process' context. options: - label: Standalone + required: true - label: Server + required: true - type: textarea id: config attributes: From 6f9129fa3af94379db59641ebb2e950d65dffdab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Feb 2025 14:56:22 +0100 Subject: [PATCH 055/353] Revert "forgejo: server/standalone: what happens when we set required=true?" This reverts commit 70aa033d7912b978264d035b095b25090ff00f07. --- .forgejo/issue_template/issue_template.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/issue_template.yml index 3c6dee90..a5000090 100644 --- a/.forgejo/issue_template/issue_template.yml +++ b/.forgejo/issue_template/issue_template.yml @@ -61,9 +61,7 @@ body: description: Does the issue occur in foot server, or standalone mode, or both? Note that you **cannot** test standalone mode by manually running `foot` from a `footclient` instance, since then the standalone foot will simply inherit the server process' context. options: - label: Standalone - required: true - label: Server - required: true - type: textarea id: config attributes: From 2fe72effa9870ee2d66aad56ce048960f933c96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Feb 2025 11:39:06 +0100 Subject: [PATCH 056/353] term: ptmx pause/resume: don't modify the FDM if ptmx has been closed This fixes error message spam when resizing a terminal window executed with --hold, and where the client application has terminated. --- terminal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terminal.c b/terminal.c index 6936ff29..9fd20002 100644 --- a/terminal.c +++ b/terminal.c @@ -387,12 +387,16 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) bool term_ptmx_pause(struct terminal *term) { + if (term->ptmx < 0) + return false; return fdm_event_del(term->fdm, term->ptmx, EPOLLIN); } bool term_ptmx_resume(struct terminal *term) { + if (term->ptmx < 0) + return false; return fdm_event_add(term->fdm, term->ptmx, EPOLLIN); } From 8de378963b90bca1bc932c094a78c683d01019aa Mon Sep 17 00:00:00 2001 From: sewn Date: Wed, 5 Feb 2025 14:23:17 +0300 Subject: [PATCH 057/353] server: don't instantiate a client without a monitor --- server.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server.c b/server.c index 78d98d53..5981a14c 100644 --- a/server.c +++ b/server.c @@ -211,6 +211,12 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) return true; } + if (tll_length(server->wayl->monitors) == 0) { + LOG_ERR("no monitors available for new terminal"); + client_send_exit_code(client, -26); + goto shutdown; + } + /* All initialization data received - time to instantiate a terminal! */ xassert(client->instance == NULL); From 9443ac7e2937bb4f26cf44c73bb8150860c5df45 Mon Sep 17 00:00:00 2001 From: Thomas Bonnefille Date: Tue, 4 Feb 2025 09:48:13 +0100 Subject: [PATCH 058/353] box-drawings: handle architecture with soft-float Currently, architecture using soft-floats doesn't support instructions FE_INVALID, FE_DIVBYZERO, FE_OVERFLOW and FE_UNDERFLOW and so building on those architectures results with a build error. As the sqrt math function should set errno to EDOM if an error occurs, fetestexcept shouldn't be mandatory. This commit removes the float environment error handling. Signed-off-by: Thomas Bonnefille --- box-drawing.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/box-drawing.c b/box-drawing.c index 1c613051..421ff54d 100644 --- a/box-drawing.c +++ b/box-drawing.c @@ -1462,14 +1462,12 @@ draw_box_drawings_light_arc(struct buf *buf, char32_t wc) */ for (double i = y_min*16; i <= y_max*16; i++) { errno = 0; - feclearexcept(FE_ALL_EXCEPT); double y = i / 16.; double x = circle_hemisphere * sqrt(c_r2 - (y - c_y) * (y - c_y)) + c_x; /* See math_error(7) */ - if (errno != 0 || - fetestexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW)) + if (errno != 0) { continue; } From aae794e9bdd5f19019560041479cf7ebc46b4fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 09:06:24 +0100 Subject: [PATCH 059/353] xmalloc: add xreallocarray() --- xmalloc.c | 7 +++++++ xmalloc.h | 1 + 2 files changed, 8 insertions(+) diff --git a/xmalloc.c b/xmalloc.c index ded7f4e3..0a67cdb2 100644 --- a/xmalloc.c +++ b/xmalloc.c @@ -36,6 +36,13 @@ xrealloc(void *ptr, size_t size) return unlikely(size == 0) ? alloc : check_alloc(alloc); } +void * +xreallocarray(void *ptr, size_t n, size_t size) +{ + void *alloc = reallocarray(ptr, n, size); + return unlikely(size == 0) ? alloc : check_alloc(alloc); +} + char * xstrdup(const char *str) { diff --git a/xmalloc.h b/xmalloc.h index 8a2c208f..03e6eb0d 100644 --- a/xmalloc.h +++ b/xmalloc.h @@ -12,6 +12,7 @@ void *xmalloc(size_t size) XMALLOC; void *xcalloc(size_t nmemb, size_t size) XMALLOC; void *xrealloc(void *ptr, size_t size); +void *xreallocarray(void *ptr, size_t n, size_t size); char *xstrdup(const char *str) XSTRDUP; char *xstrndup(const char *str, size_t n) XSTRDUP; char *xasprintf(const char *format, ...) PRINTF(1) XMALLOC; From 32919b1049b2f333208920f6b3c240fe7c637f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 09:06:40 +0100 Subject: [PATCH 060/353] grid: typo --- grid.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grid.c b/grid.c index 3f5c617d..b7c0447c 100644 --- a/grid.c +++ b/grid.c @@ -36,7 +36,8 @@ grid_row_abs_to_sb(const struct grid *grid, int screen_rows, int abs_row) return rebased_row; } -int grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row) +int +grid_row_sb_to_abs(const struct grid *grid, int screen_rows, int sb_rel_row) { const int scrollback_start = grid->offset + screen_rows; int abs_row = sb_rel_row + scrollback_start; From 1c15ee940d0063b7cfa11845c0af537d50ca8acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 09:06:47 +0100 Subject: [PATCH 061/353] url-mode: wip: convert to regex matching for auto-detection --- url-mode.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/url-mode.c b/url-mode.c index 986860af..0ed79a99 100644 --- a/url-mode.c +++ b/url-mode.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -291,6 +292,149 @@ urls_input(struct seat *seat, struct terminal *term, } } +struct charmap { + const struct row *row; + int col; +}; + +struct vline { + char *utf8; + size_t len; + size_t sz; + struct charmap *map; +}; + +static void +regex_detected(const struct terminal *term, enum url_action action, url_list_t *urls) +{ + struct vline vlines[term->rows]; + size_t vline_idx = 0; + + memset(vlines, 0, sizeof(vlines)); + struct vline *vline = &vlines[vline_idx]; + + mbstate_t ps = {0}; + + for (int row_no = 0; row_no < term->rows; row_no++) { + const struct row *row = grid_row_in_view(term->grid, row_no); + + for (int c = 0; c < term->cols; c++) { + const struct cell *cell = &row->cells[c]; + const char32_t *wc = &cell->wc; + size_t wc_count = 1; + + if (wc[0] >= CELL_COMB_CHARS_LO && wc[0] <= CELL_COMB_CHARS_HI) { + const struct composed *composed = + composed_lookup(term->composed, wc[0] - CELL_COMB_CHARS_LO); + xassert(composed != NULL); + + wc = composed->chars; + wc_count = composed->count; + } + + for (size_t i = 0; i < wc_count; i++) { + char buf[16]; + size_t char_len = c32rtomb(buf, wc[i], &ps); + if (char_len == (size_t)-1) + continue; + + if (vline->len == 0 && char_len == 1 && buf[0] == 0) + continue; + + for (size_t j = 0; j < char_len; j++) { + if (vline->len + char_len > vline->sz) { + /* TODO: grow dynamically */ + size_t new_count = (vline->len + char_len) * 2; + vline->utf8 = xreallocarray(vline->utf8, new_count, 1); + vline->map = xreallocarray(vline->map, new_count, sizeof(vline->map[0])); + } + + vline->utf8[vline->len + j] = buf[j]; + vline->map[vline->len + j].col = c; + vline->map[vline->len + j].row = row; + } + + vline->len += char_len; + } + } + + if (row->linebreak) { + if (vline->len > 0) { + vline->utf8[vline->len++] = '\0'; + ps = (mbstate_t){0}; + + vline_idx++; + vline = &vlines[vline_idx]; + } + } + } + + // https://gist.github.com/gruber/249502 + regex_t preg; + const char *foo = + "(" + "[a-z][[:alpha:]-]+:" // protocol + "(" + "/{1,3}|[a-z0-9%]" // slashes (what's the OR part for?) + ")" + "|" + "www[:digit:]{0,3}[.]" + "|" + "[a-z0-9.\\-]+[.][a-z]{2,4}/" + ")" + "(" + "[^[:space:]()<>]+" + "|" + "\\(([^[:space:]()<>]+|(\\([^[:space:]()<>]+\\)))*\\)" + ")+" + "(" + "\\(([^[:space:]()<>]+|(\\([^[:space:]()<>]+\\)))*\\)" + "|" + // TODO: figure out how to add \\] to the expression below... + "[^[:space:]`!()\\[{};:'\".,<>?«»“”‘’]" + ")" + ; + + LOG_ERR("foo=%s", foo); + + int r = regcomp(&preg, foo, REG_EXTENDED); + + if (r != 0) { + char err_buf[1024]; + regerror(r, &preg, err_buf, sizeof(err_buf)); + LOG_ERR("regcomp: %s", err_buf); + } else { + size_t i = 0; + while (true) { + const struct vline *v = &vlines[i++]; + if (v->utf8 == NULL) + break; + + + regmatch_t matches[preg.re_nsub + 1]; + r = regexec(&preg, v->utf8, preg.re_nsub + 1, matches, 0); + + if (r == REG_NOMATCH) + continue; + + size_t mlen = matches[0].rm_eo - matches[0].rm_so; + LOG_WARN("MATCH at %d: %.*s (%zu)", matches[0].rm_so, (int)mlen, &v->utf8[matches[0].rm_so], mlen); + } + regfree(&preg); + } + + size_t i = 0; + while (true) { + const struct vline *v = &vlines[i++]; + if (v->utf8 == NULL) + break; + + LOG_WARN("%.*s", (int)v->len, v->utf8); + free(v->utf8); + free(v->map); + } +} + static int c32cmp_single(const void *_a, const void *_b) { @@ -634,6 +778,7 @@ urls_collect(const struct terminal *term, enum url_action action, url_list_t *ur xassert(tll_length(term->urls) == 0); osc8_uris(term, action, urls); auto_detected(term, action, urls); + regex_detected(term, action, urls); remove_overlapping(urls, term->grid->num_cols); } From 859b4c8921f5cde8cfc1f358e865ba417fe68a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 09:51:50 +0100 Subject: [PATCH 062/353] url-mode: wip: more work on regex matching Remove the old auto-detection and instead use the regex matches. --- url-mode.c | 371 ++++++++++++----------------------------------------- 1 file changed, 80 insertions(+), 291 deletions(-) diff --git a/url-mode.c b/url-mode.c index 0ed79a99..18085ab5 100644 --- a/url-mode.c +++ b/url-mode.c @@ -292,21 +292,31 @@ urls_input(struct seat *seat, struct terminal *term, } } -struct charmap { - const struct row *row; - int col; -}; - struct vline { char *utf8; - size_t len; - size_t sz; - struct charmap *map; + size_t len; /* Length of utf8[] */ + size_t sz; /* utf8[] allocated size */ + struct coord *map; /* Maps utf8[ofs] to grid coordinates */ }; static void regex_detected(const struct terminal *term, enum url_action action, url_list_t *urls) { + /* + * Use regcomp()+regexec() to find patterns. + * + * Since we can't feed regexec() one character at a time, and + * since it doesn't accept wide characters, we need to build utf8 + * strings. + * + * Each string represents a logical line (i.e. handle line-wrap). + * To be able to map regex matches back to the grid, we store the + * grid coordinates of *each* character, in the line struct as + * well. This is offset based; utf8[ofs] has its grid coordinates + * in map[ofs. + */ + + /* There is *at most* term->rows logical lines */ struct vline vlines[term->rows]; size_t vline_idx = 0; @@ -315,14 +325,15 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * mbstate_t ps = {0}; - for (int row_no = 0; row_no < term->rows; row_no++) { - const struct row *row = grid_row_in_view(term->grid, row_no); + for (int r = 0; r < term->rows; r++) { + const struct row *row = grid_row_in_view(term->grid, r); for (int c = 0; c < term->cols; c++) { const struct cell *cell = &row->cells[c]; const char32_t *wc = &cell->wc; size_t wc_count = 1; + /* Expand combining characters */ if (wc[0] >= CELL_COMB_CHARS_LO && wc[0] <= CELL_COMB_CHARS_HI) { const struct composed *composed = composed_lookup(term->composed, wc[0] - CELL_COMB_CHARS_LO); @@ -332,26 +343,26 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * wc_count = composed->count; } + /* Convert wide character to utf8 */ for (size_t i = 0; i < wc_count; i++) { char buf[16]; size_t char_len = c32rtomb(buf, wc[i], &ps); + if (char_len == (size_t)-1) continue; - if (vline->len == 0 && char_len == 1 && buf[0] == 0) - continue; - for (size_t j = 0; j < char_len; j++) { - if (vline->len + char_len > vline->sz) { - /* TODO: grow dynamically */ - size_t new_count = (vline->len + char_len) * 2; - vline->utf8 = xreallocarray(vline->utf8, new_count, 1); - vline->map = xreallocarray(vline->map, new_count, sizeof(vline->map[0])); + const size_t requires_size = vline->len + char_len; + + if (requires_size > vline->sz) { + const size_t new_size = requires_size * 2; + vline->utf8 = xreallocarray(vline->utf8, new_size, 1); + vline->map = xreallocarray(vline->map, new_size, sizeof(vline->map[0])); + vline->sz = new_size; } vline->utf8[vline->len + j] = buf[j]; - vline->map[vline->len + j].col = c; - vline->map[vline->len + j].row = row; + vline->map[vline->len + j] = (struct coord){c, term->grid->view + r}; } vline->len += char_len; @@ -371,9 +382,9 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * // https://gist.github.com/gruber/249502 regex_t preg; - const char *foo = + const char *regex_string = "(" - "[a-z][[:alpha:]-]+:" // protocol + "[a-z][[:alnum:]-]+:" // protocol "(" "/{1,3}|[a-z0-9%]" // slashes (what's the OR part for?) ")" @@ -390,291 +401,70 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * "(" "\\(([^[:space:]()<>]+|(\\([^[:space:]()<>]+\\)))*\\)" "|" - // TODO: figure out how to add \\] to the expression below... - "[^[:space:]`!()\\[{};:'\".,<>?«»“”‘’]" + "[^]\\[[:space:]`!(){};:'\".,<>?«»“”‘’]" ")" ; - LOG_ERR("foo=%s", foo); - - int r = regcomp(&preg, foo, REG_EXTENDED); + int r = regcomp(&preg, regex_string, REG_EXTENDED); if (r != 0) { char err_buf[1024]; regerror(r, &preg, err_buf, sizeof(err_buf)); - LOG_ERR("regcomp: %s", err_buf); - } else { - size_t i = 0; - while (true) { - const struct vline *v = &vlines[i++]; - if (v->utf8 == NULL) - break; + LOG_ERR("failed to compile regular expression: %s", err_buf); - - regmatch_t matches[preg.re_nsub + 1]; - r = regexec(&preg, v->utf8, preg.re_nsub + 1, matches, 0); - - if (r == REG_NOMATCH) - continue; - - size_t mlen = matches[0].rm_eo - matches[0].rm_so; - LOG_WARN("MATCH at %d: %.*s (%zu)", matches[0].rm_so, (int)mlen, &v->utf8[matches[0].rm_so], mlen); + for (size_t i = 0; i < ALEN(vlines); i++) { + const struct vline *v = &vlines[i]; + free(v->utf8); + free(v->map); } - regfree(&preg); + + return; } - size_t i = 0; - while (true) { - const struct vline *v = &vlines[i++]; + for (size_t i = 0; i < ALEN(vlines); i++) { + const struct vline *v = &vlines[i]; if (v->utf8 == NULL) - break; + continue;; + + const char *search_string = v->utf8; + while (true) { + + regmatch_t matches[preg.re_nsub + 1]; + r = regexec(&preg, search_string, preg.re_nsub + 1, matches, 0); + + if (r == REG_NOMATCH) + break; + + const size_t mlen = matches[0].rm_eo - matches[0].rm_so; + const size_t start = &search_string[matches[0].rm_so] - v->utf8; + const size_t end = start + mlen; + + LOG_DBG( + "MATCH at %d: %.*s (%zu) row/col = %dx%d", + matches[0].rm_so, (int)mlen, &search_string[matches[0].rm_so], + mlen, v->map[start].row, v->map[start].col); + + tll_push_back( + *urls, + ((struct url){ + .id = (uint64_t)rand() << 32 | rand(), + .url = xstrndup(&v->utf8[start], mlen), + .range = { + .start = v->map[start], + .end = v->map[end - 1], /* Inclusive */ + }, + .action = action, + .osc8 = false})); + + search_string += matches[0].rm_eo; + } - LOG_WARN("%.*s", (int)v->len, v->utf8); free(v->utf8); free(v->map); } + + regfree(&preg); } - -static int -c32cmp_single(const void *_a, const void *_b) -{ - const char32_t *a = _a; - const char32_t *b = _b; - return *a - *b; -} - -static void -auto_detected(const struct terminal *term, enum url_action action, - url_list_t *urls) -{ - const struct config *conf = term->conf; - - const char32_t *uri_characters = conf->url.uri_characters; - if (uri_characters == NULL) - return; - - const size_t uri_characters_count = c32len(uri_characters); - if (uri_characters_count == 0) - return; - - size_t max_prot_len = conf->url.max_prot_len; - char32_t proto_chars[max_prot_len]; - struct coord proto_start[max_prot_len]; - size_t proto_char_count = 0; - - enum { - STATE_PROTOCOL, - STATE_URL, - } state = STATE_PROTOCOL; - - struct coord start = {-1, -1}; - char32_t url[term->cols * term->rows + 1]; - size_t len = 0; - - ssize_t parenthesis = 0; - ssize_t brackets = 0; - ssize_t ltgts = 0; - - for (int r = 0; r < term->rows; r++) { - const struct row *row = grid_row_in_view(term->grid, r); - - for (int c = 0; c < term->cols; c++) { - const struct cell *cell = &row->cells[c]; - - if (cell->wc >= CELL_SPACER) - continue; - - const char32_t *wcs = NULL; - size_t wc_count = 0; - - if (cell->wc >= CELL_COMB_CHARS_LO && cell->wc <= CELL_COMB_CHARS_HI) { - struct composed *composed = - composed_lookup(term->composed, cell->wc - CELL_COMB_CHARS_LO); - wcs = composed->chars; - wc_count = composed->count; - } else { - wcs = &cell->wc; - wc_count = 1; - } - - for (size_t w_idx = 0; w_idx < wc_count; w_idx++) { - char32_t wc = wcs[w_idx]; - - switch (state) { - case STATE_PROTOCOL: - for (size_t i = 0; i < max_prot_len - 1; i++) { - proto_chars[i] = proto_chars[i + 1]; - proto_start[i] = proto_start[i + 1]; - } - - if (proto_char_count >= max_prot_len) - proto_char_count = max_prot_len - 1; - - proto_chars[max_prot_len - 1] = wc; - proto_start[max_prot_len - 1] = (struct coord){c, r}; - proto_char_count++; - - for (size_t i = 0; i < conf->url.prot_count; i++) { - size_t prot_len = c32len(conf->url.protocols[i]); - - if (proto_char_count < prot_len) - continue; - - const char32_t *proto = - &proto_chars[max_prot_len - prot_len]; - - if (c32ncasecmp(conf->url.protocols[i], proto, prot_len) == - 0) { - state = STATE_URL; - start = proto_start[max_prot_len - prot_len]; - - c32ncpy(url, proto, prot_len); - len = prot_len; - - parenthesis = brackets = ltgts = 0; - break; - } - } - break; - - case STATE_URL: { - const char32_t *match = - bsearch(&wc, uri_characters, uri_characters_count, - sizeof(uri_characters[0]), &c32cmp_single); - - bool emit_url = false; - - if (match == NULL) { - /* - * Character is not a valid URI character. Emit - * the URL we've collected so far, *without* - * including _this_ character. - */ - emit_url = true; - } else { - xassert(*match == wc); - - switch (wc) { - default: - url[len++] = wc; - break; - - case U'(': - parenthesis++; - url[len++] = wc; - break; - - case U'[': - brackets++; - url[len++] = wc; - break; - - case U'<': - ltgts++; - url[len++] = wc; - break; - - case U')': - if (--parenthesis < 0) - emit_url = true; - else - url[len++] = wc; - break; - - case U']': - if (--brackets < 0) - emit_url = true; - else - url[len++] = wc; - break; - - case U'>': - if (--ltgts < 0) - emit_url = true; - else - url[len++] = wc; - break; - } - } - - if (c >= term->cols - 1 && row->linebreak) { - /* - * Endpoint is inclusive, and we'll be subtracting - * 1 from the column when emitting the URL. - */ - c++; - emit_url = true; - } - - if (emit_url) { - struct coord end = {c, r}; - - if (--end.col < 0) { - end.row--; - end.col = term->cols - 1; - } - - /* Heuristic to remove trailing characters that - * are valid URL characters, but typically not at - * the end of the URL */ - bool done = false; - do { - switch (url[len - 1]) { - case U'.': - case U',': - case U':': - case U';': - case U'?': - case U'!': - case U'"': - case U'\'': - case U'%': - len--; - end.col--; - if (end.col < 0) { - end.row--; - end.col = term->cols - 1; - } - break; - - default: - done = true; - break; - } - } while (!done); - - url[len] = U'\0'; - - start.row += term->grid->view; - end.row += term->grid->view; - - char *url_utf8 = ac32tombs(url); - if (url_utf8 != NULL) { - tll_push_back( - *urls, - ((struct url){.id = (uint64_t)rand() << 32 | rand(), - .url = url_utf8, - .range = - { - .start = start, - .end = end, - }, - .action = action, - .osc8 = false})); - } - - state = STATE_PROTOCOL; - len = 0; - parenthesis = brackets = ltgts = 0; - } - break; - } - } - } - } - } -} - static void osc8_uris(const struct terminal *term, enum url_action action, url_list_t *urls) { @@ -777,7 +567,6 @@ urls_collect(const struct terminal *term, enum url_action action, url_list_t *ur { xassert(tll_length(term->urls) == 0); osc8_uris(term, action, urls); - auto_detected(term, action, urls); regex_detected(term, action, urls); remove_overlapping(urls, term->grid->num_cols); } From 031382f428d783cdf692cd17dac9489fb5c0b530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 11:52:18 +0100 Subject: [PATCH 063/353] url-mode: wip: regex: don't allow {}, do allow matched [] --- url-mode.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/url-mode.c b/url-mode.c index 18085ab5..83cb4982 100644 --- a/url-mode.c +++ b/url-mode.c @@ -394,12 +394,16 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * "[a-z0-9.\\-]+[.][a-z]{2,4}/" ")" "(" - "[^[:space:]()<>]+" + "[^[:space:](){}<>]+" "|" - "\\(([^[:space:]()<>]+|(\\([^[:space:]()<>]+\\)))*\\)" + "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" + "|" + "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" ")+" "(" - "\\(([^[:space:]()<>]+|(\\([^[:space:]()<>]+\\)))*\\)" + "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" + "|" + "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" "|" "[^]\\[[:space:]`!(){};:'\".,<>?«»“”‘’]" ")" From 6d344f82ee151eb202dd786a63c12492f48fa4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 11:53:52 +0100 Subject: [PATCH 064/353] url-mode: wip: regex: mention changes from original regex --- url-mode.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/url-mode.c b/url-mode.c index 83cb4982..9aac9be0 100644 --- a/url-mode.c +++ b/url-mode.c @@ -380,7 +380,11 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * } } - // https://gist.github.com/gruber/249502 + /* + * Based on https://gist.github.com/gruber/249502, but modified: + * - Do not allow {} at all + * - Do allow matched [] + */ regex_t preg; const char *regex_string = "(" From 05207fcde34d742f2e16e8ecfedd90f23a2f8849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 11:55:09 +0100 Subject: [PATCH 065/353] url-mode: wip: regex: tweak debug log message --- url-mode.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/url-mode.c b/url-mode.c index 9aac9be0..36dc0250 100644 --- a/url-mode.c +++ b/url-mode.c @@ -448,9 +448,9 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * const size_t end = start + mlen; LOG_DBG( - "MATCH at %d: %.*s (%zu) row/col = %dx%d", + "regex match at row %d: %.*srow/col = %dx%d", matches[0].rm_so, (int)mlen, &search_string[matches[0].rm_so], - mlen, v->map[start].row, v->map[start].col); + v->map[start].row, v->map[start].col); tll_push_back( *urls, From e76d8dd7af0cd7b6b2e1b5ed9c395bd75f503e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 11:58:52 +0100 Subject: [PATCH 066/353] config: remove url.{uri-characters,protocols} --- CHANGELOG.md | 7 +++ config.c | 111 -------------------------------------------- config.h | 5 -- doc/foot.ini.5.scd | 13 ------ foot.ini | 2 - tests/test-config.c | 47 ------------------- 6 files changed, 7 insertions(+), 178 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a14b0fab..8b750f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,12 +72,19 @@ * Do not try to set a zero width, or height, if the compositor sends a _configure_ event with only one dimension being zero ([#1925][1925]). +* Auto-detection of URLs (i.e. not OSC-8 based URLs) are now regex + based. [1925]: https://codeberg.org/dnkl/foot/issues/1925 ### Deprecated ### Removed + +* `url.uri-characters` and `url.protocols`. Both options have been + replaced by `url.regex`. + + ### Fixed * Kitty keyboard protocol: alternate key reporting failing to report diff --git a/config.c b/config.c index a9db87d5..c96e7168 100644 --- a/config.c +++ b/config.c @@ -420,14 +420,6 @@ done: return ret; } -static int -c32cmp_single(const void *_a, const void *_b) -{ - const char32_t *a = _a; - const char32_t *b = _b; - return *a - *b; -} - static bool str_has_prefix(const char *str, const char *prefix) { @@ -1225,7 +1217,6 @@ parse_section_url(struct context *ctx) { struct config *conf = ctx->conf; const char *key = ctx->key; - const char *value = ctx->value; if (streq(key, "launch")) return value_to_spawn_template(ctx, &conf->url.launch); @@ -1243,70 +1234,6 @@ parse_section_url(struct context *ctx) (int *)&conf->url.osc8_underline); } - else if (streq(key, "protocols")) { - for (size_t i = 0; i < conf->url.prot_count; i++) - free(conf->url.protocols[i]); - free(conf->url.protocols); - - conf->url.max_prot_len = 0; - conf->url.prot_count = 0; - conf->url.protocols = NULL; - - char *copy = xstrdup(value); - - for (char *prot = strtok(copy, ","); - prot != NULL; - prot = strtok(NULL, ",")) - { - - /* Strip leading whitespace */ - while (isspace(prot[0])) - prot++; - - /* Strip trailing whitespace */ - size_t len = strlen(prot); - while (isspace(prot[len - 1])) - len--; - prot[len] = '\0'; - - size_t chars = mbsntoc32(NULL, prot, len, 0); - if (chars == (size_t)-1) { - ctx->value = prot; - LOG_CONTEXTUAL_ERRNO("invalid protocol"); - return false; - } - - conf->url.prot_count++; - conf->url.protocols = xrealloc( - conf->url.protocols, - conf->url.prot_count * sizeof(conf->url.protocols[0])); - - size_t idx = conf->url.prot_count - 1; - conf->url.protocols[idx] = xmalloc((chars + 1 + 3) * sizeof(char32_t)); - mbsntoc32(conf->url.protocols[idx], prot, len, chars + 1); - c32cpy(&conf->url.protocols[idx][chars], U"://"); - - chars += 3; /* Include the "://" */ - if (chars > conf->url.max_prot_len) - conf->url.max_prot_len = chars; - } - - free(copy); - return true; - } - - else if (streq(key, "uri-characters")) { - if (!value_to_wchars(ctx, &conf->url.uri_characters)) - return false; - - qsort( - conf->url.uri_characters, - c32len(conf->url.uri_characters), - sizeof(conf->url.uri_characters[0]), - &c32cmp_single); - return true; - } - else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -3196,7 +3123,6 @@ config_load(struct config *conf, const char *conf_path, }, .url = { .label_letters = xc32dup(U"sadfjklewcmpgh"), - .uri_characters = xc32dup(U"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,~:;/?#@!$&%*+=\"'()[]"), .osc8_underline = OSC8_UNDERLINE_URL_MODE, }, .can_shape_grapheme = fcft_caps & FCFT_CAPABILITY_GRAPHEME_SHAPING, @@ -3315,34 +3241,6 @@ config_load(struct config *conf, const char *conf_path, tokenize_cmdline("--action ${action-name}=${action-label}", &conf->desktop_notifications.command_action_arg.argv.args); tokenize_cmdline("xdg-open ${url}", &conf->url.launch.argv.args); - static const char32_t *url_protocols[] = { - U"http://", - U"https://", - U"ftp://", - U"ftps://", - U"file://", - U"gemini://", - U"gopher://", - U"irc://", - U"ircs://", - }; - conf->url.protocols = xmalloc( - ALEN(url_protocols) * sizeof(conf->url.protocols[0])); - conf->url.prot_count = ALEN(url_protocols); - conf->url.max_prot_len = 0; - - for (size_t i = 0; i < ALEN(url_protocols); i++) { - size_t len = c32len(url_protocols[i]); - if (len > conf->url.max_prot_len) - conf->url.max_prot_len = len; - conf->url.protocols[i] = xc32dup(url_protocols[i]); - } - - qsort( - conf->url.uri_characters, - c32len(conf->url.uri_characters), - sizeof(conf->url.uri_characters[0]), - &c32cmp_single); tll_foreach(*initial_user_notifications, it) { tll_push_back(conf->notifications, it->item); @@ -3577,12 +3475,7 @@ config_clone(const struct config *old) config_font_list_clone(&conf->csd.font, &old->csd.font); conf->url.label_letters = xc32dup(old->url.label_letters); - conf->url.uri_characters = xc32dup(old->url.uri_characters); spawn_template_clone(&conf->url.launch, &old->url.launch); - conf->url.protocols = xmalloc( - old->url.prot_count * sizeof(conf->url.protocols[0])); - for (size_t i = 0; i < old->url.prot_count; i++) - conf->url.protocols[i] = xc32dup(old->url.protocols[i]); key_binding_list_clone(&conf->bindings.key, &old->bindings.key); key_binding_list_clone(&conf->bindings.search, &old->bindings.search); @@ -3663,10 +3556,6 @@ config_free(struct config *conf) free(conf->url.label_letters); spawn_template_free(&conf->url.launch); - for (size_t i = 0; i < conf->url.prot_count; i++) - free(conf->url.protocols[i]); - free(conf->url.protocols); - free(conf->url.uri_characters); free_key_binding_list(&conf->bindings.key); free_key_binding_list(&conf->bindings.search); diff --git a/config.h b/config.h index 7d9f88c3..a7947d9d 100644 --- a/config.h +++ b/config.h @@ -219,11 +219,6 @@ struct config { OSC8_UNDERLINE_URL_MODE, OSC8_UNDERLINE_ALWAYS, } osc8_underline; - - char32_t **protocols; - char32_t *uri_characters; - size_t prot_count; - size_t max_prot_len; } url; struct { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 903d3375..813348c9 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -782,19 +782,6 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 Default: _sadfjklewcmpgh_. -*protocols* - Comma separated list of protocols (schemes) that should be - recognized in URL mode. Note that only auto-detected URLs are - affected by this option. OSC-8 URLs are always enabled, regardless - of protocol. Default: _http, https, ftp, ftps, file, gemini, - gopher, irc, ircs_. - -*uri-characters* - Set of characters allowed in auto-detected URLs. Any character not - included in this set constitutes a URL delimiter. - - Default: - _abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-\_.,~:;/?#@!$&%\*+="'()[]_ # SECTION: cursor diff --git a/foot.ini b/foot.ini index 17fabd3d..864c990c 100644 --- a/foot.ini +++ b/foot.ini @@ -69,8 +69,6 @@ # launch=xdg-open ${url} # label-letters=sadfjklewcmpgh # osc8-underline=url-mode -# protocols=http, https, ftp, ftps, file, gemini, gopher -# uri-characters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,~:;/?#@!$&%*+="'()[] [cursor] # style=block diff --git a/tests/test-config.c b/tests/test-config.c index 303ddd6f..37cf4f22 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -106,50 +106,6 @@ test_c32string(struct context *ctx, bool (*parse_fun)(struct context *ctx), } } -static void -test_protocols(struct context *ctx, bool (*parse_fun)(struct context *ctx), - const char *key, char32_t **const *ptr) -{ - ctx->key = key; - - static const struct { - const char *option_string; - int count; - const char32_t *value[2]; - bool invalid; - } input[] = { - {""}, - {"http", 1, {U"http://"}}, - {" http", 1, {U"http://"}}, - {"http, https", 2, {U"http://", U"https://"}}, - {"longprotocolislong", 1, {U"longprotocolislong://"}}, - }; - - for (size_t i = 0; i < ALEN(input); i++) { - ctx->value = input[i].option_string; - - if (input[i].invalid) { - if (parse_fun(ctx)) { - BUG("[%s].%s=%s: did not fail to parse as expected", - ctx->section, ctx->key, &ctx->value[0]); - } - } else { - if (!parse_fun(ctx)) { - BUG("[%s].%s=%s: failed to parse", - ctx->section, ctx->key, &ctx->value[0]); - } - for (int c = 0; c < input[i].count; c++) { - if (c32cmp((*ptr)[c], input[i].value[c]) != 0) { - BUG("[%s].%s=%s: set value[%d] (%ls) not the expected one (%ls)", - ctx->section, ctx->key, &ctx->value[c], c, - (const wchar_t *)(*ptr)[c], - (const wchar_t *)input[i].value[c]); - } - } - } - } -} - static void test_boolean(struct context *ctx, bool (*parse_fun)(struct context *ctx), const char *key, const bool *ptr) @@ -647,9 +603,6 @@ test_section_url(void) (int []){OSC8_UNDERLINE_URL_MODE, OSC8_UNDERLINE_ALWAYS}, (int *)&conf.url.osc8_underline); test_c32string(&ctx, &parse_section_url, "label-letters", &conf.url.label_letters); - test_protocols(&ctx, &parse_section_url, "protocols", &conf.url.protocols); - - /* TODO: uri-characters (wchar string, but sorted) */ config_free(&conf); } From d41b28bd027affe67bfd8032a2041241df8be823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 12:26:23 +0100 Subject: [PATCH 067/353] url-mode+config: wip: add url.regex option --- config.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++ config.h | 6 ++++- doc/foot.ini.5.scd | 5 ++++ foot.ini | 2 ++ url-mode.c | 57 ++++--------------------------------------- 5 files changed, 76 insertions(+), 54 deletions(-) diff --git a/config.c b/config.c index c96e7168..dd4300e3 100644 --- a/config.c +++ b/config.c @@ -1234,6 +1234,26 @@ parse_section_url(struct context *ctx) (int *)&conf->url.osc8_underline); } + else if (streq(key, "regex")) { + const char *regex = ctx->value; + regex_t preg; + + int r = regcomp(&preg, regex, REG_EXTENDED); + if (r != 0) { + char err_buf[128]; + regerror(r, &preg, err_buf, sizeof(err_buf)); + LOG_CONTEXTUAL_ERR("invalid regex: %s", err_buf); + return false; + } + + regfree(&conf->url.preg); + free(conf->url.regex); + + conf->url.regex = xstrdup(regex); + conf->url.preg = preg; + return true; + } + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -3241,6 +3261,42 @@ config_load(struct config *conf, const char *conf_path, tokenize_cmdline("--action ${action-name}=${action-label}", &conf->desktop_notifications.command_action_arg.argv.args); tokenize_cmdline("xdg-open ${url}", &conf->url.launch.argv.args); + { + /* + * Based on https://gist.github.com/gruber/249502, but modified: + * - Do not allow {} at all + * - Do allow matched [] + */ + const char *url_regex_string = + "(" + "[a-z][[:alnum:]-]+:" // protocol + "(" + "/{1,3}|[a-z0-9%]" // slashes (what's the OR part for?) + ")" + "|" + "www[:digit:]{0,3}[.]" + //"|" + //"[a-z0-9.\\-]+[.][a-z]{2,4}/" /* "looks like domain name followed by a slash" - remove? */ + ")" + "(" + "[^[:space:](){}<>]+" + "|" + "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" + "|" + "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" + ")+" + "(" + "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" + "|" + "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" + "|" + "[^]\\[[:space:]`!(){};:'\".,<>?«»“”‘’]" + ")" + ; + int r = regcomp(&conf->url.preg, url_regex_string, REG_EXTENDED); + xassert(r == 0); + conf->url.regex = xstrdup(url_regex_string); + } tll_foreach(*initial_user_notifications, it) { tll_push_back(conf->notifications, it->item); @@ -3476,6 +3532,8 @@ config_clone(const struct config *old) conf->url.label_letters = xc32dup(old->url.label_letters); spawn_template_clone(&conf->url.launch, &old->url.launch); + conf->url.regex = xstrdup(old->url.regex); + regcomp(&conf->url.preg, conf->url.regex, REG_EXTENDED); key_binding_list_clone(&conf->bindings.key, &old->bindings.key); key_binding_list_clone(&conf->bindings.search, &old->bindings.search); @@ -3556,6 +3614,8 @@ config_free(struct config *conf) free(conf->url.label_letters); spawn_template_free(&conf->url.launch); + regfree(&conf->url.preg); + free(conf->url.regex); free_key_binding_list(&conf->bindings.key); free_key_binding_list(&conf->bindings.search); diff --git a/config.h b/config.h index a7947d9d..ea0bd942 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,8 @@ #pragma once -#include +#include #include +#include #include #include @@ -219,6 +220,9 @@ struct config { OSC8_UNDERLINE_URL_MODE, OSC8_UNDERLINE_ALWAYS, } osc8_underline; + + char *regex; + regex_t preg; } url; struct { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 813348c9..ddf5ce84 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -782,6 +782,11 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 Default: _sadfjklewcmpgh_. +*regex* + URL regex to use when auto-detecting URLs. The format is + "POSIX-Extended Regular Expressions". + + Default: _TODO_ # SECTION: cursor diff --git a/foot.ini b/foot.ini index 864c990c..ae0002d4 100644 --- a/foot.ini +++ b/foot.ini @@ -69,6 +69,8 @@ # launch=xdg-open ${url} # label-letters=sadfjklewcmpgh # osc8-underline=url-mode +# regex=TODO + [cursor] # style=block diff --git a/url-mode.c b/url-mode.c index 36dc0250..9ba07d14 100644 --- a/url-mode.c +++ b/url-mode.c @@ -380,54 +380,7 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * } } - /* - * Based on https://gist.github.com/gruber/249502, but modified: - * - Do not allow {} at all - * - Do allow matched [] - */ - regex_t preg; - const char *regex_string = - "(" - "[a-z][[:alnum:]-]+:" // protocol - "(" - "/{1,3}|[a-z0-9%]" // slashes (what's the OR part for?) - ")" - "|" - "www[:digit:]{0,3}[.]" - "|" - "[a-z0-9.\\-]+[.][a-z]{2,4}/" - ")" - "(" - "[^[:space:](){}<>]+" - "|" - "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" - "|" - "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" - ")+" - "(" - "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" - "|" - "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" - "|" - "[^]\\[[:space:]`!(){};:'\".,<>?«»“”‘’]" - ")" - ; - - int r = regcomp(&preg, regex_string, REG_EXTENDED); - - if (r != 0) { - char err_buf[1024]; - regerror(r, &preg, err_buf, sizeof(err_buf)); - LOG_ERR("failed to compile regular expression: %s", err_buf); - - for (size_t i = 0; i < ALEN(vlines); i++) { - const struct vline *v = &vlines[i]; - free(v->utf8); - free(v->map); - } - - return; - } + const regex_t *preg = &term->conf->url.preg; for (size_t i = 0; i < ALEN(vlines); i++) { const struct vline *v = &vlines[i]; @@ -436,9 +389,8 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * const char *search_string = v->utf8; while (true) { - - regmatch_t matches[preg.re_nsub + 1]; - r = regexec(&preg, search_string, preg.re_nsub + 1, matches, 0); + regmatch_t matches[preg->re_nsub + 1]; + int r = regexec(preg, search_string, preg->re_nsub + 1, matches, 0); if (r == REG_NOMATCH) break; @@ -470,9 +422,8 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * free(v->utf8); free(v->map); } - - regfree(&preg); } + static void osc8_uris(const struct terminal *term, enum url_action action, url_list_t *urls) { From 130b05f02baaebe533bb00cc2824ffb9902f11d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Jan 2025 12:33:58 +0100 Subject: [PATCH 068/353] foot.ini+doc: add default value of url.regex --- config.c | 1 + doc/foot.ini.5.scd | 2 +- foot.ini | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config.c b/config.c index dd4300e3..1f779724 100644 --- a/config.c +++ b/config.c @@ -1239,6 +1239,7 @@ parse_section_url(struct context *ctx) regex_t preg; int r = regcomp(&preg, regex, REG_EXTENDED); + if (r != 0) { char err_buf[128]; regerror(r, &preg, err_buf, sizeof(err_buf)); diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index ddf5ce84..a23e3977 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -786,7 +786,7 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 URL regex to use when auto-detecting URLs. The format is "POSIX-Extended Regular Expressions". - Default: _TODO_ + Default: _([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’])_ # SECTION: cursor diff --git a/foot.ini b/foot.ini index ae0002d4..85f3c861 100644 --- a/foot.ini +++ b/foot.ini @@ -69,7 +69,8 @@ # launch=xdg-open ${url} # label-letters=sadfjklewcmpgh # osc8-underline=url-mode -# regex=TODO +# regex=([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’]) + [cursor] From ab4426f9873eee6d83871f9bd03d2d64a5f862b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 31 Jan 2025 13:10:58 +0100 Subject: [PATCH 069/353] url-mode: regex: make sure there's always room for the NULL terminator --- url-mode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/url-mode.c b/url-mode.c index 9ba07d14..5b74771a 100644 --- a/url-mode.c +++ b/url-mode.c @@ -354,7 +354,8 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * for (size_t j = 0; j < char_len; j++) { const size_t requires_size = vline->len + char_len; - if (requires_size > vline->sz) { + /* Need to grow? Remember to save at least one byte for terminator */ + if (vline->sz == 0 || requires_size > vline->sz - 1) { const size_t new_size = requires_size * 2; vline->utf8 = xreallocarray(vline->utf8, new_size, 1); vline->map = xreallocarray(vline->map, new_size, sizeof(vline->map[0])); From f718cb3fb0d627cab8d952b94891e38238934090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 08:31:31 +0100 Subject: [PATCH 070/353] xmalloc: calling xrealloc() or xreallocarray() with a 0-size is UB in C23 And likely to in future POSIX too. --- xmalloc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xmalloc.c b/xmalloc.c index 0a67cdb2..ccfb5c48 100644 --- a/xmalloc.c +++ b/xmalloc.c @@ -32,15 +32,17 @@ xcalloc(size_t nmemb, size_t size) void * xrealloc(void *ptr, size_t size) { + xassert(size != 0); void *alloc = realloc(ptr, size); - return unlikely(size == 0) ? alloc : check_alloc(alloc); + return check_alloc(alloc); } void * xreallocarray(void *ptr, size_t n, size_t size) { + xassert(n != 0 && size != 0); void *alloc = reallocarray(ptr, n, size); - return unlikely(size == 0) ? alloc : check_alloc(alloc); + return check_alloc(alloc); } char * From 051cd6ecfc8b98e1d80b399ebea40207fe040750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 08:55:47 +0100 Subject: [PATCH 071/353] config+url: add support for user-defined regex patterns Users can now define their own regex patterns, and use them via key bindings: [regex:foo] regex=foo(bar)? launch=path-to-script-or-application {match} [key-bindings] regex-launch=[foo] Control+Shift+q regex-copy=[foo] Control+Mod1+Shift+q That is, add a section called 'regex:', followed by an identifier. Define a regex and a launcher command line. Add a key-binding, regex-launch and/or regex-copy (similar to show-urls-launch and show-urls-copy), and connect them to the regex with the "[regex-name]" syntax (similar to how the pipe-* bindings work). --- config.c | 263 ++++++++++++++++++++++++++++++++++++++++++++------ config.h | 12 +++ foot.ini | 4 +- input.c | 40 +++++++- key-binding.h | 4 +- terminal.h | 1 + url-mode.c | 29 ++++-- url-mode.h | 5 +- 8 files changed, 310 insertions(+), 48 deletions(-) diff --git a/config.c b/config.c index 1f779724..e35e4233 100644 --- a/config.c +++ b/config.c @@ -140,6 +140,8 @@ static const char *const binding_action_map[] = { [BIND_ACTION_PROMPT_NEXT] = "prompt-next", [BIND_ACTION_UNICODE_INPUT] = "unicode-input", [BIND_ACTION_QUIT] = "quit", + [BIND_ACTION_REGEX_LAUNCH] = "regex-launch", + [BIND_ACTION_REGEX_COPY] = "regex-copy", /* Mouse-specific actions */ [BIND_ACTION_SCROLLBACK_UP_MOUSE] = "scrollback-up-mouse", @@ -207,6 +209,7 @@ static_assert(ALEN(url_binding_action_map) == BIND_ACTION_URL_COUNT, struct context { struct config *conf; const char *section; + const char *section_suffix; const char *key; const char *value; @@ -257,8 +260,9 @@ log_contextual(struct context *ctx, enum log_class log_class, char *formatted_msg = xvasprintf(fmt, va); va_end(va); - bool print_dot = ctx->key != NULL; - bool print_colon = ctx->value != NULL; + const bool print_dot = ctx->key != NULL; + const bool print_colon = ctx->value != NULL; + const bool print_section_suffix = ctx->section_suffix != NULL; if (!print_dot) ctx->key = ""; @@ -266,10 +270,15 @@ log_contextual(struct context *ctx, enum log_class log_class, if (!print_colon) ctx->value = ""; + if (!print_section_suffix) + ctx->section_suffix = ""; + log_and_notify( - ctx->conf, log_class, file, lineno, "%s:%d: [%s]%s%s%s%s: %s", - ctx->path, ctx->lineno, ctx->section, print_dot ? "." : "", - ctx->key, print_colon ? ": " : "", ctx->value, formatted_msg); + ctx->conf, log_class, file, lineno, "%s:%d: [%s%s%s]%s%s%s%s: %s", + ctx->path, ctx->lineno, ctx->section, + print_section_suffix ? ":" : "", ctx->section_suffix, + print_dot ? "." : "", ctx->key, print_colon ? ": " : "", + ctx->value, formatted_msg); free(formatted_msg); } @@ -1261,6 +1270,72 @@ parse_section_url(struct context *ctx) } } +static bool +parse_section_regex(struct context *ctx) +{ + struct config *conf = ctx->conf; + const char *key = ctx->key; + + const char *regex_name = + ctx->section_suffix != NULL ? ctx->section_suffix : ""; + + struct custom_regex *regex = NULL; + tll_foreach(conf->custom_regexes, it) { + if (streq(it->item.name, regex_name)) { + regex = &it->item; + break; + } + } + + if (streq(key, "regex")) { + const char *regex_string = ctx->value; + regex_t preg; + + int r = regcomp(&preg, regex_string, REG_EXTENDED); + + if (r != 0) { + char err_buf[128]; + regerror(r, &preg, err_buf, sizeof(err_buf)); + LOG_CONTEXTUAL_ERR("invalid regex: %s", err_buf); + return false; + } + + if (regex == NULL) { + tll_push_back(conf->custom_regexes, + ((struct custom_regex){.name = xstrdup(regex_name)})); + regex = &tll_back(conf->custom_regexes); + } + + regfree(®ex->preg); + free(regex->regex); + + regex->regex = xstrdup(regex_string); + regex->preg = preg; + return true; + } + + else if (streq(key, "launch")) { + struct config_spawn_template launch; + if (!value_to_spawn_template(ctx, &launch)) + return false; + + if (regex == NULL) { + tll_push_back(conf->custom_regexes, + ((struct custom_regex){.name = xstrdup(regex_name)})); + regex = &tll_back(conf->custom_regexes); + } + + spawn_template_free(®ex->launch); + regex->launch = launch; + return true; + } + + else { + LOG_CONTEXTUAL_ERR("not a valid option: %s", key); + return false; + } +} + static bool parse_section_colors(struct context *ctx) { @@ -1602,6 +1677,7 @@ free_binding_aux(struct binding_aux *aux) case BINDING_AUX_NONE: break; case BINDING_AUX_PIPE: free_argv(&aux->pipe); break; case BINDING_AUX_TEXT: free(aux->text.data); break; + case BINDING_AUX_REGEX: free(aux->regex_name); break; } } @@ -1691,7 +1767,10 @@ binding_aux_equal(const struct binding_aux *a, case BINDING_AUX_TEXT: return a->text.len == b->text.len && - memcmp(a->text.data, b->text.data, a->text.len) == 0; + memcmp(a->text.data, b->text.data, a->text.len) == 0; + + case BINDING_AUX_REGEX: + return streq(a->regex_name, b->regex_name); } BUG("invalid AUX type: %d", a->type); @@ -1965,19 +2044,23 @@ modifiers_disjoint(const config_modifier_list_t *mods1, } static char * NOINLINE -modifiers_to_str(const config_modifier_list_t *mods) +modifiers_to_str(const config_modifier_list_t *mods, bool strip_last_plus) { - size_t len = tll_length(*mods); /* '+' , and NULL terminator */ + size_t len = tll_length(*mods); /* '+' separator */ tll_foreach(*mods, it) len += strlen(it->item); - char *ret = xmalloc(len); + char *ret = xmalloc(len + 1); size_t idx = 0; tll_foreach(*mods, it) { idx += snprintf(&ret[idx], len - idx, "%s", it->item); ret[idx++] = '+'; } - ret[--idx] = '\0'; + + if (strip_last_plus) + idx--; + + ret[idx] = '\0'; return ret; } @@ -2036,21 +2119,40 @@ pipe_argv_from_value(struct context *ctx, struct argv *argv) return remove_len; } +static ssize_t NOINLINE +regex_name_from_value(struct context *ctx, char **regex_name) +{ + *regex_name = NULL; + + if (ctx->value[0] != '[') + return 0; + + const char *regex_end = strrchr(ctx->value, ']'); + if (regex_end == NULL) { + LOG_CONTEXTUAL_ERR("unclosed '['"); + return -1; + } + + size_t regex_len = regex_end - ctx->value - 1; + *regex_name = xstrndup(&ctx->value[1], regex_len); + + ssize_t remove_len = regex_end + 1 - ctx->value; + ctx->value = regex_end + 1; + while (isspace(*ctx->value)) { + ctx->value++; + remove_len++; + } + + return remove_len; +} + + static bool NOINLINE parse_key_binding_section(struct context *ctx, int action_count, const char *const action_map[static action_count], struct config_key_binding_list *bindings) { - struct binding_aux aux; - - ssize_t pipe_remove_len = pipe_argv_from_value(ctx, &aux.pipe); - if (pipe_remove_len < 0) - return false; - - aux.type = pipe_remove_len == 0 ? BINDING_AUX_NONE : BINDING_AUX_PIPE; - aux.master_copy = true; - for (int action = 0; action < action_count; action++) { if (action_map[action] == NULL) continue; @@ -2058,6 +2160,33 @@ parse_key_binding_section(struct context *ctx, if (!streq(ctx->key, action_map[action])) continue; + struct binding_aux aux = {.type = BINDING_AUX_NONE, .master_copy = true}; + + /* TODO: this is ugly... */ + if (action_map == binding_action_map && + action >= BIND_ACTION_PIPE_SCROLLBACK && + action <= BIND_ACTION_PIPE_COMMAND_OUTPUT) + { + ssize_t pipe_remove_len = pipe_argv_from_value(ctx, &aux.pipe); + if (pipe_remove_len <= 0) + return false; + + aux.type = BINDING_AUX_PIPE; + aux.master_copy = true; + } else if (action_map == binding_action_map && + action >= BIND_ACTION_REGEX_LAUNCH && + action <= BIND_ACTION_REGEX_COPY) + { + char *regex_name = NULL; + ssize_t regex_remove_len = regex_name_from_value(ctx, ®ex_name); + if (regex_remove_len <= 0) + return false; + + aux.type = BINDING_AUX_REGEX; + aux.master_copy = true; + aux.regex_name = regex_name; + } + if (!value_to_key_combos(ctx, action, &aux, bindings, KEY_BINDING)) { free_binding_aux(&aux); return false; @@ -2067,7 +2196,6 @@ parse_key_binding_section(struct context *ctx, } LOG_CONTEXTUAL_ERR("not a valid action: %s", ctx->key); - free_binding_aux(&aux); return false; } @@ -2265,7 +2393,7 @@ resolve_key_binding_collisions(struct config *conf, const char *section_name, } if (collision_type != COLLISION_NONE) { - char *modifier_names = modifiers_to_str(mods1); + char *modifier_names = modifiers_to_str(mods1, false); char sym_name[64]; switch (type){ @@ -2307,7 +2435,7 @@ resolve_key_binding_collisions(struct config *conf, const char *section_name, case COLLISION_OVERRIDE: { char *override_names = modifiers_to_str( - &conf->mouse.selection_override_modifiers); + &conf->mouse.selection_override_modifiers, true); if (override_names[0] != '\0') override_names[strlen(override_names) - 1] = '\0'; @@ -2646,7 +2774,7 @@ parse_section_touch(struct context *ctx) { } static bool -parse_key_value(char *kv, const char **section, const char **key, const char **value) +parse_key_value(char *kv, char **section, const char **key, const char **value) { bool section_is_needed = section != NULL; @@ -2715,6 +2843,7 @@ enum section { SECTION_DESKTOP_NOTIFICATIONS, SECTION_SCROLLBACK, SECTION_URL, + SECTION_REGEX, SECTION_COLORS, SECTION_CURSOR, SECTION_MOUSE, @@ -2736,6 +2865,7 @@ typedef bool (*parser_fun_t)(struct context *ctx); static const struct { parser_fun_t fun; const char *name; + bool allow_colon_suffix; } section_info[] = { [SECTION_MAIN] = {&parse_section_main, "main"}, [SECTION_SECURITY] = {&parse_section_security, "security"}, @@ -2743,6 +2873,7 @@ static const struct { [SECTION_DESKTOP_NOTIFICATIONS] = {&parse_section_desktop_notifications, "desktop-notifications"}, [SECTION_SCROLLBACK] = {&parse_section_scrollback, "scrollback"}, [SECTION_URL] = {&parse_section_url, "url"}, + [SECTION_REGEX] = {&parse_section_regex, "regex", true}, [SECTION_COLORS] = {&parse_section_colors, "colors"}, [SECTION_CURSOR] = {&parse_section_cursor, "cursor"}, [SECTION_MOUSE] = {&parse_section_mouse, "mouse"}, @@ -2760,11 +2891,29 @@ static const struct { static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch"); static enum section -str_to_section(const char *str) +str_to_section(char *str, char **suffix) { + *suffix = NULL; + for (enum section section = SECTION_MAIN; section < SECTION_COUNT; ++section) { - if (streq(str, section_info[section].name)) + const char *name = section_info[section].name; + + if (streq(str, name)) return section; + + else if (section_info[section].allow_colon_suffix) { + const size_t str_len = strlen(str); + const size_t name_len = strlen(name); + + /* At least "section:" chars? */ + if (str_len > name_len + 1) { + if (strncmp(str, name, name_len) == 0 && str[name_len] == ':') { + str[name_len] = '\0'; + *suffix = &str[name_len + 1]; + return section; + } + } + } } return SECTION_COUNT; } @@ -2788,10 +2937,12 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar } char *section_name = xstrdup("main"); + char *section_suffix = NULL; struct context context = { .conf = conf, .section = section_name, + .section_suffix = section_suffix, .path = path, .lineno = 0, .errors_are_fatal = errors_are_fatal, @@ -2872,7 +3023,8 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar error_or_continue(); } - section = str_to_section(key_value); + char *maybe_section_suffix; + section = str_to_section(key_value, &maybe_section_suffix); if (section == SECTION_COUNT) { context.section = key_value; LOG_CONTEXTUAL_ERR("invalid section name: %s", key_value); @@ -2881,8 +3033,11 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar } free(section_name); + free(section_suffix); section_name = xstrdup(key_value); + section_suffix = maybe_section_suffix != NULL ? xstrdup(maybe_section_suffix) : NULL; context.section = section_name; + context.section_suffix = section_suffix; /* Process next line */ continue; @@ -2922,6 +3077,7 @@ parse_config_file(FILE *f, struct config *conf, const char *path, bool errors_ar done: free(section_name); + free(section_suffix); free(_line); return ret; } @@ -3016,7 +3172,6 @@ add_default_search_bindings(struct config *conf) {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_Delete}}}, {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_ALT), {{XKB_KEY_d}}}, {BIND_ACTION_SEARCH_EXTEND_CHAR, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Right}}}, - {BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_w}}}, {BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_Right}}}, {BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_w}}}, {BIND_ACTION_SEARCH_EXTEND_WORD_WS, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_w}}}, @@ -3146,6 +3301,7 @@ config_load(struct config *conf, const char *conf_path, .label_letters = xc32dup(U"sadfjklewcmpgh"), .osc8_underline = OSC8_UNDERLINE_URL_MODE, }, + .custom_regexes = tll_init(), .can_shape_grapheme = fcft_caps & FCFT_CAPABILITY_GRAPHEME_SHAPING, .scrollback = { .lines = 1000, @@ -3385,6 +3541,8 @@ bool config_override_apply(struct config *conf, config_override_t *overrides, bool errors_are_fatal) { + char *section_name = NULL; + struct context context = { .conf = conf, .path = "override", @@ -3396,8 +3554,7 @@ config_override_apply(struct config *conf, config_override_t *overrides, tll_foreach(*overrides, it) { context.lineno++; - if (!parse_key_value( - it->item, &context.section, &context.key, &context.value)) + if (!parse_key_value(it->item, §ion_name, &context.key, &context.value)) { LOG_CONTEXTUAL_ERR("syntax error: key/value pair has no %s", context.key == NULL ? "key" : "value"); @@ -3406,20 +3563,28 @@ config_override_apply(struct config *conf, config_override_t *overrides, continue; } - if (context.section[0] == '\0') { + if (section_name[0] == '\0') { LOG_CONTEXTUAL_ERR("empty section name"); if (errors_are_fatal) return false; continue; } - enum section section = str_to_section(context.section); + LOG_ERR("section-name=%s", section_name); + + char *maybe_section_suffix = NULL; + enum section section = str_to_section(section_name, &maybe_section_suffix); + + context.section = section_name; + context.section_suffix = maybe_section_suffix; + if (section == SECTION_COUNT) { - LOG_CONTEXTUAL_ERR("invalid section name: %s", context.section); + LOG_CONTEXTUAL_ERR("invalid section name: %s", section_name); if (errors_are_fatal) return false; continue; } + parser_fun_t section_parser = section_info[section].fun; xassert(section_parser != NULL); @@ -3455,6 +3620,7 @@ key_binding_list_clone(struct config_key_binding_list *dst, struct argv *last_master_argv = NULL; uint8_t *last_master_text_data = NULL; size_t last_master_text_len = 0; + char *last_master_regex_name = NULL; dst->count = src->count; dst->arr = xmalloc(src->count * sizeof(dst->arr[0])); @@ -3502,6 +3668,16 @@ key_binding_list_clone(struct config_key_binding_list *dst, } last_master_argv = NULL; break; + + case BINDING_AUX_REGEX: + if (old->aux.master_copy) { + new->aux.regex_name = xstrdup(old->aux.regex_name); + last_master_regex_name = new->aux.regex_name; + } else { + xassert(last_master_regex_name != NULL); + new->aux.regex_name = last_master_regex_name; + } + break; } } } @@ -3536,6 +3712,20 @@ config_clone(const struct config *old) conf->url.regex = xstrdup(old->url.regex); regcomp(&conf->url.preg, conf->url.regex, REG_EXTENDED); + memset(&conf->custom_regexes, 0, sizeof(conf->custom_regexes)); + tll_foreach(old->custom_regexes, it) { + const struct custom_regex *old_regex = &it->item; + + tll_push_back(conf->custom_regexes, + ((struct custom_regex){.name = xstrdup(old_regex->name), + .regex = xstrdup(old_regex->regex)})); + + + struct custom_regex *new_regex = &tll_back(conf->custom_regexes); + regcomp(&new_regex->preg, new_regex->regex, REG_EXTENDED); + spawn_template_clone(&new_regex->launch, &old_regex->launch); + } + key_binding_list_clone(&conf->bindings.key, &old->bindings.key); key_binding_list_clone(&conf->bindings.search, &old->bindings.search); key_binding_list_clone(&conf->bindings.url, &old->bindings.url); @@ -3618,6 +3808,15 @@ config_free(struct config *conf) regfree(&conf->url.preg); free(conf->url.regex); + tll_foreach(conf->custom_regexes, it) { + struct custom_regex *regex = &it->item; + free(regex->name); + free(regex->regex); + regfree(®ex->preg); + spawn_template_free(®ex->launch); + tll_remove(conf->custom_regexes, it); + } + free_key_binding_list(&conf->bindings.key); free_key_binding_list(&conf->bindings.search); free_key_binding_list(&conf->bindings.url); diff --git a/config.h b/config.h index ea0bd942..3535064e 100644 --- a/config.h +++ b/config.h @@ -61,6 +61,7 @@ enum binding_aux_type { BINDING_AUX_NONE, BINDING_AUX_PIPE, BINDING_AUX_TEXT, + BINDING_AUX_REGEX, }; struct binding_aux { @@ -74,6 +75,8 @@ struct binding_aux { uint8_t *data; size_t len; } text; + + char *regex_name; }; }; @@ -121,6 +124,13 @@ struct env_var { }; typedef tll(struct env_var) env_var_list_t; +struct custom_regex { + char *name; + char *regex; + regex_t preg; + struct config_spawn_template launch; +}; + struct config { char *term; char *shell; @@ -225,6 +235,8 @@ struct config { regex_t preg; } url; + tll(struct custom_regex) custom_regexes; + struct { uint32_t fg; uint32_t bg; diff --git a/foot.ini b/foot.ini index 85f3c861..1fb20e67 100644 --- a/foot.ini +++ b/foot.ini @@ -71,7 +71,9 @@ # osc8-underline=url-mode # regex=([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’]) - +# [regex:your-fancy-name] +# regex= +# launch= {match} [cursor] # style=block diff --git a/input.c b/input.c index c3ddbf13..916f30e4 100644 --- a/input.c +++ b/input.c @@ -349,9 +349,9 @@ execute_binding(struct seat *seat, struct terminal *term, action == BIND_ACTION_SHOW_URLS_LAUNCH ? URL_ACTION_LAUNCH : URL_ACTION_PERSISTENT; - urls_collect(term, url_action, &term->urls); + urls_collect(term, url_action, &term->conf->url.preg, true, &term->urls); urls_assign_key_combos(term->conf, &term->urls); - urls_render(term); + urls_render(term, &term->conf->url.launch); return true; } @@ -448,6 +448,42 @@ execute_binding(struct seat *seat, struct terminal *term, term_shutdown(term); return true; + case BIND_ACTION_REGEX_LAUNCH: + case BIND_ACTION_REGEX_COPY: + if (binding->aux->type != BINDING_AUX_REGEX) + return true; + + tll_foreach(term->conf->custom_regexes, it) { + const struct custom_regex *regex = &it->item; + + if (streq(regex->name, binding->aux->regex_name)) { + xassert(!urls_mode_is_active(term)); + + enum url_action url_action = action == BIND_ACTION_REGEX_LAUNCH + ? URL_ACTION_LAUNCH : URL_ACTION_COPY; + + if (regex->regex == NULL) { + LOG_ERR("regex:%s has no regex defined", regex->name); + return true; + } + if (url_action == URL_ACTION_LAUNCH && regex->launch.argv.args == NULL) { + LOG_ERR("regex:%s has no launch command defined", regex->name); + return true; + } + + urls_collect(term, url_action, ®ex->preg, false, &term->urls); + urls_assign_key_combos(term->conf, &term->urls); + urls_render(term, ®ex->launch); + return true; + } + } + + LOG_ERR( + "no regex section named '%s' defined in the configuration", + binding->aux->regex_name); + + 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 f42dbc48..5f5bb9d7 100644 --- a/key-binding.h +++ b/key-binding.h @@ -41,6 +41,8 @@ enum bind_action_normal { BIND_ACTION_PROMPT_NEXT, BIND_ACTION_UNICODE_INPUT, BIND_ACTION_QUIT, + BIND_ACTION_REGEX_LAUNCH, + BIND_ACTION_REGEX_COPY, /* Mouse specific actions - i.e. they require a mouse coordinate */ BIND_ACTION_SCROLLBACK_UP_MOUSE, @@ -54,7 +56,7 @@ enum bind_action_normal { BIND_ACTION_SELECT_QUOTE, BIND_ACTION_SELECT_ROW, - BIND_ACTION_KEY_COUNT = BIND_ACTION_QUIT + 1, + BIND_ACTION_KEY_COUNT = BIND_ACTION_REGEX_COPY + 1, BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1, }; diff --git a/terminal.h b/terminal.h index 813510fe..4242ed1d 100644 --- a/terminal.h +++ b/terminal.h @@ -789,6 +789,7 @@ struct terminal { bool urls_show_uri_on_jump_label; struct grid *url_grid_snapshot; bool ime_reenable_after_url_mode; + const struct config_spawn_template *url_launch; #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED bool ime_enabled; diff --git a/url-mode.c b/url-mode.c index 5b74771a..3108ec12 100644 --- a/url-mode.c +++ b/url-mode.c @@ -67,12 +67,13 @@ spawn_url_launcher_with_token(struct terminal *term, return false; } + xassert(term->url_launch != NULL); bool ret = false; if (spawn_expand_template( - &term->conf->url.launch, 1, - (const char *[]){"url"}, - (const char *[]){url}, + term->url_launch, 2, + (const char *[]){"url", "match"}, + (const char *[]){url, url}, &argc, &argv)) { ret = spawn( @@ -84,6 +85,8 @@ spawn_url_launcher_with_token(struct terminal *term, free(argv); } + term->url_launch = NULL; + close(dev_null); return ret; } @@ -107,6 +110,8 @@ static bool spawn_url_launcher(struct seat *seat, struct terminal *term, const char *url, uint32_t serial) { + xassert(term->url_launch != NULL); + struct spawn_activation_context *ctx = xmalloc(sizeof(*ctx)); *ctx = (struct spawn_activation_context){ .term = term, @@ -300,7 +305,8 @@ struct vline { }; static void -regex_detected(const struct terminal *term, enum url_action action, url_list_t *urls) +regex_detected(const struct terminal *term, enum url_action action, + const regex_t *preg, url_list_t *urls) { /* * Use regcomp()+regexec() to find patterns. @@ -381,8 +387,6 @@ regex_detected(const struct terminal *term, enum url_action action, url_list_t * } } - const regex_t *preg = &term->conf->url.preg; - for (size_t i = 0; i < ALEN(vlines); i++) { const struct vline *v = &vlines[i]; if (v->utf8 == NULL) @@ -523,11 +527,13 @@ remove_overlapping(url_list_t *urls, int cols) } void -urls_collect(const struct terminal *term, enum url_action action, url_list_t *urls) +urls_collect(const struct terminal *term, enum url_action action, + const regex_t *preg, bool osc8, url_list_t *urls) { xassert(tll_length(term->urls) == 0); - osc8_uris(term, action, urls); - regex_detected(term, action, urls); + if (osc8) + osc8_uris(term, action, urls); + regex_detected(term, action, preg, urls); remove_overlapping(urls, term->grid->num_cols); } @@ -710,7 +716,7 @@ tag_cells_for_url(struct terminal *term, const struct url *url, bool value) } void -urls_render(struct terminal *term) +urls_render(struct terminal *term, const struct config_spawn_template *launch) { struct wl_window *win = term->window; @@ -745,6 +751,9 @@ urls_render(struct terminal *term) /* Snapshot the current grid */ term->url_grid_snapshot = grid_snapshot(term->grid); + /* Remember which launcher to use */ + term->url_launch = launch; + xassert(tll_length(win->urls) == 0); tll_foreach(win->term->urls, it) { struct wl_url url = {.url = &it->item}; diff --git a/url-mode.h b/url-mode.h index eefe07c0..758cd92f 100644 --- a/url-mode.h +++ b/url-mode.h @@ -14,10 +14,11 @@ static inline bool urls_mode_is_active(const struct terminal *term) } void urls_collect( - const struct terminal *term, enum url_action action, url_list_t *urls); + const struct terminal *term, enum url_action action, const regex_t *preg, + bool osc8, url_list_t *urls); void urls_assign_key_combos(const struct config *conf, url_list_t *urls); -void urls_render(struct terminal *term); +void urls_render(struct terminal *term, const struct config_spawn_template *launch); void urls_reset(struct terminal *term); void urls_input(struct seat *seat, struct terminal *term, From 9d0f5cbd2ac0457ef825cb1553d68c35f76e3dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 09:05:46 +0100 Subject: [PATCH 072/353] foot.ini: improve documentation of custom regex --- foot.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/foot.ini b/foot.ini index 1fb20e67..7514c02b 100644 --- a/foot.ini +++ b/foot.ini @@ -71,9 +71,17 @@ # osc8-underline=url-mode # regex=([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’]) +# You can define your own regex's, by adding a section called +# 'regex:' with a 'regex' and 'launch' key. These can then be tied +# to a key-binding: + # [regex:your-fancy-name] # regex= # launch= {match} +# +# [key-bindings] +# regex-launch=[your-fancy-name] Control+Shift+q +# regex-copy=[your-fancy-name] Control+Alt+Shift+q [cursor] # style=block From 2f902c1f5b5cd3a0bcbc985865a533de58a2a655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 09:15:33 +0100 Subject: [PATCH 073/353] doc: foot.ini: document custom regular expressions --- doc/foot.ini.5.scd | 58 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index a23e3977..af355d68 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -755,6 +755,9 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 # SECTION: url +Note that you can also add custom regular expressions, see the 'regex' +section. + *launch* Command to execute when opening URLs. _${url}_ will be replaced with the actual URL. Default: _xdg-open ${url}_. @@ -783,11 +786,40 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 Default: _sadfjklewcmpgh_. *regex* - URL regex to use when auto-detecting URLs. The format is + + Regular expression to use when auto-detecting URLs. The format is "POSIX-Extended Regular Expressions". Default: _([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’])_ +# SECTION: regex + +Similar to the 'url' mode, but with custom defined regular expressions +(and launchers). + +To use a custom defined regular expression, you also need to add a key +binding for it. This is done in the *key-binding* section, see below +for details. In short, you need to: + +``` +[regex:foo] +regex=foo(bar)? +launch=path-to-script-or-application {match} + +[key-bindings] +regex-launch=[foo] Control+Shift+q +regex-copy=[foo] Control+Mod1+Shift+q +``` + +*launch* + Command to execute when "launching" a regex match. _${match}_ will + be replaced with the actual URL. Default: _not set_. + +*regex* + Regular expression to use when matching text. The format is + "POSIX-Extended Regular Expressions". Default: _not set_. + + # SECTION: cursor This section controls the cursor style and color. Note that @@ -1230,6 +1262,30 @@ e.g. *search-start=none*. jump label with a key sequence that will place the URL in the clipboard. Default: _none_. +*regex-launch* + Enter regex mode. This works exactly the same as URL mode; all + regex matches are tagged with a jump label with a key sequence + that will "launch" to match (and exit regex mode). + + The name of the regex section must be specified in the key + binding: + + ``` + [regex:foo] + regex=foo(bar)? + launch=path-to-script-or-application {match} + + [key-bindings] + regex-launch=[foo] Control+Shift+q + regex-copy=[foo] Control+Mod1+Shift+q + ``` + + Default: _none_. + +*regex-copy* + Same as *regex-copy*, but the match is placed in the clipboard, + instead of "launched", upon activation. Default: _none_. + *prompt-prev* Jump to the previous, currently not visible, prompt (requires shell integration, see *foot*(1)). Default: _Control+Shift+z_. From cf4324e6c64dc9f19eda8379141e06423d5c38f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 09:29:42 +0100 Subject: [PATCH 074/353] tests: config: handle regex key bindings --- tests/test-config.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/tests/test-config.c b/tests/test-config.c index 37cf4f22..c9f6586c 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -796,7 +796,7 @@ static void test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), int action, int max_action, const char *const *map, struct config_key_binding_list *bindings, - enum key_binding_type type) + enum key_binding_type type, bool need_argv, bool need_section_id) { xassert(map[action] != NULL); xassert(bindings->count == 0); @@ -808,7 +808,10 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), const bool alt = action % 3; const bool shift = action % 4; const bool super = action % 5; - const bool argv = action % 6; + const bool argv = need_argv; + const bool section_id = need_section_id; + + xassert(!(argv && section_id)); static const char *const args[] = { "command", "arg1", "arg2", "arg3 has spaces"}; @@ -847,7 +850,7 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), xkb_keysym_get_name(sym, sym_name, sizeof(sym_name)); snprintf(value, sizeof(value), "%s%s%s", - argv ? "[command arg1 arg2 \"arg3 has spaces\"] " : "", + argv ? "[command arg1 arg2 \"arg3 has spaces\"] " : section_id ? "[foobar]" : "", modifier_string, sym_name); break; } @@ -856,7 +859,7 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), const char *const button_name = button_map[button_idx].name; int chars = snprintf( value, sizeof(value), "%s%s%s", - argv ? "[command arg1 arg2 \"arg3 has spaces\"] " : "", + argv ? "[command arg1 arg2 \"arg3 has spaces\"] " : section_id ? "[foobar]" : "", modifier_string, button_name); xassert(click_count > 0); @@ -897,6 +900,18 @@ test_key_binding(struct context *ctx, bool (*parse_fun)(struct context *ctx), ctx->section, ctx->key, ctx->value, ALEN(args), binding->aux.pipe.args[ALEN(args)]); } + } else if (section_id) { + if (binding->aux.regex_name == NULL) { + BUG("[%s].%s=%s: regex name is NULL", + ctx->section, ctx->key, ctx->value); + } + + if (!streq(binding->aux.regex_name, "foobar")) { + BUG("[%s].%s=%s: regex name not the expected one: " + "expected=\"%s\", got=\"%s\"", + ctx->section, ctx->key, ctx->value, + "foobar", binding->aux.regex_name); + } } else { if (binding->aux.pipe.args != NULL) { BUG("[%s].%s=%s: pipe argv not NULL", @@ -1092,7 +1107,9 @@ test_section_key_bindings(void) test_key_binding( &ctx, &parse_section_key_bindings, action, BIND_ACTION_KEY_COUNT - 1, - binding_action_map, &conf.bindings.key, KEY_BINDING); + binding_action_map, &conf.bindings.key, KEY_BINDING, + action >= BIND_ACTION_PIPE_SCROLLBACK && action <= BIND_ACTION_PIPE_COMMAND_OUTPUT, + action >= BIND_ACTION_REGEX_LAUNCH && action <= BIND_ACTION_REGEX_COPY); } config_free(&conf); @@ -1127,7 +1144,8 @@ test_section_search_bindings(void) test_key_binding( &ctx, &parse_section_search_bindings, action, BIND_ACTION_SEARCH_COUNT - 1, - search_binding_action_map, &conf.bindings.search, KEY_BINDING); + search_binding_action_map, &conf.bindings.search, KEY_BINDING, + false, false); } config_free(&conf); @@ -1163,7 +1181,8 @@ test_section_url_bindings(void) test_key_binding( &ctx, &parse_section_url_bindings, action, BIND_ACTION_URL_COUNT - 1, - url_binding_action_map, &conf.bindings.url, KEY_BINDING); + url_binding_action_map, &conf.bindings.url, KEY_BINDING, + false, false); } config_free(&conf); @@ -1199,7 +1218,8 @@ test_section_mouse_bindings(void) test_key_binding( &ctx, &parse_section_mouse_bindings, action, BIND_ACTION_COUNT - 1, - binding_action_map, &conf.bindings.mouse, MOUSE_BINDING); + binding_action_map, &conf.bindings.mouse, MOUSE_BINDING, + false, false); } config_free(&conf); From 31f536ff8c8cb746de7d4dea2eb1402d44a4b43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 09:31:34 +0100 Subject: [PATCH 075/353] config: remove debug logging --- config.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/config.c b/config.c index e35e4233..82a4811f 100644 --- a/config.c +++ b/config.c @@ -3570,8 +3570,6 @@ config_override_apply(struct config *conf, config_override_t *overrides, continue; } - LOG_ERR("section-name=%s", section_name); - char *maybe_section_suffix = NULL; enum section section = str_to_section(section_name, &maybe_section_suffix); From a984531ce57f30bc9ec7f664e7c2e8c77b3f439e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 13:56:57 +0100 Subject: [PATCH 076/353] url-mode: use the first *sub* expression as URL When auto-matching URLs (or custom regular expressions), use the first *subexpression* as URL, rather than the while regex match. This allows us to write custom regular expressions with prefix/suffix strings that should not be included in the presented match. --- config.c | 56 +++++++++++++++++++++++++++++----------------- doc/foot.ini.5.scd | 15 +++++++++---- foot.ini | 2 +- url-mode.c | 6 ++--- 4 files changed, 51 insertions(+), 28 deletions(-) diff --git a/config.c b/config.c index 82a4811f..604c0a76 100644 --- a/config.c +++ b/config.c @@ -1256,6 +1256,12 @@ parse_section_url(struct context *ctx) return false; } + if (preg.re_nsub == 0) { + LOG_CONTEXTUAL_ERR("invalid regex: no marked subexpression(s)"); + regfree(&preg); + return false; + } + regfree(&conf->url.preg); free(conf->url.regex); @@ -1300,6 +1306,12 @@ parse_section_regex(struct context *ctx) return false; } + if (preg.re_nsub == 0) { + LOG_CONTEXTUAL_ERR("invalid regex: no marked subexpression(s)"); + regfree(&preg); + return false; + } + if (regex == NULL) { tll_push_back(conf->custom_regexes, ((struct custom_regex){.name = xstrdup(regex_name)})); @@ -3426,33 +3438,37 @@ config_load(struct config *conf, const char *conf_path, */ const char *url_regex_string = "(" - "[a-z][[:alnum:]-]+:" // protocol "(" - "/{1,3}|[a-z0-9%]" // slashes (what's the OR part for?) + "[a-z][[:alnum:]-]+:" // protocol + "(" + "/{1,3}|[a-z0-9%]" // slashes (what's the OR part for?) + ")" + "|" + "www[:digit:]{0,3}[.]" + //"|" + //"[a-z0-9.\\-]+[.][a-z]{2,4}/" /* "looks like domain name followed by a slash" - remove? */ + ")" + "(" + "[^[:space:](){}<>]+" + "|" + "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" + "|" + "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" + ")+" + "(" + "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" + "|" + "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" + "|" + "[^]\\[[:space:]`!(){};:'\".,<>?«»“”‘’]" ")" - "|" - "www[:digit:]{0,3}[.]" - //"|" - //"[a-z0-9.\\-]+[.][a-z]{2,4}/" /* "looks like domain name followed by a slash" - remove? */ - ")" - "(" - "[^[:space:](){}<>]+" - "|" - "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" - "|" - "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" - ")+" - "(" - "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" - "|" - "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" - "|" - "[^]\\[[:space:]`!(){};:'\".,<>?«»“”‘’]" ")" ; + int r = regcomp(&conf->url.preg, url_regex_string, REG_EXTENDED); xassert(r == 0); conf->url.regex = xstrdup(url_regex_string); + xassert(conf->url.preg.re_nsub >= 1); } tll_foreach(*initial_user_notifications, it) { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index af355d68..742281d4 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -786,11 +786,13 @@ section. Default: _sadfjklewcmpgh_. *regex* - Regular expression to use when auto-detecting URLs. The format is - "POSIX-Extended Regular Expressions". + "POSIX-Extended Regular Expressions". Note that the first marked + subexpression is used a the URL. In other words, if you want the + whole regex matćh to be used as an URL, surround all of it with + parenthesis: *(regex-pattern)*. - Default: _([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’])_ + Default: _(([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’]))_ # SECTION: regex @@ -817,7 +819,12 @@ regex-copy=[foo] Control+Mod1+Shift+q *regex* Regular expression to use when matching text. The format is - "POSIX-Extended Regular Expressions". Default: _not set_. + "POSIX-Extended Regular Expressions". Note that the first marked + subexpression is used a the URL. In other words, if you want the + whole regex matćh to be used as an URL, surround all of it with + parenthesis: *(regex-pattern)*. + + Default: _not set_. # SECTION: cursor diff --git a/foot.ini b/foot.ini index 7514c02b..a9a790ac 100644 --- a/foot.ini +++ b/foot.ini @@ -69,7 +69,7 @@ # launch=xdg-open ${url} # label-letters=sadfjklewcmpgh # osc8-underline=url-mode -# regex=([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’]) +# regex=(([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’])) # You can define your own regex's, by adding a section called # 'regex:' with a 'regex' and 'launch' key. These can then be tied diff --git a/url-mode.c b/url-mode.c index 3108ec12..0101de19 100644 --- a/url-mode.c +++ b/url-mode.c @@ -400,13 +400,13 @@ regex_detected(const struct terminal *term, enum url_action action, if (r == REG_NOMATCH) break; - const size_t mlen = matches[0].rm_eo - matches[0].rm_so; - const size_t start = &search_string[matches[0].rm_so] - v->utf8; + const size_t mlen = matches[1].rm_eo - matches[1].rm_so; + const size_t start = &search_string[matches[1].rm_so] - v->utf8; const size_t end = start + mlen; LOG_DBG( "regex match at row %d: %.*srow/col = %dx%d", - matches[0].rm_so, (int)mlen, &search_string[matches[0].rm_so], + matches[1].rm_so, (int)mlen, &search_string[matches[1].rm_so], v->map[start].row, v->map[start].col); tll_push_back( From 0a32dc3820af009aedbb58cfb5817b9462dbb010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 14:08:23 +0100 Subject: [PATCH 077/353] spawn template variables are on the form ${}, not {} --- doc/foot.ini.5.scd | 4 ++-- foot.ini | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 742281d4..89984ef5 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -806,7 +806,7 @@ for details. In short, you need to: ``` [regex:foo] regex=foo(bar)? -launch=path-to-script-or-application {match} +launch=path-to-script-or-application ${match} [key-bindings] regex-launch=[foo] Control+Shift+q @@ -1280,7 +1280,7 @@ e.g. *search-start=none*. ``` [regex:foo] regex=foo(bar)? - launch=path-to-script-or-application {match} + launch=path-to-script-or-application ${match} [key-bindings] regex-launch=[foo] Control+Shift+q diff --git a/foot.ini b/foot.ini index a9a790ac..2489887f 100644 --- a/foot.ini +++ b/foot.ini @@ -77,7 +77,7 @@ # [regex:your-fancy-name] # regex= -# launch= {match} +# launch= ${match} # # [key-bindings] # regex-launch=[your-fancy-name] Control+Shift+q From b1f16c84e0be8d3074139f0be56e0852acceaef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Feb 2025 10:10:10 +0100 Subject: [PATCH 078/353] doc: improve regex example --- doc/foot.ini.5.scd | 19 ++++++++++--------- foot.ini | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 89984ef5..68216fcd 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -801,16 +801,17 @@ Similar to the 'url' mode, but with custom defined regular expressions To use a custom defined regular expression, you also need to add a key binding for it. This is done in the *key-binding* section, see below -for details. In short, you need to: +for details. For example, a regex to detect hash digests (e.g. git +commit hashes) could look like: ``` -[regex:foo] -regex=foo(bar)? +[regex:hashes] +regex=([a-fA-f0-9]{7,128}) launch=path-to-script-or-application ${match} [key-bindings] -regex-launch=[foo] Control+Shift+q -regex-copy=[foo] Control+Mod1+Shift+q +regex-launch=[hashes] Control+Shift+q +regex-copy=[hashes] Control+Mod1+Shift+q ``` *launch* @@ -1278,13 +1279,13 @@ e.g. *search-start=none*. binding: ``` - [regex:foo] - regex=foo(bar)? + [regex:hashes] + regex=([a-fA-f0-9]{7,128}) launch=path-to-script-or-application ${match} [key-bindings] - regex-launch=[foo] Control+Shift+q - regex-copy=[foo] Control+Mod1+Shift+q + regex-launch=[hashes] Control+Shift+q + regex-copy=[hashes] Control+Mod1+Shift+q ``` Default: _none_. diff --git a/foot.ini b/foot.ini index 2489887f..a1aa118c 100644 --- a/foot.ini +++ b/foot.ini @@ -73,7 +73,7 @@ # You can define your own regex's, by adding a section called # 'regex:' with a 'regex' and 'launch' key. These can then be tied -# to a key-binding: +# to a key-binding. See foot.ini(5) for details # [regex:your-fancy-name] # regex= From 9e12f791c5df3ac25574894d41d606967d618888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Feb 2025 13:43:11 +0100 Subject: [PATCH 079/353] doc: regex: custom regex's aren't URLs --- doc/foot.ini.5.scd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 68216fcd..61216c75 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -788,8 +788,8 @@ section. *regex* Regular expression to use when auto-detecting URLs. The format is "POSIX-Extended Regular Expressions". Note that the first marked - subexpression is used a the URL. In other words, if you want the - whole regex matćh to be used as an URL, surround all of it with + subexpression is used as the URL. In other words, if you want the + whole regex match to be used as an URL, surround all of it with parenthesis: *(regex-pattern)*. Default: _(([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’]))_ @@ -821,8 +821,8 @@ regex-copy=[hashes] Control+Mod1+Shift+q *regex* Regular expression to use when matching text. The format is "POSIX-Extended Regular Expressions". Note that the first marked - subexpression is used a the URL. In other words, if you want the - whole regex matćh to be used as an URL, surround all of it with + subexpression is used as the match. In other words, if you want + the whole regex match to be used, surround all of it with parenthesis: *(regex-pattern)*. Default: _not set_. From 9d8021de478a82e6ea41a950afcc43388c600137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Feb 2025 13:46:00 +0100 Subject: [PATCH 080/353] changelog: custom regex's --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b750f3d..05bda33c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,11 @@ * Support for the new Wayland protocol `xdg-system-bell-v1` protocol (added in wayland-protocols 1.38), via the new config option `bell.system=no|yes` (defaults to `yes`). +* Added support for custom regex matching ([#1386][1386], + [#1872][1872]) + +[1386]: https://codeberg.org/dnkl/foot/issues/1386 +[1872]: https://codeberg.org/dnkl/foot/issues/1872 ### Changed From 88dcde3ed8b30c14a5d834d0e88f9b4a5f584939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 6 Feb 2025 07:31:30 +0100 Subject: [PATCH 081/353] term: insert-mode: handle combining characters correctly When the client application emits combining characters, for example multi-codepoint emojis, in insert-mode, we ended up pushing partial graphemes to the right, for each codepoint, resulting in too many cells (and with the wrong content) being inserted. The fix is fairly simple; don't "insert" when appending characters to an existing grapheme cluster. This isn't something we can detect easily in print_insert() (it would require us to do grapheme clustering again). Fortunately, we do have the required information in action_utf8_print(). So, pass this information as a boolean to term_print(). Closes #1947 --- CHANGELOG.md | 4 ++++ csi.c | 2 +- terminal.c | 7 ++++--- terminal.h | 3 ++- vt.c | 4 +++- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05bda33c..b9b89bf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,9 +105,13 @@ ([#1929][1929]). * Foot not closing file descriptors for unrecognized or `no_keymap` keymaps. +* Combining characters (including emojis consisting of multiple + codepoints) not being handled correctly when _insert mode_ is + enabled ([#1947][1947]). [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 +[1947]: https://codeberg.org/dnkl/foot/issues/1947 ### Security diff --git a/csi.c b/csi.c index 61cbdced..b982023c 100644 --- a/csi.c +++ b/csi.c @@ -793,7 +793,7 @@ csi_dispatch(struct terminal *term, uint8_t final) const int width = c32width(term->vt.last_printed); if (width > 0) { for (int i = 0; i < count; i++) - term_print(term, term->vt.last_printed, width); + term_print(term, term->vt.last_printed, width, false); } } break; diff --git a/terminal.c b/terminal.c index 9fd20002..b88a794e 100644 --- a/terminal.c +++ b/terminal.c @@ -3896,7 +3896,7 @@ term_fill(struct terminal *term, int r, int c, uint8_t data, size_t count, } void -term_print(struct terminal *term, char32_t wc, int width) +term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disable) { xassert(width > 0); @@ -3918,7 +3918,8 @@ term_print(struct terminal *term, char32_t wc, int width) } print_linewrap(term); - print_insert(term, width); + if (!insert_mode_disable) + print_insert(term, width); int col = grid->cursor.point.col; @@ -3990,7 +3991,7 @@ term_print(struct terminal *term, char32_t wc, int width) static void ascii_printer_generic(struct terminal *term, char32_t wc) { - term_print(term, wc, 1); + term_print(term, wc, 1, false); } static void diff --git a/terminal.h b/terminal.h index 4242ed1d..d8e7cf94 100644 --- a/terminal.h +++ b/terminal.h @@ -894,7 +894,8 @@ void term_cursor_up(struct terminal *term, int count); void term_cursor_down(struct terminal *term, int count); void term_cursor_blink_update(struct terminal *term); -void term_print(struct terminal *term, char32_t wc, int width); +void term_print(struct terminal *term, char32_t wc, int width, + bool insert_mode_disable); void term_fill(struct terminal *term, int row, int col, uint8_t c, size_t count, bool use_sgr_attrs); diff --git a/vt.c b/vt.c index 2b5eb27d..bd1cf4ca 100644 --- a/vt.c +++ b/vt.c @@ -703,6 +703,7 @@ static void action_utf8_print(struct terminal *term, char32_t wc) { int width = c32width(wc); + bool insert_mode_disable = false; const bool grapheme_clustering = term->grapheme_shaping; #if !defined(FOOT_GRAPHEME_CLUSTERING) @@ -757,6 +758,7 @@ action_utf8_print(struct terminal *term, char32_t wc) if (base_width > 0) { term->grid->cursor.point.col = col; term->grid->cursor.lcf = false; + insert_mode_disable = true; if (composed == NULL) { bool base_from_primary; @@ -954,7 +956,7 @@ action_utf8_print(struct terminal *term, char32_t wc) out: if (width > 0) - term_print(term, wc, width); + term_print(term, wc, width, insert_mode_disable); } static void From 1181f74d19f6f9e881b539ed9fdb8cc17d03f7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 24 Jan 2025 09:52:57 +0100 Subject: [PATCH 082/353] composed: re-factor: break out key calculation from vt.c --- composed.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ composed.h | 3 +++ vt.c | 24 ++---------------------- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/composed.c b/composed.c index 442325ea..7a36275e 100644 --- a/composed.c +++ b/composed.c @@ -4,6 +4,52 @@ #include #include "debug.h" +#include "terminal.h" + +uint32_t +composed_key_from_chars(const uint32_t chars[], size_t count) +{ + if (count == 0) + return 0; + + uint32_t key = chars[0]; + for (size_t i = 1; i < count; i++) + key = composed_key_from_key(key, chars[i]); + + return key; +} + +uint32_t +composed_key_from_key(uint32_t prev_key, uint32_t next_char) +{ + unsigned bits = 32 - __builtin_clz(CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO); + + /* Rotate old key 8 bits */ + uint32_t new_key = (prev_key << 8) | (prev_key >> (bits - 8)); + + /* xor with new char */ + new_key ^= next_char; + + /* Multiply with magic hash constant */ + new_key *= 2654435761ul; + + /* And mask, to ensure the new value is within range */ + new_key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO; + return new_key; +} + +UNITTEST +{ + const char32_t chars[] = U"abcdef"; + + uint32_t k1 = composed_key_from_key(chars[0], chars[1]); + uint32_t k2 = composed_key_from_chars(chars, 2); + xassert(k1 == k2); + + uint32_t k3 = composed_key_from_key(k2, chars[2]); + uint32_t k4 = composed_key_from_chars(chars, 3); + xassert(k3 == k4); +} struct composed * composed_lookup(struct composed *root, uint32_t key) diff --git a/composed.h b/composed.h index 17158407..fcaf87d4 100644 --- a/composed.h +++ b/composed.h @@ -12,6 +12,9 @@ struct composed { uint8_t width; }; +uint32_t composed_key_from_chars(const uint32_t chars[], size_t count); +uint32_t composed_key_from_key(uint32_t prev_key, uint32_t next_char); + struct composed *composed_lookup(struct composed *root, uint32_t key); void composed_insert(struct composed **root, struct composed *node); diff --git a/vt.c b/vt.c index bd1cf4ca..8f5d27d9 100644 --- a/vt.c +++ b/vt.c @@ -647,26 +647,6 @@ action_put(struct terminal *term, uint8_t c) dcs_put(term, c); } -static inline uint32_t -chain_key(uint32_t old_key, uint32_t new_wc) -{ - unsigned bits = 32 - __builtin_clz(CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO); - - /* Rotate old key 8 bits */ - uint32_t new_key = (old_key << 8) | (old_key >> (bits - 8)); - - /* xor with new char */ - new_key ^= new_wc; - - /* Multiply with magic hash constant */ - new_key *= 2654435761ul; - - /* And mask, to ensure the new value is within range */ - new_key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO; - - return new_key; -} - #if defined(FOOT_GRAPHEME_CLUSTERING) static int emoji_vs_compare(const void *_key, const void *_entry) @@ -738,9 +718,9 @@ action_utf8_print(struct terminal *term, char32_t wc) if (composed != NULL) { base = composed->chars[0]; last = composed->chars[composed->count - 1]; - key = chain_key(composed->key, wc); + key = composed_key_from_key(composed->key, wc); } else - key = chain_key(base, wc); + key = composed_key_from_key(base, wc); #if defined(FOOT_GRAPHEME_CLUSTERING) if (grapheme_clustering) { From e248e73753d61bfd24f1af8e824231434db63c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 24 Jan 2025 14:15:01 +0100 Subject: [PATCH 083/353] composed: refactor: break out lookup with collision detection --- composed.c | 37 ++++++++++++++++++++++++++++++- composed.h | 6 ++++- vt.c | 64 +++++++++++------------------------------------------- 3 files changed, 54 insertions(+), 53 deletions(-) diff --git a/composed.c b/composed.c index 7a36275e..2d9ed47d 100644 --- a/composed.c +++ b/composed.c @@ -51,7 +51,7 @@ UNITTEST xassert(k3 == k4); } -struct composed * +const struct composed * composed_lookup(struct composed *root, uint32_t key) { struct composed *node = root; @@ -66,6 +66,41 @@ composed_lookup(struct composed *root, uint32_t key) return NULL; } +const struct composed * +composed_lookup_without_collision(struct composed *root, uint32_t *key, + const char32_t *prefix_text, size_t prefix_len, + char32_t wc, int forced_width) +{ + while (true) { + const struct composed *cc = composed_lookup(root, *key); + if (cc == NULL) + return NULL; + + bool match = cc->count == prefix_len + 1 && + cc->forced_width == forced_width && + cc->chars[prefix_len] == wc; + + if (match) { + for (size_t i = 0; i < prefix_len; i++) { + if (cc->chars[i] != prefix_text[i]) { + match = false; + break; + } + } + } + + if (match) + return cc; + + (*key)++; + *key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO; + + /* TODO: this will loop infinitly if the composed table is full */ + } + + return NULL; +} + void composed_insert(struct composed **root, struct composed *node) { diff --git a/composed.h b/composed.h index fcaf87d4..18afb146 100644 --- a/composed.h +++ b/composed.h @@ -10,12 +10,16 @@ struct composed { uint32_t key; uint8_t count; uint8_t width; + uint8_t forced_width; }; uint32_t composed_key_from_chars(const uint32_t chars[], size_t count); uint32_t composed_key_from_key(uint32_t prev_key, uint32_t next_char); -struct composed *composed_lookup(struct composed *root, uint32_t key); +const struct composed *composed_lookup(struct composed *root, uint32_t key); +const struct composed *composed_lookup_without_collision( + struct composed *root, uint32_t *key, + const char32_t *prefix, size_t prefix_len, char32_t wc, int forced_width); void composed_insert(struct composed **root, struct composed *node); void composed_free(struct composed *root); diff --git a/vt.c b/vt.c index 8f5d27d9..5447493a 100644 --- a/vt.c +++ b/vt.c @@ -793,60 +793,21 @@ action_utf8_print(struct terminal *term, char32_t wc) xassert(wanted_count <= 255); - size_t collision_count = 0; - - /* Look for existing combining chain */ - while (true) { - if (unlikely(collision_count > 128)) { - static bool have_warned = false; - if (!have_warned) { - have_warned = true; - LOG_WARN("ignoring composed character: " - "too many collisions in hash table"); - } - return; - } - - const struct composed *cc = composed_lookup(term->composed, key); - if (cc == NULL) - break; - - /* - * We may have a key collisison, so need to check that - * it's a true match. If not, bump the key and try - * again. - */ - - xassert(key == cc->key); - if (cc->chars[0] != base || - cc->count != wanted_count || - cc->chars[wanted_count - 1] != wc) - { -#if 0 - LOG_WARN("COLLISION: base: %04x/%04x, count: %d/%zu, last: %04x/%04x", - cc->chars[0], base, cc->count, wanted_count, cc->chars[wanted_count - 1], wc); -#endif - key++; - key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO; - collision_count++; - continue; - } - - bool match = composed != NULL - ? memcmp(&cc->chars[1], &composed->chars[1], - (wanted_count - 2) * sizeof(cc->chars[0])) == 0 - : true; - - if (!match) { - key++; - key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO; - collision_count++; - continue; - } + /* Check if we already have a match for the entire compose chain */ + const struct composed *cc = + composed_lookup_without_collision( + term->composed, &key, + composed != NULL ? composed->chars : &(char32_t){base}, + composed != NULL ? composed->count : 1, + wc, 0); + if (cc != NULL) { + /* We *do* have a match! */ wc = CELL_COMB_CHARS_LO + cc->key; width = cc->width; goto out; + } else { + /* No match - allocate a new chain below */ } if (unlikely(term->composed_count >= @@ -867,6 +828,7 @@ action_utf8_print(struct terminal *term, char32_t wc) new_cc->count = wanted_count; new_cc->chars[0] = base; new_cc->chars[wanted_count - 1] = wc; + new_cc->forced_width = 0; if (composed != NULL) { memcpy(&new_cc->chars[1], &composed->chars[1], @@ -923,7 +885,7 @@ action_utf8_print(struct terminal *term, char32_t wc) term->composed_count++; composed_insert(&term->composed, new_cc); - wc = CELL_COMB_CHARS_LO + key; + wc = CELL_COMB_CHARS_LO + new_cc->key; width = new_cc->width; xassert(wc >= CELL_COMB_CHARS_LO); From 1111f7e918a3b41512d11023ef5bf9585fa30eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 25 Jan 2025 14:06:30 +0100 Subject: [PATCH 084/353] grid: reflow: handle composed characters longer than 2 cells The logic that tries to ensure we don't break a line in the middle of a multi-cell character was flawed when the number of cells were larger than 2. In particular, if the number of cells to copy were limited by the number of cells left on the current (new) line, and were less than the length of the multi-cell character, then we failed to insert the correct number of spacers, and also ended up misplacing the multi-cell character; instead of pushing it to the next line, it was inserted on the current line, even though it doesn't fit. Also change how trailing SPACER cells are rendered (cells that are "fillers" at then end of a line, when a multi-column character was pushed over to the next line): don't copy the previous cell's attributes (which may be wrong anyway), use default attributes instead. --- grid.c | 8 +++----- terminal.c | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/grid.c b/grid.c index b7c0447c..eb24869d 100644 --- a/grid.c +++ b/grid.c @@ -1052,7 +1052,7 @@ grid_resize_and_reflow( */ while ( unlikely( - amount > 1 && + amount > 0 && from + amount < old_cols && old_row->cells[from + amount].wc >= CELL_SPACER + 1)) { @@ -1061,7 +1061,7 @@ grid_resize_and_reflow( } xassert( - amount == 1 || + amount <= 1 || old_row->cells[from + amount - 1].wc <= CELL_SPACER + 1); } @@ -1084,11 +1084,9 @@ grid_resize_and_reflow( if (unlikely(spacers > 0)) { xassert(new_col_idx + spacers == new_cols); - const struct cell *cell = &old_row->cells[from - 1]; - for (int i = 0; i < spacers; i++, new_col_idx++) { new_row->cells[new_col_idx].wc = CELL_SPACER; - new_row->cells[new_col_idx].attrs = cell->attrs; + new_row->cells[new_col_idx].attrs = (struct attributes){0}; } } } diff --git a/terminal.c b/terminal.c index b88a794e..bf70a37e 100644 --- a/terminal.c +++ b/terminal.c @@ -3826,7 +3826,7 @@ print_spacer(struct terminal *term, int col, int remaining) struct cell *cell = &row->cells[col]; cell->wc = CELL_SPACER + remaining; - cell->attrs = term->vt.attrs; + cell->attrs = (struct attributes){0}; } /* From 7a8d2b5e012636def9545075e01cdf9a6f309355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 25 Jan 2025 14:09:35 +0100 Subject: [PATCH 085/353] osc: wip: kitty text size protocol This brings initial support for the new kitty text-sizing protocol. Note hat only the width-parameter ('w') is supported. That is, no font scaling, and no multi-line cells. For now, only explicit widths are supported. That is, w=0 does not yet work. There are a couple of changes to the renderer, to handle e.g. OSC 66 ; w=6 ; foobar ST There are two ways this can get rendered, depending on whether grapheme shaping has been enabled. We either shape it, and get an array of glyphs back that we render. Or, we rasterize each codepoint ourselves, and render each resulting glyph. The two cases ends up in two different renderer loops, that worked somewhat different. In particular, the first case has probably never been tested/used at all... With this patch, both are changed, and now uses some heuristic to differentiate between multi-cell text strings (like in the example above), or single-cell combining characters. The difference is mainly in which offset to use for the secondary glyphs. In a multi-cell string, each glyph is mapped to its own cell, while in the combining case, we try to map all glyphs to the same cell. --- osc.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- render.c | 37 +++++++++++++++++--------- 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/osc.c b/osc.c index e335dc61..6d8bb40c 100644 --- a/osc.c +++ b/osc.c @@ -610,7 +610,6 @@ verify_kitty_id_is_valid(const char *id) } UNIGNORE_WARNINGS - static void kitty_notification(struct terminal *term, char *string) { @@ -1135,6 +1134,82 @@ out: free(sound_name); } +static void +kitty_text_size(struct terminal *term, char *string) +{ + char *text = strchr(string, ';'); + if (text == NULL) + return; + + char *parameters = string; + *text = '\0'; + text++; + + char32_t *wchars = ambstoc32(text); + if (wchars == NULL) + return; + + int width = 0; + + char *ctx = NULL; + for (char *param = strtok_r(parameters, ":", &ctx); + param != NULL; + param = strtok_r(NULL, ":", &ctx)) + { + /* All parameters are on the form X=value, where X is always + exactly one character */ + if (param[0] == '\0' || param[1] != '=') + continue; + + char *value = ¶m[2]; + + switch (param[0]) { + case 'w': { + errno = 0; + char *end = NULL; + unsigned long w = strtoul(value, &end, 10); + + if (*end == '\0' && errno == 0 && w <= 7) { + width = (int)w; + break; + } else + LOG_ERR("OSC-66: invalid 'w' value, ignoring"); + break; + } + + case 's': + case 'n': + case 'd': + case 'v': + LOG_WARN("OSC-66: unsupported: '%c' parameter, ignoring", param[0]); + break; + } + } + + const size_t len = c32len(wchars); + uint32_t key = composed_key_from_chars(wchars, len); + + const struct composed *composed = composed_lookup_without_collision( + term->composed, &key, wchars, len - 1, wchars[len - 1], width); + + if (composed == NULL) { + struct composed *new_cc = xmalloc(sizeof(*new_cc)); + new_cc->chars = wchars; + new_cc->count = len; + new_cc->key = key; + new_cc->width = width; + new_cc->forced_width = width; + + term->composed_count++; + composed_insert(&term->composed, new_cc); + composed = new_cc; + } else if (composed->width == width) { + free(wchars); + } + + term_print(term, CELL_COMB_CHARS_LO + composed->key, composed->forced_width > 0 ? composed->forced_width : composed->width); +} + void osc_dispatch(struct terminal *term) { @@ -1371,6 +1446,10 @@ osc_dispatch(struct terminal *term) osc_selection(term, string); break; + case 66: /* text-size protocol (kitty) */ + kitty_text_size(term, string); + break; + case 99: /* Kitty notifications */ kitty_notification(term, string); break; diff --git a/render.c b/render.c index 0cca0643..13e9d708 100644 --- a/render.c +++ b/render.c @@ -869,11 +869,16 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag } if (grapheme != NULL) { - cell_cols = composed->width; + const int forced_width = composed->forced_width; + + cell_cols = forced_width > 0 ? forced_width : composed->width; composed = NULL; glyphs = grapheme->glyphs; glyph_count = grapheme->count; + + if (forced_width > 0) + glyph_count = min(glyph_count, forced_width); } } @@ -890,7 +895,9 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag } else { glyph_count = 1; glyphs = &single; - cell_cols = single->cols; + + const size_t forced_width = composed != NULL ? composed->forced_width : 0; + cell_cols = forced_width > 0 ? forced_width : single->cols; } } } @@ -972,7 +979,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag int g_x = glyph->x; int g_y = glyph->y; - if (i > 0 && glyph->x >= 0) + if (i > 0 && glyph->x >= 0 && cell_cols == 1) g_x -= term->cell_width; if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) { @@ -993,9 +1000,9 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag if (composed != NULL) { assert(glyph_count == 1); - for (size_t i = 1; i < composed->count; i++) { + for (size_t j = 1; j < composed->count; j++) { const struct fcft_glyph *g = fcft_rasterize_char_utf32( - font, composed->chars[i], term->font_subpixel); + font, composed->chars[j], term->font_subpixel); if (g == NULL) continue; @@ -1017,22 +1024,26 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag * somewhat deal with double-width glyphs we use * an offset of *one* cell. */ - int x_ofs = g->x < 0 - ? cell_cols * term->cell_width - : (cell_cols - 1) * term->cell_width; + int x_ofs = cell_cols == 1 + ? g->x < 0 + ? cell_cols * term->cell_width + : (cell_cols - 1) * term->cell_width + : 0; + + if (cell_cols > 1) + pen_x += term->cell_width; pixman_image_composite32( PIXMAN_OP_OVER, clr_pix, g->pix, pix, 0, 0, 0, 0, /* Some fonts use a negative offset, while others use a * "normal" offset */ - pen_x + x_ofs + g->x, - y + term->font_baseline - g->y, - g->width, g->height); + pen_x + letter_x_ofs + x_ofs + g->x, + y + term->font_baseline - g->y, g->width, g->height); } } } - pen_x += glyph->advance.x; + pen_x += cell_cols > 1 ? term->cell_width : glyph->advance.x; } pixman_image_unref(clr_pix); @@ -4398,7 +4409,7 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) } /* Don't shrink grid too much */ - const int min_cols = 2; + const int min_cols = 7; const int min_rows = 1; /* Minimum window size (must be divisible by the scaling factor)*/ From d3f692990ef66f550bb3a0ade2e84107cfbeca47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 26 Jan 2025 07:33:53 +0100 Subject: [PATCH 086/353] term+vt: refactor: move "utf8" char processing to term_process_and_print_non_ascii() This function "prints" any non-ascii character (i.e. any character that ends up in the action_utf8_print() function in vt.c) to the grid. This includes grapheme cluster processing etc. action_utf8_print() now simply calls this function. This allows us to re-use the same functionality from other places (like the text-sizing protocol). --- osc.c | 5 +- terminal.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++ terminal.h | 1 + vt.c | 251 +--------------------------------------------------- 4 files changed, 261 insertions(+), 251 deletions(-) diff --git a/osc.c b/osc.c index 6d8bb40c..49bdba67 100644 --- a/osc.c +++ b/osc.c @@ -1207,7 +1207,10 @@ kitty_text_size(struct terminal *term, char *string) free(wchars); } - term_print(term, CELL_COMB_CHARS_LO + composed->key, composed->forced_width > 0 ? composed->forced_width : composed->width); + term_print( + term, CELL_COMB_CHARS_LO + composed->key, + composed->forced_width > 0 ? composed->forced_width : composed->width, + false); } void diff --git a/terminal.c b/terminal.c index bf70a37e..96a215ba 100644 --- a/terminal.c +++ b/terminal.c @@ -27,6 +27,7 @@ #include "commands.h" #include "config.h" #include "debug.h" +#include "emoji-variation-sequences.h" #include "extract.h" #include "grid.h" #include "ime.h" @@ -4073,6 +4074,260 @@ term_single_shift(struct terminal *term, enum charset_designator idx) term->ascii_printer = &ascii_printer_single_shift; } +#if defined(FOOT_GRAPHEME_CLUSTERING) +static int +emoji_vs_compare(const void *_key, const void *_entry) +{ + const struct emoji_vs *key = _key; + const struct emoji_vs *entry = _entry; + + uint32_t cp = key->start; + + if (cp < entry->start) + return -1; + else if (cp > entry->end) + return 1; + else + return 0; +} + +UNITTEST +{ + /* Verify the emoji_vs list is sorted */ + int64_t last_end = -1; + + for (size_t i = 0; i < sizeof(emoji_vs) / sizeof(emoji_vs[0]); i++) { + const struct emoji_vs *vs = &emoji_vs[i]; + xassert(vs->start <= vs->end); + xassert(vs->start > last_end); + xassert(vs->vs15 || vs->vs16); + last_end = vs->end; + } +} +#endif + +void +term_process_and_print_non_ascii(struct terminal *term, char32_t wc) +{ + int width = c32width(wc); + bool insert_mode_disable = false; + const bool grapheme_clustering = term->grapheme_shaping; + +#if !defined(FOOT_GRAPHEME_CLUSTERING) + xassert(!grapheme_clustering); +#endif + + if (term->grid->cursor.point.col > 0 && + (grapheme_clustering || + (!grapheme_clustering && width == 0 && wc >= 0x300))) + { + int col = term->grid->cursor.point.col; + if (!term->grid->cursor.lcf) + col--; + + /* Skip past spacers */ + struct row *row = term->grid->cur_row; + while (row->cells[col].wc >= CELL_SPACER && col > 0) + col--; + + xassert(col >= 0 && col < term->cols); + char32_t base = row->cells[col].wc; + char32_t UNUSED last = base; + + /* Is base cell already a cluster? */ + const struct composed *composed = + (base >= CELL_COMB_CHARS_LO && base <= CELL_COMB_CHARS_HI) + ? composed_lookup(term->composed, base - CELL_COMB_CHARS_LO) + : NULL; + + uint32_t key; + + if (composed != NULL) { + base = composed->chars[0]; + last = composed->chars[composed->count - 1]; + key = composed_key_from_key(composed->key, wc); + } else + key = composed_key_from_key(base, wc); + +#if defined(FOOT_GRAPHEME_CLUSTERING) + if (grapheme_clustering) { + /* Check if we're on a grapheme cluster break */ + if (utf8proc_grapheme_break_stateful( + last, wc, &term->vt.grapheme_state)) + { + term_reset_grapheme_state(term); + goto out; + } + } +#endif + + int base_width = c32width(base); + if (base_width > 0) { + term->grid->cursor.point.col = col; + term->grid->cursor.lcf = false; + insert_mode_disable = true; + + if (composed == NULL) { + bool base_from_primary; + bool comb_from_primary; + bool pre_from_primary; + + char32_t precomposed = term->fonts[0] != NULL + ? fcft_precompose( + term->fonts[0], base, wc, &base_from_primary, + &comb_from_primary, &pre_from_primary) + : (char32_t)-1; + + int precomposed_width = c32width(precomposed); + + /* + * Only use the pre-composed character if: + * + * 1. we *have* a pre-composed character + * 2. the width matches the base characters width + * 3. it's in the primary font, OR one of the base or + * combining characters are *not* from the primary + * font + */ + + if (precomposed != (char32_t)-1 && + precomposed_width == base_width && + (pre_from_primary || + !base_from_primary || + !comb_from_primary)) + { + wc = precomposed; + width = precomposed_width; + term_reset_grapheme_state(term); + goto out; + } + } + + size_t wanted_count = composed != NULL ? composed->count + 1 : 2; + if (wanted_count > 255) { + xassert(composed != NULL); + +#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG + LOG_WARN("combining character overflow:"); + LOG_WARN(" base: 0x%04x", composed->chars[0]); + for (size_t i = 1; i < composed->count; i++) + LOG_WARN(" cc: 0x%04x", composed->chars[i]); + LOG_ERR(" new: 0x%04x", wc); +#endif + /* This is going to break anyway... */ + wanted_count--; + } + + xassert(wanted_count <= 255); + + /* Check if we already have a match for the entire compose chain */ + const struct composed *cc = + composed_lookup_without_collision( + term->composed, &key, + composed != NULL ? composed->chars : &(char32_t){base}, + composed != NULL ? composed->count : 1, + wc, 0); + + if (cc != NULL) { + /* We *do* have a match! */ + wc = CELL_COMB_CHARS_LO + cc->key; + width = cc->width; + goto out; + } else { + /* No match - allocate a new chain below */ + } + + if (unlikely(term->composed_count >= + (CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO))) + { + /* We reached our maximum number of allowed composed + * character chains. Fall through here and print the + * current zero-width character to the current cell */ + LOG_WARN("maximum number of composed characters reached"); + term_reset_grapheme_state(term); + goto out; + } + + /* Allocate new chain */ + struct composed *new_cc = xmalloc(sizeof(*new_cc)); + new_cc->chars = xmalloc(wanted_count * sizeof(new_cc->chars[0])); + new_cc->key = key; + new_cc->count = wanted_count; + new_cc->chars[0] = base; + new_cc->chars[wanted_count - 1] = wc; + new_cc->forced_width = 0; + + if (composed != NULL) { + memcpy(&new_cc->chars[1], &composed->chars[1], + (wanted_count - 2) * sizeof(new_cc->chars[0])); + } + + const int grapheme_width = + composed != NULL ? composed->width : base_width; + + switch (term->conf->tweak.grapheme_width_method) { + case GRAPHEME_WIDTH_MAX: + new_cc->width = max(grapheme_width, width); + break; + + case GRAPHEME_WIDTH_DOUBLE: + new_cc->width = min(grapheme_width + width, 2); + +#if defined(FOOT_GRAPHEME_CLUSTERING) + /* Handle VS-15 and VS-16 variation selectors */ + if (unlikely(grapheme_clustering && + (wc == 0xfe0e || wc == 0xfe0f) && + new_cc->count == 2)) + { + const struct emoji_vs *vs = + bsearch( + &(struct emoji_vs){.start = new_cc->chars[0]}, + emoji_vs, sizeof(emoji_vs) / sizeof(emoji_vs[0]), + sizeof(struct emoji_vs), + &emoji_vs_compare); + + if (vs != NULL) { + xassert(new_cc->chars[0] >= vs->start && + new_cc->chars[0] <= vs->end); + + /* Force a grapheme width of 1 for VS-15, and 2 for VS-16 */ + if (wc == 0xfe0e) { + if (vs->vs15) + new_cc->width = 1; + } else if (wc == 0xfe0f) { + if (vs->vs16) + new_cc->width = 2; + } + } + } +#endif + + break; + + case GRAPHEME_WIDTH_WCSWIDTH: + new_cc->width = grapheme_width + width; + break; + } + + term->composed_count++; + composed_insert(&term->composed, new_cc); + + wc = CELL_COMB_CHARS_LO + new_cc->key; + width = new_cc->width; + + xassert(wc >= CELL_COMB_CHARS_LO); + xassert(wc <= CELL_COMB_CHARS_HI); + goto out; + } + } else + term_reset_grapheme_state(term); + + +out: + if (width > 0) + term_print(term, wc, width, insert_mode_disable); +} + enum term_surface term_surface_kind(const struct terminal *term, const struct wl_surface *surface) { diff --git a/terminal.h b/terminal.h index d8e7cf94..a69a8d0f 100644 --- a/terminal.h +++ b/terminal.h @@ -894,6 +894,7 @@ void term_cursor_up(struct terminal *term, int count); void term_cursor_down(struct terminal *term, int count); void term_cursor_blink_update(struct terminal *term); +void term_process_and_print_non_ascii(struct terminal *term, char32_t wc); void term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disable); void term_fill(struct terminal *term, int row, int col, uint8_t c, size_t count, diff --git a/vt.c b/vt.c index 5447493a..9c758c55 100644 --- a/vt.c +++ b/vt.c @@ -16,7 +16,6 @@ #include "csi.h" #include "dcs.h" #include "debug.h" -#include "emoji-variation-sequences.h" #include "osc.h" #include "sixel.h" #include "util.h" @@ -647,258 +646,10 @@ action_put(struct terminal *term, uint8_t c) dcs_put(term, c); } -#if defined(FOOT_GRAPHEME_CLUSTERING) -static int -emoji_vs_compare(const void *_key, const void *_entry) -{ - const struct emoji_vs *key = _key; - const struct emoji_vs *entry = _entry; - - uint32_t cp = key->start; - - if (cp < entry->start) - return -1; - else if (cp > entry->end) - return 1; - else - return 0; -} - -UNITTEST -{ - /* Verify the emoji_vs list is sorted */ - int64_t last_end = -1; - - for (size_t i = 0; i < sizeof(emoji_vs) / sizeof(emoji_vs[0]); i++) { - const struct emoji_vs *vs = &emoji_vs[i]; - xassert(vs->start <= vs->end); - xassert(vs->start > last_end); - xassert(vs->vs15 || vs->vs16); - last_end = vs->end; - } -} -#endif - static void action_utf8_print(struct terminal *term, char32_t wc) { - int width = c32width(wc); - bool insert_mode_disable = false; - const bool grapheme_clustering = term->grapheme_shaping; - -#if !defined(FOOT_GRAPHEME_CLUSTERING) - xassert(!grapheme_clustering); -#endif - - if (term->grid->cursor.point.col > 0 && - (grapheme_clustering || - (!grapheme_clustering && width == 0 && wc >= 0x300))) - { - int col = term->grid->cursor.point.col; - if (!term->grid->cursor.lcf) - col--; - - /* Skip past spacers */ - struct row *row = term->grid->cur_row; - while (row->cells[col].wc >= CELL_SPACER && col > 0) - col--; - - xassert(col >= 0 && col < term->cols); - char32_t base = row->cells[col].wc; - char32_t UNUSED last = base; - - /* Is base cell already a cluster? */ - const struct composed *composed = - (base >= CELL_COMB_CHARS_LO && base <= CELL_COMB_CHARS_HI) - ? composed_lookup(term->composed, base - CELL_COMB_CHARS_LO) - : NULL; - - uint32_t key; - - if (composed != NULL) { - base = composed->chars[0]; - last = composed->chars[composed->count - 1]; - key = composed_key_from_key(composed->key, wc); - } else - key = composed_key_from_key(base, wc); - -#if defined(FOOT_GRAPHEME_CLUSTERING) - if (grapheme_clustering) { - /* Check if we're on a grapheme cluster break */ - if (utf8proc_grapheme_break_stateful( - last, wc, &term->vt.grapheme_state)) - { - term_reset_grapheme_state(term); - goto out; - } - } -#endif - - int base_width = c32width(base); - if (base_width > 0) { - term->grid->cursor.point.col = col; - term->grid->cursor.lcf = false; - insert_mode_disable = true; - - if (composed == NULL) { - bool base_from_primary; - bool comb_from_primary; - bool pre_from_primary; - - char32_t precomposed = term->fonts[0] != NULL - ? fcft_precompose( - term->fonts[0], base, wc, &base_from_primary, - &comb_from_primary, &pre_from_primary) - : (char32_t)-1; - - int precomposed_width = c32width(precomposed); - - /* - * Only use the pre-composed character if: - * - * 1. we *have* a pre-composed character - * 2. the width matches the base characters width - * 3. it's in the primary font, OR one of the base or - * combining characters are *not* from the primary - * font - */ - - if (precomposed != (char32_t)-1 && - precomposed_width == base_width && - (pre_from_primary || - !base_from_primary || - !comb_from_primary)) - { - wc = precomposed; - width = precomposed_width; - term_reset_grapheme_state(term); - goto out; - } - } - - size_t wanted_count = composed != NULL ? composed->count + 1 : 2; - if (wanted_count > 255) { - xassert(composed != NULL); - -#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG - LOG_WARN("combining character overflow:"); - LOG_WARN(" base: 0x%04x", composed->chars[0]); - for (size_t i = 1; i < composed->count; i++) - LOG_WARN(" cc: 0x%04x", composed->chars[i]); - LOG_ERR(" new: 0x%04x", wc); -#endif - /* This is going to break anyway... */ - wanted_count--; - } - - xassert(wanted_count <= 255); - - /* Check if we already have a match for the entire compose chain */ - const struct composed *cc = - composed_lookup_without_collision( - term->composed, &key, - composed != NULL ? composed->chars : &(char32_t){base}, - composed != NULL ? composed->count : 1, - wc, 0); - - if (cc != NULL) { - /* We *do* have a match! */ - wc = CELL_COMB_CHARS_LO + cc->key; - width = cc->width; - goto out; - } else { - /* No match - allocate a new chain below */ - } - - if (unlikely(term->composed_count >= - (CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO))) - { - /* We reached our maximum number of allowed composed - * character chains. Fall through here and print the - * current zero-width character to the current cell */ - LOG_WARN("maximum number of composed characters reached"); - term_reset_grapheme_state(term); - goto out; - } - - /* Allocate new chain */ - struct composed *new_cc = xmalloc(sizeof(*new_cc)); - new_cc->chars = xmalloc(wanted_count * sizeof(new_cc->chars[0])); - new_cc->key = key; - new_cc->count = wanted_count; - new_cc->chars[0] = base; - new_cc->chars[wanted_count - 1] = wc; - new_cc->forced_width = 0; - - if (composed != NULL) { - memcpy(&new_cc->chars[1], &composed->chars[1], - (wanted_count - 2) * sizeof(new_cc->chars[0])); - } - - const int grapheme_width = - composed != NULL ? composed->width : base_width; - - switch (term->conf->tweak.grapheme_width_method) { - case GRAPHEME_WIDTH_MAX: - new_cc->width = max(grapheme_width, width); - break; - - case GRAPHEME_WIDTH_DOUBLE: - new_cc->width = min(grapheme_width + width, 2); - -#if defined(FOOT_GRAPHEME_CLUSTERING) - /* Handle VS-15 and VS-16 variation selectors */ - if (unlikely(grapheme_clustering && - (wc == 0xfe0e || wc == 0xfe0f) && - new_cc->count == 2)) - { - const struct emoji_vs *vs = - bsearch( - &(struct emoji_vs){.start = new_cc->chars[0]}, - emoji_vs, sizeof(emoji_vs) / sizeof(emoji_vs[0]), - sizeof(struct emoji_vs), - &emoji_vs_compare); - - if (vs != NULL) { - xassert(new_cc->chars[0] >= vs->start && - new_cc->chars[0] <= vs->end); - - /* Force a grapheme width of 1 for VS-15, and 2 for VS-16 */ - if (wc == 0xfe0e) { - if (vs->vs15) - new_cc->width = 1; - } else if (wc == 0xfe0f) { - if (vs->vs16) - new_cc->width = 2; - } - } - } -#endif - - break; - - case GRAPHEME_WIDTH_WCSWIDTH: - new_cc->width = grapheme_width + width; - break; - } - - term->composed_count++; - composed_insert(&term->composed, new_cc); - - wc = CELL_COMB_CHARS_LO + new_cc->key; - width = new_cc->width; - - xassert(wc >= CELL_COMB_CHARS_LO); - xassert(wc <= CELL_COMB_CHARS_HI); - goto out; - } - } else - term_reset_grapheme_state(term); - - -out: - if (width > 0) - term_print(term, wc, width, insert_mode_disable); + term_process_and_print_non_ascii(term, wc); } static void From 1260004330359619113aac3cb3ea1a7fe2fddb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 26 Jan 2025 07:36:11 +0100 Subject: [PATCH 087/353] osc: text-sizing: implement w=0, plus optimize single-codepoint cases If there's a single codepoint in the text portion of the OSC sequence, and its calculated width matches the forced width, print it directly to the grid instead of emitting a combining character. When w=0, we split up the text string "as we normally would". Since we don't support any other text-sizing parameters, this means simply printing each codepoint to the grid. --- osc.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/osc.c b/osc.c index 49bdba67..f6398165 100644 --- a/osc.c +++ b/osc.c @@ -1149,7 +1149,7 @@ kitty_text_size(struct terminal *term, char *string) if (wchars == NULL) return; - int width = 0; + int forced_width = 0; char *ctx = NULL; for (char *param = strtok_r(parameters, ":", &ctx); @@ -1170,7 +1170,7 @@ kitty_text_size(struct terminal *term, char *string) unsigned long w = strtoul(value, &end, 10); if (*end == '\0' && errno == 0 && w <= 7) { - width = (int)w; + forced_width = (int)w; break; } else LOG_ERR("OSC-66: invalid 'w' value, ignoring"); @@ -1187,10 +1187,57 @@ kitty_text_size(struct terminal *term, char *string) } const size_t len = c32len(wchars); + + if (forced_width == 0) { + /* + * w=0 means we split the text up as we'd normally do... Since + * we don't support any other parameters of the text-sizing + * protocol, that means we just process the string as if it + * has been printed without this OSC. + */ + for (size_t i = 0; i < len; i++) + term_process_and_print_non_ascii(term, wchars[i]); + free(wchars); + return; + } + + size_t max_cp_width = 0; + size_t all_cp_width = 0; + + for (size_t i = 0; i < len; i++) { + const size_t cp_width = c32width(wchars[i]); + all_cp_width += cp_width; + max_cp_width = max(max_cp_width, cp_width); + } + + size_t calculated_width = 0; + switch (term->conf->tweak.grapheme_width_method) { + case GRAPHEME_WIDTH_WCSWIDTH: calculated_width = all_cp_width; break; + case GRAPHEME_WIDTH_MAX: calculated_width = max_cp_width; break; + case GRAPHEME_WIDTH_DOUBLE: calculated_width = min(max_cp_width, 2); break; + } + + const size_t width = forced_width == 0 ? calculated_width : forced_width; + + LOG_DBG("len=%zu, forced=%d, calculated=%zu, using=%zu", + len, forced_width, calculated_width, width); + + if (len == 1 && calculated_width == forced_width) { + /* + * Optimization: if there's a single codepoint, and either + * w=0, or the 'w' matches the calculated width, print + * codepoint directly instead of creating a combining + * character. + */ + term_print(term, wchars[0], width); + free(wchars); + return; + } + uint32_t key = composed_key_from_chars(wchars, len); const struct composed *composed = composed_lookup_without_collision( - term->composed, &key, wchars, len - 1, wchars[len - 1], width); + term->composed, &key, wchars, len - 1, wchars[len - 1], forced_width); if (composed == NULL) { struct composed *new_cc = xmalloc(sizeof(*new_cc)); @@ -1198,7 +1245,7 @@ kitty_text_size(struct terminal *term, char *string) new_cc->count = len; new_cc->key = key; new_cc->width = width; - new_cc->forced_width = width; + new_cc->forced_width = forced_width; term->composed_count++; composed_insert(&term->composed, new_cc); From 3998f8570caaf7d92cecc3b29b76c3351087930b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 07:35:10 +0100 Subject: [PATCH 088/353] composed: codespell: infinitely --- composed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composed.c b/composed.c index 2d9ed47d..fc7dfa00 100644 --- a/composed.c +++ b/composed.c @@ -95,7 +95,7 @@ composed_lookup_without_collision(struct composed *root, uint32_t *key, (*key)++; *key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO; - /* TODO: this will loop infinitly if the composed table is full */ + /* TODO: this will loop infinitely if the composed table is full */ } return NULL; From ed35a238d62473d77729748ca6d39ab3f3d42602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 27 Jan 2025 10:12:26 +0100 Subject: [PATCH 089/353] doc: ctlseq: add OSC 66 (kitty text sizing) --- doc/foot-ctlseqs.7.scd | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index f8eb1222..6c702738 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -729,7 +729,10 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_. : Copy _Pd_ (base64 encoded text) to the clipboard. _Pc_ denotes the target: *c* targets the clipboard and *s* and *p* the primary selection. -| \\E] 99 ; _params_ ; _payload_ \\E\\ +| \\E] 66 ; _params_ ; text \\E\\ +: kitty +: Text sizing protocol (only 'w', width, supported) +| \\E] 99 ; _params_ ; _payload_ \\E\\ : kitty : Desktop notification; uses *desktop-notifications.command* in *foot.ini*(5). From 0f93766614bde055ce61e1b7ffc8f6b5aeef91d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 15:30:00 +0100 Subject: [PATCH 090/353] osc: text-size: disable optimization The optimization prevents the forced-width to be set on the new combining character, causing issues when followed by more zero-width codepoints. --- osc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osc.c b/osc.c index f6398165..eaf6e33e 100644 --- a/osc.c +++ b/osc.c @@ -1222,6 +1222,7 @@ kitty_text_size(struct terminal *term, char *string) LOG_DBG("len=%zu, forced=%d, calculated=%zu, using=%zu", len, forced_width, calculated_width, width); +#if 0 if (len == 1 && calculated_width == forced_width) { /* * Optimization: if there's a single codepoint, and either @@ -1233,6 +1234,7 @@ kitty_text_size(struct terminal *term, char *string) free(wchars); return; } +#endif uint32_t key = composed_key_from_chars(wchars, len); From 98402040977435b0029dbe4952c083e65eb8ef69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 3 Feb 2025 15:31:03 +0100 Subject: [PATCH 091/353] term: print-non-ascii: propagate existing forced-width When appending to an existing composed character, "inherit" its forced width, if set. Also make sure to actually _use_ the forced width, if set, rather than the calculated width. This fixes an issue when appending zero-width codepoints to a forced-width combining character. --- terminal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index 96a215ba..c8e49663 100644 --- a/terminal.c +++ b/terminal.c @@ -4255,7 +4255,7 @@ term_process_and_print_non_ascii(struct terminal *term, char32_t wc) new_cc->count = wanted_count; new_cc->chars[0] = base; new_cc->chars[wanted_count - 1] = wc; - new_cc->forced_width = 0; + new_cc->forced_width = composed != NULL ? composed->forced_width : 0; if (composed != NULL) { memcpy(&new_cc->chars[1], &composed->chars[1], @@ -4313,7 +4313,7 @@ term_process_and_print_non_ascii(struct terminal *term, char32_t wc) composed_insert(&term->composed, new_cc); wc = CELL_COMB_CHARS_LO + new_cc->key; - width = new_cc->width; + width = new_cc->forced_width > 0 ? new_cc->forced_width : new_cc->width; xassert(wc >= CELL_COMB_CHARS_LO); xassert(wc <= CELL_COMB_CHARS_HI); From d7e8f29ee24365a5aeeca4460afa228a32308e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Feb 2025 11:36:53 +0100 Subject: [PATCH 092/353] grid: reflow: get number of spacers to insert from the old grid When checking if we're breaking in the middle of a multi-column character, we counted spacers starting from the break point. But, the character may be wider than that. Use the fact that the spacers cells encode how many *more* there are after them; when we get to the first one, we know exactly how wide the character is. --- grid.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grid.c b/grid.c index eb24869d..2dc4fcd5 100644 --- a/grid.c +++ b/grid.c @@ -1056,8 +1056,8 @@ grid_resize_and_reflow( from + amount < old_cols && old_row->cells[from + amount].wc >= CELL_SPACER + 1)) { + spacers = old_row->cells[from + amount].wc - CELL_SPACER + 1; amount--; - spacers++; } xassert( From a3a404a2570b72636da0583492d9cf87b700ef6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Feb 2025 11:38:29 +0100 Subject: [PATCH 093/353] render: resize: note why min_cols=7 --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 13e9d708..9ff9c681 100644 --- a/render.c +++ b/render.c @@ -4409,7 +4409,7 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) } /* Don't shrink grid too much */ - const int min_cols = 7; + const int min_cols = 7; /* See OSC-66 */ const int min_rows = 1; /* Minimum window size (must be divisible by the scaling factor)*/ From 8d20b82721ac95cea89a3ec87c8ec32e00f224e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 6 Feb 2025 14:02:04 +0100 Subject: [PATCH 094/353] changelog: text-sizing protocol --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b89bf3..c707d5a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,8 +65,9 @@ * Support for the new Wayland protocol `xdg-system-bell-v1` protocol (added in wayland-protocols 1.38), via the new config option `bell.system=no|yes` (defaults to `yes`). -* Added support for custom regex matching ([#1386][1386], +* Support for custom regex matching ([#1386][1386], [#1872][1872]) +* Support for kitty's text-sizing protocol (`w`, width, only), OSC-66. [1386]: https://codeberg.org/dnkl/foot/issues/1386 [1872]: https://codeberg.org/dnkl/foot/issues/1872 From 325086291b1e2c31322780377abb0759e9870489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 07:43:52 +0100 Subject: [PATCH 095/353] config: regex: fix invalid free Zero-initialize the 'launch' spawn template before calling value_to_spawn_template(). This is needed since value_to_spawn_template() tries to free the old value before assigning the new one. Closes #1951 --- config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.c b/config.c index 604c0a76..7a12cb1a 100644 --- a/config.c +++ b/config.c @@ -1327,7 +1327,7 @@ parse_section_regex(struct context *ctx) } else if (streq(key, "launch")) { - struct config_spawn_template launch; + struct config_spawn_template launch = {NULL}; if (!value_to_spawn_template(ctx, &launch)) return false; From 4e5ad6e013666e4724b4de4273ac445adceb0865 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sun, 9 Feb 2025 09:11:27 +0100 Subject: [PATCH 096/353] Fix URL detection regression on lines with NUL bytes Commit 859b4c89 (url-mode: wip: more work on regex matching, 2025-01-30) regressed URL detection in weechat. Some of the URLs still work but others don't. This is because regexec() stops at the first NUL, thus skipping the rest of the line. weechat seems create NUL cells between their UI widgets. Work around this by replacing NUL with space. This is probably correct because selecting and copying those cells also translates to space (not sure where in the code). --- url-mode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/url-mode.c b/url-mode.c index 0101de19..6fd7f03a 100644 --- a/url-mode.c +++ b/url-mode.c @@ -368,7 +368,8 @@ regex_detected(const struct terminal *term, enum url_action action, vline->sz = new_size; } - vline->utf8[vline->len + j] = buf[j]; + vline->utf8[vline->len + j] = + (buf[j] == '\0') ? ' ' : buf[j]; vline->map[vline->len + j] = (struct coord){c, term->grid->view + r}; } From 98db9658136cfc41b496b005c9df537f59aed6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 08:54:42 +0100 Subject: [PATCH 097/353] url-mode: terminate last virtual line before regex matching If the last line doesn't have a hard linebreak, it was never NULL terminated, causing regexec() to crash on an out-of-bounds access. --- url-mode.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/url-mode.c b/url-mode.c index 6fd7f03a..00d38d75 100644 --- a/url-mode.c +++ b/url-mode.c @@ -388,10 +388,14 @@ regex_detected(const struct terminal *term, enum url_action action, } } + /* Terminate the last line, if necessary */ + if (vline->len > 0 && vline->utf8[vline->len - 1] != '\0') + vline->utf8[vline->len++] = '\0'; + for (size_t i = 0; i < ALEN(vlines); i++) { const struct vline *v = &vlines[i]; if (v->utf8 == NULL) - continue;; + continue; const char *search_string = v->utf8; while (true) { From 26acf41d1391afbded51137e02113c5ef9ab1edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 09:08:14 +0100 Subject: [PATCH 098/353] grid: pull in misc.h when TIME_REFLOW is defined --- grid.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/grid.c b/grid.c index 2dc4fcd5..6e52464c 100644 --- a/grid.c +++ b/grid.c @@ -16,6 +16,10 @@ #define TIME_REFLOW 0 +#if defined(TIME_REFLOW) +#include "misc.h" +#endif + /* * "sb" (scrollback relative) coordinates * From fce755aafe3f5fabc417992378c81ea86c22f698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 12:58:35 +0100 Subject: [PATCH 099/353] forgejo: better names for templates --- .forgejo/issue_template/{issue_template.yml => bug.yml} | 0 .../issue_template/{issue_template.yaml => feature_request.yml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .forgejo/issue_template/{issue_template.yml => bug.yml} (100%) rename .forgejo/issue_template/{issue_template.yaml => feature_request.yml} (100%) diff --git a/.forgejo/issue_template/issue_template.yml b/.forgejo/issue_template/bug.yml similarity index 100% rename from .forgejo/issue_template/issue_template.yml rename to .forgejo/issue_template/bug.yml diff --git a/.forgejo/issue_template/issue_template.yaml b/.forgejo/issue_template/feature_request.yml similarity index 100% rename from .forgejo/issue_template/issue_template.yaml rename to .forgejo/issue_template/feature_request.yml From 970d95c5a1e15a6db397984fce5735239fb6fc23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 13:08:33 +0100 Subject: [PATCH 100/353] doc: foot.ini: fix 'hashes' regex example It's A-F, not A-f --- doc/foot.ini.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 61216c75..b38189b1 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -806,7 +806,7 @@ commit hashes) could look like: ``` [regex:hashes] -regex=([a-fA-f0-9]{7,128}) +regex=([a-fA-F0-9]{7,128}) launch=path-to-script-or-application ${match} [key-bindings] @@ -1280,7 +1280,7 @@ e.g. *search-start=none*. ``` [regex:hashes] - regex=([a-fA-f0-9]{7,128}) + regex=([a-fA-F0-9]{7,128}) launch=path-to-script-or-application ${match} [key-bindings] From c63202ee0ee45182ec5ab440262e63b85104e59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 13:09:07 +0100 Subject: [PATCH 101/353] url-mode: regex: don't try to NULL-terminate an invalid vline --- url-mode.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/url-mode.c b/url-mode.c index 00d38d75..f04550f8 100644 --- a/url-mode.c +++ b/url-mode.c @@ -389,8 +389,11 @@ regex_detected(const struct terminal *term, enum url_action action, } /* Terminate the last line, if necessary */ - if (vline->len > 0 && vline->utf8[vline->len - 1] != '\0') + if (vline_idx < ALEN(vlines) && + vline->len > 0 && vline->utf8[vline->len - 1] != '\0') + { vline->utf8[vline->len++] = '\0'; + } for (size_t i = 0; i < ALEN(vlines); i++) { const struct vline *v = &vlines[i]; From 3d66db63ccb1baab2d78e6d104a9c15d3f6684d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 08:57:51 +0100 Subject: [PATCH 102/353] grid: refactor reflow We've been trying to performance optimize reflow by "chunking" cells; try to gather as many as possible, and memcpy a chunk at once. The problem is that a) this quickly becomes very complex, and b) is very hard to get right for multi-column characters, especially when we need to truncate long ones due to the window being too small. Refactor, and once again walk and copy all cells one by one. This is slower, but at least it's correct. --- grid.c | 231 ++++++++++++++++++++----------------------------------- grid.h | 2 +- render.c | 6 +- 3 files changed, 88 insertions(+), 151 deletions(-) diff --git a/grid.c b/grid.c index 6e52464c..0e15a151 100644 --- a/grid.c +++ b/grid.c @@ -816,7 +816,7 @@ tp_cmp(const void *_a, const void *_b) void grid_resize_and_reflow( - struct grid *grid, int new_rows, int new_cols, + struct grid *grid, const struct terminal *term, int new_rows, int new_cols, int old_screen_rows, int new_screen_rows, size_t tracking_points_count, struct coord *const _tracking_points[static tracking_points_count]) @@ -960,7 +960,7 @@ grid_resize_and_reflow( /* Does this row have any URIs? */ struct row_range *uri_range, *uri_range_terminator; struct row_range *underline_range, *underline_range_terminator; - struct row_data *extra = old_row->extra; + const struct row_data *extra = old_row->extra; if (extra != NULL && extra->uri_ranges.count > 0) { uri_range = &extra->uri_ranges.v[0]; @@ -984,181 +984,118 @@ grid_resize_and_reflow( } else underline_range = underline_range_terminator = NULL; - for (int start = 0, left = col_count; left > 0;) { - int end; - bool tp_break = false; - bool uri_break = false; - bool underline_break = false; - bool ftcs_break = false; + for (int c = 0; c < col_count; c++) { + const struct cell *old = &old_row->cells[c]; - /* Figure out where to end this chunk */ - { - const int uri_col = uri_range != uri_range_terminator - ? ((uri_range->start >= start ? uri_range->start : uri_range->end) + 1) - : INT_MAX; - const int underline_col = underline_range != underline_range_terminator - ? ((underline_range->start >= start ? underline_range->start : underline_range->end) + 1) - : INT_MAX; - const int tp_col = tp != NULL ? tp->col + 1 : INT_MAX; - const int ftcs_col = old_row->shell_integration.cmd_start >= start - ? old_row->shell_integration.cmd_start + 1 - : old_row->shell_integration.cmd_end >= start - ? old_row->shell_integration.cmd_end + 1 - : INT_MAX; + /* Row full, emit newline and get a new, fresh, row */ + xassert(new_col_idx <= new_cols); + if (unlikely(new_col_idx >= new_cols)) + line_wrap(); - end = min(col_count, min(min(tp_col, min(uri_col, underline_col)), ftcs_col)); + char32_t wc = old->wc; + int width = 1; - uri_break = end == uri_col; - underline_break = end == underline_col; - tp_break = end == tp_col; - ftcs_break = end == ftcs_col; + if (unlikely(wc >= CELL_COMB_CHARS_LO && wc <= CELL_COMB_CHARS_HI)) { + const struct composed *composed = + composed_lookup(term->composed, wc - CELL_COMB_CHARS_LO); + + width = composed->forced_width > 0 ? composed->forced_width : composed->width; + } else if (unlikely(c + 1 < col_count && (old + 1)->wc >= CELL_SPACER + 1)) { + /* Wide character, get its width from the next cell's + SPACER value */ + width = (old + 1)->wc - CELL_SPACER + 1; } - int cols = end - start; - xassert(cols > 0); - xassert(start + cols <= old_cols); - /* - * Copy the row chunk to the new grid. Note that there may - * be fewer cells left on the new row than what we have in - * the chunk. I.e. the chunk may have to be split up into - * multiple memcpy:ies. - */ - - for (int count = cols, from = start; count > 0;) { - xassert(new_col_idx <= new_cols); - int new_row_cells_left = new_cols - new_col_idx; - - /* Row full, emit newline and get a new, fresh, row */ - if (new_row_cells_left <= 0) { - line_wrap(); - new_row_cells_left = new_cols; - } - - /* Number of cells we can copy */ - int amount = min(count, new_row_cells_left); - xassert(amount > 0); - - /* - * If we're going to reach the end of the new row, we - * need to make sure we don't end in the middle of a - * multi-column character. - */ - int spacers = 0; - if (new_col_idx + amount >= new_cols) { - /* - * While the cell *after* the last cell is a CELL_SPACER - * - * This means we have a multi-column character - * that doesn't fit on the current row. We need to - * push it to the next row, and insert CELL_SPACER - * cells as padding. - */ - while ( - unlikely( - amount > 0 && - from + amount < old_cols && - old_row->cells[from + amount].wc >= CELL_SPACER + 1)) - { - spacers = old_row->cells[from + amount].wc - CELL_SPACER + 1; - amount--; - } - - xassert( - amount <= 1 || - old_row->cells[from + amount - 1].wc <= CELL_SPACER + 1); - } - - xassert(new_col_idx + amount <= new_cols); - xassert(from + amount <= old_cols); - - if (from == 0) - new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker; - - memcpy( - &new_row->cells[new_col_idx], &old_row->cells[from], - amount * sizeof(struct cell)); - - count -= amount; - from += amount; - new_col_idx += amount; - - xassert(new_col_idx <= new_cols); - - if (unlikely(spacers > 0)) { - xassert(new_col_idx + spacers == new_cols); - - for (int i = 0; i < spacers; i++, new_col_idx++) { - new_row->cells[new_col_idx].wc = CELL_SPACER; - new_row->cells[new_col_idx].attrs = (struct attributes){0}; - } + * Check if character fits, if not, emit spacers, and push + the character to the next row */ + if (unlikely(new_col_idx + width > new_cols && width <= new_cols)) { + for (; new_col_idx < new_cols; new_col_idx++) { + new_row->cells[new_col_idx].wc = CELL_SPACER; + new_row->cells[new_col_idx].attrs = (struct attributes){0}; } + line_wrap(); } - xassert(new_col_idx > 0); + if (unlikely(c == 0)) + new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker; - if (tp_break) { - do { - xassert(tp != NULL); - xassert(tp->row == old_row_idx); - xassert(tp->col == end - 1); + new_row->cells[new_col_idx] = *old; - tp->row = new_row_idx; - tp->col = new_col_idx - 1; - - next_tp++; - tp = *next_tp; - } while (tp->row == old_row_idx && tp->col == end - 1); - - if (tp->row != old_row_idx) - tp = NULL; - - LOG_DBG("next TP (tp=%p): %dx%d", - (void*)tp, (*next_tp)->row, (*next_tp)->col); - } - - if (uri_break) { - xassert(uri_range != NULL); - - if (uri_range->start == end - 1) + if (unlikely(uri_range != uri_range_terminator)) { + if (uri_range->start == c) { reflow_range_start( - uri_range, ROW_RANGE_URI, new_row, new_col_idx - 1); + uri_range, ROW_RANGE_URI, new_row, new_col_idx); + } - if (uri_range->end == end - 1) { + if (uri_range->end == c) { reflow_range_end( - uri_range, ROW_RANGE_URI, new_row, new_col_idx - 1); + uri_range, ROW_RANGE_URI, new_row, new_col_idx); grid_row_uri_range_destroy(uri_range); uri_range++; } } - if (underline_break) { - xassert(underline_range != NULL); - - if (underline_range->start == end - 1) + if (unlikely(underline_range != underline_range_terminator)) { + if (underline_range->start == c) { reflow_range_start( - underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx - 1); + underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx); + } - if (underline_range->end == end - 1) { + if (underline_range->end == c) { reflow_range_end( - underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx - 1); + underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx); grid_row_underline_range_destroy(underline_range); underline_range++; } } - if (ftcs_break) { - xassert(old_row->shell_integration.cmd_start == start + cols - 1 || - old_row->shell_integration.cmd_end == start + cols - 1); + if (unlikely(tp != NULL)) { + if (tp->col == c) { + do { + xassert(tp->row == old_row_idx); - if (old_row->shell_integration.cmd_start == start + cols - 1) - new_row->shell_integration.cmd_start = new_col_idx - 1; - if (old_row->shell_integration.cmd_end == start + cols - 1) - new_row->shell_integration.cmd_end = new_col_idx - 1; + tp->row = new_row_idx; + tp->col = new_col_idx; + + next_tp++; + tp = *next_tp; + } while (tp->row == old_row_idx && tp->col == c); + + if (tp->row != old_row_idx) + tp = NULL; + + LOG_DBG("next TP (tp=%p): %dx%d", + (void*)tp, (*next_tp)->row, (*next_tp)->col); + } } - left -= cols; - start += cols; + if (unlikely(old_row->shell_integration.cmd_start >= 0)) { + if (old_row->shell_integration.cmd_start == c) { + new_row->shell_integration.cmd_start = new_col_idx; + } else if (old_row->shell_integration.cmd_end == c) { + new_row->shell_integration.cmd_end = new_col_idx; + } + } + + new_col_idx++; + + if (unlikely(width > 1)) { + if (unlikely(width > new_cols)) { + /* Wide character no longer fits on a row, replace + it with a single space */ + new_row->cells[new_col_idx - 1].wc = 0; + + /* Walk past the SPACER cells */ + for (int i = 1; i < width; i++, c++, old++) + ; + } else { + /* Copy spacers */ + xassert(new_col_idx + width - 1 <= new_cols); + for (int i = 1; i < width; i++, c++) + new_row->cells[new_col_idx++] = *(++old); + } + } } if (old_row->linebreak) { diff --git a/grid.h b/grid.h index de8f98ab..71bdc29e 100644 --- a/grid.h +++ b/grid.h @@ -16,7 +16,7 @@ void grid_resize_without_reflow( int old_screen_rows, int new_screen_rows); void grid_resize_and_reflow( - struct grid *grid, int new_rows, int new_cols, + struct grid *grid, const struct terminal *term, int new_rows, int new_cols, int old_screen_rows, int new_screen_rows, size_t tracking_points_count, struct coord *const _tracking_points[static tracking_points_count]); diff --git a/render.c b/render.c index 9ff9c681..3c09f7bb 100644 --- a/render.c +++ b/render.c @@ -4190,7 +4190,7 @@ delayed_reflow_of_normal_grid(struct terminal *term) /* Reflow the original (since before the resize was started) grid, * to the *current* dimensions */ grid_resize_and_reflow( - term->interactive_resizing.grid, + term->interactive_resizing.grid, term, term->interactive_resizing.new_rows, term->normal.num_cols, term->interactive_resizing.old_screen_rows, term->rows, term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, @@ -4409,7 +4409,7 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) } /* Don't shrink grid too much */ - const int min_cols = 7; /* See OSC-66 */ + const int min_cols = 2; const int min_rows = 1; /* Minimum window size (must be divisible by the scaling factor)*/ @@ -4691,7 +4691,7 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) }; grid_resize_and_reflow( - &term->normal, new_normal_grid_rows, new_cols, old_normal_rows, new_rows, + &term->normal, term, new_normal_grid_rows, new_cols, old_normal_rows, new_rows, term->selection.coords.end.row >= 0 ? ALEN(tracking_points) : 0, tracking_points); } From 6a181c9f72e16b629e99dead328bcd3eb10c044d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 12:00:51 +0100 Subject: [PATCH 103/353] grid: performance: check for non-NULL before comparing with terminator This should be slightly faster in the normal(?) case (no styled underlines or OSC-8). --- grid.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grid.c b/grid.c index 0e15a151..5888cdc1 100644 --- a/grid.c +++ b/grid.c @@ -1022,7 +1022,7 @@ grid_resize_and_reflow( new_row->cells[new_col_idx] = *old; - if (unlikely(uri_range != uri_range_terminator)) { + if (unlikely(uri_range != NULL && uri_range != uri_range_terminator)) { if (uri_range->start == c) { reflow_range_start( uri_range, ROW_RANGE_URI, new_row, new_col_idx); @@ -1036,7 +1036,7 @@ grid_resize_and_reflow( } } - if (unlikely(underline_range != underline_range_terminator)) { + if (unlikely(underline_range != NULL && underline_range != underline_range_terminator)) { if (underline_range->start == c) { reflow_range_start( underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx); From eced7cf1d6e5ea8cc1f569d022ddea938cd907a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 12:38:11 +0100 Subject: [PATCH 104/353] grid: reflow: don't special case the first cell in a multi-column character Wrap *all* cell copying logic in a for-loop for the characters width. This _should_, in theory, mean reflow of e.g. cursor coordinates in the middle of a multi-column character works correctly. Also fix reflow of cmd start/end integration. --- grid.c | 118 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 60 insertions(+), 58 deletions(-) diff --git a/grid.c b/grid.c index 5888cdc1..2b537ba9 100644 --- a/grid.c +++ b/grid.c @@ -984,7 +984,7 @@ grid_resize_and_reflow( } else underline_range = underline_range_terminator = NULL; - for (int c = 0; c < col_count; c++) { + for (int c = 0; c < col_count;) { const struct cell *old = &old_row->cells[c]; /* Row full, emit newline and get a new, fresh, row */ @@ -1017,84 +1017,86 @@ grid_resize_and_reflow( line_wrap(); } - if (unlikely(c == 0)) - new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker; + new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker; - new_row->cells[new_col_idx] = *old; + for (int i = 0; i < width; i++) { + if (unlikely(uri_range != NULL && uri_range != uri_range_terminator)) { + if (unlikely(uri_range->start == c)) { + reflow_range_start( + uri_range, ROW_RANGE_URI, new_row, new_col_idx); + } - if (unlikely(uri_range != NULL && uri_range != uri_range_terminator)) { - if (uri_range->start == c) { - reflow_range_start( - uri_range, ROW_RANGE_URI, new_row, new_col_idx); + if (unlikely(uri_range->end == c)) { + reflow_range_end( + uri_range, ROW_RANGE_URI, new_row, new_col_idx); + grid_row_uri_range_destroy(uri_range); + uri_range++; + } } - if (uri_range->end == c) { - reflow_range_end( - uri_range, ROW_RANGE_URI, new_row, new_col_idx); - grid_row_uri_range_destroy(uri_range); - uri_range++; - } - } + if (unlikely(underline_range != NULL && underline_range != underline_range_terminator)) { + if (unlikely(underline_range->start == c)) { + reflow_range_start( + underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx); + } - if (unlikely(underline_range != NULL && underline_range != underline_range_terminator)) { - if (underline_range->start == c) { - reflow_range_start( - underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx); + if (unlikely(underline_range->end == c)) { + reflow_range_end( + underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx); + grid_row_underline_range_destroy(underline_range); + underline_range++; + } } - if (underline_range->end == c) { - reflow_range_end( - underline_range, ROW_RANGE_UNDERLINE, new_row, new_col_idx); - grid_row_underline_range_destroy(underline_range); - underline_range++; + if (unlikely(tp != NULL)) { + if (unlikely(tp->col == c)) { + do { + xassert(tp->row == old_row_idx); + + tp->row = new_row_idx; + tp->col = new_col_idx; + + next_tp++; + tp = *next_tp; + } while (tp->row == old_row_idx && tp->col == c); + + if (tp->row != old_row_idx) + tp = NULL; + + LOG_DBG("next TP (tp=%p): %dx%d", + (void*)tp, (*next_tp)->row, (*next_tp)->col); + } } - } - if (unlikely(tp != NULL)) { - if (tp->col == c) { - do { - xassert(tp->row == old_row_idx); - - tp->row = new_row_idx; - tp->col = new_col_idx; - - next_tp++; - tp = *next_tp; - } while (tp->row == old_row_idx && tp->col == c); - - if (tp->row != old_row_idx) - tp = NULL; - - LOG_DBG("next TP (tp=%p): %dx%d", - (void*)tp, (*next_tp)->row, (*next_tp)->col); - } - } - - if (unlikely(old_row->shell_integration.cmd_start >= 0)) { - if (old_row->shell_integration.cmd_start == c) { + if (unlikely(old_row->shell_integration.cmd_start >= 0 && + old_row->shell_integration.cmd_start == c)) + { new_row->shell_integration.cmd_start = new_col_idx; - } else if (old_row->shell_integration.cmd_end == c) { + } + + if (unlikely(old_row->shell_integration.cmd_end >= 0 && + old_row->shell_integration.cmd_end == c)) + { new_row->shell_integration.cmd_end = new_col_idx; } - } - new_col_idx++; - - if (unlikely(width > 1)) { if (unlikely(width > new_cols)) { /* Wide character no longer fits on a row, replace it with a single space */ - new_row->cells[new_col_idx - 1].wc = 0; + new_row->cells[new_col_idx++].wc = 0; + c++; /* Walk past the SPACER cells */ for (int i = 1; i < width; i++, c++, old++) ; - } else { - /* Copy spacers */ - xassert(new_col_idx + width - 1 <= new_cols); - for (int i = 1; i < width; i++, c++) - new_row->cells[new_col_idx++] = *(++old); + + /* Continue with next character in the *old* grid */ + break; } + + new_row->cells[new_col_idx++] = *old; + old++; + c++; } } From 8b63869f5707a771e60ae745d1069860c9056cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 12:42:29 +0100 Subject: [PATCH 105/353] render: minimum window size: 2 cols -> 1 col --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index 3c09f7bb..cf4f303e 100644 --- a/render.c +++ b/render.c @@ -4409,7 +4409,7 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) } /* Don't shrink grid too much */ - const int min_cols = 2; + const int min_cols = 1; const int min_rows = 1; /* Minimum window size (must be divisible by the scaling factor)*/ From 7445471238bd5b78cdfce82b0e49f5bf876a0c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Feb 2025 12:46:31 +0100 Subject: [PATCH 106/353] grid: reflow: shell integration: no need to check for >= 0 --- grid.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/grid.c b/grid.c index 2b537ba9..ceaeb230 100644 --- a/grid.c +++ b/grid.c @@ -1068,17 +1068,11 @@ grid_resize_and_reflow( } } - if (unlikely(old_row->shell_integration.cmd_start >= 0 && - old_row->shell_integration.cmd_start == c)) - { + if (unlikely(old_row->shell_integration.cmd_start == c)) new_row->shell_integration.cmd_start = new_col_idx; - } - if (unlikely(old_row->shell_integration.cmd_end >= 0 && - old_row->shell_integration.cmd_end == c)) - { + if (unlikely(old_row->shell_integration.cmd_end == c)) new_row->shell_integration.cmd_end = new_col_idx; - } if (unlikely(width > new_cols)) { /* Wide character no longer fits on a row, replace From 888a6770da4f9f1c138b5d0ed6ec9b1d577337ca Mon Sep 17 00:00:00 2001 From: Ludovico Gerardi Date: Thu, 6 Feb 2025 10:13:25 +0100 Subject: [PATCH 107/353] themes: update Tokyo Night Light --- CHANGELOG.md | 1 + themes/tokyonight-day | 21 --------------------- themes/tokyonight-light | 25 +++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 21 deletions(-) delete mode 100644 themes/tokyonight-day create mode 100644 themes/tokyonight-light diff --git a/CHANGELOG.md b/CHANGELOG.md index c707d5a6..4b63bdc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ ([#1925][1925]). * Auto-detection of URLs (i.e. not OSC-8 based URLs) are now regex based. +* Rename Tokyo Night Day theme to Tokyo Night Light and update colors. [1925]: https://codeberg.org/dnkl/foot/issues/1925 diff --git a/themes/tokyonight-day b/themes/tokyonight-day deleted file mode 100644 index 5143aa07..00000000 --- a/themes/tokyonight-day +++ /dev/null @@ -1,21 +0,0 @@ -# -*- conf -*- - -[colors] -background=e1e2e7 -foreground=3760bf -regular0=e9e9ed -regular1=f52a65 -regular2=587539 -regular3=8c6c3e -regular4=2e7de9 -regular5=9854f1 -regular6=007197 -regular7=6172b0 -bright0=a1a6c5 -bright1=f52a65 -bright2=587539 -bright3=8c6c3e -bright4=2e7de9 -bright5=9854f1 -bright6=007197 -bright7=3760bf \ No newline at end of file diff --git a/themes/tokyonight-light b/themes/tokyonight-light new file mode 100644 index 00000000..ffcae689 --- /dev/null +++ b/themes/tokyonight-light @@ -0,0 +1,25 @@ +# -*- conf -*- + +# Reference: https://github.com/tokyo-night/tokyo-night-vscode-theme/blob/master/themes/tokyo-night-light-color-theme.json + +[colors] +background=d6d8df +foreground=343b58 +regular0=343b58 +regular1=8c4351 +regular2=33635c +regular3=8f5e15 +regular4=2959aa +regular5=7b43ba +regular6=006c86 +regular7=707280 +bright0=343b58 +bright1=8c4351 +bright2=33635c +bright3=8f5e15 +bright4=2959aa +bright5=7b43ba +bright6=006c86 +bright7=707280 + +jump-labels=343b58 e19d37 # brighter yellow than regular3 From d7a4f9e99e7e3517d339df53eee3fe6b9677f9c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 13 Feb 2025 08:00:50 +0100 Subject: [PATCH 108/353] grid: reflow: fix cursor reflow when LCF is set When the cursor is at the end of the line, with a pending wrap (LCF set), the lcf flag should be cleared *and* the cursor moved one cell to the right. Before this patch, we cleared LCF, but didn't move the cursor. Closes #1954 --- CHANGELOG.md | 3 +++ grid.c | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b63bdc8..8a090062 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -110,10 +110,13 @@ * Combining characters (including emojis consisting of multiple codepoints) not being handled correctly when _insert mode_ is enabled ([#1947][1947]). +* Reflow of the cursor (active + saved) when at the end of the line + with a pending wrap (LCF set) ([#1954][1954]). [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 [1947]: https://codeberg.org/dnkl/foot/issues/1947 +[1954]: https://codeberg.org/dnkl/foot/issues/1954 ### Security diff --git a/grid.c b/grid.c index ceaeb230..2deb9111 100644 --- a/grid.c +++ b/grid.c @@ -1211,15 +1211,26 @@ grid_resize_and_reflow( saved_cursor.row = min(saved_cursor.row, new_screen_rows - 1); saved_cursor.col = min(saved_cursor.col, new_cols - 1); + if (grid->cursor.lcf) { + if (cursor.col + 1 < new_cols) { + cursor.col++; + grid->cursor.lcf = false; + } + } + + if (grid->saved_cursor.lcf) { + if (saved_cursor.col + 1 < new_cols) { + saved_cursor.col++; + grid->saved_cursor.lcf = false; + } + } + grid->cur_row = new_grid[(grid->offset + cursor.row) & (new_rows - 1)]; xassert(grid->cur_row != NULL); grid->cursor.point = cursor; grid->saved_cursor.point = saved_cursor; - grid->cursor.lcf = false; - grid->saved_cursor.lcf = false; - /* Free sixels we failed to "map" to the new grid */ tll_foreach(untranslated_sixels, it) sixel_destroy(&it->item); From 4abbaf134535da823c45949bbe1f3904caf5e085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 16 Feb 2025 09:11:52 +0100 Subject: [PATCH 109/353] doc: foot.ini: font: add one more fontfeatures example Add a fontfeatures example where we: * set multiple features * assign a value to the features (as opposed to just enabling a boolean feature) --- doc/foot.ini.5.scd | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index b38189b1..87673233 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -58,9 +58,16 @@ empty string to be set, but it must be quoted: *KEY=""*) - Dina:weight=bold:slant=italic - Courier New:size=12 - Fantasque Sans Mono:fontfeatures=ss01 + - Iosevka:fontfeatures=cv01=1:fontfeatures=cv06=1 - Meslo LG S:size=12, Noto Color Emoji:size=12 - Courier New:pixelsize=8 + Be aware that, depending on your setup, there may be global + FontConfig options that overrides options set here. If an option + appears to have no effect, ensure there is no global configuration + file that sets the same option with *assign* or *assign_replace*; + use one of the many *append* or possibly *prepend* modes. + For each option, the first font is the primary font. The remaining fonts are fallback fonts that will be used whenever a glyph cannot be found in the primary font. From 76503fb86a8b8a6b5c3ce1be87c15a55af38508d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 16 Feb 2025 07:25:25 +0100 Subject: [PATCH 110/353] term: append zero-width grapheme breaking characters to previous cell When compiled with grapheme clustering support, zero-width characters that also are grapheme breaks, were ignored (not stored in the grid). When utf8proc says the character is a grapheme break, we try to print the character to the current cell. But this is only done when width > 0. As a result, zero width grapheme breaks were simply discarded. This only happens when grapheme clustering is enabled; when disabled, all zero width characters are appended. Fix this by also requiring the width to be non-zero when if we should append the character or not. Closes #1960 --- CHANGELOG.md | 4 ++++ terminal.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a090062..7cc5e13f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,11 +112,15 @@ enabled ([#1947][1947]). * Reflow of the cursor (active + saved) when at the end of the line with a pending wrap (LCF set) ([#1954][1954]). +* Zero-width characters that also are grapheme breaks (e.g. U+200B, + ZERO WIDTH SPACE) being ignored (discarded and never stored in the + grid) ([#1960][1960]). [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 [1947]: https://codeberg.org/dnkl/foot/issues/1947 [1954]: https://codeberg.org/dnkl/foot/issues/1954 +[1960]: https://codeberg.org/dnkl/foot/issues/1960 ### Security diff --git a/terminal.c b/terminal.c index c8e49663..2e868793 100644 --- a/terminal.c +++ b/terminal.c @@ -4153,7 +4153,7 @@ term_process_and_print_non_ascii(struct terminal *term, char32_t wc) if (grapheme_clustering) { /* Check if we're on a grapheme cluster break */ if (utf8proc_grapheme_break_stateful( - last, wc, &term->vt.grapheme_state)) + last, wc, &term->vt.grapheme_state) && width > 0) { term_reset_grapheme_state(term); goto out; From d66a00678d434afbdf31dbaad61b9837983ecc6e Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Thu, 13 Feb 2025 16:16:43 +0100 Subject: [PATCH 111/353] server: fix --server= on OSes returning SO_ACCEPTCONN > 1 Closes #1956 --- server.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server.c b/server.c index 5981a14c..22dd473b 100644 --- a/server.c +++ b/server.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,8 @@ #include "wayland.h" #include "xmalloc.h" +#define NON_ZERO_OPT (INT_MIN / 7) + struct client; struct terminal_instance; @@ -474,7 +477,7 @@ prepare_socket(int fd) } int const socket_options[] = { SO_DOMAIN, SO_ACCEPTCONN, SO_TYPE }; - int const socket_options_values[] = { AF_UNIX, 1, SOCK_STREAM}; + int const socket_options_values[] = { AF_UNIX, NON_ZERO_OPT, SOCK_STREAM}; char const * const socket_options_names[] = { "SO_DOMAIN", "SO_ACCEPTCONN", "SO_TYPE" }; xassert(ALEN(socket_options) == ALEN(socket_options_values)); @@ -489,6 +492,8 @@ prepare_socket(int fd) LOG_ERRNO("failed to read socket option from passed file descriptor"); return false; } + if (socket_options_values[i] == NON_ZERO_OPT && socket_option) + socket_option = NON_ZERO_OPT; if (socket_option != socket_options_values[i]) { LOG_ERR("wrong socket value for socket option '%s' on passed file descriptor", socket_options_names[i]); From ba5f4abdd47e572f88e4ccba75cd8af82f6ee507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 16 Feb 2025 13:56:43 +0100 Subject: [PATCH 112/353] changelog: --server=FD failing on FreeBSD --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cc5e13f..3eedcb41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,12 +115,14 @@ * Zero-width characters that also are grapheme breaks (e.g. U+200B, ZERO WIDTH SPACE) being ignored (discarded and never stored in the grid) ([#1960][1960]). +* `--server=` not working on FreeBSD ([#1956][1956]). [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 [1947]: https://codeberg.org/dnkl/foot/issues/1947 [1954]: https://codeberg.org/dnkl/foot/issues/1954 [1960]: https://codeberg.org/dnkl/foot/issues/1960 +[1956]: https://codeberg.org/dnkl/foot/issues/1956 ### Security From 9f9ffa94348905509083904f5d542481875b1b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 18 Feb 2025 15:09:23 +0100 Subject: [PATCH 113/353] term: set_app_id(): app_id may be NULL, in which case we can't do strlen() Closes #1963 --- CHANGELOG.md | 3 +++ terminal.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3eedcb41..f4ed6459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,8 @@ ZERO WIDTH SPACE) being ignored (discarded and never stored in the grid) ([#1960][1960]). * `--server=` not working on FreeBSD ([#1956][1956]). +* Crash when resetting the terminal and an application had previously + set a custom app ID ([#1963][1963]) [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 @@ -123,6 +125,7 @@ [1954]: https://codeberg.org/dnkl/foot/issues/1954 [1960]: https://codeberg.org/dnkl/foot/issues/1960 [1956]: https://codeberg.org/dnkl/foot/issues/1956 +[1963]: https://codeberg.org/dnkl/foot/issues/1963 ### Security diff --git a/terminal.c b/terminal.c index 2e868793..fd1c8937 100644 --- a/terminal.c +++ b/terminal.c @@ -3620,7 +3620,7 @@ term_set_app_id(struct terminal *term, const char *app_id) term->app_id = NULL; } - const size_t length = strlen(app_id); + const size_t length = app_id != NULL ? strlen(app_id) : 0; if (length > 2048) { /* * Not sure if there's a limit in the protocol, or the From 101bc28698177807e997951d046a12dd2c741848 Mon Sep 17 00:00:00 2001 From: Craig Barnes Date: Tue, 18 Feb 2025 17:32:54 +0000 Subject: [PATCH 114/353] terminal: add comment/link to cursor::lcf, to clarify its purpose --- terminal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal.h b/terminal.h index a69a8d0f..e03b7bf7 100644 --- a/terminal.h +++ b/terminal.h @@ -88,7 +88,7 @@ struct range { struct cursor { struct coord point; - bool lcf; + bool lcf; /* Last Column Flag; https://github.com/mattiase/wraptest#basic-vt-line-wrapping-rules */ }; enum damage_type {DAMAGE_SCROLL, DAMAGE_SCROLL_REVERSE, From c41008da31aba42e3d0e25966af9eab27cfd3fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 19 Feb 2025 11:44:38 +0100 Subject: [PATCH 115/353] config+render: allow cursor.style=hollow Closes #1965 --- CHANGELOG.md | 2 ++ config.c | 2 +- config.h | 2 +- doc/foot.ini.5.scd | 4 ++-- render.c | 5 +++++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ed6459..8e9ca7d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,9 +68,11 @@ * Support for custom regex matching ([#1386][1386], [#1872][1872]) * Support for kitty's text-sizing protocol (`w`, width, only), OSC-66. +* `cursor.style` can now be set to `hollow` ([#1965][1965]). [1386]: https://codeberg.org/dnkl/foot/issues/1386 [1872]: https://codeberg.org/dnkl/foot/issues/1872 +[1965]: https://codeberg.org/dnkl/foot/issues/1965 ### Changed diff --git a/config.c b/config.c index 7a12cb1a..de2dc07f 100644 --- a/config.c +++ b/config.c @@ -1517,7 +1517,7 @@ parse_section_cursor(struct context *ctx) return value_to_enum( ctx, - (const char *[]){"block", "underline", "beam", NULL}, + (const char *[]){"block", "underline", "beam", "hollow", NULL}, (int *)&conf->cursor.style); } diff --git a/config.h b/config.h index 3535064e..deddcf04 100644 --- a/config.h +++ b/config.h @@ -28,7 +28,7 @@ struct font_size_adjustment { float percent; }; -enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM }; +enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM, CURSOR_HOLLOW }; enum cursor_unfocused_style { CURSOR_UNFOCUSED_UNCHANGED, CURSOR_UNFOCUSED_HOLLOW, diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 87673233..56004654 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -842,8 +842,8 @@ applications can change these at runtime. *style* Configures the default cursor style, and is one of: *block*, - *beam* or *underline*. Note that this can be overridden by - applications. Default: _block_. + *beam*, *underline* or *hollow*. Note that this can be overridden + by applications. Default: _block_. *unfocused-style* Configures how the cursor is rendered when the terminal window is diff --git a/render.c b/render.c index cf4f303e..701cefae 100644 --- a/render.c +++ b/render.c @@ -647,6 +647,11 @@ draw_cursor(const struct terminal *term, const struct cell *cell, draw_underline_cursor(term, pix, font, &cursor_color, x, y, cols); } break; + + case CURSOR_HOLLOW: + if (likely(term->cursor_blink.state == CURSOR_BLINK_ON)) + draw_hollow_block(term, pix, &cursor_color, x, y, cols); + break; } } From 4f11d6086fd9341e663fafaf2b256981af389eef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 21 Feb 2025 08:03:41 +0100 Subject: [PATCH 116/353] DECSCUSR+DECRQSS: treat hollow cursor as a block cursor If the user has configured cursor.style=hollow, make DECSCUSR 1/2 set the cursor to hollow rather than block, and make DECRQSS DECSCUSR respond as if cursor.style=block. The idea is to not expose the hollow variant in DECSCUSR in any way, to avoid having to extend it with custom encodings. Another way to think about it is this is just a slightly more discoverable way of doing: cursor.style=block cursor.block-cursor-is-hollow=yes --- csi.c | 3 ++- dcs.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/csi.c b/csi.c index b982023c..81c71e31 100644 --- a/csi.c +++ b/csi.c @@ -1756,7 +1756,8 @@ csi_dispatch(struct terminal *term, uint8_t final) case 1: /* blinking block */ case 2: /* steady block */ - term->cursor_style = CURSOR_BLOCK; + term->cursor_style = term->conf->cursor.style == CURSOR_HOLLOW + ? CURSOR_HOLLOW : CURSOR_BLOCK; break; case 3: /* blinking underline */ diff --git a/dcs.c b/dcs.c index ebea9e4c..376c73bd 100644 --- a/dcs.c +++ b/dcs.c @@ -422,6 +422,7 @@ decrqss_unhook(struct terminal *term) int mode; switch (term->cursor_style) { + case CURSOR_HOLLOW: /* FALLTHROUGH */ case CURSOR_BLOCK: mode = 2; break; case CURSOR_UNDERLINE: mode = 4; break; case CURSOR_BEAM: mode = 6; break; From 882f4b246870d525cf65bb927ec4788010150722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 2 Mar 2025 10:18:00 +0100 Subject: [PATCH 117/353] shm-format: add new shm formats --- shm-formats.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/shm-formats.h b/shm-formats.h index 3ada8266..a73ba1f2 100644 --- a/shm-formats.h +++ b/shm-formats.h @@ -117,5 +117,22 @@ static const struct shm_formats { {WL_SHM_FORMAT_ARGB16161616, "ARGB16161616"}, {WL_SHM_FORMAT_ABGR16161616, "ABGR16161616"}, #endif +#if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 23 + {WL_SHM_FORMAT_C1, "C1"}, + {WL_SHM_FORMAT_C2, "C2"}, + {WL_SHM_FORMAT_C4, "C4"}, + {WL_SHM_FORMAT_D1, "D1"}, + {WL_SHM_FORMAT_D2, "D2"}, + {WL_SHM_FORMAT_D4, "D4"}, + {WL_SHM_FORMAT_D8, "D8"}, + {WL_SHM_FORMAT_R1, "R1"}, + {WL_SHM_FORMAT_R2, "R2"}, + {WL_SHM_FORMAT_R4, "R4"}, + {WL_SHM_FORMAT_R10, "R10"}, + {WL_SHM_FORMAT_R12, "R12"}, + {WL_SHM_FORMAT_AVUY8888, "AVUY8888"}, + {WL_SHM_FORMAT_XVUY8888, "XVUY8888"}, + {WL_SHM_FORMAT_P030, "P030"}, +#endif }; #endif From 9e6d334bd8d8a565a7255f91ddb4de849563dbc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Mar 2025 07:50:03 +0100 Subject: [PATCH 118/353] term: reset the grapheme clustering state on cursor movements --- CHANGELOG.md | 1 + terminal.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9ca7d1..30adad73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -120,6 +120,7 @@ * `--server=` not working on FreeBSD ([#1956][1956]). * Crash when resetting the terminal and an application had previously set a custom app ID ([#1963][1963]) +* Grapheme clustering state not reset on cursor movements. [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 diff --git a/terminal.c b/terminal.c index fd1c8937..0cd4fbca 100644 --- a/terminal.c +++ b/terminal.c @@ -2860,6 +2860,8 @@ term_cursor_to(struct terminal *term, int row, int col) term->grid->cursor.point.col = col; term->grid->cursor.point.row = row; + term_reset_grapheme_state(term); + term->grid->cur_row = grid_row(term->grid, row); } @@ -2876,6 +2878,7 @@ term_cursor_col(struct terminal *term, int col) term->grid->cursor.lcf = false; term->grid->cursor.point.col = col; + term_reset_grapheme_state(term); } void @@ -2885,6 +2888,7 @@ term_cursor_left(struct terminal *term, int count) term->grid->cursor.point.col -= move_amount; xassert(term->grid->cursor.point.col >= 0); term->grid->cursor.lcf = false; + term_reset_grapheme_state(term); } void @@ -2894,6 +2898,7 @@ term_cursor_right(struct terminal *term, int count) term->grid->cursor.point.col += move_amount; xassert(term->grid->cursor.point.col < term->cols); term->grid->cursor.lcf = false; + term_reset_grapheme_state(term); } void @@ -3165,6 +3170,8 @@ term_linefeed(struct terminal *term) term_scroll(term, 1); else term_cursor_down(term, 1); + + term_reset_grapheme_state(term); } void From 6d39f66eb7c0cafb9e12be7da6831598c6dd4afb Mon Sep 17 00:00:00 2001 From: Adrian fxj9a Date: Mon, 3 Mar 2025 14:27:30 +0100 Subject: [PATCH 119/353] config: add search-bindings.delete-to-{start,end} key bindings Defaults to ctrl+u and ctrl+k respectively. Closes #1972 --- CHANGELOG.md | 4 ++++ config.c | 4 ++++ doc/foot.1.scd | 6 ++++++ doc/foot.ini.5.scd | 6 ++++++ foot.ini | 2 ++ key-binding.h | 2 ++ search.c | 23 +++++++++++++++++++++++ 7 files changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30adad73..59d69f3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,10 +69,14 @@ [#1872][1872]) * Support for kitty's text-sizing protocol (`w`, width, only), OSC-66. * `cursor.style` can now be set to `hollow` ([#1965][1965]). +* `search-bindings.delete-to-start` and + `search-bindings.delete-to-end` key bindings, defaulting to + `Control+u` and `Control+k` respectively ([#1972][1972]). [1386]: https://codeberg.org/dnkl/foot/issues/1386 [1872]: https://codeberg.org/dnkl/foot/issues/1872 [1965]: https://codeberg.org/dnkl/foot/issues/1965 +[1972]: https://codeberg.org/dnkl/foot/issues/1972 ### Changed diff --git a/config.c b/config.c index de2dc07f..df7f0031 100644 --- a/config.c +++ b/config.c @@ -180,6 +180,8 @@ static const char *const search_binding_action_map[] = { [BIND_ACTION_SEARCH_DELETE_PREV_WORD] = "delete-prev-word", [BIND_ACTION_SEARCH_DELETE_NEXT] = "delete-next", [BIND_ACTION_SEARCH_DELETE_NEXT_WORD] = "delete-next-word", + [BIND_ACTION_SEARCH_DELETE_TO_START] = "delete-to-start", + [BIND_ACTION_SEARCH_DELETE_TO_END] = "delete-to-end", [BIND_ACTION_SEARCH_EXTEND_CHAR] = "extend-char", [BIND_ACTION_SEARCH_EXTEND_WORD] = "extend-to-word-boundary", [BIND_ACTION_SEARCH_EXTEND_WORD_WS] = "extend-to-next-whitespace", @@ -3183,6 +3185,8 @@ add_default_search_bindings(struct config *conf) {BIND_ACTION_SEARCH_DELETE_NEXT, m("none"), {{XKB_KEY_Delete}}}, {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_Delete}}}, {BIND_ACTION_SEARCH_DELETE_NEXT_WORD, m(XKB_MOD_NAME_ALT), {{XKB_KEY_d}}}, + {BIND_ACTION_SEARCH_DELETE_TO_START, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_u}}}, + {BIND_ACTION_SEARCH_DELETE_TO_END, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_k}}}, {BIND_ACTION_SEARCH_EXTEND_CHAR, m(XKB_MOD_NAME_SHIFT), {{XKB_KEY_Right}}}, {BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL "+" XKB_MOD_NAME_SHIFT), {{XKB_KEY_Right}}}, {BIND_ACTION_SEARCH_EXTEND_WORD, m(XKB_MOD_NAME_CTRL), {{XKB_KEY_w}}}, diff --git a/doc/foot.1.scd b/doc/foot.1.scd index fad44f19..f868c12c 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -301,6 +301,12 @@ These shortcuts affect the search box in scrollback-search mode: *alt*+*delete*, *ctrl*+*delete* Deletes the **word after** the cursor. +*ctrl*+*u* + Deletes from the cursor to the start of the input + +*ctrl*+*k* + Deletes from the cursor to the end of the input + These shortcuts affect scrolling in scrollback-search mode: *shift*+*page-up* diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 56004654..c55419d1 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1401,6 +1401,12 @@ scrollback search mode. The syntax is exactly the same as the regular Deletes the **word after** the cursor. Default: _Mod1+d Control+Delete_. +*delete-to-start* + Deletes search input before the cursor. Default: _Ctrl+u_. + +*delete-to-end* + Deletes search input after the cursor. Default: _Ctrl+k_. + *extend-char* Extend current selection to the right, by one character. Default: _Shift+Right_. diff --git a/foot.ini b/foot.ini index a1aa118c..b852da07 100644 --- a/foot.ini +++ b/foot.ini @@ -225,6 +225,8 @@ # delete-prev-word=Mod1+BackSpace Control+BackSpace # delete-next=Delete # delete-next-word=Mod1+d Control+Delete +# delete-to-start=Control+u +# delete-to-end=Control+k # extend-char=Shift+Right # extend-to-word-boundary=Control+w Control+Shift+Right # extend-to-next-whitespace=Control+Shift+w diff --git a/key-binding.h b/key-binding.h index 5f5bb9d7..89398859 100644 --- a/key-binding.h +++ b/key-binding.h @@ -84,6 +84,8 @@ enum bind_action_search { BIND_ACTION_SEARCH_DELETE_PREV_WORD, BIND_ACTION_SEARCH_DELETE_NEXT, BIND_ACTION_SEARCH_DELETE_NEXT_WORD, + BIND_ACTION_SEARCH_DELETE_TO_START, + BIND_ACTION_SEARCH_DELETE_TO_END, BIND_ACTION_SEARCH_EXTEND_CHAR, BIND_ACTION_SEARCH_EXTEND_WORD, BIND_ACTION_SEARCH_EXTEND_WORD_WS, diff --git a/search.c b/search.c index 20990c87..dda84e6d 100644 --- a/search.c +++ b/search.c @@ -1265,6 +1265,29 @@ execute_binding(struct seat *seat, struct terminal *term, return true; } + case BIND_ACTION_SEARCH_DELETE_TO_START: { + if (term->search.cursor > 0) { + memmove(&term->search.buf[0], + &term->search.buf[term->search.cursor], + (term->search.len - term->search.cursor) + * sizeof(char32_t)); + + term->search.len -= term->search.cursor; + term->search.cursor = 0; + *update_search_result = *redraw = true; + } + return true; + } + + case BIND_ACTION_SEARCH_DELETE_TO_END: { + if (term->search.cursor < term->search.len) { + term->search.buf[term->search.cursor] = '\0'; + term->search.len = term->search.cursor; + *update_search_result = *redraw = true; + } + return true; + } + case BIND_ACTION_SEARCH_EXTEND_CHAR: { struct coord target; if (search_extend_find_char_right(term, &target)) { From ccf625b9914917e131beb83379c526bab1927edc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 21 Feb 2025 11:01:29 +0100 Subject: [PATCH 120/353] render: gamma-correct blending This implements gamma-correct blending, which mainly affects font rendering. The implementation requires compile-time availability of the new color-management protocol (available in wayland-protocols >= 1.41), and run-time support for the same in the compositor (specifically, the EXT_LINEAR TF function and sRGB primaries). How it works: all colors are decoded from sRGB to linear (using a lookup table, generated in the exact same way pixman generates it's internal conversion tables) before being used by pixman. The resulting image buffer is thus in decoded/linear format. We use the color-management protocol to inform the compositor of this, by tagging the wayland surfaces with the 'ext_linear' image attribute. Sixes: all colors are sRGB internally, and decoded to linear before being used in any sixels. Thus, the image buffers will contain linear colors. This is important, since otherwise there would be a decode/encode penalty every time a sixel is blended to the grid. Emojis: we require fcft >= 3.2, which adds support for sRGB decoding color glyphs. Meaning, the emoji pixman surfaces can be blended directly to the grid, just like sixels. Gamma-correct blending is enabled by default *when the compositor supports it*. There's a new option to explicitly enable/disable it: gamma-correct-blending=no|yes. If set to 'yes', and the compositor does not implement the required color-management features, warning logs are emitted. There's a loss of precision when storing linear pixels in 8-bit channels. For this reason, this patch also adds supports for 10-bit surfaces. For now, this is disabled by default since such surfaces only have 2 bits for alpha. It can be enabled with tweak.surface-bit-depth=10-bit. Perhaps, in the future, we can enable it by default if: * gamma-correct blending is enabled * the user has not enabled a transparent background --- CHANGELOG.md | 6 ++ client.c | 3 +- config.c | 38 ++++++++++ config.h | 4 + doc/foot.ini.5.scd | 46 ++++++++++++ foot-features.h | 9 +++ main.c | 3 +- meson.build | 19 ++++- pgo/pgo.c | 10 ++- quirks.c | 1 + render.c | 171 +++++++++++++++++++++++++++--------------- render.h | 2 + scripts/srgb.py | 90 ++++++++++++++++++++++ shm.c | 80 +++++++++++++++++++- shm.h | 5 +- sixel.c | 97 ++++++++++++++++++++---- terminal.c | 54 ++++++++++---- terminal.h | 4 + wayland.c | 182 ++++++++++++++++++++++++++++++++++++++++++++- wayland.h | 23 ++++++ 20 files changed, 746 insertions(+), 101 deletions(-) create mode 100755 scripts/srgb.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 59d69f3e..152b7728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,11 @@ * `search-bindings.delete-to-start` and `search-bindings.delete-to-end` key bindings, defaulting to `Control+u` and `Control+k` respectively ([#1972][1972]). +* Gamma-correct font rendering. Requires compositor support + (`wp_color_management_v1`, and specifically, the `ext_linear` + transfer function). Enabled by default when compositor support is + available. Can be explicitly enabled or disabled with + `gamma-correct-blending=no|yes`. [1386]: https://codeberg.org/dnkl/foot/issues/1386 [1872]: https://codeberg.org/dnkl/foot/issues/1872 @@ -87,6 +92,7 @@ * Auto-detection of URLs (i.e. not OSC-8 based URLs) are now regex based. * Rename Tokyo Night Day theme to Tokyo Night Light and update colors. +* fcft >= 3.2.0 is now required. [1925]: https://codeberg.org/dnkl/foot/issues/1925 diff --git a/client.c b/client.c index dabcc327..ceee1b29 100644 --- a/client.c +++ b/client.c @@ -67,13 +67,14 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %cassertions", + "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %ccolor-management %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', feature_xdg_toplevel_icon() ? '+' : '-', feature_xdg_system_bell() ? '+' : '-', + feature_wp_color_management() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } diff --git a/config.c b/config.c index df7f0031..1f287250 100644 --- a/config.c +++ b/config.c @@ -1107,6 +1107,28 @@ parse_section_main(struct context *ctx) return true; } + else if (streq(key, "gamma-correct-blending")) { + bool gamma_correct; + if (!value_to_bool(ctx, &gamma_correct)) + return false; + +#if defined(HAVE_WP_COLOR_MANAGEMENT) + conf->gamma_correct = + gamma_correct + ? GAMMA_CORRECT_ENABLED + : GAMMA_CORRECT_DISABLED; + return true; +#else + if (gamma_correct) { + LOG_CONTEXTUAL_WARN( + "ignoring; foot was built without color-management support"); + } + + conf->gamma_correct = GAMMA_CORRECT_DISABLED; + return true; +#endif + } + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -2767,6 +2789,16 @@ parse_section_tweak(struct context *ctx) else if (streq(key, "bold-text-in-bright-amount")) return value_to_float(ctx, &conf->bold_in_bright.amount); + else if (streq(key, "surface-bit-depth")) { + _Static_assert(sizeof(conf->tweak.surface_bit_depth) == sizeof(int), + "enum is not 32-bit"); + + return value_to_enum( + ctx, + (const char *[]){"8-bit", "10-bit", NULL}, + (int *)&conf->tweak.surface_bit_depth); + } + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -3300,6 +3332,11 @@ config_load(struct config *conf, const char *conf_path, .underline_thickness = {.pt = 0., .px = -1}, .strikeout_thickness = {.pt = 0., .px = -1}, .dpi_aware = false, +#if defined(HAVE_WP_COLOR_MANAGEMENT) + .gamma_correct = GAMMA_CORRECT_AUTO, +#else + .gamma_correct = GAMMA_CORRECT_DISABLED, +#endif .security = { .osc52 = OSC52_ENABLED, }, @@ -3408,6 +3445,7 @@ config_load(struct config *conf, const char *conf_path, .box_drawing_solid_shades = true, .font_monospace_warn = true, .sixel = true, + .surface_bit_depth = 8, }, .touch = { diff --git a/config.h b/config.h index deddcf04..fb019d90 100644 --- a/config.h +++ b/config.h @@ -164,6 +164,9 @@ struct config { enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode; bool dpi_aware; + enum {GAMMA_CORRECT_DISABLED, + GAMMA_CORRECT_ENABLED, + GAMMA_CORRECT_AUTO} gamma_correct; struct config_font_list fonts[4]; struct font_size_adjustment font_size_adjustment; @@ -397,6 +400,7 @@ struct config { bool box_drawing_solid_shades; bool font_monospace_warn; bool sixel; + enum { SHM_8_BIT, SHM_10_BIT } surface_bit_depth; } tweak; struct { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index c55419d1..952c7ae2 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -198,6 +198,35 @@ empty string to be set, but it must be quoted: *KEY=""*) Default: _unset_ +*gamma-correct-blending* + Boolean. When enabled, foot will do gamma-correct blending in + linear color space. This is how font glyphs are supposed to be + rendered, but since nearly no applications or toolkits are doing + it on Linux, the result may not look like you are used to. + + Compared to the default (disabled), bright glyphs on a dark + background will appear thicker, and dark glyphs on a light + background will appear thinner. + + Also be aware that many fonts have been developed on systems that + do not do gamma-correct blending, and may therefore look thicker + than intended when rendered with gamma-correct blending, since the + font designer set the font weight based on incorrect rendering. + + FreeType can limit the effect of the latter, with a technique + called stem darkening. It is only available for CFF fonts + (OpenType, .otf) and disabled by default (in FreeType). You can + enable it by setting the environment variable + *FREETYPE_PROPERTIES="cff:no-stem-darkening=0"* before starting + foot. + + You may also want to enable 10-bit image buffers when + gamma-correct blending is enabled. Though probably only if you do + not use a transparent background (with 10-bit buffers, you only + get 2 bits alpha). See *tweak.surface-bit-depth*. + + Default: enabled when compositor support is available + *box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates box/line drawing characters itself. The are several advantages to doing this instead of using @@ -1917,6 +1946,23 @@ any of these options. *bold-text-in-bright* is set to *yes* (the *palette-based* variant is not affected by this option). Default: _1.3_. +*surface-bit-depth* + Selects which RGB bit depth to use for image buffers. One of + *8-bit*, or *10-bit*. + + The default, *8-bit*, uses 8 bits for all channels, alpha + included. When *gamma-correct-blending* is disabled, this is the + best option. + + When *gamma-correct-blending* is enabled, you may want to enable + 10-bit surfaces, as that improves the color resolution. Be aware + however, that in this mode, the alpha channel is only 2 bits + instead of 8 bits. Thus, if you are using a transparent + background, you may want to use the default, *8-bit*, even if you + have gamma-correct blending enabled. + + Default: _8-bit_ + # SEE ALSO *foot*(1), *footclient*(1) diff --git a/foot-features.h b/foot-features.h index 0eef5eac..c6c9c6f4 100644 --- a/foot-features.h +++ b/foot-features.h @@ -55,3 +55,12 @@ static inline bool feature_xdg_system_bell(void) return false; #endif } + +static inline bool feature_wp_color_management(void) +{ +#if defined(HAVE_WP_COLOR_MANAGEMENT) + return true; +#else + return false; +#endif +} diff --git a/main.c b/main.c index 0ba574c9..1a001186 100644 --- a/main.c +++ b/main.c @@ -51,13 +51,14 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %cassertions", + "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %ccolor-management %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', feature_xdg_toplevel_icon() ? '+' : '-', feature_xdg_system_bell() ? '+' : '-', + feature_wp_color_management() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } diff --git a/meson.build b/meson.build index 27ea3d53..6505460f 100644 --- a/meson.build +++ b/meson.build @@ -146,7 +146,7 @@ if utf8proc.found() endif tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist') -fcft = dependency('fcft', version: ['>=3.0.1', '<4.0.0'], fallback: 'fcft') +fcft = dependency('fcft', version: ['>=3.2.0', '<4.0.0'], fallback: 'fcft') wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir') @@ -187,6 +187,13 @@ else xdg_system_bell = false endif +if wayland_protocols.version().version_compare('>=1.41') + add_project_arguments('-DHAVE_WP_COLOR_MANAGEMENT', language: 'c') + wl_proto_xml += [wayland_protocols_datadir / 'staging/color-management/color-management-v1.xml'] + wp_color_management = true +else + wp_color_management = false +endif foreach prot : wl_proto_xml wl_proto_headers += custom_target( @@ -228,6 +235,13 @@ emoji_variation_sequences = custom_target( command: [python, generate_emoji_variation_sequences, '@INPUT@', '@OUTPUT@'] ) +generate_srgb_funcs = files('scripts/srgb.py') +srgb_funcs = custom_target( + 'generate_srgb_funcs', + output: ['srgb.c', 'srgb.h'], + command: [python, generate_srgb_funcs, '@OUTPUT0@', '@OUTPUT1@'] +) + common = static_library( 'common', 'log.c', 'log.h', @@ -260,7 +274,7 @@ vtlib = static_library( 'osc.c', 'osc.h', 'sixel.c', 'sixel.h', 'vt.c', 'vt.h', - builtin_terminfo, emoji_variation_sequences, + builtin_terminfo, emoji_variation_sequences, srgb_funcs, wl_proto_src + wl_proto_headers, version, dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc], @@ -424,6 +438,7 @@ summary( 'Grapheme clustering': utf8proc.found(), 'Wayland: xdg-toplevel-icon-v1': xdg_toplevel_icon, 'Wayland: xdg-system-bell-v1': xdg_system_bell, + 'Wayland: wp-color-management-v1': wp_color_management, 'utmp backend': utmp_backend, 'utmp helper default path': utmp_default_helper_path, 'Build terminfo': tic.found(), diff --git a/pgo/pgo.c b/pgo/pgo.c index 88e862b8..8a4967ba 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -128,6 +128,12 @@ render_worker_thread(void *_ctx) return 0; } +bool +render_do_linear_blending(const struct terminal *term) +{ + return false; +} + struct extraction_context * extract_begin(enum selection_kind kind, bool strip_trailing_empty) { @@ -197,7 +203,9 @@ void shm_unref(struct buffer *buf) {} void shm_chain_free(struct buffer_chain *chain) {} struct buffer_chain * -shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances) +shm_chain_new( + struct wayland *wayl, bool scrollable, size_t pix_instances, + bool ten_bit_it_if_capable) { return NULL; } diff --git a/quirks.c b/quirks.c index 9769f1ff..7cc8a8f1 100644 --- a/quirks.c +++ b/quirks.c @@ -86,6 +86,7 @@ is_sway(void) void quirk_sway_subsurface_unmap(struct terminal *term) { + return; if (!is_sway()) return; diff --git a/render.c b/render.c index 701cefae..eea43c10 100644 --- a/render.c +++ b/render.c @@ -44,6 +44,7 @@ #include "selection.h" #include "shm.h" #include "sixel.h" +#include "srgb.h" #include "url-mode.h" #include "util.h" #include "xmalloc.h" @@ -232,22 +233,45 @@ attrs_to_font(const struct terminal *term, const struct attributes *attrs) return term->fonts[idx]; } -static inline pixman_color_t -color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha) +static pixman_color_t +color_hex_to_pixman_srgb(uint32_t color, uint16_t alpha) { return (pixman_color_t){ - .red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)) * alpha / 0xffff, - .green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)) * alpha / 0xffff, - .blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)) * alpha / 0xffff, - .alpha = alpha, + .alpha = alpha, /* Consider alpha linear already? */ + .red = srgb_decode_8_to_16((color >> 16) & 0xff), + .green = srgb_decode_8_to_16((color >> 8) & 0xff), + .blue = srgb_decode_8_to_16((color >> 0) & 0xff), }; } static inline pixman_color_t -color_hex_to_pixman(uint32_t color) +color_hex_to_pixman_with_alpha(uint32_t color, uint16_t alpha, bool srgb) +{ + pixman_color_t ret; + + if (srgb) + ret = color_hex_to_pixman_srgb(color, alpha); + else { + ret = (pixman_color_t){ + .red = ((color >> 16 & 0xff) | (color >> 8 & 0xff00)), + .green = ((color >> 8 & 0xff) | (color >> 0 & 0xff00)), + .blue = ((color >> 0 & 0xff) | (color << 8 & 0xff00)), + .alpha = alpha, + }; + } + + ret.red = (uint32_t)ret.red * alpha / 0xffff; + ret.green = (uint32_t)ret.green * alpha / 0xffff; + ret.blue = (uint32_t)ret.blue * alpha / 0xffff; + + return ret; +} + +static inline pixman_color_t +color_hex_to_pixman(uint32_t color, bool srgb) { /* Count on the compiler optimizing this */ - return color_hex_to_pixman_with_alpha(color, 0xffff); + return color_hex_to_pixman_with_alpha(color, 0xffff, srgb); } static inline uint32_t @@ -568,23 +592,24 @@ draw_strikeout(const struct terminal *term, pixman_image_t *pix, static void cursor_colors_for_cell(const struct terminal *term, const struct cell *cell, - const pixman_color_t *fg, const pixman_color_t *bg, - pixman_color_t *cursor_color, pixman_color_t *text_color) + const pixman_color_t *fg, const pixman_color_t *bg, + pixman_color_t *cursor_color, pixman_color_t *text_color, + bool gamma_correct) { if (term->colors.cursor_bg >> 31) - *cursor_color = color_hex_to_pixman(term->colors.cursor_bg); + *cursor_color = color_hex_to_pixman(term->colors.cursor_bg, gamma_correct); else *cursor_color = *fg; if (term->colors.cursor_fg >> 31) - *text_color = color_hex_to_pixman(term->colors.cursor_fg); + *text_color = color_hex_to_pixman(term->colors.cursor_fg, gamma_correct); else { *text_color = *bg; if (unlikely(text_color->alpha != 0xffff)) { /* The *only* color that can have transparency is the * default background color */ - *text_color = color_hex_to_pixman(term->colors.bg); + *text_color = color_hex_to_pixman(term->colors.bg, gamma_correct); } } @@ -592,8 +617,8 @@ cursor_colors_for_cell(const struct terminal *term, const struct cell *cell, text_color->green == cursor_color->green && text_color->blue == cursor_color->blue) { - *text_color = color_hex_to_pixman(term->colors.bg); - *cursor_color = color_hex_to_pixman(term->colors.fg); + *text_color = color_hex_to_pixman(term->colors.bg, gamma_correct); + *cursor_color = color_hex_to_pixman(term->colors.fg, gamma_correct); } } @@ -604,7 +629,8 @@ draw_cursor(const struct terminal *term, const struct cell *cell, { pixman_color_t cursor_color; pixman_color_t text_color; - cursor_colors_for_cell(term, cell, fg, bg, &cursor_color, &text_color); + cursor_colors_for_cell(term, cell, fg, bg, &cursor_color, &text_color, + render_do_linear_blending(term)); if (unlikely(!term->kbd_focus)) { switch (term->conf->cursor.unfocused_style) { @@ -656,8 +682,9 @@ draw_cursor(const struct terminal *term, const struct cell *cell, } static int -render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damage, - struct row *row, int row_no, int col, bool has_cursor) +render_cell(struct terminal *term, pixman_image_t *pix, + pixman_region32_t *damage, struct row *row, int row_no, int col, + bool has_cursor) { struct cell *cell = &row->cells[col]; if (cell->attrs.clean) @@ -776,8 +803,9 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag if (cell->attrs.blink && term->blink.state == BLINK_OFF) _fg = color_decrease_luminance(_fg); - pixman_color_t fg = color_hex_to_pixman(_fg); - pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha); + const bool gamma_correct = render_do_linear_blending(term); + pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct); + pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct); struct fcft_font *font = attrs_to_font(term, &cell->attrs); const struct composed *composed = NULL; @@ -987,7 +1015,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag if (i > 0 && glyph->x >= 0 && cell_cols == 1) g_x -= term->cell_width; - if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) { + if (unlikely(glyph->is_color_glyph)) { /* Glyph surface is a pre-rendered image (typically a color emoji...) */ if (!(cell->attrs.blink && term->blink.state == BLINK_OFF)) { pixman_image_composite32( @@ -1071,12 +1099,12 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag switch (range->underline.color_src) { case COLOR_BASE256: underline_color = color_hex_to_pixman( - term->colors.table[range->underline.color]); + term->colors.table[range->underline.color], gamma_correct); break; case COLOR_RGB: underline_color = - color_hex_to_pixman(range->underline.color); + color_hex_to_pixman(range->underline.color, gamma_correct); break; case COLOR_DEFAULT: @@ -1105,8 +1133,8 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damag pixman_color_t url_color = color_hex_to_pixman( term->conf->colors.use_custom.url ? term->conf->colors.url - : term->colors.table[3] - ); + : term->colors.table[3], + gamma_correct); draw_underline(term, pix, font, &url_color, x, y, cell_cols); } @@ -1119,8 +1147,9 @@ draw_cursor: } static void -render_row(struct terminal *term, pixman_image_t *pix, pixman_region32_t *damage, - struct row *row, int row_no, int cursor_col) +render_row(struct terminal *term, pixman_image_t *pix, + pixman_region32_t *damage, struct row *row, + int row_no, int cursor_col) { for (int col = term->cols - 1; col >= 0; col--) render_cell(term, pix, damage, row, row_no, col, cursor_col == col); @@ -1130,7 +1159,7 @@ static void render_urgency(struct terminal *term, struct buffer *buf) { uint32_t red = term->colors.table[1]; - pixman_color_t bg = color_hex_to_pixman(red); + pixman_color_t bg = color_hex_to_pixman(red, render_do_linear_blending(term)); int width = min(min(term->margins.left, term->margins.right), min(term->margins.top, term->margins.bottom)); @@ -1161,6 +1190,7 @@ render_margin(struct terminal *term, struct buffer *buf, const int bmargin = term->height - term->margins.bottom; const int line_count = end_line - start_line; + const bool gamma_correct = render_do_linear_blending(term); const uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg; uint16_t alpha = term->colors.alpha; @@ -1169,7 +1199,7 @@ render_margin(struct terminal *term, struct buffer *buf, alpha = 0xffff; } - pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha); + pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct); pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &bg, 4, @@ -1596,8 +1626,7 @@ render_sixel(struct terminal *term, pixman_image_t *pix, static void render_sixel_images(struct terminal *term, pixman_image_t *pix, - pixman_region32_t *damage, - const struct coord *cursor) + pixman_region32_t *damage, const struct coord *cursor) { if (likely(tll_length(term->grid->sixel_images)) == 0) return; @@ -1649,6 +1678,8 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, if (unlikely(term->is_searching)) return; + const bool gamma_correct = render_do_linear_blending(term); + /* Adjust cursor position to viewport */ struct coord cursor; cursor = term->grid->cursor.point; @@ -1753,12 +1784,12 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, if (!seat->ime.preedit.cursor.hidden) { const struct cell *start_cell = &seat->ime.preedit.cells[0]; - pixman_color_t fg = color_hex_to_pixman(term->colors.fg); - pixman_color_t bg = color_hex_to_pixman(term->colors.bg); + pixman_color_t fg = color_hex_to_pixman(term->colors.fg, gamma_correct); + pixman_color_t bg = color_hex_to_pixman(term->colors.bg, gamma_correct); pixman_color_t cursor_color, text_color; cursor_colors_for_cell( - term, start_cell, &fg, &bg, &cursor_color, &text_color); + term, start_cell, &fg, &bg, &cursor_color, &text_color, gamma_correct); int x = term->margins.left + (col_idx + start) * term->cell_width; int y = term->margins.top + row_idx * term->cell_height; @@ -1789,12 +1820,14 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, row->cells[col_idx + i] = real_cells[i]; free(real_cells); + const int damage_x = term->margins.left + col_idx * term->cell_width; + const int damage_y = term->margins.top + row_idx * term->cell_height; + const int damage_w = cells_used * term->cell_width; + const int damage_h = term->cell_height; + wl_surface_damage_buffer( term->window->surface.surf, - term->margins.left, - term->margins.top + row_idx * term->cell_height, - term->width - term->margins.left - term->margins.right, - 1 * term->cell_height); + damage_x, damage_y, damage_w, damage_h); } #endif @@ -1916,7 +1949,7 @@ render_overlay(struct terminal *term) case OVERLAY_FLASH: color = color_hex_to_pixman_with_alpha( term->conf->colors.flash, - term->conf->colors.flash_alpha); + term->conf->colors.flash_alpha, render_do_linear_blending(term)); break; case OVERLAY_NONE: @@ -2118,6 +2151,7 @@ render_worker_thread(void *_ctx) sem_wait(start); struct buffer *buf = term->render.workers.buf; + bool frame_done = false; /* Translate offset-relative cursor row to view-relative */ @@ -2138,8 +2172,6 @@ render_worker_thread(void *_ctx) switch (row_no) { default: { - xassert(buf != NULL); - struct row *row = grid_row_in_view(term->grid, row_no); int cursor_col = cursor.row == row_no ? cursor.col : -1; @@ -2259,13 +2291,14 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf, pixman_image_set_clip_region32(buf->pix[0], &clip); pixman_region32_fini(&clip); + const bool gamma_correct = render_do_linear_blending(term); uint16_t alpha = _bg >> 24 | (_bg >> 24 << 8); - pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha); + pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct); pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &bg, 1, &(pixman_rectangle16_t){0, 0, buf->width, buf->height}); - pixman_color_t fg = color_hex_to_pixman(_fg); + pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct); const int x_ofs = term->font_x_ofs; const size_t len = c32len(text); @@ -2312,7 +2345,7 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf, for (size_t i = 0; i < glyph_count; i++) { const struct fcft_glyph *glyph = glyphs[i]; - if (pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) { + if (unlikely(glyph->is_color_glyph)) { pixman_image_composite32( PIXMAN_OP_OVER, glyph->pix, NULL, buf->pix[0], 0, 0, 0, 0, x + x_ofs + glyph->x, y - glyph->y, @@ -2399,8 +2432,11 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, if (info->width == 0 || info->height == 0) return; + const bool gamma_correct = render_do_linear_blending(term); + { - pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0); + /* Fully transparent - no need to do a color space transform */ + pixman_color_t color = color_hex_to_pixman_with_alpha(0, 0, gamma_correct); render_csd_part(term, surf->surf, buf, info->width, info->height, &color); } @@ -2461,7 +2497,8 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, _color = color_dim(term, _color); uint16_t alpha = _color >> 24 | (_color >> 24 << 8); - pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha); + pixman_color_t color = + color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct); pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &color, 1, @@ -2472,8 +2509,9 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, } static pixman_color_t -get_csd_button_fg_color(const struct config *conf) +get_csd_button_fg_color(const struct terminal *term) { + const struct config *conf = term->conf; uint32_t _color = conf->colors.bg; uint16_t alpha = 0xffff; @@ -2482,13 +2520,14 @@ get_csd_button_fg_color(const struct config *conf) alpha = _color >> 24 | (_color >> 24 << 8); } - return color_hex_to_pixman_with_alpha(_color, alpha); + return color_hex_to_pixman_with_alpha( + _color, alpha, render_do_linear_blending(term)); } static void render_csd_button_minimize(struct terminal *term, struct buffer *buf) { - pixman_color_t color = get_csd_button_fg_color(term->conf); + pixman_color_t color = get_csd_button_fg_color(term); pixman_image_t *src = pixman_image_create_solid_fill(&color); const int max_height = buf->height / 3; @@ -2516,7 +2555,7 @@ static void render_csd_button_maximize_maximized( struct terminal *term, struct buffer *buf) { - pixman_color_t color = get_csd_button_fg_color(term->conf); + pixman_color_t color = get_csd_button_fg_color(term); pixman_image_t *src = pixman_image_create_solid_fill(&color); const int max_height = buf->height / 3; @@ -2548,7 +2587,7 @@ static void render_csd_button_maximize_window( struct terminal *term, struct buffer *buf) { - pixman_color_t color = get_csd_button_fg_color(term->conf); + pixman_color_t color = get_csd_button_fg_color(term); pixman_image_t *src = pixman_image_create_solid_fill(&color); const int max_height = buf->height / 3; @@ -2588,7 +2627,7 @@ render_csd_button_maximize(struct terminal *term, struct buffer *buf) static void render_csd_button_close(struct terminal *term, struct buffer *buf) { - pixman_color_t color = get_csd_button_fg_color(term->conf); + pixman_color_t color = get_csd_button_fg_color(term); pixman_image_t *src = pixman_image_create_solid_fill(&color); const int max_height = buf->height / 3; @@ -2759,14 +2798,14 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, if (!term->visual_focus) _color = color_dim(term, _color); - pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha); + const bool gamma_correct = render_do_linear_blending(term); + pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct); render_csd_part(term, surf->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: BUG("unhandled surface type: %u", (unsigned)surf_idx); @@ -3618,6 +3657,7 @@ render_search_box(struct terminal *term) : term->conf->colors.use_custom.search_box_no_match; /* Background - yellow on empty/match, red on mismatch (default) */ + const bool gamma_correct = render_do_linear_blending(term); const pixman_color_t color = color_hex_to_pixman( is_match ? (custom_colors @@ -3625,13 +3665,14 @@ render_search_box(struct terminal *term) : term->colors.table[3]) : (custom_colors ? term->conf->colors.search_box.no_match.bg - : term->colors.table[1])); + : term->colors.table[1]), + gamma_correct); pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &color, 1, &(pixman_rectangle16_t){width - visible_width, 0, visible_width, height}); - pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0); + pixman_color_t transparent = color_hex_to_pixman_with_alpha(0, 0, gamma_correct); pixman_image_fill_rectangles( PIXMAN_OP_SRC, buf->pix[0], &transparent, 1, &(pixman_rectangle16_t){0, 0, width - visible_width, height}); @@ -3641,12 +3682,14 @@ render_search_box(struct terminal *term) const int x_ofs = term->font_x_ofs; int x = x_left; int y = margin; + pixman_color_t fg = color_hex_to_pixman( custom_colors ? (is_match ? term->conf->colors.search_box.match.fg : term->conf->colors.search_box.no_match.fg) - : term->colors.table[0]); + : term->colors.table[0], + gamma_correct); /* Move offset we start rendering at, to ensure the cursor is visible */ for (size_t i = 0, cell_idx = 0; i <= term->search.cursor; cell_idx += widths[i], i++) { @@ -3802,8 +3845,7 @@ render_search_box(struct terminal *term) continue; } - if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) { - /* Glyph surface is a pre-rendered image (typically a color emoji...) */ + if (unlikely(glyph->is_color_glyph)) { pixman_image_composite32( PIXMAN_OP_OVER, glyph->pix, NULL, buf->pix[0], 0, 0, 0, 0, x + x_ofs + glyph->x, y + term->font_baseline - glyph->y, @@ -5186,3 +5228,14 @@ render_xcursor_set(struct seat *seat, struct terminal *term, seat->pointer.xcursor_pending = true; return true; } + +bool +render_do_linear_blending(const struct terminal *term) +{ +#if defined(HAVE_WP_COLOR_MANAGEMENT) + return term->conf->gamma_correct != GAMMA_CORRECT_DISABLED && + term->wl->color_management.img_description != NULL; +#else + return false; +#endif +} diff --git a/render.h b/render.h index 81d2a905..c7b8e4a5 100644 --- a/render.h +++ b/render.h @@ -47,3 +47,5 @@ struct csd_data { }; struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx); + +bool render_do_linear_blending(const struct terminal *term); diff --git a/scripts/srgb.py b/scripts/srgb.py new file mode 100755 index 00000000..7655dbe4 --- /dev/null +++ b/scripts/srgb.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +import argparse +import math +import sys + + +def srgb_to_linear(f: float) -> float: + assert(f >= 0 and f <= 1.0) + + if f <= 0.04045: + return f / 12.92 + + return math.pow((f + 0.055) / 1.055, 2.4) + + +def linear_to_srgb(f: float) -> float: + if f < 0.0031308: + return f * 12.92 + + return 1.055 * math.pow(f, 1 / 2.4) - 0.055 + + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('c_output', type=argparse.FileType('w')) + parser.add_argument('h_output', type=argparse.FileType('w')) + opts = parser.parse_args() + + linear_table: list[int] = [] + srgb_table: list[int] = [] + + for i in range(256): + linear_table.append(int(srgb_to_linear(float(i) / 255) * 65535 + 0.5)) + + for i in range(4096): + srgb_table.append(int(linear_to_srgb(float(i) / 4095) * 255 + 0.5)) + + for i in range(256): + while True: + linear = linear_table[i] + srgb = srgb_table[linear >> 4] + + if i == srgb: + break + + linear_table[i] += 1 + + + opts.h_output.write("#pragma once\n") + opts.h_output.write("#include \n") + opts.h_output.write("\n") + opts.h_output.write('/* 8-bit input, 16-bit output */\n') + opts.h_output.write("extern const uint16_t srgb_decode_8_to_16_table[256];") + + opts.h_output.write('\n') + opts.h_output.write('static inline uint16_t\n') + opts.h_output.write('srgb_decode_8_to_16(uint8_t v)\n') + opts.h_output.write('{\n') + opts.h_output.write(' return srgb_decode_8_to_16_table[v];\n') + opts.h_output.write('}\n') + + opts.h_output.write('\n') + opts.h_output.write('/* 8-bit input, 8-bit output */\n') + opts.h_output.write("extern const uint8_t srgb_decode_8_to_8_table[256];\n") + + opts.h_output.write('\n') + opts.h_output.write('static inline uint8_t\n') + opts.h_output.write('srgb_decode_8_to_8(uint8_t v)\n') + opts.h_output.write('{\n') + opts.h_output.write(' return srgb_decode_8_to_8_table[v];\n') + opts.h_output.write('}\n') + + opts.c_output.write('#include "srgb.h"\n') + opts.c_output.write('\n') + + opts.c_output.write("const uint16_t srgb_decode_8_to_16_table[256] = {\n") + for i in range(256): + opts.c_output.write(f' {linear_table[i]},\n') + opts.c_output.write('};\n') + + opts.c_output.write("const uint8_t srgb_decode_8_to_8_table[256] = {\n") + for i in range(256): + opts.c_output.write(f' {linear_table[i] >> 8},\n') + opts.c_output.write('};\n') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/shm.c b/shm.c index 9a745f6c..32e6bdd0 100644 --- a/shm.c +++ b/shm.c @@ -92,6 +92,12 @@ struct buffer_chain { struct wl_shm *shm; size_t pix_instances; bool scrollable; + + pixman_format_code_t pixman_fmt_without_alpha; + enum wl_shm_format shm_format_without_alpha; + + pixman_format_code_t pixman_fmt_with_alpha; + enum wl_shm_format shm_format_with_alpha; }; static tll(struct buffer_private *) deferred; @@ -115,6 +121,7 @@ buffer_destroy_dont_close(struct buffer *buf) if (buf->pix[i] != NULL) pixman_image_unref(buf->pix[i]); } + if (buf->wl_buf != NULL) wl_buffer_destroy(buf->wl_buf); @@ -262,7 +269,9 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset) wl_buf = wl_shm_pool_create_buffer( pool->wl_pool, new_offset, buf->public.width, buf->public.height, buf->public.stride, - buf->with_alpha ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888); + buf->with_alpha + ? buf->chain->shm_format_with_alpha + : buf->chain->shm_format_without_alpha); if (wl_buf == NULL) { LOG_ERR("failed to create SHM buffer"); @@ -272,9 +281,12 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset) /* One pixman image for each worker thread (do we really need multiple?) */ for (size_t i = 0; i < buf->public.pix_instances; i++) { pix[i] = pixman_image_create_bits_no_clear( - buf->with_alpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, + buf->with_alpha + ? buf->chain->pixman_fmt_with_alpha + : buf->chain->pixman_fmt_without_alpha, buf->public.width, buf->public.height, (uint32_t *)mmapped, buf->public.stride); + if (pix[i] == NULL) { LOG_ERR("failed to create pixman image"); goto err; @@ -959,14 +971,74 @@ shm_unref(struct buffer *_buf) } struct buffer_chain * -shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances) +shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, + bool ten_bit_if_capable) { + pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8; + enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888; + + pixman_format_code_t pixman_fmt_with_alpha = PIXMAN_a8r8g8b8; + enum wl_shm_format shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB8888; + + static bool have_logged = false; + + + if (ten_bit_if_capable) { + if (wayl->shm_have_argb2101010 && wayl->shm_have_xrgb2101010) { + pixman_fmt_without_alpha = PIXMAN_x2r10g10b10; + shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB2101010; + + pixman_fmt_with_alpha = PIXMAN_a2r10g10b10; + shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB2101010; + + if (!have_logged) { + have_logged = true; + LOG_INFO("using 10-bit RGB surfaces"); + } + } + + else if (wayl->shm_have_abgr2101010 && wayl->shm_have_xbgr2101010) { + pixman_fmt_without_alpha = PIXMAN_x2b10g10r10; + shm_fmt_without_alpha = WL_SHM_FORMAT_XBGR2101010; + + pixman_fmt_with_alpha = PIXMAN_a2b10g10r10; + shm_fmt_with_alpha = WL_SHM_FORMAT_ABGR2101010; + + if (!have_logged) { + have_logged = true; + LOG_INFO("using 10-bit BGR surfaces"); + } + } + + else { + if (!have_logged) { + have_logged = true; + + LOG_WARN( + "10-bit surfaces requested, but compositor does not " + "implement ARGB2101010+XRGB2101010, or " + "ABGR2101010+XBGR2101010. Falling back to 8-bit surfaces"); + } + } + } else { + if (!have_logged) { + have_logged = true; + LOG_INFO("using 8-bit RGB surfaces"); + } + } + struct buffer_chain *chain = xmalloc(sizeof(*chain)); *chain = (struct buffer_chain){ .bufs = tll_init(), - .shm = shm, + .shm = wayl->shm, .pix_instances = pix_instances, .scrollable = scrollable, + + .pixman_fmt_without_alpha = pixman_fmt_without_alpha, + .shm_format_without_alpha = shm_fmt_without_alpha, + + .pixman_fmt_with_alpha = pixman_fmt_with_alpha, + .shm_format_with_alpha = shm_fmt_with_alpha, }; return chain; } diff --git a/shm.h b/shm.h index b4b075ca..2af185c9 100644 --- a/shm.h +++ b/shm.h @@ -9,6 +9,8 @@ #include +#include "wayland.h" + struct damage; struct buffer { @@ -43,7 +45,8 @@ void shm_set_max_pool_size(off_t max_pool_size); struct buffer_chain; struct buffer_chain *shm_chain_new( - struct wl_shm *shm, bool scrollable, size_t pix_instances); + struct wayland *wayl, bool scrollable, size_t pix_instances, + bool ten_bit_it_if_capable); void shm_chain_free(struct buffer_chain *chain); /* diff --git a/sixel.c b/sixel.c index 44a5995b..dd933d7a 100644 --- a/sixel.c +++ b/sixel.c @@ -10,6 +10,7 @@ #include "grid.h" #include "hsl.h" #include "render.h" +#include "srgb.h" #include "util.h" #include "xmalloc.h" #include "xsnprintf.h" @@ -19,6 +20,40 @@ static size_t count; static void sixel_put_generic(struct terminal *term, uint8_t c); static void sixel_put_ar_11(struct terminal *term, uint8_t c); +static uint32_t +color_decode_srgb(const struct terminal *term, uint16_t r, uint16_t g, uint16_t b) +{ + if (term->sixel.linear_blending) { + if (term->sixel.use_10bit) { + r = srgb_decode_8_to_16(r) >> 6; + g = srgb_decode_8_to_16(g) >> 6; + b = srgb_decode_8_to_16(b) >> 6; + } else { + r = srgb_decode_8_to_8(r); + g = srgb_decode_8_to_8(g); + b = srgb_decode_8_to_8(b); + } + } else { + if (term->sixel.use_10bit) { + r <<= 2; + g <<= 2; + b <<= 2; + } + } + + uint32_t color; + + if (term->sixel.use_10bit) { + if (PIXMAN_FORMAT_TYPE(term->sixel.pixman_fmt) == PIXMAN_TYPE_ARGB) + color = 0x3u << 30 | r << 20 | g << 10 | b; + else + color = 0x3u << 30 | b << 20 | g << 10 | r; + } else + color = 0xffu << 24 | r << 16 | g << 8 | b; + + return color; +} + void sixel_fini(struct terminal *term) { @@ -75,6 +110,23 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) term->sixel.image.height = 0; term->sixel.image.alloc_height = 0; term->sixel.image.bottom_pixel = 0; + term->sixel.linear_blending = render_do_linear_blending(term); + term->sixel.pixman_fmt = PIXMAN_a8r8g8b8; + + if (term->conf->tweak.surface_bit_depth == SHM_10_BIT) { + if (term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) { + term->sixel.use_10bit = true; + term->sixel.pixman_fmt = PIXMAN_a2r10g10b10; + } + + else if (term->wl->shm_have_abgr2101010 && term->wl->shm_have_xbgr2101010) { + term->sixel.use_10bit = true; + term->sixel.pixman_fmt = PIXMAN_a2b10g10r10; + } + } + + const size_t active_palette_entries = min( + ALEN(term->conf->colors.sixel), term->sixel.palette_size); if (term->sixel.use_private_palette) { xassert(term->sixel.private_palette == NULL); @@ -83,11 +135,18 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) memcpy( term->sixel.private_palette, term->conf->colors.sixel, - min(sizeof(term->conf->colors.sixel), - term->sixel.palette_size * sizeof(term->sixel.private_palette[0]))); + active_palette_entries * sizeof(term->sixel.private_palette[0])); + + if (term->sixel.linear_blending || term->sixel.use_10bit) { + for (size_t i = 0; i < active_palette_entries; i++) { + uint8_t r = (term->sixel.private_palette[i] >> 16) & 0xff; + uint8_t g = (term->sixel.private_palette[i] >> 8) & 0xff; + uint8_t b = (term->sixel.private_palette[i] >> 0) & 0xff; + term->sixel.private_palette[i] = color_decode_srgb(term, r, g, b); + } + } term->sixel.palette = term->sixel.private_palette; - } else { if (term->sixel.shared_palette == NULL) { term->sixel.shared_palette = xcalloc( @@ -95,8 +154,16 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) memcpy( term->sixel.shared_palette, term->conf->colors.sixel, - min(sizeof(term->conf->colors.sixel), - term->sixel.palette_size * sizeof(term->sixel.shared_palette[0]))); + active_palette_entries * sizeof(term->sixel.shared_palette[0])); + + if (term->sixel.linear_blending || term->sixel.use_10bit) { + for (size_t i = 0; i < active_palette_entries; i++) { + uint8_t r = (term->sixel.private_palette[i] >> 16) & 0xff; + uint8_t g = (term->sixel.private_palette[i] >> 8) & 0xff; + uint8_t b = (term->sixel.private_palette[i] >> 0) & 0xff; + term->sixel.private_palette[i] = color_decode_srgb(term, r, g, b); + } + } } else { /* Shared palette - do *not* reset palette for new sixels */ } @@ -488,7 +555,7 @@ blend_new_image_over_old(const struct terminal *term, int stride = new_width * sizeof(uint32_t); uint32_t *new_data = xmalloc(stride * new_height); pixman_image_t *pix2 = pixman_image_create_bits_no_clear( - PIXMAN_a8r8g8b8, new_width, new_height, new_data, stride); + term->sixel.pixman_fmt, new_width, new_height, new_data, stride); #if defined(_DEBUG) /* Fill new image with an easy-to-recognize color (green) */ @@ -651,8 +718,7 @@ sixel_overwrite(struct terminal *term, struct sixel *six, } pixman_image_t *new_pix = pixman_image_create_bits_no_clear( - PIXMAN_a8r8g8b8, - new_width, new_height, new_data, new_width * sizeof(uint32_t)); + term->sixel.pixman_fmt, new_width, new_height, new_data, new_width * sizeof(uint32_t)); struct sixel new_six = { .pix = NULL, @@ -948,7 +1014,7 @@ sixel_sync_cache(const struct terminal *term, struct sixel *six) uint8_t *scaled_data = xmalloc(scaled_height * scaled_stride); pixman_image_t *scaled_pix = pixman_image_create_bits_no_clear( - PIXMAN_a8r8g8b8, scaled_width, scaled_height, + term->sixel.pixman_fmt, scaled_width, scaled_height, (uint32_t *)scaled_data, scaled_stride); pixman_image_composite32( @@ -1232,7 +1298,7 @@ sixel_unhook(struct terminal *term) image.pos.row, image.pos.row + image.rows); image.original.pix = pixman_image_create_bits_no_clear( - PIXMAN_a8r8g8b8, image.original.width, image.original.height, + term->sixel.pixman_fmt, image.original.width, image.original.height, img_data, stride); pixel_row_idx += height; @@ -2006,15 +2072,14 @@ decgci(struct terminal *term, uint8_t c) } case 2: { /* RGB */ - uint8_t r = 255 * min(c1, 100) / 100; - uint8_t g = 255 * min(c2, 100) / 100; - uint8_t b = 255 * min(c3, 100) / 100; + uint16_t r = 255 * min(c1, 100) / 100; + uint16_t g = 255 * min(c2, 100) / 100; + uint16_t b = 255 * min(c3, 100) / 100; - LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu", + LOG_DBG("setting palette #%d = RGB %hu/%hu/%hu", term->sixel.color_idx, r, g, b); - term->sixel.palette[term->sixel.color_idx] = - 0xffu << 24 | r << 16 | g << 8 | b; + term->sixel.palette[term->sixel.color_idx] = color_decode_srgb(term, r, g, b); break; } } diff --git a/terminal.c b/terminal.c index 0cd4fbca..1c607787 100644 --- a/terminal.c +++ b/terminal.c @@ -991,6 +991,7 @@ struct font_load_data { const char **names; const char *attrs; + const struct fcft_font_options *options; struct fcft_font **font; }; @@ -998,7 +999,8 @@ static int font_loader_thread(void *_data) { struct font_load_data *data = _data; - *data->font = fcft_from_name(data->count, data->names, data->attrs); + *data->font = fcft_from_name2( + data->count, data->names, data->attrs, data->options); return *data->font != NULL; } @@ -1065,14 +1067,32 @@ reload_fonts(struct terminal *term, bool resize_grid) [1] = xstrjoin(dpi, !custom_bold ? ":weight=bold" : ""), [2] = xstrjoin(dpi, !custom_italic ? ":slant=italic" : ""), [3] = xstrjoin(dpi, !custom_bold_italic ? ":weight=bold:slant=italic" : ""), - }; + }; + + struct fcft_font_options *options = fcft_font_options_create(); + + options->color_glyphs.format = PIXMAN_a8r8g8b8; + options->color_glyphs.srgb_decode = render_do_linear_blending(term); + + if (conf->tweak.surface_bit_depth == SHM_10_BIT) { + if ((term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) || + (term->wl->shm_have_abgr2101010 && term->wl->shm_have_xbgr2101010)) + { + /* + * Use a high-res buffer type for emojis. We don't want to + * use an a2r10g0b10 type of surface, since we need more + * than 2 bits for alpha. + */ + options->color_glyphs.format = PIXMAN_rgba_float; + } + } struct fcft_font *fonts[4]; struct font_load_data data[4] = { - {count_regular, names_regular, attrs[0], &fonts[0]}, - {count_bold, names_bold, attrs[1], &fonts[1]}, - {count_italic, names_italic, attrs[2], &fonts[2]}, - {count_bold_italic, names_bold_italic, attrs[3], &fonts[3]}, + {count_regular, names_regular, attrs[0], options, &fonts[0]}, + {count_bold, names_bold, attrs[1], options, &fonts[1]}, + {count_italic, names_italic, attrs[2], options, &fonts[2]}, + {count_bold_italic, names_bold_italic, attrs[3], options, &fonts[3]}, }; thrd_t tids[4] = {0}; @@ -1097,6 +1117,8 @@ reload_fonts(struct terminal *term, bool resize_grid) success = false; } + fcft_font_options_destroy(options); + for (size_t i = 0; i < 4; i++) { for (size_t j = 0; j < counts[i]; j++) free(names[i][j]); @@ -1237,6 +1259,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, goto err; } + const bool ten_bit_surfaces = conf->tweak.surface_bit_depth == SHM_10_BIT; + /* Initialize configure-based terminal attributes */ *term = (struct terminal) { .fdm = fdm, @@ -1320,13 +1344,14 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .wl = wayl, .render = { .chains = { - .grid = shm_chain_new(wayl->shm, true, 1 + conf->render_worker_count), - .search = shm_chain_new(wayl->shm, false, 1), - .scrollback_indicator = shm_chain_new(wayl->shm, false, 1), - .render_timer = shm_chain_new(wayl->shm, false, 1), - .url = shm_chain_new(wayl->shm, false, 1), - .csd = shm_chain_new(wayl->shm, false, 1), - .overlay = shm_chain_new(wayl->shm, false, 1), + .grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count, + ten_bit_surfaces), + .search = shm_chain_new(wayl, false, 1 ,ten_bit_surfaces), + .scrollback_indicator = shm_chain_new(wayl, false, 1, ten_bit_surfaces), + .render_timer = shm_chain_new(wayl, false, 1, ten_bit_surfaces), + .url = shm_chain_new(wayl, false, 1, ten_bit_surfaces), + .csd = shm_chain_new(wayl, false, 1, ten_bit_surfaces), + .overlay = shm_chain_new(wayl, false, 1, ten_bit_surfaces), }, .scrollback_lines = conf->scrollback.lines, .app_sync_updates.timer_fd = app_sync_updates_fd, @@ -1468,6 +1493,9 @@ term_window_configured(struct terminal *term) if (!term->shutdown.in_progress) { xassert(term->window->is_configured); fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term); + + const bool gamma_correct = render_do_linear_blending(term); + LOG_INFO("gamma-correct blending: %s", gamma_correct ? "enabled" : "disabled"); } } diff --git a/terminal.h b/terminal.h index e03b7bf7..518e36ef 100644 --- a/terminal.h +++ b/terminal.h @@ -777,6 +777,10 @@ struct terminal { bool transparent_bg; + bool linear_blending; + bool use_10bit; + pixman_format_code_t pixman_fmt; + /* Application configurable */ unsigned palette_size; /* Number of colors in palette */ unsigned max_width; /* Maximum image width, in pixels */ diff --git a/wayland.c b/wayland.c index 3a46133f..1c083a9e 100644 --- a/wayland.c +++ b/wayland.c @@ -237,6 +237,15 @@ seat_destroy(struct seat *seat) static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { + struct wayland *wayl = data; + + switch (format) { + case WL_SHM_FORMAT_XRGB2101010: wayl->shm_have_xrgb2101010 = true; break; + case WL_SHM_FORMAT_ARGB2101010: wayl->shm_have_argb2101010 = true; break; + case WL_SHM_FORMAT_XBGR2101010: wayl->shm_have_xbgr2101010 = true; break; + case WL_SHM_FORMAT_ABGR2101010: wayl->shm_have_abgr2101010 = true; break; + } + #if defined(_DEBUG) bool have_description = false; @@ -666,6 +675,91 @@ static const struct wp_presentation_listener presentation_listener = { .clock_id = &clock_id, }; +#if defined(HAVE_WP_COLOR_MANAGEMENT) + +static void +color_manager_create_image_description(struct wayland *wayl) +{ + if (!wayl->color_management.have_feat_parametric) + return; + + if (!wayl->color_management.have_primaries_srgb) + return; + + if (!wayl->color_management.have_tf_ext_linear) + return; + + struct wp_image_description_creator_params_v1 *params = + wp_color_manager_v1_create_parametric_creator(wayl->color_management.manager); + + wp_image_description_creator_params_v1_set_tf_named( + params, WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR); + + wp_image_description_creator_params_v1_set_primaries_named( + params, WP_COLOR_MANAGER_V1_PRIMARIES_SRGB); + + wayl->color_management.img_description = + wp_image_description_creator_params_v1_create(params); +} + +static void +color_manager_supported_intent(void *data, + struct wp_color_manager_v1 *wp_color_manager_v1, + uint32_t render_intent) +{ + struct wayland *wayl = data; + if (render_intent == WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL) + wayl->color_management.have_intent_perceptual = true; +} + +static void +color_manager_supported_feature(void *data, + struct wp_color_manager_v1 *wp_color_manager_v1, + uint32_t feature) +{ + struct wayland *wayl = data; + + if (feature == WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC) + wayl->color_management.have_feat_parametric = true; +} + +static void +color_manager_supported_tf_named(void *data, + struct wp_color_manager_v1 *wp_color_manager_v1, + uint32_t tf) +{ + struct wayland *wayl = data; + if (tf == WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR) + wayl->color_management.have_tf_ext_linear = true; +} + +static void +color_manager_supported_primaries_named(void *data, + struct wp_color_manager_v1 *wp_color_manager_v1, + uint32_t primaries) +{ + struct wayland *wayl = data; + if (primaries == WP_COLOR_MANAGER_V1_PRIMARIES_SRGB) + wayl->color_management.have_primaries_srgb = true; +} + +static void +color_manager_done(void *data, + struct wp_color_manager_v1 *wp_color_manager_v1) +{ + struct wayland *wayl = data; + color_manager_create_image_description(wayl); +} + +static const struct wp_color_manager_v1_listener color_manager_listener = { + .supported_intent = &color_manager_supported_intent, + .supported_feature = &color_manager_supported_feature, + .supported_primaries_named = &color_manager_supported_primaries_named, + .supported_tf_named = &color_manager_supported_tf_named, + .done = &color_manager_done, +}; +#endif + static bool verify_iface_version(const char *iface, uint32_t version, uint32_t wanted) { @@ -1385,6 +1479,20 @@ handle_global(void *data, struct wl_registry *registry, } #endif +#if defined(HAVE_WP_COLOR_MANAGEMENT) + else if (streq(interface, wp_color_manager_v1_interface.name)) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->color_management.manager = wl_registry_bind( + wayl->registry, name, &wp_color_manager_v1_interface, required); + + wp_color_manager_v1_add_listener( + wayl->color_management.manager, &color_manager_listener, wayl); + } +#endif + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (streq(interface, zwp_text_input_manager_v3_interface.name)) { const uint32_t required = 1; @@ -1707,6 +1815,13 @@ wayl_destroy(struct wayland *wayl) zwp_text_input_manager_v3_destroy(wayl->text_input_manager); #endif +#if defined(HAVE_WP_COLOR_MANAGEMENT) + if (wayl->color_management.img_description != NULL) + wp_image_description_v1_destroy(wayl->color_management.img_description); + if (wayl->color_management.manager != NULL) + wp_color_manager_v1_destroy(wayl->color_management.manager); +#endif + #if defined(HAVE_XDG_SYSTEM_BELL) if (wayl->system_bell != NULL) xdg_system_bell_v1_destroy(wayl->system_bell); @@ -1847,6 +1962,38 @@ wayl_win_init(struct terminal *term, const char *token) } #endif +#if defined(HAVE_WP_COLOR_MANAGEMENT) + if (term->conf->gamma_correct != GAMMA_CORRECT_DISABLED) { + if (wayl->color_management.img_description != NULL) { + xassert(wayl->color_management.manager != NULL); + + win->surface.color_management = wp_color_manager_v1_get_surface( + term->wl->color_management.manager, win->surface.surf); + + wp_color_management_surface_v1_set_image_description( + win->surface.color_management, wayl->color_management.img_description, + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); + } else if (term->conf->gamma_correct == GAMMA_CORRECT_ENABLED) { + if (wayl->color_management.manager == NULL) { + LOG_WARN( + "gamma-corrected-blending: disabling; " + "compositor does not implement the color-management protocol"); + } else { + LOG_WARN( + "gamma-corrected-blending: disabling; " + "compositor does not implement all required color-management features"); + LOG_WARN("use e.g. 'wayland-info' and verify the compositor implements:"); + LOG_WARN(" - feature: parametric"); + LOG_WARN(" - render intent: perceptual"); + LOG_WARN(" - TF: ext_linear"); + LOG_WARN(" - primaries: sRGB"); + } + } else { + /* "auto" - don't warn */ + } + } +#endif + if (conf->csd.preferred == CONF_CSD_PREFER_NONE) { /* User specifically do *not* want decorations */ win->csd_mode = CSD_NO; @@ -1861,8 +2008,8 @@ wayl_win_init(struct terminal *term, const char *token) 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_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); @@ -1987,7 +2134,12 @@ wayl_win_destroy(struct wl_window *win) free(it->item); tll_remove(win->xdg_tokens, it); - } +} + +#if defined(HAVE_WP_COLOR_MANAGEMENT) + if (win->surface.color_management != NULL) + wp_color_management_surface_v1_destroy(win->surface.color_management); +#endif if (win->fractional_scale != NULL) wp_fractional_scale_v1_destroy(win->fractional_scale); @@ -2308,6 +2460,7 @@ wayl_win_subsurface_new_with_custom_parent( struct wayland *wayl = win->term->wl; surf->surface.surf = NULL; + surf->surface.viewport = NULL; surf->sub = NULL; struct wl_surface *main_surface @@ -2318,6 +2471,22 @@ wayl_win_subsurface_new_with_custom_parent( return false; } +#if defined(HAVE_WP_COLOR_MANAGEMENT) + surf->surface.color_management = NULL; + if (win->term->conf->gamma_correct && + wayl->color_management.img_description != NULL) + { + xassert(wayl->color_management.manager != NULL); + + surf->surface.color_management = wp_color_manager_v1_get_surface( + wayl->color_management.manager, main_surface); + + wp_color_management_surface_v1_set_image_description( + surf->surface.color_management, wayl->color_management.img_description, + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); + } +#endif + struct wl_subsurface *sub = wl_subcompositor_get_subsurface( wayl->sub_compositor, main_surface, parent); @@ -2369,6 +2538,13 @@ wayl_win_subsurface_destroy(struct wayl_sub_surface *surf) if (surf == NULL) return; +#if defined(HAVE_WP_COLOR_MANAGEMENT) + if (surf->surface.color_management != NULL) { + wp_color_management_surface_v1_destroy(surf->surface.color_management); + surf->surface.color_management = NULL; + } +#endif + if (surf->surface.viewport != NULL) { wp_viewport_destroy(surf->surface.viewport); surf->surface.viewport = NULL; diff --git a/wayland.h b/wayland.h index b3ef5a2b..ec27281a 100644 --- a/wayland.h +++ b/wayland.h @@ -28,6 +28,10 @@ #include #endif +#if defined(HAVE_WP_COLOR_MANAGEMENT) + #include +#endif + #include #include @@ -61,6 +65,9 @@ enum touch_state { struct wayl_surface { struct wl_surface *surf; struct wp_viewport *viewport; +#if defined(HAVE_WP_COLOR_MANAGEMENT) + struct wp_color_management_surface_v1 *color_management; +#endif }; struct wayl_sub_surface { @@ -459,6 +466,17 @@ struct wayland { struct xdg_system_bell_v1 *system_bell; #endif +#if defined(HAVE_WP_COLOR_MANAGEMENT) + struct { + struct wp_color_manager_v1 *manager; + struct wp_image_description_v1 *img_description; + bool have_intent_perceptual; + bool have_feat_parametric; + bool have_tf_ext_linear; + bool have_primaries_srgb; + } color_management; +#endif + bool presentation_timings; struct wp_presentation *presentation; uint32_t presentation_clock_id; @@ -474,6 +492,11 @@ struct wayland { /* WL_SHM >= 2 */ bool use_shm_release; + + bool shm_have_argb2101010:1; + bool shm_have_xrgb2101010:1; + bool shm_have_abgr2101010:1; + bool shm_have_xbgr2101010:1; }; struct wayland *wayl_init( From a80b32d006b24512bb8bd6c4d362f90abb4388d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 28 Feb 2025 08:11:50 +0100 Subject: [PATCH 121/353] term: tweak linebreaking Don't set linebreak on linefeed. Instead, rely on the default value of true, and that it is only cleared when a character is printed while LCF=1. Note that printing to a row that has linebreak cleared, will set the linebreak flag again. --- CHANGELOG.md | 6 ++++++ terminal.c | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 152b7728..cb3ae470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,8 +93,14 @@ based. * Rename Tokyo Night Day theme to Tokyo Night Light and update colors. * fcft >= 3.2.0 is now required. +* Linefeed:ing control characters (e.g. `\n`) no longer **clears** a + row's internal linebreak flag. This fixes an issue where + e.g. multi-line prompt input in fish is treated as separate lines, + rather than one logical, when selecting and copying it + ([#1487][1487]). [1925]: https://codeberg.org/dnkl/foot/issues/1925 +[1487]: https://codeberg.org/dnkl/foot/issues/1487 ### Deprecated diff --git a/terminal.c b/terminal.c index 1c607787..988716f4 100644 --- a/terminal.c +++ b/terminal.c @@ -3191,7 +3191,6 @@ term_carriage_return(struct terminal *term) void term_linefeed(struct terminal *term) { - term->grid->cur_row->linebreak = true; term->grid->cursor.lcf = false; if (term->grid->cursor.point.row == term->scroll_region.end - 1) From 7b6efcf19a10794dd7b41e30ce706eae3a290388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 4 Mar 2025 08:34:18 +0100 Subject: [PATCH 122/353] grid: change default value of linebreak to true This way, all lines are treated as having a hard linebreak, until it's cleared when we do an auto-wrap. This change alone causes issues when reflowing text, as now all trailing lines in an otherwise empty window are treated as hard linebreaks, causing the new grid to insert lots of unwanted, empty lines. Fix by doing two things: * *clear* the linebreak flag when we pull in new lines for the new grid. We only want to set it explicitly, when an old row has its linebreak flag set. * Coalesce empty lines with linebreak=true, and only "emit" them as new liens in the new grid if they are followed by non-empty lines. --- grid.c | 87 +++++++++++++++++++++++++++++++++++++----------------- terminal.c | 2 +- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/grid.c b/grid.c index 2deb9111..4b9a7fb6 100644 --- a/grid.c +++ b/grid.c @@ -439,7 +439,7 @@ grid_row_alloc(int cols, bool initialize) { struct row *row = xmalloc(sizeof(*row)); row->dirty = false; - row->linebreak = false; + row->linebreak = true; row->extra = NULL; row->shell_integration.prompt_marker = false; row->shell_integration.cmd_start = -1; @@ -709,14 +709,21 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, /* Scrollback not yet full, allocate a completely new row */ new_row = grid_row_alloc(col_count, false); new_grid[*row_idx] = new_row; + + /* *clear* linebreak, since we only want to set it when we + reach the end of an old row, with linebreak=true */ + new_row->linebreak = false; } else { /* Scrollback is full, need to reuse a row */ grid_row_reset_extra(new_row); - new_row->linebreak = false; new_row->shell_integration.prompt_marker = false; new_row->shell_integration.cmd_start = -1; new_row->shell_integration.cmd_end = -1; + /* *clear* linebreak, since we only want to set it when we + reach the end of an old row, with linebreak=true */ + new_row->linebreak = false; + tll_foreach(old_grid->sixel_images, it) { if (it->item.pos.row == *row_idx) { sixel_destroy(&it->item); @@ -894,6 +901,8 @@ grid_resize_and_reflow( i, tracking_points[i]->row, tracking_points[i]->col); } + int coalesced_linebreaks = 0; + /* * Walk the old grid */ @@ -984,6 +993,20 @@ grid_resize_and_reflow( } else underline_range = underline_range_terminator = NULL; + if (unlikely(col_count > 0 && coalesced_linebreaks > 0)) { + for (size_t apa = 0; apa < coalesced_linebreaks; apa++) { + /* Erase the remaining cells */ + memset(&new_row->cells[new_col_idx], 0, + (new_cols - new_col_idx) * sizeof(new_row->cells[0])); + new_row->linebreak = true; + + if (r + 1 < old_rows) + line_wrap(); + } + + coalesced_linebreaks = 0; + } + for (int c = 0; c < col_count;) { const struct cell *old = &old_row->cells[c]; @@ -1095,33 +1118,43 @@ grid_resize_and_reflow( } if (old_row->linebreak) { - /* Erase the remaining cells */ - memset(&new_row->cells[new_col_idx], 0, - (new_cols - new_col_idx) * sizeof(new_row->cells[0])); - new_row->linebreak = true; - - if (r + 1 < old_rows) - line_wrap(); - else if (new_row->extra != NULL) { - if (new_row->extra->uri_ranges.count > 0) { - /* - * line_wrap() "closes" still-open URIs. Since - * this is the *last* row, and since we're - * line-breaking due to a hard line-break (rather - * than running out of cells in the "new_row"), - * there shouldn't be an open URI (it would have - * been closed when we reached the end of the URI - * while reflowing the last "old" row). - */ - int last_idx = new_row->extra->uri_ranges.count - 1; - xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0); - } - - if (new_row->extra->underline_ranges.count > 0) { - int last_idx = new_row->extra->underline_ranges.count - 1; - xassert(new_row->extra->underline_ranges.v[last_idx].end >= 0); + if (col_count > 0) { + /* Erase the remaining cells */ + memset(&new_row->cells[new_col_idx], 0, + (new_cols - new_col_idx) * sizeof(new_row->cells[0])); + new_row->linebreak = true; + if (r + 1 < old_rows) { + /* Not the last (old) row */ + line_wrap(); + } else if (new_row->extra != NULL) { + if (new_row->extra->uri_ranges.count > 0) { + /* + * line_wrap() "closes" still-open URIs. Since + * this is the *last* row, and since we're + * line-breaking due to a hard line-break (rather + * than running out of cells in the "new_row"), + * there shouldn't be an open URI (it would have + * been closed when we reached the end of the URI + * while reflowing the last "old" row). + */ + int last_idx = new_row->extra->uri_ranges.count - 1; + xassert(new_row->extra->uri_ranges.v[last_idx].end >= 0); + } + + if (new_row->extra->underline_ranges.count > 0) { + int last_idx = new_row->extra->underline_ranges.count - 1; + xassert(new_row->extra->underline_ranges.v[last_idx].end >= 0); + } } + } else { + /* + * rows have linebreak=true by default. But we don't + * want trailing empty lines to result in actual lines + * in the new grid (think: empty window with prompt at + * the top) + */ + coalesced_linebreaks++; } } diff --git a/terminal.c b/terminal.c index 988716f4..2ba85276 100644 --- a/terminal.c +++ b/terminal.c @@ -2057,7 +2057,7 @@ static inline void erase_line(struct terminal *term, struct row *row) { erase_cell_range(term, row, 0, term->cols - 1); - row->linebreak = false; + row->linebreak = true; row->shell_integration.prompt_marker = false; row->shell_integration.cmd_start = -1; row->shell_integration.cmd_end = -1; From 605694bc938ace9a7501d6730c1865ea531870df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 5 Mar 2025 07:38:44 +0100 Subject: [PATCH 123/353] grid: set linebreak=false when printing to a line, not when allocating it This ensures empty lines are treated correctly, and is also more in line with how lines are handled at runtime, when filling the scrollback. For now, set linebreak=false as soon as something is printed on a line. It will remain like that *until* we reach the end of an old row with linebreak=true, at which point we set linebreak=true on the current new line. --- grid.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/grid.c b/grid.c index 4b9a7fb6..cb6e1a91 100644 --- a/grid.c +++ b/grid.c @@ -501,7 +501,6 @@ grid_resize_without_reflow( sizeof(struct cell) * min(old_cols, new_cols)); new_row->dirty = old_row->dirty; - new_row->linebreak = false; new_row->shell_integration.prompt_marker = old_row->shell_integration.prompt_marker; new_row->shell_integration.cmd_start = min(old_row->shell_integration.cmd_start, new_cols - 1); new_row->shell_integration.cmd_end = min(old_row->shell_integration.cmd_end, new_cols - 1); @@ -709,10 +708,6 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, /* Scrollback not yet full, allocate a completely new row */ new_row = grid_row_alloc(col_count, false); new_grid[*row_idx] = new_row; - - /* *clear* linebreak, since we only want to set it when we - reach the end of an old row, with linebreak=true */ - new_row->linebreak = false; } else { /* Scrollback is full, need to reuse a row */ grid_row_reset_extra(new_row); @@ -720,10 +715,6 @@ _line_wrap(struct grid *old_grid, struct row **new_grid, struct row *row, new_row->shell_integration.cmd_start = -1; new_row->shell_integration.cmd_end = -1; - /* *clear* linebreak, since we only want to set it when we - reach the end of an old row, with linebreak=true */ - new_row->linebreak = false; - tll_foreach(old_grid->sixel_images, it) { if (it->item.pos.row == *row_idx) { sixel_destroy(&it->item); @@ -1112,6 +1103,17 @@ grid_resize_and_reflow( } new_row->cells[new_col_idx++] = *old; + + /* + * TODO: simulate LCF instead? + * + * Rows have linebreak=true by default. This is needed + * for a number of reasons. However, we want non-empty + * rows to have linebreak=false, *until* we reach the + * end of an old row with linebreak=true, at which + * point we set linebreak=true on the new row. + */ + new_row->linebreak = false; old++; c++; } From 8d2627b1ef8fd7f8313e4441de3c76b873575a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 10 Mar 2025 15:47:20 +0100 Subject: [PATCH 124/353] input: kitty: always use shifted key when it's the result of a compose Closes #1987 --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index 916f30e4..a2867ac6 100644 --- a/input.c +++ b/input.c @@ -1411,7 +1411,7 @@ emit_escapes: size_t left = sizeof(buf); size_t bytes; - const int key = unshifted > 0 && isc32print(unshifted) ? unshifted : shifted; + const int key = unshifted > 0 && isc32print(unshifted) && !composed ? unshifted : shifted; const int alternate = shifted; if (final == 'u' || final == '~') { From 04fcc5f5b5d0a3f45d752e987a0c87baa4e50779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 11 Mar 2025 08:23:23 +0100 Subject: [PATCH 125/353] input: kitty: regression test for #1987 --- input.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/input.c b/input.c index a2867ac6..0507d8bd 100644 --- a/input.c +++ b/input.c @@ -2087,6 +2087,103 @@ UNITTEST seat.kbd.xkb_keymap = NULL; } + /* us(intl) keymap */ + { + seat.kbd.xkb_keymap = xkb_keymap_new_from_names( + seat.kbd.xkb, &(struct xkb_rule_names){.layout = "us", .variant = "intl"}, + XKB_KEYMAP_COMPILE_NO_FLAGS); + + if (seat.kbd.xkb_keymap == NULL) { + /* Skip test */ + goto no_keymap; + } + + seat.kbd.xkb_state = xkb_state_new(seat.kbd.xkb_keymap); + xassert(seat.kbd.xkb_state != NULL); + + seat.kbd.xkb_compose_table = xkb_compose_table_new_from_locale( + seat.kbd.xkb, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS); + xassert(seat.kbd.xkb_compose_table != NULL); + + seat.kbd.xkb_compose_state = xkb_compose_state_new( + seat.kbd.xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); + xassert(seat.kbd.xkb_compose_state != NULL); + + seat.kbd.mod_shift = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_SHIFT); + seat.kbd.mod_alt = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_ALT) ; + seat.kbd.mod_ctrl = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CTRL); + seat.kbd.mod_super = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_LOGO); + seat.kbd.mod_caps = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_CAPS); + seat.kbd.mod_num = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_NUM); + + /* Significant modifiers in the legacy keyboard protocol */ + seat.kbd.legacy_significant = 0; + if (seat.kbd.mod_shift != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_shift; + if (seat.kbd.mod_alt != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_alt; + if (seat.kbd.mod_ctrl != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_ctrl; + if (seat.kbd.mod_super != XKB_MOD_INVALID) + seat.kbd.legacy_significant |= 1 << seat.kbd.mod_super; + + /* Significant modifiers in the kitty keyboard protocol */ + seat.kbd.kitty_significant = seat.kbd.legacy_significant; + if (seat.kbd.mod_caps != XKB_MOD_INVALID) + seat.kbd.kitty_significant |= 1 << seat.kbd.mod_caps; + if (seat.kbd.mod_num != XKB_MOD_INVALID) + seat.kbd.kitty_significant |= 1 << seat.kbd.mod_num; + + key_binding_new_for_seat(key_binding_manager, &seat); + key_binding_load_keymap(key_binding_manager, &seat); + + { + /* + * Test the compose sequence "shift+', shift+space" + * + * Should result in a double quote, but a regression + * caused it to instead emit a space. See #1987 + * + * Note: "shift+', space" also results in a double quote, + * but never regressed to a space. + */ + grid.kitty_kbd.flags[0] = KITTY_KBD_DISAMBIGUATE; + xkb_compose_state_reset(seat.kbd.xkb_compose_state); + + xkb_mod_mask_t mods = 1u << seat.kbd.mod_shift; + keyboard_modifiers(&seat, NULL, 1337, mods, 0, 0, 1); + + key_press_release(&seat, &term, 1337, KEY_APOSTROPHE + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + key_press_release(&seat, &term, 1337, KEY_APOSTROPHE + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + + key_press_release(&seat, &term, 1337, KEY_SPACE + 8, WL_KEYBOARD_KEY_STATE_PRESSED); + + char escape[64] = {0}; + ssize_t count = read(chan[0], escape, sizeof(escape)); + + /* key: 34 = '"', alternate: N/A, base: N/A, mods: 2 = shift */ + const char expected_shift_apostrophe[] = "\033[34;2u"; + xassert(count == strlen(expected_shift_apostrophe)); + xassert(streq(escape, expected_shift_apostrophe)); + + key_press_release(&seat, &term, 1337, KEY_SPACE + 8, WL_KEYBOARD_KEY_STATE_RELEASED); + + grid.kitty_kbd.flags[0] = KITTY_KBD_DISAMBIGUATE | KITTY_KBD_REPORT_ALTERNATE; + } + + key_binding_unload_keymap(key_binding_manager, &seat); + key_binding_remove_seat(key_binding_manager, &seat); + + xkb_compose_state_unref(seat.kbd.xkb_compose_state); + xkb_compose_table_unref(seat.kbd.xkb_compose_table); + + xkb_state_unref(seat.kbd.xkb_state); + xkb_keymap_unref(seat.kbd.xkb_keymap); + + seat.kbd.xkb_state = NULL; + seat.kbd.xkb_keymap = NULL; + } + no_keymap: xkb_context_unref(seat.kbd.xkb); key_binding_manager_destroy(key_binding_manager); From 7976975a8a6382a1e81a5d6c2749bc5beb23ff41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 11 Mar 2025 08:36:37 +0100 Subject: [PATCH 126/353] input: kitty: send release events for composed keys --- input.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/input.c b/input.c index 0507d8bd..c0b2323f 100644 --- a/input.c +++ b/input.c @@ -1157,9 +1157,6 @@ kitty_kbd_protocol(struct seat *seat, struct terminal *term, if (!report_events && released) return false; - if (composed && released) - return false; - /* TODO: should we even bother with this, or just say it's not supported? */ if (!disambiguate && !report_all_as_escapes && pressed) return legacy_kbd_protocol(seat, term, ctx); From edbfdd51508ce6e4a44078db49f62151707eb928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 11 Mar 2025 08:37:42 +0100 Subject: [PATCH 127/353] changelog: kitty: release events for composed keys --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3ae470..b86c826e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -137,6 +137,8 @@ * Crash when resetting the terminal and an application had previously set a custom app ID ([#1963][1963]) * Grapheme clustering state not reset on cursor movements. +* Kitty keyboard protocol: no release events emitted for composed + keys. [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 From cfa178ab259528a106a2855bdd2e07c34a822224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 11 Mar 2025 08:42:03 +0100 Subject: [PATCH 128/353] input: kitty: unittest: don't fail if system has no compose tables --- input.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/input.c b/input.c index c0b2323f..cbe8e1c7 100644 --- a/input.c +++ b/input.c @@ -2100,11 +2100,15 @@ UNITTEST seat.kbd.xkb_compose_table = xkb_compose_table_new_from_locale( seat.kbd.xkb, setlocale(LC_CTYPE, NULL), XKB_COMPOSE_COMPILE_NO_FLAGS); - xassert(seat.kbd.xkb_compose_table != NULL); + if (seat.kbd.xkb_compose_table == NULL) + goto no_keymap; seat.kbd.xkb_compose_state = xkb_compose_state_new( seat.kbd.xkb_compose_table, XKB_COMPOSE_STATE_NO_FLAGS); - xassert(seat.kbd.xkb_compose_state != NULL); + if (seat.kbd.xkb_compose_state == NULL) { + xkb_compose_table_unref(seat.kbd.xkb_compose_table); + goto no_keymap; + } seat.kbd.mod_shift = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_SHIFT); seat.kbd.mod_alt = xkb_keymap_mod_get_index(seat.kbd.xkb_keymap, XKB_MOD_NAME_ALT) ; From 7f11ba59efad352b1ff0bb11c0286942ae3d1edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 12 Mar 2025 10:03:06 +0100 Subject: [PATCH 129/353] fcft: require fcft >= 3.3.0, add support for new scaling-filters Update tweak.scaling-filter to recognize the new scaling filters added in fcft-3.3.0. Since fcft_set_scaling_filter() is deprecated in 3.3.0, don't use it anymore, and set the scaling filter via fcft_font_options instead. --- CHANGELOG.md | 2 +- config.c | 7 +++++++ doc/foot.ini.5.scd | 5 ++--- main.c | 1 - meson.build | 2 +- terminal.c | 1 + 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b86c826e..0843c29d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,7 +92,7 @@ * Auto-detection of URLs (i.e. not OSC-8 based URLs) are now regex based. * Rename Tokyo Night Day theme to Tokyo Night Light and update colors. -* fcft >= 3.2.0 is now required. +* fcft >= 3.3.0 is now required. * Linefeed:ing control characters (e.g. `\n`) no longer **clears** a row's internal linebreak flag. This fixes an issue where e.g. multi-line prompt input in fish is treated as separate lines, diff --git a/config.c b/config.c index 1f287250..b141f564 100644 --- a/config.c +++ b/config.c @@ -2677,8 +2677,15 @@ parse_section_tweak(struct context *ctx) [FCFT_SCALING_FILTER_NONE] = "none", [FCFT_SCALING_FILTER_NEAREST] = "nearest", [FCFT_SCALING_FILTER_BILINEAR] = "bilinear", + + [FCFT_SCALING_FILTER_IMPULSE] = "impulse", + [FCFT_SCALING_FILTER_BOX] = "box", + [FCFT_SCALING_FILTER_LINEAR] = "linear", [FCFT_SCALING_FILTER_CUBIC] = "cubic", + [FCFT_SCALING_FILTER_GAUSSIAN] = "gaussian", + [FCFT_SCALING_FILTER_LANCZOS2] = "lanczos2", [FCFT_SCALING_FILTER_LANCZOS3] = "lanczos3", + [FCFT_SCALING_FILTER_LANCZOS3_STRETCHED] = "lanczos3-stretched", NULL, }; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 952c7ae2..a75ffc6e 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1716,9 +1716,8 @@ any of these options. *scaling-filter* Overrides the default scaling filter used when down-scaling bitmap fonts (e.g. emoji fonts). Possible values are *none*, *nearest*, - *bilinear*, *cubic* or *lanczos3*. *cubic* and *lanczos3* produce - the best results, but are slower (with *lanczos3* being the best - _and_ slowest). + *bilinear*, *impulse*, *box*, *linear*, *cubic* *gaussian*, + *lanczos2*, *lanczos3* or *lanczos3-stretched*. Default: _lanczos3_. diff --git a/main.c b/main.c index 1a001186..e7183238 100644 --- a/main.c +++ b/main.c @@ -518,7 +518,6 @@ main(int argc, char *const *argv) (enum fcft_log_colorize)log_colorize, as_server && log_syslog, (enum fcft_log_class)log_level); - fcft_set_scaling_filter(conf.tweak.fcft_filter); if (conf_server_socket_path != NULL) { free(conf.server_socket_path); diff --git a/meson.build b/meson.build index 6505460f..251e6fde 100644 --- a/meson.build +++ b/meson.build @@ -146,7 +146,7 @@ if utf8proc.found() endif tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist') -fcft = dependency('fcft', version: ['>=3.2.0', '<4.0.0'], fallback: 'fcft') +fcft = dependency('fcft', version: ['>=3.3.0', '<4.0.0'], fallback: 'fcft') wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir') diff --git a/terminal.c b/terminal.c index 2ba85276..ae1adb1a 100644 --- a/terminal.c +++ b/terminal.c @@ -1071,6 +1071,7 @@ reload_fonts(struct terminal *term, bool resize_grid) struct fcft_font_options *options = fcft_font_options_create(); + options->scaling_filter = conf->tweak.fcft_filter; options->color_glyphs.format = PIXMAN_a8r8g8b8; options->color_glyphs.srgb_decode = render_do_linear_blending(term); From 16c384b707757e69f3d01ae666f81e7d65a282b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 12 Mar 2025 10:06:13 +0100 Subject: [PATCH 130/353] changelog: mention some of the side-effects the new fcft requirement brings --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0843c29d..466e3a91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,6 +93,9 @@ based. * Rename Tokyo Night Day theme to Tokyo Night Light and update colors. * fcft >= 3.3.0 is now required. + - `tweak.scaling-filter` now supports more scaling-filters + - scaled bitmap fonts (when enabled in FontConfig) no longer have a + scaling-filter applied * Linefeed:ing control characters (e.g. `\n`) no longer **clears** a row's internal linebreak flag. This fixes an issue where e.g. multi-line prompt input in fish is treated as separate lines, From a79fd6a7cf585209e3204b87098f34e0c0f5aa45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 13 Mar 2025 13:23:25 +0100 Subject: [PATCH 131/353] meson: require fcft-3.3.1 fcft-3.3.0 is not binary compatible with 3.2.x, and earlier. --- CHANGELOG.md | 2 +- meson.build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466e3a91..cda805ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,7 +92,7 @@ * Auto-detection of URLs (i.e. not OSC-8 based URLs) are now regex based. * Rename Tokyo Night Day theme to Tokyo Night Light and update colors. -* fcft >= 3.3.0 is now required. +* fcft >= 3.3.1 is now required. - `tweak.scaling-filter` now supports more scaling-filters - scaled bitmap fonts (when enabled in FontConfig) no longer have a scaling-filter applied diff --git a/meson.build b/meson.build index 251e6fde..d84a848c 100644 --- a/meson.build +++ b/meson.build @@ -146,7 +146,7 @@ if utf8proc.found() endif tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist') -fcft = dependency('fcft', version: ['>=3.3.0', '<4.0.0'], fallback: 'fcft') +fcft = dependency('fcft', version: ['>=3.3.1', '<4.0.0'], fallback: 'fcft') wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir') From d48a1c53f59e8b7c99c18f4e4a5693ca0b10bd52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 12 Mar 2025 17:53:04 +0100 Subject: [PATCH 132/353] meson: require wayland-protocols >= 1.41 --- CHANGELOG.md | 1 + client.c | 5 +---- config.c | 14 -------------- foot-features.h | 27 --------------------------- main.c | 5 +---- meson.build | 32 ++++---------------------------- render.c | 9 --------- wayland.c | 33 --------------------------------- wayland.h | 23 +++-------------------- 9 files changed, 10 insertions(+), 139 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cda805ca..541096ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ e.g. multi-line prompt input in fish is treated as separate lines, rather than one logical, when selecting and copying it ([#1487][1487]). +* wayland-protocols >= 1.41 is now required. [1925]: https://codeberg.org/dnkl/foot/issues/1925 [1487]: https://codeberg.org/dnkl/foot/issues/1487 diff --git a/client.c b/client.c index ceee1b29..e7df3768 100644 --- a/client.c +++ b/client.c @@ -67,14 +67,11 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %ccolor-management %cassertions", + "version: %s %cpgo %cime %cgraphemes %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', - feature_xdg_toplevel_icon() ? '+' : '-', - feature_xdg_system_bell() ? '+' : '-', - feature_wp_color_management() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } diff --git a/config.c b/config.c index b141f564..6c8e147f 100644 --- a/config.c +++ b/config.c @@ -1112,21 +1112,11 @@ parse_section_main(struct context *ctx) if (!value_to_bool(ctx, &gamma_correct)) return false; -#if defined(HAVE_WP_COLOR_MANAGEMENT) conf->gamma_correct = gamma_correct ? GAMMA_CORRECT_ENABLED : GAMMA_CORRECT_DISABLED; return true; -#else - if (gamma_correct) { - LOG_CONTEXTUAL_WARN( - "ignoring; foot was built without color-management support"); - } - - conf->gamma_correct = GAMMA_CORRECT_DISABLED; - return true; -#endif } else { @@ -3339,11 +3329,7 @@ config_load(struct config *conf, const char *conf_path, .underline_thickness = {.pt = 0., .px = -1}, .strikeout_thickness = {.pt = 0., .px = -1}, .dpi_aware = false, -#if defined(HAVE_WP_COLOR_MANAGEMENT) .gamma_correct = GAMMA_CORRECT_AUTO, -#else - .gamma_correct = GAMMA_CORRECT_DISABLED, -#endif .security = { .osc52 = OSC52_ENABLED, }, diff --git a/foot-features.h b/foot-features.h index c6c9c6f4..ad447767 100644 --- a/foot-features.h +++ b/foot-features.h @@ -37,30 +37,3 @@ static inline bool feature_graphemes(void) return false; #endif } - -static inline bool feature_xdg_toplevel_icon(void) -{ -#if defined(HAVE_XDG_TOPLEVEL_ICON) - return true; -#else - return false; -#endif -} - -static inline bool feature_xdg_system_bell(void) -{ -#if defined(HAVE_XDG_SYSTEM_BELL) - return true; -#else - return false; -#endif -} - -static inline bool feature_wp_color_management(void) -{ -#if defined(HAVE_WP_COLOR_MANAGEMENT) - return true; -#else - return false; -#endif -} diff --git a/main.c b/main.c index e7183238..7e07038f 100644 --- a/main.c +++ b/main.c @@ -51,14 +51,11 @@ version_and_features(void) { static char buf[256]; snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %ctoplevel-icon %csystem-bell %ccolor-management %cassertions", + "version: %s %cpgo %cime %cgraphemes %cassertions", FOOT_VERSION, feature_pgo() ? '+' : '-', feature_ime() ? '+' : '-', feature_graphemes() ? '+' : '-', - feature_xdg_toplevel_icon() ? '+' : '-', - feature_xdg_system_bell() ? '+' : '-', - feature_wp_color_management() ? '+' : '-', feature_assertions() ? '+' : '-'); return buf; } diff --git a/meson.build b/meson.build index d84a848c..3d2ce91a 100644 --- a/meson.build +++ b/meson.build @@ -132,7 +132,7 @@ math = cc.find_library('m') threads = [dependency('threads'), cc.find_library('stdthreads', required: false)] libepoll = dependency('epoll-shim', required: false) pixman = dependency('pixman-1') -wayland_protocols = dependency('wayland-protocols', version: '>=1.32', +wayland_protocols = dependency('wayland-protocols', version: '>=1.41', fallback: 'wayland-protocols', default_options: ['tests=false']) wayland_client = dependency('wayland-client') @@ -169,32 +169,11 @@ wl_proto_xml = [ wayland_protocols_datadir / 'unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1 wayland_protocols_datadir / 'staging/cursor-shape/cursor-shape-v1.xml', wayland_protocols_datadir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', + wayland_protocols_datadir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml', + wayland_protocols_datadir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml', + wayland_protocols_datadir / 'staging/color-management/color-management-v1.xml', ] -if wayland_protocols.version().version_compare('>=1.37') - add_project_arguments('-DHAVE_XDG_TOPLEVEL_ICON', language: 'c') - wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml'] - xdg_toplevel_icon = true -else - xdg_toplevel_icon = false -endif - -if wayland_protocols.version().version_compare('>=1.38') - add_project_arguments('-DHAVE_XDG_SYSTEM_BELL', language: 'c') - wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml'] - xdg_system_bell = true -else - xdg_system_bell = false -endif - -if wayland_protocols.version().version_compare('>=1.41') - add_project_arguments('-DHAVE_WP_COLOR_MANAGEMENT', language: 'c') - wl_proto_xml += [wayland_protocols_datadir / 'staging/color-management/color-management-v1.xml'] - wp_color_management = true -else - wp_color_management = false -endif - foreach prot : wl_proto_xml wl_proto_headers += custom_target( prot.underscorify() + '-client-header', @@ -436,9 +415,6 @@ summary( 'Themes': get_option('themes'), 'IME': get_option('ime'), 'Grapheme clustering': utf8proc.found(), - 'Wayland: xdg-toplevel-icon-v1': xdg_toplevel_icon, - 'Wayland: xdg-system-bell-v1': xdg_system_bell, - 'Wayland: wp-color-management-v1': wp_color_management, 'utmp backend': utmp_backend, 'utmp helper default path': utmp_default_helper_path, 'Build terminfo': tic.found(), diff --git a/render.c b/render.c index eea43c10..4975394f 100644 --- a/render.c +++ b/render.c @@ -22,10 +22,7 @@ #include #include #include - -#if defined(HAVE_XDG_TOPLEVEL_ICON) #include -#endif #include @@ -5092,7 +5089,6 @@ render_refresh_app_id(struct terminal *term) void render_refresh_icon(struct terminal *term) { -#if defined(HAVE_XDG_TOPLEVEL_ICON) if (term->wl->toplevel_icon_manager == NULL) { LOG_DBG("compositor does not implement xdg-toplevel-icon: " "ignoring request to refresh window icon"); @@ -5126,7 +5122,6 @@ render_refresh_icon(struct terminal *term) xdg_toplevel_icon_v1_destroy(icon); term->render.icon.last_update = now; -#endif } void @@ -5232,10 +5227,6 @@ render_xcursor_set(struct seat *seat, struct terminal *term, bool render_do_linear_blending(const struct terminal *term) { -#if defined(HAVE_WP_COLOR_MANAGEMENT) return term->conf->gamma_correct != GAMMA_CORRECT_DISABLED && term->wl->color_management.img_description != NULL; -#else - return false; -#endif } diff --git a/wayland.c b/wayland.c index 1c083a9e..14d9bed9 100644 --- a/wayland.c +++ b/wayland.c @@ -675,8 +675,6 @@ static const struct wp_presentation_listener presentation_listener = { .clock_id = &clock_id, }; -#if defined(HAVE_WP_COLOR_MANAGEMENT) - static void color_manager_create_image_description(struct wayland *wayl) { @@ -758,7 +756,6 @@ static const struct wp_color_manager_v1_listener color_manager_listener = { .supported_tf_named = &color_manager_supported_tf_named, .done = &color_manager_done, }; -#endif static bool verify_iface_version(const char *iface, uint32_t version, uint32_t wanted) @@ -1457,7 +1454,6 @@ handle_global(void *data, struct wl_registry *registry, &wp_single_pixel_buffer_manager_v1_interface, required); } -#if defined(HAVE_XDG_TOPLEVEL_ICON) else if (streq(interface, xdg_toplevel_icon_v1_interface.name)) { const uint32_t required = 1; if (!verify_iface_version(interface, version, required)) @@ -1466,9 +1462,7 @@ handle_global(void *data, struct wl_registry *registry, wayl->toplevel_icon_manager = wl_registry_bind( wayl->registry, name, &xdg_toplevel_icon_v1_interface, required); } -#endif -#if defined(HAVE_XDG_SYSTEM_BELL) else if (streq(interface, xdg_system_bell_v1_interface.name)) { const uint32_t required = 1; if (!verify_iface_version(interface, version, required)) @@ -1477,9 +1471,7 @@ handle_global(void *data, struct wl_registry *registry, wayl->system_bell = wl_registry_bind( wayl->registry, name, &xdg_system_bell_v1_interface, required); } -#endif -#if defined(HAVE_WP_COLOR_MANAGEMENT) else if (streq(interface, wp_color_manager_v1_interface.name)) { const uint32_t required = 1; if (!verify_iface_version(interface, version, required)) @@ -1491,7 +1483,6 @@ handle_global(void *data, struct wl_registry *registry, wp_color_manager_v1_add_listener( wayl->color_management.manager, &color_manager_listener, wayl); } -#endif #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (streq(interface, zwp_text_input_manager_v3_interface.name)) { @@ -1733,11 +1724,9 @@ wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager, "falling back to client-side cursors"); } -#if defined(HAVE_XDG_TOPLEVEL_ICON) if (wayl->toplevel_icon_manager == NULL) { LOG_WARN("compositor does not implement the XDG toplevel icon protocol"); } -#endif #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED if (wayl->text_input_manager == NULL) { @@ -1815,21 +1804,14 @@ wayl_destroy(struct wayland *wayl) zwp_text_input_manager_v3_destroy(wayl->text_input_manager); #endif -#if defined(HAVE_WP_COLOR_MANAGEMENT) if (wayl->color_management.img_description != NULL) wp_image_description_v1_destroy(wayl->color_management.img_description); if (wayl->color_management.manager != NULL) wp_color_manager_v1_destroy(wayl->color_management.manager); -#endif - -#if defined(HAVE_XDG_SYSTEM_BELL) if (wayl->system_bell != NULL) xdg_system_bell_v1_destroy(wayl->system_bell); -#endif -#if defined(HAVE_XDG_TOPLEVEL_ICON) if (wayl->toplevel_icon_manager != NULL) xdg_toplevel_icon_manager_v1_destroy(wayl->toplevel_icon_manager); -#endif if (wayl->single_pixel_manager != NULL) wp_single_pixel_buffer_manager_v1_destroy(wayl->single_pixel_manager); if (wayl->fractional_scale_manager != NULL) @@ -1947,7 +1929,6 @@ wayl_win_init(struct terminal *term, const char *token) xdg_toplevel_set_app_id(win->xdg_toplevel, conf->app_id); -#if defined(HAVE_XDG_TOPLEVEL_ICON) if (wayl->toplevel_icon_manager != NULL) { const char *app_id = term->app_id != NULL ? term->app_id : term->conf->app_id; @@ -1960,9 +1941,7 @@ wayl_win_init(struct terminal *term, const char *token) wayl->toplevel_icon_manager, win->xdg_toplevel, icon); xdg_toplevel_icon_v1_destroy(icon); } -#endif -#if defined(HAVE_WP_COLOR_MANAGEMENT) if (term->conf->gamma_correct != GAMMA_CORRECT_DISABLED) { if (wayl->color_management.img_description != NULL) { xassert(wayl->color_management.manager != NULL); @@ -1992,7 +1971,6 @@ wayl_win_init(struct terminal *term, const char *token) /* "auto" - don't warn */ } } -#endif if (conf->csd.preferred == CONF_CSD_PREFER_NONE) { /* User specifically do *not* want decorations */ @@ -2136,11 +2114,8 @@ wayl_win_destroy(struct wl_window *win) tll_remove(win->xdg_tokens, it); } -#if defined(HAVE_WP_COLOR_MANAGEMENT) if (win->surface.color_management != NULL) wp_color_management_surface_v1_destroy(win->surface.color_management); -#endif - if (win->fractional_scale != NULL) wp_fractional_scale_v1_destroy(win->fractional_scale); if (win->surface.viewport != NULL) @@ -2417,7 +2392,6 @@ wayl_win_set_urgent(struct wl_window *win) bool wayl_win_ring_bell(const struct wl_window *win) { -#if defined(HAVE_XDG_SYSTEM_BELL) if (win->term->wl->system_bell == NULL) { static bool have_warned = false; @@ -2431,9 +2405,6 @@ wayl_win_ring_bell(const struct wl_window *win) xdg_system_bell_v1_ring(win->term->wl->system_bell, win->surface.surf); return true; -#else - return false; -#endif } bool @@ -2471,7 +2442,6 @@ wayl_win_subsurface_new_with_custom_parent( return false; } -#if defined(HAVE_WP_COLOR_MANAGEMENT) surf->surface.color_management = NULL; if (win->term->conf->gamma_correct && wayl->color_management.img_description != NULL) @@ -2485,7 +2455,6 @@ wayl_win_subsurface_new_with_custom_parent( surf->surface.color_management, wayl->color_management.img_description, WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); } -#endif struct wl_subsurface *sub = wl_subcompositor_get_subsurface( wayl->sub_compositor, main_surface, parent); @@ -2538,12 +2507,10 @@ wayl_win_subsurface_destroy(struct wayl_sub_surface *surf) if (surf == NULL) return; -#if defined(HAVE_WP_COLOR_MANAGEMENT) if (surf->surface.color_management != NULL) { wp_color_management_surface_v1_destroy(surf->surface.color_management); surf->surface.color_management = NULL; } -#endif if (surf->surface.viewport != NULL) { wp_viewport_destroy(surf->surface.viewport); diff --git a/wayland.h b/wayland.h index ec27281a..6215d708 100644 --- a/wayland.h +++ b/wayland.h @@ -9,6 +9,7 @@ #include /* Wayland protocols */ +#include #include #include #include @@ -19,18 +20,8 @@ #include #include #include - -#if defined(HAVE_XDG_TOPLEVEL_ICON) - #include -#endif - -#if defined(HAVE_XDG_SYSTEM_BELL) - #include -#endif - -#if defined(HAVE_WP_COLOR_MANAGEMENT) - #include -#endif +#include +#include #include #include @@ -65,9 +56,7 @@ enum touch_state { struct wayl_surface { struct wl_surface *surf; struct wp_viewport *viewport; -#if defined(HAVE_WP_COLOR_MANAGEMENT) struct wp_color_management_surface_v1 *color_management; -#endif }; struct wayl_sub_surface { @@ -458,15 +447,10 @@ struct wayland { struct wp_single_pixel_buffer_manager_v1 *single_pixel_manager; -#if defined(HAVE_XDG_TOPLEVEL_ICON) struct xdg_toplevel_icon_manager_v1 *toplevel_icon_manager; -#endif -#if defined(HAVE_XDG_SYSTEM_BELL) struct xdg_system_bell_v1 *system_bell; -#endif -#if defined(HAVE_WP_COLOR_MANAGEMENT) struct { struct wp_color_manager_v1 *manager; struct wp_image_description_v1 *img_description; @@ -475,7 +459,6 @@ struct wayland { bool have_tf_ext_linear; bool have_primaries_srgb; } color_management; -#endif bool presentation_timings; struct wp_presentation *presentation; From eb9357709bfd521a87f5fc4232bf37ce360a0317 Mon Sep 17 00:00:00 2001 From: Craig Barnes Date: Fri, 14 Mar 2025 20:15:11 +0000 Subject: [PATCH 133/353] main/client: simplify code for printing --version string --- client.c | 17 +---------------- foot-features.c | 30 ++++++++++++++++++++++++++++++ foot-features.h | 40 +++++++--------------------------------- main.c | 19 ++----------------- meson.build | 4 ++-- 5 files changed, 42 insertions(+), 68 deletions(-) create mode 100644 foot-features.c diff --git a/client.c b/client.c index e7df3768..e76f2d51 100644 --- a/client.c +++ b/client.c @@ -22,7 +22,6 @@ #include "foot-features.h" #include "macros.h" #include "util.h" -#include "version.h" #include "xmalloc.h" extern char **environ; @@ -62,20 +61,6 @@ sendall(int sock, const void *_buf, size_t len) return len; } -static const char * -version_and_features(void) -{ - static char buf[256]; - snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %cassertions", - FOOT_VERSION, - feature_pgo() ? '+' : '-', - feature_ime() ? '+' : '-', - feature_graphemes() ? '+' : '-', - feature_assertions() ? '+' : '-'); - return buf; -} - static void print_usage(const char *prog_name) { @@ -328,7 +313,7 @@ main(int argc, char *const *argv) break; case 'v': - printf("footclient %s\n", version_and_features()); + print_version_and_features("footclient "); ret = EXIT_SUCCESS; goto err; diff --git a/foot-features.c b/foot-features.c new file mode 100644 index 00000000..1b5bf7fd --- /dev/null +++ b/foot-features.c @@ -0,0 +1,30 @@ +#include "foot-features.h" +#include "version.h" + +const char version_and_features[] = + "version: " FOOT_VERSION + +#if defined(FOOT_PGO_ENABLED) && FOOT_PGO_ENABLED + " +pgo" +#else + " -pgo" +#endif + +#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED + " +ime" +#else + " -ime" +#endif + +#if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING + " +graphemes" +#else + " -graphemes" +#endif + +#if !defined(NDEBUG) + " +assertions" +#else + " -assertions" +#endif +; diff --git a/foot-features.h b/foot-features.h index ad447767..49cc56ed 100644 --- a/foot-features.h +++ b/foot-features.h @@ -1,39 +1,13 @@ #pragma once -#include +#include -static inline bool feature_assertions(void) -{ -#if defined(NDEBUG) - return false; -#else - return true; -#endif -} +extern const char version_and_features[]; -static inline bool feature_ime(void) +static inline void +print_version_and_features(const char *prefix) { -#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED - return true; -#else - return false; -#endif -} - -static inline bool feature_pgo(void) -{ -#if defined(FOOT_PGO_ENABLED) && FOOT_PGO_ENABLED - return true; -#else - return false; -#endif -} - -static inline bool feature_graphemes(void) -{ -#if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING - return true; -#else - return false; -#endif + fputs(prefix, stdout); + fputs(version_and_features, stdout); + fputc('\n', stdout); } diff --git a/main.c b/main.c index 7e07038f..b9404503 100644 --- a/main.c +++ b/main.c @@ -31,7 +31,6 @@ #include "shm.h" #include "terminal.h" #include "util.h" -#include "version.h" #include "xmalloc.h" #include "xsnprintf.h" @@ -46,20 +45,6 @@ fdm_sigint(struct fdm *fdm, int signo, void *data) return true; } -static const char * -version_and_features(void) -{ - static char buf[256]; - snprintf(buf, sizeof(buf), - "version: %s %cpgo %cime %cgraphemes %cassertions", - FOOT_VERSION, - feature_pgo() ? '+' : '-', - feature_ime() ? '+' : '-', - feature_graphemes() ? '+' : '-', - feature_assertions() ? '+' : '-'); - return buf; -} - static void print_usage(const char *prog_name) { @@ -377,7 +362,7 @@ main(int argc, char *const *argv) break; case 'v': - printf("foot %s\n", version_and_features()); + print_version_and_features("foot "); return EXIT_SUCCESS; case 'h': @@ -405,7 +390,7 @@ main(int argc, char *const *argv) argv += optind; } - LOG_INFO("%s", version_and_features()); + LOG_INFO("%s", version_and_features); { struct utsname name; diff --git a/meson.build b/meson.build index 3d2ce91a..c8f23dfc 100644 --- a/meson.build +++ b/meson.build @@ -295,7 +295,7 @@ executable( 'commands.c', 'commands.h', 'extract.c', 'extract.h', 'fdm.c', 'fdm.h', - 'foot-features.h', + 'foot-features.c', 'foot-features.h', 'ime.c', 'ime.h', 'input.c', 'input.h', 'key-binding.c', 'key-binding.h', @@ -323,7 +323,7 @@ executable( executable( 'footclient', 'client.c', 'client-protocol.h', - 'foot-features.h', + 'foot-features.c', 'foot-features.h', 'macros.h', 'util.h', version, From cd4ee8ae49f7c3ba1441917decd2e4ebb804e4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Mar 2025 08:43:12 +0100 Subject: [PATCH 134/353] ime: fix initial cursor rectangle being reported as 0,0,0,0 Closes #1994 --- CHANGELOG.md | 3 +++ ime.c | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 541096ec..8b55c42f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -143,6 +143,8 @@ * Grapheme clustering state not reset on cursor movements. * Kitty keyboard protocol: no release events emitted for composed keys. +* IME: the initial cursor position was reported as 0,0,0,0 + ([#1994][1994]). [1918]: https://codeberg.org/dnkl/foot/issues/1918 [1929]: https://codeberg.org/dnkl/foot/issues/1929 @@ -151,6 +153,7 @@ [1960]: https://codeberg.org/dnkl/foot/issues/1960 [1956]: https://codeberg.org/dnkl/foot/issues/1956 [1963]: https://codeberg.org/dnkl/foot/issues/1963 +[1994]: https://codeberg.org/dnkl/foot/issues/1994 ### Security diff --git a/ime.c b/ime.c index 54cfa908..c6ccb479 100644 --- a/ime.c +++ b/ime.c @@ -68,6 +68,16 @@ enter(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, /* The main grid is the *only* input-receiving surface we have */ seat->ime_focus = term; + + const struct coord *cursor = &term->grid->cursor.point; + + term_ime_set_cursor_rect( + term, + term->margins.left + cursor->col * term->cell_width, + term->margins.top + cursor->row * term->cell_height, + term->cell_width, + term->cell_height); + ime_enable(seat); } From 7dbfdc73b66a65956b4ca15e3ad7d88ae5a2bac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Mar 2025 08:51:27 +0100 Subject: [PATCH 135/353] doc: foot.init: surface-bit-depth: mention 10-bit surfaces are slow --- doc/foot.ini.5.scd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index a75ffc6e..a1d3bfc1 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1960,6 +1960,9 @@ any of these options. background, you may want to use the default, *8-bit*, even if you have gamma-correct blending enabled. + You shouuld also note that 10-bit surface is slower. This will + increase input latency and decrease rendering throughput. + Default: _8-bit_ # SEE ALSO From d2ede697f9cb40269399bd2f72bcac11a2b918b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Mar 2025 12:02:57 +0100 Subject: [PATCH 136/353] config: remove deprecated options 'notify' and 'notify-focus-inhibit' They've been deprecated since 1.18.0 --- CHANGELOG.md | 2 ++ config.c | 24 ------------------------ tests/test-config.c | 3 --- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b55c42f..3abc2552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -112,6 +112,8 @@ * `url.uri-characters` and `url.protocols`. Both options have been replaced by `url.regex`. +* `notify` option (has been deprecated since 1.18.0). +* `notify-focus-inhibit` option (has been deprecated since 1.18.0). ### Fixed diff --git a/config.c b/config.c index 6c8e147f..222b1b60 100644 --- a/config.c +++ b/config.c @@ -1058,30 +1058,6 @@ parse_section_main(struct context *ctx) else if (streq(key, "word-delimiters")) return value_to_wchars(ctx, &conf->word_delimiters); - else if (streq(key, "notify")) { - user_notification_add( - &conf->notifications, USER_NOTIFICATION_DEPRECATED, - xstrdup("notify: use desktop-notifications.command instead")); - log_msg( - LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, - "deprecated: notify: use desktop-notifications.command instead"); - return value_to_spawn_template( - ctx, &conf->desktop_notifications.command); - } - - else if (streq(key, "notify-focus-inhibit")) { - user_notification_add( - &conf->notifications, USER_NOTIFICATION_DEPRECATED, - xstrdup("notify-focus-inhibit: " - "use desktop-notifications.inhibit-when-focused instead")); - log_msg( - LOG_CLASS_WARNING, LOG_MODULE, __FILE__, __LINE__, - "deprecrated: notify-focus-inhibit: " - "use desktop-notifications.inhibit-when-focused instead"); - return value_to_bool( - ctx, &conf->desktop_notifications.inhibit_when_focused); - } - else if (streq(key, "selection-target")) { _Static_assert(sizeof(conf->selection_target) == sizeof(int), "enum is not 32-bit"); diff --git a/tests/test-config.c b/tests/test-config.c index c9f6586c..f431f4ab 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -467,7 +467,6 @@ test_section_main(void) test_boolean(&ctx, &parse_section_main, "login-shell", &conf.login_shell); test_boolean(&ctx, &parse_section_main, "box-drawings-uses-font-glyphs", &conf.box_drawings_uses_font_glyphs); test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title); - test_boolean(&ctx, &parse_section_main, "notify-focus-inhibit", &conf.desktop_notifications.inhibit_when_focused); /* Deprecated */ test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware); test_pt_or_px(&ctx, &parse_section_main, "font-size-adjustment", &conf.font_size_adjustment.pt_or_px); /* TODO: test ‘N%’ values too */ @@ -481,8 +480,6 @@ test_section_main(void) test_uint16(&ctx, &parse_section_main, "resize-delay-ms", &conf.resize_delay_ms); test_uint16(&ctx, &parse_section_main, "workers", &conf.render_worker_count); - test_spawn_template(&ctx, &parse_section_main, "notify", &conf.desktop_notifications.command); /* Deprecated */ - test_enum(&ctx, &parse_section_main, "selection-target", 4, (const char *[]){"none", "primary", "clipboard", "both"}, From 3eef3ec877fa55e3faf2b6e0c5b7fffc92cab8e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Mar 2025 12:04:46 +0100 Subject: [PATCH 137/353] changelog: prepare for 1.21.0 --- CHANGELOG.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3abc2552..5e598010 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.21.0](#1-21-0) * [1.20.2](#1-20-2) * [1.20.1](#1-20-1) * [1.20.0](#1-20-0) @@ -58,7 +58,7 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.21.0 ### Added @@ -107,7 +107,6 @@ [1487]: https://codeberg.org/dnkl/foot/issues/1487 -### Deprecated ### Removed * `url.uri-characters` and `url.protocols`. Both options have been @@ -158,9 +157,19 @@ [1994]: https://codeberg.org/dnkl/foot/issues/1994 -### Security ### Contributors +* Adrian fxj9a +* Alexander Orzechowski +* Attila Fidan +* camel-cdr +* Craig Barnes +* Guillaume Outters +* Johannes Altmanninger +* Ludovico Gerardi +* sewn +* Thomas Bonnefille + ## 1.20.2 From df32cd0504c59243f06904bb9a6e1b29ccedfbe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Mar 2025 12:04:59 +0100 Subject: [PATCH 138/353] meson: bump version to 1.21.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index c8f23dfc..e85d95e5 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.20.2', + version: '1.21.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 49d2c08912c6e15ff39f2c7a9e69727e348f7e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Mar 2025 12:08:27 +0100 Subject: [PATCH 139/353] doc: foot.ini: codespell: shouuld -> should --- doc/foot.ini.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index a1d3bfc1..7fad2b9c 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1960,7 +1960,7 @@ any of these options. background, you may want to use the default, *8-bit*, even if you have gamma-correct blending enabled. - You shouuld also note that 10-bit surface is slower. This will + You should also note that 10-bit surface is slower. This will increase input latency and decrease rendering throughput. Default: _8-bit_ From 68f5eab0b0fa08becebbed412947ba19246c2518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Mar 2025 12:08:27 +0100 Subject: [PATCH 140/353] doc: foot.ini: codespell: shouuld -> should --- doc/foot.ini.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index a1d3bfc1..7fad2b9c 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1960,7 +1960,7 @@ any of these options. background, you may want to use the default, *8-bit*, even if you have gamma-correct blending enabled. - You shouuld also note that 10-bit surface is slower. This will + You should also note that 10-bit surface is slower. This will increase input latency and decrease rendering throughput. Default: _8-bit_ From 6813b321f5bd0661eea0af0b9104d615e3d6d0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 17 Mar 2025 12:15:36 +0100 Subject: [PATCH 141/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e598010..30f4dc75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.21.0](#1-21-0) * [1.20.2](#1-20-2) * [1.20.1](#1-20-1) @@ -58,6 +59,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.21.0 ### Added From 878e07da59855d62e89eba5f5f479a5ee598998e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 18 Mar 2025 14:37:28 +0100 Subject: [PATCH 142/353] vt: utf8: don't discard current byte when an invalid UTF-8 sequence is detected Example: printf "pok\xe9mon\n" would result in 'pokon' - the 'm' has been discarded along with E9. While correct, in some sense, it's perhaps not intuitive. This patch changes the VT parser to instead discard everything up to the invalid byte, but then try the invalid byte from the ground state. This way, invalid UTF-8 sequences followed by both plain ASCII, or longer (and valid) UTF-8 sequences are printed as expected instead of being discarded. --- CHANGELOG.md | 4 ++++ vt.c | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30f4dc75..ac022c4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,10 @@ ## Unreleased ### Added ### Changed + +* UTF-8 error recovery now discards fewer bytes. + + ### Deprecated ### Removed ### Fixed diff --git a/vt.c b/vt.c index 9c758c55..173b59a6 100644 --- a/vt.c +++ b/vt.c @@ -1041,7 +1041,7 @@ state_utf8_21_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_22(term, data); return STATE_GROUND; - default: return STATE_GROUND; + default: return state_ground_switch(term, data); } } @@ -1051,7 +1051,7 @@ state_utf8_31_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_32(term, data); return STATE_UTF8_32; - default: return STATE_GROUND; + default: return state_ground_switch(term, data); } } @@ -1061,7 +1061,7 @@ state_utf8_32_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_33(term, data); return STATE_GROUND; - default: return STATE_GROUND; + default: return state_ground_switch(term, data); } } @@ -1071,7 +1071,7 @@ state_utf8_41_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_42(term, data); return STATE_UTF8_42; - default: return STATE_GROUND; + default: return state_ground_switch(term, data); } } @@ -1081,7 +1081,7 @@ state_utf8_42_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_43(term, data); return STATE_UTF8_43; - default: return STATE_GROUND; + default: return state_ground_switch(term, data); } } @@ -1091,7 +1091,7 @@ state_utf8_43_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_44(term, data); return STATE_GROUND; - default: return STATE_GROUND; + default: return state_ground_switch(term, data); } } From a02c0c8d4de96832f2bb3e8c16b8e6e4cf6a9662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 18 Mar 2025 18:28:09 +0100 Subject: [PATCH 143/353] vt: utf8: insert a REPLACEMENT CHARACTER when an invalid UTF-8 sequence is detected --- vt.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vt.c b/vt.c index 173b59a6..1d8297be 100644 --- a/vt.c +++ b/vt.c @@ -1041,7 +1041,7 @@ state_utf8_21_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_22(term, data); return STATE_GROUND; - default: return state_ground_switch(term, data); + default: action_utf8_print(term, 0xfffd); return state_ground_switch(term, data); } } @@ -1051,7 +1051,7 @@ state_utf8_31_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_32(term, data); return STATE_UTF8_32; - default: return state_ground_switch(term, data); + default: action_utf8_print(term, 0xfffd); return state_ground_switch(term, data); } } @@ -1061,7 +1061,7 @@ state_utf8_32_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_33(term, data); return STATE_GROUND; - default: return state_ground_switch(term, data); + default: action_utf8_print(term, 0xfffd); return state_ground_switch(term, data); } } @@ -1071,7 +1071,7 @@ state_utf8_41_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_42(term, data); return STATE_UTF8_42; - default: return state_ground_switch(term, data); + default: action_utf8_print(term, 0xfffd); return state_ground_switch(term, data); } } @@ -1081,7 +1081,7 @@ state_utf8_42_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_43(term, data); return STATE_UTF8_43; - default: return state_ground_switch(term, data); + default: action_utf8_print(term, 0xfffd); return state_ground_switch(term, data); } } @@ -1091,7 +1091,7 @@ state_utf8_43_switch(struct terminal *term, uint8_t data) switch (data) { /* exit current enter new state */ case 0x80 ... 0xbf: action_utf8_44(term, data); return STATE_GROUND; - default: return state_ground_switch(term, data); + default: action_utf8_print(term, 0xfffd); return state_ground_switch(term, data); } } From cc99db5bc4f02fffe0dde7483fb91a8056e112d8 Mon Sep 17 00:00:00 2001 From: llyyr Date: Wed, 19 Mar 2025 10:06:38 +0530 Subject: [PATCH 144/353] url-mode: fix crash when opening multiple urls with persist mode Fixes: 051cd6ecfc8b98e1d80b399ebea40207fe040750 Closes #2000 --- url-mode.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/url-mode.c b/url-mode.c index f04550f8..ed260597 100644 --- a/url-mode.c +++ b/url-mode.c @@ -85,8 +85,6 @@ spawn_url_launcher_with_token(struct terminal *term, free(argv); } - term->url_launch = NULL; - close(dev_null); return ret; } From 5f72f51ae8671d232f7fdc8c1ce42b537d73c409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 20 Mar 2025 08:51:43 +0100 Subject: [PATCH 145/353] changelog: url-mode: show-urls-persistent regression fix --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac022c4f..7ed4bd7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,13 @@ ### Deprecated ### Removed ### Fixed + +* Regression: assertion in `url-mode.c` when activating a second URL + via `show-urls-persistent` ([#2000][2000]). + +[2000]: https://codeberg.org/dnkl/foot/issues/2000 + + ### Security ### Contributors From 663c9082db4dfe43bb329a09fd5b93a6ba99fdb6 Mon Sep 17 00:00:00 2001 From: Sam McCall Date: Sat, 22 Mar 2025 20:11:23 +0100 Subject: [PATCH 146/353] render: dim and brighten using linear rgb interpolation Adds setting tweak.dim-amount, similar to bold-text-in-bright-amount. Closes #2006 --- CHANGELOG.md | 6 +++++ config.c | 4 ++++ config.h | 4 ++++ doc/foot.ini.5.scd | 11 +++++---- hsl.c | 35 ----------------------------- hsl.h | 1 - render.c | 56 +++++++++++++++++++++++++--------------------- 7 files changed, 51 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed4bd7a..671b6dad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,12 @@ ### Changed * UTF-8 error recovery now discards fewer bytes. +* Auto-calculated dimmed and brightened colors (e.g. when custom dim + colors has not configured) is now done by linear RGB interpolation, + rather than converting to HSL and adjusting the luminance + ([#2006][2006]). + +[2006]: https://codeberg.org/dnkl/foot/issues/2006 ### Deprecated diff --git a/config.c b/config.c index 222b1b60..a8cdb34a 100644 --- a/config.c +++ b/config.c @@ -2759,6 +2759,9 @@ parse_section_tweak(struct context *ctx) else if (streq(key, "sixel")) return value_to_bool(ctx, &conf->tweak.sixel); + else if (streq(key, "dim-amount")) + return value_to_float(ctx, &conf->dim.amount); + else if (streq(key, "bold-text-in-bright-amount")) return value_to_float(ctx, &conf->bold_in_bright.amount); @@ -3288,6 +3291,7 @@ config_load(struct config *conf, const char *conf_path, .resize_by_cells = true, .resize_keep_grid = true, .resize_delay_ms = 100, + .dim = { .amount = 1.5 }, .bold_in_bright = { .enabled = false, .palette_based = false, diff --git a/config.h b/config.h index fb019d90..a08fae31 100644 --- a/config.h +++ b/config.h @@ -155,6 +155,10 @@ struct config { uint16_t resize_delay_ms; + struct { + float amount; + } dim; + struct { bool enabled; bool palette_based; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 7fad2b9c..2a065a0c 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -396,7 +396,7 @@ empty string to be set, but it must be quoted: *KEY=""*) *bold-text-in-bright* Semi-boolean. When enabled, bold text is rendered in a brighter color (in addition to using a bold font). The color is brightened - by increasing its luminance. + by blending it with white. If set to *palette-based*, rather than a simple *yes|true*, colors matching one of the 8 regular palette colors will be brightened @@ -986,8 +986,8 @@ can configure the background transparency with the _alpha_ option. an entry in the color palette. Applications emit them by combining a color value, and a "dim" attribute. - By default, foot implements this by reducing the luminance of the - current color. This is a generic approach that applies to both + By default, foot implements this by blending the current color + with black. This is a generic approach that applies to both colors from the 256-color palette, as well as 24-bit RGB colors. You can change this behavior by setting the *dimN* options. When @@ -999,7 +999,7 @@ can configure the background transparency with the _alpha_ option. the corresponding *regularN* color will be used. If the current color does not match any known color, it is dimmed - by reducing the luminance (i.e. the same behavior as if the *dimN* + by blending with black (i.e. the same behavior as if the *dimN* options are unconfigured). 24-bit RGB colors will typically fall into this category. @@ -1940,6 +1940,9 @@ any of these options. Boolean. When enabled, foot will process sixel images. Default: _yes_ +*dim-amount* + Amount by which dimmed text is darkened. Default: _1.5_. + *bold-text-in-bright-amount* Amount by which bold fonts are brightened when *bold-text-in-bright* is set to *yes* (the *palette-based* variant diff --git a/hsl.c b/hsl.c index d5d00e67..1a8c919e 100644 --- a/hsl.c +++ b/hsl.c @@ -2,41 +2,6 @@ #include -#include "util.h" - -void -rgb_to_hsl(uint32_t rgb, int *hue, int *sat, int *lum) -{ - double r = (double)((rgb >> 16) & 0xff) / 255.; - double g = (double)((rgb >> 8) & 0xff) / 255.; - double b = (double)((rgb >> 0) & 0xff) / 255.; - - double x_max = max(max(r, g), b); - double x_min = min(min(r, g), b); - double V = x_max; - - double C = x_max - x_min; - double L = (x_max + x_min) / 2.; - - *lum = 100 * L; - - if (C == 0.0) - *hue = 0; - else if (V == r) - *hue = 60. * (0. + (g - b) / C); - else if (V == g) - *hue = 60. * (2. + (b - r) / C); - else if (V == b) - *hue = 60. * (4. + (r - g) / C); - if (*hue < 0) - *hue += 360; - - double S = C == 0.0 - ? 0 - : C / (1. - fabs(2. * L - 1.)); - *sat = 100 * S; -} - uint32_t hsl_to_rgb(int hue, int sat, int lum) { diff --git a/hsl.h b/hsl.h index 2a46c117..1aaf7e66 100644 --- a/hsl.h +++ b/hsl.h @@ -2,5 +2,4 @@ #include -void rgb_to_hsl(uint32_t rgb, int *hue, int *sat, int *lum); uint32_t hsl_to_rgb(int hue, int sat, int lum); diff --git a/render.c b/render.c index 4975394f..d2202468 100644 --- a/render.c +++ b/render.c @@ -34,7 +34,6 @@ #include "config.h" #include "cursor-shape.h" #include "grid.h" -#include "hsl.h" #include "ime.h" #include "quirks.h" #include "search.h" @@ -271,13 +270,23 @@ color_hex_to_pixman(uint32_t color, bool srgb) return color_hex_to_pixman_with_alpha(color, 0xffff, srgb); } +static inline int i_lerp(int from, int to, float t) { + return from + (to - from) * t; +} + static inline uint32_t -color_decrease_luminance(uint32_t color) +color_blend_towards(uint32_t from, uint32_t to, float amount) { - uint32_t alpha = color & 0xff000000; - int hue, sat, lum; - rgb_to_hsl(color, &hue, &sat, &lum); - return alpha | hsl_to_rgb(hue, sat, lum / 1.5); + if (unlikely(amount == 0)) + return from; + float t = 1 - 1/amount; + + uint32_t alpha = from & 0xff000000; + uint8_t r = i_lerp((from>>16)&0xff, (to>>16)&0xff, t); + uint8_t g = i_lerp((from>>8)&0xff, (to>>8)&0xff, t); + uint8_t b = i_lerp((from>>0)&0xff, (to>>0)&0xff, t); + + return alpha | (r<<16) | (g<<8) | (b<<0); } static inline uint32_t @@ -286,25 +295,24 @@ color_dim(const struct terminal *term, uint32_t color) const struct config *conf = term->conf; const uint8_t custom_dim = conf->colors.use_custom.dim; - if (likely(custom_dim == 0)) - return color_decrease_luminance(color); + if (unlikely(custom_dim != 0)) { + for (size_t i = 0; i < 8; i++) { + if (((custom_dim >> i) & 1) == 0) + continue; - for (size_t i = 0; i < 8; i++) { - if (((custom_dim >> i) & 1) == 0) - continue; + if (term->colors.table[0 + i] == color) { + /* "Regular" color, return the corresponding "dim" */ + return conf->colors.dim[i]; + } - if (term->colors.table[0 + i] == color) { - /* "Regular" color, return the corresponding "dim" */ - return conf->colors.dim[i]; - } - - else if (term->colors.table[8 + i] == color) { - /* "Bright" color, return the corresponding "regular" */ - return term->colors.table[i]; + else if (term->colors.table[8 + i] == color) { + /* "Bright" color, return the corresponding "regular" */ + return term->colors.table[i]; + } } } - return color_decrease_luminance(color); + return color_blend_towards(color, 0x00000000, conf->dim.amount); } static inline uint32_t @@ -322,11 +330,7 @@ color_brighten(const struct terminal *term, uint32_t color) return color; } - int hue, sat, lum; - rgb_to_hsl(color, &hue, &sat, &lum); - - lum = (int)roundf(lum * term->conf->bold_in_bright.amount); - return hsl_to_rgb(hue, sat, min(lum, 100)); + return color_blend_towards(color, 0x00ffffff, term->conf->bold_in_bright.amount); } static void @@ -798,7 +802,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, _fg = color_brighten(term, _fg); if (cell->attrs.blink && term->blink.state == BLINK_OFF) - _fg = color_decrease_luminance(_fg); + _fg = color_blend_towards(_fg, 0x00000000, term->conf->dim.amount); const bool gamma_correct = render_do_linear_blending(term); pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct); From 6922ab2b8efa1422f88730b6bc7ba09fff39996e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 23 Mar 2025 17:00:19 +0100 Subject: [PATCH 147/353] doc: foot.ini: gamma-correct: move section --- doc/foot.ini.5.scd | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 2a065a0c..c663223d 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -208,11 +208,6 @@ empty string to be set, but it must be quoted: *KEY=""*) background will appear thicker, and dark glyphs on a light background will appear thinner. - Also be aware that many fonts have been developed on systems that - do not do gamma-correct blending, and may therefore look thicker - than intended when rendered with gamma-correct blending, since the - font designer set the font weight based on incorrect rendering. - FreeType can limit the effect of the latter, with a technique called stem darkening. It is only available for CFF fonts (OpenType, .otf) and disabled by default (in FreeType). You can @@ -220,6 +215,11 @@ empty string to be set, but it must be quoted: *KEY=""*) *FREETYPE_PROPERTIES="cff:no-stem-darkening=0"* before starting foot. + Also be aware that many fonts have been developed on systems that + do not do gamma-correct blending, and may therefore look thicker + than intended when rendered with gamma-correct blending, since the + font designer set the font weight based on incorrect rendering. + You may also want to enable 10-bit image buffers when gamma-correct blending is enabled. Though probably only if you do not use a transparent background (with 10-bit buffers, you only From 9b776f2d6de39569670dbd76f635c11a383b8971 Mon Sep 17 00:00:00 2001 From: "Alex Xu (Hello71)" Date: Mon, 17 Mar 2025 16:51:53 -0400 Subject: [PATCH 148/353] meson: add foot (render.c) -> srgb.h dep otherwise, depending on ninja dependency resolution order and parallel build, srgb.h may not be built in time Fixes: ccf625b991 ("render: gamma-correct blending") --- CHANGELOG.md | 1 + meson.build | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 671b6dad..1b36e86b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ * Regression: assertion in `url-mode.c` when activating a second URL via `show-urls-persistent` ([#2000][2000]). +* Build failure (`srgb.h` not found) when doing a parallel build. [2000]: https://codeberg.org/dnkl/foot/issues/2000 diff --git a/meson.build b/meson.build index e85d95e5..a9e47b3b 100644 --- a/meson.build +++ b/meson.build @@ -314,7 +314,7 @@ executable( 'url-mode.c', 'url-mode.h', 'user-notification.c', 'user-notification.h', 'wayland.c', 'wayland.h', 'shm-formats.h', - wl_proto_src + wl_proto_headers, version, + srgb_funcs, wl_proto_src + wl_proto_headers, version, dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc, tllist, fcft], link_with: pgolib, From c8470f40c1f5a850048a5b296a944e07abae716a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 29 Mar 2025 10:15:13 +0100 Subject: [PATCH 149/353] grid: reflow: fix empty line coalescing If a range of empty lines ended with a non-empty line at the very bottom of the to-be-resized grid, all those empty lines were removed. Closes #2011 --- CHANGELOG.md | 3 +++ grid.c | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b36e86b..466947cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,8 +79,11 @@ * Regression: assertion in `url-mode.c` when activating a second URL via `show-urls-persistent` ([#2000][2000]). * Build failure (`srgb.h` not found) when doing a parallel build. +* Regression: reflowing (changing the window size) removing empty + lines ([#2011][2011]). [2000]: https://codeberg.org/dnkl/foot/issues/2000 +[2011]: https://codeberg.org/dnkl/foot/issues/2011 ### Security diff --git a/grid.c b/grid.c index cb6e1a91..df7ef61c 100644 --- a/grid.c +++ b/grid.c @@ -985,14 +985,12 @@ grid_resize_and_reflow( underline_range = underline_range_terminator = NULL; if (unlikely(col_count > 0 && coalesced_linebreaks > 0)) { - for (size_t apa = 0; apa < coalesced_linebreaks; apa++) { + for (size_t line_no = 0; line_no < coalesced_linebreaks; line_no++) { /* Erase the remaining cells */ memset(&new_row->cells[new_col_idx], 0, (new_cols - new_col_idx) * sizeof(new_row->cells[0])); new_row->linebreak = true; - - if (r + 1 < old_rows) - line_wrap(); + line_wrap(); } coalesced_linebreaks = 0; From 58910856c8a86095883625009a089cef464edc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 29 Mar 2025 10:34:40 +0100 Subject: [PATCH 150/353] input: xkb: ignore virtual modifiers Some compositors (mutter/GNOME is one) adds _virtual_ modifiers to the set of active modifiers when e.g. Alt, Meta, Super or Hyper is pressed. For example, pressing Alt+b would result in *both* the Alt *and* the Mod1 modifier being set. Since foot makes close to zero assumptions on how the modifiers should be interpreted, this causes various breakages. For example, a foot shortcut defined as Mod1+b will not match, since the Alt modifiers is also set. This has forced users to redefine/override some of the default key bindings to include the additional modifiers. It also causes issues with the kitty keyboard protocol, for some key combinations. Mainly whether or not to use unshifted key or not, resulting in incorrect escape sequences. Since all the "real" modifiers are always set as well, we can safely ignore the virtual modifiers. Closes #2009 --- CHANGELOG.md | 12 ++++++++++++ input.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wayland.h | 2 ++ 3 files changed, 66 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466947cd..92b7524f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,8 +68,20 @@ colors has not configured) is now done by linear RGB interpolation, rather than converting to HSL and adjusting the luminance ([#2006][2006]). +* XKB: virtual modifiers are now ignored. This works around various + issues seen when running foot under mutter (GNOME) ([#2009][2009]): + - Some key combinations generating the wrong escape sequence in the + kitty keyboard protocol. + - some of foot's default shortcuts not working (mainly those using + `Mod1`) out of the box. + - **Note: if you have custom key bindings in `foot.ini` that + includes one or more of the `Alt`, `Meta`, `Super`, `Hyper`, + `NumLock`, `ScrollLock`, `LevelThree` or `LevelFive` modifiers, + you need to update them; i.e. remove the virtual modifier(s), + leaving only the real modifiers (`Mod1`, `Mod2` etc).** [2006]: https://codeberg.org/dnkl/foot/issues/2006 +[2009]: https://codeberg.org/dnkl/foot/issues/2009 ### Deprecated diff --git a/input.c b/input.c index cbe8e1c7..abaac8eb 100644 --- a/input.c +++ b/input.c @@ -620,6 +620,54 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, if (seat->kbd.mod_num != XKB_MOD_INVALID) seat->kbd.kitty_significant |= 1 << seat->kbd.mod_num; + + /* + * Create a mask of all "virtual" modifiers. Some compositors + * add these *in addition* to the "real" modifiers (Mod1, + * Mod2, etc). + * + * Since our modifier logic (both for internal shortcut + * processing, and e.g. the kitty keyboard protocol) makes + * very few assumptions on available modifiers, which keys map + * to which modifier etc, the presence of virtual modifiers + * causes various things to break. + * + * For example, if a foot shortcut is Mod1+b (i.e. Alt+b), it + * won't match if the compositor _also_ sets the Alt modifier + * (the corresponding shortcut in foot would be Alt+Mod1+b). + * + * See https://codeberg.org/dnkl/foot/issues/2009 + * + * Mutter (GNOME) is known to set the virtual modifiers in + * addtiion to the real modifiers. + * + * As far as I know, there's no compositor that _only_ sets + * virtual modifiers (don't think that's even legal...?) + */ + { + xkb_mod_index_t alt = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_ALT); + xkb_mod_index_t meta = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_META); + xkb_mod_index_t super = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SUPER); + xkb_mod_index_t hyper = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_HYPER); + xkb_mod_index_t num_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_NUM); + xkb_mod_index_t scroll_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SCROLL); + xkb_mod_index_t level_three = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL3); + xkb_mod_index_t level_five = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL5); + + xkb_mod_index_t ignore = 0; + + if (alt != XKB_MOD_INVALID) ignore |= 1 << alt; + if (meta != XKB_MOD_INVALID) ignore |= 1 << meta; + if (super != XKB_MOD_INVALID) ignore |= 1 << super; + if (hyper != XKB_MOD_INVALID) ignore |= 1 << hyper; + if (num_lock != XKB_MOD_INVALID) ignore |= 1 << num_lock; + if (scroll_lock != XKB_MOD_INVALID) ignore |= 1 << scroll_lock; + if (level_three != XKB_MOD_INVALID) ignore |= 1 << level_three; + if (level_five != XKB_MOD_INVALID) ignore |= 1 << level_five; + + seat->kbd.virtual_modifiers = ignore; + } + seat->kbd.key_arrow_up = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "UP"); seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN"); } @@ -1759,6 +1807,10 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, { struct seat *seat = data; + mods_depressed &= ~seat->kbd.virtual_modifiers; + mods_latched &= ~seat->kbd.virtual_modifiers; + mods_locked &= ~seat->kbd.virtual_modifiers; + #if defined(_DEBUG) char depressed[256]; char latched[256]; diff --git a/wayland.h b/wayland.h index 6215d708..37dd7860 100644 --- a/wayland.h +++ b/wayland.h @@ -136,6 +136,8 @@ struct seat { xkb_mod_mask_t legacy_significant; /* Significant modifiers for the legacy keyboard protocol */ xkb_mod_mask_t kitty_significant; /* Significant modifiers for the kitty keyboard protocol */ + xkb_mod_mask_t virtual_modifiers; /* Set of modifiers to completely ignore */ + xkb_keycode_t key_arrow_up; xkb_keycode_t key_arrow_down; From dc99cf735888d0aba31194b8c59e92eed7e1bc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 31 Mar 2025 10:11:30 +0200 Subject: [PATCH 151/353] key-binding: recognize virtual modifiers, and translate to the corresponding real modifier. --- CHANGELOG.md | 9 ++++-- key-binding.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b7524f..7dcc8372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,8 +68,9 @@ colors has not configured) is now done by linear RGB interpolation, rather than converting to HSL and adjusting the luminance ([#2006][2006]). -* XKB: virtual modifiers are now ignored. This works around various - issues seen when running foot under mutter (GNOME) ([#2009][2009]): +* XKB: virtual modifiers in keyboard events from the compositor are + now ignored. This works around various issues seen when running foot + under mutter (GNOME) ([#2009][2009]): - Some key combinations generating the wrong escape sequence in the kitty keyboard protocol. - some of foot's default shortcuts not working (mainly those using @@ -79,6 +80,10 @@ `NumLock`, `ScrollLock`, `LevelThree` or `LevelFive` modifiers, you need to update them; i.e. remove the virtual modifier(s), leaving only the real modifiers (`Mod1`, `Mod2` etc).** +* Virtual modifiers (e.g. `Alt` instead of `Mod1`, `Super` instead of + `Mod4` etc) in key bindings are now recognized as being virtual, and + are automatically mapped to the corresponding real modifier. This + means you can use e.g. `Alt+b` instead of `Mod1+b`. [2006]: https://codeberg.org/dnkl/foot/issues/2006 [2009]: https://codeberg.org/dnkl/foot/issues/2009 diff --git a/key-binding.c b/key-binding.c index 1c131e72..e5b7ac81 100644 --- a/key-binding.c +++ b/key-binding.c @@ -13,12 +13,21 @@ #include "wayland.h" #include "xmalloc.h" +struct vmod_map { + const char *name; + xkb_mod_mask_t virtual_mask; + xkb_mod_mask_t real_mask; +}; + struct key_set { struct key_binding_set public; const struct config *conf; const struct seat *seat; size_t conf_ref_count; + + /* Virtual to real modifier mappings */ + struct vmod_map vmods[8]; }; typedef tll(struct key_set) bind_set_list_t; @@ -44,6 +53,50 @@ key_binding_manager_destroy(struct key_binding_manager *mgr) free(mgr); } +static void +initialize_vmod_mappings(struct key_set *set) +{ + if (set->seat == NULL || set->seat->kbd.xkb_keymap == NULL) + return; + + set->vmods[0].name = XKB_VMOD_NAME_ALT; + set->vmods[1].name = XKB_VMOD_NAME_HYPER; + set->vmods[2].name = XKB_VMOD_NAME_LEVEL3; + set->vmods[3].name = XKB_VMOD_NAME_LEVEL5; + set->vmods[4].name = XKB_VMOD_NAME_META; + set->vmods[5].name = XKB_VMOD_NAME_NUM; + set->vmods[6].name = XKB_VMOD_NAME_SCROLL; + set->vmods[7].name = XKB_VMOD_NAME_SUPER; + + struct xkb_state *scratch_state = xkb_state_new(set->seat->kbd.xkb_keymap); + xassert(scratch_state != NULL); + + for (size_t i = 0; i < ALEN(set->vmods); i++) { + xkb_mod_index_t virt_idx = xkb_keymap_mod_get_index( + set->seat->kbd.xkb_keymap, set->vmods[i].name); + + if (virt_idx != XKB_MOD_INVALID) { + xkb_mod_mask_t vmask = 1 << virt_idx; + xkb_state_update_mask(scratch_state, vmask, 0, 0, 0, 0, 0); + set->vmods[i].real_mask = xkb_state_serialize_mods( + scratch_state, XKB_STATE_MODS_DEPRESSED) & ~vmask; + set->vmods[i].virtual_mask = vmask; + + LOG_DBG("%s: 0x%04x -> 0x%04x", + set->vmods[i].name, + set->vmods[i].virtual_mask, + set->vmods[i].real_mask); + } else { + set->vmods[i].virtual_mask = 0; + set->vmods[i].real_mask = 0; + + LOG_DBG("%s: virtual modifier not available", set->vmods[i].name); + } + } + + xkb_state_unref(scratch_state); +} + void key_binding_new_for_seat(struct key_binding_manager *mgr, const struct seat *seat) @@ -67,6 +120,7 @@ key_binding_new_for_seat(struct key_binding_manager *mgr, }; tll_push_back(mgr->binding_sets, set); + initialize_vmod_mappings(&tll_back(mgr->binding_sets)); LOG_DBG("new (seat): set=%p, seat=%p, conf=%p, ref-count=1", (void *)&tll_back(mgr->binding_sets), @@ -107,6 +161,7 @@ key_binding_new_for_conf(struct key_binding_manager *mgr, }; tll_push_back(mgr->binding_sets, set); + initialize_vmod_mappings(&tll_back(mgr->binding_sets)); load_keymap(&tll_back(mgr->binding_sets)); @@ -405,18 +460,35 @@ sort_binding_list(key_binding_list_t *list) } static xkb_mod_mask_t -mods_to_mask(const struct seat *seat, const config_modifier_list_t *mods) +mods_to_mask(const struct seat *seat, + const struct vmod_map *vmods, size_t vmod_count, + const config_modifier_list_t *mods) { xkb_mod_mask_t mask = 0; tll_foreach(*mods, it) { - xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item); + const xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item); if (idx == XKB_MOD_INVALID) { LOG_ERR("%s: invalid modifier name", it->item); continue; } - mask |= 1 << idx; + xkb_mod_mask_t mod = 1 << idx; + + /* Check if this is a virtual modifier, and if so, use the + real modifier it maps to instead */ + for (size_t i = 0; i < vmod_count; i++) { + if (vmods[i].virtual_mask == mod) { + mask |= vmods[i].real_mask; + mod = 0; + + LOG_DBG("%s: virtual modifier, mapped to 0x%04x", + it->item, vmods[i].real_mask); + break; + } + } + + mask |= mod; } return mask; @@ -429,7 +501,8 @@ convert_key_binding(struct key_set *set, { const struct seat *seat = set->seat; - xkb_mod_mask_t mods = mods_to_mask(seat, &conf_binding->modifiers); + xkb_mod_mask_t mods = mods_to_mask( + seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers); xkb_keysym_t sym = maybe_repair_key_combo(seat, conf_binding->k.sym, mods); struct key_binding binding = { @@ -487,7 +560,7 @@ convert_mouse_binding(struct key_set *set, .type = MOUSE_BINDING, .action = conf_binding->action, .aux = &conf_binding->aux, - .mods = mods_to_mask(set->seat, &conf_binding->modifiers), + .mods = mods_to_mask(set->seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers), .m = { .button = conf_binding->m.button, .count = conf_binding->m.count, @@ -528,7 +601,8 @@ load_keymap(struct key_set *set) convert_mouse_bindings(set); set->public.selection_overrides = mods_to_mask( - set->seat, &set->conf->mouse.selection_override_modifiers); + set->seat, set->vmods, ALEN(set->vmods), + &set->conf->mouse.selection_override_modifiers); } void @@ -538,8 +612,10 @@ key_binding_load_keymap(struct key_binding_manager *mgr, tll_foreach(mgr->binding_sets, it) { struct key_set *set = &it->item; - if (set->seat == seat) + if (set->seat == seat) { + initialize_vmod_mappings(set); load_keymap(set); + } } } From a43614f098236adf3b876cf8d6fbf1e72a1297ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 31 Mar 2025 10:13:19 +0200 Subject: [PATCH 152/353] doc: foot.ini: mention virtual modifiers are allowed --- doc/foot.ini.5.scd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index c663223d..043600d2 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1160,7 +1160,8 @@ Note that if *Shift* is one of the modifiers, the _key_ *must not* be in upper case. For example, *Control+Shift+V* will never trigger, but *Control+Shift+v* will. -Note that *Alt* is usually called *Mod1*. +The default key bindings all use "real" modifiers (*Mod1*, *Mod4* +etc), but "virtual" modifiers (*Alt*, *Super* etc) are allowed. *xkbcli interactive-wayland* can be useful for finding keysym names. From 0d8c7db962f43255ed0c2e98d9a36253bdddbd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 31 Mar 2025 11:08:22 +0200 Subject: [PATCH 153/353] changelog: reword, and remove section that no longer applies --- CHANGELOG.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dcc8372..bc297e6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,18 +68,13 @@ colors has not configured) is now done by linear RGB interpolation, rather than converting to HSL and adjusting the luminance ([#2006][2006]). -* XKB: virtual modifiers in keyboard events from the compositor are - now ignored. This works around various issues seen when running foot +* Virtual modifiers in keyboard events from the compositor are now + supported. This works around various issues seen when running foot under mutter (GNOME) ([#2009][2009]): - Some key combinations generating the wrong escape sequence in the kitty keyboard protocol. - some of foot's default shortcuts not working (mainly those using `Mod1`) out of the box. - - **Note: if you have custom key bindings in `foot.ini` that - includes one or more of the `Alt`, `Meta`, `Super`, `Hyper`, - `NumLock`, `ScrollLock`, `LevelThree` or `LevelFive` modifiers, - you need to update them; i.e. remove the virtual modifier(s), - leaving only the real modifiers (`Mod1`, `Mod2` etc).** * Virtual modifiers (e.g. `Alt` instead of `Mod1`, `Super` instead of `Mod4` etc) in key bindings are now recognized as being virtual, and are automatically mapped to the corresponding real modifier. This From 1760cb6ab82b355a0751f614f3b45c7446e23e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 2 Apr 2025 08:41:46 +0200 Subject: [PATCH 154/353] config: update default URL regex The old one is in some cases too liberal. The new one is stricter in two ways: 1. The protocol list is now explicit, rather than matching anything:// 2. Allowed characters are now limited to the "safe character set", the "reserved character set", and some from the "unsafe character set" Furthermore, some of the characters are restricted in how/when they are allowed: 1. Periods, commas, question marks etc are allowed inside an URL, but not at the end. 2. [ ], ( ), " " and ' ' are allowed but only when balanced. This allows us to match e.g. [http://foo.bar/foo[bar]] correctly. Closes #2016 --- CHANGELOG.md | 5 ++++ config.c | 69 +++++++++++++++++++++++++--------------------- doc/foot.ini.5.scd | 2 +- foot.ini | 2 +- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc297e6c..3e421014 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,9 +79,14 @@ `Mod4` etc) in key bindings are now recognized as being virtual, and are automatically mapped to the corresponding real modifier. This means you can use e.g. `Alt+b` instead of `Mod1+b`. +* Default URL regex changed to a much more strict variant + ([#2016][2016]). You can manually set the [old + one](https://codeberg.org/dnkl/foot/src/tag/1.21.0/foot.ini#L72), if + you prefer it over the new regex. [2006]: https://codeberg.org/dnkl/foot/issues/2006 [2009]: https://codeberg.org/dnkl/foot/issues/2009 +[2016]: https://codeberg.org/dnkl/foot/issues/2016 ### Deprecated diff --git a/config.c b/config.c index a8cdb34a..bfd3ffed 100644 --- a/config.c +++ b/config.c @@ -3446,39 +3446,46 @@ config_load(struct config *conf, const char *conf_path, tokenize_cmdline("xdg-open ${url}", &conf->url.launch.argv.args); { - /* - * Based on https://gist.github.com/gruber/249502, but modified: - * - Do not allow {} at all - * - Do allow matched [] - */ - const char *url_regex_string = + const char *url_regex_string = + "(" "(" - "(" - "[a-z][[:alnum:]-]+:" // protocol - "(" - "/{1,3}|[a-z0-9%]" // slashes (what's the OR part for?) - ")" - "|" - "www[:digit:]{0,3}[.]" - //"|" - //"[a-z0-9.\\-]+[.][a-z]{2,4}/" /* "looks like domain name followed by a slash" - remove? */ - ")" - "(" - "[^[:space:](){}<>]+" - "|" - "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" - "|" - "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" - ")+" - "(" - "\\(([^[:space:](){}<>]+|(\\([^[:space:](){}<>]+\\)))*\\)" - "|" - "\\[([^]\\[[:space:](){}<>]+|(\\[[^]\\[[:space:](){}<>]+\\]))*\\]" - "|" - "[^]\\[[:space:]`!(){};:'\".,<>?«»“”‘’]" - ")" + "(https?://|mailto:|ftp://|file:|ssh:|ssh://|git://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:)" + "|" + "www\\." ")" - ; + "(" + /* Safe + reserved + some unsafe characters parenthesis and double quotes omitted (we only allow them when balanced) */ + "[0-9a-zA-Z:/?#@!$&*+,;=.~_%^\\-]+" + "|" + /* Balanced "(...)". Content is same as above, plus all _other_ characters we require to be balanced */ + "\\([]\\[\"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\\-]*\\)" + "|" + /* Balanced "[...]". Content is same as above, plus all _other_ characters we require to be balanced */ + "\\[[\\(\\)\"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\\-]*\\]" + "|" + /* Balanced '"..."'. Content is same as above, plus all _other_ characters we require to be balanced */ + "\"[]\\[\\(\\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\\-]*\"" + "|" + /* Balanced "'...'". Content is same as above, plus all _other_ characters we require to be balanced */ + "'[]\\[\\(\\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\\-]*'" + ")+" + "(" + /* Same as above, except :?!,;. are excluded */ + "[0-9a-zA-Z/#@$&*+=~_%^\\-]" + "|" + /* Balanced "(...)". Content is same as above, plus all _other_ characters we require to be balanced */ + "\\([]\\[\"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\\-]*\\)" + "|" + /* Balanced "[...]". Content is same as above, plus all _other_ characters we require to be balanced */ + "\\[[\\(\\)\"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\\-]*\\]" + "|" + /* Balanced '"..."'. Content is same as above, plus all _other_ characters we require to be balanced */ + "\"[]\\[\\(\\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\\-]*\"" + "|" + /* Balanced "'...'". Content is same as above, plus all _other_ characters we require to be balanced */ + "'[]\\[\\(\\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\\-]*'" + ")" + ")"; int r = regcomp(&conf->url.preg, url_regex_string, REG_EXTENDED); xassert(r == 0); diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 043600d2..c32a8e06 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -828,7 +828,7 @@ section. whole regex match to be used as an URL, surround all of it with parenthesis: *(regex-pattern)*. - Default: _(([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))\*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))\*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’]))_ + Default: _(((https?://|mailto:|ftp://|file:|ssh:|ssh://|git://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:)|www\.)([0-9a-zA-Z:/?#@!$&\*+,;=.~\_%^\-]+|\([]\["0-9a-zA-Z:/?#@!$&'\*+,;=.~\_%^\-]\*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'\*+,;=.~\_%^\-]\*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'\*+,;=.~\_%^\-]\*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&\*+,;=.~\_%^\-]\*')+([0-9a-zA-Z/#@$&\*+=~\_%^\-]|\([]\["0-9a-zA-Z:/?#@!$&'\*+,;=.~\_%^\-]\*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'\*+,;=.~\_%^\-]\*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'\*+,;=.~\_%^\-]\*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&\*+,;=.~\_%^\-]\*'))_ # SECTION: regex diff --git a/foot.ini b/foot.ini index b852da07..b170dc34 100644 --- a/foot.ini +++ b/foot.ini @@ -69,7 +69,7 @@ # launch=xdg-open ${url} # label-letters=sadfjklewcmpgh # osc8-underline=url-mode -# regex=(([a-z][[:alnum:]-]+:(/{1,3}|[a-z0-9%])|www[:digit:]{0,3}[.])([^[:space:](){}<>]+|\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\])+(\(([^[:space:](){}<>]+|(\([^[:space:](){}<>]+\)))*\)|\[([^]\[[:space:](){}<>]+|(\[[^]\[[:space:](){}<>]+\]))*\]|[^]\[[:space:]`!(){};:'".,<>?«»“”‘’])) +# regex=(((https?://|mailto:|ftp://|file:|ssh:|ssh://|git://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:)|www\.)([0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]+|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*')+([0-9a-zA-Z/#@$&*+=~_%^\-]|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*')) # You can define your own regex's, by adding a section called # 'regex:' with a 'regex' and 'launch' key. These can then be tied From bdf65672c0d7567b78d065583bac3c2473690a0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wojni=C5=82owicz?= Date: Thu, 3 Apr 2025 18:09:53 +0200 Subject: [PATCH 155/353] Themes: Add 'Molokai' theme --- themes/molokai | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 themes/molokai diff --git a/themes/molokai b/themes/molokai new file mode 100644 index 00000000..c3935f69 --- /dev/null +++ b/themes/molokai @@ -0,0 +1,23 @@ +# -*- conf -*- +# Molokai +# Based on zhou13's at https://github.com/zhou13/molokai-terminal/blob/master/xterm/Xresources + +[colors] +background=1B1D1E +foreground=CCCCCC +regular0=1B1D1E +regular1=FF0044 +regular2=82B414 +regular3=FD971F +regular4=266C98 +regular5=AC0CB1 +regular6=AE81FF +regular7=CCCCCC +bright0=808080 +bright1=F92672 +bright2=A6E22E +bright3=E6DB74 +bright4=7070F0 +bright5=D63AE1 +bright6=66D9EF +bright7=F8F8F2 From 34d3f4664b93d42ec3e1eef9a11e78756465d25a Mon Sep 17 00:00:00 2001 From: Dominique Martinet Date: Sun, 6 Apr 2025 15:35:54 +0900 Subject: [PATCH 156/353] xkbcommon: require libxkbcommon >= 1.8.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trying to build with an older libxkbcommon fails as follow: ``` ../input.c: In function ‘keyboard_keymap’: ../input.c:648:82: error: ‘XKB_VMOD_NAME_ALT’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_ALT’? 648 | xkb_mod_index_t alt = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_ALT); | ^~~~~~~~~~~~~~~~~ | XKB_MOD_NAME_ALT ../input.c:648:82: note: each undeclared identifier is reported only once for each function it appears in ../input.c:649:83: error: ‘XKB_VMOD_NAME_META’ undeclared (first use in this function); did you mean XKB_MOD_NAME_ALT’? 649 | xkb_mod_index_t meta = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_META); | ^~~~~~~~~~~~~~~~~~ | XKB_MOD_NAME_ALT ../input.c:650:84: error: ‘XKB_VMOD_NAME_SUPER’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_NUM’? 650 | xkb_mod_index_t super = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SUPER); | ^~~~~~~~~~~~~~~~~~~ | XKB_MOD_NAME_NUM ../input.c:651:84: error: ‘XKB_VMOD_NAME_HYPER’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_CAPS’? 651 | xkb_mod_index_t hyper = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_HYPER); | ^~~~~~~~~~~~~~~~~~~ | XKB_MOD_NAME_CAPS ../input.c:652:87: error: ‘XKB_VMOD_NAME_NUM’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_NUM’? 652 | xkb_mod_index_t num_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_NUM); | ^~~~~~~~~~~~~~~~~ | XKB_MOD_NAME_NUM ../input.c:653:90: error: ‘XKB_VMOD_NAME_SCROLL’ undeclared (first use in this function); did you mean ‘XKB_LED_NAME_SCROLL’? 653 | xkb_mod_index_t scroll_lock = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_SCROLL); | ^~~~~~~~~~~~~~~~~~~~ | XKB_LED_NAME_SCROLL ../input.c:654:90: error: ‘XKB_VMOD_NAME_LEVEL3’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_CTRL’? 654 | xkb_mod_index_t level_three = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL3); | ^~~~~~~~~~~~~~~~~~~~ | XKB_MOD_NAME_CTRL ../input.c:655:89: error: ‘XKB_VMOD_NAME_LEVEL5’ undeclared (first use in this function); did you mean ‘XKB_MOD_NAME_CTRL’? 655 | xkb_mod_index_t level_five = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, XKB_VMOD_NAME_LEVEL5); | ^~~~~~~~~~~~~~~~~~~~ | XKB_MOD_NAME_CTRL ``` --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index a9e47b3b..77869cc7 100644 --- a/meson.build +++ b/meson.build @@ -137,7 +137,7 @@ wayland_protocols = dependency('wayland-protocols', version: '>=1.41', default_options: ['tests=false']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') -xkb = dependency('xkbcommon', version: '>=1.0.0') +xkb = dependency('xkbcommon', version: '>=1.8.0') fontconfig = dependency('fontconfig') utf8proc = dependency('libutf8proc', required: get_option('grapheme-clustering')) From 091aa90f1a726507803108b217f52224904115be Mon Sep 17 00:00:00 2001 From: Dominique Martinet Date: Sun, 6 Apr 2025 15:48:29 +0900 Subject: [PATCH 157/353] wayland: handle xdg-shell edge constraints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wayland-protocols commit 86750c99ed06 ("xdg-shell: Add edge constraints") added a few more enums to handle, making the build fail with -Werror: ../wayland.c: In function ‘xdg_toplevel_configure’: ../wayland.c:878:9: error: enumeration value ‘XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT’ not handled in switch [-Werror=switch] 878 | switch (*state) { | ^~~~~~ ../wayland.c:878:9: error: enumeration value ‘XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT’ not handled in switch [-Werror=switch] ../wayland.c:878:9: error: enumeration value ‘XDG_TOPLEVEL_STATE_CONSTRAINED_TOP’ not handled in switch [-Werror=switch] ../wayland.c:878:9: error: enumeration value ‘XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM’ not handled in switch [-Werror=switch] (This is not part of any release yet, but can be used when building with the submodule) From a quick look it sounds like the meaning is the same as tiling as far as we are concerned so handle these as we do of tiling. --- wayland.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wayland.c b/wayland.c index 14d9bed9..b13e801d 100644 --- a/wayland.c +++ b/wayland.c @@ -887,6 +887,12 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, #if defined(XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION) case XDG_TOPLEVEL_STATE_SUSPENDED: is_suspended = true; break; +#endif +#if defined(XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION) + case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: is_tiled_left = true; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: is_tiled_right = true; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: is_tiled_top = true; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: is_tiled_bottom = true; break; #endif } From 23431e3ecfb59f71627b332852b906dc3d0bfad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 7 Apr 2025 13:32:30 +0200 Subject: [PATCH 158/353] wayland+input: add support for toplevel edge constraints Edge constraints are new (not yet available in a wayland-protocols release) toplevel states, acting as a complement to the existing tiled states. Tiled tells us we shouldn't draw shadows etc *outside our window geometry*. Constrained tells us the window cannot be resized in the constrained direction. This patch does a couple of things: * Recognize the new states when debug logging * Change is_top_left() etc to look at the new constrained state instead of the tiled state. These functions are used when both choosing cursor shape, and when determining if/how to resize a window on a CSD edge click-and-drag. * Update cursor shape selection to use the default (left_ptr) shape when on a constrained edge (or corner). * Update CSD resize triggering, to not trigger a resize when attempted on a constrained edge (or corner). See https://gitlab.freedesktop.org/wayland/wayland-protocols/-/commit/86750c99ed062c306e837f11bb9492df572ad677: An edge constraint is an complementery state to the tiled state, meaning that it's not only tiled, but constrained in a way that it can't resize in that direction. This typically means that the constrained edge is tiled against a monitor edge. An example configuration is two windows tiled next to each other on a single monitor. Together they cover the whole work area. The left window would have the following tiled and edge constraint state: [ tiled_top, tiled_right, tiled_bottom, tiled_left, constrained_top, constrained_bottom, constrained_left ] while the right window would have the following: [ tiled_top, tiled_right, tiled_bottom, tiled_left, constrained_top, constrained_bottom, constrained_right ] This aims to replace and deprecate the `gtk_surface1.configure_edges` event and the `gtk_surface1.edge_constraint` enum. --- CHANGELOG.md | 8 ++++++ input.c | 71 +++++++++++++++++++++++++++++++++++++--------------- wayland.c | 52 ++++++++++++++++++++++++++++++-------- wayland.h | 13 ++++++++++ 4 files changed, 114 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e421014..bf01bb7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,12 @@ ## Unreleased ### Added + +* Support for toplevel edge constraints. When the compositor indicates + the toplevel has edge constraints, foot will not allow the window to + be resized (via CSDs) in the constrained directions. + + ### Changed * UTF-8 error recovery now discards fewer bytes. @@ -83,6 +89,8 @@ ([#2016][2016]). You can manually set the [old one](https://codeberg.org/dnkl/foot/src/tag/1.21.0/foot.ini#L72), if you prefer it over the new regex. +* A tiled window can now be resized in the corners (via CSDs), unless + the compositor has indicated the toplevel has edge constraints. [2006]: https://codeberg.org/dnkl/foot/issues/2006 [2009]: https://codeberg.org/dnkl/foot/issues/2009 diff --git a/input.c b/input.c index abaac8eb..0f2a8446 100644 --- a/input.c +++ b/input.c @@ -2280,7 +2280,7 @@ is_top_left(const struct terminal *term, int x, int y) { int csd_border_size = term->conf->csd.border_width; return ( - (!term->window->is_tiled_top && !term->window->is_tiled_left) && + (!term->window->is_constrained_top && !term->window->is_constrained_left) && ((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))); } @@ -2290,7 +2290,7 @@ is_top_right(const struct terminal *term, int x, int y) { int csd_border_size = term->conf->csd.border_width; return ( - (!term->window->is_tiled_top && !term->window->is_tiled_right) && + (!term->window->is_constrained_top && !term->window->is_constrained_right) && ((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))); } @@ -2301,7 +2301,7 @@ 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->window->is_tiled_bottom && !term->window->is_tiled_left) && + (!term->window->is_constrained_bottom && !term->window->is_constrained_left) && ((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))); } @@ -2312,7 +2312,7 @@ 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->window->is_tiled_bottom && !term->window->is_tiled_right) && + (!term->window->is_constrained_bottom && !term->window->is_constrained_right) && ((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))); } @@ -2324,10 +2324,23 @@ xcursor_for_csd_border(struct terminal *term, int x, int y) else if (is_top_right(term, x, y)) return CURSOR_SHAPE_TOP_RIGHT_CORNER; else if (is_bottom_left(term, x, y)) return CURSOR_SHAPE_BOTTOM_LEFT_CORNER; else if (is_bottom_right(term, x, y)) return CURSOR_SHAPE_BOTTOM_RIGHT_CORNER; - else if (term->active_surface == TERM_SURF_BORDER_LEFT) return CURSOR_SHAPE_LEFT_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_RIGHT) return CURSOR_SHAPE_RIGHT_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_TOP) return CURSOR_SHAPE_TOP_SIDE; - else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) return CURSOR_SHAPE_BOTTOM_SIDE; + + else if (term->active_surface == TERM_SURF_BORDER_LEFT) + return !term->window->is_constrained_left + ? CURSOR_SHAPE_LEFT_SIDE : CURSOR_SHAPE_LEFT_PTR; + + else if (term->active_surface == TERM_SURF_BORDER_RIGHT) + return !term->window->is_constrained_right + ? CURSOR_SHAPE_RIGHT_SIDE : CURSOR_SHAPE_LEFT_PTR; + + else if (term->active_surface == TERM_SURF_BORDER_TOP) + return !term->window->is_constrained_top + ? CURSOR_SHAPE_TOP_SIDE : CURSOR_SHAPE_LEFT_PTR; + + else if (term->active_surface == TERM_SURF_BORDER_BOTTOM) + return !term->window->is_constrained_bottom + ? CURSOR_SHAPE_BOTTOM_SIDE : CURSOR_SHAPE_LEFT_PTR; + else { BUG("Unreachable"); return CURSOR_SHAPE_NONE; @@ -3095,15 +3108,8 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, 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; + enum xdg_toplevel_resize_edge resize_type = XDG_TOPLEVEL_RESIZE_EDGE_NONE; int x = seat->mouse.x; int y = seat->mouse.y; @@ -3116,11 +3122,36 @@ wl_pointer_button(void *data, struct wl_pointer *wl_pointer, 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]; + else { + if (term->active_surface == TERM_SURF_BORDER_LEFT && + !term->window->is_constrained_left) + { + resize_type = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + } - xdg_toplevel_resize( - term->window->xdg_toplevel, seat->wl_seat, serial, resize_type); + else if (term->active_surface == TERM_SURF_BORDER_RIGHT && + !term->window->is_constrained_right) + { + resize_type = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + } + + else if (term->active_surface == TERM_SURF_BORDER_TOP && + !term->window->is_constrained_top) + { + resize_type = XDG_TOPLEVEL_RESIZE_EDGE_TOP; + } + + else if (term->active_surface == TERM_SURF_BORDER_BOTTOM && + !term->window->is_constrained_bottom) + { + resize_type = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + } + } + + if (resize_type != XDG_TOPLEVEL_RESIZE_EDGE_NONE) { + xdg_toplevel_resize( + term->window->xdg_toplevel, seat->wl_seat, serial, resize_type); + } } return; } diff --git a/wayland.c b/wayland.c index b13e801d..853124be 100644 --- a/wayland.c +++ b/wayland.c @@ -852,6 +852,10 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, bool is_tiled_bottom = false; bool is_tiled_left = false; bool is_tiled_right = false; + bool is_constrained_top = false; + bool is_constrained_bottom = false; + bool is_constrained_left = false; + bool is_constrained_right = false; bool is_suspended UNUSED = false; #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG @@ -869,6 +873,12 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, [XDG_TOPLEVEL_STATE_TILED_BOTTOM] = "tiled:bottom", #if defined(XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION) /* wayland-protocols >= 1.32 */ [XDG_TOPLEVEL_STATE_SUSPENDED] = "suspended", +#endif +#if defined(XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION) + [XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT] = "constrained:left", + [XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT] = "constrained:right", + [XDG_TOPLEVEL_STATE_CONSTRAINED_TOP] = "constrained:top", + [XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM] = "constrained:bottom", #endif }; #endif @@ -889,10 +899,10 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, case XDG_TOPLEVEL_STATE_SUSPENDED: is_suspended = true; break; #endif #if defined(XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION) - case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: is_tiled_left = true; break; - case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: is_tiled_right = true; break; - case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: is_tiled_top = true; break; - case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: is_tiled_bottom = true; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT: is_constrained_left = true; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT: is_constrained_right = true; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_TOP: is_constrained_top = true; break; + case XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM: is_constrained_bottom = true; break; #endif } @@ -933,6 +943,10 @@ xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, win->configure.is_tiled_bottom = is_tiled_bottom; win->configure.is_tiled_left = is_tiled_left; win->configure.is_tiled_right = is_tiled_right; + win->configure.is_constrained_top = is_constrained_top; + win->configure.is_constrained_bottom = is_constrained_bottom; + win->configure.is_constrained_left = is_constrained_left; + win->configure.is_constrained_right = is_constrained_right; win->configure.width = width; win->configure.height = height; } @@ -1056,14 +1070,22 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, win->is_maximized = win->configure.is_maximized; win->is_fullscreen = win->configure.is_fullscreen; win->is_resizing = win->configure.is_resizing; + win->is_tiled_top = win->configure.is_tiled_top; win->is_tiled_bottom = win->configure.is_tiled_bottom; win->is_tiled_left = win->configure.is_tiled_left; win->is_tiled_right = win->configure.is_tiled_right; + + win->is_constrained_top = win->configure.is_constrained_top; + win->is_constrained_bottom = win->configure.is_constrained_bottom; + win->is_constrained_left = win->configure.is_constrained_left; + win->is_constrained_right = win->configure.is_constrained_right; + win->is_tiled = (win->is_tiled_top || win->is_tiled_bottom || win->is_tiled_left || win->is_tiled_right); + win->csd_mode = win->configure.csd_mode; bool enable_csd = win->csd_mode == CSD_YES && !win->is_fullscreen; @@ -1239,13 +1261,23 @@ handle_global(void *data, struct wl_registry *registry, return; /* - * We *require* version 1, but _can_ use version 5. Version 2 - * adds 'tiled' window states. We use that information to - * restore the window size when window is un-tiled. Version 5 - * adds 'wm_capabilities'. We use that information to draw - * window decorations. + * We *require* version 1, but _can_ use version 2, 5 or 7, if + * available. + * + * Version 2 adds 'tiled' window states. We use this + * information to restore the window size when window is + * un-tiled. + * + * Version 5 adds 'wm_capabilities'. We use this information + * to draw window decorations. + * + * Version 7 adds 'constrained' window states. We use this + * information to determine whether to allow window resize + * (via CSDs) or not. */ -#if defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) +#if defined(XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION) + const uint32_t preferred = XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION; +#elif defined(XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) const uint32_t preferred = XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION; #elif defined(XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION) const uint32_t preferred = XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION; diff --git a/wayland.h b/wayland.h index 37dd7860..a9d6858c 100644 --- a/wayland.h +++ b/wayland.h @@ -402,6 +402,12 @@ struct wl_window { bool is_tiled_left; bool is_tiled_right; bool is_tiled; /* At least one of is_tiled_{top,bottom,left,right} is true */ + + bool is_constrained_top; + bool is_constrained_bottom; + bool is_constrained_left; + bool is_constrained_right; + struct { int width; int height; @@ -409,10 +415,17 @@ struct wl_window { bool is_fullscreen:1; bool is_maximized:1; bool is_resizing:1; + bool is_tiled_top:1; bool is_tiled_bottom:1; bool is_tiled_left:1; bool is_tiled_right:1; + + bool is_constrained_top:1; + bool is_constrained_bottom:1; + bool is_constrained_left:1; + bool is_constrained_right:1; + enum csd_mode csd_mode; } configure; From bc2e0a29bba936728cd2033fcb795351b738e8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 10 Apr 2025 12:18:34 +0200 Subject: [PATCH 159/353] changelog: move vmod support in config from "changed" to "added" --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf01bb7c..c2f18c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,10 @@ * Support for toplevel edge constraints. When the compositor indicates the toplevel has edge constraints, foot will not allow the window to be resized (via CSDs) in the constrained directions. +* Virtual modifiers (e.g. `Alt` instead of `Mod1`, `Super` instead of + `Mod4` etc) in key bindings are now recognized as being virtual, and + are automatically mapped to the corresponding real modifier. This + means you can use e.g. `Alt+b` instead of `Mod1+b`. ### Changed @@ -81,10 +85,6 @@ kitty keyboard protocol. - some of foot's default shortcuts not working (mainly those using `Mod1`) out of the box. -* Virtual modifiers (e.g. `Alt` instead of `Mod1`, `Super` instead of - `Mod4` etc) in key bindings are now recognized as being virtual, and - are automatically mapped to the corresponding real modifier. This - means you can use e.g. `Alt+b` instead of `Mod1+b`. * Default URL regex changed to a much more strict variant ([#2016][2016]). You can manually set the [old one](https://codeberg.org/dnkl/foot/src/tag/1.21.0/foot.ini#L72), if From b93d2f042c378e9a2a6d0882c79331492d5dee09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 13 Apr 2025 08:26:20 +0200 Subject: [PATCH 160/353] url-mode: fix double-width characters not being handled correctly When a regex matches a string containing double-width characters, the CELL_SPACER values were included in the URL string. This meant the final URL (either launched, or copied) weren't handled correctly, as invalid UTF-8 sequences were inserted in the middle of the string. Closes #2027 --- CHANGELOG.md | 3 +++ url-mode.c | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2f18c5f..e9f82999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,9 +106,12 @@ * Build failure (`srgb.h` not found) when doing a parallel build. * Regression: reflowing (changing the window size) removing empty lines ([#2011][2011]). +* `url/regex-copy` not handling double-width characters correctly + ([#2027][2027]). [2000]: https://codeberg.org/dnkl/foot/issues/2000 [2011]: https://codeberg.org/dnkl/foot/issues/2011 +[2027]: https://codeberg.org/dnkl/foot/issues/2027 ### Security diff --git a/url-mode.c b/url-mode.c index ed260597..d0f7fc53 100644 --- a/url-mode.c +++ b/url-mode.c @@ -347,6 +347,9 @@ regex_detected(const struct terminal *term, enum url_action action, wc_count = composed->count; } + else if (wc[0] >= CELL_SPACER) + continue; + /* Convert wide character to utf8 */ for (size_t i = 0; i < wc_count; i++) { char buf[16]; @@ -355,6 +358,7 @@ regex_detected(const struct terminal *term, enum url_action action, if (char_len == (size_t)-1) continue; + for (size_t j = 0; j < char_len; j++) { const size_t requires_size = vline->len + char_len; @@ -411,9 +415,9 @@ regex_detected(const struct terminal *term, enum url_action action, const size_t end = start + mlen; LOG_DBG( - "regex match at row %d: %.*srow/col = %dx%d", + "regex match at row %d: %.*s (%zu bytes), row/col = %dx%d", matches[1].rm_so, (int)mlen, &search_string[matches[1].rm_so], - v->map[start].row, v->map[start].col); + mlen, v->map[start].row, v->map[start].col); tll_push_back( *urls, From 9a6227acb354255aec507329d29b3f4a31429ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 14 Apr 2025 07:03:37 +0200 Subject: [PATCH 161/353] doc: foot.ini: workers: "if you have a ridiculous number of cores" --- doc/foot.ini.5.scd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index c32a8e06..24df3cbb 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -419,6 +419,10 @@ empty string to be set, but it must be quoted: *KEY=""*) multithreading. Default: the number of available logical CPUs (including SMT). Note that this is not always the best value. In some cases, the number of physical _cores_ is better. + + In case you have a ridiculous amount of cores and/or threads, + consider limiting the number of *workers*, since foot cannot + parallelize more than the number of visible rows. *utmp-helper* Path to utmp logging helper binary. From 5f83278afd0530c323d4192e1095b3d1dea644c9 Mon Sep 17 00:00:00 2001 From: Fazzi Date: Mon, 9 Oct 2023 18:47:09 +0100 Subject: [PATCH 162/353] config: add alpha_mode option --- CHANGELOG.md | 3 +++ config.c | 10 ++++++++++ config.h | 2 ++ foot.ini | 2 ++ render.c | 21 +++++++++++++++++++++ 5 files changed, 38 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9f82999..f3ef0622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -788,6 +788,9 @@ ### Added +* `alpha-mode` option to `foot.ini`. Defaults to `default`. This + config changes how alpha is handled on background colours not set by + the terminal.(e.g. vim) ([#1510](1510)) * Support for building with _wayland-protocols_ as a subproject. * Mouse wheel scrolls can now be used in `mouse-bindings` ([#1077][1077]). diff --git a/config.c b/config.c index bfd3ffed..aa52b89b 100644 --- a/config.c +++ b/config.c @@ -1095,6 +1095,15 @@ parse_section_main(struct context *ctx) return true; } + else if (strcmp(key, "alpha-mode") == 0) { + _Static_assert(sizeof(conf->alpha_mode) == sizeof(int), + "enum is not 32-bit"); + return value_to_enum( + ctx, + (const char *[]){"default", "matching", "all", NULL}, + (int *)&conf->alpha_mode); + } + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -3338,6 +3347,7 @@ config_load(struct config *conf, const char *conf_path, }, .multiplier = 3., }, + .alpha_mode = ALPHA_MODE_DEFAULT, .colors = { .fg = default_foreground, .bg = default_background, diff --git a/config.h b/config.h index a08fae31..18d1a477 100644 --- a/config.h +++ b/config.h @@ -167,6 +167,8 @@ struct config { enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode; + enum { ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL } alpha_mode; + bool dpi_aware; enum {GAMMA_CORRECT_DISABLED, GAMMA_CORRECT_ENABLED, diff --git a/foot.ini b/foot.ini index b170dc34..0981e180 100644 --- a/foot.ini +++ b/foot.ini @@ -38,6 +38,8 @@ # utmp-helper=/usr/lib/utempter/utempter # When utmp backend is ‘libutempter’ (Linux) # utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ‘ulog’ (FreeBSD) +# alpha-mode=default # Can be `default`, `matching` or `all` + [environment] # name=value diff --git a/render.c b/render.c index d2202468..2766e5ee 100644 --- a/render.c +++ b/render.c @@ -788,6 +788,27 @@ render_cell(struct terminal *term, pixman_image_t *pix, alpha = term->colors.alpha; } } + + if (!term->window->is_fullscreen) { + switch (term->conf->alpha_mode) { + case ALPHA_MODE_DEFAULT: { + if (cell->attrs.bg_src == COLOR_DEFAULT) { + alpha = term->colors.alpha; + } + break; + } + case ALPHA_MODE_MATCHING: { + if (cell->attrs.bg == term->colors.bg) { + alpha = term->colors.alpha; + } + break; + } + case ALPHA_MODE_ALL: { + alpha = term->colors.alpha; + break; + } + } + } } if (unlikely(is_selected && _fg == _bg)) { From bacfba135da5326d72508ba788a5cd8db3dcd671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 14 Apr 2025 16:48:44 +0200 Subject: [PATCH 163/353] changelog: move 'alpha-mode' to next-release --- CHANGELOG.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3ef0622..d28f567a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,11 @@ `Mod4` etc) in key bindings are now recognized as being virtual, and are automatically mapped to the corresponding real modifier. This means you can use e.g. `Alt+b` instead of `Mod1+b`. +* `alpha-mode` option to `foot.ini`. Defaults to `default`. This + config changes how alpha is handled on background colours not set by + the terminal.(e.g. vim) ([#2026](2026)) + +[2026]: https://codeberg.org/dnkl/foot/issues/2026 ### Changed @@ -788,9 +793,6 @@ ### Added -* `alpha-mode` option to `foot.ini`. Defaults to `default`. This - config changes how alpha is handled on background colours not set by - the terminal.(e.g. vim) ([#1510](1510)) * Support for building with _wayland-protocols_ as a subproject. * Mouse wheel scrolls can now be used in `mouse-bindings` ([#1077][1077]). From d2d4f538619177bc3af6c09e2465272f564ee780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 14 Apr 2025 16:58:23 +0200 Subject: [PATCH 164/353] config+render: move alpha-mode to colors.alpha-mode, fix cursor handling Move main.alpha-mode to colors.alpha-mode. Fix (inverted) cursor handling, by always using the bg color without alpha. Do a minor optimization, where we don't even lock at colors.alpha-mode if there's no transparency configured. --- config.c | 20 ++++----- config.h | 8 +++- render.c | 124 ++++++++++++++++++++++++++----------------------------- 3 files changed, 74 insertions(+), 78 deletions(-) diff --git a/config.c b/config.c index aa52b89b..347cc1ec 100644 --- a/config.c +++ b/config.c @@ -1095,15 +1095,6 @@ parse_section_main(struct context *ctx) return true; } - else if (strcmp(key, "alpha-mode") == 0) { - _Static_assert(sizeof(conf->alpha_mode) == sizeof(int), - "enum is not 32-bit"); - return value_to_enum( - ctx, - (const char *[]){"default", "matching", "all", NULL}, - (int *)&conf->alpha_mode); - } - else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -1490,6 +1481,15 @@ parse_section_colors(struct context *ctx) return true; } + else if (strcmp(key, "alpha-mode") == 0) { + _Static_assert(sizeof(conf->colors.alpha_mode) == sizeof(int), + "enum is not 32-bit"); + + return value_to_enum( + ctx, + (const char *[]){"default", "matching", "all", NULL}, + (int *)&conf->colors.alpha_mode); + } else { LOG_CONTEXTUAL_ERR("not valid option"); @@ -3347,13 +3347,13 @@ config_load(struct config *conf, const char *conf_path, }, .multiplier = 3., }, - .alpha_mode = ALPHA_MODE_DEFAULT, .colors = { .fg = default_foreground, .bg = default_background, .flash = 0x7f7f00, .flash_alpha = 0x7fff, .alpha = 0xffff, + .alpha_mode = ALPHA_MODE_DEFAULT, .selection_fg = 0x80000000, /* Use default bg */ .selection_bg = 0x80000000, /* Use default fg */ .use_custom = { diff --git a/config.h b/config.h index 18d1a477..2dec82c1 100644 --- a/config.h +++ b/config.h @@ -167,8 +167,6 @@ struct config { enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode; - enum { ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL } alpha_mode; - bool dpi_aware; enum {GAMMA_CORRECT_DISABLED, GAMMA_CORRECT_ENABLED, @@ -260,6 +258,12 @@ struct config { uint32_t dim[8]; uint32_t sixel[16]; + enum { + ALPHA_MODE_DEFAULT, + ALPHA_MODE_MATCHING, + ALPHA_MODE_ALL + } alpha_mode; + struct { uint32_t fg; uint32_t bg; diff --git a/render.c b/render.c index 2766e5ee..fdf7015c 100644 --- a/render.c +++ b/render.c @@ -605,13 +605,8 @@ cursor_colors_for_cell(const struct terminal *term, const struct cell *cell, if (term->colors.cursor_fg >> 31) *text_color = color_hex_to_pixman(term->colors.cursor_fg, gamma_correct); else { + xassert(bg->alpha == 0xffff); *text_color = *bg; - - if (unlikely(text_color->alpha != 0xffff)) { - /* The *only* color that can have transparency is the - * default background color */ - *text_color = color_hex_to_pixman(term->colors.bg, gamma_correct); - } } if (text_color->red == cursor_color->red && @@ -749,65 +744,58 @@ render_cell(struct terminal *term, pixman_image_t *pix, _bg = swap; } - else if (cell->attrs.bg_src == COLOR_DEFAULT) { - if (term->window->is_fullscreen) { - /* - * Note: disable transparency when fullscreened. - * - * This is because the wayland protocol mandates no - * screen content is shown behind the fullscreened - * window. - * - * The _intent_ of the specification is that a black - * (or other static color) should be used as - * background. - * - * There's a bit of gray area however, and some - * compositors have chosen to interpret the - * specification in a way that allows wallpapers to be - * seen through a fullscreen window. - * - * Given that a) the intent of the specification, and - * b) we don't know what the compositor will do, we - * simply disable transparency while in fullscreen. - * - * To see why, consider what happens if we keep our - * transparency. For example, if the background color - * is white, and alpha is 0.5, then the window will be - * drawn in a shade of gray while fullscreened. - * - * See - * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/116 - * for a discussion on whether transparent, fullscreen - * windows should be allowed in some way or not. - * - * NOTE: if changing this, also update render_margin() - */ - xassert(alpha == 0xffff); - } else { - alpha = term->colors.alpha; - } - } - - if (!term->window->is_fullscreen) { - switch (term->conf->alpha_mode) { - case ALPHA_MODE_DEFAULT: { - if (cell->attrs.bg_src == COLOR_DEFAULT) { - alpha = term->colors.alpha; - } - break; - } - case ALPHA_MODE_MATCHING: { - if (cell->attrs.bg == term->colors.bg) { - alpha = term->colors.alpha; - } - break; - } - case ALPHA_MODE_ALL: { + if (!term->window->is_fullscreen && term->colors.alpha != 0xffff) { + switch (term->conf->colors.alpha_mode) { + case ALPHA_MODE_DEFAULT: { + if (cell->attrs.bg_src == COLOR_DEFAULT) { alpha = term->colors.alpha; - break; } + break; } + + case ALPHA_MODE_MATCHING: { + if (cell->attrs.bg == term->colors.bg) + alpha = term->colors.alpha; + break; + } + + case ALPHA_MODE_ALL: { + alpha = term->colors.alpha; + break; + } + } + } else { + /* + * Note: disable transparency when fullscreened. + * + * This is because the wayland protocol mandates no screen + * content is shown behind the fullscreened window. + * + * The _intent_ of the specification is that a black (or + * other static color) should be used as background. + * + * There's a bit of gray area however, and some + * compositors have chosen to interpret the specification + * in a way that allows wallpapers to be seen through a + * fullscreen window. + * + * Given that a) the intent of the specification, and b) + * we don't know what the compositor will do, we simply + * disable transparency while in fullscreen. + * + * To see why, consider what happens if we keep our + * transparency. For example, if the background color is + * white, and alpha is 0.5, then the window will be drawn + * in a shade of gray while fullscreened. + * + * See + * https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/116 + * for a discussion on whether transparent, fullscreen + * windows should be allowed in some way or not. + * + * NOTE: if changing this, also update render_margin() + */ + xassert(alpha == 0xffff); } } @@ -1012,8 +1000,10 @@ render_cell(struct terminal *term, pixman_image_t *pix, mtx_unlock(&term->render.workers.lock); } - if (unlikely(has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus)) - draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols); + if (unlikely(has_cursor && term->cursor_style == CURSOR_BLOCK && term->kbd_focus)) { + const pixman_color_t bg_without_alpha = color_hex_to_pixman(_bg, gamma_correct); + draw_cursor(term, cell, font, pix, &fg, &bg_without_alpha, x, y, cell_cols); + } if (cell->wc == 0 || cell->wc >= CELL_SPACER || cell->wc == U'\t' || (unlikely(cell->attrs.conceal) && !is_selected)) @@ -1161,8 +1151,10 @@ render_cell(struct terminal *term, pixman_image_t *pix, } draw_cursor: - if (has_cursor && (term->cursor_style != CURSOR_BLOCK || !term->kbd_focus)) - draw_cursor(term, cell, font, pix, &fg, &bg, x, y, cell_cols); + if (has_cursor && (term->cursor_style != CURSOR_BLOCK || !term->kbd_focus)) { + const pixman_color_t bg_without_alpha = color_hex_to_pixman(_bg, gamma_correct); + draw_cursor(term, cell, font, pix, &fg, &bg_without_alpha, x, y, cell_cols); + } pixman_image_set_clip_region32(pix, NULL); return cell_cols; From f7807c0f4c5f1e01b72157bbad650d20a1a9d4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 14 Apr 2025 17:00:07 +0200 Subject: [PATCH 165/353] tests: config: test colors.alpha-mode --- tests/test-config.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test-config.c b/tests/test-config.c index f431f4ab..69d349b4 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -720,6 +720,11 @@ test_section_colors(void) &conf.colors.search_box.match.fg, &conf.colors.search_box.match.bg); + test_enum(&ctx, &parse_section_colors, "alpha-mode", 3, + (const char *[]){"default", "matching", "all"}, + (int []){ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL}, + (int *)&conf.colors.alpha_mode); + for (size_t i = 0; i < 255; i++) { char key_name[4]; sprintf(key_name, "%zu", i); From 9ba8caf30b9d042260e4125b30a17a0f3c96382e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 14 Apr 2025 17:02:45 +0200 Subject: [PATCH 166/353] doc: foot.ini: add colors.alpha-mode --- doc/foot.ini.5.scd | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 24df3cbb..083a1087 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1031,6 +1031,21 @@ can configure the background transparency with the _alpha_ option. 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_. +*alpha-mode* + Specifies when *alpha* is applied. One of *default*, *matching* or + *all*. + + *default* applies *alpha* to cells with the default background + color, excluding cells with the same RGB value as the default + background color. + + *matching* is the same as *default*, but also applies *alpha* to + cells with the same RGB value as the default background color. + + *all* applies *alpha* to all cells, regardless of background color. + + Default: _default_ + *selection-foreground*, *selection-background* Foreground (text) and background color to use in selected text. Note that *both* options must be set, or the default will be From b46a9aa6d7b76712ea0dca4f3799ae4a504843db Mon Sep 17 00:00:00 2001 From: datsudo <76833632+datsudo@users.noreply.github.com> Date: Mon, 14 Apr 2025 22:01:54 +0800 Subject: [PATCH 167/353] themes: add "Night Owl" theme --- themes/night-owl | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 themes/night-owl diff --git a/themes/night-owl b/themes/night-owl new file mode 100644 index 00000000..03e1d8f7 --- /dev/null +++ b/themes/night-owl @@ -0,0 +1,30 @@ +# _*_ conf _*_ +# Night Owl + +[cursor] +color=011627 80a4c2 + +[colors] +foreground=d6deeb +background=011627 + +regular0=011627 +regular1=ef5350 +regular2=22da6e +regular3=addb67 +regular4=82aaff +regular5=c792ea +regular6=21c7a8 +regular7=ffffff + +bright0=575656 +bright1=ef5350 +bright2=22da6e +bright3=ffeb95 +bright4=82aaff +bright5=c792ea +bright6=7fdbca +bright7=ffffff + +selection-background=5f7e97 +selection-foreground=dfe5ee From 2c8214f6eac2ad8def58ecce76e806db18b4a4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 17 Apr 2025 14:41:13 +0200 Subject: [PATCH 168/353] changelog: prepare for 1.22.0 --- CHANGELOG.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d28f567a..374d0c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.22.0](#1-22-0) * [1.21.0](#1-21-0) * [1.20.2](#1-20-2) * [1.20.1](#1-20-1) @@ -59,7 +59,8 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.22.0 + ### Added * Support for toplevel edge constraints. When the compositor indicates @@ -102,8 +103,6 @@ [2016]: https://codeberg.org/dnkl/foot/issues/2016 -### Deprecated -### Removed ### Fixed * Regression: assertion in `url-mode.c` when activating a second URL @@ -119,9 +118,16 @@ [2027]: https://codeberg.org/dnkl/foot/issues/2027 -### Security ### Contributors +* Alex Xu (Hello71) +* datsudo +* Dominique Martinet +* Fazzi +* llyyr +* Łukasz Wojniłowicz +* Sam McCall + ## 1.21.0 From 95f7b7105841f81788229dbc29055b0a98fff041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 17 Apr 2025 14:41:32 +0200 Subject: [PATCH 169/353] meson: bump version to 1.22.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 77869cc7..ed2dc7e4 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.21.0', + version: '1.22.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 6e5a602f67a1c95fbec07a1e19027c4454e8ab81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 17 Apr 2025 14:44:05 +0200 Subject: [PATCH 170/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 374d0c2c..d8787775 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.22.0](#1-22-0) * [1.21.0](#1-21-0) * [1.20.2](#1-20-2) @@ -59,6 +60,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.22.0 ### Added From 30aafce82d344e0ec77e4409945b733c66011433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 18 Apr 2025 13:59:43 +0200 Subject: [PATCH 171/353] foot.ini: move alpha-mode to colors section This is where the config parser expects it --- foot.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/foot.ini b/foot.ini index 0981e180..7d96ca0f 100644 --- a/foot.ini +++ b/foot.ini @@ -38,8 +38,6 @@ # utmp-helper=/usr/lib/utempter/utempter # When utmp backend is ‘libutempter’ (Linux) # utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ‘ulog’ (FreeBSD) -# alpha-mode=default # Can be `default`, `matching` or `all` - [environment] # name=value @@ -102,6 +100,7 @@ [colors] # alpha=1.0 +# alpha-mode=default # Can be `default`, `matching` or `all` # background=242424 # foreground=ffffff # flash=7f7f00 From 155c7c96b7a384913d62021b1fa039a1e9d01b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 18 Apr 2025 14:43:36 +0200 Subject: [PATCH 172/353] doc: foot.ini: key-bindings: untranslated symbols are tried before translated --- doc/foot.ini.5.scd | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 083a1087..d51409b8 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1190,17 +1190,18 @@ different approaches. As an example, let's say you press ctrl+shift+c (assume plain us ASCII layout). XKB will tell foot *Control+C* was pressed. Note the lack of the shift modifier, and the upper case 'C'. Internally, this is called -the "translated" form, and is what foot tries to match first. +the "translated" form. -If no "translated" key bindings can be found, foot proceeds to -checking the "untranslated" variant. Using the same example as above, -this will match *Control+Shift+c* (shift modifier present, lower case -'c'). +The "untranslated" form (*Control+Shift+c*) is derived from the +translated form, and is what foot tries to match first. + +If no "untranslated" key bindings can be found, foot proceeds to +checking the "translated" variant. This means you can use either form in your foot configuration, and -that *Control+C* (and similar) has higher priority than -*Control+Shift+c*. Also note that while foot normally detects when the -same combination is assigned to multiple actions, it will not detect +that *Control+Shift+c* (and similar) has higher priority than +*Control+C*. Also note that while foot normally detects when the same +combination is assigned to multiple actions, it will not detect *Control+C* vs. *Control+Shift+c* collisions. Call it a known bug... Finally, foot tries to match the raw key code. Here, the primary From 179e14e0a1792ebf7a5c2774a66b822ce2017a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 19 Apr 2025 09:16:28 +0200 Subject: [PATCH 173/353] doc: foot.ini: gamma-correct-blending: mention colors being off --- doc/foot.ini.5.scd | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index d51409b8..26eb1780 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -220,11 +220,12 @@ empty string to be set, but it must be quoted: *KEY=""*) than intended when rendered with gamma-correct blending, since the font designer set the font weight based on incorrect rendering. - You may also want to enable 10-bit image buffers when - gamma-correct blending is enabled. Though probably only if you do - not use a transparent background (with 10-bit buffers, you only - get 2 bits alpha). See *tweak.surface-bit-depth*. - + Note that some colors (especially dark ones) will look a bit + off. The reason for this is loss of color precision, due to foot + using 8-bit surfaces (i.e. each color channel is 8 bits). The + amount of errors can be reduced by using 10-bit surfaces; see + *tweak.surface-bit-depth*. + Default: enabled when compositor support is available *box-drawings-uses-font-glyphs* @@ -1978,13 +1979,13 @@ any of these options. best option. When *gamma-correct-blending* is enabled, you may want to enable - 10-bit surfaces, as that improves the color resolution. Be aware + 10-bit surfaces, as that improves color precision. Be aware however, that in this mode, the alpha channel is only 2 bits instead of 8 bits. Thus, if you are using a transparent background, you may want to use the default, *8-bit*, even if you have gamma-correct blending enabled. - You should also note that 10-bit surface is slower. This will + You should also note that 10-bit surface is much slower. This will increase input latency and decrease rendering throughput. Default: _8-bit_ From 1bf91566287904664f5c7f68ad09082148eceab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 19 Apr 2025 11:59:50 +0200 Subject: [PATCH 174/353] doc: foot.ini: spaces -> tab (for indentation) --- doc/foot.ini.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 26eb1780..cdfc65a0 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -225,7 +225,7 @@ empty string to be set, but it must be quoted: *KEY=""*) using 8-bit surfaces (i.e. each color channel is 8 bits). The amount of errors can be reduced by using 10-bit surfaces; see *tweak.surface-bit-depth*. - + Default: enabled when compositor support is available *box-drawings-uses-font-glyphs* From 1a2e5f4932a17c9804ca6d201b7d8cf84d7f19d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 19 Apr 2025 07:46:06 +0200 Subject: [PATCH 175/353] render: fix colors.alpha-mode=matching Before this patch, it only matched RGB color sources. It did not match the default bg color, or indexed colors. That is, e.g. CSI 43m didn't apply alpha, even if the color3 matched the default background color. --- CHANGELOG.md | 4 ++++ render.c | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8787775..6958c869 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,10 @@ ### Deprecated ### Removed ### Fixed + +* `colors.alpha-mode=matching` not working as intended. + + ### Security ### Contributors diff --git a/render.c b/render.c index fdf7015c..0e403949 100644 --- a/render.c +++ b/render.c @@ -754,8 +754,15 @@ render_cell(struct terminal *term, pixman_image_t *pix, } case ALPHA_MODE_MATCHING: { - if (cell->attrs.bg == term->colors.bg) + if (cell->attrs.bg_src == COLOR_DEFAULT || + ((cell->attrs.bg_src == COLOR_BASE16 || + cell->attrs.bg_src == COLOR_BASE256) && + term->colors.table[cell->attrs.bg] == term->colors.bg) || + (cell->attrs.bg_src == COLOR_RGB && + cell->attrs.bg == term->colors.bg)) + { alpha = term->colors.alpha; + } break; } From cb2a64c5854205092150afe80f96177764bd4038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 19 Apr 2025 12:16:48 +0200 Subject: [PATCH 176/353] csi: don't allow client app to enable grapheme-shaping when disabled at compile-time Closes #2039 --- CHANGELOG.md | 5 +++++ csi.c | 2 ++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6958c869..c15e05bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,11 @@ ### Fixed * `colors.alpha-mode=matching` not working as intended. +* Grapheme shaping was allowed to be "enabled" at runtime, even though + disabled at compile time. This caused mis-rendering of certain + codepoints ([#2039][2039]). + +[2039]: https://codeberg.org/dnkl/foot/issues/2039 ### Security diff --git a/csi.c b/csi.c index 81c71e31..b66fda21 100644 --- a/csi.c +++ b/csi.c @@ -558,7 +558,9 @@ decset_decrst(struct terminal *term, unsigned param, bool enable) break; case 2027: +#if defined(FOOT_GRAPHEME_CLUSTERING) term->grapheme_shaping = enable; +#endif break; case 2048: From ef4a680ae813b23e7b9d1b6dd67413dcad377655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 19 Apr 2025 08:05:15 +0200 Subject: [PATCH 177/353] input: reset modifiers in keyboard_leave() Closes #2034 --- CHANGELOG.md | 3 +++ input.c | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c15e05bf..35669f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,8 +71,11 @@ * Grapheme shaping was allowed to be "enabled" at runtime, even though disabled at compile time. This caused mis-rendering of certain codepoints ([#2039][2039]). +* Keyboard modifiers not being reset on keyboard leave events + ([#2034][2034]). [2039]: https://codeberg.org/dnkl/foot/issues/2039 +[2034]: https://codeberg.org/dnkl/foot/issues/2034 ### Security diff --git a/input.c b/input.c index 0f2a8446..d7a7975a 100644 --- a/input.c +++ b/input.c @@ -765,9 +765,17 @@ keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, seat->kbd.alt = false; seat->kbd.ctrl = false; seat->kbd.super = false; + if (seat->kbd.xkb_compose_state != NULL) xkb_compose_state_reset(seat->kbd.xkb_compose_state); + if (seat->kbd.xkb_state != NULL && seat->kbd.xkb_keymap != NULL) { + const xkb_layout_index_t layout_count = xkb_keymap_num_layouts(seat->kbd.xkb_keymap); + + for (xkb_layout_index_t i = 0; i < layout_count; i++) + xkb_state_update_mask(seat->kbd.xkb_state, 0, 0, 0, i, i, i); + } + if (old_focused != NULL) { seat->pointer.hidden = false; term_xcursor_update_for_seat(old_focused, seat); From 8bded8ce8cf22db51dd42bb71a2a5d39624df402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 19 Apr 2025 17:10:52 +0200 Subject: [PATCH 178/353] doc: foot.ini: add newish Unicode range to 'box-drawings-uses-font-glyphs' --- doc/foot.ini.5.scd | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index cdfc65a0..cffbf9c5 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -250,6 +250,7 @@ empty string to be set, but it must be quoted: *KEY=""*) - U+02500 - U+0259F - U+02800 - U+028FF + - U+1CD00 - U+1CDE5 - U+1Fb00 - U+1FB9B Default: _no_. From bc8d6d1ff350672cae7d7e6572eab2d10b1b415e Mon Sep 17 00:00:00 2001 From: Jan Palus Date: Wed, 23 Apr 2025 11:44:41 +0200 Subject: [PATCH 179/353] build: fix race when generating emoji-variation-sequences.h d3f692990ef6 moved emoji-variation-sequences.h header inclusion from vt.c to terminal.c. these two files are part of different libraries hence target for generating emoji-variation-sequences.h needs to be moved too. --- meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index ed2dc7e4..3d97040d 100644 --- a/meson.build +++ b/meson.build @@ -253,7 +253,7 @@ vtlib = static_library( 'osc.c', 'osc.h', 'sixel.c', 'sixel.h', 'vt.c', 'vt.h', - builtin_terminfo, emoji_variation_sequences, srgb_funcs, + builtin_terminfo, srgb_funcs, wl_proto_src + wl_proto_headers, version, dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc], @@ -265,6 +265,7 @@ pgolib = static_library( 'grid.c', 'grid.h', 'selection.c', 'selection.h', 'terminal.c', 'terminal.h', + emoji_variation_sequences, wl_proto_src + wl_proto_headers, dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc], link_with: vtlib, From b2dfd339e4478ab4d16e249bf491c45f7f4ae587 Mon Sep 17 00:00:00 2001 From: valoq Date: Mon, 21 Apr 2025 13:35:05 +0000 Subject: [PATCH 180/353] Add alacritty theme This adds the default colors from alacritty as an additional theme --- themes/alacritty | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 themes/alacritty diff --git a/themes/alacritty b/themes/alacritty new file mode 100644 index 00000000..a5e4d2c1 --- /dev/null +++ b/themes/alacritty @@ -0,0 +1,59 @@ +# -*- conf -*- +# Alacritty + +[cursor] +color = 181818 56d8c9 + +[colors] +background= 181818 +foreground= d8d8d8 + +#black +regular0= 181818 + +#red +regular1= ac4242 + +#green +regular2= 90a959 + +#yellow +regular3= f4bf75 + +#blue +regular4= 6a9fb5 + +#magenta +regular5= aa759f + +#cyan +regular6= 75b5aa + +#white/grey +regular7= d8d8d8 + + + +#grey/black +bright0= 6b6b6b + +#red +bright1= c55555 + +#green +bright2= aac474 + +#yellow +bright3= feca88 + +#blue +bright4= 82b8c8 + +#pink +bright5= c28cb8 + +#cyan +bright6= 93d3c3 + +#grey +bright7= f8f8f8 \ No newline at end of file From 70b324b24c86473a99528feb6f1f91ca70e11a02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 24 Apr 2025 08:23:56 +0200 Subject: [PATCH 181/353] term: ignore LTR+RTL markers (U+200E + U+200F) Foot doesn't implement RTL, and explicit LTR markers is neither needed, nor used in anyway. In fact, they cause issues with font lookup, as fcft often fails to find the marker codepoint in the primary font, causing a fallback font to be used instead. Closes #2049 --- CHANGELOG.md | 9 +++++++++ terminal.c | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35669f03..62dc7e03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,13 @@ ## Unreleased ### Added ### Changed + +* Left-to-right and right-to-left markers (U+200E and U+200F) are now + ignored ([#2049][2049]). + +[2049]: https://codeberg.org/dnkl/foot/issues/2049 + + ### Deprecated ### Removed ### Fixed @@ -73,6 +80,8 @@ codepoints ([#2039][2039]). * Keyboard modifiers not being reset on keyboard leave events ([#2034][2034]). +* Last character in a `remind` calendar event being in the wrong font + and color ([#2049][2049]). [2039]: https://codeberg.org/dnkl/foot/issues/2039 [2034]: https://codeberg.org/dnkl/foot/issues/2034 diff --git a/terminal.c b/terminal.c index ae1adb1a..59c39760 100644 --- a/terminal.c +++ b/terminal.c @@ -4156,6 +4156,14 @@ term_process_and_print_non_ascii(struct terminal *term, char32_t wc) (grapheme_clustering || (!grapheme_clustering && width == 0 && wc >= 0x300))) { + if (unlikely(wc == 0x200e || wc == 0x200f)) { + /* + * Ignore left-to-right and right-to-left markers + * see https://codeberg.org/dnkl/foot/issues/2049 + */ + return; + } + int col = term->grid->cursor.point.col; if (!term->grid->cursor.lcf) col--; From 1b15cc5f3d633809114d9d569b34abf934426c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 24 Apr 2025 18:20:18 +0200 Subject: [PATCH 182/353] Revert "term: ignore LTR+RTL markers (U+200E + U+200F)" This reverts commit 70b324b24c86473a99528feb6f1f91ca70e11a02. --- CHANGELOG.md | 9 --------- terminal.c | 8 -------- 2 files changed, 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62dc7e03..35669f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,13 +63,6 @@ ## Unreleased ### Added ### Changed - -* Left-to-right and right-to-left markers (U+200E and U+200F) are now - ignored ([#2049][2049]). - -[2049]: https://codeberg.org/dnkl/foot/issues/2049 - - ### Deprecated ### Removed ### Fixed @@ -80,8 +73,6 @@ codepoints ([#2039][2039]). * Keyboard modifiers not being reset on keyboard leave events ([#2034][2034]). -* Last character in a `remind` calendar event being in the wrong font - and color ([#2049][2049]). [2039]: https://codeberg.org/dnkl/foot/issues/2039 [2034]: https://codeberg.org/dnkl/foot/issues/2034 diff --git a/terminal.c b/terminal.c index 59c39760..ae1adb1a 100644 --- a/terminal.c +++ b/terminal.c @@ -4156,14 +4156,6 @@ term_process_and_print_non_ascii(struct terminal *term, char32_t wc) (grapheme_clustering || (!grapheme_clustering && width == 0 && wc >= 0x300))) { - if (unlikely(wc == 0x200e || wc == 0x200f)) { - /* - * Ignore left-to-right and right-to-left markers - * see https://codeberg.org/dnkl/foot/issues/2049 - */ - return; - } - int col = term->grid->cursor.point.col; if (!term->grid->cursor.lcf) col--; From 1fec0cf5ea0c3fe3be92a49eaee5eef5aafa9c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 24 Apr 2025 18:22:37 +0200 Subject: [PATCH 183/353] Revert "term: append zero-width grapheme breaking characters to previous cell" This reverts commit 76503fb86a8b8a6b5c3ce1be87c15a55af38508d. --- CHANGELOG.md | 4 ++-- terminal.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35669f03..1aedf331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -229,9 +229,9 @@ enabled ([#1947][1947]). * Reflow of the cursor (active + saved) when at the end of the line with a pending wrap (LCF set) ([#1954][1954]). -* Zero-width characters that also are grapheme breaks (e.g. U+200B, +* ~~Zero-width characters that also are grapheme breaks (e.g. U+200B, ZERO WIDTH SPACE) being ignored (discarded and never stored in the - grid) ([#1960][1960]). + grid) ([#1960][1960]).~~ (reverted) * `--server=` not working on FreeBSD ([#1956][1956]). * Crash when resetting the terminal and an application had previously set a custom app ID ([#1963][1963]) diff --git a/terminal.c b/terminal.c index ae1adb1a..f2d03e77 100644 --- a/terminal.c +++ b/terminal.c @@ -4188,7 +4188,7 @@ term_process_and_print_non_ascii(struct terminal *term, char32_t wc) if (grapheme_clustering) { /* Check if we're on a grapheme cluster break */ if (utf8proc_grapheme_break_stateful( - last, wc, &term->vt.grapheme_state) && width > 0) + last, wc, &term->vt.grapheme_state)) { term_reset_grapheme_state(term); goto out; From d43326d2b5775f832c50c9cb297c25c5950d6a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 24 Apr 2025 18:40:22 +0200 Subject: [PATCH 184/353] changelog: zero-width grapheme breaking codepoints causing fallback font to be used --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aedf331..3edd8212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,9 +73,13 @@ codepoints ([#2039][2039]). * Keyboard modifiers not being reset on keyboard leave events ([#2034][2034]). +* Fallback font (and possibly wrong color) being used when a character + was followed by a zero-width grapheme breaking codepoint (for + example, _LEFT-TO-RIGHT MARK_) ([#2049][2049]). [2039]: https://codeberg.org/dnkl/foot/issues/2039 [2034]: https://codeberg.org/dnkl/foot/issues/2034 +[2049]: https://codeberg.org/dnkl/foot/issues/2049 ### Security From cb1b7ba0c5752eeb92c72a80640fbd1f09ad4b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 25 Apr 2025 19:20:36 +0200 Subject: [PATCH 185/353] render: regression: alpha applied to inversed text/selections Introduced by 5f83278afd0530c323d4192e1095b3d1dea644c9 Closes #2073 --- CHANGELOG.md | 2 ++ render.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3edd8212..3e9ce91c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,8 @@ * Fallback font (and possibly wrong color) being used when a character was followed by a zero-width grapheme breaking codepoint (for example, _LEFT-TO-RIGHT MARK_) ([#2049][2049]). +* Regression: alpha applied to inversed text/selections + ([#2073][2073]). [2039]: https://codeberg.org/dnkl/foot/issues/2039 [2034]: https://codeberg.org/dnkl/foot/issues/2034 diff --git a/render.c b/render.c index 0e403949..b0d21d18 100644 --- a/render.c +++ b/render.c @@ -744,7 +744,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, _bg = swap; } - if (!term->window->is_fullscreen && term->colors.alpha != 0xffff) { + else if (!term->window->is_fullscreen && term->colors.alpha != 0xffff) { switch (term->conf->colors.alpha_mode) { case ALPHA_MODE_DEFAULT: { if (cell->attrs.bg_src == COLOR_DEFAULT) { From 0020ef12b472f8a57dc67fc912d64872f05b3760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 10:31:09 +0200 Subject: [PATCH 186/353] changelog: add missing bug ref --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e9ce91c..2f2e9d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ [2039]: https://codeberg.org/dnkl/foot/issues/2039 [2034]: https://codeberg.org/dnkl/foot/issues/2034 [2049]: https://codeberg.org/dnkl/foot/issues/2049 +[2073]: https://codeberg.org/dnkl/foot/issues/2073 ### Security From 89bfac00e7cc4f36a17d91208678412a6cba4f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 10:36:13 +0200 Subject: [PATCH 187/353] changelog: prepare for 1.22.1 --- CHANGELOG.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f2e9d88..aeabbe14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.22.1](#1-22-1) * [1.22.0](#1-22-0) * [1.21.0](#1-21-0) * [1.20.2](#1-20-2) @@ -60,11 +60,8 @@ * [1.2.0](#1-2-0) -## Unreleased -### Added -### Changed -### Deprecated -### Removed +## 1.22.1 + ### Fixed * `colors.alpha-mode=matching` not working as intended. @@ -85,9 +82,11 @@ [2073]: https://codeberg.org/dnkl/foot/issues/2073 -### Security ### Contributors +* Jan Palus +* valoq + ## 1.22.0 From c85d5d50965d7fdc5718b3d1b69b9e09e90e44f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 10:36:23 +0200 Subject: [PATCH 188/353] meson: bump version to 1.22.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 3d97040d..7ac033b5 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.22.0', + version: '1.22.1', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 79f6b4b1deefac678665a7420cd68fd410c80f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 10:41:14 +0200 Subject: [PATCH 189/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aeabbe14..3c48c41c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.22.1](#1-22-1) * [1.22.0](#1-22-0) * [1.21.0](#1-21-0) @@ -60,6 +61,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.22.1 ### Fixed From a7276d9dff31f5a8d889a9a0c5da974019e1a623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Apr 2025 06:54:58 +0200 Subject: [PATCH 190/353] config: refactor: break out 'colors' to a color_theme struct --- config.h | 106 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 54 insertions(+), 52 deletions(-) diff --git a/config.h b/config.h index 2dec82c1..4a4e4ae9 100644 --- a/config.h +++ b/config.h @@ -131,6 +131,59 @@ struct custom_regex { struct config_spawn_template launch; }; +struct color_theme { + uint32_t fg; + uint32_t bg; + uint32_t flash; + uint32_t flash_alpha; + uint32_t table[256]; + uint16_t alpha; + uint32_t selection_fg; + uint32_t selection_bg; + uint32_t url; + + uint32_t dim[8]; + uint32_t sixel[16]; + + enum { + ALPHA_MODE_DEFAULT, + ALPHA_MODE_MATCHING, + ALPHA_MODE_ALL + } alpha_mode; + + struct { + uint32_t fg; + uint32_t bg; + } jump_label; + + struct { + uint32_t fg; + uint32_t bg; + } scrollback_indicator; + + struct { + struct { + uint32_t fg; + uint32_t bg; + } no_match; + + struct { + uint32_t fg; + uint32_t bg; + } match; + } search_box; + + struct { + bool selection:1; + bool jump_label:1; + bool scrollback_indicator:1; + bool url:1; + bool search_box_no_match:1; + bool search_box_match:1; + uint8_t dim; + } use_custom; +}; + struct config { char *term; char *shell; @@ -244,58 +297,7 @@ struct config { tll(struct custom_regex) custom_regexes; - struct { - uint32_t fg; - uint32_t bg; - uint32_t flash; - uint32_t flash_alpha; - uint32_t table[256]; - uint16_t alpha; - uint32_t selection_fg; - uint32_t selection_bg; - uint32_t url; - - uint32_t dim[8]; - uint32_t sixel[16]; - - enum { - ALPHA_MODE_DEFAULT, - ALPHA_MODE_MATCHING, - ALPHA_MODE_ALL - } alpha_mode; - - struct { - uint32_t fg; - uint32_t bg; - } jump_label; - - struct { - uint32_t fg; - uint32_t bg; - } scrollback_indicator; - - struct { - struct { - uint32_t fg; - uint32_t bg; - } no_match; - - struct { - uint32_t fg; - uint32_t bg; - } match; - } search_box; - - struct { - bool selection:1; - bool jump_label:1; - bool scrollback_indicator:1; - bool url:1; - bool search_box_no_match:1; - bool search_box_match:1; - uint8_t dim; - } use_custom; - } colors; + struct color_theme colors; struct { enum cursor_style style; From 624c383a1f45a4fbd5ebf687613c509e6c22d909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Apr 2025 07:16:18 +0200 Subject: [PATCH 191/353] config: move cursor.color to colors.cursor --- CHANGELOG.md | 8 ++++++++ config.c | 41 +++++++++++++++++++++++++++++++---------- config.h | 12 +++++++----- doc/foot.ini.5.scd | 18 +++++++++--------- foot.ini | 3 ++- osc.c | 10 ++++++++-- terminal.c | 8 ++++---- 7 files changed, 69 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c48c41c..7b8348ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,7 +64,15 @@ ## Unreleased ### Added ### Changed + +* `cursor.color` moved to `colors.cursor`. + + ### Deprecated + +* `cursor.color` config option; use `colors.cursor` instead. + + ### Removed ### Fixed ### Security diff --git a/config.c b/config.c index 347cc1ec..4337d001 100644 --- a/config.c +++ b/config.c @@ -1445,6 +1445,20 @@ parse_section_colors(struct context *ctx) return true; } + else if (streq(key, "cursor")) { + if (!value_to_two_colors( + ctx, + &conf->colors.cursor.text, + &conf->colors.cursor.cursor, + false)) + { + return false; + } + + conf->colors.use_custom.cursor = true; + return true; + } + else if (streq(key, "urls")) { if (!value_to_color(ctx, &conf->colors.url, false)) return false; @@ -1537,17 +1551,24 @@ parse_section_cursor(struct context *ctx) return value_to_uint32(ctx, 10, &conf->cursor.blink.rate_ms); else if (streq(key, "color")) { + LOG_WARN("%s:%d: cursor.color: deprecated; use colors.cursor instead", + ctx->path, ctx->lineno); + + user_notification_add( + &conf->notifications, + USER_NOTIFICATION_DEPRECATED, + xstrdup("cursor.color: use colors.cursor instead")); + if (!value_to_two_colors( - ctx, - &conf->cursor.color.text, - &conf->cursor.color.cursor, - false)) + ctx, + &conf->colors.cursor.text, + &conf->colors.cursor.cursor, + false)) { return false; } - conf->cursor.color.text |= 1u << 31; - conf->cursor.color.cursor |= 1u << 31; + conf->colors.use_custom.cursor = true; return true; } @@ -3356,6 +3377,10 @@ config_load(struct config *conf, const char *conf_path, .alpha_mode = ALPHA_MODE_DEFAULT, .selection_fg = 0x80000000, /* Use default bg */ .selection_bg = 0x80000000, /* Use default fg */ + .cursor = { + .text = 0, + .cursor = 0, + }, .use_custom = { .selection = false, .jump_label = false, @@ -3371,10 +3396,6 @@ config_load(struct config *conf, const char *conf_path, .enabled = false, .rate_ms = 500, }, - .color = { - .text = 0, - .cursor = 0, - }, .beam_thickness = {.pt = 1.5}, .underline_thickness = {.pt = 0., .px = -1}, }, diff --git a/config.h b/config.h index 4a4e4ae9..89740db3 100644 --- a/config.h +++ b/config.h @@ -149,7 +149,12 @@ struct color_theme { ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL - } alpha_mode; + } alpha_mode; + + struct { + uint32_t text; + uint32_t cursor; + } cursor; struct { uint32_t fg; @@ -174,6 +179,7 @@ struct color_theme { } search_box; struct { + bool cursor:1; bool selection:1; bool jump_label:1; bool scrollback_indicator:1; @@ -306,10 +312,6 @@ struct config { bool enabled; uint32_t rate_ms; } blink; - struct { - uint32_t text; - uint32_t cursor; - } color; struct pt_or_px beam_thickness; struct pt_or_px underline_thickness; } cursor; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index cffbf9c5..13f768c2 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -898,15 +898,6 @@ applications can change these at runtime. enabled. Expressed in milliseconds between each blink. Default: _500_. -*color* - Two space separated RRGGBB values (i.e. plain old 6-digit hex - values, without prefix) specifying the foreground (text) and - background (cursor) colors for the cursor. - - Example: *ff0000 00ff00* (green cursor, red text) - - Default: the regular foreground and background colors, reversed. - *beam-thickness* Thickness (width) of the beam styled cursor. The value is in points, and its exact value thus depends on the monitor's DPI. To @@ -967,6 +958,15 @@ The colors are in RRGGBB format (i.e. plain old 6-digit hex values, without prefix). That is, they do *not* have an alpha component. You can configure the background transparency with the _alpha_ option. +*cursor* + Two space separated RRGGBB values (i.e. plain old 6-digit hex + values, without prefix) specifying the foreground (text) and + background (cursor) colors for the cursor. + + Example: *ff0000 00ff00* (green cursor, red text) + + Default: the regular foreground and background colors, reversed. + *foreground* Default foreground color. This is the color used when no ANSI color is being used. Default: _839496_. diff --git a/foot.ini b/foot.ini index 7d96ca0f..dc2ad6cd 100644 --- a/foot.ini +++ b/foot.ini @@ -85,7 +85,6 @@ [cursor] # style=block -# color= # blink=no # blink-rate=500 # beam-thickness=1.5 @@ -106,6 +105,8 @@ # flash=7f7f00 # flash-alpha=0.5 +# cursor= + ## Normal/regular colors (color palette 0-7) # regular0=242424 # black # regular1=f62b5a # red diff --git a/osc.c b/osc.c index eaf6e33e..7e3e6376 100644 --- a/osc.c +++ b/osc.c @@ -1570,8 +1570,14 @@ osc_dispatch(struct terminal *term) case 112: LOG_DBG("resetting cursor color"); - term->colors.cursor_fg = term->conf->cursor.color.text; - term->colors.cursor_bg = term->conf->cursor.color.cursor; + term->colors.cursor_fg = term->conf->colors.cursor.text; + term->colors.cursor_bg = term->conf->colors.cursor.cursor; + + if (term->conf->colors.use_custom.cursor) { + term->colors.cursor_fg |= 1u << 31; + term->colors.cursor_bg |= 1u << 31; + } + term_damage_cursor(term); break; diff --git a/terminal.c b/terminal.c index f2d03e77..16663647 100644 --- a/terminal.c +++ b/terminal.c @@ -1298,8 +1298,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .fg = conf->colors.fg, .bg = conf->colors.bg, .alpha = conf->colors.alpha, - .cursor_fg = conf->cursor.color.text, - .cursor_bg = conf->cursor.color.cursor, + .cursor_fg = (conf->colors.use_custom.cursor ? 1u << 31 : 0) | conf->colors.cursor.text, + .cursor_bg = (conf->colors.use_custom.cursor ? 1u << 31 : 0) | conf->colors.cursor.cursor, .selection_fg = conf->colors.selection_fg, .selection_bg = conf->colors.selection_bg, .use_custom_selection = conf->colors.use_custom.selection, @@ -2153,8 +2153,8 @@ term_reset(struct terminal *term, bool hard) term->colors.fg = term->conf->colors.fg; term->colors.bg = term->conf->colors.bg; term->colors.alpha = term->conf->colors.alpha; - term->colors.cursor_fg = term->conf->cursor.color.text; - term->colors.cursor_bg = term->conf->cursor.color.cursor; + term->colors.cursor_fg = (term->conf->colors.use_custom.cursor ? 1u << 31 : 0) | term->conf->colors.cursor.text; + term->colors.cursor_bg = (term->conf->colors.use_custom.cursor ? 1u << 31 : 0) | term->conf->colors.cursor.cursor; term->colors.selection_fg = term->conf->colors.selection_fg; term->colors.selection_bg = term->conf->colors.selection_bg; term->colors.use_custom_selection = term->conf->colors.use_custom.selection; From 5406ae335530e37f2e6ead3132b15cb5cb71499d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Apr 2025 07:16:37 +0200 Subject: [PATCH 192/353] themes: cursor.color -> colors.cursor --- themes/aeroroot | 4 +--- themes/alacritty | 4 +--- themes/apprentice | 4 +--- themes/ayu-mirage | 4 +--- themes/chiba-dark | 4 +--- themes/derp | 4 +--- themes/deus | 4 +--- themes/dracula | 4 +--- themes/dracula-iterm | 4 +--- themes/electrophoretic | 4 +--- themes/hacktober | 3 +-- themes/jetbrains-darcula | 4 +--- themes/kitty | 4 +--- themes/material-amber | 4 +--- themes/moonfly | 4 +--- themes/night-owl | 4 +--- themes/nightfly | 4 +--- themes/noirblaze | 4 +--- themes/nord | 4 +--- themes/nordiq | 4 +--- themes/nvim-dark | 4 +--- themes/nvim-light | 4 +--- themes/onedark | 4 +--- themes/onehalf-dark | 4 +--- themes/paper-color-dark | 4 +--- themes/paper-color-light | 4 +--- themes/poimandres | 4 +--- themes/rose-pine | 4 +--- themes/rose-pine-dawn | 4 +--- themes/rose-pine-moon | 4 +--- themes/selenized-black | 4 +--- themes/selenized-dark | 4 +--- themes/selenized-light | 4 +--- themes/selenized-white | 4 +--- themes/solarized-dark | 4 +--- themes/solarized-dark-normal-brights | 4 +--- themes/solarized-light | 4 +--- themes/tango | 4 +--- themes/tempus-autumn | 4 +--- themes/tempus-classic | 4 +--- themes/tempus-dawn | 4 +--- themes/tempus-day | 4 +--- themes/tempus-dusk | 4 +--- themes/tempus-fugit | 4 +--- themes/tempus-future | 4 +--- themes/tempus-night | 4 +--- themes/tempus-past | 4 +--- themes/tempus-rift | 4 +--- themes/tempus-spring | 4 +--- themes/tempus-summer | 4 +--- themes/tempus-tempest | 4 +--- themes/tempus-totus | 4 +--- themes/tempus-warp | 4 +--- themes/tempus-winter | 4 +--- themes/visibone | 4 +--- 55 files changed, 55 insertions(+), 164 deletions(-) diff --git a/themes/aeroroot b/themes/aeroroot index 3b887448..2a0e0985 100644 --- a/themes/aeroroot +++ b/themes/aeroroot @@ -1,10 +1,8 @@ # -*- conf -*- # Aero root theme -[cursor] -color=1a1a1a 9fd5f5 - [colors] +cursor=1a1a1a 9fd5f5 foreground=dedeef background=1a1a1a diff --git a/themes/alacritty b/themes/alacritty index a5e4d2c1..14503887 100644 --- a/themes/alacritty +++ b/themes/alacritty @@ -1,10 +1,8 @@ # -*- conf -*- # Alacritty -[cursor] -color = 181818 56d8c9 - [colors] +cursor = 181818 56d8c9 background= 181818 foreground= d8d8d8 diff --git a/themes/apprentice b/themes/apprentice index 941a27b4..6b67d21d 100644 --- a/themes/apprentice +++ b/themes/apprentice @@ -1,10 +1,8 @@ # -*- conf -*- # https://github.com/romainl/Apprentice -[cursor] -color=262626 6c6c6c - [colors] +cursor=262626 6c6c6c foreground=bcbcbc background=262626 regular0=1c1c1c diff --git a/themes/ayu-mirage b/themes/ayu-mirage index 64e85a4e..4646e418 100644 --- a/themes/ayu-mirage +++ b/themes/ayu-mirage @@ -2,10 +2,8 @@ # theme: Ayu Mirage # description: a theme based on Ayu Mirage for Sublime Text (original: https://github.com/dempfi/ayu) -[cursor] -color = ffcc66 665a44 - [colors] +cursor = ffcc66 665a44 foreground = cccac2 background = 242936 diff --git a/themes/chiba-dark b/themes/chiba-dark index bc3b1420..8727f684 100644 --- a/themes/chiba-dark +++ b/themes/chiba-dark @@ -3,10 +3,8 @@ # author: ayushnix (https://sr.ht/~ayushnix) # description: A dark theme with bright cyberpunk colors (WCAG AAA compliant) -[cursor] -color = 181818 cdcdcd - [colors] +cursor = 181818 cdcdcd foreground = cdcdcd background = 181818 regular0 = 181818 diff --git a/themes/derp b/themes/derp index 0925d2c2..45eed752 100644 --- a/themes/derp +++ b/themes/derp @@ -1,10 +1,8 @@ # -*- conf -*- # Derp -[cursor] -color=000000 ffffff - [colors] +cursor=000000 ffffff foreground=ffffff background=000000 regular0=111111 diff --git a/themes/deus b/themes/deus index 8fb37f75..0d52e55b 100644 --- a/themes/deus +++ b/themes/deus @@ -2,10 +2,8 @@ # Deus # Color palette based on: https://github.com/ajmwagar/vim-deus -[cursor] -color=2c323b eaeaea - [colors] +cursor=2c323b eaeaea background=2c323b foreground=eaeaea regular0=242a32 diff --git a/themes/dracula b/themes/dracula index 8b6ab542..008fc150 100644 --- a/themes/dracula +++ b/themes/dracula @@ -1,10 +1,8 @@ # -*- conf -*- # Dracula -[cursor] -color=282a36 f8f8f2 - [colors] +cursor=282a36 f8f8f2 foreground=f8f8f2 background=282a36 regular0=000000 # black diff --git a/themes/dracula-iterm b/themes/dracula-iterm index 8c2f66c3..249bb6ab 100644 --- a/themes/dracula-iterm +++ b/themes/dracula-iterm @@ -1,10 +1,8 @@ # -*- conf -*- # Dracula iTerm2 variant -[cursor] -color=ffffff bbbbbb - [colors] +cursor=ffffff bbbbbb foreground=f8f8f2 background=1e1f29 regular0=000000 # black diff --git a/themes/electrophoretic b/themes/electrophoretic index d2b67434..e0bf6e79 100644 --- a/themes/electrophoretic +++ b/themes/electrophoretic @@ -5,10 +5,8 @@ # text and the white background. # author: Eugen Rahaian -[cursor] -color=ffffff 515151 - [colors] +cursor=ffffff 515151 background= ffffff foreground= 000000 diff --git a/themes/hacktober b/themes/hacktober index acb6c0b1..dfcc4c7e 100644 --- a/themes/hacktober +++ b/themes/hacktober @@ -1,8 +1,7 @@ # -*- conf -*- -[cursor] -color=141414 c9c9c9 [colors] +cursor=141414 c9c9c9 foreground=c9c9c9 background=141414 regular0=191918 # black diff --git a/themes/jetbrains-darcula b/themes/jetbrains-darcula index 82528498..e6997848 100644 --- a/themes/jetbrains-darcula +++ b/themes/jetbrains-darcula @@ -2,10 +2,8 @@ # JetBrains Darcula # Palette based on the same theme from https://github.com/dexpota/kitty-themes -[cursor] -color=202020 ffffff - [colors] +cursor=202020 ffffff background=202020 foreground=adadad regular0=000000 # black diff --git a/themes/kitty b/themes/kitty index b5b813cc..f43eea9d 100644 --- a/themes/kitty +++ b/themes/kitty @@ -1,9 +1,7 @@ # -*- conf -*- -[cursor] -color=111111 cccccc - [colors] +cursor=111111 cccccc foreground=dddddd background=000000 regular0=000000 # black diff --git a/themes/material-amber b/themes/material-amber index ad844a9a..27983833 100644 --- a/themes/material-amber +++ b/themes/material-amber @@ -2,10 +2,8 @@ # Material Amber # Based on material.io guidelines with Amber 50 background -[cursor] -color=fff8e1 21201d - [colors] +cursor=fff8e1 21201d foreground = 21201d background = fff8e1 diff --git a/themes/moonfly b/themes/moonfly index 870de9d0..0dbe0e95 100644 --- a/themes/moonfly +++ b/themes/moonfly @@ -2,10 +2,8 @@ # moonfly # Based on https://github.com/bluz71/vim-moonfly-colors -[cursor] -color = 080808 9e9e9e - [colors] +cursor = 080808 9e9e9e foreground = b2b2b2 background = 080808 diff --git a/themes/night-owl b/themes/night-owl index 03e1d8f7..43a5c054 100644 --- a/themes/night-owl +++ b/themes/night-owl @@ -1,10 +1,8 @@ # _*_ conf _*_ # Night Owl -[cursor] -color=011627 80a4c2 - [colors] +cursor=011627 80a4c2 foreground=d6deeb background=011627 diff --git a/themes/nightfly b/themes/nightfly index 2a27fb2d..37205f0f 100644 --- a/themes/nightfly +++ b/themes/nightfly @@ -2,10 +2,8 @@ # nightfly # Based on https://github.com/bluz71/vim-nightfly-guicolors -[cursor] -color = 080808 9ca1aa - [colors] +cursor = 080808 9ca1aa foreground = acb4c2 background = 011627 diff --git a/themes/noirblaze b/themes/noirblaze index 3cf452e6..42daf11b 100644 --- a/themes/noirblaze +++ b/themes/noirblaze @@ -3,10 +3,8 @@ # https://github.com/n1ghtmare/noirblaze-kitty -[cursor] -color=121212 ff0088 - [colors] +cursor=121212 ff0088 foreground=d5d5d5 background=121212 diff --git a/themes/nord b/themes/nord index 4ce3a53e..9b988ad6 100644 --- a/themes/nord +++ b/themes/nord @@ -6,10 +6,8 @@ # this specific foot theme is based on nord-alacritty: # https://github.com/arcticicestudio/nord-alacritty/blob/develop/src/nord.yml -[cursor] -color = 2e3440 d8dee9 - [colors] +cursor = 2e3440 d8dee9 foreground = d8dee9 background = 2e3440 diff --git a/themes/nordiq b/themes/nordiq index f309de23..0df5c7de 100644 --- a/themes/nordiq +++ b/themes/nordiq @@ -1,10 +1,8 @@ # -*- conf -*- # Nordiq -[cursor] -color=eeeeee 9f515a - [colors] +cursor=eeeeee 9f515a foreground=dbdee9 background=0e1420 regular0=5b6272 diff --git a/themes/nvim-dark b/themes/nvim-dark index 4c13770a..9a177770 100644 --- a/themes/nvim-dark +++ b/themes/nvim-dark @@ -3,10 +3,8 @@ # Uses the dark color palette from the default Neovim color scheme # See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L419 -[cursor] -color=14161b e0e2ea # NvimDarkGrey2 NvimLightGrey2 - [colors] +cursor=14161b e0e2ea # NvimDarkGrey2 NvimLightGrey2 foreground=e0e2ea # NvimLightGrey2 background=14161b # NvimDarkGrey2 diff --git a/themes/nvim-light b/themes/nvim-light index 5afec9d7..aca4e156 100644 --- a/themes/nvim-light +++ b/themes/nvim-light @@ -3,10 +3,8 @@ # Uses the light color palette from the default Neovim color scheme # See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L334 -[cursor] -color=e0e2ea 14161b # NvimLightGrey2 NvimDarkGrey2 - [colors] +cursor=e0e2ea 14161b # NvimLightGrey2 NvimDarkGrey2 foreground=14161b # NvimDarkGrey2 background=e0e2ea # NvimLightGrey2 diff --git a/themes/onedark b/themes/onedark index ac5cc834..0932960b 100644 --- a/themes/onedark +++ b/themes/onedark @@ -1,10 +1,8 @@ # OneDark # Palette based on the same theme from https://github.com/dexpota/kitty-themes -[cursor] -color=111111 cccccc - [colors] +cursor=111111 cccccc foreground=979eab background=282c34 regular0=282c34 # black diff --git a/themes/onehalf-dark b/themes/onehalf-dark index c37a7984..1adc9e23 100644 --- a/themes/onehalf-dark +++ b/themes/onehalf-dark @@ -7,10 +7,8 @@ # + cursor colors from: # https://github.com/sonph/onehalf/blob/master/iterm/OneHalfDark.itermcolors -[cursor] -color=dcdfe4 a3b3cc - [colors] +cursor=dcdfe4 a3b3cc foreground=dcdfe4 background=282c34 regular0=282c34 # black diff --git a/themes/paper-color-dark b/themes/paper-color-dark index 18cd7f17..991bcc9d 100644 --- a/themes/paper-color-dark +++ b/themes/paper-color-dark @@ -2,10 +2,8 @@ # PaperColorDark # Palette based on https://github.com/NLKNguyen/papercolor-theme -[cursor] -color=1c1c1c eeeeee - [colors] +cursor=1c1c1c eeeeee background=1c1c1c foreground=eeeeee regular0=1c1c1c # black diff --git a/themes/paper-color-light b/themes/paper-color-light index b08ea707..b8a6ceec 100644 --- a/themes/paper-color-light +++ b/themes/paper-color-light @@ -2,10 +2,8 @@ # PaperColor Light # Palette based on https://github.com/NLKNguyen/papercolor-theme -[cursor] -color=eeeeee 444444 - [colors] +cursor=eeeeee 444444 background=eeeeee foreground=444444 regular0=eeeeee # black diff --git a/themes/poimandres b/themes/poimandres index d8a6b0a7..b4edc175 100644 --- a/themes/poimandres +++ b/themes/poimandres @@ -1,10 +1,8 @@ # Based on Poimandres color theme for kitti terminal emulator # https://github.com/ubmit/poimandres-kitty -[cursor] -color=1b1e28 ffffff - [colors] +cursor=1b1e28 ffffff foreground=a6accd background=1b1e28 diff --git a/themes/rose-pine b/themes/rose-pine index 78d77dd9..2cae00e8 100644 --- a/themes/rose-pine +++ b/themes/rose-pine @@ -1,10 +1,8 @@ # -*- conf -*- # Rosé Pine -[cursor] -color=191724 e0def4 - [colors] +cursor=191724 e0def4 background=191724 foreground=e0def4 diff --git a/themes/rose-pine-dawn b/themes/rose-pine-dawn index 52008b44..674c7a21 100644 --- a/themes/rose-pine-dawn +++ b/themes/rose-pine-dawn @@ -1,10 +1,8 @@ # -*- conf -*- # Rosé Pine Dawn -[cursor] -color=faf4ed 575279 - [colors] +cursor=faf4ed 575279 background=faf4ed foreground=575279 diff --git a/themes/rose-pine-moon b/themes/rose-pine-moon index 732e5943..cbc81451 100644 --- a/themes/rose-pine-moon +++ b/themes/rose-pine-moon @@ -1,10 +1,8 @@ # -*- conf -*- # Rosé Pine Moon -[cursor] -color=232136 e0def4 - [colors] +cursor=232136 e0def4 background=232136 foreground=e0def4 diff --git a/themes/selenized-black b/themes/selenized-black index 28392add..591751f0 100644 --- a/themes/selenized-black +++ b/themes/selenized-black @@ -1,10 +1,8 @@ # -*- conf -*- # Selenized black -[cursor] -color = 181818 56d8c9 - [colors] +cursor = 181818 56d8c9 background= 181818 foreground= b9b9b9 diff --git a/themes/selenized-dark b/themes/selenized-dark index ed74cdfc..5d062dec 100644 --- a/themes/selenized-dark +++ b/themes/selenized-dark @@ -1,10 +1,8 @@ # -*- conf -*- # Selenized dark -[cursor] -color = 103c48 53d6c7 - [colors] +cursor = 103c48 53d6c7 background= 103c48 foreground= adbcbc diff --git a/themes/selenized-light b/themes/selenized-light index 7e599d8e..04dffbea 100644 --- a/themes/selenized-light +++ b/themes/selenized-light @@ -1,10 +1,8 @@ # -*- conf -*- # Selenized light -[cursor] -color=fbf3db 00978a - [colors] +cursor=fbf3db 00978a background= fbf3db foreground= 53676d diff --git a/themes/selenized-white b/themes/selenized-white index b4d25315..5a7d68b2 100644 --- a/themes/selenized-white +++ b/themes/selenized-white @@ -1,10 +1,8 @@ # -*- conf -*- # Selenized white -[cursor] -color=ffffff 009a8a - [colors] +cursor=ffffff 009a8a background= ffffff foreground= 474747 diff --git a/themes/solarized-dark b/themes/solarized-dark index cad2945e..4997eb4a 100644 --- a/themes/solarized-dark +++ b/themes/solarized-dark @@ -1,10 +1,8 @@ # -*- conf -*- # Solarized dark -[cursor] -color= 002b36 93a1a1 - [colors] +cursor= 002b36 93a1a1 background= 002b36 foreground= 839496 regular0= 073642 diff --git a/themes/solarized-dark-normal-brights b/themes/solarized-dark-normal-brights index 1ab7d375..f0c2172d 100644 --- a/themes/solarized-dark-normal-brights +++ b/themes/solarized-dark-normal-brights @@ -1,10 +1,8 @@ # -*- conf -*- # Solarized dark -[cursor] -color= 002b36 93a1a1 - [colors] +cursor= 002b36 93a1a1 background= 002b36 foreground= 839496 regular0= 073642 diff --git a/themes/solarized-light b/themes/solarized-light index 74474573..3d750277 100644 --- a/themes/solarized-light +++ b/themes/solarized-light @@ -1,10 +1,8 @@ # -*- conf -*- # Solarized light -[cursor] -color=fdf6e3 586e75 - [colors] +cursor= fdf6e3 586e75 background= fdf6e3 foreground= 657b83 regular0= eee8d5 diff --git a/themes/tango b/themes/tango index a326f8ad..a93d29cb 100644 --- a/themes/tango +++ b/themes/tango @@ -1,10 +1,8 @@ # -*- conf -*- # Tango -[cursor] -color=000000 babdb6 - [colors] +cursor=000000 babdb6 foreground=babdb6 background=000000 regular0=2e3436 diff --git a/themes/tempus-autumn b/themes/tempus-autumn index 9c1f8797..74228e90 100644 --- a/themes/tempus-autumn +++ b/themes/tempus-autumn @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by earthly colours (WCAG AA compliant) -#[cursor] -#color = 302420 a9a2a6 - [colors] +#cursor = 302420 a9a2a6 foreground = a9a2a6 background = 302420 regular0 = 302420 diff --git a/themes/tempus-classic b/themes/tempus-classic index 0164605b..b35dc5e5 100644 --- a/themes/tempus-classic +++ b/themes/tempus-classic @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with warm hues (WCAG AA compliant) -#[cursor] -#color = 232323 aeadaf - [colors] +#cursor = 232323 aeadaf foreground = aeadaf background = 232323 regular0 = 232323 diff --git a/themes/tempus-dawn b/themes/tempus-dawn index cf143fba..dc45f29d 100644 --- a/themes/tempus-dawn +++ b/themes/tempus-dawn @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme with a soft, slightly desaturated palette (WCAG AA compliant) -#[cursor] -#color = eff0f2 4a4b4e - [colors] +#cursor = eff0f2 4a4b4e foreground = 4a4b4e background = eff0f2 regular0 = 4a4b4e diff --git a/themes/tempus-day b/themes/tempus-day index b287d45c..1df70137 100644 --- a/themes/tempus-day +++ b/themes/tempus-day @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme with warm colours (WCAG AA compliant) -#[cursor] -#color = f8f2e5 464340 - [colors] +#cursor = f8f2e5 464340 foreground = 464340 background = f8f2e5 regular0 = 464340 diff --git a/themes/tempus-dusk b/themes/tempus-dusk index 2c0308e1..5b4d1bea 100644 --- a/themes/tempus-dusk +++ b/themes/tempus-dusk @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a deep blue-ish, slightly desaturated palette (WCAG AA compliant) -#[cursor] -#color = 1f252d a2a8ba - [colors] +#cursor = 1f252d a2a8ba foreground = a2a8ba background = 1f252d regular0 = 1f252d diff --git a/themes/tempus-fugit b/themes/tempus-fugit index 9ebbcee7..ebd082fe 100644 --- a/themes/tempus-fugit +++ b/themes/tempus-fugit @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light, pleasant theme optimised for long writing/coding sessions (WCAG AA compliant) -#[cursor] -#color = fff5f3 4d595f - [colors] +#cursor = fff5f3 4d595f foreground = 4d595f background = fff5f3 regular0 = 4d595f diff --git a/themes/tempus-future b/themes/tempus-future index 3dd8c7a6..c97d379d 100644 --- a/themes/tempus-future +++ b/themes/tempus-future @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with colours inspired by concept art of outer space (WCAG AAA compliant) -#[cursor] -#color = 090a18 b4abac - [colors] +#cursor = 090a18 b4abac foreground = b4abac background = 090a18 regular0 = 090a18 diff --git a/themes/tempus-night b/themes/tempus-night index de7be5ff..7c97681d 100644 --- a/themes/tempus-night +++ b/themes/tempus-night @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: High contrast dark theme with bright colours (WCAG AAA compliant) -#[cursor] -#color = 1a1a1a e0e0e0 - [colors] +#cursor = 1a1a1a e0e0e0 foreground = e0e0e0 background = 1a1a1a regular0 = 1a1a1a diff --git a/themes/tempus-past b/themes/tempus-past index 8c66f54d..af408b00 100644 --- a/themes/tempus-past +++ b/themes/tempus-past @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme inspired by old vaporwave concept art (WCAG AA compliant) -#[cursor] -#color = f3f2f4 53545b - [colors] +#cursor = f3f2f4 53545b foreground = 53545b background = f3f2f4 regular0 = 53545b diff --git a/themes/tempus-rift b/themes/tempus-rift index 3657a7fe..e0cea4da 100644 --- a/themes/tempus-rift +++ b/themes/tempus-rift @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a subdued palette on the green side of the spectrum (WCAG AA compliant) -#[cursor] -#color = 162c22 bbbcbc - [colors] +#cursor = 162c22 bbbcbc foreground = bbbcbc background = 162c22 regular0 = 162c22 diff --git a/themes/tempus-spring b/themes/tempus-spring index d50e6d06..b98be3b4 100644 --- a/themes/tempus-spring +++ b/themes/tempus-spring @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by early spring colours (WCAG AA compliant) -#[cursor] -#color = 283a37 b5b8b7 - [colors] +#cursor = 283a37 b5b8b7 foreground = b5b8b7 background = 283a37 regular0 = 283a37 diff --git a/themes/tempus-summer b/themes/tempus-summer index 7da1d8c4..cd904010 100644 --- a/themes/tempus-summer +++ b/themes/tempus-summer @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with colours inspired by summer evenings by the sea (WCAG AA compliant) -#[cursor] -#color = 202c3d a0abae - [colors] +#cursor = 202c3d a0abae foreground = a0abae background = 202c3d regular0 = 202c3d diff --git a/themes/tempus-tempest b/themes/tempus-tempest index 57c300aa..2c84454e 100644 --- a/themes/tempus-tempest +++ b/themes/tempus-tempest @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: A green-scale, subtle theme for late night hackers (WCAG AAA compliant) -#[cursor] -#color = 282b2b b6e0ca - [colors] +#cursor = 282b2b b6e0ca foreground = b6e0ca background = 282b2b regular0 = 282b2b diff --git a/themes/tempus-totus b/themes/tempus-totus index 01e84692..3eb21644 100644 --- a/themes/tempus-totus +++ b/themes/tempus-totus @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme for prose or for coding in an open space (WCAG AAA compliant) -#[cursor] -#color = ffffff 4a484d - [colors] +#cursor = ffffff 4a484d foreground = 4a484d background = ffffff regular0 = 4a484d diff --git a/themes/tempus-warp b/themes/tempus-warp index fa8c21c2..911fb266 100644 --- a/themes/tempus-warp +++ b/themes/tempus-warp @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a vibrant palette (WCAG AA compliant) -#[cursor] -#color = 001514 a29fa0 - [colors] +#cursor = 001514 a29fa0 foreground = a29fa0 background = 001514 regular0 = 001514 diff --git a/themes/tempus-winter b/themes/tempus-winter index 8db97057..e4307142 100644 --- a/themes/tempus-winter +++ b/themes/tempus-winter @@ -3,10 +3,8 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by winter nights at the city (WCAG AA compliant) -#[cursor] -#color = 202427 8da3b8 - [colors] +#cursor = 202427 8da3b8 foreground = 8da3b8 background = 202427 regular0 = 202427 diff --git a/themes/visibone b/themes/visibone index 3ee665d0..9979bee0 100644 --- a/themes/visibone +++ b/themes/visibone @@ -1,10 +1,8 @@ # -*- conf -*- # VisiBone -[cursor] -color=010101 ffffff - [colors] +cursor=010101 ffffff foreground=ffffff background=010101 regular0=666666 From b24a9a59b9db95fe9a5195a30d982d9ed2150a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Apr 2025 07:29:54 +0200 Subject: [PATCH 193/353] tests: config: colors: verify loaded color is correct --- tests/test-config.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test-config.c b/tests/test-config.c index 69d349b4..7dfb8556 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -399,6 +399,16 @@ test_color(struct context *ctx, bool (*parse_fun)(struct context *ctx), BUG("[%s].%s=%s: failed to parse", ctx->section, ctx->key, ctx->value); } + + uint32_t color = input[i].color; + if (alpha_allowed && strlen(input[i].option_string) == 6) + color |= 0xff000000; + + if (*ptr != color) { + BUG("[%s].%s=%s: expected 0x%08x, got 0x%08x", + ctx->section, ctx->key, ctx->value, + color, *ptr); + } } } } @@ -445,6 +455,18 @@ test_two_colors(struct context *ctx, bool (*parse_fun)(struct context *ctx), BUG("[%s].%s=%s: failed to parse", ctx->section, ctx->key, ctx->value); } + + if (*ptr1 != input[i].color1) { + BUG("[%s].%s=%s: expected 0x%08x, got 0x%08x", + ctx->section, ctx->key, ctx->value, + input[i].color1, *ptr1); + } + + if (*ptr2 != input[i].color2) { + BUG("[%s].%s=%s: expected 0x%08x, got 0x%08x", + ctx->section, ctx->key, ctx->value, + input[i].color2, *ptr2); + } } } } @@ -720,6 +742,10 @@ test_section_colors(void) &conf.colors.search_box.match.fg, &conf.colors.search_box.match.bg); + test_two_colors(&ctx, &parse_section_colors, "cursor", false, + &conf.colors.cursor.text, + &conf.colors.cursor.cursor); + test_enum(&ctx, &parse_section_colors, "alpha-mode", 3, (const char *[]){"default", "matching", "all"}, (int []){ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL}, From 01c43f1644a691a01266cc4ec013dafae9eaf984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Apr 2025 07:32:48 +0200 Subject: [PATCH 194/353] config: refactor: break out color theme parsing to a separate function --- config.c | 79 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/config.c b/config.c index 4337d001..ce88d6ce 100644 --- a/config.c +++ b/config.c @@ -1339,9 +1339,8 @@ parse_section_regex(struct context *ctx) } static bool -parse_section_colors(struct context *ctx) +parse_color_theme(struct context *ctx, struct color_theme *theme) { - struct config *conf = ctx->conf; const char *key = ctx->key; size_t key_len = strlen(key); @@ -1350,28 +1349,26 @@ parse_section_colors(struct context *ctx) if (isdigit(key[0])) { unsigned long index; - if (!str_to_ulong(key, 0, &index) || - index >= ALEN(conf->colors.table)) - { + if (!str_to_ulong(key, 0, &index) || index >= ALEN(theme->table)) { LOG_CONTEXTUAL_ERR( "invalid color palette index: %s (not in range 0-%zu)", - key, ALEN(conf->colors.table)); + key, ALEN(theme->table)); return false; } - color = &conf->colors.table[index]; + color = &theme->table[index]; } else if (key_len == 8 && str_has_prefix(key, "regular") && last_digit < 8) - color = &conf->colors.table[last_digit]; + color = &theme->table[last_digit]; else if (key_len == 7 && str_has_prefix(key, "bright") && last_digit < 8) - color = &conf->colors.table[8 + last_digit]; + color = &theme->table[8 + last_digit]; else if (key_len == 4 && str_has_prefix(key, "dim") && last_digit < 8) { - if (!value_to_color(ctx, &conf->colors.dim[last_digit], false)) + if (!value_to_color(ctx, &theme->dim[last_digit], false)) return false; - conf->colors.use_custom.dim |= 1 << last_digit; + theme->use_custom.dim |= 1 << last_digit; return true; } @@ -1380,90 +1377,90 @@ parse_section_colors(struct context *ctx) (key_len == 7 && key[5] == '1' && last_digit < 6))) { size_t idx = key_len == 6 ? last_digit : 10 + last_digit; - return value_to_color(ctx, &conf->colors.sixel[idx], false); + return value_to_color(ctx, &theme->sixel[idx], false); } - else if (streq(key, "flash")) color = &conf->colors.flash; - else if (streq(key, "foreground")) color = &conf->colors.fg; - else if (streq(key, "background")) color = &conf->colors.bg; - else if (streq(key, "selection-foreground")) color = &conf->colors.selection_fg; - else if (streq(key, "selection-background")) color = &conf->colors.selection_bg; + else if (streq(key, "flash")) color = &theme->flash; + else if (streq(key, "foreground")) color = &theme->fg; + else if (streq(key, "background")) color = &theme->bg; + else if (streq(key, "selection-foreground")) color = &theme->selection_fg; + else if (streq(key, "selection-background")) color = &theme->selection_bg; else if (streq(key, "jump-labels")) { if (!value_to_two_colors( ctx, - &conf->colors.jump_label.fg, - &conf->colors.jump_label.bg, + &theme->jump_label.fg, + &theme->jump_label.bg, false)) { return false; } - conf->colors.use_custom.jump_label = true; + theme->use_custom.jump_label = true; return true; } else if (streq(key, "scrollback-indicator")) { if (!value_to_two_colors( ctx, - &conf->colors.scrollback_indicator.fg, - &conf->colors.scrollback_indicator.bg, + &theme->scrollback_indicator.fg, + &theme->scrollback_indicator.bg, false)) { return false; } - conf->colors.use_custom.scrollback_indicator = true; + theme->use_custom.scrollback_indicator = true; return true; } else if (streq(key, "search-box-no-match")) { if (!value_to_two_colors( ctx, - &conf->colors.search_box.no_match.fg, - &conf->colors.search_box.no_match.bg, + &theme->search_box.no_match.fg, + &theme->search_box.no_match.bg, false)) { return false; } - conf->colors.use_custom.search_box_no_match = true; + theme->use_custom.search_box_no_match = true; return true; } else if (streq(key, "search-box-match")) { if (!value_to_two_colors( ctx, - &conf->colors.search_box.match.fg, - &conf->colors.search_box.match.bg, + &theme->search_box.match.fg, + &theme->search_box.match.bg, false)) { return false; } - conf->colors.use_custom.search_box_match = true; + theme->use_custom.search_box_match = true; return true; } else if (streq(key, "cursor")) { if (!value_to_two_colors( ctx, - &conf->colors.cursor.text, - &conf->colors.cursor.cursor, + &theme->cursor.text, + &theme->cursor.cursor, false)) { return false; } - conf->colors.use_custom.cursor = true; + theme->use_custom.cursor = true; return true; } else if (streq(key, "urls")) { - if (!value_to_color(ctx, &conf->colors.url, false)) + if (!value_to_color(ctx, &theme->url, false)) return false; - conf->colors.use_custom.url = true; + theme->use_custom.url = true; return true; } @@ -1477,7 +1474,7 @@ parse_section_colors(struct context *ctx) return false; } - conf->colors.alpha = alpha * 65535.; + theme->alpha = alpha * 65535.; return true; } @@ -1491,18 +1488,18 @@ parse_section_colors(struct context *ctx) return false; } - conf->colors.flash_alpha = alpha * 65535.; + theme->flash_alpha = alpha * 65535.; return true; } else if (strcmp(key, "alpha-mode") == 0) { - _Static_assert(sizeof(conf->colors.alpha_mode) == sizeof(int), + _Static_assert(sizeof(theme->alpha_mode) == sizeof(int), "enum is not 32-bit"); return value_to_enum( ctx, (const char *[]){"default", "matching", "all", NULL}, - (int *)&conf->colors.alpha_mode); + (int *)&theme->alpha_mode); } else { @@ -1518,6 +1515,12 @@ parse_section_colors(struct context *ctx) return true; } +static bool +parse_section_colors(struct context *ctx) +{ + return parse_color_theme(ctx, &ctx->conf->colors); +} + static bool parse_section_cursor(struct context *ctx) { From 1423babc35e2896b602d124b634816ebfaeae243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Apr 2025 07:36:58 +0200 Subject: [PATCH 195/353] config: add new section 'colors2' This section defines an alternative color theme. The keys are the same as in the 'colors' section, as are the default values. Values are *not* inherited from 'colors'. That is, if you set a value in 'colors', but not in 'colors2', it is *not* inherited by 'colors2'. --- config.c | 8 ++++++++ config.h | 1 + doc/foot.ini.5.scd | 9 +++++++++ foot.ini | 3 +++ 4 files changed, 21 insertions(+) diff --git a/config.c b/config.c index ce88d6ce..1b73fbcf 100644 --- a/config.c +++ b/config.c @@ -1521,6 +1521,12 @@ parse_section_colors(struct context *ctx) return parse_color_theme(ctx, &ctx->conf->colors); } +static bool +parse_section_colors2(struct context *ctx) +{ + return parse_color_theme(ctx, &ctx->conf->colors2); +} + static bool parse_section_cursor(struct context *ctx) { @@ -2900,6 +2906,7 @@ enum section { SECTION_URL, SECTION_REGEX, SECTION_COLORS, + SECTION_COLORS2, SECTION_CURSOR, SECTION_MOUSE, SECTION_CSD, @@ -2930,6 +2937,7 @@ static const struct { [SECTION_URL] = {&parse_section_url, "url"}, [SECTION_REGEX] = {&parse_section_regex, "regex", true}, [SECTION_COLORS] = {&parse_section_colors, "colors"}, + [SECTION_COLORS2] = {&parse_section_colors2, "colors2"}, [SECTION_CURSOR] = {&parse_section_cursor, "cursor"}, [SECTION_MOUSE] = {&parse_section_mouse, "mouse"}, [SECTION_CSD] = {&parse_section_csd, "csd"}, diff --git a/config.h b/config.h index 89740db3..8954b270 100644 --- a/config.h +++ b/config.h @@ -304,6 +304,7 @@ struct config { tll(struct custom_regex) custom_regexes; struct color_theme colors; + struct color_theme colors2; struct { enum cursor_style style; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 13f768c2..45084ab6 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1084,6 +1084,15 @@ can configure the background transparency with the _alpha_ option. Flash translucency. A value in the range 0.0-1.0, where 0.0 means completely transparent, and 1.0 is opaque. Default: _0.5_. +# SECTION: colors2 + +This section defines an alternative color theme. It has the exact same +keys as the *colors* section. The default values are the same. + +Note that values are not inherited. That is, if you set a value in +*colors*, but not in *colors2*, the value from *colors* is not +inherited by *colors2*. + # SECTION: csd This section controls the look of the _CSDs_ (Client Side diff --git a/foot.ini b/foot.ini index dc2ad6cd..8482af6b 100644 --- a/foot.ini +++ b/foot.ini @@ -164,6 +164,9 @@ # search-box-match= # black-on-yellow # urls= +[colors2] +# Alternative color theme, see man page foot.ini(5) + [csd] # preferred=server # size=26 From 6bc91b5e288ef16613a4e6f9d0a9f6077cedc267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Apr 2025 07:58:02 +0200 Subject: [PATCH 196/353] key-bindings: add bindings to switch between color themes * color-theme-switch-1: select the primary color theme * color-theme-switch-2: select the alternative color theme * color-theme-toggle: toggle between the primary and alternative color themes --- CHANGELOG.md | 10 ++++++++++ config.c | 4 ++++ doc/foot.ini.5.scd | 16 ++++++++++++++++ foot.ini | 3 +++ input.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ key-binding.h | 5 ++++- terminal.c | 27 +++++++++++++++++---------- terminal.h | 3 +++ 8 files changed, 102 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8348ed..4296da09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,16 @@ ## Unreleased ### Added + +* `colors2` config section. This section duplicates the `colors` + section, and lets you define an alternative color theme. +* `key-bindings.color-theme-switch-1`, + `key-bindings.color-theme-switch-2` and + `key-bindings.color-theme-toggle` key bindings. These can be used to + switch between the primary and alternative color themes. They are + not bound by default. + + ### Changed * `cursor.color` moved to `colors.cursor`. diff --git a/config.c b/config.c index 1b73fbcf..921b2d68 100644 --- a/config.c +++ b/config.c @@ -142,6 +142,9 @@ static const char *const binding_action_map[] = { [BIND_ACTION_QUIT] = "quit", [BIND_ACTION_REGEX_LAUNCH] = "regex-launch", [BIND_ACTION_REGEX_COPY] = "regex-copy", + [BIND_ACTION_THEME_SWITCH_1] = "color-theme-switch-1", + [BIND_ACTION_THEME_SWITCH_2] = "color-theme-switch-2", + [BIND_ACTION_THEME_TOGGLE] = "color-theme-toggle", /* Mouse-specific actions */ [BIND_ACTION_SCROLLBACK_UP_MOUSE] = "scrollback-up-mouse", @@ -3479,6 +3482,7 @@ config_load(struct config *conf, const char *conf_path, memcpy(conf->colors.table, default_color_table, sizeof(default_color_table)); memcpy(conf->colors.sixel, default_sixel_colors, sizeof(default_sixel_colors)); + memcpy(&conf->colors2, &conf->colors, sizeof(conf->colors)); parse_modifiers(XKB_MOD_NAME_SHIFT, 5, &conf->mouse.selection_override_modifiers); tokenize_cmdline( diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 45084ab6..af6f7875 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1396,6 +1396,22 @@ e.g. *search-start=none*. Default: _Control+Shift+u_. +*color-theme-switch-1*, *color-theme-switch-2*, *color-theme-toggle* + Switch between the primary color theme (defined in the *colors* + section), and the alternative color theme (defined in the + *colors2* section). + + *color-theme-switch-1* applies the primary color theme regardless + of which color theme is currently active. + + *color-theme-switch-2* applies the alternative color theme regardless + of which color theme is currently active. + + *color-theme-toggle* toggles between the primary and alternative + color themes. + + Default: _none_ + *quit* Quit foot. Default: _none_. diff --git a/foot.ini b/foot.ini index 8482af6b..ebbc8ca7 100644 --- a/foot.ini +++ b/foot.ini @@ -212,6 +212,9 @@ # prompt-prev=Control+Shift+z # prompt-next=Control+Shift+x # unicode-input=Control+Shift+u +# color-theme-switch-1=none +# color-theme-switch-2=none +# color-theme-toggle=none # noop=none # quit=none diff --git a/input.c b/input.c index d7a7975a..ebb646c6 100644 --- a/input.c +++ b/input.c @@ -484,6 +484,51 @@ execute_binding(struct seat *seat, struct terminal *term, return true; + case BIND_ACTION_THEME_SWITCH_1: + if (term->colors.active_theme != COLOR_THEME1) { + term_theme_apply(term, &term->conf->colors); + term->colors.active_theme = COLOR_THEME1; + + wayl_win_alpha_changed(term->window); + term_font_subpixel_changed(term); + + term_damage_view(term); + term_damage_margins(term); + render_refresh(term); + } + return true; + + case BIND_ACTION_THEME_SWITCH_2: + if (term->colors.active_theme != COLOR_THEME2) { + term_theme_apply(term, &term->conf->colors2); + term->colors.active_theme = COLOR_THEME2; + + wayl_win_alpha_changed(term->window); + term_font_subpixel_changed(term); + + term_damage_view(term); + term_damage_margins(term); + render_refresh(term); + } + return true; + + case BIND_ACTION_THEME_TOGGLE: + if (term->colors.active_theme == COLOR_THEME1) { + term_theme_apply(term, &term->conf->colors2); + term->colors.active_theme = COLOR_THEME2; + } else { + term_theme_apply(term, &term->conf->colors); + term->colors.active_theme = COLOR_THEME1; + } + + wayl_win_alpha_changed(term->window); + term_font_subpixel_changed(term); + + term_damage_view(term); + term_damage_margins(term); + render_refresh(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 89398859..5f0c1f1e 100644 --- a/key-binding.h +++ b/key-binding.h @@ -43,6 +43,9 @@ enum bind_action_normal { BIND_ACTION_QUIT, BIND_ACTION_REGEX_LAUNCH, BIND_ACTION_REGEX_COPY, + BIND_ACTION_THEME_SWITCH_1, + BIND_ACTION_THEME_SWITCH_2, + BIND_ACTION_THEME_TOGGLE, /* Mouse specific actions - i.e. they require a mouse coordinate */ BIND_ACTION_SCROLLBACK_UP_MOUSE, @@ -56,7 +59,7 @@ enum bind_action_normal { BIND_ACTION_SELECT_QUOTE, BIND_ACTION_SELECT_ROW, - BIND_ACTION_KEY_COUNT = BIND_ACTION_REGEX_COPY + 1, + BIND_ACTION_KEY_COUNT = BIND_ACTION_THEME_TOGGLE + 1, BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1, }; diff --git a/terminal.c b/terminal.c index 16663647..29e03b8e 100644 --- a/terminal.c +++ b/terminal.c @@ -1303,6 +1303,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .selection_fg = conf->colors.selection_fg, .selection_bg = conf->colors.selection_bg, .use_custom_selection = conf->colors.use_custom.selection, + .active_theme = COLOR_THEME1, }, .color_stack = { .stack = NULL, @@ -2150,16 +2151,8 @@ term_reset(struct terminal *term, bool hard) term->flash.active = false; term->blink.state = BLINK_ON; fdm_del(term->fdm, term->blink.fd); term->blink.fd = -1; - term->colors.fg = term->conf->colors.fg; - term->colors.bg = term->conf->colors.bg; - term->colors.alpha = term->conf->colors.alpha; - term->colors.cursor_fg = (term->conf->colors.use_custom.cursor ? 1u << 31 : 0) | term->conf->colors.cursor.text; - term->colors.cursor_bg = (term->conf->colors.use_custom.cursor ? 1u << 31 : 0) | term->conf->colors.cursor.cursor; - term->colors.selection_fg = term->conf->colors.selection_fg; - term->colors.selection_bg = term->conf->colors.selection_bg; - term->colors.use_custom_selection = term->conf->colors.use_custom.selection; - memcpy(term->colors.table, term->conf->colors.table, - sizeof(term->colors.table)); + term_theme_apply(term, &term->conf->colors); + term->colors.active_theme = COLOR_THEME1; free(term->color_stack.stack); term->color_stack.stack = NULL; term->color_stack.size = 0; @@ -4693,3 +4686,17 @@ term_send_size_notification(struct terminal *term) term->rows, term->cols, height, width); term_to_slave(term, buf, n); } + +void +term_theme_apply(struct terminal *term, const struct color_theme *theme) +{ + term->colors.fg = theme->fg; + term->colors.bg = theme->bg; + term->colors.alpha = theme->alpha; + term->colors.cursor_fg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.text; + term->colors.cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor; + term->colors.selection_fg = theme->selection_fg; + term->colors.selection_bg = theme->selection_bg; + term->colors.use_custom_selection = theme->use_custom.selection; + memcpy(term->colors.table, theme->table, sizeof(term->colors.table)); +} diff --git a/terminal.h b/terminal.h index 518e36ef..45e13925 100644 --- a/terminal.h +++ b/terminal.h @@ -405,6 +405,7 @@ struct colors { uint32_t selection_fg; uint32_t selection_bg; bool use_custom_selection; + enum { COLOR_THEME1, COLOR_THEME2 } active_theme; }; struct terminal { @@ -982,6 +983,8 @@ void term_enable_size_notifications(struct terminal *term); void term_disable_size_notifications(struct terminal *term); void term_send_size_notification(struct terminal *term); +void term_theme_apply(struct terminal *term, const struct color_theme *theme); + static inline void term_reset_grapheme_state(struct terminal *term) { #if defined(FOOT_GRAPHEME_CLUSTERING) From 10e7f291498a6600cfecc3cde604ea51de5f0f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 20 Apr 2025 12:48:37 +0200 Subject: [PATCH 197/353] csi: implement private mode 2031 (dark/light mode detection) * Recognize 'CSI ? 996 n', and respond with - 'CSI ? 997 ; 1 n' if the primary theme is active - 'CSI ? 997 ; 2 n' if the alternative theme is actice * Implement private mode 2031, where changing the color theme (currently only possible via key bindings) causes the terminal to send the same CSI sequences as above. In this context, foot's primary theme is considered dark, and the alternative theme light (since the default theme is dark). Closes #2025 --- CHANGELOG.md | 5 +++++ csi.c | 33 +++++++++++++++++++++++++++++++++ doc/foot-ctlseqs.7.scd | 10 ++++++++++ doc/foot.ini.5.scd | 8 ++++++++ input.c | 12 ++++++++++++ terminal.h | 2 ++ 6 files changed, 70 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4296da09..5c40b8cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,11 @@ `key-bindings.color-theme-toggle` key bindings. These can be used to switch between the primary and alternative color themes. They are not bound by default. +* Support for private mode 2031 - [_Dark and Light Mode + Detection_](https://contour-terminal.org/vt-extensions/color-palette-update-notifications/) + ([#2025][2025]) + +[2025]: https://codeberg.org/dnkl/foot/issues/2025 ### Changed diff --git a/csi.c b/csi.c index b66fda21..e8b2c492 100644 --- a/csi.c +++ b/csi.c @@ -563,6 +563,10 @@ decset_decrst(struct terminal *term, unsigned param, bool enable) #endif break; + case 2031: + term->report_theme_changes = enable; + break; + case 2048: if (enable) term_enable_size_notifications(term); @@ -657,6 +661,7 @@ decrqm(const struct terminal *term, unsigned param) case 2027: return term->conf->tweak.grapheme_width_method != GRAPHEME_WIDTH_DOUBLE ? DECRPM_PERMANENTLY_RESET : decrpm(term->grapheme_shaping); + case 2031: return decrpm(term->report_theme_changes); case 2048: return decrpm(term->size_notifications); case 8452: return decrpm(term->sixel.cursor_right_of_graphics); case 737769: return decrpm(term_ime_is_enabled(term)); @@ -702,6 +707,7 @@ xtsave(struct terminal *term, unsigned param) case 2004: term->xtsave.bracketed_paste = term->bracketed_paste; break; case 2026: term->xtsave.app_sync_updates = term->render.app_sync_updates.enabled; break; case 2027: term->xtsave.grapheme_shaping = term->grapheme_shaping; break; + case 2031: term->xtsave.report_theme_changes = term->report_theme_changes; break; case 2048: term->xtsave.size_notifications = term->size_notifications; break; case 8452: term->xtsave.sixel_cursor_right_of_graphics = term->sixel.cursor_right_of_graphics; break; case 737769: term->xtsave.ime = term_ime_is_enabled(term); break; @@ -746,6 +752,7 @@ xtrestore(struct terminal *term, unsigned param) case 2004: enable = term->xtsave.bracketed_paste; break; case 2026: enable = term->xtsave.app_sync_updates; break; case 2027: enable = term->xtsave.grapheme_shaping; break; + case 2031: enable = term->xtsave.report_theme_changes; break; case 2048: enable = term->xtsave.size_notifications; break; case 8452: enable = term->xtsave.sixel_cursor_right_of_graphics; break; case 737769: enable = term->xtsave.ime; break; @@ -1539,6 +1546,32 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } + case 'n': { + const int param = vt_param_get(term, 0, 0); + + switch (param) { + case 996: { /* Query current theme mode (see private mode 2031) */ + /* + * 1 - dark mode + * 2 - light mode + * + * In foot, the themes aren't necessarily light/dark, + * but by convention, the primary theme is dark, and + * the alternative theme is light. + */ + char reply[16] = {0}; + int chars = snprintf( + reply, sizeof(reply), + "\033[?997;%dn", + term->colors.active_theme == COLOR_THEME1 ? 1 : 2); + + term_to_slave(term, reply, chars); + break; + } + } + break; + } + case 'p': { /* * Request status of ECMA-48/"ANSI" private mode (DECRQM diff --git a/doc/foot-ctlseqs.7.scd b/doc/foot-ctlseqs.7.scd index 6c702738..40906ebf 100644 --- a/doc/foot-ctlseqs.7.scd +++ b/doc/foot-ctlseqs.7.scd @@ -337,6 +337,9 @@ that corresponds to one of the following modes: | 2027 : contour : Grapheme cluster processing +| 2031 +: contour +: Request color theme updates | 2048 : TODO : In-band window resize notifications @@ -657,6 +660,13 @@ manipulation sequences. The generic format is: : xterm : Report the current entry on the palette stack, and the number of palettes stored on the stack. +| \\E[ ? 996 n +: Query the current (color) theme mode +: contour +: The current color theme mode (light or dark) is reported as *CSI ? + 997 ; 1|2 n*, where *1* means dark and *2* light. By convention, the + primary theme in foot is considered dark, and the alternative theme + light. # OSC diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index af6f7875..85a7cf7b 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -958,6 +958,10 @@ The colors are in RRGGBB format (i.e. plain old 6-digit hex values, without prefix). That is, they do *not* have an alpha component. You can configure the background transparency with the _alpha_ option. +In the context of private mode 2031 (Dark and Light Mode detection), +the primary theme (i.e. the *colors* section) is considered to be the +dark theme (since the default theme is dark). + *cursor* Two space separated RRGGBB values (i.e. plain old 6-digit hex values, without prefix) specifying the foreground (text) and @@ -1093,6 +1097,10 @@ Note that values are not inherited. That is, if you set a value in *colors*, but not in *colors2*, the value from *colors* is not inherited by *colors2*. +In the context of private mode 2031 (Dark and Light Mode detection), +the primary theme (i.e. the *colors2* section) is considered to be the +light theme (since the default theme is dark). + # SECTION: csd This section controls the look of the _CSDs_ (Client Side diff --git a/input.c b/input.c index ebb646c6..b6c56fde 100644 --- a/input.c +++ b/input.c @@ -492,6 +492,9 @@ execute_binding(struct seat *seat, struct terminal *term, wayl_win_alpha_changed(term->window); term_font_subpixel_changed(term); + if (term->report_theme_changes) + term_to_slave(term, "\033[?997;1n", 9); + term_damage_view(term); term_damage_margins(term); render_refresh(term); @@ -506,6 +509,9 @@ execute_binding(struct seat *seat, struct terminal *term, wayl_win_alpha_changed(term->window); term_font_subpixel_changed(term); + if (term->report_theme_changes) + term_to_slave(term, "\033[?997;2n", 9); + term_damage_view(term); term_damage_margins(term); render_refresh(term); @@ -516,9 +522,15 @@ execute_binding(struct seat *seat, struct terminal *term, if (term->colors.active_theme == COLOR_THEME1) { term_theme_apply(term, &term->conf->colors2); term->colors.active_theme = COLOR_THEME2; + + if (term->report_theme_changes) + term_to_slave(term, "\033[?997;2n", 9); } else { term_theme_apply(term, &term->conf->colors); term->colors.active_theme = COLOR_THEME1; + + if (term->report_theme_changes) + term_to_slave(term, "\033[?997;1n", 9); } wayl_win_alpha_changed(term->window); diff --git a/terminal.h b/terminal.h index 45e13925..e6499ef7 100644 --- a/terminal.h +++ b/terminal.h @@ -518,6 +518,7 @@ struct terminal { bool num_lock_modifier; bool bell_action_enabled; + bool report_theme_changes; /* Saved DECSET modes - we save the SET state */ struct { @@ -548,6 +549,7 @@ struct terminal { bool ime:1; bool app_sync_updates:1; bool grapheme_shaping:1; + bool report_theme_changes:1; bool size_notifications:1; From bc5b71666867c00e7dc850740bb5cc2a13a9c2a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 21 Apr 2025 12:19:11 +0200 Subject: [PATCH 198/353] config: add initial-color-theme=1|2 This option selects which color theme to use by default. I.e. at startup, and after a reset. This is useful with combined theme files, where a single file defines e.g. both a dark and light version of the theme. --- CHANGELOG.md | 2 ++ config.c | 11 ++++++++++- config.h | 6 ++++++ doc/foot.ini.5.scd | 12 ++++++++++++ foot.ini | 1 + terminal.c | 39 ++++++++++++++++++++++++++------------- terminal.h | 2 +- 7 files changed, 58 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c40b8cf..41457205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,8 @@ * Support for private mode 2031 - [_Dark and Light Mode Detection_](https://contour-terminal.org/vt-extensions/color-palette-update-notifications/) ([#2025][2025]) +* Added `initial-color-theme=1|2` config option. `1` uses colors from + the `[colors]` section, `2` uses `[colors2]`. [2025]: https://codeberg.org/dnkl/foot/issues/2025 diff --git a/config.c b/config.c index 921b2d68..7dc41aa6 100644 --- a/config.c +++ b/config.c @@ -1098,6 +1098,15 @@ parse_section_main(struct context *ctx) return true; } + else if (streq(key, "initial-color-theme")) { + _Static_assert( + sizeof(conf->initial_color_theme) == sizeof(int), + "enum is not 32-bit"); + + return value_to_enum(ctx, (const char*[]){"1", "2", NULL}, + (int *)&conf->initial_color_theme); + } + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -3402,7 +3411,7 @@ config_load(struct config *conf, const char *conf_path, .url = false, }, }, - + .initial_color_theme = COLOR_THEME1, .cursor = { .style = CURSOR_BLOCK, .unfocused_style = CURSOR_UNFOCUSED_HOLLOW, diff --git a/config.h b/config.h index 8954b270..cbdf11b1 100644 --- a/config.h +++ b/config.h @@ -190,6 +190,11 @@ struct color_theme { } use_custom; }; +enum which_color_theme { + COLOR_THEME1, + COLOR_THEME2, +}; + struct config { char *term; char *shell; @@ -305,6 +310,7 @@ struct config { struct color_theme colors; struct color_theme colors2; + enum which_color_theme initial_color_theme; struct { enum cursor_style style; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 85a7cf7b..f7da2c53 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -349,6 +349,18 @@ empty string to be set, but it must be quoted: *KEY=""*) Default: _yes_ +*initial-color-theme* + Selects which color theme to use, *1*, or *2*. + + *1* uses the colors defined in the *colors* section, while *2* + uses the colors from the *colors2* section. + + Use the *color-theme-switch-1*, *color-theme-switch-2* and + *color-theme-toggle* key bindings to switch between the two themes + at runtime. + + Default: _1_ + *initial-window-size-pixels* Initial window width and height in _pixels_ (subject to output scaling), in the form _WIDTHxHEIGHT_. The height _includes_ the diff --git a/foot.ini b/foot.ini index ebbc8ca7..563558db 100644 --- a/foot.ini +++ b/foot.ini @@ -23,6 +23,7 @@ # box-drawings-uses-font-glyphs=no # dpi-aware=no +# initial-color-theme=1 # initial-window-size-pixels=700x500 # Or, # initial-window-size-chars= # initial-window-mode=windowed diff --git a/terminal.c b/terminal.c index 29e03b8e..d25516cb 100644 --- a/terminal.c +++ b/terminal.c @@ -1262,6 +1262,12 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, const bool ten_bit_surfaces = conf->tweak.surface_bit_depth == SHM_10_BIT; + const struct color_theme *theme = NULL; + switch (conf->initial_color_theme) { + case COLOR_THEME1: theme = &conf->colors; break; + case COLOR_THEME2: theme = &conf->colors2; break; + } + /* Initialize configure-based terminal attributes */ *term = (struct terminal) { .fdm = fdm, @@ -1279,7 +1285,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, }, .font_dpi = 0., .font_dpi_before_unmap = -1., - .font_subpixel = (conf->colors.alpha == 0xffff /* Can't do subpixel rendering on transparent background */ + .font_subpixel = (theme->alpha == 0xffff /* Can't do subpixel rendering on transparent background */ ? FCFT_SUBPIXEL_DEFAULT : FCFT_SUBPIXEL_NONE), .cursor_keys_mode = CURSOR_KEYS_NORMAL, @@ -1295,15 +1301,15 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .state = 0, /* STATE_GROUND */ }, .colors = { - .fg = conf->colors.fg, - .bg = conf->colors.bg, - .alpha = conf->colors.alpha, - .cursor_fg = (conf->colors.use_custom.cursor ? 1u << 31 : 0) | conf->colors.cursor.text, - .cursor_bg = (conf->colors.use_custom.cursor ? 1u << 31 : 0) | conf->colors.cursor.cursor, - .selection_fg = conf->colors.selection_fg, - .selection_bg = conf->colors.selection_bg, - .use_custom_selection = conf->colors.use_custom.selection, - .active_theme = COLOR_THEME1, + .fg = theme->fg, + .bg = theme->bg, + .alpha = theme->alpha, + .cursor_fg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.text, + .cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor, + .selection_fg = theme->selection_fg, + .selection_bg = theme->selection_bg, + .use_custom_selection = theme->use_custom.selection, + .active_theme = conf->initial_color_theme, }, .color_stack = { .stack = NULL, @@ -1434,7 +1440,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, xassert(tll_length(term->wl->monitors) > 0); term->scale = tll_front(term->wl->monitors).scale; - memcpy(term->colors.table, term->conf->colors.table, sizeof(term->colors.table)); + memcpy(term->colors.table, theme->table, sizeof(term->colors.table)); /* Initialize the Wayland window backend */ if ((term->window = wayl_win_init(term, token)) == NULL) @@ -2148,11 +2154,18 @@ term_reset(struct terminal *term, bool hard) if (!hard) return; + const struct color_theme *theme = NULL; + + switch (term->conf->initial_color_theme) { + case COLOR_THEME1: theme = &term->conf->colors; break; + case COLOR_THEME2: theme = &term->conf->colors2; break; + } + term->flash.active = false; term->blink.state = BLINK_ON; fdm_del(term->fdm, term->blink.fd); term->blink.fd = -1; - term_theme_apply(term, &term->conf->colors); - term->colors.active_theme = COLOR_THEME1; + term_theme_apply(term, theme); + term->colors.active_theme = term->conf->initial_color_theme; free(term->color_stack.stack); term->color_stack.stack = NULL; term->color_stack.size = 0; diff --git a/terminal.h b/terminal.h index e6499ef7..4639fa69 100644 --- a/terminal.h +++ b/terminal.h @@ -405,7 +405,7 @@ struct colors { uint32_t selection_fg; uint32_t selection_bg; bool use_custom_selection; - enum { COLOR_THEME1, COLOR_THEME2 } active_theme; + enum which_color_theme active_theme; }; struct terminal { From 537092e6434361307f2c7d88b327920dc176c8d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 21 Apr 2025 12:20:28 +0200 Subject: [PATCH 199/353] themes: solarized: add dark/light combined theme file These themes uses the 'colors' section to define the dark variant, and 'colors2' to define the light variant. --- themes/solarized | 47 ++++++++++++++++++++++++++++ themes/solarized-normal-brights | 54 +++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 themes/solarized create mode 100644 themes/solarized-normal-brights diff --git a/themes/solarized b/themes/solarized new file mode 100644 index 00000000..335c738e --- /dev/null +++ b/themes/solarized @@ -0,0 +1,47 @@ +# -*- conf -*- +# Solarized dark+light + +# Dark +[colors] +cursor= 002b36 93a1a1 +background= 002b36 +foreground= 839496 +regular0= 073642 +regular1= dc322f +regular2= 859900 +regular3= b58900 +regular4= 268bd2 +regular5= d33682 +regular6= 2aa198 +regular7= eee8d5 +bright0= 002b36 +bright1= cb4b16 +bright2= 586e75 +bright3= 657b83 +bright4= 839496 +bright5= 6c71c4 +bright6= 93a1a1 +bright7= fdf6e3 + + +# Light +[colors2] +cursor= fdf6e3 586e75 +background= fdf6e3 +foreground= 657b83 +regular0= eee8d5 +regular1= dc322f +regular2= 859900 +regular3= b58900 +regular4= 268bd2 +regular5= d33682 +regular6= 2aa198 +regular7= 073642 +bright0= cb4b16 +bright1= fdf6e3 +bright2= 93a1a1 +bright3= 839496 +bright4= 657b83 +bright5= 6c71c4 +bright6= 586e75 +bright7= 002b36 diff --git a/themes/solarized-normal-brights b/themes/solarized-normal-brights new file mode 100644 index 00000000..a7724cd3 --- /dev/null +++ b/themes/solarized-normal-brights @@ -0,0 +1,54 @@ +# -*- conf -*- +# Solarized dark+light +# +# Bright colors are brighter versions of the regular colors, instead +# of the normal solarized palette. +# +# They were generated by taking the regular colors, decoding from sRGB +# to linear, multiplying the linear RGB values by 1.8, and then +# encoding to sRGB again. + +# Dark +[colors] +cursor= 002b36 93a1a1 +background= 002b36 +foreground= 839496 +regular0= 073642 +regular1= dc322f +regular2= 859900 +regular3= b58900 +regular4= 268bd2 +regular5= d33682 +regular6= 2aa198 +regular7= eee8d5 +bright0= 0c4958 +bright1= ff4440 +bright2= aec700 +bright3= ebb300 +bright4= 34b5ff +bright5= ff49aa +bright6= 3ad2c6 +bright7= ffffff + + +# Light +[colors2] +cursor= fdf6e3 586e75 +background= fdf6e3 +foreground= 657b83 +regular0= eee8d5 +regular1= dc322f +regular2= 859900 +regular3= b58900 +regular4= 268bd2 +regular5= d33682 +regular6= 2aa198 +regular7= 073642 +bright0= ffffff +bright1= ff4440 +bright2= aec700 +bright3= ebb300 +bright4= 34b5ff +bright5= ff49aa +bright6= 3ad2c6 +bright7= 0c4958 From 1dc14a300107e13b87662a010968763f64ed9321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 15:23:44 +0200 Subject: [PATCH 200/353] themes: selenized: add dark/light combined theme file --- themes/selenized | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 themes/selenized diff --git a/themes/selenized b/themes/selenized new file mode 100644 index 00000000..cde35723 --- /dev/null +++ b/themes/selenized @@ -0,0 +1,48 @@ +# -*- conf -*- +# Selenized dark + +[colors] +cursor = 103c48 53d6c7 +background= 103c48 +foreground= adbcbc + +regular0= 184956 +regular1= fa5750 +regular2= 75b938 +regular3= dbb32d +regular4= 4695f7 +regular5= f275be +regular6= 41c7b9 +regular7= 72898f + +bright0= 2d5b69 +bright1= ff665c +bright2= 84c747 +bright3= ebc13d +bright4= 58a3ff +bright5= ff84cd +bright6= 53d6c7 +bright7= cad8d9 + +[colors2] +cursor=fbf3db 00978a +background= fbf3db +foreground= 53676d + +regular0= ece3cc +regular1= d2212d +regular2= 489100 +regular3= ad8900 +regular4= 0072d4 +regular5= ca4898 +regular6= 009c8f +regular7= 909995 + +bright0= d5cdb6 +bright1= cc1729 +bright2= 428b00 +bright3= a78300 +bright4= 006dce +bright5= c44392 +bright6= 00978a +bright7= 3a4d53 From 6a1c3b89c2f0b5f34d7c2f9957846eb90ca1f62c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 15:26:22 +0200 Subject: [PATCH 201/353] themes: gruvbox: add dark/light combined theme file --- themes/gruvbox | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 themes/gruvbox diff --git a/themes/gruvbox b/themes/gruvbox new file mode 100644 index 00000000..6bc97352 --- /dev/null +++ b/themes/gruvbox @@ -0,0 +1,42 @@ +# -*- conf -*- +# Gruvbox + +[colors] +background=282828 +foreground=ebdbb2 +regular0=282828 +regular1=cc241d +regular2=98971a +regular3=d79921 +regular4=458588 +regular5=b16286 +regular6=689d6a +regular7=a89984 +bright0=928374 +bright1=fb4934 +bright2=b8bb26 +bright3=fabd2f +bright4=83a598 +bright5=d3869b +bright6=8ec07c +bright7=ebdbb2 + +[colors2] +background=fbf1c7 +foreground=3c3836 +regular0=fbf1c7 +regular1=cc241d +regular2=98971a +regular3=d79921 +regular4=458588 +regular5=b16286 +regular6=689d6a +regular7=7c6f64 +bright0=928374 +bright1=9d0006 +bright2=79740e +bright3=b57614 +bright4=076678 +bright5=8f3f71 +bright6=427b58 +bright7=3c3836 From d3e45791bde9af9c6ae8e0380f4f810778f59a57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 15:26:31 +0200 Subject: [PATCH 202/353] themes: nvim: add dark/light combined theme file --- themes/nvim | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 themes/nvim diff --git a/themes/nvim b/themes/nvim new file mode 100644 index 00000000..bf629c0a --- /dev/null +++ b/themes/nvim @@ -0,0 +1,56 @@ +# -*- conf -*- +# Neovim Dark theme +# Uses the dark color palette from the default Neovim color scheme +# See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L419 + +[colors] +cursor=14161b e0e2ea # NvimDarkGrey2 NvimLightGrey2 +foreground=e0e2ea # NvimLightGrey2 +background=14161b # NvimDarkGrey2 + +selection-foreground=e0e2ea # NvimLightGrey2 +selection-background=4f5258 # NvimDarkGrey4 + +regular0=07080d # NvimDarkGrey1 +regular1=ffc0b9 # NvimLightRed +regular2=b3f6c0 # NvimLightGreen +regular3=fce094 # NvimLightYellow +regular4=a6dbff # NvimLightBlue +regular5=ffcaff # NvimLightMagenta +regular6=8cf8f7 # NvimLightCyan +regular7=c4c6cd # NvimLightGrey3 + +bright0=2c2e33 # NvimDarkGrey3 +bright1=ffc0b9 # NvimLightRed +bright2=b3f6c0 # NvimLightGreen +bright3=fce094 # NvimLightYellow +bright4=a6dbff # NvimLightBlue +bright5=ffcaff # NvimLightMagenta +bright6=8cf8f7 # NvimLightCyan +bright7=eef1f8 # NvimLightGrey1 + +[colors2] +cursor=e0e2ea 14161b # NvimLightGrey2 NvimDarkGrey2 +foreground=14161b # NvimDarkGrey2 +background=e0e2ea # NvimLightGrey2 + +selection-foreground=14161b # NvimDarkGrey2 +selection-background=9b9ea4 # NvimLightGrey4 + +regular0=eef1f8 # NvimLightGrey1 +regular1=590008 # NvimDarkRed +regular2=005523 # NvimDarkGreen +regular3=6b5300 # NvimDarkYellow +regular4=004c73 # NvimDarkBlue +regular5=470045 # NvimDarkMagenta +regular6=007373 # NvimDarkCyan +regular7=2c2e33 # NvimDarkGrey3 + +bright0=c4c6cd # NvimLightGrey3 +bright1=590008 # NvimDarkRed +bright2=005523 # NvimDarkGreen +bright3=6b5300 # NvimDarkYellow +bright4=004c73 # NvimDarkBlue +bright5=470045 # NvimDarkMagenta +bright6=007373 # NvimDarkCyan +bright7=07080d # NvimDarkGrey1 From 8273514d3c98108fbd02316fb5f44a721cca8e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 15:26:36 +0200 Subject: [PATCH 203/353] themes: paper-color: add dark/light combined theme file --- themes/paper-color | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 themes/paper-color diff --git a/themes/paper-color b/themes/paper-color new file mode 100644 index 00000000..f158c148 --- /dev/null +++ b/themes/paper-color @@ -0,0 +1,49 @@ +# -*- conf -*- +# PaperColorDark +# Palette based on https://github.com/NLKNguyen/papercolor-theme + +[colors] +cursor=1c1c1c eeeeee +background=1c1c1c +foreground=eeeeee +regular0=1c1c1c # black +regular1=af005f # red +regular2=5faf00 # green +regular3=d7af5f # yellow +regular4=5fafd7 # blue +regular5=808080 # magenta +regular6=d7875f # cyan +regular7=d0d0d0 # white +bright0=bcbcbc # bright black +bright1=5faf5f # bright red +bright2=afd700 # bright green +bright3=af87d7 # bright yellow +bright4=ffaf00 # bright blue +bright5=ff5faf # bright magenta +bright6=00afaf # bright cyan +bright7=5f8787 # bright white +# selection-foreground=1c1c1c +# selection-background=af87d7 + +[colors2] +cursor=eeeeee 444444 +background=eeeeee +foreground=444444 +regular0=eeeeee # black +regular1=af0000 # red +regular2=008700 # green +regular3=5f8700 # yellow +regular4=0087af # blue +regular5=878787 # magenta +regular6=005f87 # cyan +regular7=764e37 # white +bright0=bcbcbc # bright black +bright1=d70000 # bright red +bright2=d70087 # bright green +bright3=8700af # bright yellow +bright4=d75f00 # bright blue +bright5=d75f00 # bright magenta +bright6=4c7a5d # bright cyan +bright7=005faf # bright white +# selection-foreground=eeeeee +# selection-background=0087af From 4d70bb7b420c748ed6fc33262a3b0cf0bc0865eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 18:15:31 +0200 Subject: [PATCH 204/353] changelog: mention the new combined dark/light theme files --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41457205..91627d0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,13 @@ ([#2025][2025]) * Added `initial-color-theme=1|2` config option. `1` uses colors from the `[colors]` section, `2` uses `[colors2]`. +* Combined dark/light theme files for (dark variant is the default, + set `initial-color-theme=2` to use the light variant by default): + - gruvbox + - nvim + - paper-color + - selenized + - solarized [2025]: https://codeberg.org/dnkl/foot/issues/2025 From d20fbc68078fe5b3bc8782e8e7827caf406708e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 27 Apr 2025 07:46:09 +0200 Subject: [PATCH 205/353] config: parse_color_theme(): make NOINLINE --- config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.c b/config.c index 7dc41aa6..04a9c9d4 100644 --- a/config.c +++ b/config.c @@ -1350,7 +1350,7 @@ parse_section_regex(struct context *ctx) } } -static bool +static bool NOINLINE parse_color_theme(struct context *ctx, struct color_theme *theme) { const char *key = ctx->key; From 97910a5cbac84bb99254849ee1b5c00b00b983c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 27 Apr 2025 10:14:45 +0200 Subject: [PATCH 206/353] scripts: srgb: use 2.2 gamma TF instead of piece-wise sRGB TF --- CHANGELOG.md | 12 ++++++++++++ scripts/srgb.py | 29 +++++------------------------ 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91627d0d..f231a1be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,9 @@ ### Changed * `cursor.color` moved to `colors.cursor`. +* `gamma-correct-blending=yes` now uses a pure gamma 2.2 transfer + function, instead of the piece-wise sRGB transfer function, to match + what compositors do. ### Deprecated @@ -99,6 +102,15 @@ ### Removed ### Fixed + +* Wrong colors when `gamma-correct-blending=yes` (the default when + there is compositor support). Note that some colors will still be + off by a **very** small amount, due to loss of precision when + converting to a linear color space. ([#2035][2035]). + +[2035]: https://codeberg.org/dnkl/foot/issues/2035 + + ### Security ### Contributors diff --git a/scripts/srgb.py b/scripts/srgb.py index 7655dbe4..12056956 100755 --- a/scripts/srgb.py +++ b/scripts/srgb.py @@ -5,21 +5,16 @@ import math import sys +# Note: we use a pure gamma 2.2 function, rather than the piece-wise +# sRGB transfer function, since that is what all compositors do. + def srgb_to_linear(f: float) -> float: assert(f >= 0 and f <= 1.0) - - if f <= 0.04045: - return f / 12.92 - - return math.pow((f + 0.055) / 1.055, 2.4) + return math.pow(f, 2.2) def linear_to_srgb(f: float) -> float: - if f < 0.0031308: - return f * 12.92 - - return 1.055 * math.pow(f, 1 / 2.4) - 0.055 - + return math.pow(f, 1 / 2.2) def main(): @@ -29,24 +24,10 @@ def main(): opts = parser.parse_args() linear_table: list[int] = [] - srgb_table: list[int] = [] for i in range(256): linear_table.append(int(srgb_to_linear(float(i) / 255) * 65535 + 0.5)) - for i in range(4096): - srgb_table.append(int(linear_to_srgb(float(i) / 4095) * 255 + 0.5)) - - for i in range(256): - while True: - linear = linear_table[i] - srgb = srgb_table[linear >> 4] - - if i == srgb: - break - - linear_table[i] += 1 - opts.h_output.write("#pragma once\n") opts.h_output.write("#include \n") From d7b48d3924eac9013fbd6b1f2220f44d2f392b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 28 Apr 2025 12:32:40 +0200 Subject: [PATCH 207/353] doc: foot.ini: gamma-correct: tweak wording of 8- vs. 10-bit surfaces --- doc/foot.ini.5.scd | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index f7da2c53..215809f8 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -220,11 +220,12 @@ empty string to be set, but it must be quoted: *KEY=""*) than intended when rendered with gamma-correct blending, since the font designer set the font weight based on incorrect rendering. - Note that some colors (especially dark ones) will look a bit + Note that some colors (especially dark ones) may be slightly off. The reason for this is loss of color precision, due to foot - using 8-bit surfaces (i.e. each color channel is 8 bits). The - amount of errors can be reduced by using 10-bit surfaces; see - *tweak.surface-bit-depth*. + using 8-bit surfaces (i.e. each color channel is 8 bits). In all + known cases, the difference is small enough not to be noticed + though. The amount of errors can be reduced by using 10-bit + surfaces; see *tweak.surface-bit-depth*. Default: enabled when compositor support is available From eb79a27900603aeb171e3c43c00452a11d695a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 30 Apr 2025 09:28:35 +0200 Subject: [PATCH 208/353] readme: donations: add liberapay --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3395aff0..95f43fb4 100644 --- a/README.md +++ b/README.md @@ -689,6 +689,7 @@ Every now and then I post foot related updates on # Sponsoring/donations +* Liberapay: https://liberapay.com/dnkl * GitHub Sponsors: https://github.com/sponsors/dnkl From 1ea20b1b707ded204e6239f2704ae97ec9cd8852 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Apr 2025 10:41:14 +0200 Subject: [PATCH 209/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aeabbe14..3c48c41c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.22.1](#1-22-1) * [1.22.0](#1-22-0) * [1.21.0](#1-21-0) @@ -60,6 +61,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.22.1 ### Fixed From ce424e0990f9bf20995cfde36f3ec635512d64fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 27 Apr 2025 10:14:45 +0200 Subject: [PATCH 210/353] scripts: srgb: use 2.2 gamma TF instead of piece-wise sRGB TF --- CHANGELOG.md | 15 +++++++++++++++ scripts/srgb.py | 29 +++++------------------------ 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c48c41c..645c6c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,9 +64,24 @@ ## Unreleased ### Added ### Changed + +* `gamma-correct-blending=yes` now uses a pure gamma 2.2 transfer + function, instead of the piece-wise sRGB transfer function, to match + what compositors do. + + ### Deprecated ### Removed ### Fixed + +* Wrong colors when `gamma-correct-blending=yes` (the default when + there is compositor support). Note that some colors will still be + off by a **very** small amount, due to loss of precision when + converting to a linear color space. ([#2035][2035]). + +[2035]: https://codeberg.org/dnkl/foot/issues/2035 + + ### Security ### Contributors diff --git a/scripts/srgb.py b/scripts/srgb.py index 7655dbe4..12056956 100755 --- a/scripts/srgb.py +++ b/scripts/srgb.py @@ -5,21 +5,16 @@ import math import sys +# Note: we use a pure gamma 2.2 function, rather than the piece-wise +# sRGB transfer function, since that is what all compositors do. + def srgb_to_linear(f: float) -> float: assert(f >= 0 and f <= 1.0) - - if f <= 0.04045: - return f / 12.92 - - return math.pow((f + 0.055) / 1.055, 2.4) + return math.pow(f, 2.2) def linear_to_srgb(f: float) -> float: - if f < 0.0031308: - return f * 12.92 - - return 1.055 * math.pow(f, 1 / 2.4) - 0.055 - + return math.pow(f, 1 / 2.2) def main(): @@ -29,24 +24,10 @@ def main(): opts = parser.parse_args() linear_table: list[int] = [] - srgb_table: list[int] = [] for i in range(256): linear_table.append(int(srgb_to_linear(float(i) / 255) * 65535 + 0.5)) - for i in range(4096): - srgb_table.append(int(linear_to_srgb(float(i) / 4095) * 255 + 0.5)) - - for i in range(256): - while True: - linear = linear_table[i] - srgb = srgb_table[linear >> 4] - - if i == srgb: - break - - linear_table[i] += 1 - opts.h_output.write("#pragma once\n") opts.h_output.write("#include \n") From 172f67a8df71d859e3f610dabffabff0623775a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 28 Apr 2025 12:32:40 +0200 Subject: [PATCH 211/353] doc: foot.ini: gamma-correct: tweak wording of 8- vs. 10-bit surfaces --- doc/foot.ini.5.scd | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index cffbf9c5..95e491eb 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -220,11 +220,12 @@ empty string to be set, but it must be quoted: *KEY=""*) than intended when rendered with gamma-correct blending, since the font designer set the font weight based on incorrect rendering. - Note that some colors (especially dark ones) will look a bit + Note that some colors (especially dark ones) may be slightly off. The reason for this is loss of color precision, due to foot - using 8-bit surfaces (i.e. each color channel is 8 bits). The - amount of errors can be reduced by using 10-bit surfaces; see - *tweak.surface-bit-depth*. + using 8-bit surfaces (i.e. each color channel is 8 bits). In all + known cases, the difference is small enough not to be noticed + though. The amount of errors can be reduced by using 10-bit + surfaces; see *tweak.surface-bit-depth*. Default: enabled when compositor support is available From fc293bad5e3989a7aef778ad8d0dc054794a990c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 30 Apr 2025 10:23:20 +0200 Subject: [PATCH 212/353] changelog: prepare 1.22.2 --- CHANGELOG.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 645c6c38..8ef3ae43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.22.2](#1-22-2) * [1.22.1](#1-22-1) * [1.22.0](#1-22-0) * [1.21.0](#1-21-0) @@ -61,8 +61,8 @@ * [1.2.0](#1-2-0) -## Unreleased -### Added +## 1.22.2 + ### Changed * `gamma-correct-blending=yes` now uses a pure gamma 2.2 transfer @@ -70,8 +70,6 @@ what compositors do. -### Deprecated -### Removed ### Fixed * Wrong colors when `gamma-correct-blending=yes` (the default when @@ -82,10 +80,6 @@ [2035]: https://codeberg.org/dnkl/foot/issues/2035 -### Security -### Contributors - - ## 1.22.1 ### Fixed From 513e91c33a0b1593f80b887e42c38ef4ba981634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 30 Apr 2025 10:23:51 +0200 Subject: [PATCH 213/353] meson: bump version to 1.22.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7ac033b5..b3163586 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.22.1', + version: '1.22.2', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 1dc8354534c9b1f1c7ae7e1bbe1cbd3df0cd1260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 30 Apr 2025 11:43:13 +0200 Subject: [PATCH 214/353] readme: add liberapay donation button --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 95f43fb4..e8f3c8cd 100644 --- a/README.md +++ b/README.md @@ -692,6 +692,8 @@ Every now and then I post foot related updates on * Liberapay: https://liberapay.com/dnkl * GitHub Sponsors: https://github.com/sponsors/dnkl +[![Donate using Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/dnkl/donate) + # License From b07ce56321404a34fc02635ecf4d5b63ddddf1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 1 May 2025 08:09:08 +0200 Subject: [PATCH 215/353] config: gamma-correct-blending: disable by default --- CHANGELOG.md | 1 + config.c | 15 +++------------ config.h | 4 +--- doc/foot.ini.5.scd | 2 +- foot.ini | 1 + render.c | 2 +- tests/test-config.c | 1 + wayland.c | 6 ++---- 8 files changed, 11 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f57ca9fb..8521fd86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ ### Changed * `cursor.color` moved to `colors.cursor`. +* `gamma-correct-blending` now defaults to `no` instead of `yes`. ### Deprecated diff --git a/config.c b/config.c index 04a9c9d4..f182241c 100644 --- a/config.c +++ b/config.c @@ -1086,17 +1086,8 @@ parse_section_main(struct context *ctx) return true; } - else if (streq(key, "gamma-correct-blending")) { - bool gamma_correct; - if (!value_to_bool(ctx, &gamma_correct)) - return false; - - conf->gamma_correct = - gamma_correct - ? GAMMA_CORRECT_ENABLED - : GAMMA_CORRECT_DISABLED; - return true; - } + else if (streq(key, "gamma-correct-blending")) + return value_to_bool(ctx, &conf->gamma_correct); else if (streq(key, "initial-color-theme")) { _Static_assert( @@ -3362,7 +3353,7 @@ config_load(struct config *conf, const char *conf_path, .underline_thickness = {.pt = 0., .px = -1}, .strikeout_thickness = {.pt = 0., .px = -1}, .dpi_aware = false, - .gamma_correct = GAMMA_CORRECT_AUTO, + .gamma_correct = false, .security = { .osc52 = OSC52_ENABLED, }, diff --git a/config.h b/config.h index cbdf11b1..be465d68 100644 --- a/config.h +++ b/config.h @@ -232,9 +232,7 @@ struct config { enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode; bool dpi_aware; - enum {GAMMA_CORRECT_DISABLED, - GAMMA_CORRECT_ENABLED, - GAMMA_CORRECT_AUTO} gamma_correct; + bool gamma_correct; struct config_font_list fonts[4]; struct font_size_adjustment font_size_adjustment; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 215809f8..0f06d0ca 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -227,7 +227,7 @@ empty string to be set, but it must be quoted: *KEY=""*) though. The amount of errors can be reduced by using 10-bit surfaces; see *tweak.surface-bit-depth*. - Default: enabled when compositor support is available + Default: _no_. *box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates box/line drawing characters diff --git a/foot.ini b/foot.ini index 563558db..f3ef6d85 100644 --- a/foot.ini +++ b/foot.ini @@ -22,6 +22,7 @@ # strikeout-thickness= # box-drawings-uses-font-glyphs=no # dpi-aware=no +# gamma-correct-blending=no # initial-color-theme=1 # initial-window-size-pixels=700x500 # Or, diff --git a/render.c b/render.c index b0d21d18..0ee60d65 100644 --- a/render.c +++ b/render.c @@ -5251,6 +5251,6 @@ render_xcursor_set(struct seat *seat, struct terminal *term, bool render_do_linear_blending(const struct terminal *term) { - return term->conf->gamma_correct != GAMMA_CORRECT_DISABLED && + return term->conf->gamma_correct && term->wl->color_management.img_description != NULL; } diff --git a/tests/test-config.c b/tests/test-config.c index 7dfb8556..bab57788 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -490,6 +490,7 @@ test_section_main(void) test_boolean(&ctx, &parse_section_main, "box-drawings-uses-font-glyphs", &conf.box_drawings_uses_font_glyphs); test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title); test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware); + test_boolean(&ctx, &parse_section_main, "gamma-correct-blending", &conf.gamma_correct); test_pt_or_px(&ctx, &parse_section_main, "font-size-adjustment", &conf.font_size_adjustment.pt_or_px); /* TODO: test ‘N%’ values too */ test_pt_or_px(&ctx, &parse_section_main, "line-height", &conf.line_height); diff --git a/wayland.c b/wayland.c index 853124be..9b143508 100644 --- a/wayland.c +++ b/wayland.c @@ -1980,7 +1980,7 @@ wayl_win_init(struct terminal *term, const char *token) xdg_toplevel_icon_v1_destroy(icon); } - if (term->conf->gamma_correct != GAMMA_CORRECT_DISABLED) { + if (term->conf->gamma_correct) { if (wayl->color_management.img_description != NULL) { xassert(wayl->color_management.manager != NULL); @@ -1990,7 +1990,7 @@ wayl_win_init(struct terminal *term, const char *token) wp_color_management_surface_v1_set_image_description( win->surface.color_management, wayl->color_management.img_description, WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); - } else if (term->conf->gamma_correct == GAMMA_CORRECT_ENABLED) { + } else { if (wayl->color_management.manager == NULL) { LOG_WARN( "gamma-corrected-blending: disabling; " @@ -2005,8 +2005,6 @@ wayl_win_init(struct terminal *term, const char *token) LOG_WARN(" - TF: ext_linear"); LOG_WARN(" - primaries: sRGB"); } - } else { - /* "auto" - don't warn */ } } From e5a0755451b736c635be58764799ef8d7b536e8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 1 May 2025 08:34:49 +0200 Subject: [PATCH 216/353] config: tweak.surface-bit-depth now defaults to 'auto' When set to 'auto', use 10-bit surfaces if gamma-correct blending is enabled, and 8-bit surfaces otherwise. Note that we may still fallback to 8-bit surfaces (without disabling gamma-correct blending) if the compositor does not support 10-bit surfaces. Closes #2082 --- CHANGELOG.md | 10 ++++++++++ config.c | 4 ++-- config.h | 8 +++++++- doc/foot.ini.5.scd | 41 ++++++++++++++++++++++------------------- pgo/pgo.c | 5 +++-- render.c | 31 +++++++++++++------------------ render.h | 2 -- shm.c | 15 ++++++++++++--- shm.h | 5 ++++- sixel.c | 4 ++-- terminal.c | 42 +++++++++++++++++++++--------------------- wayland.c | 7 +++++++ wayland.h | 2 ++ 13 files changed, 105 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8521fd86..aada556e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ - paper-color - selenized - solarized +* `auto` to the `tweak.surface-bit-depth` option. [2025]: https://codeberg.org/dnkl/foot/issues/2025 @@ -92,6 +93,9 @@ * `cursor.color` moved to `colors.cursor`. * `gamma-correct-blending` now defaults to `no` instead of `yes`. +* `tweak.surface-bit-depth` default value changed to `auto`; uses + 10-bit surfaces when `gamma-correct-blending=yes`, and 8-bit + surfaces otherwise. ### Deprecated @@ -101,6 +105,12 @@ ### Removed ### Fixed + +* Inaccurate colors when `gamma-correct-blending=yes` ([#2082][2082]). + +[2082]: https://codeberg.org/dnkl/foot/issues/2082 + + ### Security ### Contributors diff --git a/config.c b/config.c index f182241c..64e45135 100644 --- a/config.c +++ b/config.c @@ -2813,7 +2813,7 @@ parse_section_tweak(struct context *ctx) return value_to_enum( ctx, - (const char *[]){"8-bit", "10-bit", NULL}, + (const char *[]){"auto", "8-bit", "10-bit", NULL}, (int *)&conf->tweak.surface_bit_depth); } @@ -3463,7 +3463,7 @@ config_load(struct config *conf, const char *conf_path, .box_drawing_solid_shades = true, .font_monospace_warn = true, .sixel = true, - .surface_bit_depth = 8, + .surface_bit_depth = SHM_BITS_AUTO, }, .touch = { diff --git a/config.h b/config.h index be465d68..80081906 100644 --- a/config.h +++ b/config.h @@ -195,6 +195,12 @@ enum which_color_theme { COLOR_THEME2, }; +enum shm_bit_depth { + SHM_BITS_AUTO, + SHM_BITS_8, + SHM_BITS_10 +}; + struct config { char *term; char *shell; @@ -419,7 +425,7 @@ struct config { bool box_drawing_solid_shades; bool font_monospace_warn; bool sixel; - enum { SHM_8_BIT, SHM_10_BIT } surface_bit_depth; + enum shm_bit_depth surface_bit_depth; } tweak; struct { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 0f06d0ca..b9ab9c6a 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -220,12 +220,13 @@ empty string to be set, but it must be quoted: *KEY=""*) than intended when rendered with gamma-correct blending, since the font designer set the font weight based on incorrect rendering. - Note that some colors (especially dark ones) may be slightly - off. The reason for this is loss of color precision, due to foot - using 8-bit surfaces (i.e. each color channel is 8 bits). In all - known cases, the difference is small enough not to be noticed - though. The amount of errors can be reduced by using 10-bit - surfaces; see *tweak.surface-bit-depth*. + In order to represent colors faithfully, higher precision image + buffers are required. By default, foot will use 10-bit color + channels, if available, when gamma-correct blending is + enabled. However, the high precision buffers are slow; if you want + to use gamma-correct blending, but prefer speed (throughput and + input latency) over accurate colors, you can force 8-bit color + channels by setting *tweak.surface-bit-depth=8-bit*. Default: _no_. @@ -2019,23 +2020,25 @@ any of these options. *surface-bit-depth* Selects which RGB bit depth to use for image buffers. One of - *8-bit*, or *10-bit*. + *auto*, *8-bit*, or *10-bit*. - The default, *8-bit*, uses 8 bits for all channels, alpha - included. When *gamma-correct-blending* is disabled, this is the - best option. + *auto* chooses bit depth depending on other settings, and + availability. - When *gamma-correct-blending* is enabled, you may want to enable - 10-bit surfaces, as that improves color precision. Be aware - however, that in this mode, the alpha channel is only 2 bits - instead of 8 bits. Thus, if you are using a transparent - background, you may want to use the default, *8-bit*, even if you - have gamma-correct blending enabled. + *8-bit*, uses 8 bits for each color channel, alpha included. This + is the default when *gamma-correct-blending=no*. - You should also note that 10-bit surface is much slower. This will - increase input latency and decrease rendering throughput. + *10-bit* uses 10 bits for each RGB channel, and 2 bits for the + alpha channel. Thus, it provides higher precision color channels, + but a lower precision alpha channel. It is the default when + *gamma-correct-blending=yes*, if supported by the compositor. - Default: _8-bit_ + Note that *10-bit* is much slower than *8-bit*; if you want to use + gamma-correct blending, and if you prefer speed (throughput and + input latency) over accurate colors, you can set + *surface-bit-depth=8-bit* explicitly. + + Default: _auto_ # SEE ALSO diff --git a/pgo/pgo.c b/pgo/pgo.c index 8a4967ba..757dcd06 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -129,7 +129,7 @@ render_worker_thread(void *_ctx) } bool -render_do_linear_blending(const struct terminal *term) +wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf) { return false; } @@ -201,11 +201,12 @@ void urls_reset(struct terminal *term) {} void shm_unref(struct buffer *buf) {} void shm_chain_free(struct buffer_chain *chain) {} +enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain) { return SHM_BITS_8; } struct buffer_chain * shm_chain_new( struct wayland *wayl, bool scrollable, size_t pix_instances, - bool ten_bit_it_if_capable) + enum shm_bit_depth desired_bit_depth) { return NULL; } diff --git a/render.c b/render.c index 0ee60d65..55c2ec4d 100644 --- a/render.c +++ b/render.c @@ -626,7 +626,7 @@ draw_cursor(const struct terminal *term, const struct cell *cell, pixman_color_t cursor_color; pixman_color_t text_color; cursor_colors_for_cell(term, cell, fg, bg, &cursor_color, &text_color, - render_do_linear_blending(term)); + wayl_do_linear_blending(term->wl, term->conf)); if (unlikely(!term->kbd_focus)) { switch (term->conf->cursor.unfocused_style) { @@ -820,7 +820,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, if (cell->attrs.blink && term->blink.state == BLINK_OFF) _fg = color_blend_towards(_fg, 0x00000000, term->conf->dim.amount); - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct); pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct); @@ -1180,7 +1180,8 @@ static void render_urgency(struct terminal *term, struct buffer *buf) { uint32_t red = term->colors.table[1]; - pixman_color_t bg = color_hex_to_pixman(red, render_do_linear_blending(term)); + pixman_color_t bg = color_hex_to_pixman( + red, wayl_do_linear_blending(term->wl, term->conf)); int width = min(min(term->margins.left, term->margins.right), min(term->margins.top, term->margins.bottom)); @@ -1211,7 +1212,7 @@ render_margin(struct terminal *term, struct buffer *buf, const int bmargin = term->height - term->margins.bottom; const int line_count = end_line - start_line; - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); const uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg; uint16_t alpha = term->colors.alpha; @@ -1699,7 +1700,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, if (unlikely(term->is_searching)) return; - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); /* Adjust cursor position to viewport */ struct coord cursor; @@ -1970,7 +1971,8 @@ render_overlay(struct terminal *term) case OVERLAY_FLASH: color = color_hex_to_pixman_with_alpha( term->conf->colors.flash, - term->conf->colors.flash_alpha, render_do_linear_blending(term)); + term->conf->colors.flash_alpha, + wayl_do_linear_blending(term->wl, term->conf)); break; case OVERLAY_NONE: @@ -2312,7 +2314,7 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf, pixman_image_set_clip_region32(buf->pix[0], &clip); pixman_region32_fini(&clip); - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); uint16_t alpha = _bg >> 24 | (_bg >> 24 << 8); pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct); pixman_image_fill_rectangles( @@ -2453,7 +2455,7 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, if (info->width == 0 || info->height == 0) return; - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); { /* Fully transparent - no need to do a color space transform */ @@ -2542,7 +2544,7 @@ get_csd_button_fg_color(const struct terminal *term) } return color_hex_to_pixman_with_alpha( - _color, alpha, render_do_linear_blending(term)); + _color, alpha, wayl_do_linear_blending(term->wl, term->conf)); } static void @@ -2819,7 +2821,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, if (!term->visual_focus) _color = color_dim(term, _color); - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct); render_csd_part(term, surf->surf, buf, info->width, info->height, &color); @@ -3678,7 +3680,7 @@ render_search_box(struct terminal *term) : term->conf->colors.use_custom.search_box_no_match; /* Background - yellow on empty/match, red on mismatch (default) */ - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); const pixman_color_t color = color_hex_to_pixman( is_match ? (custom_colors @@ -5247,10 +5249,3 @@ render_xcursor_set(struct seat *seat, struct terminal *term, seat->pointer.xcursor_pending = true; return true; } - -bool -render_do_linear_blending(const struct terminal *term) -{ - return term->conf->gamma_correct && - term->wl->color_management.img_description != NULL; -} diff --git a/render.h b/render.h index c7b8e4a5..81d2a905 100644 --- a/render.h +++ b/render.h @@ -47,5 +47,3 @@ struct csd_data { }; struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx); - -bool render_do_linear_blending(const struct terminal *term); diff --git a/shm.c b/shm.c index 32e6bdd0..38944020 100644 --- a/shm.c +++ b/shm.c @@ -972,7 +972,7 @@ shm_unref(struct buffer *_buf) struct buffer_chain * shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, - bool ten_bit_if_capable) + enum shm_bit_depth desired_bit_depth) { pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8; enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888; @@ -982,8 +982,7 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, static bool have_logged = false; - - if (ten_bit_if_capable) { + if (desired_bit_depth == SHM_BITS_10) { if (wayl->shm_have_argb2101010 && wayl->shm_have_xrgb2101010) { pixman_fmt_without_alpha = PIXMAN_x2r10g10b10; shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB2101010; @@ -1058,3 +1057,13 @@ shm_chain_free(struct buffer_chain *chain) free(chain); } + +enum shm_bit_depth +shm_chain_bit_depth(const struct buffer_chain *chain) +{ + const pixman_format_code_t fmt = chain->pixman_fmt_with_alpha; + + return (fmt == PIXMAN_a2r10g10b10 || fmt == PIXMAN_a2b10g10r10) + ? SHM_BITS_10 + : SHM_BITS_8; +} diff --git a/shm.h b/shm.h index 2af185c9..8f8c406a 100644 --- a/shm.h +++ b/shm.h @@ -9,6 +9,7 @@ #include +#include "config.h" #include "wayland.h" struct damage; @@ -46,9 +47,11 @@ void shm_set_max_pool_size(off_t max_pool_size); struct buffer_chain; struct buffer_chain *shm_chain_new( struct wayland *wayl, bool scrollable, size_t pix_instances, - bool ten_bit_it_if_capable); + enum shm_bit_depth desired_bit_depth); void shm_chain_free(struct buffer_chain *chain); +enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain); + /* * Returns a single buffer. * diff --git a/sixel.c b/sixel.c index dd933d7a..680c258f 100644 --- a/sixel.c +++ b/sixel.c @@ -110,10 +110,10 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) term->sixel.image.height = 0; term->sixel.image.alloc_height = 0; term->sixel.image.bottom_pixel = 0; - term->sixel.linear_blending = render_do_linear_blending(term); + term->sixel.linear_blending = wayl_do_linear_blending(term->wl, term->conf); term->sixel.pixman_fmt = PIXMAN_a8r8g8b8; - if (term->conf->tweak.surface_bit_depth == SHM_10_BIT) { + if (term->conf->tweak.surface_bit_depth == SHM_BITS_10) { if (term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) { term->sixel.use_10bit = true; term->sixel.pixman_fmt = PIXMAN_a2r10g10b10; diff --git a/terminal.c b/terminal.c index d25516cb..793a1616 100644 --- a/terminal.c +++ b/terminal.c @@ -1073,19 +1073,16 @@ reload_fonts(struct terminal *term, bool resize_grid) options->scaling_filter = conf->tweak.fcft_filter; options->color_glyphs.format = PIXMAN_a8r8g8b8; - options->color_glyphs.srgb_decode = render_do_linear_blending(term); + options->color_glyphs.srgb_decode = + wayl_do_linear_blending(term->wl, term->conf); - if (conf->tweak.surface_bit_depth == SHM_10_BIT) { - if ((term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) || - (term->wl->shm_have_abgr2101010 && term->wl->shm_have_xbgr2101010)) - { - /* - * Use a high-res buffer type for emojis. We don't want to - * use an a2r10g0b10 type of surface, since we need more - * than 2 bits for alpha. - */ - options->color_glyphs.format = PIXMAN_rgba_float; - } + if (shm_chain_bit_depth(term->render.chains.grid) >= SHM_BITS_10) { + /* + * Use a high-res buffer type for emojis. We don't want to use + * an a2r10g0b10 type of surface, since we need more than 2 + * bits for alpha. + */ + options->color_glyphs.format = PIXMAN_rgba_float; } struct fcft_font *fonts[4]; @@ -1260,7 +1257,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, goto err; } - const bool ten_bit_surfaces = conf->tweak.surface_bit_depth == SHM_10_BIT; + const enum shm_bit_depth desired_bit_depth = + conf->tweak.surface_bit_depth == SHM_BITS_AUTO + ? wayl_do_linear_blending(wayl, conf) ? SHM_BITS_10 : SHM_BITS_8 + : conf->tweak.surface_bit_depth; const struct color_theme *theme = NULL; switch (conf->initial_color_theme) { @@ -1353,13 +1353,13 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .render = { .chains = { .grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count, - ten_bit_surfaces), - .search = shm_chain_new(wayl, false, 1 ,ten_bit_surfaces), - .scrollback_indicator = shm_chain_new(wayl, false, 1, ten_bit_surfaces), - .render_timer = shm_chain_new(wayl, false, 1, ten_bit_surfaces), - .url = shm_chain_new(wayl, false, 1, ten_bit_surfaces), - .csd = shm_chain_new(wayl, false, 1, ten_bit_surfaces), - .overlay = shm_chain_new(wayl, false, 1, ten_bit_surfaces), + desired_bit_depth), + .search = shm_chain_new(wayl, false, 1 ,desired_bit_depth), + .scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth), + .render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth), + .url = shm_chain_new(wayl, false, 1, desired_bit_depth), + .csd = shm_chain_new(wayl, false, 1, desired_bit_depth), + .overlay = shm_chain_new(wayl, false, 1, desired_bit_depth), }, .scrollback_lines = conf->scrollback.lines, .app_sync_updates.timer_fd = app_sync_updates_fd, @@ -1502,7 +1502,7 @@ term_window_configured(struct terminal *term) xassert(term->window->is_configured); fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term); - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); LOG_INFO("gamma-correct blending: %s", gamma_correct ? "enabled" : "disabled"); } } diff --git a/wayland.c b/wayland.c index 9b143508..320f03aa 100644 --- a/wayland.c +++ b/wayland.c @@ -2640,3 +2640,10 @@ wayl_activate(struct wayland *wayl, struct wl_window *win, const char *token) xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface.surf); } + +bool +wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf) +{ + return conf->gamma_correct && + wayl->color_management.img_description != NULL; +} diff --git a/wayland.h b/wayland.h index a9d6858c..044b217f 100644 --- a/wayland.h +++ b/wayland.h @@ -26,6 +26,7 @@ #include #include +#include "config.h" #include "cursor-shape.h" #include "fdm.h" @@ -539,3 +540,4 @@ bool wayl_get_activation_token( struct wl_window *win, activation_token_cb_t cb, void *cb_data); void wayl_activate(struct wayland *wayl, struct wl_window *win, const char *token); +bool wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf); From 9ff0151055e4883c588c8ce8c9f683c8b5d475bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 1 May 2025 10:17:20 +0200 Subject: [PATCH 217/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ef3ae43..6bc5b9df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.22.2](#1-22-2) * [1.22.1](#1-22-1) * [1.22.0](#1-22-0) @@ -61,6 +62,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.22.2 ### Changed From 7ced397089fb5724fb2021bea8491f361b289366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 1 May 2025 08:09:08 +0200 Subject: [PATCH 218/353] config: gamma-correct-blending: disable by default --- CHANGELOG.md | 4 ++++ config.c | 15 +++------------ config.h | 4 +--- doc/foot.ini.5.scd | 2 +- foot.ini | 1 + render.c | 2 +- tests/test-config.c | 1 + wayland.c | 6 ++---- 8 files changed, 14 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bc5b9df..dd6300ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,10 @@ ## Unreleased ### Added ### Changed + +* `gamma-correct-blending` now defaults to `no` instead of `yes`. + + ### Deprecated ### Removed ### Fixed diff --git a/config.c b/config.c index 347cc1ec..c7cf03f9 100644 --- a/config.c +++ b/config.c @@ -1083,17 +1083,8 @@ parse_section_main(struct context *ctx) return true; } - else if (streq(key, "gamma-correct-blending")) { - bool gamma_correct; - if (!value_to_bool(ctx, &gamma_correct)) - return false; - - conf->gamma_correct = - gamma_correct - ? GAMMA_CORRECT_ENABLED - : GAMMA_CORRECT_DISABLED; - return true; - } + else if (streq(key, "gamma-correct-blending")) + return value_to_bool(ctx, &conf->gamma_correct); else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); @@ -3318,7 +3309,7 @@ config_load(struct config *conf, const char *conf_path, .underline_thickness = {.pt = 0., .px = -1}, .strikeout_thickness = {.pt = 0., .px = -1}, .dpi_aware = false, - .gamma_correct = GAMMA_CORRECT_AUTO, + .gamma_correct = false, .security = { .osc52 = OSC52_ENABLED, }, diff --git a/config.h b/config.h index 2dec82c1..fe7a0331 100644 --- a/config.h +++ b/config.h @@ -168,9 +168,7 @@ struct config { enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode; bool dpi_aware; - enum {GAMMA_CORRECT_DISABLED, - GAMMA_CORRECT_ENABLED, - GAMMA_CORRECT_AUTO} gamma_correct; + bool gamma_correct; struct config_font_list fonts[4]; struct font_size_adjustment font_size_adjustment; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 95e491eb..52a14524 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -227,7 +227,7 @@ empty string to be set, but it must be quoted: *KEY=""*) though. The amount of errors can be reduced by using 10-bit surfaces; see *tweak.surface-bit-depth*. - Default: enabled when compositor support is available + Default: _no_. *box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates box/line drawing characters diff --git a/foot.ini b/foot.ini index 7d96ca0f..2ac0c05e 100644 --- a/foot.ini +++ b/foot.ini @@ -22,6 +22,7 @@ # strikeout-thickness= # box-drawings-uses-font-glyphs=no # dpi-aware=no +# gamma-correct-blending=no # initial-window-size-pixels=700x500 # Or, # initial-window-size-chars= diff --git a/render.c b/render.c index b0d21d18..0ee60d65 100644 --- a/render.c +++ b/render.c @@ -5251,6 +5251,6 @@ render_xcursor_set(struct seat *seat, struct terminal *term, bool render_do_linear_blending(const struct terminal *term) { - return term->conf->gamma_correct != GAMMA_CORRECT_DISABLED && + return term->conf->gamma_correct && term->wl->color_management.img_description != NULL; } diff --git a/tests/test-config.c b/tests/test-config.c index 69d349b4..99398fec 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -468,6 +468,7 @@ test_section_main(void) test_boolean(&ctx, &parse_section_main, "box-drawings-uses-font-glyphs", &conf.box_drawings_uses_font_glyphs); test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title); test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware); + test_boolean(&ctx, &parse_section_main, "gamma-correct-blending", &conf.gamma_correct); test_pt_or_px(&ctx, &parse_section_main, "font-size-adjustment", &conf.font_size_adjustment.pt_or_px); /* TODO: test ‘N%’ values too */ test_pt_or_px(&ctx, &parse_section_main, "line-height", &conf.line_height); diff --git a/wayland.c b/wayland.c index 853124be..9b143508 100644 --- a/wayland.c +++ b/wayland.c @@ -1980,7 +1980,7 @@ wayl_win_init(struct terminal *term, const char *token) xdg_toplevel_icon_v1_destroy(icon); } - if (term->conf->gamma_correct != GAMMA_CORRECT_DISABLED) { + if (term->conf->gamma_correct) { if (wayl->color_management.img_description != NULL) { xassert(wayl->color_management.manager != NULL); @@ -1990,7 +1990,7 @@ wayl_win_init(struct terminal *term, const char *token) wp_color_management_surface_v1_set_image_description( win->surface.color_management, wayl->color_management.img_description, WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL); - } else if (term->conf->gamma_correct == GAMMA_CORRECT_ENABLED) { + } else { if (wayl->color_management.manager == NULL) { LOG_WARN( "gamma-corrected-blending: disabling; " @@ -2005,8 +2005,6 @@ wayl_win_init(struct terminal *term, const char *token) LOG_WARN(" - TF: ext_linear"); LOG_WARN(" - primaries: sRGB"); } - } else { - /* "auto" - don't warn */ } } From 2a8948a3f32eaa6894457dcbf5aced57bdd91853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 1 May 2025 08:34:49 +0200 Subject: [PATCH 219/353] config: tweak.surface-bit-depth now defaults to 'auto' When set to 'auto', use 10-bit surfaces if gamma-correct blending is enabled, and 8-bit surfaces otherwise. Note that we may still fallback to 8-bit surfaces (without disabling gamma-correct blending) if the compositor does not support 10-bit surfaces. Closes #2082 --- CHANGELOG.md | 13 +++++++++++++ config.c | 4 ++-- config.h | 8 +++++++- doc/foot.ini.5.scd | 41 ++++++++++++++++++++++------------------- pgo/pgo.c | 5 +++-- render.c | 31 +++++++++++++------------------ render.h | 2 -- shm.c | 15 ++++++++++++--- shm.h | 5 ++++- sixel.c | 4 ++-- terminal.c | 42 +++++++++++++++++++++--------------------- wayland.c | 7 +++++++ wayland.h | 2 ++ 13 files changed, 108 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd6300ae..9ea93304 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,14 +64,27 @@ ## Unreleased ### Added + +* `auto` to the `tweak.surface-bit-depth` option. + + ### Changed * `gamma-correct-blending` now defaults to `no` instead of `yes`. +* `tweak.surface-bit-depth` default value changed to `auto`; uses + 10-bit surfaces when `gamma-correct-blending=yes`, and 8-bit + surfaces otherwise. ### Deprecated ### Removed ### Fixed + +* Inaccurate colors when `gamma-correct-blending=yes` ([#2082][2082]). + +[2082]: https://codeberg.org/dnkl/foot/issues/2082 + + ### Security ### Contributors diff --git a/config.c b/config.c index c7cf03f9..3f7ffa28 100644 --- a/config.c +++ b/config.c @@ -2771,7 +2771,7 @@ parse_section_tweak(struct context *ctx) return value_to_enum( ctx, - (const char *[]){"8-bit", "10-bit", NULL}, + (const char *[]){"auto", "8-bit", "10-bit", NULL}, (int *)&conf->tweak.surface_bit_depth); } @@ -3419,7 +3419,7 @@ config_load(struct config *conf, const char *conf_path, .box_drawing_solid_shades = true, .font_monospace_warn = true, .sixel = true, - .surface_bit_depth = 8, + .surface_bit_depth = SHM_BITS_AUTO, }, .touch = { diff --git a/config.h b/config.h index fe7a0331..ea0160bf 100644 --- a/config.h +++ b/config.h @@ -131,6 +131,12 @@ struct custom_regex { struct config_spawn_template launch; }; +enum shm_bit_depth { + SHM_BITS_AUTO, + SHM_BITS_8, + SHM_BITS_10 +}; + struct config { char *term; char *shell; @@ -408,7 +414,7 @@ struct config { bool box_drawing_solid_shades; bool font_monospace_warn; bool sixel; - enum { SHM_8_BIT, SHM_10_BIT } surface_bit_depth; + enum shm_bit_depth surface_bit_depth; } tweak; struct { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 52a14524..650242a2 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -220,12 +220,13 @@ empty string to be set, but it must be quoted: *KEY=""*) than intended when rendered with gamma-correct blending, since the font designer set the font weight based on incorrect rendering. - Note that some colors (especially dark ones) may be slightly - off. The reason for this is loss of color precision, due to foot - using 8-bit surfaces (i.e. each color channel is 8 bits). In all - known cases, the difference is small enough not to be noticed - though. The amount of errors can be reduced by using 10-bit - surfaces; see *tweak.surface-bit-depth*. + In order to represent colors faithfully, higher precision image + buffers are required. By default, foot will use 10-bit color + channels, if available, when gamma-correct blending is + enabled. However, the high precision buffers are slow; if you want + to use gamma-correct blending, but prefer speed (throughput and + input latency) over accurate colors, you can force 8-bit color + channels by setting *tweak.surface-bit-depth=8-bit*. Default: _no_. @@ -1974,23 +1975,25 @@ any of these options. *surface-bit-depth* Selects which RGB bit depth to use for image buffers. One of - *8-bit*, or *10-bit*. + *auto*, *8-bit*, or *10-bit*. - The default, *8-bit*, uses 8 bits for all channels, alpha - included. When *gamma-correct-blending* is disabled, this is the - best option. + *auto* chooses bit depth depending on other settings, and + availability. - When *gamma-correct-blending* is enabled, you may want to enable - 10-bit surfaces, as that improves color precision. Be aware - however, that in this mode, the alpha channel is only 2 bits - instead of 8 bits. Thus, if you are using a transparent - background, you may want to use the default, *8-bit*, even if you - have gamma-correct blending enabled. + *8-bit*, uses 8 bits for each color channel, alpha included. This + is the default when *gamma-correct-blending=no*. - You should also note that 10-bit surface is much slower. This will - increase input latency and decrease rendering throughput. + *10-bit* uses 10 bits for each RGB channel, and 2 bits for the + alpha channel. Thus, it provides higher precision color channels, + but a lower precision alpha channel. It is the default when + *gamma-correct-blending=yes*, if supported by the compositor. - Default: _8-bit_ + Note that *10-bit* is much slower than *8-bit*; if you want to use + gamma-correct blending, and if you prefer speed (throughput and + input latency) over accurate colors, you can set + *surface-bit-depth=8-bit* explicitly. + + Default: _auto_ # SEE ALSO diff --git a/pgo/pgo.c b/pgo/pgo.c index 8a4967ba..757dcd06 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -129,7 +129,7 @@ render_worker_thread(void *_ctx) } bool -render_do_linear_blending(const struct terminal *term) +wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf) { return false; } @@ -201,11 +201,12 @@ void urls_reset(struct terminal *term) {} void shm_unref(struct buffer *buf) {} void shm_chain_free(struct buffer_chain *chain) {} +enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain) { return SHM_BITS_8; } struct buffer_chain * shm_chain_new( struct wayland *wayl, bool scrollable, size_t pix_instances, - bool ten_bit_it_if_capable) + enum shm_bit_depth desired_bit_depth) { return NULL; } diff --git a/render.c b/render.c index 0ee60d65..55c2ec4d 100644 --- a/render.c +++ b/render.c @@ -626,7 +626,7 @@ draw_cursor(const struct terminal *term, const struct cell *cell, pixman_color_t cursor_color; pixman_color_t text_color; cursor_colors_for_cell(term, cell, fg, bg, &cursor_color, &text_color, - render_do_linear_blending(term)); + wayl_do_linear_blending(term->wl, term->conf)); if (unlikely(!term->kbd_focus)) { switch (term->conf->cursor.unfocused_style) { @@ -820,7 +820,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, if (cell->attrs.blink && term->blink.state == BLINK_OFF) _fg = color_blend_towards(_fg, 0x00000000, term->conf->dim.amount); - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); pixman_color_t fg = color_hex_to_pixman(_fg, gamma_correct); pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct); @@ -1180,7 +1180,8 @@ static void render_urgency(struct terminal *term, struct buffer *buf) { uint32_t red = term->colors.table[1]; - pixman_color_t bg = color_hex_to_pixman(red, render_do_linear_blending(term)); + pixman_color_t bg = color_hex_to_pixman( + red, wayl_do_linear_blending(term->wl, term->conf)); int width = min(min(term->margins.left, term->margins.right), min(term->margins.top, term->margins.bottom)); @@ -1211,7 +1212,7 @@ render_margin(struct terminal *term, struct buffer *buf, const int bmargin = term->height - term->margins.bottom; const int line_count = end_line - start_line; - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); const uint32_t _bg = !term->reverse ? term->colors.bg : term->colors.fg; uint16_t alpha = term->colors.alpha; @@ -1699,7 +1700,7 @@ render_ime_preedit_for_seat(struct terminal *term, struct seat *seat, if (unlikely(term->is_searching)) return; - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); /* Adjust cursor position to viewport */ struct coord cursor; @@ -1970,7 +1971,8 @@ render_overlay(struct terminal *term) case OVERLAY_FLASH: color = color_hex_to_pixman_with_alpha( term->conf->colors.flash, - term->conf->colors.flash_alpha, render_do_linear_blending(term)); + term->conf->colors.flash_alpha, + wayl_do_linear_blending(term->wl, term->conf)); break; case OVERLAY_NONE: @@ -2312,7 +2314,7 @@ render_osd(struct terminal *term, const struct wayl_sub_surface *sub_surf, pixman_image_set_clip_region32(buf->pix[0], &clip); pixman_region32_fini(&clip); - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); uint16_t alpha = _bg >> 24 | (_bg >> 24 << 8); pixman_color_t bg = color_hex_to_pixman_with_alpha(_bg, alpha, gamma_correct); pixman_image_fill_rectangles( @@ -2453,7 +2455,7 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, if (info->width == 0 || info->height == 0) return; - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); { /* Fully transparent - no need to do a color space transform */ @@ -2542,7 +2544,7 @@ get_csd_button_fg_color(const struct terminal *term) } return color_hex_to_pixman_with_alpha( - _color, alpha, render_do_linear_blending(term)); + _color, alpha, wayl_do_linear_blending(term->wl, term->conf)); } static void @@ -2819,7 +2821,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, if (!term->visual_focus) _color = color_dim(term, _color); - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); pixman_color_t color = color_hex_to_pixman_with_alpha(_color, alpha, gamma_correct); render_csd_part(term, surf->surf, buf, info->width, info->height, &color); @@ -3678,7 +3680,7 @@ render_search_box(struct terminal *term) : term->conf->colors.use_custom.search_box_no_match; /* Background - yellow on empty/match, red on mismatch (default) */ - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); const pixman_color_t color = color_hex_to_pixman( is_match ? (custom_colors @@ -5247,10 +5249,3 @@ render_xcursor_set(struct seat *seat, struct terminal *term, seat->pointer.xcursor_pending = true; return true; } - -bool -render_do_linear_blending(const struct terminal *term) -{ - return term->conf->gamma_correct && - term->wl->color_management.img_description != NULL; -} diff --git a/render.h b/render.h index c7b8e4a5..81d2a905 100644 --- a/render.h +++ b/render.h @@ -47,5 +47,3 @@ struct csd_data { }; struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx); - -bool render_do_linear_blending(const struct terminal *term); diff --git a/shm.c b/shm.c index 32e6bdd0..38944020 100644 --- a/shm.c +++ b/shm.c @@ -972,7 +972,7 @@ shm_unref(struct buffer *_buf) struct buffer_chain * shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, - bool ten_bit_if_capable) + enum shm_bit_depth desired_bit_depth) { pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8; enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888; @@ -982,8 +982,7 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, static bool have_logged = false; - - if (ten_bit_if_capable) { + if (desired_bit_depth == SHM_BITS_10) { if (wayl->shm_have_argb2101010 && wayl->shm_have_xrgb2101010) { pixman_fmt_without_alpha = PIXMAN_x2r10g10b10; shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB2101010; @@ -1058,3 +1057,13 @@ shm_chain_free(struct buffer_chain *chain) free(chain); } + +enum shm_bit_depth +shm_chain_bit_depth(const struct buffer_chain *chain) +{ + const pixman_format_code_t fmt = chain->pixman_fmt_with_alpha; + + return (fmt == PIXMAN_a2r10g10b10 || fmt == PIXMAN_a2b10g10r10) + ? SHM_BITS_10 + : SHM_BITS_8; +} diff --git a/shm.h b/shm.h index 2af185c9..8f8c406a 100644 --- a/shm.h +++ b/shm.h @@ -9,6 +9,7 @@ #include +#include "config.h" #include "wayland.h" struct damage; @@ -46,9 +47,11 @@ void shm_set_max_pool_size(off_t max_pool_size); struct buffer_chain; struct buffer_chain *shm_chain_new( struct wayland *wayl, bool scrollable, size_t pix_instances, - bool ten_bit_it_if_capable); + enum shm_bit_depth desired_bit_depth); void shm_chain_free(struct buffer_chain *chain); +enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain); + /* * Returns a single buffer. * diff --git a/sixel.c b/sixel.c index dd933d7a..680c258f 100644 --- a/sixel.c +++ b/sixel.c @@ -110,10 +110,10 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) term->sixel.image.height = 0; term->sixel.image.alloc_height = 0; term->sixel.image.bottom_pixel = 0; - term->sixel.linear_blending = render_do_linear_blending(term); + term->sixel.linear_blending = wayl_do_linear_blending(term->wl, term->conf); term->sixel.pixman_fmt = PIXMAN_a8r8g8b8; - if (term->conf->tweak.surface_bit_depth == SHM_10_BIT) { + if (term->conf->tweak.surface_bit_depth == SHM_BITS_10) { if (term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) { term->sixel.use_10bit = true; term->sixel.pixman_fmt = PIXMAN_a2r10g10b10; diff --git a/terminal.c b/terminal.c index f2d03e77..1ebe067e 100644 --- a/terminal.c +++ b/terminal.c @@ -1073,19 +1073,16 @@ reload_fonts(struct terminal *term, bool resize_grid) options->scaling_filter = conf->tweak.fcft_filter; options->color_glyphs.format = PIXMAN_a8r8g8b8; - options->color_glyphs.srgb_decode = render_do_linear_blending(term); + options->color_glyphs.srgb_decode = + wayl_do_linear_blending(term->wl, term->conf); - if (conf->tweak.surface_bit_depth == SHM_10_BIT) { - if ((term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) || - (term->wl->shm_have_abgr2101010 && term->wl->shm_have_xbgr2101010)) - { - /* - * Use a high-res buffer type for emojis. We don't want to - * use an a2r10g0b10 type of surface, since we need more - * than 2 bits for alpha. - */ - options->color_glyphs.format = PIXMAN_rgba_float; - } + if (shm_chain_bit_depth(term->render.chains.grid) >= SHM_BITS_10) { + /* + * Use a high-res buffer type for emojis. We don't want to use + * an a2r10g0b10 type of surface, since we need more than 2 + * bits for alpha. + */ + options->color_glyphs.format = PIXMAN_rgba_float; } struct fcft_font *fonts[4]; @@ -1260,7 +1257,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, goto err; } - const bool ten_bit_surfaces = conf->tweak.surface_bit_depth == SHM_10_BIT; + const enum shm_bit_depth desired_bit_depth = + conf->tweak.surface_bit_depth == SHM_BITS_AUTO + ? wayl_do_linear_blending(wayl, conf) ? SHM_BITS_10 : SHM_BITS_8 + : conf->tweak.surface_bit_depth; /* Initialize configure-based terminal attributes */ *term = (struct terminal) { @@ -1346,13 +1346,13 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .render = { .chains = { .grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count, - ten_bit_surfaces), - .search = shm_chain_new(wayl, false, 1 ,ten_bit_surfaces), - .scrollback_indicator = shm_chain_new(wayl, false, 1, ten_bit_surfaces), - .render_timer = shm_chain_new(wayl, false, 1, ten_bit_surfaces), - .url = shm_chain_new(wayl, false, 1, ten_bit_surfaces), - .csd = shm_chain_new(wayl, false, 1, ten_bit_surfaces), - .overlay = shm_chain_new(wayl, false, 1, ten_bit_surfaces), + desired_bit_depth), + .search = shm_chain_new(wayl, false, 1 ,desired_bit_depth), + .scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth), + .render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth), + .url = shm_chain_new(wayl, false, 1, desired_bit_depth), + .csd = shm_chain_new(wayl, false, 1, desired_bit_depth), + .overlay = shm_chain_new(wayl, false, 1, desired_bit_depth), }, .scrollback_lines = conf->scrollback.lines, .app_sync_updates.timer_fd = app_sync_updates_fd, @@ -1495,7 +1495,7 @@ term_window_configured(struct terminal *term) xassert(term->window->is_configured); fdm_add(term->fdm, term->ptmx, EPOLLIN, &fdm_ptmx, term); - const bool gamma_correct = render_do_linear_blending(term); + const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); LOG_INFO("gamma-correct blending: %s", gamma_correct ? "enabled" : "disabled"); } } diff --git a/wayland.c b/wayland.c index 9b143508..320f03aa 100644 --- a/wayland.c +++ b/wayland.c @@ -2640,3 +2640,10 @@ wayl_activate(struct wayland *wayl, struct wl_window *win, const char *token) xdg_activation_v1_activate(wayl->xdg_activation, token, win->surface.surf); } + +bool +wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf) +{ + return conf->gamma_correct && + wayl->color_management.img_description != NULL; +} diff --git a/wayland.h b/wayland.h index a9d6858c..044b217f 100644 --- a/wayland.h +++ b/wayland.h @@ -26,6 +26,7 @@ #include #include +#include "config.h" #include "cursor-shape.h" #include "fdm.h" @@ -539,3 +540,4 @@ bool wayl_get_activation_token( struct wl_window *win, activation_token_cb_t cb, void *cb_data); void wayl_activate(struct wayland *wayl, struct wl_window *win, const char *token); +bool wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf); From acea863fbef16bdd72e14f8fdbbf51db49bf0fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 1 May 2025 10:20:22 +0200 Subject: [PATCH 220/353] changelog: prepare for 1.22.3 --- CHANGELOG.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ea93304..0f08c500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.22.3](#1-22-3) * [1.22.2](#1-22-2) * [1.22.1](#1-22-1) * [1.22.0](#1-22-0) @@ -62,7 +62,8 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.22.3 + ### Added * `auto` to the `tweak.surface-bit-depth` option. @@ -76,8 +77,6 @@ surfaces otherwise. -### Deprecated -### Removed ### Fixed * Inaccurate colors when `gamma-correct-blending=yes` ([#2082][2082]). @@ -85,10 +84,6 @@ [2082]: https://codeberg.org/dnkl/foot/issues/2082 -### Security -### Contributors - - ## 1.22.2 ### Changed From 85c81042d2b16094677ccb383dd527a0df52e755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 1 May 2025 10:20:38 +0200 Subject: [PATCH 221/353] meson: bump version to 1.22.3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index b3163586..4bf4993c 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.22.2', + version: '1.22.3', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 0ea572dc63083e0d415382da2df8050a70f8cd07 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Mon, 28 Apr 2025 19:56:32 -0400 Subject: [PATCH 222/353] Paste URL/regex selection to prompt if key is uppercase. In copy-regex/show-urls-copy mode, if the last input character was uppercase, copy the selection to the clipboard _and_ paste it. This is useful for taking a file path from a command output:(log, git, test failure, etc.) and using it in another command. This is inspired by the behavior of copy mode in wezterm: https://wezterm.org/quickselect.html I could have made it check every character in the hint, but it seemed fine to assume that if the last character was uppercase, the user wanted this behavior. Closes #1975. --- CHANGELOG.md | 3 +++ doc/foot.ini.5.scd | 9 ++++++--- url-mode.c | 14 ++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe3b8bc..533f601c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,8 +85,11 @@ - paper-color - selenized - solarized +* `regex-copy`/`show-urls-copy` will copy and paste the selected text if the hint + is completed with an uppercase character ([#1975][1975]). [2025]: https://codeberg.org/dnkl/foot/issues/2025 +[1975]: https://codeberg.org/dnkl/foot/issues/1975 ### Changed diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index b9ab9c6a..3e70074e 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1358,7 +1358,8 @@ e.g. *search-start=none*. *show-urls-copy* Enter URL mode, where all currently visible URLs are tagged with a jump label with a key sequence that will place the URL in the - clipboard. Default: _none_. + clipboard. If the hint is completed with an uppercase character, + the match will also be pasted. Default: _none_. *regex-launch* Enter regex mode. This works exactly the same as URL mode; all @@ -1381,8 +1382,10 @@ e.g. *search-start=none*. Default: _none_. *regex-copy* - Same as *regex-copy*, but the match is placed in the clipboard, - instead of "launched", upon activation. Default: _none_. + Same as *regex-launch*, but the match is placed in the clipboard, + instead of "launched", upon activation. If the hint is completed + with an uppercase character, the match will also be pasted. + Default: _none_. *prompt-prev* Jump to the previous, currently not visible, prompt (requires diff --git a/url-mode.c b/url-mode.c index d0f7fc53..199ff3f1 100644 --- a/url-mode.c +++ b/url-mode.c @@ -131,7 +131,7 @@ spawn_url_launcher(struct seat *seat, struct terminal *term, const char *url, static void activate_url(struct seat *seat, struct terminal *term, const struct url *url, - uint32_t serial) + uint32_t serial, bool paste_url_to_self) { char *url_string = NULL; @@ -159,6 +159,15 @@ activate_url(struct seat *seat, struct terminal *term, const struct url *url, switch (url->action) { case URL_ACTION_COPY: + if (paste_url_to_self) { + if (term->bracketed_paste) + term_to_slave(term, "\033[200~", 6); + + term_to_slave(term, url_string, strlen(url_string)); + + if (term->bracketed_paste) + term_to_slave(term, "\033[201~", 6); + } if (text_to_clipboard(seat, term, url_string, seat->kbd.serial)) { /* Now owned by our clipboard “manager” */ url_string = NULL; @@ -273,7 +282,8 @@ urls_input(struct seat *seat, struct terminal *term, } if (match) { - activate_url(seat, term, match, serial); + // If the last hint character was uppercase, copy and paste + activate_url(seat, term, match, serial, wc == toc32upper(wc)); switch (match->action) { case URL_ACTION_COPY: From 237db6e771293689d6739de5aac7358d35dce5d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 2 May 2025 13:43:59 +0200 Subject: [PATCH 223/353] wayland: always call wl_display_dispatch_pending() at least once, after reading This fixes an issue where protocol errors aren't reported. I'm guessing the read succeeds, but that prepare_read() _also_ succeeds immediately, since there aren't any events to dispatch (only log the protocol error). By calling dispatch unconditionally, we ensure any error messages are printed. Then we proceed to loop prepare_read() + dispatch_pending() until the queue is empty. --- wayland.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wayland.c b/wayland.c index 320f03aa..a41b5060 100644 --- a/wayland.c +++ b/wayland.c @@ -1650,6 +1650,8 @@ fdm_wayl(struct fdm *fdm, int fd, int events, void *data) return false; } + wl_display_dispatch_pending(wayl->display); + while (wl_display_prepare_read(wayl->display) != 0) { if (wl_display_dispatch_pending(wayl->display) < 0) { LOG_ERRNO("failed to dispatch pending Wayland events"); From 5080e271c2fa5899ccb37384c26491e025b225ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 2 May 2025 13:46:18 +0200 Subject: [PATCH 224/353] wayland: attempt to log protocol errors on failure to flush When failing to flush, and the error is EPIPE, attempt to read and dispatch events. This ensures protocol errors are logged. --- wayland.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/wayland.c b/wayland.c index a41b5060..08994202 100644 --- a/wayland.c +++ b/wayland.c @@ -2247,7 +2247,14 @@ wayl_flush(struct wayland *wayl) } if (errno != EAGAIN) { - LOG_ERRNO("failed to flush wayland socket"); + const int saved_errno = errno; + + if (errno == EPIPE) { + wl_display_read_events(wayl->display); + wl_display_dispatch_pending(wayl->display); + } + + LOG_ERRNO_P(saved_errno, "failed to flush wayland socket"); return; } From 7354b94f737c9f589803b23714e7eddfb2f0fd69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 2 May 2025 08:53:43 +0200 Subject: [PATCH 225/353] osc: restore configured alpha if OSC-11 has no alpha value When parsing an OSC-11 without an alpha value (i.e. standard OSC-11, not rxvt's extended variant), restore the alpha value from the configuration, rather than keeping whatever the current alpha is. --- CHANGELOG.md | 3 +++ osc.c | 21 +++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 533f601c..54ad3a25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,9 @@ ### Changed * `cursor.color` moved to `colors.cursor`. +* OSC-11 without an alpha value will now restore the configured + (i.e. from `foot.ini`) alpha, rather than keeping whatever the + current alpha value is, unchanged. ### Deprecated diff --git a/osc.c b/osc.c index 7e3e6376..78f335e1 100644 --- a/osc.c +++ b/osc.c @@ -1455,15 +1455,20 @@ osc_dispatch(struct terminal *term) case 11: term->colors.bg = color; - if (have_alpha) { - const bool changed = term->colors.alpha != alpha; - term->colors.alpha = alpha; - - if (changed) { - wayl_win_alpha_changed(term->window); - term_font_subpixel_changed(term); - } + if (!have_alpha) { + alpha = term->colors.active_theme == COLOR_THEME1 + ? term->conf->colors.alpha + : term->conf->colors2.alpha; } + + const bool changed = term->colors.alpha != alpha; + term->colors.alpha = alpha; + + if (changed) { + wayl_win_alpha_changed(term->window); + term_font_subpixel_changed(term); + } + term_damage_color(term, COLOR_DEFAULT, 0); term_damage_margins(term); break; From 970e13db8deebf6c7542c7aede4f34703fa86769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 1 May 2025 09:37:47 +0200 Subject: [PATCH 226/353] config: tweak.surface-bit-depth: add support for 16-bit surfaces This adds supports for 16-bit surfaces, using the new PIXMAN_a16b16g16r16 buffer format. This maps to WL_SHM_FORMAT_ABGR16161616 (little-endian). Use the new 16-bit surfaces by default, when gamma-correct-blending=yes. --- CHANGELOG.md | 5 +++++ config.c | 9 +++++++- config.h | 3 ++- doc/foot.ini.5.scd | 29 +++++++++++++------------ meson.build | 4 ++++ shm.c | 53 +++++++++++++++++++++++++++++++++++++++------- sixel.c | 13 +++++++++++- terminal.c | 6 +++++- wayland.c | 2 ++ wayland.h | 2 ++ 10 files changed, 101 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54ad3a25..1a9917b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,9 @@ - solarized * `regex-copy`/`show-urls-copy` will copy and paste the selected text if the hint is completed with an uppercase character ([#1975][1975]). +* `16-bit` to `tweak.surface-bit-depth`. Makes foot use 16-bit image + buffers. They provide the necessary color precision required by + `gamma-correct-blending=yes`. [2025]: https://codeberg.org/dnkl/foot/issues/2025 [1975]: https://codeberg.org/dnkl/foot/issues/1975 @@ -98,6 +101,8 @@ * OSC-11 without an alpha value will now restore the configured (i.e. from `foot.ini`) alpha, rather than keeping whatever the current alpha value is, unchanged. +* `gamma-correct-blending=yes` now defaults to `16-bit` image buffers, + instead of `10-bit`. ### Deprecated diff --git a/config.c b/config.c index 64e45135..d0aae6a5 100644 --- a/config.c +++ b/config.c @@ -2809,12 +2809,19 @@ parse_section_tweak(struct context *ctx) else if (streq(key, "surface-bit-depth")) { _Static_assert(sizeof(conf->tweak.surface_bit_depth) == sizeof(int), - "enum is not 32-bit"); + "enum is not 32-bit"); +#if defined(HAVE_PIXMAN_RGBA_16) + return value_to_enum( + ctx, + (const char *[]){"auto", "8-bit", "10-bit", "16-bit", NULL}, + (int *)&conf->tweak.surface_bit_depth); +#else return value_to_enum( ctx, (const char *[]){"auto", "8-bit", "10-bit", NULL}, (int *)&conf->tweak.surface_bit_depth); +#endif } else { diff --git a/config.h b/config.h index 80081906..197b67cd 100644 --- a/config.h +++ b/config.h @@ -198,7 +198,8 @@ enum which_color_theme { enum shm_bit_depth { SHM_BITS_AUTO, SHM_BITS_8, - SHM_BITS_10 + SHM_BITS_10, + SHM_BITS_16, }; struct config { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 3e70074e..c1847932 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -207,7 +207,7 @@ empty string to be set, but it must be quoted: *KEY=""*) Compared to the default (disabled), bright glyphs on a dark background will appear thicker, and dark glyphs on a light background will appear thinner. - + FreeType can limit the effect of the latter, with a technique called stem darkening. It is only available for CFF fonts (OpenType, .otf) and disabled by default (in FreeType). You can @@ -221,12 +221,13 @@ empty string to be set, but it must be quoted: *KEY=""*) font designer set the font weight based on incorrect rendering. In order to represent colors faithfully, higher precision image - buffers are required. By default, foot will use 10-bit color - channels, if available, when gamma-correct blending is - enabled. However, the high precision buffers are slow; if you want - to use gamma-correct blending, but prefer speed (throughput and - input latency) over accurate colors, you can force 8-bit color - channels by setting *tweak.surface-bit-depth=8-bit*. + buffers are required. By default, foot will use either 16-bit, or + 10-bit color channels, depending on availability, when + gamma-correct blending is enabled. However, the high precision + buffers are slow; if you want to use gamma-correct blending, but + prefer speed (throughput and input latency) over accurate colors, + you can force 8-bit color channels by setting + *tweak.surface-bit-depth=8-bit*. Default: _no_. @@ -2023,7 +2024,7 @@ any of these options. *surface-bit-depth* Selects which RGB bit depth to use for image buffers. One of - *auto*, *8-bit*, or *10-bit*. + *auto*, *8-bit*, *10-bit* or *16-bit*. *auto* chooses bit depth depending on other settings, and availability. @@ -2033,12 +2034,14 @@ any of these options. *10-bit* uses 10 bits for each RGB channel, and 2 bits for the alpha channel. Thus, it provides higher precision color channels, - but a lower precision alpha channel. It is the default when - *gamma-correct-blending=yes*, if supported by the compositor. + but a lower precision alpha channel. - Note that *10-bit* is much slower than *8-bit*; if you want to use - gamma-correct blending, and if you prefer speed (throughput and - input latency) over accurate colors, you can set + *16-bit* 16 bits for each color channel, alpha included. If + available, this is the default when *gamma-correct-blending=yes*. + + Note that both *10-bit* and *16-bit* are much slower than *8-bit*; + if you want to use gamma-correct blending, and if you prefer speed + (throughput and input latency) over accurate colors, you can set *surface-bit-depth=8-bit* explicitly. Default: _auto_ diff --git a/meson.build b/meson.build index 4bf4993c..a884e533 100644 --- a/meson.build +++ b/meson.build @@ -145,6 +145,10 @@ if utf8proc.found() add_project_arguments('-DFOOT_GRAPHEME_CLUSTERING=1', language: 'c') endif +if pixman.version().version_compare('>=0.46.0') + add_project_arguments('-DHAVE_PIXMAN_RGBA_16', language: 'c') +endif + tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist') fcft = dependency('fcft', version: ['>=3.3.1', '<4.0.0'], fallback: 'fcft') diff --git a/shm.c b/shm.c index 38944020..b586b504 100644 --- a/shm.c +++ b/shm.c @@ -338,7 +338,10 @@ get_new_buffers(struct buffer_chain *chain, size_t count, size_t total_size = 0; for (size_t i = 0; i < count; i++) { stride[i] = stride_for_format_and_width( - with_alpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8, widths[i]); + with_alpha + ? chain->pixman_fmt_with_alpha + : chain->pixman_fmt_without_alpha, + widths[i]); sizes[i] = stride[i] * heights[i]; total_size += sizes[i]; } @@ -981,8 +984,38 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, enum wl_shm_format shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB8888; static bool have_logged = false; + static bool have_logged_10_fallback = false; - if (desired_bit_depth == SHM_BITS_10) { +#if defined(HAVE_PIXMAN_RGBA_16) + static bool have_logged_16_fallback = false; + + if (desired_bit_depth == SHM_BITS_16) { + if (wayl->shm_have_abgr161616 && wayl->shm_have_xbgr161616) { + pixman_fmt_without_alpha = PIXMAN_a16b16g16r16; + shm_fmt_without_alpha = WL_SHM_FORMAT_XBGR16161616; + + pixman_fmt_without_alpha = PIXMAN_a16b16g16r16; + shm_fmt_with_alpha = WL_SHM_FORMAT_ABGR16161616; + + if (!have_logged) { + have_logged = true; + LOG_INFO("using 16-bit BGR surfaces"); + } + } else { + if (!have_logged_16_fallback) { + have_logged_16_fallback = true; + + LOG_WARN( + "16-bit surfaces requested, but compositor does not " + "implement ABGR161616+XBGR161616"); + } + } + } +#endif + + if (desired_bit_depth >= SHM_BITS_10 && + pixman_fmt_with_alpha == PIXMAN_a8r8g8b8) + { if (wayl->shm_have_argb2101010 && wayl->shm_have_xrgb2101010) { pixman_fmt_without_alpha = PIXMAN_x2r10g10b10; shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB2101010; @@ -1010,13 +1043,13 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, } else { - if (!have_logged) { - have_logged = true; + if (!have_logged_10_fallback) { + have_logged_10_fallback = true; LOG_WARN( "10-bit surfaces requested, but compositor does not " "implement ARGB2101010+XRGB2101010, or " - "ABGR2101010+XBGR2101010. Falling back to 8-bit surfaces"); + "ABGR2101010+XBGR2101010"); } } } else { @@ -1063,7 +1096,11 @@ shm_chain_bit_depth(const struct buffer_chain *chain) { const pixman_format_code_t fmt = chain->pixman_fmt_with_alpha; - return (fmt == PIXMAN_a2r10g10b10 || fmt == PIXMAN_a2b10g10r10) - ? SHM_BITS_10 - : SHM_BITS_8; + return fmt == PIXMAN_a8r8g8b8 + ? SHM_BITS_8 +#if defined(HAVE_PIXMAN_RGBA_16) + : fmt == PIXMAN_a16b16g16r16 + ? SHM_BITS_16 +#endif + : SHM_BITS_10; } diff --git a/sixel.c b/sixel.c index 680c258f..c5ef01a1 100644 --- a/sixel.c +++ b/sixel.c @@ -113,7 +113,18 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) term->sixel.linear_blending = wayl_do_linear_blending(term->wl, term->conf); term->sixel.pixman_fmt = PIXMAN_a8r8g8b8; - if (term->conf->tweak.surface_bit_depth == SHM_BITS_10) { + /* + * Use higher-precision sixel surfaces if we're using + * higher-precision window surfaces. + * + * This is to a) get more accurate colors when doing gamma-correct + * blending, and b) use the same pixman format as the main + * surfaces, for (hopefully) better performance. + * + * For now, don't support 16-bit surfaces (too much sixel logic + * that assumes 32-bit pixels). + */ + if (shm_chain_bit_depth(term->render.chains.grid) >= SHM_BITS_10) { if (term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) { term->sixel.use_10bit = true; term->sixel.pixman_fmt = PIXMAN_a2r10g10b10; diff --git a/terminal.c b/terminal.c index 793a1616..f3a4b7d0 100644 --- a/terminal.c +++ b/terminal.c @@ -1082,7 +1082,11 @@ reload_fonts(struct terminal *term, bool resize_grid) * an a2r10g0b10 type of surface, since we need more than 2 * bits for alpha. */ +#if defined(HAVE_PIXMAN_RGBA_16) + options->color_glyphs.format = PIXMAN_a16b16g16r16; +#else options->color_glyphs.format = PIXMAN_rgba_float; +#endif } struct fcft_font *fonts[4]; @@ -1259,7 +1263,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, const enum shm_bit_depth desired_bit_depth = conf->tweak.surface_bit_depth == SHM_BITS_AUTO - ? wayl_do_linear_blending(wayl, conf) ? SHM_BITS_10 : SHM_BITS_8 + ? wayl_do_linear_blending(wayl, conf) ? SHM_BITS_16 : SHM_BITS_8 : conf->tweak.surface_bit_depth; const struct color_theme *theme = NULL; diff --git a/wayland.c b/wayland.c index 08994202..368b3be7 100644 --- a/wayland.c +++ b/wayland.c @@ -244,6 +244,8 @@ shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) case WL_SHM_FORMAT_ARGB2101010: wayl->shm_have_argb2101010 = true; break; case WL_SHM_FORMAT_XBGR2101010: wayl->shm_have_xbgr2101010 = true; break; case WL_SHM_FORMAT_ABGR2101010: wayl->shm_have_abgr2101010 = true; break; + case WL_SHM_FORMAT_XBGR16161616: wayl->shm_have_xbgr161616 = true; break; + case WL_SHM_FORMAT_ABGR16161616: wayl->shm_have_abgr161616 = true; break; } #if defined(_DEBUG) diff --git a/wayland.h b/wayland.h index 044b217f..b7e8e79f 100644 --- a/wayland.h +++ b/wayland.h @@ -496,6 +496,8 @@ struct wayland { bool shm_have_xrgb2101010:1; bool shm_have_abgr2101010:1; bool shm_have_xbgr2101010:1; + bool shm_have_abgr161616:1; + bool shm_have_xbgr161616:1; }; struct wayland *wayl_init( From c6db0bed42d85776e5e74d2f56e355da2962b886 Mon Sep 17 00:00:00 2001 From: Chen Mulong Date: Sat, 3 May 2025 09:31:24 +0800 Subject: [PATCH 227/353] Update catppuccin themes From https://github.com/catppuccin/foot Without the 'cursor.color', those themes have problems with cursor display problems in the zsh vi normal mode. --- themes/catppuccin-frappe | 5 +++++ themes/catppuccin-latte | 5 +++++ themes/catppuccin-macchiato | 5 +++++ themes/catppuccin-mocha | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/themes/catppuccin-frappe b/themes/catppuccin-frappe index 3b2e0131..44bef16c 100644 --- a/themes/catppuccin-frappe +++ b/themes/catppuccin-frappe @@ -23,6 +23,11 @@ bright5=f4b8e4 bright6=81c8be bright7=a5adce +cursor=232634 f2d5cf + +16=ef9f76 +17=f2d5cf + selection-foreground=c6d0f5 selection-background=4f5369 diff --git a/themes/catppuccin-latte b/themes/catppuccin-latte index 8e545f70..d0b90e64 100644 --- a/themes/catppuccin-latte +++ b/themes/catppuccin-latte @@ -23,6 +23,11 @@ bright5=ea76cb bright6=179299 bright7=bcc0cc +cursor=eff1f5 dc8a78 + +16=fe640b +17=dc8a78 + selection-foreground=4c4f69 selection-background=ccced7 diff --git a/themes/catppuccin-macchiato b/themes/catppuccin-macchiato index 50aca7da..ae8adab8 100644 --- a/themes/catppuccin-macchiato +++ b/themes/catppuccin-macchiato @@ -23,6 +23,11 @@ bright5=f5bde6 bright6=8bd5ca bright7=a5adcb +cursor=181926 f4dbd6 + +16=f5a97f +17=f4dbd6 + selection-foreground=cad3f5 selection-background=454a5f diff --git a/themes/catppuccin-mocha b/themes/catppuccin-mocha index 508ca382..d29eb0ec 100644 --- a/themes/catppuccin-mocha +++ b/themes/catppuccin-mocha @@ -23,6 +23,11 @@ bright5=f5c2e7 bright6=94e2d5 bright7=a6adc8 +cursor=11111b f5e0dc + +16=fab387 +17=f5e0dc + selection-foreground=cdd6f4 selection-background=414356 From c037836bbd8415ff0fcd428cbadea3bed7dbe022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 5 May 2025 13:02:04 +0200 Subject: [PATCH 228/353] doc: foot.ini: fix description of dark/light themes --- doc/foot.ini.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index c1847932..1cc45231 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1113,8 +1113,8 @@ Note that values are not inherited. That is, if you set a value in inherited by *colors2*. In the context of private mode 2031 (Dark and Light Mode detection), -the primary theme (i.e. the *colors2* section) is considered to be the -light theme (since the default theme is dark). +the alternative theme (i.e. the *colors2* section) is considered to be +the light theme (since the default, the primary theme, is dark). # SECTION: csd From 073b637d4535cd0dfe92ca1ce954b552ae9f1a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 5 May 2025 12:43:02 +0200 Subject: [PATCH 229/353] render: refactor to allow setting only selection bg or fg Before this, we only applied custom selection colors, if *both* the selection bg and fg had been set. Since the options are already split up into two separate options, and since it makes sense to at least be able to keep the foreground colors unchanged (i.e. only setting the selection background), let's allow only having one of the selection colors set. Closes #1846 --- CHANGELOG.md | 4 ++ config.c | 5 --- config.h | 1 - doc/foot.ini.5.scd | 3 +- osc.c | 4 -- render.c | 108 ++++++++++++++++++++++++++------------------- terminal.c | 2 - terminal.h | 1 - 8 files changed, 68 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a9917b5..ea818d53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,10 @@ current alpha value is, unchanged. * `gamma-correct-blending=yes` now defaults to `16-bit` image buffers, instead of `10-bit`. +* Allow setting either selection background, or selection foreground, + only ([#1846][1846]). + +[1846]: https://codeberg.org/dnkl/foot/issues/1846 ### Deprecated diff --git a/config.c b/config.c index d0aae6a5..07f781d6 100644 --- a/config.c +++ b/config.c @@ -3403,7 +3403,6 @@ config_load(struct config *conf, const char *conf_path, .cursor = 0, }, .use_custom = { - .selection = false, .jump_label = false, .scrollback_indicator = false, .url = false, @@ -3593,10 +3592,6 @@ config_load(struct config *conf, const char *conf_path, if (!config_override_apply(conf, overrides, errors_are_fatal)) ret = !errors_are_fatal; - conf->colors.use_custom.selection = - conf->colors.selection_fg >> 24 == 0 && - conf->colors.selection_bg >> 24 == 0; - if (ret && conf->fonts[0].count == 0) { struct config_font font; if (!config_font_parse("monospace", &font)) { diff --git a/config.h b/config.h index 197b67cd..7cf6f6f5 100644 --- a/config.h +++ b/config.h @@ -180,7 +180,6 @@ struct color_theme { struct { bool cursor:1; - bool selection:1; bool jump_label:1; bool scrollback_indicator:1; bool url:1; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 1cc45231..81b88f64 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1069,8 +1069,7 @@ dark theme (since the default theme is dark). *selection-foreground*, *selection-background* Foreground (text) and background color to use in selected - text. Note that *both* options must be set, or the default will be - used. Default: _inverse foreground/background_. + text. Default: _inverse foreground/background_. *jump-labels* Two color values specifying the foreground (text) and background diff --git a/osc.c b/osc.c index 78f335e1..d59adc5a 100644 --- a/osc.c +++ b/osc.c @@ -1480,12 +1480,10 @@ osc_dispatch(struct terminal *term) case 17: term->colors.selection_bg = color; - term->colors.use_custom_selection = true; break; case 19: term->colors.selection_fg = color; - term->colors.use_custom_selection = true; break; } @@ -1589,13 +1587,11 @@ osc_dispatch(struct terminal *term) case 117: LOG_DBG("resetting selection background color"); term->colors.selection_bg = term->conf->colors.selection_bg; - term->colors.use_custom_selection = term->conf->colors.use_custom.selection; break; case 119: LOG_DBG("resetting selection foreground color"); term->colors.selection_fg = term->conf->colors.selection_fg; - term->colors.use_custom_selection = term->conf->colors.use_custom.selection; break; case 133: diff --git a/render.c b/render.c index 55c2ec4d..83a160bc 100644 --- a/render.c +++ b/render.c @@ -694,51 +694,75 @@ render_cell(struct terminal *term, pixman_image_t *pix, const int x = term->margins.left + col * width; const int y = term->margins.top + row_no * height; - bool is_selected = cell->attrs.selected; - uint32_t _fg = 0; uint32_t _bg = 0; uint16_t alpha = 0xffff; + const bool is_selected = cell->attrs.selected; + + /* Use cell specific color, if set, otherwise the default colors (possible reversed) */ + switch (cell->attrs.fg_src) { + case COLOR_RGB: + _fg = cell->attrs.fg; + break; + + case COLOR_BASE16: + case COLOR_BASE256: + xassert(cell->attrs.fg < ALEN(term->colors.table)); + _fg = term->colors.table[cell->attrs.fg]; + break; + + case COLOR_DEFAULT: + _fg = term->reverse ? term->colors.bg : term->colors.fg; + break; + } + + switch (cell->attrs.bg_src) { + case COLOR_RGB: + _bg = cell->attrs.bg; + break; + + case COLOR_BASE16: + case COLOR_BASE256: + xassert(cell->attrs.bg < ALEN(term->colors.table)); + _bg = term->colors.table[cell->attrs.bg]; + break; + + case COLOR_DEFAULT: + _bg = term->reverse ? term->colors.fg : term->colors.bg; + break; + } + + if (unlikely(is_selected)) { + const uint32_t cell_fg = _fg; + const uint32_t cell_bg = _bg; + + const bool custom_fg = term->colors.selection_fg >> 24 == 0; + const bool custom_bg = term->colors.selection_bg >> 24 == 0; + const bool custom_both = custom_fg && custom_bg; + + if (custom_both) { + _fg = term->colors.selection_fg; + _bg = term->colors.selection_bg; + } else if (custom_bg) { + _bg = term->colors.selection_bg; + _fg = cell->attrs.reverse ? cell_bg : cell_fg; + } else if (custom_fg) { + _fg = term->colors.selection_fg; + _bg = cell->attrs.reverse ? cell_fg : cell_bg; + } else { + _bg = cell_fg; + _fg = cell_bg; + } + + if (unlikely(_fg == _bg)) { + /* Invert bg when selected/highlighted text has same fg/bg */ + _bg = ~_bg; + alpha = 0xffff; + } - if (is_selected && term->colors.use_custom_selection) { - _fg = term->colors.selection_fg; - _bg = term->colors.selection_bg; } else { - /* Use cell specific color, if set, otherwise the default colors (possible reversed) */ - switch (cell->attrs.fg_src) { - case COLOR_RGB: - _fg = cell->attrs.fg; - break; - - case COLOR_BASE16: - case COLOR_BASE256: - xassert(cell->attrs.fg < ALEN(term->colors.table)); - _fg = term->colors.table[cell->attrs.fg]; - break; - - case COLOR_DEFAULT: - _fg = term->reverse ? term->colors.bg : term->colors.fg; - break; - } - - switch (cell->attrs.bg_src) { - case COLOR_RGB: - _bg = cell->attrs.bg; - break; - - case COLOR_BASE16: - case COLOR_BASE256: - xassert(cell->attrs.bg < ALEN(term->colors.table)); - _bg = term->colors.table[cell->attrs.bg]; - break; - - case COLOR_DEFAULT: - _bg = term->reverse ? term->colors.fg : term->colors.bg; - break; - } - - if (cell->attrs.reverse ^ is_selected) { + if (unlikely(cell->attrs.reverse)) { uint32_t swap = _fg; _fg = _bg; _bg = swap; @@ -806,12 +830,6 @@ render_cell(struct terminal *term, pixman_image_t *pix, } } - if (unlikely(is_selected && _fg == _bg)) { - /* Invert bg when selected/highlighted text has same fg/bg */ - _bg = ~_bg; - alpha = 0xffff; - } - if (cell->attrs.dim) _fg = color_dim(term, _fg); if (term->conf->bold_in_bright.enabled && cell->attrs.bold) diff --git a/terminal.c b/terminal.c index f3a4b7d0..9ec538c6 100644 --- a/terminal.c +++ b/terminal.c @@ -1312,7 +1312,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor, .selection_fg = theme->selection_fg, .selection_bg = theme->selection_bg, - .use_custom_selection = theme->use_custom.selection, .active_theme = conf->initial_color_theme, }, .color_stack = { @@ -4714,6 +4713,5 @@ term_theme_apply(struct terminal *term, const struct color_theme *theme) term->colors.cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor; term->colors.selection_fg = theme->selection_fg; term->colors.selection_bg = theme->selection_bg; - term->colors.use_custom_selection = theme->use_custom.selection; memcpy(term->colors.table, theme->table, sizeof(term->colors.table)); } diff --git a/terminal.h b/terminal.h index 4639fa69..3122cef3 100644 --- a/terminal.h +++ b/terminal.h @@ -404,7 +404,6 @@ struct colors { uint32_t cursor_bg; /* cursor color */ uint32_t selection_fg; uint32_t selection_bg; - bool use_custom_selection; enum which_color_theme active_theme; }; From 9b0d5e7c96f75d7ca81cd2e452cda54688ecc259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 8 May 2025 10:22:45 +0200 Subject: [PATCH 230/353] term: unittest: auto-scroll timer FD is created on-demand nowadays --- terminal.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/terminal.c b/terminal.c index 9ec538c6..18f3bc9f 100644 --- a/terminal.c +++ b/terminal.c @@ -2769,13 +2769,11 @@ UNITTEST }, .kind = SELECTION_NONE, .auto_scroll = { - .fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK), + .fd = -1, }, }, }; - xassert(term.selection.auto_scroll.fd >= 0); - #define populate_scrollback() do { \ for (int i = 0; i < scrollback_rows; i++) { \ if (term.normal.rows[i] == NULL) { \ @@ -2865,7 +2863,7 @@ UNITTEST /* Cleanup */ tll_free(term.normal.sixel_images); - close(term.selection.auto_scroll.fd); + xassert(term.selection.auto_scroll.fd == -1); for (int i = 0; i < scrollback_rows; i++) grid_row_free(term.normal.rows[i]); free(term.normal.rows); From ebd1614316ea541cd1ac0cc0029f771be333176e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 16 May 2025 10:46:25 +0200 Subject: [PATCH 231/353] csi: when REP:ing a "combining" character, use correct width Before this patch, we just called c32width(), which only works on actual codepoints. If the last printed character is a "combining" character, i.e. a key into our lookup table for multi-codepoint graphemes, we need to lookup the grapheme and pick the width from there. See https://gitlab.com/AutumnMeowMeow/jexer/-/issues/119#note_2499712901 --- CHANGELOG.md | 4 ++++ csi.c | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea818d53..00c1f0a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,6 +116,10 @@ ### Removed ### Fixed + +* `REP`: wrong width of repeated multi-codepoint graphemes. + + ### Security ### Contributors diff --git a/csi.c b/csi.c index e8b2c492..6d4845be 100644 --- a/csi.c +++ b/csi.c @@ -799,7 +799,17 @@ csi_dispatch(struct terminal *term, uint8_t final) int count = vt_param_get(term, 0, 1); LOG_DBG("REP: '%lc' %d times", (wint_t)term->vt.last_printed, count); - const int width = c32width(term->vt.last_printed); + int width; + + if (term->vt.last_printed >= CELL_COMB_CHARS_LO) { + const struct composed *comp = composed_lookup( + term->composed, term->vt.last_printed - CELL_COMB_CHARS_LO); + + xassert(comp != NULL); + width = comp->forced_width > 0 ? comp->forced_width : comp->width; + } else + width = c32width(term->vt.last_printed); + if (width > 0) { for (int i = 0; i < count; i++) term_print(term, term->vt.last_printed, width, false); From 8bd39b32cd87abc65a30d23a83a0564f6419025e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 18 May 2025 11:29:50 +0200 Subject: [PATCH 232/353] Revert "xkbcommon: require libxkbcommon >= 1.8.0" This reverts commit 34d3f4664b93d42ec3e1eef9a11e78756465d25a. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index a884e533..ae263851 100644 --- a/meson.build +++ b/meson.build @@ -137,7 +137,7 @@ wayland_protocols = dependency('wayland-protocols', version: '>=1.41', default_options: ['tests=false']) wayland_client = dependency('wayland-client') wayland_cursor = dependency('wayland-cursor') -xkb = dependency('xkbcommon', version: '>=1.8.0') +xkb = dependency('xkbcommon', version: '>=1.0.0') fontconfig = dependency('fontconfig') utf8proc = dependency('libutf8proc', required: get_option('grapheme-clustering')) From 3e1e3ea38ce8ed43ee61ddb584298f45dd72f1d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 18 May 2025 11:35:27 +0200 Subject: [PATCH 233/353] libxkbcommon: don't require 1.8.0 The version bump was done since we now use XKB_VMOD_NAME_*; macros added in libxkbcommon 1.8.0. Not all distros have updated libxkbcommon yet (read: Debian). Since it's fairly easy to work around, let's do that. Closes #2103 --- CHANGELOG.md | 3 +++ input.c | 1 + key-binding.c | 1 + meson.build | 1 + xkbcommon-vmod.h | 18 ++++++++++++++++++ 5 files changed, 24 insertions(+) create mode 100644 xkbcommon-vmod.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 00c1f0a0..cb478f87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,8 +105,11 @@ instead of `10-bit`. * Allow setting either selection background, or selection foreground, only ([#1846][1846]). +* Drop required version of libxkbcommon from 1.8.0 back to 1.0.0 + ([#2103][2103]). [1846]: https://codeberg.org/dnkl/foot/issues/1846 +[2103]: https://codeberg.org/dnkl/foot/issues/2103 ### Deprecated diff --git a/input.c b/input.c index b6c56fde..271ffb88 100644 --- a/input.c +++ b/input.c @@ -40,6 +40,7 @@ #include "url-mode.h" #include "util.h" #include "vt.h" +#include "xkbcommon-vmod.h" #include "xmalloc.h" #include "xsnprintf.h" diff --git a/key-binding.c b/key-binding.c index e5b7ac81..a2883ed5 100644 --- a/key-binding.c +++ b/key-binding.c @@ -11,6 +11,7 @@ #include "terminal.h" #include "util.h" #include "wayland.h" +#include "xkbcommon-vmod.h" #include "xmalloc.h" struct vmod_map { diff --git a/meson.build b/meson.build index ae263851..7b9490d9 100644 --- a/meson.build +++ b/meson.build @@ -319,6 +319,7 @@ executable( 'url-mode.c', 'url-mode.h', 'user-notification.c', 'user-notification.h', 'wayland.c', 'wayland.h', 'shm-formats.h', + 'xkbcommon-vmod.h', srgb_funcs, wl_proto_src + wl_proto_headers, version, dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc, tllist, fcft], diff --git a/xkbcommon-vmod.h b/xkbcommon-vmod.h new file mode 100644 index 00000000..44d818ec --- /dev/null +++ b/xkbcommon-vmod.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +/* Added in libxkbcommon 1.8.0 */ +#if !defined(XKB_VMOD_NAME_ALT) +/* Common *virtual* modifiers, encoded in xkeyboard-config in the compat and + * symbols files. They have been stable since the beginning of the project and + * are unlikely to ever change. */ +#define XKB_VMOD_NAME_ALT "Alt" +#define XKB_VMOD_NAME_HYPER "Hyper" +#define XKB_VMOD_NAME_LEVEL3 "LevelThree" +#define XKB_VMOD_NAME_LEVEL5 "LevelFive" +#define XKB_VMOD_NAME_META "Meta" +#define XKB_VMOD_NAME_NUM "NumLock" +#define XKB_VMOD_NAME_SCROLL "ScrollLock" +#define XKB_VMOD_NAME_SUPER "Super" +#endif From 456ac5d79f46a670479fcdaa75df0942a1571c78 Mon Sep 17 00:00:00 2001 From: Kirill Primak Date: Tue, 20 May 2025 15:01:25 +0300 Subject: [PATCH 234/353] render: improve CSD button positioning This commit fixes titlebar button positioning when maximization isn't available but minimization is. --- render.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/render.c b/render.c index 83a160bc..e0f32575 100644 --- a/render.c +++ b/render.c @@ -2254,16 +2254,21 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) const int button_width = title_visible ? roundf(term->conf->csd.button_width * scale) : 0; - const int button_close_width = term->width >= 1 * button_width - ? button_width : 0; + int remaining_width = term->width; - const int button_maximize_width = - term->width >= 2 * button_width && term->window->wm_capabilities.maximize - ? button_width : 0; + const int button_close_width = remaining_width >= button_width ? button_width : 0; + remaining_width -= button_close_width; + const int button_close_start = remaining_width; - const int button_minimize_width = - term->width >= 3 * button_width && term->window->wm_capabilities.minimize - ? button_width : 0; + const int button_maximize_width = remaining_width >= button_width && + term->window->wm_capabilities.maximize ? button_width : 0; + remaining_width -= button_maximize_width; + const int button_maximize_start = remaining_width; + + const int button_minimize_width = remaining_width >= button_width && + term->window->wm_capabilities.minimize ? button_width : 0; + remaining_width -= button_minimize_width; + const int button_minimize_start = remaining_width; /* * With fractional scaling, we must ensure the offset, when @@ -2288,9 +2293,9 @@ get_csd_data(const struct terminal *term, enum csd_surface surf_idx) case CSD_SURF_BOTTOM: return (struct csd_data){-border_width, term->height, top_bottom_width, border_width}; /* Positioned relative to CSD_SURF_TITLE */ - case CSD_SURF_MINIMIZE: return (struct csd_data){term->width - 3 * button_width, 0, button_minimize_width, title_height}; - case CSD_SURF_MAXIMIZE: return (struct csd_data){term->width - 2 * button_width, 0, button_maximize_width, title_height}; - case CSD_SURF_CLOSE: return (struct csd_data){term->width - 1 * button_width, 0, button_close_width, title_height}; + case CSD_SURF_MINIMIZE: return (struct csd_data){button_minimize_start, 0, button_minimize_width, title_height}; + case CSD_SURF_MAXIMIZE: return (struct csd_data){button_maximize_start, 0, button_maximize_width, title_height}; + case CSD_SURF_CLOSE: return (struct csd_data){ button_close_start, 0, button_close_width, title_height}; case CSD_SURF_COUNT: break; From d26659988113662d1500f600f5c10899920a5e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 21 May 2025 13:01:30 +0200 Subject: [PATCH 235/353] wayland: configure: don't commit if we have a pending refresh Currently, if the following occurs: 1. foot has AxB size 2. Compositor sends CxD size 3. foot detects a resize, acks and saves CxD, but doesn't redraw immediately 4. Compositor sends CxD size again (due to a toplevel state array change, for example) Then foot will detect no resize occurred, and will do an "empty" commit immediately. In this particular case that's wrong, since we're effectively acking+committing the initial AxB size. Fix by only doing the immediate commit if there's no size change **and** there's no pending refresh. Note: normally, we'd resize and refresh+commit immediately, but if we're waiting for a frame callback, then the refresh+commit will be delayed (i.e. scheduled). This is what we're checking here. Closes #2105 --- CHANGELOG.md | 4 ++++ wayland.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb478f87..fe149600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,10 @@ ### Fixed * `REP`: wrong width of repeated multi-codepoint graphemes. +* Incorrect surface commit after a configure event, under certain + conditions ([#2105][2105]). + +[2105]: https://codeberg.org/dnkl/foot/issues/2105 ### Security diff --git a/wayland.c b/wayland.c index 368b3be7..37fefb29 100644 --- a/wayland.c +++ b/wayland.c @@ -1134,7 +1134,7 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, else term_visual_focus_out(term); - if (!resized) { + if (!resized && !term->render.pending.grid) { /* * If we didn't resize, we won't be committing a new surface * anytime soon. Some compositors require a commit in From 664cdcc65cf42ab269ea4a2c4962c3ee65d7e92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 21 May 2025 15:25:28 +0200 Subject: [PATCH 236/353] cursor-shape: add 'dnd-ask' and 'all-resize' These (non-css) cursor shapes were added to the cursor-shape-v1 protocol in wayland-protocols 1.42. We don't need (or use them at all) internally, but add them to the list we use to translate from shape names to shape enums. This allows users to set a custom shape (via OSC-22), while still using server side cursors (i.e. no need to fallback to client-side cursors). If we try to set a shape not implemented by the server, we get a protocol error and foot exits. This is bad. So, make sure we don't do that: 1. First, we need to explicitly bind v2 if implemented by the server 2. Track the bound version number in the wayland struct 3. When matching shape enum, skip shapes not supported in the currently bound version of the cursor-shape protocol --- CHANGELOG.md | 1 + cursor-shape.c | 22 +++++++++++++++++++++- cursor-shape.h | 2 +- render.c | 5 +++-- terminal.c | 4 +++- wayland.c | 10 +++++++++- wayland.h | 1 + 7 files changed, 39 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe149600..66fa7c93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ * `16-bit` to `tweak.surface-bit-depth`. Makes foot use 16-bit image buffers. They provide the necessary color precision required by `gamma-correct-blending=yes`. +* New cursor shapes, from `cursor-shape-v1` version 2. [2025]: https://codeberg.org/dnkl/foot/issues/2025 [1975]: https://codeberg.org/dnkl/foot/issues/1975 diff --git a/cursor-shape.c b/cursor-shape.c index bbf75ab8..6e859259 100644 --- a/cursor-shape.c +++ b/cursor-shape.c @@ -54,7 +54,7 @@ cursor_shape_to_server_shape(enum cursor_shape shape) } enum wp_cursor_shape_device_v1_shape -cursor_string_to_server_shape(const char *xcursor) +cursor_string_to_server_shape(const char *xcursor, int bound_version) { if (xcursor == NULL) return 0; @@ -94,9 +94,29 @@ cursor_string_to_server_shape(const char *xcursor) [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = {"all-scroll", "fleur"}, [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = {"zoom-in"}, [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = {"zoom-out"}, +#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION) /* 1.42 */ + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK] = {"dnd-ask"}, +#endif +#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION) /* 1.42 */ + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE] = {"all-resize"}, +#endif }; for (size_t i = 0; i < ALEN(table); i++) { +#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION) + if (i == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK && + bound_version < WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION) + { + continue; + } +#endif +#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION) + if (i == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE && + bound_version < WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION) + { + continue; + } +#endif for (size_t j = 0; j < ALEN(table[i]); j++) { if (table[i][j] != NULL && streq(xcursor, table[i][j])) { return i; diff --git a/cursor-shape.h b/cursor-shape.h index 110dbd2e..13690588 100644 --- a/cursor-shape.h +++ b/cursor-shape.h @@ -26,4 +26,4 @@ const char *const *cursor_shape_to_string(enum cursor_shape shape); enum wp_cursor_shape_device_v1_shape cursor_shape_to_server_shape( enum cursor_shape shape); enum wp_cursor_shape_device_v1_shape cursor_string_to_server_shape( - const char *xcursor); + const char *xcursor, int bound_version); diff --git a/render.c b/render.c index e0f32575..a41eee0c 100644 --- a/render.c +++ b/render.c @@ -4929,8 +4929,9 @@ render_xcursor_update(struct seat *seat) const enum wp_cursor_shape_device_v1_shape custom_shape = (shape == CURSOR_SHAPE_CUSTOM && xcursor != NULL - ? cursor_string_to_server_shape(xcursor) - : 0); + ? cursor_string_to_server_shape( + xcursor, seat->wayl->shape_manager_version) + : 0); if (shape != CURSOR_SHAPE_CUSTOM || custom_shape != 0) { xassert(custom_shape == 0 || shape == CURSOR_SHAPE_CUSTOM); diff --git a/terminal.c b/terminal.c index 18f3bc9f..6f66f65b 100644 --- a/terminal.c +++ b/terminal.c @@ -3571,7 +3571,9 @@ term_xcursor_update_for_seat(struct terminal *term, struct seat *seat) if (seat->pointer.hidden) shape = CURSOR_SHAPE_HIDDEN; - else if (cursor_string_to_server_shape(term->mouse_user_cursor) != 0 || + else if (cursor_string_to_server_shape( + term->mouse_user_cursor, + term->wl->shape_manager_version) != 0 || render_xcursor_is_valid(seat, term->mouse_user_cursor)) { shape = CURSOR_SHAPE_CUSTOM; diff --git a/wayland.c b/wayland.c index 37fefb29..7d3c7c67 100644 --- a/wayland.c +++ b/wayland.c @@ -1480,8 +1480,16 @@ handle_global(void *data, struct wl_registry *registry, if (!verify_iface_version(interface, version, required)) return; +#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION) /* 1.42 */ + const uint32_t preferred = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION; +#else + const uint32_t preferred = required; +#endif + + wayl->shape_manager_version = min(required, preferred); wayl->cursor_shape_manager = wl_registry_bind( - wayl->registry, name, &wp_cursor_shape_manager_v1_interface, required); + wayl->registry, name, &wp_cursor_shape_manager_v1_interface, + min(required, preferred)); } else if (streq(interface, wp_single_pixel_buffer_manager_v1_interface.name)) { diff --git a/wayland.h b/wayland.h index b7e8e79f..eb1c35a3 100644 --- a/wayland.h +++ b/wayland.h @@ -460,6 +460,7 @@ struct wayland { struct wp_fractional_scale_manager_v1 *fractional_scale_manager; struct wp_cursor_shape_manager_v1 *cursor_shape_manager; + int shape_manager_version; struct wp_single_pixel_buffer_manager_v1 *single_pixel_manager; From 5621829bb00deea6c187c0c328e2560b84f809d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 23 May 2025 13:31:53 +0200 Subject: [PATCH 237/353] cursor-shape: map "dnd-move" to WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE --- cursor-shape.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cursor-shape.c b/cursor-shape.c index 6e859259..c195a554 100644 --- a/cursor-shape.c +++ b/cursor-shape.c @@ -72,7 +72,7 @@ cursor_string_to_server_shape(const char *xcursor, int bound_version) [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT] = {"vertical-text"}, [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS] = {"alias", "dnd-link"}, [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY] = {"copy", "dnd-copy"}, - [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = {"move"}, /* dnd-move? */ + [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = {"move", "dnd-move"}, [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP] = {"no-drop", "dnd-no-drop"}, [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED] = {"not-allowed", "crossed_circle"}, [WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB] = {"grab", "hand1"}, From 5a84f8d841d09e9aa91befbbb8e8b634b7f8959a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 23 May 2025 08:38:00 +0200 Subject: [PATCH 238/353] conf: pad: add center-when-fullscreen and center-when-maximized-and-fullscreen Before this patch, the grid content was *always* centered when the window was maximized or fullscreened, regardless of how the user had configured padding. Now, the behavior is controlled by the 'pad' option. Before this patch, the syntax was pad MxN [center] Now it is pad MxN [center|center-when-fullscreen|center-when-maximized-and-fullscreen] The default is "pad 0x0 center-when-maximized-and-fullscreen", to match current behavior. Closes #2111 --- CHANGELOG.md | 4 ++++ config.c | 28 +++++++++++++++++++++------- config.h | 10 +++++++++- doc/foot.ini.5.scd | 27 +++++++++++++++++++-------- foot.ini | 2 +- render.c | 9 ++++++--- 6 files changed, 60 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66fa7c93..9ade3b6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,9 +91,13 @@ buffers. They provide the necessary color precision required by `gamma-correct-blending=yes`. * New cursor shapes, from `cursor-shape-v1` version 2. +* `center-when-fullscreen` and `center-when-maximized-and-fullscreen` + to the `pad` option. This allows you to configure when the grid is + centered in more detail ([#2111][2111]). [2025]: https://codeberg.org/dnkl/foot/issues/2025 [1975]: https://codeberg.org/dnkl/foot/issues/1975 +[2111]: https://codeberg.org/dnkl/foot/issues/2111 ### Changed diff --git a/config.c b/config.c index 07f781d6..d8f1c0ed 100644 --- a/config.c +++ b/config.c @@ -933,21 +933,34 @@ parse_section_main(struct context *ctx) else if (streq(key, "pad")) { unsigned x, y; - char mode[16] = {0}; + char mode[64] = {0}; + int ret = sscanf(value, "%ux%u %63s", &x, &y, mode); - int ret = sscanf(value, "%ux%u %15s", &x, &y, mode); - bool center = strcasecmp(mode, "center") == 0; - bool invalid_mode = !center && mode[0] != '\0'; + enum center_when center = CENTER_NEVER; - if ((ret != 2 && ret != 3) || invalid_mode) { + if (ret == 3) { + if (strcasecmp(mode, "center") == 0) + center = CENTER_ALWAYS; + else if (strcasecmp(mode, "center-when-fullscreen") == 0) + center = CENTER_FULLSCREEN; + else if (strcasecmp(mode, "center-when-maximized-and-fullscreen") == 0) + center = CENTER_MAXIMIZED_AND_FULLSCREEN; + else + center = CENTER_INVALID; + } + + if ((ret != 2 && ret != 3) || center == CENTER_INVALID) { LOG_CONTEXTUAL_ERR( - "invalid padding (must be in the form PAD_XxPAD_Y [center])"); + "invalid padding (must be in the form PAD_XxPAD_Y " + "[center|" + "center-when-fullscreen|" + "center-when-maximized-and-fullscreen])"); return false; } conf->pad_x = x; conf->pad_y = y; - conf->center = center; + conf->center_when = ret == 2 ? CENTER_NEVER : center; return true; } @@ -3339,6 +3352,7 @@ config_load(struct config *conf, const char *conf_path, }, .pad_x = 0, .pad_y = 0, + .center_when = CENTER_MAXIMIZED_AND_FULLSCREEN, .resize_by_cells = true, .resize_keep_grid = true, .resize_delay_ms = 100, diff --git a/config.h b/config.h index 7cf6f6f5..315f7e24 100644 --- a/config.h +++ b/config.h @@ -201,6 +201,14 @@ enum shm_bit_depth { SHM_BITS_16, }; +enum center_when { + CENTER_INVALID, + CENTER_NEVER, + CENTER_FULLSCREEN, + CENTER_MAXIMIZED_AND_FULLSCREEN, + CENTER_ALWAYS, +}; + struct config { char *term; char *shell; @@ -218,7 +226,7 @@ struct config { unsigned pad_x; unsigned pad_y; - bool center; + enum center_when center_when; bool resize_by_cells; bool resize_keep_grid; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 81b88f64..74b3f35b 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -289,18 +289,29 @@ empty string to be set, but it must be quoted: *KEY=""*) *pad* Padding between border and glyphs, in pixels (subject to output - scaling), in the form _XxY_. + scaling), in the form + + ``` + _XxY_ [center | center-when-fullscreen | center-when-maximized-and-fullscreen] + ``` This will add _at least_ X pixels on both the left and right - sides, and Y pixels on the top and bottom sides. The grid content - will be anchored in the top left corner. I.e. if the window - manager forces an odd window size on foot, the additional pixels - will be added to the right and bottom sides. + sides, and Y pixels on the top and bottom sides. - To instead center the grid content, append *center* (e.g. *pad=5x5 - center*). + When no centering is specified, the grid content is anchored to + the top left corner. I.e. if the window manager forces an odd + window size on foot, the additional pixels will be added to the + right and bottom sides. - Default: _0x0_. + If *center* is specified, the grid content is instead + centered. This may cause "jumpiness" when resizing the window. + + With *center-when-fullscreen* and + *center-when-maximized-and-fullscreen*, the grid is anchored to + the top left corner, unless the window is maximized, or + fullscreened. + + Default: _0x0_ center-when-maximized-and-fullscreen. *resize-delay-ms* diff --git a/foot.ini b/foot.ini index f3ef6d85..73fdb7ab 100644 --- a/foot.ini +++ b/foot.ini @@ -28,7 +28,7 @@ # initial-window-size-pixels=700x500 # Or, # initial-window-size-chars= # initial-window-mode=windowed -# pad=0x0 # optionally append 'center' +# pad=0x0 center-when-maximized-and-fullscreen # resize-by-cells=yes # resize-keep-grid=yes # resize-delay-ms=100 diff --git a/render.c b/render.c index a41eee0c..244152ef 100644 --- a/render.c +++ b/render.c @@ -4596,9 +4596,12 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) const int total_x_pad = term->width - grid_width; const int total_y_pad = term->height - grid_height; - const bool centered_padding = term->conf->center - || term->window->is_fullscreen - || term->window->is_maximized; + const enum center_when center = term->conf->center_when; + const bool centered_padding = + center == CENTER_ALWAYS || + (center == CENTER_MAXIMIZED_AND_FULLSCREEN && + (term->window->is_fullscreen || term->window->is_maximized)) || + (center == CENTER_FULLSCREEN && term->window->is_fullscreen); if (centered_padding && !term->window->is_resizing) { term->margins.left = total_x_pad / 2; From eeaecba7238fb3a501cb25b55b7d2ada2bc4d023 Mon Sep 17 00:00:00 2001 From: tokyo4j Date: Sat, 24 May 2025 19:06:29 +0900 Subject: [PATCH 239/353] wayland: fix global listener for xdg_toplevel_icon_manager_v1 --- wayland.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wayland.c b/wayland.c index 7d3c7c67..5f68ecf7 100644 --- a/wayland.c +++ b/wayland.c @@ -1502,13 +1502,13 @@ handle_global(void *data, struct wl_registry *registry, &wp_single_pixel_buffer_manager_v1_interface, required); } - else if (streq(interface, xdg_toplevel_icon_v1_interface.name)) { + else if (streq(interface, xdg_toplevel_icon_manager_v1_interface.name)) { const uint32_t required = 1; if (!verify_iface_version(interface, version, required)) return; wayl->toplevel_icon_manager = wl_registry_bind( - wayl->registry, name, &xdg_toplevel_icon_v1_interface, required); + wayl->registry, name, &xdg_toplevel_icon_manager_v1_interface, required); } else if (streq(interface, xdg_system_bell_v1_interface.name)) { From 7347f4beb1314df12e834a260f18feae8b775a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Jun 2025 07:08:24 +0200 Subject: [PATCH 240/353] quirks: remove subsurface unmap quirk for Sway Sway used to have an issue where unmapping a subsurface did not damage the surface below (https://github.com/swaywm/sway/issues/6960). This has been fixed for quite some time now, so let's remove the quirk. --- CHANGELOG.md | 5 +++++ quirks.c | 12 ++---------- quirks.h | 2 -- render.c | 11 +---------- url-mode.c | 4 ---- 5 files changed, 8 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ade3b6f..b048bda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,6 +123,11 @@ ### Removed + +* Subsurface unmap quirk for Sway. This was a workaround added in + 1.12.1, for Sway issue [#6960][sway-6960]. + + ### Fixed * `REP`: wrong width of repeated multi-codepoint graphemes. diff --git a/quirks.c b/quirks.c index 7cc8a8f1..67cb587e 100644 --- a/quirks.c +++ b/quirks.c @@ -67,6 +67,7 @@ quirk_weston_csd_off(struct terminal *term) quirk_weston_subsurface_desync_off(term->window->csd.surface[i].sub); } +#if 0 static bool is_sway(void) { @@ -82,13 +83,4 @@ is_sway(void) return is_sway; } - -void -quirk_sway_subsurface_unmap(struct terminal *term) -{ - return; - if (!is_sway()) - return; - - wl_surface_damage_buffer(term->window->surface.surf, 0, 0, INT32_MAX, INT32_MAX); -} +#endif diff --git a/quirks.h b/quirks.h index 0e840667..e762bb3e 100644 --- a/quirks.h +++ b/quirks.h @@ -21,5 +21,3 @@ 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); - -void quirk_sway_subsurface_unmap(struct terminal *term); diff --git a/render.c b/render.c index 244152ef..1c24bafa 100644 --- a/render.c +++ b/render.c @@ -1970,10 +1970,6 @@ render_overlay(struct terminal *term) wl_surface_commit(overlay->surface.surf); term->render.last_overlay_style = OVERLAY_NONE; term->render.last_overlay_buf = NULL; - - /* Work around Sway bug - unmapping a sub-surface does not - * damage the underlying surface */ - quirk_sway_subsurface_unmap(term); } return; } @@ -2919,13 +2915,8 @@ render_scrollback_position(struct terminal *term) struct wl_window *win = term->window; if (term->grid->view == term->grid->offset) { - if (win->scrollback_indicator.surface.surf != NULL) { + if (win->scrollback_indicator.surface.surf != NULL) wayl_win_subsurface_destroy(&win->scrollback_indicator); - - /* Work around Sway bug - unmapping a sub-surface does not damage - * the underlying surface */ - quirk_sway_subsurface_unmap(term); - } return; } diff --git a/url-mode.c b/url-mode.c index 199ff3f1..35843219 100644 --- a/url-mode.c +++ b/url-mode.c @@ -820,10 +820,6 @@ urls_reset(struct terminal *term) tll_foreach(term->window->urls, it) { wayl_win_subsurface_destroy(&it->item.surf); tll_remove(term->window->urls, it); - - /* Work around Sway bug - unmapping a sub-surface does not - * damage the underlying surface */ - quirk_sway_subsurface_unmap(term); } } From 33eefa7b45c2d20750f5788dfdcc1008cf33ca47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Jun 2025 07:37:29 +0200 Subject: [PATCH 241/353] term+input: refactor: move theme switching into term_theme_* functions --- input.c | 51 ++------------------------------- terminal.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++------ terminal.h | 4 ++- 3 files changed, 80 insertions(+), 58 deletions(-) diff --git a/input.c b/input.c index 271ffb88..fe90a7f0 100644 --- a/input.c +++ b/input.c @@ -486,60 +486,15 @@ execute_binding(struct seat *seat, struct terminal *term, return true; case BIND_ACTION_THEME_SWITCH_1: - if (term->colors.active_theme != COLOR_THEME1) { - term_theme_apply(term, &term->conf->colors); - term->colors.active_theme = COLOR_THEME1; - - wayl_win_alpha_changed(term->window); - term_font_subpixel_changed(term); - - if (term->report_theme_changes) - term_to_slave(term, "\033[?997;1n", 9); - - term_damage_view(term); - term_damage_margins(term); - render_refresh(term); - } + term_theme_switch_to_1(term); return true; case BIND_ACTION_THEME_SWITCH_2: - if (term->colors.active_theme != COLOR_THEME2) { - term_theme_apply(term, &term->conf->colors2); - term->colors.active_theme = COLOR_THEME2; - - wayl_win_alpha_changed(term->window); - term_font_subpixel_changed(term); - - if (term->report_theme_changes) - term_to_slave(term, "\033[?997;2n", 9); - - term_damage_view(term); - term_damage_margins(term); - render_refresh(term); - } + term_theme_switch_to_2(term); return true; case BIND_ACTION_THEME_TOGGLE: - if (term->colors.active_theme == COLOR_THEME1) { - term_theme_apply(term, &term->conf->colors2); - term->colors.active_theme = COLOR_THEME2; - - if (term->report_theme_changes) - term_to_slave(term, "\033[?997;2n", 9); - } else { - term_theme_apply(term, &term->conf->colors); - term->colors.active_theme = COLOR_THEME1; - - if (term->report_theme_changes) - term_to_slave(term, "\033[?997;1n", 9); - } - - wayl_win_alpha_changed(term->window); - term_font_subpixel_changed(term); - - term_damage_view(term); - term_damage_margins(term); - render_refresh(term); + term_theme_toggle(term); return true; case BIND_ACTION_SELECT_BEGIN: diff --git a/terminal.c b/terminal.c index 6f66f65b..135f21ce 100644 --- a/terminal.c +++ b/terminal.c @@ -2074,6 +2074,19 @@ erase_line(struct terminal *term, struct row *row) row->shell_integration.cmd_end = -1; } +static void +term_theme_apply(struct terminal *term, const struct color_theme *theme) +{ + term->colors.fg = theme->fg; + term->colors.bg = theme->bg; + term->colors.alpha = theme->alpha; + term->colors.cursor_fg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.text; + term->colors.cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor; + term->colors.selection_fg = theme->selection_fg; + term->colors.selection_bg = theme->selection_bg; + memcpy(term->colors.table, theme->table, sizeof(term->colors.table)); +} + void term_reset(struct terminal *term, bool hard) { @@ -4704,14 +4717,66 @@ term_send_size_notification(struct terminal *term) } void -term_theme_apply(struct terminal *term, const struct color_theme *theme) +term_theme_switch_to_1(struct terminal *term) { - term->colors.fg = theme->fg; - term->colors.bg = theme->bg; - term->colors.alpha = theme->alpha; - term->colors.cursor_fg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.text; - term->colors.cursor_bg = (theme->use_custom.cursor ? 1u << 31 : 0) | theme->cursor.cursor; - term->colors.selection_fg = theme->selection_fg; - term->colors.selection_bg = theme->selection_bg; - memcpy(term->colors.table, theme->table, sizeof(term->colors.table)); + if (term->colors.active_theme == COLOR_THEME1) + return; + + term_theme_apply(term, &term->conf->colors); + term->colors.active_theme = COLOR_THEME1; + + wayl_win_alpha_changed(term->window); + term_font_subpixel_changed(term); + + if (term->report_theme_changes) + term_to_slave(term, "\033[?997;1n", 9); + + term_damage_view(term); + term_damage_margins(term); + render_refresh(term); +} + +void +term_theme_switch_to_2(struct terminal *term) +{ + if (term->colors.active_theme == COLOR_THEME2) + return; + + term_theme_apply(term, &term->conf->colors2); + term->colors.active_theme = COLOR_THEME2; + + wayl_win_alpha_changed(term->window); + term_font_subpixel_changed(term); + + if (term->report_theme_changes) + term_to_slave(term, "\033[?997;2n", 9); + + term_damage_view(term); + term_damage_margins(term); + render_refresh(term); +} + +void +term_theme_toggle(struct terminal *term) +{ + if (term->colors.active_theme == COLOR_THEME1) { + term_theme_apply(term, &term->conf->colors2); + term->colors.active_theme = COLOR_THEME2; + + if (term->report_theme_changes) + term_to_slave(term, "\033[?997;2n", 9); + } else { + term_theme_apply(term, &term->conf->colors); + term->colors.active_theme = COLOR_THEME1; + + if (term->report_theme_changes) + term_to_slave(term, "\033[?997;1n", 9); + } + + wayl_win_alpha_changed(term->window); + term_font_subpixel_changed(term); + + term_damage_view(term); + term_damage_margins(term); + render_refresh(term); } diff --git a/terminal.h b/terminal.h index 3122cef3..88371b07 100644 --- a/terminal.h +++ b/terminal.h @@ -984,7 +984,9 @@ void term_enable_size_notifications(struct terminal *term); void term_disable_size_notifications(struct terminal *term); void term_send_size_notification(struct terminal *term); -void term_theme_apply(struct terminal *term, const struct color_theme *theme); +void term_theme_switch_to_1(struct terminal *term); +void term_theme_switch_to_2(struct terminal *term); +void term_theme_toggle(struct terminal *term); static inline void term_reset_grapheme_state(struct terminal *term) { From d9675a714016553264ae3b77fee785c6c49d2b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Jun 2025 07:38:26 +0200 Subject: [PATCH 242/353] main: do a theme toggle upon receiving SIGUSR1 Caveat: in server mode, *all* instances toggle their themes. --- CHANGELOG.md | 2 ++ main.c | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b048bda3..82b4238b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,8 @@ `key-bindings.color-theme-toggle` key bindings. These can be used to switch between the primary and alternative color themes. They are not bound by default. +* Sending `SIGUSR1` to the foot process now triggers a theme switch + (in server mode, **all** instances toggles their themes). * Support for private mode 2031 - [_Dark and Light Mode Detection_](https://contour-terminal.org/vt-extensions/color-palette-update-notifications/) ([#2025][2025]) diff --git a/main.c b/main.c index b9404503..37bbb6a7 100644 --- a/main.c +++ b/main.c @@ -45,6 +45,19 @@ fdm_sigint(struct fdm *fdm, int signo, void *data) return true; } +static bool +fdm_sigusr1(struct fdm *fdm, int signo, void *data) +{ + struct wayland *wayl = data; + + tll_foreach(wayl->terms, it) { + struct terminal *term = it->item; + term_theme_toggle(term); + } + + return true; +} + static void print_usage(const char *prog_name) { @@ -608,6 +621,9 @@ main(int argc, char *const *argv) goto out; } + if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr1, wayl)) + goto out; + struct sigaction sig_ign = {.sa_handler = SIG_IGN}; sigemptyset(&sig_ign.sa_mask); if (sigaction(SIGHUP, &sig_ign, NULL) < 0 || @@ -643,6 +659,7 @@ out: wayl_destroy(wayl); key_binding_manager_destroy(key_binding_manager); reaper_destroy(reaper); + fdm_signal_del(fdm, SIGUSR1); fdm_signal_del(fdm, SIGTERM); fdm_signal_del(fdm, SIGINT); fdm_destroy(fdm); From 499f019deaf6e7b7e7bb6bbb43b93a7296d9b4fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Jun 2025 09:12:08 +0200 Subject: [PATCH 243/353] osc: 52: clear selection if the payload is the empty string --- CHANGELOG.md | 1 + osc.c | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b4238b..329a7650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,7 @@ only ([#1846][1846]). * Drop required version of libxkbcommon from 1.8.0 back to 1.0.0 ([#2103][2103]). +* OSC-52: an empty payload now clears the clipboard. [1846]: https://codeberg.org/dnkl/foot/issues/1846 [2103]: https://codeberg.org/dnkl/foot/issues/2103 diff --git a/osc.c b/osc.c index d59adc5a..834029c1 100644 --- a/osc.c +++ b/osc.c @@ -73,16 +73,19 @@ osc_to_clipboard(struct terminal *term, const char *target, } char *decoded = base64_decode(base64_data, NULL); - if (decoded == NULL) { - if (errno == EINVAL) - LOG_WARN("OSC: invalid clipboard data: %s", base64_data); - else - LOG_ERRNO("base64_decode() failed"); + if (decoded == NULL || decoded[0] == '\0') { + if (decoded == NULL) { + if (errno == EINVAL) + LOG_WARN("OSC: invalid clipboard data: %s", base64_data); + else + LOG_ERRNO("base64_decode() failed"); + } if (to_clipboard) selection_clipboard_unset(seat); if (to_primary) selection_primary_unset(seat); + free(decoded); return; } From 968bc05c32a2e68282fd28e840b06ac63556d82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 9 Jun 2025 09:19:07 +0200 Subject: [PATCH 244/353] csi: add '52' to the DA reply, to indicate PSC-52 support Note: only *copy* is required to be enabled in security.osc52; paste is optional, see https://github.com/contour-terminal/contour/issues/1761#issuecomment-2944492097 --- CHANGELOG.md | 3 +++ csi.c | 17 ++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 329a7650..5a6cf6a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,9 @@ * Drop required version of libxkbcommon from 1.8.0 back to 1.0.0 ([#2103][2103]). * OSC-52: an empty payload now clears the clipboard. +* DA (Device Attributes): include `52` in the reply, to indicate + OSC-52 support (when at least _copy_ has been enabled in + `security.osc52`). [1846]: https://codeberg.org/dnkl/foot/issues/1846 [2103]: https://codeberg.org/dnkl/foot/issues/2103 diff --git a/csi.c b/csi.c index 6d4845be..437fd8bc 100644 --- a/csi.c +++ b/csi.c @@ -850,6 +850,7 @@ csi_dispatch(struct terminal *term, uint8_t final) * - 22 ANSI color, e.g., VT525. * - 28 Rectangular editing. * - 29 ANSI text locator (i.e., DEC Locator mode). + * - 52 Clipboard access * * Note: we report ourselves as a VT220, mainly to be able * to pass parameters, to indicate we support sixel, and @@ -860,13 +861,15 @@ csi_dispatch(struct terminal *term, uint8_t final) * * Note: tertiary DA responds with "FOOT". */ - if (term->conf->tweak.sixel) { - static const char reply[] = "\033[?62;4;22;28c"; - term_to_slave(term, reply, sizeof(reply) - 1); - } else { - static const char reply[] = "\033[?62;22;28c"; - term_to_slave(term, reply, sizeof(reply) - 1); - } + char reply[32]; + + int len = snprintf( + reply, sizeof(reply), "\033[?62%s;22;28%sc", + term->conf->tweak.sixel ? ";4" : "", + (term->conf->security.osc52 == OSC52_ENABLED || + term->conf->security.osc52 == OSC52_COPY_ENABLED ? ";52" : "")); + + term_to_slave(term, reply, len); break; } From aa579acd6ecd76941ca5bb8821396cf9c06ca8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 11 Jul 2025 16:30:18 +0200 Subject: [PATCH 245/353] issue template: compositor version -> compositor name and version The existing hints and descriptions are apparently not enough; some people still only mention the version, which is rather useless. --- .forgejo/issue_template/bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/issue_template/bug.yml b/.forgejo/issue_template/bug.yml index a5000090..921bd68f 100644 --- a/.forgejo/issue_template/bug.yml +++ b/.forgejo/issue_template/bug.yml @@ -29,7 +29,7 @@ body: - type: input id: compositor attributes: - label: Compositor Version + label: Compositor Name and Version description: "The name and version of your compositor" placeholder: "sway version 1.9" validations: From 693aefa96a74b0e4b9117a0c4ffe8c95b35b0c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 11 Jul 2025 16:47:51 +0200 Subject: [PATCH 246/353] config: silence valgrind-detected leak in config_font_parse() --- config.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.c b/config.c index d8f1c0ed..77dc3a73 100644 --- a/config.c +++ b/config.c @@ -3956,9 +3956,10 @@ config_font_parse(const char *pattern, struct config_font *font) * both "size" and "pixelsize" being set, and we don't know * which one takes priority. */ + FcConfig *fc_conf = FcConfigCreate(); FcPattern *pat_copy = FcPatternDuplicate(pat); if (pat_copy == NULL || - !FcConfigSubstitute(NULL, pat_copy, FcMatchPattern)) + !FcConfigSubstitute(fc_conf, pat_copy, FcMatchPattern)) { LOG_WARN("%s: failed to do config substitution", pattern); } else { @@ -3967,6 +3968,7 @@ config_font_parse(const char *pattern, struct config_font *font) } FcPatternDestroy(pat_copy); + FcConfigDestroy(fc_conf); if (have_pt_size != FcResultMatch && have_px_size != FcResultMatch) pt_size = 8.0; From e72e08625d9f085755f476e6deb327de913031c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 16 Jul 2025 08:14:54 +0200 Subject: [PATCH 247/353] changelog: prepare for 1.23.0 --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a6cf6a9..8985905e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.23.0](#1-23-0) * [1.22.3](#1-22-3) * [1.22.2](#1-22-2) * [1.22.1](#1-22-1) @@ -63,7 +63,8 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.23.0 + ### Added * `colors2` config section. This section duplicates the `colors` @@ -143,9 +144,13 @@ [2105]: https://codeberg.org/dnkl/foot/issues/2105 -### Security ### Contributors +* Chen Mulong +* Kirill Primak +* Ryan Roden-Corrent +* tokyo4j + ## 1.22.3 From d62bff1440472f33b5350b3146f691ce906a3588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 16 Jul 2025 08:15:34 +0200 Subject: [PATCH 248/353] meson: bump to 1.23.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7b9490d9..b9994c11 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.22.3', + version: '1.23.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 692b22cbbb24efa91cb24446d189bc8c2535c1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 16 Jul 2025 08:31:42 +0200 Subject: [PATCH 249/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8985905e..6121955f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.23.0](#1-23-0) * [1.22.3](#1-22-3) * [1.22.2](#1-22-2) @@ -63,6 +64,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.23.0 ### Added From cc290fa9b0c36e90f6af978f839c083d05f22a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 17 Jul 2025 10:40:20 +0200 Subject: [PATCH 250/353] url-mode: assign label keys in reverse order The _last_ URL is often the one you are interested in, and with this change, it is always assigned the first (and thus the same) key. Closes #2140 --- CHANGELOG.md | 8 ++++++++ url-mode.c | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6121955f..df52e27d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,14 @@ ## Unreleased ### Added ### Changed + +* URL labels are now assigned in reverse order, from bottom to + top. This ensures the **last** URL (which is often the one you are + interested in) is always assigned the same key ([#2140][2140]). + +[2140]: https://codeberg.org/dnkl/foot/issues/2140 + + ### Deprecated ### Removed ### Fixed diff --git a/url-mode.c b/url-mode.c index 35843219..c25396cd 100644 --- a/url-mode.c +++ b/url-mode.c @@ -634,12 +634,12 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) size_t combo_idx = 0; - tll_foreach(*urls, it) { + tll_rforeach(*urls, it) { bool id_already_seen = false; /* Look for already processed URLs where both the URI and the * ID matches */ - tll_foreach(*urls, it2) { + tll_rforeach(*urls, it2) { if (&it->item == &it2->item) break; @@ -659,7 +659,7 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) * them; if so, reuse the *same* key combo. */ bool url_already_seen = false; - tll_foreach(*urls, it2) { + tll_rforeach(*urls, it2) { if (&it->item == &it2->item) break; @@ -679,7 +679,7 @@ urls_assign_key_combos(const struct config *conf, url_list_t *urls) free(combos[i]); #if defined(_DEBUG) && LOG_ENABLE_DBG - tll_foreach(*urls, it) { + tll_rforeach(*urls, it) { if (it->item.key == NULL) continue; From 01387f9593294f4eb467a162f245e3c712635f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 17 Jul 2025 10:18:17 +0200 Subject: [PATCH 251/353] main: SIGUSR1 selects the first color theme, SIGUSR2 the second Before this patch, SIGUSR1 toggled between [colors] and [colors2]. Now, SIGUSR1 changes to [colors], regardless of what the current color theme is, and SIGUSR2 changes to [colors2]. Closes #2144 --- CHANGELOG.md | 4 ++++ main.c | 22 +++++++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df52e27d..af7fe73a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,8 +71,12 @@ * URL labels are now assigned in reverse order, from bottom to top. This ensures the **last** URL (which is often the one you are interested in) is always assigned the same key ([#2140][2140]). +* Sending `SIGUSR1` no longer **toggles** between `[colors]` and + `[colors2]`, but explicitly changes to `[colors]`. `SIGUSR2` changes + to `[colors2]` ([#2144][2144]). [2140]: https://codeberg.org/dnkl/foot/issues/2140 +[2144]: https://codeberg.org/dnkl/foot/issues/2144 ### Deprecated diff --git a/main.c b/main.c index 37bbb6a7..517d8460 100644 --- a/main.c +++ b/main.c @@ -46,13 +46,22 @@ fdm_sigint(struct fdm *fdm, int signo, void *data) } static bool -fdm_sigusr1(struct fdm *fdm, int signo, void *data) +fdm_sigusr(struct fdm *fdm, int signo, void *data) { struct wayland *wayl = data; - tll_foreach(wayl->terms, it) { - struct terminal *term = it->item; - term_theme_toggle(term); + xassert(signo == SIGUSR1 || signo == SIGUSR2); + + if (signo == SIGUSR1) { + tll_foreach(wayl->terms, it) { + struct terminal *term = it->item; + term_theme_switch_to_1(term); + } + } else { + tll_foreach(wayl->terms, it) { + struct terminal *term = it->item; + term_theme_switch_to_2(term); + } } return true; @@ -621,8 +630,11 @@ main(int argc, char *const *argv) goto out; } - if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr1, wayl)) + if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr, wayl) || + !fdm_signal_add(fdm, SIGUSR2, &fdm_sigusr, wayl)) + { goto out; + } struct sigaction sig_ign = {.sa_handler = SIG_IGN}; sigemptyset(&sig_ign.sa_mask); From 57ae3bb89c6bf50e70e831e3658e59a6877e9817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 18 Jul 2025 17:24:18 +0200 Subject: [PATCH 252/353] main: unregister SIGUSR2 on exit --- main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/main.c b/main.c index 517d8460..f97f21b5 100644 --- a/main.c +++ b/main.c @@ -672,6 +672,7 @@ out: key_binding_manager_destroy(key_binding_manager); reaper_destroy(reaper); fdm_signal_del(fdm, SIGUSR1); + fdm_signal_del(fdm, SIGUSR2); fdm_signal_del(fdm, SIGTERM); fdm_signal_del(fdm, SIGINT); fdm_destroy(fdm); From 7ab43ebf7464ab72e2041de0a1112feaa81ea89e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 21 Jul 2025 13:49:57 +0200 Subject: [PATCH 253/353] shm: don't set pixman_fmt_without_alpha twice When selecting 16-bit surfaces, we set pixman_fmt_without_alpha twice, and never set pixman_fmt_with_alpha. This caused 10-bit surfaces to be used instead, since it checks if pixman_fmt_with_alpha has been overridden or not. --- CHANGELOG.md | 4 ++++ shm.c | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af7fe73a..cfbbde53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,10 @@ ### Deprecated ### Removed ### Fixed + +* 10-bit surfaces sometimes used instead of 16-bit. + + ### Security ### Contributors diff --git a/shm.c b/shm.c index b586b504..4680c12c 100644 --- a/shm.c +++ b/shm.c @@ -994,7 +994,7 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, pixman_fmt_without_alpha = PIXMAN_a16b16g16r16; shm_fmt_without_alpha = WL_SHM_FORMAT_XBGR16161616; - pixman_fmt_without_alpha = PIXMAN_a16b16g16r16; + pixman_fmt_with_alpha = PIXMAN_a16b16g16r16; shm_fmt_with_alpha = WL_SHM_FORMAT_ABGR16161616; if (!have_logged) { From 21db6a6cdc68a5a8b9561d07d3ef69e11d2a077e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 21 Jul 2025 15:28:52 +0200 Subject: [PATCH 254/353] fdm: when logging signal related errors, include the signal name Since sigabbrev_np() is GNU only, provide a fallback function that returns "SIG" when sigabbrev_np() doesn't exist (for example, on FreeBSD). --- fdm.c | 30 ++++++++++++++++++++++++------ meson.build | 6 ++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/fdm.c b/fdm.c index ea30443b..4822cd97 100644 --- a/fdm.c +++ b/fdm.c @@ -18,6 +18,18 @@ #include "debug.h" #include "xmalloc.h" +#if !defined(SIGABBREV_NP) +#include + +static const char * +sigabbrev_np(int sig) +{ + static char buf[16]; + snprintf(buf, sizeof(buf), "<%d>", sig); + return buf; +} +#endif + struct fd_handler { int fd; int events; @@ -113,7 +125,8 @@ fdm_destroy(struct fdm *fdm) for (int i = 0; i < SIGRTMAX; i++) { if (fdm->signal_handlers[i].callback != NULL) - LOG_WARN("handler for signal %d not removed", i); + LOG_WARN("handler for signal %d (SIG%s) not removed", + i, sigabbrev_np(i)); } if (tll_length(fdm->hooks_low) > 0 || @@ -338,7 +351,8 @@ bool fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data) { if (fdm->signal_handlers[signo].callback != NULL) { - LOG_ERR("signal %d already has a handler", signo); + LOG_ERR("signal %d (SIG%s) already has a handler", + signo, sigabbrev_np(signo)); return false; } @@ -347,14 +361,16 @@ fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *d sigaddset(&mask, signo); if (sigprocmask(SIG_BLOCK, &mask, &original) < 0) { - LOG_ERRNO("failed to block signal %d", signo); + LOG_ERRNO("failed to block signal %d (SIG%s)", + signo, sigabbrev_np(signo)); return false; } struct sigaction action = {.sa_handler = &signal_handler}; sigemptyset(&action.sa_mask); if (sigaction(signo, &action, NULL) < 0) { - LOG_ERRNO("failed to set signal handler for signal %d", signo); + LOG_ERRNO("failed to set signal handler for signal %d (SIG%s)", + signo, sigabbrev_np(signo)); sigprocmask(SIG_SETMASK, &original, NULL); return false; } @@ -374,7 +390,8 @@ fdm_signal_del(struct fdm *fdm, int signo) struct sigaction action = {.sa_handler = SIG_DFL}; sigemptyset(&action.sa_mask); if (sigaction(signo, &action, NULL) < 0) { - LOG_ERRNO("failed to restore signal handler for signal %d", signo); + LOG_ERRNO("failed to restore signal handler for signal %d (SIG%s)", + signo, sigabbrev_np(signo)); return false; } @@ -386,7 +403,8 @@ fdm_signal_del(struct fdm *fdm, int signo) sigemptyset(&mask); sigaddset(&mask, signo); if (sigprocmask(SIG_UNBLOCK, &mask, NULL) < 0) { - LOG_ERRNO("failed to unblock signal %d", signo); + LOG_ERRNO("failed to unblock signal %d (SIG%s)", + signo, sigabbrev_np(signo)); return false; } diff --git a/meson.build b/meson.build index b9994c11..8a072dcf 100644 --- a/meson.build +++ b/meson.build @@ -25,6 +25,12 @@ if cc.has_function('execvpe', add_project_arguments('-DEXECVPE', language: 'c') endif +if cc.has_function('sigabbrev_np', + args: ['-D_GNU_SOURCE'], + prefix: '#include ') + add_project_arguments('-DSIGABBREV_NP', language: 'c') +endif + utmp_backend = get_option('utmp-backend') if utmp_backend == 'auto' host_os = host_machine.system() From 42be74214a0b2cd1b4245b262c99cbd5030a0e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 22 Jul 2025 13:30:00 +0200 Subject: [PATCH 255/353] term: make sure the color table is populated *before* the slave process is spawned --- terminal.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index 135f21ce..2e23f749 100644 --- a/terminal.c +++ b/terminal.c @@ -1409,6 +1409,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, pixman_region32_init(&term->render.last_overlay_clip); term_update_ascii_printer(term); + memcpy(term->colors.table, theme->table, sizeof(term->colors.table)); for (size_t i = 0; i < 4; i++) { const struct config_font_list *font_list = &conf->fonts[i]; @@ -1443,8 +1444,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, xassert(tll_length(term->wl->monitors) > 0); term->scale = tll_front(term->wl->monitors).scale; - memcpy(term->colors.table, theme->table, sizeof(term->colors.table)); - /* Initialize the Wayland window backend */ if ((term->window = wayl_win_init(term, token)) == NULL) goto err; From fcde74a18150a1722f2be2b7990c2f9b45268854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 22 Jul 2025 13:30:28 +0200 Subject: [PATCH 256/353] osc: color reset: read default color from currently active theme --- CHANGELOG.md | 2 ++ osc.c | 69 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfbbde53..96040e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,8 @@ ### Fixed * 10-bit surfaces sometimes used instead of 16-bit. +* OSC-104/110/111/112/117/119 (reset colors) not taking the currently + active theme into account. ### Security diff --git a/osc.c b/osc.c index 834029c1..0b492564 100644 --- a/osc.c +++ b/osc.c @@ -1515,10 +1515,14 @@ osc_dispatch(struct terminal *term) case 104: { /* Reset Color Number 'c' (whole table if no parameter) */ + const struct color_theme *theme = + term->colors.active_theme == COLOR_THEME1 + ? &term->conf->colors + : &term->conf->colors2; + if (string[0] == '\0') { LOG_DBG("resetting all colors"); - for (size_t i = 0; i < ALEN(term->colors.table); i++) - term->colors.table[i] = term->conf->colors.table[i]; + memcpy(term->colors.table, theme->table, sizeof(term->colors.table)); term_damage_view(term); } @@ -1540,7 +1544,7 @@ osc_dispatch(struct terminal *term) } LOG_DBG("resetting color #%u", idx); - term->colors.table[idx] = term->conf->colors.table[idx]; + term->colors.table[idx] = theme->table[idx]; term_damage_color(term, COLOR_BASE256, idx); } @@ -1553,16 +1557,28 @@ osc_dispatch(struct terminal *term) case 110: /* Reset default text foreground color */ LOG_DBG("resetting foreground color"); - term->colors.fg = term->conf->colors.fg; + + const struct color_theme *theme = + term->colors.active_theme == COLOR_THEME1 + ? &term->conf->colors + : &term->conf->colors2; + + term->colors.fg = theme->fg; term_damage_color(term, COLOR_DEFAULT, 0); break; case 111: { /* Reset default text background color */ LOG_DBG("resetting background color"); - bool alpha_changed = term->colors.alpha != term->conf->colors.alpha; - term->colors.bg = term->conf->colors.bg; - term->colors.alpha = term->conf->colors.alpha; + const struct color_theme *theme = + term->colors.active_theme == COLOR_THEME1 + ? &term->conf->colors + : &term->conf->colors2; + + bool alpha_changed = term->colors.alpha != theme->alpha; + + term->colors.bg = theme->bg; + term->colors.alpha = theme->alpha; if (alpha_changed) { wayl_win_alpha_changed(term->window); @@ -1574,10 +1590,16 @@ osc_dispatch(struct terminal *term) break; } - case 112: + case 112: { LOG_DBG("resetting cursor color"); - term->colors.cursor_fg = term->conf->colors.cursor.text; - term->colors.cursor_bg = term->conf->colors.cursor.cursor; + + const struct color_theme *theme = + term->colors.active_theme == COLOR_THEME1 + ? &term->conf->colors + : &term->conf->colors2; + + term->colors.cursor_fg = theme->cursor.text; + term->colors.cursor_bg = theme->cursor.cursor; if (term->conf->colors.use_custom.cursor) { term->colors.cursor_fg |= 1u << 31; @@ -1586,16 +1608,31 @@ osc_dispatch(struct terminal *term) term_damage_cursor(term); break; + } - case 117: + case 117: { LOG_DBG("resetting selection background color"); - term->colors.selection_bg = term->conf->colors.selection_bg; - break; - case 119: - LOG_DBG("resetting selection foreground color"); - term->colors.selection_fg = term->conf->colors.selection_fg; + const struct color_theme *theme = + term->colors.active_theme == COLOR_THEME1 + ? &term->conf->colors + : &term->conf->colors2; + + term->colors.selection_bg = theme->selection_bg; break; + } + + case 119: { + LOG_DBG("resetting selection foreground color"); + + const struct color_theme *theme = + term->colors.active_theme == COLOR_THEME1 + ? &term->conf->colors + : &term->conf->colors2; + + term->colors.selection_fg = theme->selection_fg; + break; + } case 133: /* From 95e8b18c125c1ad2835ae59204dd0f8b5feaf64b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 23 Jul 2025 08:27:59 +0200 Subject: [PATCH 257/353] changelog: prepare for 1.23.1 --- CHANGELOG.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96040e86..6d720f0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.23.1](#1-23-1) * [1.23.0](#1-23-0) * [1.22.3](#1-22-3) * [1.22.2](#1-22-2) @@ -64,8 +64,8 @@ * [1.2.0](#1-2-0) -## Unreleased -### Added +## 1.23.1 + ### Changed * URL labels are now assigned in reverse order, from bottom to @@ -79,8 +79,6 @@ [2144]: https://codeberg.org/dnkl/foot/issues/2144 -### Deprecated -### Removed ### Fixed * 10-bit surfaces sometimes used instead of 16-bit. @@ -88,10 +86,6 @@ active theme into account. -### Security -### Contributors - - ## 1.23.0 ### Added From 43620935a169c03ab5744d12f5917269ba92567e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 23 Jul 2025 08:28:13 +0200 Subject: [PATCH 258/353] meson: bump version to 1.23.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 8a072dcf..0b7bbc17 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.23.0', + version: '1.23.1', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 86d63f08ba118f0051b6941353d0a888f4987d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 23 Jul 2025 08:31:30 +0200 Subject: [PATCH 259/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d720f0a..c628155c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.23.1](#1-23-1) * [1.23.0](#1-23-0) * [1.22.3](#1-22-3) @@ -64,6 +65,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.23.1 ### Changed From f873aa904db36ed9477deaba5ee2f4906c4d7831 Mon Sep 17 00:00:00 2001 From: Tobias Mock Date: Mon, 21 Jul 2025 23:28:02 +0200 Subject: [PATCH 260/353] Add tinted variant of modus-vivendi theme --- themes/modus-vivendi-tinted | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 themes/modus-vivendi-tinted diff --git a/themes/modus-vivendi-tinted b/themes/modus-vivendi-tinted new file mode 100644 index 00000000..67cf02a0 --- /dev/null +++ b/themes/modus-vivendi-tinted @@ -0,0 +1,25 @@ +# -*- conf -*- +# +# modus-vivendi-tinted +# See: https://protesilaos.com/emacs/modus-themes +# + +[colors] +background=0d0e1c +foreground=ffffff +regular0=000000 +regular1=ff5f59 +regular2=44bc44 +regular3=d0bc00 +regular4=2fafff +regular5=feacd0 +regular6=00d3d0 +regular7=a6a6a6 +bright0=595959 +bright1=ff6b55 +bright2=ff6b55 +bright3=fec43f +bright4=fec43f +bright5=b6a0ff +bright6=6ae4b9 +bright7=777777 From 83303bd2a461b5917de51d8740999cbdb46e7986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 29 Jul 2025 11:18:49 +0200 Subject: [PATCH 261/353] url-mode: for some reason we sorted the label letters before assigning them Don't do this. Now that we **don't** sort them, the first letter chosen by the user is always assigned to the bottom most URL. Closes #2140 (again) --- CHANGELOG.md | 5 +++++ url-mode.c | 12 ------------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c628155c..e405459e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,11 @@ ## Unreleased ### Added ### Changed + +* The label letters are no longer sorted before being assigned to URLs + ([#2140]2140[]). + + ### Deprecated ### Removed ### Fixed diff --git a/url-mode.c b/url-mode.c index c25396cd..19d95356 100644 --- a/url-mode.c +++ b/url-mode.c @@ -557,14 +557,6 @@ urls_collect(const struct terminal *term, enum url_action action, remove_overlapping(urls, term->grid->num_cols); } -static int -c32cmp_qsort_wrapper(const void *_a, const void *_b) -{ - const char32_t *a = *(const char32_t **)_a; - const char32_t *b = *(const char32_t **)_b; - return c32cmp(a, b); -} - static void generate_key_combos(const struct config *conf, size_t count, char32_t *combos[static count]) @@ -607,10 +599,6 @@ generate_key_combos(const struct config *conf, } free(hints); - /* Sorting is a kind of shuffle, since we're sorting on the - * *reversed* strings */ - qsort(combos, count, sizeof(char32_t *), &c32cmp_qsort_wrapper); - /* Reverse all strings */ for (size_t i = 0; i < count; i++) { const size_t len = c32len(combos[i]); From 7636f264a818eb11b155be051d012a616984a5a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 26 Jul 2025 12:21:51 +0200 Subject: [PATCH 262/353] slave: remove more environment variables set by other terminals This ensures applications don't mistake foot for another terminal emulator. Not that applications _should_ rely on environment variables, but some do anyway... --- slave.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/slave.c b/slave.c index 47e59e87..62899372 100644 --- a/slave.c +++ b/slave.c @@ -436,8 +436,54 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv, add_to_env(&custom_env, "COLORTERM", "truecolor"); add_to_env(&custom_env, "PWD", cwd); - del_from_env(&custom_env, "TERM_PROGRAM"); - del_from_env(&custom_env, "TERM_PROGRAM_VERSION"); + del_from_env(&custom_env, "TERM_PROGRAM"); /* Wezterm, Ghostty */ + del_from_env(&custom_env, "TERM_PROGRAM_VERSION"); /* Wezterm, Ghostty */ + del_from_env(&custom_env, "TERMINAL_NAME"); /* Contour */ + del_from_env(&custom_env, "TERMINAL_VERSION_STRING"); /* Contour */ + del_from_env(&custom_env, "TERMINAL_VERSION_TRIPLE"); /* Contour */ + + /* XTerm specific */ + del_from_env(&custom_env, "XTERM_SHELL"); + del_from_env(&custom_env, "XTERM_VERSION"); + del_from_env(&custom_env, "XTERM_LOCALE"); + + /* Mlterm specific */ + del_from_env(&custom_env, "MLTERM"); + + /* Zutty specific */ + del_from_env(&custom_env, "ZUTTY_VERSION"); + + /* Ghostty specific */ + del_from_env(&custom_env, "GHOSTTY_BIN_DIR"); + del_from_env(&custom_env, "GHOSTTY_SHELL_INTEGRATION_NO_SUDO"); + del_from_env(&custom_env, "GHOSTTY_RESOURCES_DIR"); + + /* Kitty specific */ + del_from_env(&custom_env, "KITTY_WINDOW_ID"); + del_from_env(&custom_env, "KITTY_PID"); + del_from_env(&custom_env, "KITTY_PUBLIC_KEY"); + del_from_env(&custom_env, "KITTY_INSTALLATION_DIR"); + + /* Contour specific */ + del_from_env(&custom_env, "CONTOUR_PROFILE"); + + /* Wezterm specific */ + del_from_env(&custom_env, "WEZTERM_PANE"); + del_from_env(&custom_env, "WEZTERM_EXECUTABLE"); + del_from_env(&custom_env, "WEZTERM_CONFIG_FILE"); + del_from_env(&custom_env, "WEZTERM_EXECUTABLE_DIR"); + del_from_env(&custom_env, "WEZTERM_UNIX_SOCKET"); + del_from_env(&custom_env, "WEZTERM_CONFIG_DIR"); + + /* Alacritty specific */ + del_from_env(&custom_env, "ALACRITTY_LOG"); + del_from_env(&custom_env, "ALACRITTY_WINDOW_ID"); + del_from_env(&custom_env, "ALACRITTY_SOCKET"); + + /* VTE, gnome-terminal, kgx etc */ + del_from_env(&custom_env, "VTE_VERSION"); + del_from_env(&custom_env, "GNOME_TERMINAL_SERVICE"); + del_from_env(&custom_env, "GNOME_TERMINAL_SCREEN"); #if defined(FOOT_TERMINFO_PATH) add_to_env(&custom_env, "TERMINFO", FOOT_TERMINFO_PATH); From 6eedc88d70520456fbb89db2600e762e722fe313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 30 Jul 2025 12:23:39 +0200 Subject: [PATCH 263/353] server: sigusr1/2: update conf object with the "new" theme When sending SIGUSR1/SIGUSR2 to a server process, all currently running client instances change their theme. But before this patch, all future instances used the original theme. With this patch, the server owned config object is updated with the selected theme, thus making new instances use the same theme as well. --- main.c | 36 +++++++++++++++++++++++------------- server.c | 24 ++++++++++++++++++++++-- server.h | 5 ++++- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/main.c b/main.c index f97f21b5..1afbd16d 100644 --- a/main.c +++ b/main.c @@ -45,23 +45,28 @@ fdm_sigint(struct fdm *fdm, int signo, void *data) return true; } +struct sigusr_context { + struct terminal *term; + struct server *server; +}; + static bool fdm_sigusr(struct fdm *fdm, int signo, void *data) { - struct wayland *wayl = data; - xassert(signo == SIGUSR1 || signo == SIGUSR2); - if (signo == SIGUSR1) { - tll_foreach(wayl->terms, it) { - struct terminal *term = it->item; - term_theme_switch_to_1(term); - } + struct sigusr_context *ctx = data; + + if (ctx->server != NULL) { + if (signo == SIGUSR1) + server_global_theme_switch_to_1(ctx->server); + else + server_global_theme_switch_to_2(ctx->server); } else { - tll_foreach(wayl->terms, it) { - struct terminal *term = it->item; - term_theme_switch_to_2(term); - } + if (signo == SIGUSR1) + term_theme_switch_to_1(ctx->term); + else + term_theme_switch_to_2(ctx->term); } return true; @@ -630,8 +635,13 @@ main(int argc, char *const *argv) goto out; } - if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr, wayl) || - !fdm_signal_add(fdm, SIGUSR2, &fdm_sigusr, wayl)) + struct sigusr_context sigusr_context = { + .term = term, + .server = server, + }; + + if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr, &sigusr_context) || + !fdm_signal_add(fdm, SIGUSR2, &fdm_sigusr, &sigusr_context)) { goto out; } diff --git a/server.c b/server.c index 22dd473b..97c1915d 100644 --- a/server.c +++ b/server.c @@ -30,7 +30,7 @@ struct client; struct terminal_instance; struct server { - const struct config *conf; + struct config *conf; struct fdm *fdm; struct reaper *reaper; struct wayland *wayl; @@ -505,7 +505,7 @@ prepare_socket(int fd) } struct server * -server_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, +server_init(struct config *conf, struct fdm *fdm, struct reaper *reaper, struct wayland *wayl) { int fd; @@ -617,3 +617,23 @@ server_destroy(struct server *server) unlink(server->sock_path); free(server); } + +void +server_global_theme_switch_to_1(struct server *server) +{ + server->conf->initial_color_theme = COLOR_THEME1; + tll_foreach(server->clients, it) + term_theme_switch_to_1(it->item->instance->terminal); + tll_foreach(server->terminals, it) + term_theme_switch_to_1(it->item->terminal); +} + +void +server_global_theme_switch_to_2(struct server *server) +{ + server->conf->initial_color_theme = COLOR_THEME2; + tll_foreach(server->clients, it) + term_theme_switch_to_2(it->item->instance->terminal); + tll_foreach(server->terminals, it) + term_theme_switch_to_2(it->item->terminal); +} diff --git a/server.h b/server.h index 50797540..6adfe7c6 100644 --- a/server.h +++ b/server.h @@ -6,6 +6,9 @@ #include "wayland.h" struct server; -struct server *server_init(const struct config *conf, struct fdm *fdm, +struct server *server_init(struct config *conf, struct fdm *fdm, struct reaper *reaper, struct wayland *wayl); void server_destroy(struct server *server); + +void server_global_theme_switch_to_1(struct server *server); +void server_global_theme_switch_to_2(struct server *server); From 3b8d59f4760070801e197aea1c7d6dc6392aafdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 30 Jul 2025 12:25:13 +0200 Subject: [PATCH 264/353] doc: foot: document SIGUSR1/SIGUSR2 --- doc/foot.1.scd | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/foot.1.scd b/doc/foot.1.scd index f868c12c..2aef83ca 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -689,6 +689,21 @@ variables may be defined in *foot.ini*(5). In addition to the variables listed above, custom environment variables to unset may be defined in *foot.ini*(5). +# Signals + +The following signals have special meaning in foot: + +- SIGUSR1: switch to color theme 1 (i.e. use the *[colors]* section). +- SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section)- + +Note: you can send SIGUSR1/SIGUSR2 to a *foot --server* process too, +in which case all client instances will switch theme. Furthermore, all +future client instances will also use the selected theme. + +Sending SIGUSR1/SIGUSR2 to a footclient instance is currently not +supported. + + # BUGS Please report bugs to https://codeberg.org/dnkl/foot/issues From b1b2162416cc0633b6432b0af48e79de6a5540d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 30 Jul 2025 12:25:21 +0200 Subject: [PATCH 265/353] doc: foot.ini: mention SIGUSR1/SIGUSR2 and reference foot(1) --- doc/foot.ini.5.scd | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 74b3f35b..7a7fb781 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -371,7 +371,8 @@ empty string to be set, but it must be quoted: *KEY=""*) Use the *color-theme-switch-1*, *color-theme-switch-2* and *color-theme-toggle* key bindings to switch between the two themes - at runtime. + at runtime, or send SIGUSR1/SIGUSR2 to the foot process (see + *foot*(1) for details). Default: _1_ @@ -1446,6 +1447,9 @@ e.g. *search-start=none*. *color-theme-toggle* toggles between the primary and alternative color themes. + Note: you can also send SIGUSR1/SIGUSR2 to the foot process to + change the theme (see *foot*(1) for details.) + Default: _none_ *quit* From 70d99a80513f574ac6d74989282b3481572513ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 30 Jul 2025 12:38:14 +0200 Subject: [PATCH 266/353] changelog: SIGUSR changes in the server --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e405459e..dfd2b9db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,9 @@ * The label letters are no longer sorted before being assigned to URLs ([#2140]2140[]). +* Sending SIGUSR1/SIGUSR2 to a `foot --server` process now causes + newly spawned client instances to use the selected theme, instead of + the original one. ### Deprecated From b13a8f12d2d360e0923ee30ff40f25b245576bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 31 Jul 2025 17:37:19 +0200 Subject: [PATCH 267/353] server/client: add support for sending SIGUSR to footclient This patch adds the IPC infrastructure necessary to propagate SIGUSR1/SIGUSR2 from a footclient process to the server process. By targeting a particular footclient instance, only that particular instance changes theme. This is different from when targeting the server process, where all instances change theme. Closes #2156 --- CHANGELOG.md | 4 +++ client-protocol.h | 14 ++++++++++ client.c | 65 ++++++++++++++++++++++++++++++++++++++++---- doc/foot.1.scd | 6 ++-- doc/footclient.1.scd | 15 ++++++++++ server.c | 59 +++++++++++++++++++++++++++++++++++++--- 6 files changed, 151 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd2b9db..dd5730a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,10 @@ * Sending SIGUSR1/SIGUSR2 to a `foot --server` process now causes newly spawned client instances to use the selected theme, instead of the original one. +* SIGUSR1/SIGUSR2 can now be sent to `footclient` processes, to change + the theme of that particular instance ([#2156][2156]). + +[2156]: https://codeberg.org/dnkl/foot/issues/2156 ### Deprecated diff --git a/client-protocol.h b/client-protocol.h index 505825f6..efd601d7 100644 --- a/client-protocol.h +++ b/client-protocol.h @@ -29,3 +29,17 @@ struct client_data { } __attribute__((packed)); _Static_assert(sizeof(struct client_data) == 10, "protocol struct size error"); + +enum client_ipc_code { + FOOT_IPC_SIGUSR, +}; + +struct client_ipc_hdr { + enum client_ipc_code ipc_code; + uint8_t size; +} __attribute__((packed)); + + +struct client_ipc_sigusr { + int signo; +} __attribute__((packed)); diff --git a/client.c b/client.c index e76f2d51..aa5302be 100644 --- a/client.c +++ b/client.c @@ -33,13 +33,20 @@ struct string { typedef tll(struct string) string_list_t; static volatile sig_atomic_t aborted = 0; +static volatile sig_atomic_t sigusr = 0; static void -sig_handler(int signo) +sigint_handler(int signo) { aborted = 1; } +static void +sigusr_handler(int signo) +{ + sigusr = signo; +} + static ssize_t sendall(int sock, const void *_buf, size_t len) { @@ -507,15 +514,63 @@ main(int argc, char *const *argv) if (!send_string_list(fd, &envp)) goto err; - struct sigaction sa = {.sa_handler = &sig_handler}; - sigemptyset(&sa.sa_mask); - if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) { + struct sigaction sa_int = {.sa_handler = &sigint_handler}; + struct sigaction sa_usr = {.sa_handler = &sigusr_handler}; + sigemptyset(&sa_int.sa_mask); + sigemptyset(&sa_usr.sa_mask); + + if (sigaction(SIGINT, &sa_int, NULL) < 0 || + sigaction(SIGTERM, &sa_int, NULL) < 0 || + sigaction(SIGUSR1, &sa_usr, NULL) < 0 || + sigaction(SIGUSR2, &sa_usr, NULL) < 0) + { LOG_ERRNO("failed to register signal handlers"); goto err; } int exit_code; - ssize_t rcvd = recv(fd, &exit_code, sizeof(exit_code), 0); + ssize_t rcvd = -1; + + while (true) { + rcvd = recv(fd, &exit_code, sizeof(exit_code), 0); + + const int got_sigusr = sigusr; + sigusr = 0; + + if (rcvd < 0 && errno == EINTR) { + if (aborted) + break; + else if (got_sigusr != 0) { + LOG_DBG("sending sigusr %d to server", got_sigusr); + + struct { + struct client_ipc_hdr hdr; + struct client_ipc_sigusr sigusr; + } ipc = { + .hdr = { + .ipc_code = FOOT_IPC_SIGUSR, + .size = sizeof(struct client_ipc_sigusr), + }, + .sigusr = { + .signo = got_sigusr, + }, + }; + + ssize_t count = send(fd, &ipc, sizeof(ipc), 0); + if (count < 0) { + LOG_ERRNO("failed to send SIGUSR IPC to server"); + goto err; + } else if ((size_t)count != sizeof(ipc)) { + LOG_ERR("failed to send SIGUSR IPC to server"); + goto err; + } + } + + continue; + } + + break; + } if (rcvd == -1 && errno == EINTR) xassert(aborted); diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 2aef83ca..8d968a6e 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -694,14 +694,14 @@ variables to unset may be defined in *foot.ini*(5). The following signals have special meaning in foot: - SIGUSR1: switch to color theme 1 (i.e. use the *[colors]* section). -- SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section)- +- SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section). Note: you can send SIGUSR1/SIGUSR2 to a *foot --server* process too, in which case all client instances will switch theme. Furthermore, all future client instances will also use the selected theme. -Sending SIGUSR1/SIGUSR2 to a footclient instance is currently not -supported. +You can also send SIGUSR1/SIGUSR2 to a footclient instance, see +*footclient*(1) for details. # BUGS diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index 365689af..e4f6d350 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -189,6 +189,21 @@ variables may be defined in *foot.ini*(5). In addition to the variables listed above, custom environment variables to unset may be defined in *foot.ini*(5). +# Signals + +The following signals have special meaning in footclient: + +- SIGUSR1: switch to color theme 1 (i.e. use the *[colors]* section). +- SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section). + +When sending SIGUSR1/SIGUSR2 to a footclient instance, the theme is +changed in that instance only. This is different from when you send +SIGUSR1/SIGUSR2 to the server process, where all instances change the +theme. + +Note: for obvious reasons, this is not supported when footclient is +started with *--no-wait*. + # SEE ALSO *foot*(1) diff --git a/server.c b/server.c index 97c1915d..3d4b5725 100644 --- a/server.c +++ b/server.c @@ -156,10 +156,61 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) xassert(events & EPOLLIN); if (client->instance != NULL) { - uint8_t dummy[128]; - ssize_t count = read(fd, dummy, sizeof(dummy)); - LOG_WARN("client unexpectedly sent %zd bytes", count); - return true; /* TODO: shutdown instead? */ + struct client_ipc_hdr ipc_hdr; + ssize_t count = read(fd, &ipc_hdr, sizeof(ipc_hdr)); + + if (count != sizeof(ipc_hdr)) { + LOG_WARN("client unexpectedly sent %zd bytes", count); + return true; /* TODO: shutdown instead? */ + } + + switch (ipc_hdr.ipc_code) { + case FOOT_IPC_SIGUSR: { + xassert(ipc_hdr.size == sizeof(struct client_ipc_sigusr)); + + struct client_ipc_sigusr sigusr; + count = read(fd, &sigusr, sizeof(sigusr)); + if (count < 0) { + LOG_ERRNO("failed to read SIGUSR IPC data from client"); + return true; /* TODO: shutdown instead? */ + } + + if ((size_t)count != sizeof(sigusr)) { + LOG_ERR("failed to read SIGUSR IPC data from client"); + return true; /* TODO: shutdown instead? */ + } + + switch (sigusr.signo) { + case SIGUSR1: + term_theme_switch_to_1(client->instance->terminal); + break; + + case SIGUSR2: + term_theme_switch_to_2(client->instance->terminal); + break; + + default: + LOG_ERR( + "client sent bad SIGUSR number: %d " + "(expected SIGUSR1=%d or SIGUSR2=%d)", + sigusr.signo, SIGUSR1, SIGUSR2); + break; + } + + return true; + } + + default: + LOG_WARN( + "client sent unrecognized IPC (0x%04x), ignoring %hhu bytes", + ipc_hdr.ipc_code, ipc_hdr.size); + + /* TODO: slightly broken, since not all data is guaranteed + to be readable yet */ + uint8_t dummy[ipc_hdr.size]; + read(fd, dummy, ipc_hdr.size); + return true; + } } if (client->buffer.data == NULL) { From 72d9a13c0c6b6ee4b56a38f508c2e8d5c56616b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 1 Aug 2025 09:41:37 +0200 Subject: [PATCH 268/353] server: fix compilation error: return value ignored --- server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.c b/server.c index 3d4b5725..6b3e5094 100644 --- a/server.c +++ b/server.c @@ -208,7 +208,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) /* TODO: slightly broken, since not all data is guaranteed to be readable yet */ uint8_t dummy[ipc_hdr.size]; - read(fd, dummy, ipc_hdr.size); + (void)!!read(fd, dummy, ipc_hdr.size); return true; } } From ed7652db5056c3658afbc11c04e164e77da18650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 25 Aug 2025 14:26:44 +0200 Subject: [PATCH 269/353] config: value_to_*(): don't overwrite result variable on error Some of the value_to_*() functions wrote directly to the output variable, even when the value was invalid. This often resulted in the an actual configuration option (i.e. a member in the config struct) to be overwritten by an invalid value. For example, -o initial-color-theme=0 would set conf->initial_color_theme to -1, resulting in a crash later, when initializing a terminal instance. --- CHANGELOG.md | 7 ++++++- config.c | 20 ++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd5730a1..2a2e3347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ ### Changed * The label letters are no longer sorted before being assigned to URLs - ([#2140]2140[]). + ([#2140][2140]). * Sending SIGUSR1/SIGUSR2 to a `foot --server` process now causes newly spawned client instances to use the selected theme, instead of the original one. @@ -83,6 +83,11 @@ ### Deprecated ### Removed ### Fixed + +* Invalid configuration values overriding valid ones in surprising + ways. + + ### Security ### Contributors diff --git a/config.c b/config.c index 77dc3a73..1d539bcb 100644 --- a/config.c +++ b/config.c @@ -474,8 +474,12 @@ str_to_ulong(const char *s, int base, unsigned long *res) errno = 0; char *end = NULL; - *res = strtoul(s, &end, base); - return errno == 0 && *end == '\0'; + unsigned long v = strtoul(s, &end, base); + if (!(errno == 0 && *end == '\0')) + return false; + + *res = v; + return true; } static bool NOINLINE @@ -544,12 +548,13 @@ value_to_float(struct context *ctx, float *res) errno = 0; char *end = NULL; - *res = strtof(s, &end); + float v = strtof(s, &end); if (!(errno == 0 && *end == '\0')) { LOG_CONTEXTUAL_ERR("invalid decimal value"); return false; } + *res = v; return true; } @@ -641,7 +646,6 @@ value_to_enum(struct context *ctx, const char **value_map, int *res) valid_values[idx - 2] = '\0'; LOG_CONTEXTUAL_ERR("not one of %s", valid_values); - *res = -1; return false; } @@ -690,14 +694,18 @@ value_to_two_colors(struct context *ctx, goto out; } + uint32_t a, b; + ctx->value = first_as_str; - if (!value_to_color(ctx, first, allow_alpha)) + if (!value_to_color(ctx, &a, allow_alpha)) goto out; ctx->value = second_as_str; - if (!value_to_color(ctx, second, allow_alpha)) + if (!value_to_color(ctx, &b, allow_alpha)) goto out; + *first = a; + *second = b; ret = true; out: From f0e36e35cb65bdb92dbe24d00a9cc6bc8d72a458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 30 Aug 2025 08:18:31 +0200 Subject: [PATCH 270/353] input: unit test: check pipe2() return value Fixes compilation failures with clang, in release mode. --- input.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/input.c b/input.c index fe90a7f0..fe5a8001 100644 --- a/input.c +++ b/input.c @@ -1878,7 +1878,7 @@ keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, UNITTEST { int chan[2]; - pipe2(chan, O_CLOEXEC); + xassert(pipe2(chan, O_CLOEXEC) == 0); xassert(chan[0] >= 0); xassert(chan[1] >= 0); From 298196365c06c13dd16a74e34e03d9de93670473 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Thu, 7 Aug 2025 08:18:38 -0400 Subject: [PATCH 271/353] config: add 'uppercase-regex-insert' This makes the "uppercase hint character inserts selected text" behavior added in #1975 configurable, as it can have unexpected behavior for some users. It defaults to "on", preserving the new behavior of `foot`, after Fixes #2159. --- CHANGELOG.md | 7 +++++++ config.c | 4 ++++ config.h | 1 + doc/foot.ini.5.scd | 7 +++++++ foot.ini | 2 ++ tests/test-config.c | 1 + url-mode.c | 3 ++- 7 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a2e3347..57144549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,13 @@ ## Unreleased ### Added + +* The `uppercase-regex-insert` option controls whether an uppercase hint + character will insert the selected text into the prompt in `regex-copy` + or `show-urls-copy` mode. It defaults to `true`. ([#2159][2159]). + +[2159]: https://codeberg.org/dnkl/foot/issues/2159 + ### Changed * The label letters are no longer sorted before being assigned to URLs diff --git a/config.c b/config.c index 1d539bcb..0de1a1be 100644 --- a/config.c +++ b/config.c @@ -1119,6 +1119,9 @@ parse_section_main(struct context *ctx) (int *)&conf->initial_color_theme); } + else if (streq(key, "uppercase-regex-insert")) + return value_to_bool(ctx, &conf->uppercase_regex_insert); + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -3383,6 +3386,7 @@ config_load(struct config *conf, const char *conf_path, .strikeout_thickness = {.pt = 0., .px = -1}, .dpi_aware = false, .gamma_correct = false, + .uppercase_regex_insert = true, .security = { .osc52 = OSC52_ENABLED, }, diff --git a/config.h b/config.h index 315f7e24..86fb2a8f 100644 --- a/config.h +++ b/config.h @@ -247,6 +247,7 @@ struct config { bool dpi_aware; bool gamma_correct; + bool uppercase_regex_insert; struct config_font_list fonts[4]; struct font_size_adjustment font_size_adjustment; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 7a7fb781..3a057d88 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -231,6 +231,13 @@ empty string to be set, but it must be quoted: *KEY=""*) Default: _no_. +*upppercase-regex-insert* + Boolean. When enabled, inputting an uppercase hint character in + *show-urls-copy* or *regex-copy* mode will insert the selected + text into the prompt in addition to copying it to the clipboard. + + Default: _yes_ + *box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates box/line drawing characters itself. The are several advantages to doing this instead of using diff --git a/foot.ini b/foot.ini index 73fdb7ab..44ed5785 100644 --- a/foot.ini +++ b/foot.ini @@ -40,6 +40,8 @@ # utmp-helper=/usr/lib/utempter/utempter # When utmp backend is ‘libutempter’ (Linux) # utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ‘ulog’ (FreeBSD) +# uppercase-regex-insert=yes + [environment] # name=value diff --git a/tests/test-config.c b/tests/test-config.c index bab57788..8c0805f4 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -491,6 +491,7 @@ test_section_main(void) test_boolean(&ctx, &parse_section_main, "locked-title", &conf.locked_title); test_boolean(&ctx, &parse_section_main, "dpi-aware", &conf.dpi_aware); test_boolean(&ctx, &parse_section_main, "gamma-correct-blending", &conf.gamma_correct); + test_boolean(&ctx, &parse_section_main, "uppercase-regex-insert", &conf.uppercase_regex_insert); test_pt_or_px(&ctx, &parse_section_main, "font-size-adjustment", &conf.font_size_adjustment.pt_or_px); /* TODO: test ‘N%’ values too */ test_pt_or_px(&ctx, &parse_section_main, "line-height", &conf.line_height); diff --git a/url-mode.c b/url-mode.c index 19d95356..44809f5f 100644 --- a/url-mode.c +++ b/url-mode.c @@ -283,7 +283,8 @@ urls_input(struct seat *seat, struct terminal *term, if (match) { // If the last hint character was uppercase, copy and paste - activate_url(seat, term, match, serial, wc == toc32upper(wc)); + bool insert = term->conf->uppercase_regex_insert && wc == toc32upper(wc); + activate_url(seat, term, match, serial, insert); switch (match->action) { case URL_ACTION_COPY: From 1d9ac3f611f1b81983b095a2f96e36dba4d24da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 31 Aug 2025 11:42:56 +0200 Subject: [PATCH 272/353] doc: foot.ini: typo: upppercase -> uppercase --- doc/foot.ini.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 3a057d88..7550fd13 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -231,7 +231,7 @@ empty string to be set, but it must be quoted: *KEY=""*) Default: _no_. -*upppercase-regex-insert* +*uppercase-regex-insert* Boolean. When enabled, inputting an uppercase hint character in *show-urls-copy* or *regex-copy* mode will insert the selected text into the prompt in addition to copying it to the clipboard. From 65528f455d0d7753da73365fb39b39473a87d8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Sep 2025 17:34:02 +0200 Subject: [PATCH 273/353] meson: utempter del has no argument This fixes an issue where we didn't record a logout record when using the libutempter backend. --- CHANGELOG.md | 3 ++- meson.build | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57144549..4536f6ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,7 +93,8 @@ * Invalid configuration values overriding valid ones in surprising ways. - +* Bug where the libutempter utmp backend did not record logouts + correctly. ### Security ### Contributors diff --git a/meson.build b/meson.build index 0b7bbc17..56f4a31c 100644 --- a/meson.build +++ b/meson.build @@ -53,7 +53,7 @@ if utmp_backend == 'none' elif utmp_backend == 'libutempter' utmp_add = 'add' utmp_del = 'del' - utmp_del_have_argument = true + utmp_del_have_argument = false if utmp_default_helper_path == 'auto' utmp_default_helper_path = join_paths('/usr', get_option('libdir'), 'utempter', 'utempter') endif From efc39097e5a939c4e6f54bf80d24871bfeeaf80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Sep 2025 17:34:54 +0200 Subject: [PATCH 274/353] term: no need to pass ptmx as stdout to utempter --- terminal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index 2e23f749..421a3e65 100644 --- a/terminal.c +++ b/terminal.c @@ -199,7 +199,7 @@ add_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx) return true; char *const argv[] = {conf->utmp_helper_path, UTMP_ADD, getenv("WAYLAND_DISPLAY"), NULL}; - return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL, NULL, NULL) >= 0; + return spawn(reaper, NULL, argv, ptmx, -1, -1, NULL, NULL, NULL) >= 0; #else return true; #endif @@ -223,7 +223,7 @@ del_utmp_record(const struct config *conf, struct reaper *reaper, int ptmx) ; char *const argv[] = {conf->utmp_helper_path, UTMP_DEL, del_argument, NULL}; - return spawn(reaper, NULL, argv, ptmx, ptmx, -1, NULL, NULL, NULL) >= 0; + return spawn(reaper, NULL, argv, ptmx, -1, -1, NULL, NULL, NULL) >= 0; #else return true; #endif From f715f3b55fb094e6ab1fdc18dcf8956227c495c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Sep 2025 10:18:06 +0200 Subject: [PATCH 275/353] changelog: prepare for 1.24.0 --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4536f6ea..7fa3f0ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.24.0](#1-24-0) * [1.23.1](#1-23-1) * [1.23.0](#1-23-0) * [1.22.3](#1-22-3) @@ -65,7 +65,8 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.24.0 + ### Added * The `uppercase-regex-insert` option controls whether an uppercase hint @@ -87,8 +88,6 @@ [2156]: https://codeberg.org/dnkl/foot/issues/2156 -### Deprecated -### Removed ### Fixed * Invalid configuration values overriding valid ones in surprising @@ -96,9 +95,11 @@ * Bug where the libutempter utmp backend did not record logouts correctly. -### Security ### Contributors +* Ryan Roden-Corrent +* Tobias Mock + ## 1.23.1 From fa0fd2f50f41d8fc47241dd576be42a2f29d6530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Sep 2025 10:18:33 +0200 Subject: [PATCH 276/353] meson: bump version to 1.24.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 56f4a31c..305df13c 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.23.1', + version: '1.24.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From c34f0633075769a2564e10d359feeca604373ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 12 Sep 2025 10:22:21 +0200 Subject: [PATCH 277/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fa3f0ea..3144a125 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.24.0](#1-24-0) * [1.23.1](#1-23-1) * [1.23.0](#1-23-0) @@ -65,6 +66,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.24.0 ### Added From 44a674edb86f2f8db3304d76faf567346dcd7d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 25 Sep 2025 16:57:41 +0200 Subject: [PATCH 278/353] term: erase: use erase_line() whenever a range corresponds to a full line --- terminal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/terminal.c b/terminal.c index 421a3e65..e27a8fd9 100644 --- a/terminal.c +++ b/terminal.c @@ -2648,7 +2648,10 @@ term_erase(struct terminal *term, int start_row, int start_col, if (start_row == end_row) { struct row *row = grid_row(term->grid, start_row); - erase_cell_range(term, row, start_col, end_col); + if (unlikely(start_col == 0 && end_col == term->cols - 1)) + erase_line(term, row); + else + erase_cell_range(term, row, start_col, end_col); sixel_overwrite_by_row(term, start_row, start_col, end_col - start_col + 1); return; } @@ -2664,7 +2667,10 @@ term_erase(struct terminal *term, int start_row, int start_col, sixel_overwrite_by_rectangle( term, start_row + 1, 0, end_row - start_row, term->cols); - erase_cell_range(term, grid_row(term->grid, end_row), 0, end_col); + if (unlikely(end_col == term->cols - 1)) + erase_line(term, grid_row(term->grid, end_row)); + else + erase_cell_range(term, grid_row(term->grid, end_row), 0, end_col); sixel_overwrite_by_row(term, end_row, 0, end_col + 1); } From 1dfa86c93ac3585b1b8b6ff3d8617176a57942c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Oct 2025 07:21:15 +0200 Subject: [PATCH 279/353] Revert "term: erase: use erase_line() whenever a range corresponds to a full line" This reverts commit 44a674edb86f2f8db3304d76faf567346dcd7d94. It caused a regression with prompt markers, in at least fish+starship. --- terminal.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/terminal.c b/terminal.c index e27a8fd9..421a3e65 100644 --- a/terminal.c +++ b/terminal.c @@ -2648,10 +2648,7 @@ term_erase(struct terminal *term, int start_row, int start_col, if (start_row == end_row) { struct row *row = grid_row(term->grid, start_row); - if (unlikely(start_col == 0 && end_col == term->cols - 1)) - erase_line(term, row); - else - erase_cell_range(term, row, start_col, end_col); + erase_cell_range(term, row, start_col, end_col); sixel_overwrite_by_row(term, start_row, start_col, end_col - start_col + 1); return; } @@ -2667,10 +2664,7 @@ term_erase(struct terminal *term, int start_row, int start_col, sixel_overwrite_by_rectangle( term, start_row + 1, 0, end_row - start_row, term->cols); - if (unlikely(end_col == term->cols - 1)) - erase_line(term, grid_row(term->grid, end_row)); - else - erase_cell_range(term, grid_row(term->grid, end_row), 0, end_col); + erase_cell_range(term, grid_row(term->grid, end_row), 0, end_col); sixel_overwrite_by_row(term, end_row, 0, end_col + 1); } From 80951ab7a6b3bcc4dd3c30a5454bf648d3d9ed93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Oct 2025 09:24:47 +0200 Subject: [PATCH 280/353] term: osc8: tag *all* cells in a multi-column character as an URI When we print a character to the grid, we must also update its OSC-8 state if an OSC-8 URI is currently active. For double-width characters, this was only being done for the first cell. This causes the labels in URL mode to be off, as the link was effectively chopped up into multiple pieces. Closes #2179 --- CHANGELOG.md | 7 +++++++ terminal.c | 8 +++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3144a125..2b0a7247 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,13 @@ ### Deprecated ### Removed ### Fixed + +* URL labels misplaces when URL contains double-width characters + ([#2179][2179]). + +[2179]: https://codeberg.org/dnkl/foot/issues/2179 + + ### Security ### Contributors diff --git a/terminal.c b/terminal.c index 421a3e65..60506d07 100644 --- a/terminal.c +++ b/terminal.c @@ -4005,9 +4005,11 @@ term_print(struct terminal *term, char32_t wc, int width, bool insert_mode_disab cell->wc = term->vt.last_printed = wc; cell->attrs = term->vt.attrs; - if (term->vt.osc8.uri != NULL) { - grid_row_uri_range_put( - row, col, term->vt.osc8.uri, term->vt.osc8.id); + if (unlikely(term->vt.osc8.uri != NULL)) { + for (int i = 0; i < width && (col + i) < term->cols; i++) { + grid_row_uri_range_put( + row, col + i, term->vt.osc8.uri, term->vt.osc8.id); + } switch (term->conf->url.osc8_underline) { case OSC8_UNDERLINE_ALWAYS: From fac399415452edba6f20986e5f8fa479863582fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Oct 2025 09:29:56 +0200 Subject: [PATCH 281/353] config: add tweak.min-stride-alignment This allows the user to configure the value by which a surface buffer's stride must be an even multiple of. This can be used to ensure the stride meets the GPU driver's requirements for direct import. Defaults to 256. Set to 0 to disable. Closes #2182 --- config.c | 5 +++++ config.h | 1 + main.c | 1 + shm.c | 17 ++++++++++++++++- shm.h | 3 +++ tests/test-config.c | 3 +++ 6 files changed, 29 insertions(+), 1 deletion(-) diff --git a/config.c b/config.c index 0de1a1be..459a1de9 100644 --- a/config.c +++ b/config.c @@ -2848,6 +2848,10 @@ parse_section_tweak(struct context *ctx) #endif } + else if (streq(key, "min-stride-alignment")) { + return value_to_uint32(ctx, 10, &conf->tweak.min_stride_alignment); + } + else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); return false; @@ -3496,6 +3500,7 @@ config_load(struct config *conf, const char *conf_path, .font_monospace_warn = true, .sixel = true, .surface_bit_depth = SHM_BITS_AUTO, + .min_stride_alignment = 256, }, .touch = { diff --git a/config.h b/config.h index 86fb2a8f..11439d3a 100644 --- a/config.h +++ b/config.h @@ -435,6 +435,7 @@ struct config { bool font_monospace_warn; bool sixel; enum shm_bit_depth surface_bit_depth; + uint32_t min_stride_alignment; } tweak; struct { diff --git a/main.c b/main.c index 1afbd16d..b6a0d825 100644 --- a/main.c +++ b/main.c @@ -597,6 +597,7 @@ main(int argc, char *const *argv) } shm_set_max_pool_size(conf.tweak.max_shm_pool_size); + shm_set_min_stride_alignment(conf.tweak.min_stride_alignment); if ((fdm = fdm_init()) == NULL) goto out; diff --git a/shm.c b/shm.c index 4680c12c..7b13db9b 100644 --- a/shm.c +++ b/shm.c @@ -13,7 +13,6 @@ #include -#include #include #define LOG_MODULE "shm" @@ -21,6 +20,7 @@ #include "log.h" #include "debug.h" #include "macros.h" +#include "stride.h" #include "xmalloc.h" #if !defined(MAP_UNINITIALIZED) @@ -61,6 +61,8 @@ static off_t max_pool_size = 512 * 1024 * 1024; static bool can_punch_hole = false; static bool can_punch_hole_initialized = false; +static size_t min_stride_alignment = 0; + struct buffer_pool { int fd; /* memfd */ struct wl_shm_pool *wl_pool; @@ -113,6 +115,12 @@ shm_set_max_pool_size(off_t _max_pool_size) max_pool_size = _max_pool_size; } +void +shm_set_min_stride_alignment(size_t _min_stride_alignment) +{ + min_stride_alignment = _min_stride_alignment; +} + static void buffer_destroy_dont_close(struct buffer *buf) { @@ -342,6 +350,13 @@ get_new_buffers(struct buffer_chain *chain, size_t count, ? chain->pixman_fmt_with_alpha : chain->pixman_fmt_without_alpha, widths[i]); + + if (min_stride_alignment > 0) { + const size_t m = min_stride_alignment; + stride[i] = (stride[i] + m - 1) / m * m; + } + + xassert(min_stride_alignment == 0 || stride[i] % min_stride_alignment == 0); sizes[i] = stride[i] * heights[i]; total_size += sizes[i]; } diff --git a/shm.h b/shm.h index 8f8c406a..6050f1c7 100644 --- a/shm.h +++ b/shm.h @@ -42,7 +42,10 @@ struct buffer { }; void shm_fini(void); + +/* TODO: combine into shm_init() */ void shm_set_max_pool_size(off_t max_pool_size); +void shm_set_min_stride_alignment(size_t min_stride_alignment); struct buffer_chain; struct buffer_chain *shm_chain_new( diff --git a/tests/test-config.c b/tests/test-config.c index 8c0805f4..64b61540 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -1413,6 +1413,9 @@ test_section_tweak(void) test_float(&ctx, &parse_section_tweak, "bold-text-in-bright-amount", &conf.bold_in_bright.amount); + test_uint32(&ctx, &parse_section_tweak, "min-stride-alignment", + &conf.tweak.min_stride_alignment); + #if 0 /* Must be equal to, or less than INT32_MAX */ test_uint32(&ctx, &parse_section_tweak, "max-shm-pool-size-mb", &conf.tweak.max_shm_pool_size); From bd994eda1c816d5430a9c4cdb7089e43c47d74ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Oct 2025 10:50:38 +0200 Subject: [PATCH 282/353] shm: page-align the memfd size (also needed for GPU direct import) --- shm.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/shm.c b/shm.c index 7b13db9b..31ea67ed 100644 --- a/shm.c +++ b/shm.c @@ -239,7 +239,6 @@ static const struct wl_buffer_listener buffer_listener = { .release = &buffer_release, }; -#if __SIZEOF_POINTER__ == 8 static size_t page_size(void) { @@ -256,7 +255,6 @@ page_size(void) xassert(size > 0); return size; } -#endif static bool instantiate_offset(struct buffer_private *buf, off_t new_offset) @@ -398,9 +396,11 @@ get_new_buffers(struct buffer_chain *chain, size_t count, goto err; } + const size_t page_sz = page_size(); + #if __SIZEOF_POINTER__ == 8 off_t offset = chain->scrollable && max_pool_size > 0 - ? (max_pool_size / 4) & ~(page_size() - 1) + ? (max_pool_size / 4) & ~(page_sz - 1) : 0; off_t memfd_size = chain->scrollable && max_pool_size > 0 ? max_pool_size @@ -410,7 +410,8 @@ get_new_buffers(struct buffer_chain *chain, size_t count, off_t memfd_size = total_size; #endif - xassert(chain->scrollable || (offset == 0 && memfd_size == total_size)); + /* Page align */ + memfd_size = (memfd_size + page_sz - 1) & ~(page_sz - 1); LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, offset); @@ -442,6 +443,9 @@ get_new_buffers(struct buffer_chain *chain, size_t count, memfd_size = total_size; chain->scrollable = false; + /* Page align */ + memfd_size = (memfd_size + page_sz - 1) & ~(page_sz - 1); + if (ftruncate(pool_fd, memfd_size) < 0) { LOG_ERRNO("failed to set size of SHM backing memory file"); goto err; From e43ea3676fe359864a8a22a31beed5bf289f367c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Oct 2025 15:38:35 +0200 Subject: [PATCH 283/353] doc: foot.ini: document tweak.min-stride-alignment --- doc/foot.ini.5.scd | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 7550fd13..56b76be7 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -2031,6 +2031,32 @@ any of these options. Default: _512_. Maximum allowed: _2048_ (2GB). +*min-stride-alignment* + This option controls the minimum stride alignment, in bytes, when + allocating SHM buffers. + + In some circumstances, a compositor can import foot's SHM buffers + directly to the GPU, without copying the buffer to GPU memory + (typically on integrated graphics). Different drivers have + different requirements for this, and one of those requirements is + typically the stride alignment. At the time of writing, AMD GPUs + require 256-byte alignment. + + Note that doing a direct import typically disables immediate + buffer release (if the compositor supports that), which means foot + has to double buffer. This adds a performance penalty in foot, but + the overall system performance should still be better. + + If you are not using integrated graphics, or if the compositor + does not support GPU direct imports, this option has close to zero + impact. You can save a small amount of memory by setting this to + 0. + + Ultimately, it is up to the compositor to decide whether to do + immediate buffer releases, or try to optimize GPU imports. + + Default: _256_ + *sixel* Boolean. When enabled, foot will process sixel images. Default: _yes_ From bb314425ef9c2069e3b17122b7f81ecc7d527a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 4 Oct 2025 15:40:20 +0200 Subject: [PATCH 284/353] changelog: shm buffer stride alignment --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b0a7247..a2aaf488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,16 @@ ## Unreleased ### Added ### Changed + +* SHM buffer sizes are now rounded up to nearest page size, and their + stride is always an even multiple of 256 bytes (by default, + configurable by setting `tweak.min-stride-alignment`). This allows + compositor to directly import foot's SHM buffers to the GPU, with + e.g. integrated graphics ([#2182][2182]). + +[2182]: https://codeberg.org/dnkl/foot/issues/2182 + + ### Deprecated ### Removed ### Fixed From 299186a6547f6e038ee6f3822caf9a0fdfabceef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 5 Oct 2025 10:48:36 +0200 Subject: [PATCH 285/353] render: when double-buffering, pre-apply previous frame's damage early Foot likes it when compositor releases buffer immediately, as that means we only have to re-render the cells that have changed since the last frame. For various reasons, not all compositors do this. In this case, foot is typically forced to switch between two buffers, i.e. double-buffer. In this case, each frame starts with copying over the damage from the previous frame, to the new frame. Then we start rendering the updated cells. Bringing over the previous frame's damage can be slow, if the changed area was large (e.g. when scrolling one or a few lines, or on full screen updates). It's also done single-threaded. Thus it not only slows down frame rendering, but pauses everything else (i.e. input processing). All in all, it reduces performance and increases input latency. But we don't have to wait until it's time to render a frame to copy over the previous frame's damage. We can do that as soon as the compositor has released the buffer (for the frame _before_ the previous frame). And we can do this in a thread. This frees up foot to continue processing input, and reduces frame rendering time since we can now start rendering the modified cells immediately, without first doing a large memcpy(3). In worst case scenarios (or perhaps we should consider them best case scenarios...), I've seen up to a 10x performance increase in frame rendering times (this obviously does *not* include the time it takes to copy over the previous frame's damage, since that doesn't affect neither input processing nor frame rendering). Implemented by adding a callback mechanism to the shm abstraction layer. Use it for the grid buffers, and kick off a thread that copies the previous frame's damage, and resets the buffers age to 0 (so that foot understands it can start render to it immediately when it later needs to render a frame). Since we have certain way of knowing if a compositor releases buffers immediately or not, use a bit of heuristics; if we see 10 consecutive non-immediate releases (that is, we reset the counter as soon as we do see an immediate release), this new "pre-apply damage" logic is enabled. It can be force-disabled with tweak.pre-apply-damage=no. We also need to take care to wait for the thread before resetting the render's "last_buf" pointer (or we'll SEGFAULT in the thread...). We must also ensure we wait for the thread to finish before we start rendering a new frame. Under normal circumstances, the wait time is always 0, the thread has almost always finished long before we need to render the next frame. But it _can_ happen. Closes #2188 --- CHANGELOG.md | 7 ++ config.c | 7 +- config.h | 1 + doc/foot.ini.5.scd | 35 ++++++++ pgo/pgo.c | 5 +- render.c | 200 ++++++++++++++++++++++++++++++++++++++++++--- render.h | 2 + shm.c | 24 +++++- shm.h | 3 +- terminal.c | 19 +++-- terminal.h | 10 +++ 11 files changed, 287 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2aaf488..65f0bbab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,13 @@ ## Unreleased ### Added + +* Performance increased and input latency decreased on compositors + that do not release SHM buffers immediately ([#2188][2188]). + +[2188]: https://codeberg.org/dnkl/foot/issues/2188 + + ### Changed * SHM buffer sizes are now rounded up to nearest page size, and their diff --git a/config.c b/config.c index 459a1de9..06817247 100644 --- a/config.c +++ b/config.c @@ -2848,9 +2848,11 @@ parse_section_tweak(struct context *ctx) #endif } - else if (streq(key, "min-stride-alignment")) { + else if (streq(key, "min-stride-alignment")) return value_to_uint32(ctx, 10, &conf->tweak.min_stride_alignment); - } + + else if (streq(key, "pre-apply-damage")) + return value_to_bool(ctx, &conf->tweak.preapply_damage); else { LOG_CONTEXTUAL_ERR("not a valid option: %s", key); @@ -3501,6 +3503,7 @@ config_load(struct config *conf, const char *conf_path, .sixel = true, .surface_bit_depth = SHM_BITS_AUTO, .min_stride_alignment = 256, + .preapply_damage = true, }, .touch = { diff --git a/config.h b/config.h index 11439d3a..5b7ff11e 100644 --- a/config.h +++ b/config.h @@ -436,6 +436,7 @@ struct config { bool sixel; enum shm_bit_depth surface_bit_depth; uint32_t min_stride_alignment; + bool preapply_damage; } tweak; struct { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 56b76be7..7b08d5d4 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -2093,6 +2093,41 @@ any of these options. Default: _auto_ +*pre-apply-damage* + Boolean. When enabled, foot will attempt to "pre-apply" the damage + from the last frame when foot is forced to double-buffer + (i.e. when the compositor does not release SHM buffers + immediately). All text after this assumes the compositor is not + releasing buffers immediately. + + When this option is disabled, each time foot needs to render a + frame, it has to first copy over areas that changed in the last + frame (i.e. all changes between the last two frames). This is + basically a *memcpy*(3), which can be slow if the changed area is + large. It is also done on the main thread, which means foot cannot + do anything else at the same time; no other rendering, no VT + parsing. After the changes have been brought over to the new + frame, foot proceeds with rendering the cells that has changed + between the last frame and the new frame. + + When this open is enabled, the changes between the last two frames + are brought over to what will become the next frame before foot + starts rendering the next frame. As soon as the compositor + releases the previous buffer (typically right after foot has + pushed a new frame), foot kicks off a thread that copies over the + changes to the newly released buffer. Since this is done in a + thread, foot can continue processing input at the same + time. Later, when it is time to render a new frame, the changes + have already been transferred, and foot can immediately start with + the actual rendering. + + Thus, having this option enabled improves both performance + (copying the last two frames' changes is threaded), and improves + input latency (rending the next frame no longer has to first bring + over the changes between the last two frames). + + Default: _yes_ + # SEE ALSO *foot*(1), *footclient*(1) diff --git a/pgo/pgo.c b/pgo/pgo.c index 757dcd06..4ff4111c 100644 --- a/pgo/pgo.c +++ b/pgo/pgo.c @@ -74,6 +74,8 @@ void render_refresh_icon(struct terminal *term) {} void render_overlay(struct terminal *term) {} +void render_buffer_release_callback(struct buffer *buf, void *data) {} + bool render_xcursor_is_valid(const struct seat *seat, const char *cursor) { @@ -206,7 +208,8 @@ enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain) { retur struct buffer_chain * shm_chain_new( struct wayland *wayl, bool scrollable, size_t pix_instances, - enum shm_bit_depth desired_bit_depth) + enum shm_bit_depth desired_bit_depth, + void (*release_cb)(struct buffer *buf, void *data), void *cb_data) { return NULL; } diff --git a/render.c b/render.c index 1c24bafa..35752125 100644 --- a/render.c +++ b/render.c @@ -2224,6 +2224,56 @@ render_worker_thread(void *_ctx) case -2: return 0; + + case -3: { + if (term->conf->tweak.render_timer != RENDER_TIMER_NONE) + clock_gettime(CLOCK_MONOTONIC, &term->render.workers.preapplied_damage.start); + + mtx_lock(&term->render.workers.preapplied_damage.lock); + buf = term->render.workers.preapplied_damage.buf; + xassert(buf != NULL); + + if (likely(term->render.last_buf != NULL)) { + mtx_unlock(&term->render.workers.preapplied_damage.lock); + + pixman_region32_t dmg; + pixman_region32_init(&dmg); + + if (buf->age == 0) + ; /* No need to do anything */ + else if (buf->age == 1) + pixman_region32_copy(&dmg, + &term->render.last_buf->dirty[0]); + else + pixman_region32_init_rect(&dmg, 0, 0, buf->width, + buf->height); + + pixman_image_set_clip_region32(buf->pix[my_id], &dmg); + pixman_image_composite32(PIXMAN_OP_SRC, + term->render.last_buf->pix[my_id], + NULL, buf->pix[my_id], 0, 0, 0, 0, 0, + 0, buf->width, buf->height); + + pixman_region32_fini(&dmg); + + buf->age = 0; + shm_unref(term->render.last_buf); + shm_addref(buf); + term->render.last_buf = buf; + + mtx_lock(&term->render.workers.preapplied_damage.lock); + } + + term->render.workers.preapplied_damage.buf = NULL; + cnd_signal(&term->render.workers.preapplied_damage.cond); + mtx_unlock(&term->render.workers.preapplied_damage.lock); + + if (term->conf->tweak.render_timer != RENDER_TIMER_NONE) + clock_gettime(CLOCK_MONOTONIC, &term->render.workers.preapplied_damage.stop); + + frame_done = true; + break; + } } } }; @@ -2231,6 +2281,22 @@ render_worker_thread(void *_ctx) return -1; } +static void +wait_for_preapply_damage(struct terminal *term) +{ + if (!term->render.preapply_last_frame_damage) + return; + if (term->render.workers.preapplied_damage.buf == NULL) + return; + + mtx_lock(&term->render.workers.preapplied_damage.lock); + while (term->render.workers.preapplied_damage.buf != NULL) { + cnd_wait(&term->render.workers.preapplied_damage.cond, + &term->render.workers.preapplied_damage.lock); + } + mtx_unlock(&term->render.workers.preapplied_damage.lock); +} + struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx) { @@ -3113,14 +3179,6 @@ force_full_repaint(struct terminal *term, struct buffer *buf) static void reapply_old_damage(struct terminal *term, struct buffer *new, struct buffer *old) { - static int counter = 0; - static bool have_warned = false; - if (!have_warned && ++counter > 5) { - LOG_WARN("compositor is not releasing buffers immediately; " - "expect lower rendering performance"); - have_warned = true; - } - if (new->age > 1) { memcpy(new->data, old->data, new->height * new->stride); return; @@ -3251,7 +3309,18 @@ grid_render(struct terminal *term) if (term->shutdown.in_progress) return; - struct timespec start_time, start_double_buffering = {0}, stop_double_buffering = {0}; + struct timespec start_time; + struct timespec start_wait_preapply = {0}, stop_wait_preapply = {0}; + struct timespec start_double_buffering = {0}, stop_double_buffering = {0}; + + /* Might be a thread doing pre-applied damage */ + if (unlikely(term->render.preapply_last_frame_damage && + term->render.workers.preapplied_damage.buf != NULL)) + { + clock_gettime(CLOCK_MONOTONIC, &start_wait_preapply); + wait_for_preapply_damage(term); + clock_gettime(CLOCK_MONOTONIC, &stop_wait_preapply); + } if (term->conf->tweak.render_timer != RENDER_TIMER_NONE) clock_gettime(CLOCK_MONOTONIC, &start_time); @@ -3269,6 +3338,8 @@ grid_render(struct terminal *term) dirty_old_cursor(term); dirty_cursor(term); + LOG_DBG("buffer age: %u (%p)", buf->age, (void *)buf); + if (term->render.last_buf == NULL || term->render.last_buf->width != buf->width || term->render.last_buf->height != buf->height || @@ -3285,9 +3356,27 @@ grid_render(struct terminal *term) xassert(term->render.last_buf->width == buf->width); xassert(term->render.last_buf->height == buf->height); + if (++term->render.frames_since_last_immediate_release > 10) { + static bool have_warned = false; + + if (!term->render.preapply_last_frame_damage && + term->conf->tweak.preapply_damage && + term->render.workers.count > 0) + { + LOG_INFO("enabling pre-applied frame damage"); + term->render.preapply_last_frame_damage = true; + } else if (!have_warned) { + LOG_WARN("compositor is not releasing buffers immediately; " + "expect lower rendering performance"); + have_warned = true; + } + } + clock_gettime(CLOCK_MONOTONIC, &start_double_buffering); reapply_old_damage(term, buf, term->render.last_buf); clock_gettime(CLOCK_MONOTONIC, &stop_double_buffering); + } else if (!term->render.preapply_last_frame_damage) { + term->render.frames_since_last_immediate_release = 0; } if (term->render.last_buf != NULL) { @@ -3515,27 +3604,40 @@ grid_render(struct terminal *term) struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); + struct timespec wait_time; + timespec_sub(&stop_wait_preapply, &start_wait_preapply, &wait_time); + struct timespec render_time; timespec_sub(&end_time, &start_time, &render_time); struct timespec double_buffering_time; timespec_sub(&stop_double_buffering, &start_double_buffering, &double_buffering_time); + struct timespec preapply_damage; + timespec_sub(&term->render.workers.preapplied_damage.stop, + &term->render.workers.preapplied_damage.start, + &preapply_damage); + struct timespec total_render_time; timespec_add(&render_time, &double_buffering_time, &total_render_time); + timespec_add(&wait_time, &total_render_time, &total_render_time); switch (term->conf->tweak.render_timer) { case RENDER_TIMER_LOG: case RENDER_TIMER_BOTH: LOG_INFO( "frame rendered in %lds %9ldns " - "(%lds %9ldns rendering, %lds %9ldns double buffering)", + "(%lds %9ldns wait, %lds %9ldns rendering, %lds %9ldns double buffering) not included: %lds %ldns pre-apply damage", (long)total_render_time.tv_sec, total_render_time.tv_nsec, + (long)wait_time.tv_sec, + wait_time.tv_nsec, (long)render_time.tv_sec, render_time.tv_nsec, (long)double_buffering_time.tv_sec, - double_buffering_time.tv_nsec); + double_buffering_time.tv_nsec, + (long)preapply_damage.tv_sec, + preapply_damage.tv_nsec); break; case RENDER_TIMER_OSD: @@ -4295,6 +4397,7 @@ delayed_reflow_of_normal_grid(struct terminal *term) term->interactive_resizing.old_hide_cursor = false; /* Invalidate render pointers */ + wait_for_preapply_damage(term); shm_unref(term->render.last_buf); term->render.last_buf = NULL; term->render.last_cursor.row = NULL; @@ -4869,6 +4972,7 @@ damage_view: tll_free(term->normal.scroll_damage); tll_free(term->alt.scroll_damage); + wait_for_preapply_damage(term); shm_unref(term->render.last_buf); term->render.last_buf = NULL; term_damage_view(term); @@ -5267,3 +5371,77 @@ render_xcursor_set(struct seat *seat, struct terminal *term, seat->pointer.xcursor_pending = true; return true; } + +void +render_buffer_release_callback(struct buffer *buf, void *data) +{ + /* + * Called from shm.c when a buffer is released + * + * We use it to pre-apply last-frame's damage to it, when we're + * forced to double buffer (compositor doesn't release buffers + * immediately). + * + * The timeline is thus: + * 1. We render and push a new frame + * 2. Some (hopefully short) time after that, the compositor releases the previous buffer + * 3. We're called, and kick off the thread that copies the changes from (1) to the just freed buffer + * 4. Time passes.... + * 5. The compositor calls our frame callback, signalling to us that it's time to start rendering the next frame + * 6. Hopefully, our thread is already done with copying the changes, otherwise we stall, waiting for it + * 7. We render the frame as if the compositor does immediate releases. + * + * What's the gain? Reduced latency, by applying the previous + * frame's damage as soon as possible, we shorten the time it + * takes to render the frame after the frame callback. + * + * This means the compositor can, in theory, push the frame + * callback closer to the vblank deadline, and thus reduce input + * latency. Not all compositors (most, in fact?) don't adapt like + * this, unfortunately. But some allows the user to manually + * configure the deadline. + */ + struct terminal *term = data; + + if (likely(buf->age != 1)) + return; + + if (likely(!term->render.preapply_last_frame_damage)) + return; + + if (term->render.last_buf == NULL) + return; + + if (term->render.last_buf->age != 0) + return; + + if (buf->width != term->render.last_buf->width) + return; + + if (buf->height != term->render.last_buf->height) + return; + + xassert(term->render.workers.count > 0); + xassert(term->render.last_buf != NULL); + + xassert(term->render.last_buf->age == 0); + xassert(term->render.last_buf != buf); + + mtx_lock(&term->render.workers.preapplied_damage.lock); + if (term->render.workers.preapplied_damage.buf != NULL) { + mtx_unlock(&term->render.workers.preapplied_damage.lock); + return; + } + + xassert(term->render.workers.preapplied_damage.buf == NULL); + term->render.workers.preapplied_damage.buf = buf; + term->render.workers.preapplied_damage.start = (struct timespec){0}; + term->render.workers.preapplied_damage.stop = (struct timespec){0}; + mtx_unlock(&term->render.workers.preapplied_damage.lock); + + mtx_lock(&term->render.workers.lock); + sem_post(&term->render.workers.start); + xassert(tll_length(term->render.workers.queue) == 0); + tll_push_back(term->render.workers.queue, -3); + mtx_unlock(&term->render.workers.lock); +} diff --git a/render.h b/render.h index 81d2a905..e21eaca8 100644 --- a/render.h +++ b/render.h @@ -47,3 +47,5 @@ struct csd_data { }; struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx); + +void render_buffer_release_callback(struct buffer *buf, void *data); diff --git a/shm.c b/shm.c index 31ea67ed..72b32f16 100644 --- a/shm.c +++ b/shm.c @@ -87,6 +87,9 @@ struct buffer_private { bool with_alpha; bool scrollable; + + void (*release_cb)(struct buffer *buf, void *data); + void *cb_data; }; struct buffer_chain { @@ -100,6 +103,9 @@ struct buffer_chain { pixman_format_code_t pixman_fmt_with_alpha; enum wl_shm_format shm_format_with_alpha; + + void (*release_cb)(struct buffer *buf, void *data); + void *cb_data; }; static tll(struct buffer_private *) deferred; @@ -232,6 +238,10 @@ buffer_release(void *data, struct wl_buffer *wl_buffer) xassert(found); if (!found) LOG_WARN("deferred delete: buffer not on the 'deferred' list"); + } else { + if (buffer->release_cb != NULL) { + buffer->release_cb(&buffer->public, buffer->cb_data); + } } } @@ -516,6 +526,8 @@ get_new_buffers(struct buffer_chain *chain, size_t count, .offset = 0, .size = sizes[i], .scrollable = chain->scrollable, + .release_cb = chain->release_cb, + .cb_data = chain->cb_data, }; if (!instantiate_offset(buf, offset)) { @@ -623,7 +635,7 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph * reuse. Pick the "youngest" one, and mark the * other one for purging */ if (buf->public.age < cached->public.age) { - shm_unref(&cached->public); + //shm_unref(&cached->public); cached = buf; } else { /* @@ -634,8 +646,8 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph * should be safe; "our" tll_foreach() already * holds the next pointer. */ - if (buffer_unref_no_remove_from_chain(buf)) - tll_remove(chain->bufs, it); + //if (buffer_unref_no_remove_from_chain(buf)) + // tll_remove(chain->bufs, it); } } } @@ -994,7 +1006,8 @@ shm_unref(struct buffer *_buf) struct buffer_chain * shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, - enum shm_bit_depth desired_bit_depth) + enum shm_bit_depth desired_bit_depth, + void (*release_cb)(struct buffer *buf, void *data), void *cb_data) { pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8; enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888; @@ -1090,6 +1103,9 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, .pixman_fmt_with_alpha = pixman_fmt_with_alpha, .shm_format_with_alpha = shm_fmt_with_alpha, + + .release_cb = release_cb, + .cb_data = cb_data, }; return chain; } diff --git a/shm.h b/shm.h index 6050f1c7..84eb4386 100644 --- a/shm.h +++ b/shm.h @@ -50,7 +50,8 @@ void shm_set_min_stride_alignment(size_t min_stride_alignment); struct buffer_chain; struct buffer_chain *shm_chain_new( struct wayland *wayl, bool scrollable, size_t pix_instances, - enum shm_bit_depth desired_bit_depth); + enum shm_bit_depth desired_bit_depth, + void (*release_cb)(struct buffer *buf, void *data), void *cb_data); void shm_chain_free(struct buffer_chain *chain); enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain); diff --git a/terminal.c b/terminal.c index 60506d07..36f8513b 100644 --- a/terminal.c +++ b/terminal.c @@ -719,6 +719,9 @@ initialize_render_workers(struct terminal *term) goto err_sem_destroy; } + mtx_init(&term->render.workers.preapplied_damage.lock, mtx_plain); + cnd_init(&term->render.workers.preapplied_damage.cond); + term->render.workers.threads = xcalloc( term->render.workers.count, sizeof(term->render.workers.threads[0])); @@ -1356,13 +1359,13 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .render = { .chains = { .grid = shm_chain_new(wayl, true, 1 + conf->render_worker_count, - desired_bit_depth), - .search = shm_chain_new(wayl, false, 1 ,desired_bit_depth), - .scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth), - .render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth), - .url = shm_chain_new(wayl, false, 1, desired_bit_depth), - .csd = shm_chain_new(wayl, false, 1, desired_bit_depth), - .overlay = shm_chain_new(wayl, false, 1, desired_bit_depth), + desired_bit_depth, &render_buffer_release_callback, term), + .search = shm_chain_new(wayl, false, 1 ,desired_bit_depth, NULL, NULL), + .scrollback_indicator = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), + .render_timer = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), + .url = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), + .csd = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), + .overlay = shm_chain_new(wayl, false, 1, desired_bit_depth, NULL, NULL), }, .scrollback_lines = conf->scrollback.lines, .app_sync_updates.timer_fd = app_sync_updates_fd, @@ -1893,6 +1896,8 @@ term_destroy(struct terminal *term) } } free(term->render.workers.threads); + mtx_destroy(&term->render.workers.preapplied_damage.lock); + cnd_destroy(&term->render.workers.preapplied_damage.cond); mtx_destroy(&term->render.workers.lock); sem_destroy(&term->render.workers.start); sem_destroy(&term->render.workers.done); diff --git a/terminal.h b/terminal.h index 88371b07..364d57b3 100644 --- a/terminal.h +++ b/terminal.h @@ -706,6 +706,14 @@ struct terminal { tll(int) queue; thrd_t *threads; struct buffer *buf; + + struct { + mtx_t lock; + cnd_t cond; + struct buffer *buf; + struct timespec start; + struct timespec stop; + } preapplied_damage; } workers; /* Last rendered cursor position */ @@ -716,6 +724,8 @@ struct terminal { } last_cursor; struct buffer *last_buf; /* Buffer we rendered to last time */ + size_t frames_since_last_immediate_release; + bool preapply_last_frame_damage; enum overlay_style last_overlay_style; struct buffer *last_overlay_buf; From fd88c6c61c52b75789c7f9d723815b67aed2984f Mon Sep 17 00:00:00 2001 From: Charalampos Mitrodimas Date: Thu, 2 Oct 2025 00:29:34 +0300 Subject: [PATCH 286/353] wayland: restore opacity after exiting fullscreen When exiting fullscreen mode, the window's transparency was not being restored, leaving it opaque until another window was fullscreened. This occurred because the Wayland opaque region was set based only on the configured alpha value, without considering the fullscreen state. Since commit 899b768b74 ("render: disable transparency when we're fullscreened") transparency is disabled during fullscreen to avoid compositor-mandated black backgrounds affecting the intended colors. However, the opaque region was not being updated when the fullscreen state changed. Fixes: https://codeberg.org/dnkl/foot/issues/2180 Signed-off-by: Charalampos Mitrodimas --- wayland.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/wayland.c b/wayland.c index 5f68ecf7..59b2a33e 100644 --- a/wayland.c +++ b/wayland.c @@ -1064,6 +1064,7 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, bool wasnt_configured = !win->is_configured; bool was_resizing = win->is_resizing; + bool was_fullscreen = win->is_fullscreen; bool csd_was_enabled = win->csd_mode == CSD_YES && !win->is_fullscreen; int new_width = win->configure.width; int new_height = win->configure.height; @@ -1096,6 +1097,10 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, else if (csd_was_enabled && !enable_csd) csd_destroy(win); + /* Update opaque region if fullscreen state changed */ + if (was_fullscreen != win->is_fullscreen) + wayl_win_alpha_changed(win); + if (enable_csd && new_width > 0 && new_height > 0) { if (wayl_win_csd_titlebar_visible(win)) new_height -= win->term->conf->csd.title_height; @@ -2401,7 +2406,13 @@ wayl_win_alpha_changed(struct wl_window *win) { struct terminal *term = win->term; - if (term->colors.alpha == 0xffff) { + /* + * When fullscreened, transparency is disabled (see render.c). + * Update the opaque region to match. + */ + bool is_opaque = term->colors.alpha == 0xffff || win->is_fullscreen; + + if (is_opaque) { struct wl_region *region = wl_compositor_create_region( term->wl->compositor); From e308a4733e4149b661457c9a275e48d9dbeaf23f Mon Sep 17 00:00:00 2001 From: Matthias Heyman Date: Fri, 26 Sep 2025 10:59:48 +0200 Subject: [PATCH 287/353] fix: jump labels are more readable --- themes/modus-operandi | 2 ++ 1 file changed, 2 insertions(+) diff --git a/themes/modus-operandi b/themes/modus-operandi index 5e3a9fd6..2d417bb5 100644 --- a/themes/modus-operandi +++ b/themes/modus-operandi @@ -22,3 +22,5 @@ bright4=2544bb bright5=5317ac bright6=005a5f bright7=ffffff + +jump-labels=dce0e8 0000ff From 371837ef7b23b3e9f574069671d06a96e73526aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 10 Oct 2025 10:36:41 +0200 Subject: [PATCH 288/353] changelog: updated jump label colors in modus-operandi --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65f0bbab..314f7b38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,8 @@ configurable by setting `tweak.min-stride-alignment`). This allows compositor to directly import foot's SHM buffers to the GPU, with e.g. integrated graphics ([#2182][2182]). +* Jump label colors in the modus-operandi theme, for improved + readability. [2182]: https://codeberg.org/dnkl/foot/issues/2182 From 7ed36c10334c0b2480fa026238f8f1791d2f9439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 10 Oct 2025 11:10:38 +0200 Subject: [PATCH 289/353] config: add colors.dim-blend-towards=black|white Before this patch, we always blended towards black when dimming text. However, with light color themes, it usually looks better if we dim towards white instead. This option allows you to choose which color to blend towards. The default is 'black' in '[colors]', and 'white' in '[colors2]'. Closes #2187 --- CHANGELOG.md | 4 ++++ config.c | 15 ++++++++++++++- config.h | 5 +++++ doc/foot.ini.5.scd | 14 ++++++++++++-- foot.ini | 3 +++ render.c | 9 ++++++++- tests/test-config.c | 5 +++++ 7 files changed, 51 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 314f7b38..76183459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,8 +71,12 @@ * Performance increased and input latency decreased on compositors that do not release SHM buffers immediately ([#2188][2188]). +* `colors{,2}.dim-blend-towards=black|white` option, allowing you to + select towards which color to blend when dimming text. Defaults to + `black` in `[colors]`, and `white` in `[colors2]` ([#2187][2187]). [2188]: https://codeberg.org/dnkl/foot/issues/2188 +[2187]: https://codeberg.org/dnkl/foot/issues/2187 ### Changed diff --git a/config.c b/config.c index 06817247..4449d9c2 100644 --- a/config.c +++ b/config.c @@ -1519,7 +1519,7 @@ parse_color_theme(struct context *ctx, struct color_theme *theme) return true; } - else if (strcmp(key, "alpha-mode") == 0) { + else if (streq(key, "alpha-mode")) { _Static_assert(sizeof(theme->alpha_mode) == sizeof(int), "enum is not 32-bit"); @@ -1529,6 +1529,16 @@ parse_color_theme(struct context *ctx, struct color_theme *theme) (int *)&theme->alpha_mode); } + else if (streq(key, "dim-blend-towards")) { + _Static_assert(sizeof(theme->dim_blend_towards) == sizeof(int), + "enum is not 32-bit"); + + return value_to_enum( + ctx, + (const char *[]){"black", "white", NULL}, + (int *)&theme->dim_blend_towards); + } + else { LOG_CONTEXTUAL_ERR("not valid option"); return false; @@ -3428,6 +3438,7 @@ config_load(struct config *conf, const char *conf_path, .flash_alpha = 0x7fff, .alpha = 0xffff, .alpha_mode = ALPHA_MODE_DEFAULT, + .dim_blend_towards = DIM_BLEND_TOWARDS_BLACK, .selection_fg = 0x80000000, /* Use default bg */ .selection_bg = 0x80000000, /* Use default fg */ .cursor = { @@ -3523,6 +3534,8 @@ config_load(struct config *conf, const char *conf_path, memcpy(conf->colors.table, default_color_table, sizeof(default_color_table)); memcpy(conf->colors.sixel, default_sixel_colors, sizeof(default_sixel_colors)); memcpy(&conf->colors2, &conf->colors, sizeof(conf->colors)); + conf->colors2.dim_blend_towards = DIM_BLEND_TOWARDS_WHITE; + parse_modifiers(XKB_MOD_NAME_SHIFT, 5, &conf->mouse.selection_override_modifiers); tokenize_cmdline( diff --git a/config.h b/config.h index 5b7ff11e..37b3259f 100644 --- a/config.h +++ b/config.h @@ -145,6 +145,11 @@ struct color_theme { uint32_t dim[8]; uint32_t sixel[16]; + enum { + DIM_BLEND_TOWARDS_BLACK, + DIM_BLEND_TOWARDS_WHITE, + } dim_blend_towards; + enum { ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 7b08d5d4..8697add2 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1031,7 +1031,8 @@ dark theme (since the default theme is dark). a color value, and a "dim" attribute. By default, foot implements this by blending the current color - with black. This is a generic approach that applies to both + with black or white, depending on what the *dim-blend-towards* + option is set to . This is a generic approach that applies to both colors from the 256-color palette, as well as 24-bit RGB colors. You can change this behavior by setting the *dimN* options. When @@ -1086,6 +1087,14 @@ dark theme (since the default theme is dark). Default: _default_ +*dim-blend-towards* + Which color to blend towards when "auto" dimming a color (see + *dim0*..*dim7* above). One of *black* or *white*. Blending towards + black makes the text darker, while blending towards white makes it + whiter (but still dimmer than normal text). + + Default: _black_ (*colors*), _white_ (*colors2*) + *selection-foreground*, *selection-background* Foreground (text) and background color to use in selected text. Default: _inverse foreground/background_. @@ -1124,7 +1133,8 @@ dark theme (since the default theme is dark). # SECTION: colors2 This section defines an alternative color theme. It has the exact same -keys as the *colors* section. The default values are the same. +keys as the *colors* section. The default values are the same, except +for *dim-blend-towards*, which defaults to *white* instead. Note that values are not inherited. That is, if you set a value in *colors*, but not in *colors2*, the value from *colors* is not diff --git a/foot.ini b/foot.ini index 44ed5785..2d170489 100644 --- a/foot.ini +++ b/foot.ini @@ -132,6 +132,7 @@ # bright7=ffffff # bright white ## dimmed colors (see foot.ini(5) man page) +# dim-blend-towards=black # dim0= # ... # dim7= @@ -170,6 +171,8 @@ [colors2] # Alternative color theme, see man page foot.ini(5) +# Same builtin defaults as [color], except for: +# dim-blend-towards=white [csd] # preferred=server diff --git a/render.c b/render.c index 35752125..fd721395 100644 --- a/render.c +++ b/render.c @@ -312,7 +312,14 @@ color_dim(const struct terminal *term, uint32_t color) } } - return color_blend_towards(color, 0x00000000, conf->dim.amount); + const struct color_theme *theme = term->colors.active_theme == COLOR_THEME1 + ? &conf->colors + : &conf->colors2; + + return color_blend_towards( + color, + theme->dim_blend_towards == DIM_BLEND_TOWARDS_BLACK ? 0x00000000 : 0x00ffffff, + conf->dim.amount); } static inline uint32_t diff --git a/tests/test-config.c b/tests/test-config.c index 64b61540..c442e700 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -753,6 +753,11 @@ test_section_colors(void) (int []){ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL}, (int *)&conf.colors.alpha_mode); + test_enum(&ctx, &parse_section_colors, "dim-blend-towards", 2, + (const char *[]){"black", "white"}, + (int []){DIM_BLEND_TOWARDS_BLACK, DIM_BLEND_TOWARDS_WHITE}, + (int *)&conf.colors.dim_blend_towards); + for (size_t i = 0; i < 255; i++) { char key_name[4]; sprintf(key_name, "%zu", i); From 96605bf52fa3794ee2f13739831ad92229b4b3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 11 Oct 2025 10:05:26 +0200 Subject: [PATCH 290/353] extract: number of spaces after the tab shouldn't include the tab cell itself This fixes an off by one, where we sometimes "ate" an extra space when extracting contents with tabs. This happened if the tab (and its subsequent spaces) were followed by an additional space. Closes #2194 --- CHANGELOG.md | 3 +++ extract.c | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76183459..3935889a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,8 +98,11 @@ * URL labels misplaces when URL contains double-width characters ([#2179][2179]). +* One space too much consumed when copying (or pipe:ing) contents with + tabs ([#2194][2194]) [2179]: https://codeberg.org/dnkl/foot/issues/2179 +[2194]: https://codeberg.org/dnkl/foot/issues/2194 ### Security diff --git a/extract.c b/extract.c index 31c32248..cd9a0c95 100644 --- a/extract.c +++ b/extract.c @@ -256,8 +256,8 @@ extract_one(const struct terminal *term, const struct row *row, } } - xassert(next_tab_stop >= col); - ctx->tab_spaces_left = next_tab_stop - col; + if (next_tab_stop > col) + ctx->tab_spaces_left = next_tab_stop - col - 1; } } From dbf18ba444e728ecddcc0e15fc44cc1fc0590b53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 15 Oct 2025 09:41:52 +0200 Subject: [PATCH 291/353] wayland: always render a new frame after a fullscreen change This is needed, since we disable alpha in fullscreen, and since we use different image buffer formats (XRGB vs. ARGB) when we have alpha vs. when we don't (and fullscreen always disables alpha). Normally, this happens anyway, as the window is resized when going in or out from fullscreen. But, it's technically possible for a compositor to change an application's fullscreen state without resizing the window. --- CHANGELOG.md | 4 ++++ wayland.c | 27 +++++++++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3935889a..ed8dff69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,10 @@ ([#2179][2179]). * One space too much consumed when copying (or pipe:ing) contents with tabs ([#2194][2194]) +* Ensure we render a new frame when changing fullscreen state. Before, + this was automatically done if the window was also resized. But, it + is possible for a compositor to change an application's fullscreen + state without resizing the window. [2179]: https://codeberg.org/dnkl/foot/issues/2179 [2194]: https://codeberg.org/dnkl/foot/issues/2194 diff --git a/wayland.c b/wayland.c index 59b2a33e..bac087fb 100644 --- a/wayland.c +++ b/wayland.c @@ -1097,10 +1097,6 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, else if (csd_was_enabled && !enable_csd) csd_destroy(win); - /* Update opaque region if fullscreen state changed */ - if (was_fullscreen != win->is_fullscreen) - wayl_win_alpha_changed(win); - if (enable_csd && new_width > 0 && new_height > 0) { if (wayl_win_csd_titlebar_visible(win)) new_height -= win->term->conf->csd.title_height; @@ -1139,11 +1135,26 @@ xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, else term_visual_focus_out(term); - if (!resized && !term->render.pending.grid) { + /* + * Update opaque region if fullscreen state changed, also need to + * render, since we use different buffer types with and without + * alpha + */ + if (was_fullscreen != win->is_fullscreen) { + wayl_win_alpha_changed(win); + render_refresh(term); + } + + const bool will_render_soon = resized || + term->render.refresh.grid || + term->render.pending.grid; + + if (!will_render_soon) { /* - * If we didn't resize, we won't be committing a new surface - * anytime soon. Some compositors require a commit in - * combination with an ack - make them happy. + * If we didn't resize, and aren't refreshing for other + * reasons, we won't be committing a new surface anytime + * soon. Some compositors require a commit in combination with + * an ack - make them happy. */ wl_surface_commit(win->surface.surf); } From 612adda3842fcdbbc9b7e1e2f46be02a224da1eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 16 Oct 2025 08:45:07 +0200 Subject: [PATCH 292/353] render: don't warn about immediate buffer release if pre-apply-damage has been activated --- render.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/render.c b/render.c index fd721395..1d0f08af 100644 --- a/render.c +++ b/render.c @@ -3372,7 +3372,7 @@ grid_render(struct terminal *term) { LOG_INFO("enabling pre-applied frame damage"); term->render.preapply_last_frame_damage = true; - } else if (!have_warned) { + } else if (!have_warned && !term->render.preapply_last_frame_damage) { LOG_WARN("compositor is not releasing buffers immediately; " "expect lower rendering performance"); have_warned = true; From dc5a921d2c5561f6d8f857ab3483e2fd1c59026f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 16 Oct 2025 08:46:36 +0200 Subject: [PATCH 293/353] changelog: prepare for 1.25.0 --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed8dff69..ce7d318d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.25.0](#1-25-0) * [1.24.0](#1-24-0) * [1.23.1](#1-23-1) * [1.23.0](#1-23-0) @@ -66,7 +66,8 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.25.0 + ### Added * Performance increased and input latency decreased on compositors @@ -92,8 +93,6 @@ [2182]: https://codeberg.org/dnkl/foot/issues/2182 -### Deprecated -### Removed ### Fixed * URL labels misplaces when URL contains double-width characters @@ -109,9 +108,11 @@ [2194]: https://codeberg.org/dnkl/foot/issues/2194 -### Security ### Contributors +* Charalampos Mitrodimas +* Matthias Heyman + ## 1.24.0 From b44a62724cd51c7fecdfd9d1b41a3691b11a4c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 16 Oct 2025 08:46:58 +0200 Subject: [PATCH 294/353] meson: bump version to 1.25.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 305df13c..a1d0104d 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.24.0', + version: '1.25.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 82e75851e41fd245dc08e71f1618ae4aeede7fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 16 Oct 2025 08:50:31 +0200 Subject: [PATCH 295/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce7d318d..d2eebef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.25.0](#1-25-0) * [1.24.0](#1-24-0) * [1.23.1](#1-23-1) @@ -66,6 +67,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.25.0 ### Added From 558760446932f0f22c794084285cf998462dd706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 18 Oct 2025 08:23:53 +0200 Subject: [PATCH 296/353] input: keymap(): use a goto-label on error, to ensure we always close the keymap FD --- input.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/input.c b/input.c index fe5a8001..44a99e3b 100644 --- a/input.c +++ b/input.c @@ -576,23 +576,20 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, /* Verify keymap is in a format we understand */ switch ((enum wl_keyboard_keymap_format)format) { case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP: - close(fd); - return; + goto err; case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1: break; default: LOG_WARN("unrecognized keymap format: %u", format); - close(fd); - return; + goto err; } char *map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (map_str == MAP_FAILED) { LOG_ERRNO("failed to mmap keyboard keymap"); - close(fd); - return; + goto err; } while (map_str[size - 1] == '\0') @@ -605,6 +602,8 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, } + munmap(map_str, size); + if (seat->kbd.xkb_keymap != NULL) { seat->kbd.xkb_state = xkb_state_new(seat->kbd.xkb_keymap); @@ -685,10 +684,10 @@ keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, seat->kbd.key_arrow_down = xkb_keymap_key_by_name(seat->kbd.xkb_keymap, "DOWN"); } - munmap(map_str, size); - close(fd); - key_binding_load_keymap(wayl->key_binding_manager, seat); + +err: + close(fd); } static void From 19466a21d8b7e580ef611ca56b81e7568fc44b37 Mon Sep 17 00:00:00 2001 From: Andrei Date: Fri, 24 Oct 2025 11:08:57 -0700 Subject: [PATCH 297/353] doc: foot.ini: fix typo --- doc/foot.ini.5.scd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 8697add2..2b57c467 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -2120,7 +2120,7 @@ any of these options. frame, foot proceeds with rendering the cells that has changed between the last frame and the new frame. - When this open is enabled, the changes between the last two frames + When this option is enabled, the changes between the last two frames are brought over to what will become the next frame before foot starts rendering the next frame. As soon as the compositor releases the previous buffer (typically right after foot has From 71de0c45bc38ee773f86bb4d48f96719c8abb568 Mon Sep 17 00:00:00 2001 From: c4llv07e Date: Mon, 27 Oct 2025 13:24:07 +0300 Subject: [PATCH 298/353] char32: add helper functions to work with c32 case --- char32.c | 22 ++++++++++++++++++++++ char32.h | 15 +++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/char32.c b/char32.c index 827cef8d..3d6c2c78 100644 --- a/char32.c +++ b/char32.c @@ -53,6 +53,14 @@ UNITTEST xassert(c32cmp(U"b", U"a") > 0); } +UNITTEST +{ + xassert(c32ncmp(U"foo", U"foot", 3) == 0); + xassert(c32ncmp(U"foot", U"FOOT", 4) > 0); + xassert(c32ncmp(U"a", U"b", 1) < 0); + xassert(c32ncmp(U"bb", U"aa", 2) > 0); +} + UNITTEST { char32_t copy[16]; @@ -127,6 +135,20 @@ UNITTEST xassert(c32cmp(dst, U"foobar12345678") == 0); } +UNITTEST +{ + xassert(!isc32upper(U'a')); + xassert(isc32upper(U'A')); + xassert(!isc32upper(U'a')); +} + +UNITTEST +{ + xassert(hasc32upper(U"abc1A")); + xassert(!hasc32upper(U"abc1_aaa")); + xassert(!hasc32upper(U"")); +} + UNITTEST { char32_t *c = xc32dup(U"foobar"); diff --git a/char32.h b/char32.h index 6a5eb080..dcb412ce 100644 --- a/char32.h +++ b/char32.h @@ -20,6 +20,10 @@ static inline int c32cmp(const char32_t *s1, const char32_t *s2) { return wcscmp((const wchar_t *)s1, (const wchar_t *)s2); } +static inline int c32ncmp(const char32_t *s1, const char32_t *s2, size_t n) { + return wcsncmp((const wchar_t *)s1, (const wchar_t *)s2, n); +} + static inline char32_t *c32ncpy(char32_t *dst, const char32_t *src, size_t n) { return (char32_t *)wcsncpy((wchar_t *)dst, (const wchar_t *)src, n); } @@ -60,6 +64,10 @@ static inline char32_t toc32upper(char32_t c) { return (char32_t)towupper((wint_t)c); } +static inline bool isc32upper(char32_t c32) { + return iswupper((wint_t)c32); +} + static inline bool isc32space(char32_t c32) { return iswspace((wint_t)c32); } @@ -72,6 +80,13 @@ static inline bool isc32graph(char32_t c32) { return iswgraph((wint_t)c32); } +static inline bool hasc32upper(const char32_t *s) { + for (int i = 0; s[i] != '\0'; i++) { + if (isc32upper(s[i])) return true; + } + return false; +} + static inline int c32width(char32_t c) { #if defined(FOOT_GRAPHEME_CLUSTERING) return utf8proc_charwidth((utf8proc_int32_t)c); From 5ae4955e834831b5680baaad7c4f974e407ffb7a Mon Sep 17 00:00:00 2001 From: c4llv07e Date: Mon, 27 Oct 2025 13:25:48 +0300 Subject: [PATCH 299/353] search: use case insensitive search only if there's no uppercase in search --- CHANGELOG.md | 2 ++ search.c | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2eebef8..81f7a168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,6 +100,8 @@ e.g. integrated graphics ([#2182][2182]). * Jump label colors in the modus-operandi theme, for improved readability. +* Scrollback search is now case sensitive when the search string + contains at least one upper case character. [2182]: https://codeberg.org/dnkl/foot/issues/2182 diff --git a/search.c b/search.c index dda84e6d..5a2b6236 100644 --- a/search.c +++ b/search.c @@ -283,8 +283,13 @@ matches_cell(const struct terminal *term, const struct cell *cell, size_t search if (composed == NULL && base == 0 && term->search.buf[search_ofs] == U' ') return 1; - if (c32ncasecmp(&base, &term->search.buf[search_ofs], 1) != 0) - return -1; + if (hasc32upper(term->search.buf)) { + if (c32ncmp(&base, &term->search.buf[search_ofs], 1) != 0) + return -1; + } else { + if (c32ncasecmp(&base, &term->search.buf[search_ofs], 1) != 0) + return -1; + } if (composed != NULL) { if (search_ofs + composed->count > term->search.len) From 143f220527a0cffebcecbd5eaa0dccd5e009122b Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Fri, 31 Oct 2025 15:11:53 -0700 Subject: [PATCH 300/353] search: do not emit composing keys When we are in the composing state for XCompose key sequences, we should not add the compose component keys to the search buffer. --- CHANGELOG.md | 4 ++++ search.c | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f7a168..f00b5d15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,10 @@ ### Deprecated ### Removed ### Fixed + +* Search mode: composing keys not ignored. + + ### Security ### Contributors diff --git a/search.c b/search.c index 5a2b6236..5228bf61 100644 --- a/search.c +++ b/search.c @@ -1484,7 +1484,8 @@ search_input(struct seat *seat, struct terminal *term, count = xkb_compose_state_get_utf8( seat->kbd.xkb_compose_state, (char *)buf, sizeof(buf)); xkb_compose_state_reset(seat->kbd.xkb_compose_state); - } else if (compose_status == XKB_COMPOSE_CANCELLED) { + } else if (compose_status == XKB_COMPOSE_CANCELLED || + compose_status == XKB_COMPOSE_COMPOSING) { count = 0; } else { count = xkb_state_key_get_utf8( From 9728ada0289c446585325a74ded9c94958d9ab24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 30 Oct 2025 06:29:51 +0100 Subject: [PATCH 301/353] csi: focus mode (private mode 1004): send focus event immediate, when enabled This lets the application now the current state, without having to wait for the user to switch focus. Fixes #2202 --- CHANGELOG.md | 8 ++++++++ csi.c | 2 ++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f00b5d15..21d2c5f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,14 @@ ## Unreleased ### Added ### Changed + +* When enabling _"focus mode"_ (private mode 1004), foot now sends a + focus event immediately, to inform the application what the current + state is ([#2202][2202]). + +[2202]: https://codeberg.org/dnkl/foot/issues/2202 + + ### Deprecated ### Removed ### Fixed diff --git a/csi.c b/csi.c index 437fd8bc..c5f616ac 100644 --- a/csi.c +++ b/csi.c @@ -422,6 +422,8 @@ decset_decrst(struct terminal *term, unsigned param, bool enable) case 1004: term->focus_events = enable; + if (enable) + term_to_slave(term, term->kbd_focus ? "\033[I" : "\033[O", 3); break; case 1005: From 1fce0e69f5ec2c24d518917c0538a4d762b8a1e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 1 Nov 2025 08:12:52 +0100 Subject: [PATCH 302/353] changelog: case sensitive scrollback search: move to correct release --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d2c5f5..e7f71ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,8 @@ * When enabling _"focus mode"_ (private mode 1004), foot now sends a focus event immediately, to inform the application what the current state is ([#2202][2202]). +* Scrollback search is now case sensitive when the search string + contains at least one upper case character. [2202]: https://codeberg.org/dnkl/foot/issues/2202 @@ -112,8 +114,6 @@ e.g. integrated graphics ([#2182][2182]). * Jump label colors in the modus-operandi theme, for improved readability. -* Scrollback search is now case sensitive when the search string - contains at least one upper case character. [2182]: https://codeberg.org/dnkl/foot/issues/2182 From 5cb8ff2e9c51c528589e5e1ef78b9a6bd554fc0d Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Fri, 7 Nov 2025 07:32:11 +0100 Subject: [PATCH 303/353] Fix assertion failure triple-clicking line with quote in last column By default, triple-click tries to select quoted strings within a logical line. This also works if the line spans multiple screen lines. If there is a quote character in the last column: printf %"$COLUMNS"s \'; printf wrapped; sleep inf and I triple-click on the following soft-wrapped line, there's an assertion failure because the column next to the quote is out of range. The quote position has been found by walking at least one cell backwards from "pos". This means that if the quote position is in the very last column, there must be a row below. Also move the assertion to be a pre-condition, though that's debatable. --- selection.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/selection.c b/selection.c index d7aa617a..f07396a5 100644 --- a/selection.c +++ b/selection.c @@ -19,6 +19,7 @@ #include "char32.h" #include "commands.h" #include "config.h" +#include "debug.h" #include "extract.h" #include "grid.h" #include "misc.h" @@ -558,9 +559,15 @@ selection_find_quote_left(struct terminal *term, struct coord *pos, if (*quote_char == '\0' ? (wc == '"' || wc == '\'') : wc == *quote_char) { - pos->row = next_row; - pos->col = next_col + 1; - xassert(pos->col < term->cols); + xassert(next_col + 1 <= term->cols); + if (next_col + 1 == term->cols) { + xassert(next_row < pos->row); + pos->row = next_row + 1; + pos->col = 0; + } else { + pos->row = next_row; + pos->col = next_col + 1; + } *quote_char = wc; return true; From c9abab08079a0b6eeb4f3fc9beb80eb5fd621730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 12 Nov 2025 07:46:34 +0100 Subject: [PATCH 304/353] changelog: triple-click when there's a quote in the last column --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f71ed5..654ba94f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,6 +85,8 @@ ### Fixed * Search mode: composing keys not ignored. +* Crash when triple-clicking a soft-wrapped line and there is a quote + character in the last column. ### Security From fc9625678fc7e295e7a7e03a5d47db21d4be3010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 12 Nov 2025 11:04:25 +0100 Subject: [PATCH 305/353] config: add toplevel-tag=TAG Add support for the new xdg-toplevel-tag-v1 Wayland protocol, by exposing a new config option, `toplevel-tag`, and a corresponding command option, `--toplevel-tag` (in both `foot` and `footclient`). This can help the compositor with session management, or custom window rules. Closes #2212 --- CHANGELOG.md | 9 +++++++++ client.c | 12 ++++++++++++ completions/bash/foot | 5 +++-- completions/bash/footclient | 5 +++-- completions/fish/foot.fish | 1 + completions/fish/footclient.fish | 1 + completions/zsh/_foot | 1 + completions/zsh/_footclient | 1 + config.c | 6 ++++++ config.h | 1 + doc/foot.1.scd | 5 +++++ doc/foot.ini.5.scd | 5 +++++ doc/footclient.1.scd | 5 +++++ foot-features.c | 6 ++++++ main.c | 7 +++++++ meson.build | 7 ++++++- tests/test-config.c | 1 + wayland.c | 33 +++++++++++++++++++++++++++++++- wayland.h | 8 ++++++++ 19 files changed, 113 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 654ba94f..a293c721 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,15 @@ ## Unreleased ### Added + +* `toplevel-tag` option (and `--toplevel-tag` command line options to + `foot` and `footclient`), allowing you to set a custom toplevel + tag. The compositor must implement the new `xdg-toplevel-tag-v1` + Wayland protocol ([#2212][2212]). + +[2212]: https://codeberg.org/dnkl/foot/issues/2212 + + ### Changed * When enabling _"focus mode"_ (private mode 1004), foot now sends a diff --git a/client.c b/client.c index aa5302be..85bc7fda 100644 --- a/client.c +++ b/client.c @@ -76,6 +76,7 @@ print_usage(const char *prog_name) " -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n" " -T,--title=TITLE initial window title (foot)\n" " -a,--app-id=ID window application ID (foot)\n" + " --toplevel-tag=TAG set a custom toplevel tag\n" " -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n" " -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n" " -m,--maximized start in maximized mode\n" @@ -137,6 +138,10 @@ send_string_list(int fd, const string_list_t *string_list) return true; } +enum { + TOPLEVEL_TAG_OPTION = CHAR_MAX + 1, +}; + int main(int argc, char *const *argv) { @@ -151,6 +156,7 @@ main(int argc, char *const *argv) {"term", required_argument, NULL, 't'}, {"title", required_argument, NULL, 'T'}, {"app-id", required_argument, NULL, 'a'}, + {"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION}, {"window-size-pixels", required_argument, NULL, 'w'}, {"window-size-chars", required_argument, NULL, 'W'}, {"maximized", no_argument, NULL, 'm'}, @@ -220,6 +226,12 @@ main(int argc, char *const *argv) goto err; break; + case TOPLEVEL_TAG_OPTION: + snprintf(buf, sizeof(buf), "toplevel-tag=%s", optarg); + if (!push_string(&overrides, buf, &total_len)) + goto err; + break; + case 'L': if (!push_string(&overrides, "login-shell=yes", &total_len)) goto err; diff --git a/completions/bash/foot b/completions/bash/foot index 25aa2c49..e27be2fa 100644 --- a/completions/bash/foot +++ b/completions/bash/foot @@ -6,6 +6,7 @@ _foot() local cur prev flags word commands match previous_words i offset flags=( "--app-id" + "--toplevel-tag" "--check-config" "--config" "--font" @@ -40,7 +41,7 @@ _foot() for word in "${previous_words[@]}" ; do match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null) if [[ ! -z "$match" ]] ; then - if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--config|--font|--log-level|--pty|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then + if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--toplevel-tag|--config|--font|--log-level|--pty|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then (( i++ )) continue fi @@ -75,7 +76,7 @@ _foot() COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;; --log-colorize|-l) COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;; - --app-id|--help|--override|--pty|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC]) + --app-id|--toplevel-tag|--help|--override|--pty|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC]) # Don't autocomplete for these flags : ;; *) diff --git a/completions/bash/footclient b/completions/bash/footclient index 62abdd65..c7f1df4e 100644 --- a/completions/bash/footclient +++ b/completions/bash/footclient @@ -6,6 +6,7 @@ _footclient() local cur prev flags word commands match previous_words i offset flags=( "--app-id" + "--toplevel-tag" "--fullscreen" "--help" "--hold" @@ -35,7 +36,7 @@ _footclient() for word in "${previous_words[@]}" ; do match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null) if [[ ! -z "$match" ]] ; then - if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--log-level|--server-socket|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then + if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--toplevel-tag|--log-level|--server-socket|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then (( i++ )) continue fi @@ -67,7 +68,7 @@ _footclient() COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;; --log-colorize|-l) COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;; - --app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|-[ahoTvWw]) + --app-id|--toplevel-tag|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|-[ahoTvWw]) # Don't autocomplete for these flags : ;; *) diff --git a/completions/fish/foot.fish b/completions/fish/foot.fish index 0053d18d..21b42d3d 100644 --- a/completions/fish/foot.fish +++ b/completions/fish/foot.fish @@ -6,6 +6,7 @@ complete -c foot -x -s f -l font -a "(fc-list : family | sed 's/,/ complete -c foot -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)" complete -c foot -x -s T -l title -d "initial window title" complete -c foot -x -s a -l app-id -d "value to set the app-id property on the Wayland window to (foot)" +complete -c foot -x -l toplevel-tag -d "value to set the toplevel-tag property on the Wayland window to" complete -c foot -s m -l maximized -d "start in maximized mode" complete -c foot -s F -l fullscreen -d "start in fullscreen mode" complete -c foot -s L -l login-shell -d "start shell as a login shell" diff --git a/completions/fish/footclient.fish b/completions/fish/footclient.fish index df3e1273..03624796 100644 --- a/completions/fish/footclient.fish +++ b/completions/fish/footclient.fish @@ -2,6 +2,7 @@ complete -c footclient -x -a "(__fish_complete_subcom complete -c footclient -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)" complete -c footclient -x -s T -l title -d "initial window title" complete -c footclient -x -s a -l app-id -d "value to set the app-id property on the Wayland window to (foot)" +complete -c footclient -x -l toplevel-tag -d "value to set the toplevel-tag property on the Wayland window to" complete -c footclient -s m -l maximized -d "start in maximized mode" complete -c footclient -s F -l fullscreen -d "start in fullscreen mode" complete -c footclient -s L -l login-shell -d "start shell as a login shell" diff --git a/completions/zsh/_foot b/completions/zsh/_foot index 2a0dc7b0..0fd83b3c 100644 --- a/completions/zsh/_foot +++ b/completions/zsh/_foot @@ -9,6 +9,7 @@ _arguments \ '(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \ '(-T --title)'{-T,--title}'[initial window title]:()' \ '(-a --app-id)'{-a,--app-id}'[value to set the app-id property on the Wayland window to (foot)]:()' \ + '--toplevel-tag=[value to set the toplevel-tag property on the Wayland window to]:()' \ '(-m --maximized)'{-m,--maximized}'[start in maximized mode]' \ '(-F --fullscreen)'{-F,--fullscreen}'[start in fullscreen mode]' \ '(-L --login-shell)'{-L,--login-shell}'[start shell as a login shell]' \ diff --git a/completions/zsh/_footclient b/completions/zsh/_footclient index c14d65d5..12f29d7a 100644 --- a/completions/zsh/_footclient +++ b/completions/zsh/_footclient @@ -5,6 +5,7 @@ _arguments \ '(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \ '(-T --title)'{-T,--title}'[initial window title]:()' \ '(-a --app-id)'{-a,--app-id}'[value to set the app-id property on the Wayland window to (foot)]:()' \ + '--toplevel-tag=[value to set the toplevel-tag property on the Wayland window to]:()' \ '(-m --maximized)'{-m,--maximized}'[start in maximized mode]' \ '(-F --fullscreen)'{-F,--fullscreen}'[start in fullscreen mode]' \ '(-L --login-shell)'{-L,--login-shell}'[start shell as a login shell]' \ diff --git a/config.c b/config.c index 4449d9c2..515b088c 100644 --- a/config.c +++ b/config.c @@ -923,6 +923,9 @@ parse_section_main(struct context *ctx) else if (streq(key, "app-id")) return value_to_str(ctx, &conf->app_id); + else if (streq(key, "toplevel-tag")) + return value_to_str(ctx, &conf->toplevel_tag); + else if (streq(key, "initial-window-size-pixels")) { if (!value_to_dimensions(ctx, &conf->size.width, &conf->size.height)) return false; @@ -3371,6 +3374,7 @@ config_load(struct config *conf, const char *conf_path, .shell = get_shell(), .title = xstrdup("foot"), .app_id = (as_server ? xstrdup("footclient") : xstrdup("foot")), + .toplevel_tag = xstrdup(""), .word_delimiters = xc32dup(U",│`|:\"'()[]{}<>"), .size = { .type = CONF_SIZE_PX, @@ -3823,6 +3827,7 @@ config_clone(const struct config *old) conf->shell = xstrdup(old->shell); conf->title = xstrdup(old->title); conf->app_id = xstrdup(old->app_id); + conf->toplevel_tag = xstrdup(old->toplevel_tag); conf->word_delimiters = xc32dup(old->word_delimiters); conf->scrollback.indicator.text = xc32dup(old->scrollback.indicator.text); conf->server_socket_path = xstrdup(old->server_socket_path); @@ -3922,6 +3927,7 @@ config_free(struct config *conf) free(conf->shell); free(conf->title); free(conf->app_id); + free(conf->toplevel_tag); free(conf->word_delimiters); spawn_template_free(&conf->bell.command); free(conf->scrollback.indicator.text); diff --git a/config.h b/config.h index 37b3259f..fc5e290e 100644 --- a/config.h +++ b/config.h @@ -219,6 +219,7 @@ struct config { char *shell; char *title; char *app_id; + char *toplevel_tag; char32_t *word_delimiters; bool login_shell; bool locked_title; diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 8d968a6e..cbf22f5b 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -67,6 +67,11 @@ the foot command line Value to set the *app-id* property on the Wayland window to. Default: _foot_ (normal mode), or _footclient_ (server mode). +*toplevel-tag*=_TAG_ + Value to set the *toplevel-tag* property on the Wayland window + to. The compositor can use this value for session management, + window rules etc. Default: _not set_ + *-m*,*--maximized* Start in maximized mode. If both *--maximized* and *--fullscreen* are specified, the _last_ one takes precedence. diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 2b57c467..c9782895 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -429,6 +429,11 @@ empty string to be set, but it must be quoted: *KEY=""*) apply window management rules. Default: _foot_ (normal mode), or _footclient_ (server mode). +*toplevel-tag* + Value to set the *toplevel-tag* property on the Wayland window + to. The compositor can use this value for session management, + window rules etc. Default: _not set_ + *bold-text-in-bright* Semi-boolean. When enabled, bold text is rendered in a brighter color (in addition to using a bold font). The color is brightened diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index e4f6d350..edf3e9f3 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -33,6 +33,11 @@ terminal has terminated. Value to set the *app-id* property on the Wayland window to. Default: _foot_ (normal mode), or _footclient_ (server mode). +*toplevel-tag*=_TAG_ + Value to set the *toplevel-tag* property on the Wayland window + to. The compositor can use this value for session management, + window rules etc. Default: _not set_ + *-w*,*--window-size-pixels*=_WIDTHxHEIGHT_ Set initial window width and height, in pixels. Default: _700x500_. diff --git a/foot-features.c b/foot-features.c index 1b5bf7fd..f701533c 100644 --- a/foot-features.c +++ b/foot-features.c @@ -22,6 +22,12 @@ const char version_and_features[] = " -graphemes" #endif +#if defined(HAVE_XDG_TOPLEVEL_TAG) + " +toplevel-tag" +#else + " -toplevel-tag" +#endif + #if !defined(NDEBUG) " +assertions" #else diff --git a/main.c b/main.c index b6a0d825..b933e1c1 100644 --- a/main.c +++ b/main.c @@ -84,6 +84,7 @@ print_usage(const char *prog_name) " -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n" " -T,--title=TITLE initial window title (foot)\n" " -a,--app-id=ID window application ID (foot)\n" + " --toplevel-tag=TAG set a custom toplevel tag\n" " -m,--maximized start in maximized mode\n" " -F,--fullscreen start in fullscreen mode\n" " -L,--login-shell start shell as a login shell\n" @@ -185,6 +186,7 @@ sanitize_signals(void) enum { PTY_OPTION = CHAR_MAX + 1, + TOPLEVEL_TAG_OPTION = CHAR_MAX + 2, }; int @@ -214,6 +216,7 @@ main(int argc, char *const *argv) {"term", required_argument, NULL, 't'}, {"title", required_argument, NULL, 'T'}, {"app-id", required_argument, NULL, 'a'}, + {"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION}, {"login-shell", no_argument, NULL, 'L'}, {"working-directory", required_argument, NULL, 'D'}, {"font", required_argument, NULL, 'f'}, @@ -285,6 +288,10 @@ main(int argc, char *const *argv) tll_push_back(overrides, xstrjoin("app-id=", optarg)); break; + case TOPLEVEL_TAG_OPTION: + tll_push_back(overrides, xstrjoin("toplevel-tag=", optarg)); + break; + case 'D': { struct stat st; if (stat(optarg, &st) < 0 || !(st.st_mode & S_IFDIR)) { diff --git a/meson.build b/meson.build index a1d0104d..aa8342ab 100644 --- a/meson.build +++ b/meson.build @@ -182,7 +182,12 @@ wl_proto_xml = [ wayland_protocols_datadir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml', wayland_protocols_datadir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml', wayland_protocols_datadir / 'staging/color-management/color-management-v1.xml', - ] +] + +if (wayland_protocols.version().version_compare('>=1.43')) + wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml'] + add_project_arguments('-DHAVE_XDG_TOPLEVEL_TAG=1', language: 'c') +endif foreach prot : wl_proto_xml wl_proto_headers += custom_target( diff --git a/tests/test-config.c b/tests/test-config.c index c442e700..268733db 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -482,6 +482,7 @@ test_section_main(void) test_string(&ctx, &parse_section_main, "shell", &conf.shell); test_string(&ctx, &parse_section_main, "term", &conf.term); test_string(&ctx, &parse_section_main, "app-id", &conf.app_id); + test_string(&ctx, &parse_section_main, "toplevel-tag", &conf.toplevel_tag); test_string(&ctx, &parse_section_main, "utmp-helper", &conf.utmp_helper_path); test_c32string(&ctx, &parse_section_main, "word-delimiters", &conf.word_delimiters); diff --git a/wayland.c b/wayland.c index bac087fb..6785c52d 100644 --- a/wayland.c +++ b/wayland.c @@ -1548,6 +1548,17 @@ handle_global(void *data, struct wl_registry *registry, wayl->color_management.manager, &color_manager_listener, wayl); } +#if defined(HAVE_XDG_TOPLEVEL_TAG) + else if (streq(interface, xdg_toplevel_tag_manager_v1_interface.name)) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->toplevel_tag_manager = wl_registry_bind( + wayl->registry, name, &xdg_toplevel_tag_manager_v1_interface, required); + } +#endif + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (streq(interface, zwp_text_input_manager_v3_interface.name)) { const uint32_t required = 1; @@ -1791,7 +1802,7 @@ wayl_init(struct fdm *fdm, struct key_binding_manager *key_binding_manager, } if (wayl->toplevel_icon_manager == NULL) { - LOG_WARN("compositor does not implement the XDG toplevel icon protocol"); + LOG_WARN("compositor does not implement the xdg-toplevel-icon protocol"); } #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED @@ -1870,6 +1881,11 @@ wayl_destroy(struct wayland *wayl) zwp_text_input_manager_v3_destroy(wayl->text_input_manager); #endif +#if defined(HAVE_XDG_TOPLEVEL_TAG) + if (wayl->toplevel_tag_manager != NULL) + xdg_toplevel_tag_manager_v1_destroy(wayl->toplevel_tag_manager); +#endif + if (wayl->color_management.img_description != NULL) wp_image_description_v1_destroy(wayl->color_management.img_description); if (wayl->color_management.manager != NULL) @@ -1995,6 +2011,21 @@ wayl_win_init(struct terminal *term, const char *token) xdg_toplevel_set_app_id(win->xdg_toplevel, conf->app_id); +#if defined(HAVE_XDG_TOPLEVEL_TAG) + if (conf->toplevel_tag != NULL && conf->toplevel_tag[0] != '\0') { + if (wayl->toplevel_tag_manager != NULL) { + xdg_toplevel_tag_manager_v1_set_toplevel_tag( + wayl->toplevel_tag_manager, win->xdg_toplevel, conf->toplevel_tag); + + /* TODO: the description is recommended to be the tag, but translated */ + xdg_toplevel_tag_manager_v1_set_toplevel_description( + wayl->toplevel_tag_manager, win->xdg_toplevel, conf->toplevel_tag); + } else { + LOG_WARN("compositor does not implement the xdg-toplevel-tag protocol"); + } + } +#endif + if (wayl->toplevel_icon_manager != NULL) { const char *app_id = term->app_id != NULL ? term->app_id : term->conf->app_id; diff --git a/wayland.h b/wayland.h index eb1c35a3..140c2058 100644 --- a/wayland.h +++ b/wayland.h @@ -23,6 +23,10 @@ #include #include +#if defined(HAVE_XDG_TOPLEVEL_TAG) + #include +#endif + #include #include @@ -481,6 +485,10 @@ struct wayland { struct wp_presentation *presentation; uint32_t presentation_clock_id; +#if defined(HAVE_XDG_TOPLEVEL_TAG) + struct xdg_toplevel_tag_manager_v1 *toplevel_tag_manager; +#endif + #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED struct zwp_text_input_manager_v3 *text_input_manager; #endif From be19ca2b2074c522be489d0f4760d48a09265045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 29 Nov 2025 09:47:22 +0100 Subject: [PATCH 306/353] client: add missing (for CHAR_MAX) Closes #2221 --- client.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/client.c b/client.c index 85bc7fda..befd3ab0 100644 --- a/client.c +++ b/client.c @@ -1,12 +1,13 @@ -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include From 55f838869443bb34a502c238301e22b4d6f52073 Mon Sep 17 00:00:00 2001 From: Whyme Lyu Date: Mon, 1 Dec 2025 18:38:58 +0800 Subject: [PATCH 307/353] doc: remove duplicated ctrl+shift+w in foot(1) --- doc/foot.1.scd | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/foot.1.scd b/doc/foot.1.scd index cbf22f5b..60ba622b 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -257,9 +257,6 @@ These keyboard shortcuts affect the search selection: *ctrl*+*shift*+*left* Extend current selection to the left to the last word boundary. -*ctrl*+*shift*+*w* - Extend the current selection to the right to the last whitespace. - *shift*+*down* Extend current selection down one line From 65bd79b77d0acf3fcf6be1ecfe5a4a3ce2e1151a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 10 Dec 2025 08:48:41 +0100 Subject: [PATCH 308/353] term: reverse-scroll: fix crash when viewport ends up outside the (new) scrollback If the viewport has been scrolled up, it is possible for a reverse-scroll (rin) to cause the viewport to point to lines outside the scrollback. This is an issue if the scrollback isn't full, since in that case, the viewport will contain NULL lines. This will potentially trigger assertions in a couple of different places. Example backtrace: #2 0x555555cd230c in bug ../../debug.c:44 #3 0x555555ad485e in grid_row_in_view ../../grid.h:83 #4 0x555555b15a89 in grid_render ../../render.c:3465 #5 0x555555b3b0ab in fdm_hook_refresh_pending_terminals ../../render.c:5165 #6 0x555555a74980 in fdm_poll ../../fdm.c:435 #7 0x555555ac2b85 in main ../../main.c:676 Detect when this happens, and force-move the viewport to ensure it is valid. Closes #2232 --- CHANGELOG.md | 4 ++++ terminal.c | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a293c721..77d7d772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,10 @@ * Search mode: composing keys not ignored. * Crash when triple-clicking a soft-wrapped line and there is a quote character in the last column. +* Crash when reverse-scrolling (terminfo capability `rin`) such that + the current viewport ends up outside the scrollback ([#2232][2232]). + +[2232]: https://codeberg.org/dnkl/foot/issues/2232 ### Security diff --git a/terminal.c b/terminal.c index 36f8513b..e70250d8 100644 --- a/terminal.c +++ b/terminal.c @@ -3165,11 +3165,17 @@ term_scroll_reverse_partial(struct terminal *term, sixel_scroll_down(term, rows); - bool view_follows = term->grid->view == term->grid->offset; + const bool view_follows = term->grid->view == term->grid->offset; term->grid->offset -= rows; term->grid->offset += term->grid->num_rows; term->grid->offset &= term->grid->num_rows - 1; + /* How many lines from the scrollback start is the current viewport? */ + const int view_sb_start_distance = grid_row_abs_to_sb( + term->grid, term->rows, term->grid->view); + const int offset_sb_start_distance = grid_row_abs_to_sb( + term->grid, term->rows, term->grid->offset); + xassert(term->grid->offset >= 0); xassert(term->grid->offset < term->grid->num_rows); @@ -3177,6 +3183,11 @@ term_scroll_reverse_partial(struct terminal *term, term_damage_scroll(term, DAMAGE_SCROLL_REVERSE, region, rows); selection_view_up(term, term->grid->offset); term->grid->view = term->grid->offset; + } else if (unlikely(view_sb_start_distance > offset_sb_start_distance)) { + /* Part of current view is being scrolled out */ + int new_view = term->grid->offset; + selection_view_up(term, new_view); + term->grid->view = new_view; } /* Bottom non-scrolling region */ @@ -3193,11 +3204,16 @@ term_scroll_reverse_partial(struct terminal *term, erase_line(term, row); } + if (unlikely(view_sb_start_distance > offset_sb_start_distance)) + term_damage_view(term); + term->grid->cur_row = grid_row(term->grid, term->grid->cursor.point.row); #if defined(_DEBUG) for (int r = 0; r < term->rows; r++) xassert(grid_row(term->grid, r) != NULL); + for (int r = 0; r < term->rows; r++) + xassert(grid_row_in_view(term->grid, r) != NULL); #endif } From ac6d7660dd77c5418593a35cd623bf198ac93fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 10 Dec 2025 08:54:24 +0100 Subject: [PATCH 309/353] ci: codespell: ignore 'rin' --- .woodpecker.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker.yaml b/.woodpecker.yaml index 340ba241..843c9afc 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -14,7 +14,7 @@ steps: - python3 -m venv codespell-venv - source codespell-venv/bin/activate - pip install codespell - - codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd + - codespell -Lser,doas,zar,rin README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd - deactivate - name: subprojects From 6e533231b016684a32a1975ce2e33ae3ae38b4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 10 Dec 2025 09:39:51 +0100 Subject: [PATCH 310/353] term: mouse SGR mode: don't emit negative CSI values When reporting the column/row pixel value in mouse SGR mode, we emitted negative values when the cursor was being dragged outside the window. Unfortunately, negative values aren't allowed in CSI parameters, as '-' is an intermediate value. It was done this way, to be consistent with XTerm behavior. Allegedly, XTerm has changed its behavior in patch 404. With that in mind, and seeing that foot has never emitted negative values in any other mouse mode, let's stop emitting negative values in SGR mode too. Closes #2226 --- CHANGELOG.md | 3 +++ terminal.c | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d7d772..85dc3762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -85,8 +85,11 @@ state is ([#2202][2202]). * Scrollback search is now case sensitive when the search string contains at least one upper case character. +* Mouse tracking in SGR pixel mode no longer emits negative column/row + pixel values ([#2226][2226]). [2202]: https://codeberg.org/dnkl/foot/issues/2202 +[2226]: https://codeberg.org/dnkl/foot/issues/2226 ### Deprecated diff --git a/terminal.c b/terminal.c index e70250d8..3749416b 100644 --- a/terminal.c +++ b/terminal.c @@ -3420,10 +3420,13 @@ report_mouse_click(struct terminal *term, int encoded_button, int row, int col, encoded_button, col + 1, row + 1, release ? 'm' : 'M'); break; - case MOUSE_SGR_PIXELS: + case MOUSE_SGR_PIXELS: { + const int bounded_col = max(col_pixels, 0); + const int bounded_row = max(row_pixels, 0); snprintf(response, sizeof(response), "\033[<%d;%d;%d%c", - encoded_button, col_pixels + 1, row_pixels + 1, release ? 'm' : 'M'); + encoded_button, bounded_col + 1, bounded_row + 1, release ? 'm' : 'M'); break; + } case MOUSE_URXVT: snprintf(response, sizeof(response), "\033[%d;%d;%dM", From 15ebc433baabd799252609e28bf79afd892b33a4 Mon Sep 17 00:00:00 2001 From: Yaakov Selkowitz Date: Tue, 16 Dec 2025 22:10:39 -0500 Subject: [PATCH 311/353] Fix discarded const qualifiers from string functions This is a new warning in GCC 15 that is being promoted to an error due to the werror=true in meson.build. --- notify.c | 2 +- osc.c | 2 +- tokenize.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/notify.c b/notify.c index e8688180..e454b03b 100644 --- a/notify.c +++ b/notify.c @@ -114,7 +114,7 @@ consume_stdout(struct notification *notif, bool eof) while (left > 0) { line = data; size_t len = left; - char *eol = memchr(line, '\n', left); + char *eol = (char *)memchr(line, '\n', left); if (eol != NULL) { *eol = '\0'; diff --git a/osc.c b/osc.c index 0b492564..375eae5c 100644 --- a/osc.c +++ b/osc.c @@ -513,7 +513,7 @@ osc_uri(struct terminal *term, char *string) key_value = strtok_r(NULL, ":", &ctx)) { const char *key = key_value; - char *operator = strchr(key_value, '='); + char *operator = (char *)strchr(key_value, '='); if (operator == NULL) continue; diff --git a/tokenize.c b/tokenize.c index 77cc3f1a..70ceb39b 100644 --- a/tokenize.c +++ b/tokenize.c @@ -45,7 +45,7 @@ tokenize_cmdline(const char *cmdline, char ***argv) size_t idx = 0; while (*p != '\0') { - char *end = strchr(search_start, delim); + char *end = (char *)strchr(search_start, delim); if (end == NULL) { if (delim != ' ') { LOG_ERR("unterminated %s quote", delim == '"' ? "double" : "single"); From 4e96780eef048baa7370499c64a33210b4e0406d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 16 Dec 2025 14:56:42 +0100 Subject: [PATCH 312/353] shm: revert part of 299186a6547f6e038ee6f3822caf9a0fdfabceef 299186a6547f6e038ee6f3822caf9a0fdfabceef introduced a regression, where we don't handle SHM buffer "hiccups" correctly. If foot, for some reason is forced to render a frame "too soon", we might end up having multiple buffers "in flight" (i.e. committed to the compositor). This could happen if the compositor pushes multiple configure events rapidly, for example. Or anything else that forces foot to render something "immediately", without waiting for a frame callback. The compositor typically releases both buffers at the same time (or close to it), so the _next_ time we want to render a frame, we have *two* buffers to pick between. The problem here is that after 299186a6547f6e038ee6f3822caf9a0fdfabceef, foot no longer purges the additional buffer(s), but keeps all of them around. This messes up foot's age tracking, and the _next_ time we're forced to pull two buffers (without the compositor releasing the first one in between), we try to apply damage tracking that is no longer valid. This results in visual glitches. This never self-repairs, and we're stuck with visual glitches until the window is resized, and we're forced to allocate completely new buffers. It is unclear why 299186a6547f6e038ee6f3822caf9a0fdfabceef stopped removing the buffers. It was likely done early in the development, and is no longer needed. So far, I haven't noticed any bugs by re-introducing the buffer purging, but further testing is needed. --- CHANGELOG.md | 1 + shm.c | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85dc3762..70ab43b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ character in the last column. * Crash when reverse-scrolling (terminfo capability `rin`) such that the current viewport ends up outside the scrollback ([#2232][2232]). +* Regression: visual glitches in rare circumstances. [2232]: https://codeberg.org/dnkl/foot/issues/2232 diff --git a/shm.c b/shm.c index 72b32f16..f488d6b6 100644 --- a/shm.c +++ b/shm.c @@ -628,14 +628,14 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph else #endif { - if (cached == NULL) + if (cached == NULL) { cached = buf; - else { + } else { /* We have multiple buffers eligible for * reuse. Pick the "youngest" one, and mark the * other one for purging */ if (buf->public.age < cached->public.age) { - //shm_unref(&cached->public); + shm_unref(&cached->public); cached = buf; } else { /* @@ -646,8 +646,8 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph * should be safe; "our" tll_foreach() already * holds the next pointer. */ - //if (buffer_unref_no_remove_from_chain(buf)) - // tll_remove(chain->bufs, it); + if (buffer_unref_no_remove_from_chain(buf)) + tll_remove(chain->bufs, it); } } } From cf2b390f6e096e7a2ca93d4dece153eb13261a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 19 Dec 2025 09:29:06 +0100 Subject: [PATCH 313/353] config: add [colors-dark] and [colors-light], replacing [colors] and [colors2] The main reason for having two color sections is to be able to switch between dark and light. Thus, it's better if the section names reflect this, rather than the more generic 'colors' and 'colors2' (which was the dark one and which was the light one, now again?) When the second color section was added, we kept the original name, colors, to make sure we didn't break existing configurations, and third-party themes. However, in the long run, it's probably better to be specific in the section naming, to avoid confusion. So, add 'colors-dark', and 'colors-light'. Keep 'colors' and 'colors2' as aliases for now, but mark them as deprecated. They WILL be removed in a future release. Also rename the option values for initial-color-theme, from 1/2, to dark/light. Keep the old ones for now, marked as deprecated. Update all bundled themes to use the new names. In the light-only themes (i.e. themes that define a single, light, theme), use colors-light, and set initial-color-theme=light. Possible improvements: disable color switching if only one color section has been explicitly configured (todo: figure out how to handle the default color theme values...) --- CHANGELOG.md | 7 + config.c | 128 ++++++++++++++--- config.h | 10 +- csi.c | 2 +- doc/foot.1.scd | 4 +- doc/foot.ini.5.scd | 57 ++++---- doc/footclient.1.scd | 4 +- foot.ini | 6 +- input.c | 6 +- key-binding.h | 2 + main.c | 8 +- osc.c | 44 +++--- render.c | 60 ++++---- server.c | 20 +-- server.h | 4 +- sixel.c | 6 +- terminal.c | 38 +++--- terminal.h | 4 +- tests/test-config.c | 196 +++++++++++++++++++-------- themes/aeroroot | 2 +- themes/alacritty | 2 +- themes/apprentice | 2 +- themes/ayu-mirage | 2 +- themes/catppuccin-frappe | 2 +- themes/catppuccin-latte | 5 +- themes/catppuccin-macchiato | 2 +- themes/catppuccin-mocha | 2 +- themes/chiba-dark | 2 +- themes/derp | 2 +- themes/deus | 2 +- themes/dracula | 2 +- themes/dracula-iterm | 2 +- themes/electrophoretic | 5 +- themes/gruvbox | 4 +- themes/gruvbox-dark | 2 +- themes/gruvbox-light | 5 +- themes/hacktober | 2 +- themes/iterm | 2 +- themes/jetbrains-darcula | 2 +- themes/kitty | 2 +- themes/material-amber | 5 +- themes/material-design | 2 +- themes/modus-operandi | 6 +- themes/modus-vivendi | 2 +- themes/modus-vivendi-tinted | 2 +- themes/molokai | 2 +- themes/monokai-pro | 2 +- themes/moonfly | 2 +- themes/neon | 2 +- themes/night-owl | 2 +- themes/nightfly | 2 +- themes/noirblaze | 2 +- themes/nord | 2 +- themes/nordiq | 2 +- themes/nvim | 4 +- themes/nvim-dark | 2 +- themes/nvim-light | 5 +- themes/onedark | 2 +- themes/onehalf-dark | 2 +- themes/panda | 2 +- themes/paper-color | 4 +- themes/paper-color-dark | 2 +- themes/paper-color-light | 5 +- themes/poimandres | 2 +- themes/rezza | 2 +- themes/rose-pine | 2 +- themes/rose-pine-dawn | 6 +- themes/rose-pine-moon | 2 +- themes/selenized | 4 +- themes/selenized-black | 2 +- themes/selenized-dark | 2 +- themes/selenized-light | 5 +- themes/selenized-white | 5 +- themes/solarized | 4 +- themes/solarized-dark | 2 +- themes/solarized-dark-normal-brights | 2 +- themes/solarized-light | 5 +- themes/solarized-normal-brights | 4 +- themes/srcery | 2 +- themes/starlight | 2 +- themes/tango | 2 +- themes/tempus-autumn | 2 +- themes/tempus-classic | 2 +- themes/tempus-dawn | 6 +- themes/tempus-day | 5 +- themes/tempus-dusk | 2 +- themes/tempus-fugit | 5 +- themes/tempus-future | 2 +- themes/tempus-night | 2 +- themes/tempus-past | 5 +- themes/tempus-rift | 2 +- themes/tempus-spring | 2 +- themes/tempus-summer | 2 +- themes/tempus-tempest | 2 +- themes/tempus-totus | 5 +- themes/tempus-warp | 2 +- themes/tempus-winter | 2 +- themes/tokyonight-light | 5 +- themes/tokyonight-night | 2 +- themes/tokyonight-storm | 2 +- themes/visibone | 2 +- themes/xterm | 2 +- themes/zenburn | 2 +- 103 files changed, 542 insertions(+), 298 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ab43b3..ae9feb54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,8 @@ `foot` and `footclient`), allowing you to set a custom toplevel tag. The compositor must implement the new `xdg-toplevel-tag-v1` Wayland protocol ([#2212][2212]). +* `[colors-dark]` section to `foot.ini`. Replaces `[colors]`. +* `[colors-light]` section to `foot.ini`. Replaces `[colors2]`. [2212]: https://codeberg.org/dnkl/foot/issues/2212 @@ -93,6 +95,11 @@ ### Deprecated + +* `[colors]` section in `foot.ini`. Use `[colors-dark]` instead. +* `[colors2]` section in `foot.ini`. Use `[colors-light]` instead. + + ### Removed ### Fixed diff --git a/config.c b/config.c index 515b088c..0340d418 100644 --- a/config.c +++ b/config.c @@ -144,6 +144,8 @@ static const char *const binding_action_map[] = { [BIND_ACTION_REGEX_COPY] = "regex-copy", [BIND_ACTION_THEME_SWITCH_1] = "color-theme-switch-1", [BIND_ACTION_THEME_SWITCH_2] = "color-theme-switch-2", + [BIND_ACTION_THEME_SWITCH_DARK] = "color-theme-switch-dark", + [BIND_ACTION_THEME_SWITCH_LIGHT] = "color-theme-switch-light", [BIND_ACTION_THEME_TOGGLE] = "color-theme-toggle", /* Mouse-specific actions */ @@ -1118,8 +1120,40 @@ parse_section_main(struct context *ctx) sizeof(conf->initial_color_theme) == sizeof(int), "enum is not 32-bit"); - return value_to_enum(ctx, (const char*[]){"1", "2", NULL}, - (int *)&conf->initial_color_theme); + if (!value_to_enum(ctx, (const char*[]){ + "dark", "light", "1", "2", NULL}, + (int *)&conf->initial_color_theme)) + return false; + + if (streq(ctx->value, "1")) { + LOG_WARN("%s:%d: [main].initial-color-theme=1 deprecated, " + "use [main].initial-color-theme=dark instead", + ctx->path, ctx->lineno); + + user_notification_add( + &ctx->conf->notifications, + USER_NOTIFICATION_DEPRECATED, + xstrdup("[main].initial-color-theme=1: " + "use [main].initial-color-theme=dark instead")); + + conf->initial_color_theme = COLOR_THEME_DARK; + } + + else if (streq(ctx->value, "2")) { + LOG_WARN("%s:%d: [main].initial-color-theme=2 deprecated, " + "use [main].initial-color-theme=light instead", + ctx->path, ctx->lineno); + + user_notification_add( + &ctx->conf->notifications, + USER_NOTIFICATION_DEPRECATED, + xstrdup("[main].initial-color-theme=2: " + "use [main].initial-color-theme=light instead")); + + conf->initial_color_theme = COLOR_THEME_LIGHT; + } + + return true; } else if (streq(key, "uppercase-regex-insert")) @@ -1555,16 +1589,44 @@ parse_color_theme(struct context *ctx, struct color_theme *theme) return true; } +static bool +parse_section_colors_dark(struct context *ctx) +{ + return parse_color_theme(ctx, &ctx->conf->colors_dark); +} + +static bool +parse_section_colors_light(struct context *ctx) +{ + return parse_color_theme(ctx, &ctx->conf->colors_light); +} + static bool parse_section_colors(struct context *ctx) { - return parse_color_theme(ctx, &ctx->conf->colors); + LOG_WARN("%s:%d: [colors]: deprecated; use [colors-dark] instead", + ctx->path, ctx->lineno); + + user_notification_add( + &ctx->conf->notifications, + USER_NOTIFICATION_DEPRECATED, + xstrdup("[colors]: use [colors-dark] instead")); + + return parse_color_theme(ctx, &ctx->conf->colors_dark); } static bool parse_section_colors2(struct context *ctx) { - return parse_color_theme(ctx, &ctx->conf->colors2); + LOG_WARN("%s:%d: [colors2]: deprecated; use [colors-light] instead", + ctx->path, ctx->lineno); + + user_notification_add( + &ctx->conf->notifications, + USER_NOTIFICATION_DEPRECATED, + xstrdup("[colors2]: use [colors-light] instead")); + + return parse_color_theme(ctx, &ctx->conf->colors_light); } static bool @@ -1610,14 +1672,14 @@ parse_section_cursor(struct context *ctx) if (!value_to_two_colors( ctx, - &conf->colors.cursor.text, - &conf->colors.cursor.cursor, + &conf->colors_dark.cursor.text, + &conf->colors_dark.cursor.cursor, false)) { return false; } - conf->colors.use_custom.cursor = true; + conf->colors_dark.use_custom.cursor = true; return true; } @@ -2268,6 +2330,29 @@ parse_key_binding_section(struct context *ctx, aux.regex_name = regex_name; } + if (action_map == binding_action_map && + action >= BIND_ACTION_THEME_SWITCH_1 && + action <= BIND_ACTION_THEME_SWITCH_2) + { + const char *use_instead = + action_map[action == BIND_ACTION_THEME_SWITCH_1 + ? BIND_ACTION_THEME_SWITCH_DARK + : BIND_ACTION_THEME_SWITCH_LIGHT]; + + const char *notif = action == BIND_ACTION_THEME_SWITCH_1 + ? "[key-bindings].color-theme-switch-1: use [key-bindings].color-theme-switch-dark instead" + : "[key-bindings].color-theme-switch-2: use [key-bindings].color-theme-switch-light instead"; + + LOG_WARN("%s:%d: [key-bindings].%s: deprecated, use %s instead", + ctx->path, ctx->lineno, + action_map[action], use_instead); + + user_notification_add( + &ctx->conf->notifications, + USER_NOTIFICATION_DEPRECATED, + xstrdup(notif)); + } + if (!value_to_key_combos(ctx, action, &aux, bindings, KEY_BINDING)) { free_binding_aux(&aux); return false; @@ -2958,8 +3043,8 @@ enum section { SECTION_SCROLLBACK, SECTION_URL, SECTION_REGEX, - SECTION_COLORS, - SECTION_COLORS2, + SECTION_COLORS_DARK, + SECTION_COLORS_LIGHT, SECTION_CURSOR, SECTION_MOUSE, SECTION_CSD, @@ -2971,6 +3056,11 @@ enum section { SECTION_ENVIRONMENT, SECTION_TWEAK, SECTION_TOUCH, + + /* Deprecated */ + SECTION_COLORS, + SECTION_COLORS2, + SECTION_COUNT, }; @@ -2989,8 +3079,8 @@ static const struct { [SECTION_SCROLLBACK] = {&parse_section_scrollback, "scrollback"}, [SECTION_URL] = {&parse_section_url, "url"}, [SECTION_REGEX] = {&parse_section_regex, "regex", true}, - [SECTION_COLORS] = {&parse_section_colors, "colors"}, - [SECTION_COLORS2] = {&parse_section_colors2, "colors2"}, + [SECTION_COLORS_DARK] = {&parse_section_colors_dark, "colors-dark"}, + [SECTION_COLORS_LIGHT] = {&parse_section_colors_light, "colors-light"}, [SECTION_CURSOR] = {&parse_section_cursor, "cursor"}, [SECTION_MOUSE] = {&parse_section_mouse, "mouse"}, [SECTION_CSD] = {&parse_section_csd, "csd"}, @@ -3002,6 +3092,10 @@ static const struct { [SECTION_ENVIRONMENT] = {&parse_section_environment, "environment"}, [SECTION_TWEAK] = {&parse_section_tweak, "tweak"}, [SECTION_TOUCH] = {&parse_section_touch, "touch"}, + + /* Deprecated */ + [SECTION_COLORS] = {&parse_section_colors, "colors"}, + [SECTION_COLORS2] = {&parse_section_colors2, "colors2"}, }; static_assert(ALEN(section_info) == SECTION_COUNT, "section info array size mismatch"); @@ -3435,7 +3529,7 @@ config_load(struct config *conf, const char *conf_path, }, .multiplier = 3., }, - .colors = { + .colors_dark = { .fg = default_foreground, .bg = default_background, .flash = 0x7f7f00, @@ -3455,7 +3549,7 @@ config_load(struct config *conf, const char *conf_path, .url = false, }, }, - .initial_color_theme = COLOR_THEME1, + .initial_color_theme = COLOR_THEME_DARK, .cursor = { .style = CURSOR_BLOCK, .unfocused_style = CURSOR_UNFOCUSED_HOLLOW, @@ -3535,10 +3629,10 @@ config_load(struct config *conf, const char *conf_path, .notifications = tll_init(), }; - memcpy(conf->colors.table, default_color_table, sizeof(default_color_table)); - memcpy(conf->colors.sixel, default_sixel_colors, sizeof(default_sixel_colors)); - memcpy(&conf->colors2, &conf->colors, sizeof(conf->colors)); - conf->colors2.dim_blend_towards = DIM_BLEND_TOWARDS_WHITE; + memcpy(conf->colors_dark.table, default_color_table, sizeof(default_color_table)); + memcpy(conf->colors_dark.sixel, default_sixel_colors, sizeof(default_sixel_colors)); + memcpy(&conf->colors_light, &conf->colors_dark, sizeof(conf->colors_dark)); + conf->colors_light.dim_blend_towards = DIM_BLEND_TOWARDS_WHITE; parse_modifiers(XKB_MOD_NAME_SHIFT, 5, &conf->mouse.selection_override_modifiers); diff --git a/config.h b/config.h index fc5e290e..9ca47753 100644 --- a/config.h +++ b/config.h @@ -195,8 +195,10 @@ struct color_theme { }; enum which_color_theme { - COLOR_THEME1, - COLOR_THEME2, + COLOR_THEME_DARK, + COLOR_THEME_LIGHT, + COLOR_THEME_1, /* Deprecated */ + COLOR_THEME_2, /* Deprecated */ }; enum shm_bit_depth { @@ -327,8 +329,8 @@ struct config { tll(struct custom_regex) custom_regexes; - struct color_theme colors; - struct color_theme colors2; + struct color_theme colors_dark; + struct color_theme colors_light; enum which_color_theme initial_color_theme; struct { diff --git a/csi.c b/csi.c index c5f616ac..7e0cf464 100644 --- a/csi.c +++ b/csi.c @@ -1578,7 +1578,7 @@ csi_dispatch(struct terminal *term, uint8_t final) int chars = snprintf( reply, sizeof(reply), "\033[?997;%dn", - term->colors.active_theme == COLOR_THEME1 ? 1 : 2); + term->colors.active_theme == COLOR_THEME_DARK ? 1 : 2); term_to_slave(term, reply, chars); break; diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 60ba622b..7058e96f 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -695,8 +695,8 @@ variables to unset may be defined in *foot.ini*(5). The following signals have special meaning in foot: -- SIGUSR1: switch to color theme 1 (i.e. use the *[colors]* section). -- SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section). +- SIGUSR1: switch to the dark color theme (*[colors-dark]*). +- SIGUSR2: switch to the light color theme (*[colors-light]*). Note: you can send SIGUSR1/SIGUSR2 to a *foot --server* process too, in which case all client instances will switch theme. Furthermore, all diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index c9782895..8bff9629 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -24,7 +24,7 @@ commented out will usually be installed to */etc/xdg/foot/foot.ini*. Options are set using KEY=VALUE pairs: - *\[colors\]*++ + *\[colors-dark\]*++ *background=000000*++ *foreground=ffffff* @@ -371,12 +371,12 @@ empty string to be set, but it must be quoted: *KEY=""*) Default: _yes_ *initial-color-theme* - Selects which color theme to use, *1*, or *2*. + Selects which color theme to use, *dark*, or *light*. - *1* uses the colors defined in the *colors* section, while *2* - uses the colors from the *colors2* section. + *dark* uses the colors defined in the *colors-dark* section, while + *light* uses the colors from the *colors-light* section. - Use the *color-theme-switch-1*, *color-theme-switch-2* and + Use the *color-theme-switch-dark*, *color-theme-switch-light* and *color-theme-toggle* key bindings to switch between the two themes at runtime, or send SIGUSR1/SIGUSR2 to the foot process (see *foot*(1) for details). @@ -987,19 +987,24 @@ applications can change these at runtime. Default: _400_. -# SECTION: colors +# SECTION: colors-dark, colors-light -This section controls the 16 ANSI colors, the default foreground and -background colors, and the extended 256 color palette. Note that +These two sections controls the 16 ANSI colors, the default foreground +and background colors, and the extended 256 color palette. Note that applications can change these at runtime. The colors are in RRGGBB format (i.e. plain old 6-digit hex values, without prefix). That is, they do *not* have an alpha component. You can configure the background transparency with the _alpha_ option. -In the context of private mode 2031 (Dark and Light Mode detection), -the primary theme (i.e. the *colors* section) is considered to be the -dark theme (since the default theme is dark). +*colors-dark* is intended to define a dark color theme, and +*colors-light* is intended to define a light color theme. You can +switch between them using the *color-theme-switch-dark*, +*color-theme-switch-light* and *color-theme-toggle* key bindings, or +by sending SIGUSR1/SIGUSR2 to the foot process. + +The default theme used is *colors-dark*, unless +*initial-color-theme=light* has been set. *cursor* Two space separated RRGGBB values (i.e. plain old 6-digit hex @@ -1098,7 +1103,7 @@ dark theme (since the default theme is dark). black makes the text darker, while blending towards white makes it whiter (but still dimmer than normal text). - Default: _black_ (*colors*), _white_ (*colors2*) + Default: _black_ (*colors-dark*), _white_ (*colors-light*) *selection-foreground*, *selection-background* Foreground (text) and background color to use in selected @@ -1135,20 +1140,6 @@ dark theme (since the default theme is dark). Flash translucency. A value in the range 0.0-1.0, where 0.0 means completely transparent, and 1.0 is opaque. Default: _0.5_. -# SECTION: colors2 - -This section defines an alternative color theme. It has the exact same -keys as the *colors* section. The default values are the same, except -for *dim-blend-towards*, which defaults to *white* instead. - -Note that values are not inherited. That is, if you set a value in -*colors*, but not in *colors2*, the value from *colors* is not -inherited by *colors2*. - -In the context of private mode 2031 (Dark and Light Mode detection), -the alternative theme (i.e. the *colors2* section) is considered to be -the light theme (since the default, the primary theme, is dark). - # SECTION: csd This section controls the look of the _CSDs_ (Client Side @@ -1455,16 +1446,16 @@ e.g. *search-start=none*. Default: _Control+Shift+u_. -*color-theme-switch-1*, *color-theme-switch-2*, *color-theme-toggle* - Switch between the primary color theme (defined in the *colors* - section), and the alternative color theme (defined in the - *colors2* section). +*color-theme-switch-dark*, *color-theme-switch-dark*, *color-theme-toggle* + Switch between the dark color theme (defined in the *colors-dark* + section), and the light color theme (defined in the *colors-light* + section). - *color-theme-switch-1* applies the primary color theme regardless + *color-theme-switch-dark* applies the dark color theme regardless of which color theme is currently active. - *color-theme-switch-2* applies the alternative color theme regardless - of which color theme is currently active. + *color-theme-switch-light* applies the light color theme + regardless of which color theme is currently active. *color-theme-toggle* toggles between the primary and alternative color themes. diff --git a/doc/footclient.1.scd b/doc/footclient.1.scd index edf3e9f3..ad865913 100644 --- a/doc/footclient.1.scd +++ b/doc/footclient.1.scd @@ -198,8 +198,8 @@ variables to unset may be defined in *foot.ini*(5). The following signals have special meaning in footclient: -- SIGUSR1: switch to color theme 1 (i.e. use the *[colors]* section). -- SIGUSR2: switch to color theme 2 (i.e. use the *[colors2]* section). +- SIGUSR1: switch to the dark color theme (*[colors-dark]*). +- SIGUSR2: switch to the light color theme (*[colors-light]*). When sending SIGUSR1/SIGUSR2 to a footclient instance, the theme is changed in that instance only. This is different from when you send diff --git a/foot.ini b/foot.ini index 2d170489..a9b4b83d 100644 --- a/foot.ini +++ b/foot.ini @@ -24,7 +24,7 @@ # dpi-aware=no # gamma-correct-blending=no -# initial-color-theme=1 +# initial-color-theme=dark # initial-window-size-pixels=700x500 # Or, # initial-window-size-chars= # initial-window-mode=windowed @@ -101,7 +101,7 @@ [touch] # long-press-delay=400 -[colors] +[colors-dark] # alpha=1.0 # alpha-mode=default # Can be `default`, `matching` or `all` # background=242424 @@ -169,7 +169,7 @@ # search-box-match= # black-on-yellow # urls= -[colors2] +[colors-light] # Alternative color theme, see man page foot.ini(5) # Same builtin defaults as [color], except for: # dim-blend-towards=white diff --git a/input.c b/input.c index 44a99e3b..80b028ac 100644 --- a/input.c +++ b/input.c @@ -486,11 +486,13 @@ execute_binding(struct seat *seat, struct terminal *term, return true; case BIND_ACTION_THEME_SWITCH_1: - term_theme_switch_to_1(term); + case BIND_ACTION_THEME_SWITCH_DARK: + term_theme_switch_to_dark(term); return true; case BIND_ACTION_THEME_SWITCH_2: - term_theme_switch_to_2(term); + case BIND_ACTION_THEME_SWITCH_LIGHT: + term_theme_switch_to_light(term); return true; case BIND_ACTION_THEME_TOGGLE: diff --git a/key-binding.h b/key-binding.h index 5f0c1f1e..c4a04e99 100644 --- a/key-binding.h +++ b/key-binding.h @@ -45,6 +45,8 @@ enum bind_action_normal { BIND_ACTION_REGEX_COPY, BIND_ACTION_THEME_SWITCH_1, BIND_ACTION_THEME_SWITCH_2, + BIND_ACTION_THEME_SWITCH_DARK, + BIND_ACTION_THEME_SWITCH_LIGHT, BIND_ACTION_THEME_TOGGLE, /* Mouse specific actions - i.e. they require a mouse coordinate */ diff --git a/main.c b/main.c index b933e1c1..9db77d0c 100644 --- a/main.c +++ b/main.c @@ -59,14 +59,14 @@ fdm_sigusr(struct fdm *fdm, int signo, void *data) if (ctx->server != NULL) { if (signo == SIGUSR1) - server_global_theme_switch_to_1(ctx->server); + server_global_theme_switch_to_dark(ctx->server); else - server_global_theme_switch_to_2(ctx->server); + server_global_theme_switch_to_light(ctx->server); } else { if (signo == SIGUSR1) - term_theme_switch_to_1(ctx->term); + term_theme_switch_to_dark(ctx->term); else - term_theme_switch_to_2(ctx->term); + term_theme_switch_to_light(ctx->term); } return true; diff --git a/osc.c b/osc.c index 375eae5c..9407e7b8 100644 --- a/osc.c +++ b/osc.c @@ -1459,9 +1459,9 @@ osc_dispatch(struct terminal *term) case 11: term->colors.bg = color; if (!have_alpha) { - alpha = term->colors.active_theme == COLOR_THEME1 - ? term->conf->colors.alpha - : term->conf->colors2.alpha; + alpha = term->colors.active_theme == COLOR_THEME_DARK + ? term->conf->colors_dark.alpha + : term->conf->colors_light.alpha; } const bool changed = term->colors.alpha != alpha; @@ -1516,9 +1516,9 @@ osc_dispatch(struct terminal *term) /* Reset Color Number 'c' (whole table if no parameter) */ const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME1 - ? &term->conf->colors - : &term->conf->colors2; + term->colors.active_theme == COLOR_THEME_DARK + ? &term->conf->colors_dark + : &term->conf->colors_light; if (string[0] == '\0') { LOG_DBG("resetting all colors"); @@ -1559,9 +1559,9 @@ osc_dispatch(struct terminal *term) LOG_DBG("resetting foreground color"); const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME1 - ? &term->conf->colors - : &term->conf->colors2; + term->colors.active_theme == COLOR_THEME_DARK + ? &term->conf->colors_dark + : &term->conf->colors_light; term->colors.fg = theme->fg; term_damage_color(term, COLOR_DEFAULT, 0); @@ -1571,9 +1571,9 @@ osc_dispatch(struct terminal *term) LOG_DBG("resetting background color"); const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME1 - ? &term->conf->colors - : &term->conf->colors2; + term->colors.active_theme == COLOR_THEME_DARK + ? &term->conf->colors_dark + : &term->conf->colors_light; bool alpha_changed = term->colors.alpha != theme->alpha; @@ -1594,14 +1594,14 @@ osc_dispatch(struct terminal *term) LOG_DBG("resetting cursor color"); const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME1 - ? &term->conf->colors - : &term->conf->colors2; + term->colors.active_theme == COLOR_THEME_DARK + ? &term->conf->colors_dark + : &term->conf->colors_light; term->colors.cursor_fg = theme->cursor.text; term->colors.cursor_bg = theme->cursor.cursor; - if (term->conf->colors.use_custom.cursor) { + if (term->conf->colors_dark.use_custom.cursor) { term->colors.cursor_fg |= 1u << 31; term->colors.cursor_bg |= 1u << 31; } @@ -1614,9 +1614,9 @@ osc_dispatch(struct terminal *term) LOG_DBG("resetting selection background color"); const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME1 - ? &term->conf->colors - : &term->conf->colors2; + term->colors.active_theme == COLOR_THEME_DARK + ? &term->conf->colors_dark + : &term->conf->colors_light; term->colors.selection_bg = theme->selection_bg; break; @@ -1626,9 +1626,9 @@ osc_dispatch(struct terminal *term) LOG_DBG("resetting selection foreground color"); const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME1 - ? &term->conf->colors - : &term->conf->colors2; + term->colors.active_theme == COLOR_THEME_DARK + ? &term->conf->colors_dark + : &term->conf->colors_light; term->colors.selection_fg = theme->selection_fg; break; diff --git a/render.c b/render.c index 1d0f08af..ac8ece37 100644 --- a/render.c +++ b/render.c @@ -293,7 +293,7 @@ static inline uint32_t color_dim(const struct terminal *term, uint32_t color) { const struct config *conf = term->conf; - const uint8_t custom_dim = conf->colors.use_custom.dim; + const uint8_t custom_dim = conf->colors_dark.use_custom.dim; if (unlikely(custom_dim != 0)) { for (size_t i = 0; i < 8; i++) { @@ -302,7 +302,7 @@ color_dim(const struct terminal *term, uint32_t color) if (term->colors.table[0 + i] == color) { /* "Regular" color, return the corresponding "dim" */ - return conf->colors.dim[i]; + return conf->colors_dark.dim[i]; } else if (term->colors.table[8 + i] == color) { @@ -312,9 +312,9 @@ color_dim(const struct terminal *term, uint32_t color) } } - const struct color_theme *theme = term->colors.active_theme == COLOR_THEME1 - ? &conf->colors - : &conf->colors2; + const struct color_theme *theme = term->colors.active_theme == COLOR_THEME_DARK + ? &conf->colors_dark + : &conf->colors_light; return color_blend_towards( color, @@ -776,7 +776,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, } else if (!term->window->is_fullscreen && term->colors.alpha != 0xffff) { - switch (term->conf->colors.alpha_mode) { + switch (term->conf->colors_dark.alpha_mode) { case ALPHA_MODE_DEFAULT: { if (cell->attrs.bg_src == COLOR_DEFAULT) { alpha = term->colors.alpha; @@ -1175,8 +1175,8 @@ render_cell(struct terminal *term, pixman_image_t *pix, if (unlikely(cell->attrs.url)) { pixman_color_t url_color = color_hex_to_pixman( - term->conf->colors.use_custom.url - ? term->conf->colors.url + term->conf->colors_dark.use_custom.url + ? term->conf->colors_dark.url : term->colors.table[3], gamma_correct); draw_underline(term, pix, font, &url_color, x, y, cell_cols); @@ -1991,8 +1991,8 @@ render_overlay(struct terminal *term) case OVERLAY_FLASH: color = color_hex_to_pixman_with_alpha( - term->conf->colors.flash, - term->conf->colors.flash_alpha, + term->conf->colors_dark.flash, + term->conf->colors_dark.flash_alpha, wayl_do_linear_blending(term->wl, term->conf)); break; @@ -2510,10 +2510,10 @@ render_csd_title(struct terminal *term, const struct csd_data *info, uint32_t bg = term->conf->csd.color.title_set ? term->conf->csd.color.title - : 0xffu << 24 | term->conf->colors.fg; + : 0xffu << 24 | term->conf->colors_dark.fg; uint32_t fg = term->conf->csd.color.buttons_set ? term->conf->csd.color.buttons - : term->conf->colors.bg; + : term->conf->colors_dark.bg; if (!term->visual_focus) { bg = color_dim(term, bg); @@ -2607,7 +2607,7 @@ render_csd_border(struct terminal *term, enum csd_surface surf_idx, uint32_t _color = conf->csd.color.border_set ? conf->csd.color.border : conf->csd.color.title_set ? conf->csd.color.title : - 0xffu << 24 | term->conf->colors.fg; + 0xffu << 24 | term->conf->colors_dark.fg; if (!term->visual_focus) _color = color_dim(term, _color); @@ -2627,7 +2627,7 @@ static pixman_color_t get_csd_button_fg_color(const struct terminal *term) { const struct config *conf = term->conf; - uint32_t _color = conf->colors.bg; + uint32_t _color = conf->colors_dark.bg; uint16_t alpha = 0xffff; if (conf->csd.color.buttons_set) { @@ -2872,7 +2872,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, switch (surf_idx) { case CSD_SURF_MINIMIZE: - _color = term->conf->colors.table[4]; /* blue */ + _color = term->conf->colors_dark.table[4]; /* blue */ is_set = term->conf->csd.color.minimize_set; conf_color = &term->conf->csd.color.minimize; is_active = term->active_surface == TERM_SURF_BUTTON_MINIMIZE && @@ -2880,7 +2880,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, break; case CSD_SURF_MAXIMIZE: - _color = term->conf->colors.table[2]; /* green */ + _color = term->conf->colors_dark.table[2]; /* green */ is_set = term->conf->csd.color.maximize_set; conf_color = &term->conf->csd.color.maximize; is_active = term->active_surface == TERM_SURF_BUTTON_MAXIMIZE && @@ -2888,7 +2888,7 @@ render_csd_button(struct terminal *term, enum csd_surface surf_idx, break; case CSD_SURF_CLOSE: - _color = term->conf->colors.table[1]; /* red */ + _color = term->conf->colors_dark.table[1]; /* red */ is_set = term->conf->csd.color.close_set; conf_color = &term->conf->csd.color.quit; is_active = term->active_surface == TERM_SURF_BUTTON_CLOSE && @@ -3117,9 +3117,9 @@ render_scrollback_position(struct terminal *term) uint32_t fg = term->colors.table[0]; uint32_t bg = term->colors.table[8 + 4]; - if (term->conf->colors.use_custom.scrollback_indicator) { - fg = term->conf->colors.scrollback_indicator.fg; - bg = term->conf->colors.scrollback_indicator.bg; + if (term->conf->colors_dark.use_custom.scrollback_indicator) { + fg = term->conf->colors_dark.scrollback_indicator.fg; + bg = term->conf->colors_dark.scrollback_indicator.bg; } render_osd( @@ -3799,18 +3799,18 @@ render_search_box(struct terminal *term) const bool is_match = term->search.match_len == text_len; const bool custom_colors = is_match - ? term->conf->colors.use_custom.search_box_match - : term->conf->colors.use_custom.search_box_no_match; + ? term->conf->colors_dark.use_custom.search_box_match + : term->conf->colors_dark.use_custom.search_box_no_match; /* Background - yellow on empty/match, red on mismatch (default) */ const bool gamma_correct = wayl_do_linear_blending(term->wl, term->conf); const pixman_color_t color = color_hex_to_pixman( is_match ? (custom_colors - ? term->conf->colors.search_box.match.bg + ? term->conf->colors_dark.search_box.match.bg : term->colors.table[3]) : (custom_colors - ? term->conf->colors.search_box.no_match.bg + ? term->conf->colors_dark.search_box.no_match.bg : term->colors.table[1]), gamma_correct); @@ -3832,8 +3832,8 @@ render_search_box(struct terminal *term) pixman_color_t fg = color_hex_to_pixman( custom_colors ? (is_match - ? term->conf->colors.search_box.match.fg - : term->conf->colors.search_box.no_match.fg) + ? term->conf->colors_dark.search_box.match.fg + : term->conf->colors_dark.search_box.no_match.fg) : term->colors.table[0], gamma_correct); @@ -4254,11 +4254,11 @@ render_urls(struct terminal *term) struct buffer *bufs[render_count]; shm_get_many(chain, render_count, widths, heights, bufs, false); - uint32_t fg = term->conf->colors.use_custom.jump_label - ? term->conf->colors.jump_label.fg + uint32_t fg = term->conf->colors_dark.use_custom.jump_label + ? term->conf->colors_dark.jump_label.fg : term->colors.table[0]; - uint32_t bg = term->conf->colors.use_custom.jump_label - ? term->conf->colors.jump_label.bg + uint32_t bg = term->conf->colors_dark.use_custom.jump_label + ? term->conf->colors_dark.jump_label.bg : term->colors.table[3]; for (size_t i = 0; i < render_count; i++) { diff --git a/server.c b/server.c index 6b3e5094..25963325 100644 --- a/server.c +++ b/server.c @@ -182,11 +182,11 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) switch (sigusr.signo) { case SIGUSR1: - term_theme_switch_to_1(client->instance->terminal); + term_theme_switch_to_dark(client->instance->terminal); break; case SIGUSR2: - term_theme_switch_to_2(client->instance->terminal); + term_theme_switch_to_light(client->instance->terminal); break; default: @@ -670,21 +670,21 @@ server_destroy(struct server *server) } void -server_global_theme_switch_to_1(struct server *server) +server_global_theme_switch_to_dark(struct server *server) { - server->conf->initial_color_theme = COLOR_THEME1; + server->conf->initial_color_theme = COLOR_THEME_DARK; tll_foreach(server->clients, it) - term_theme_switch_to_1(it->item->instance->terminal); + term_theme_switch_to_dark(it->item->instance->terminal); tll_foreach(server->terminals, it) - term_theme_switch_to_1(it->item->terminal); + term_theme_switch_to_dark(it->item->terminal); } void -server_global_theme_switch_to_2(struct server *server) +server_global_theme_switch_to_light(struct server *server) { - server->conf->initial_color_theme = COLOR_THEME2; + server->conf->initial_color_theme = COLOR_THEME_LIGHT; tll_foreach(server->clients, it) - term_theme_switch_to_2(it->item->instance->terminal); + term_theme_switch_to_light(it->item->instance->terminal); tll_foreach(server->terminals, it) - term_theme_switch_to_2(it->item->terminal); + term_theme_switch_to_light(it->item->terminal); } diff --git a/server.h b/server.h index 6adfe7c6..683ad74d 100644 --- a/server.h +++ b/server.h @@ -10,5 +10,5 @@ struct server *server_init(struct config *conf, struct fdm *fdm, struct reaper *reaper, struct wayland *wayl); void server_destroy(struct server *server); -void server_global_theme_switch_to_1(struct server *server); -void server_global_theme_switch_to_2(struct server *server); +void server_global_theme_switch_to_dark(struct server *server); +void server_global_theme_switch_to_light(struct server *server); diff --git a/sixel.c b/sixel.c index c5ef01a1..07b97f46 100644 --- a/sixel.c +++ b/sixel.c @@ -137,7 +137,7 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) } const size_t active_palette_entries = min( - ALEN(term->conf->colors.sixel), term->sixel.palette_size); + ALEN(term->conf->colors_dark.sixel), term->sixel.palette_size); if (term->sixel.use_private_palette) { xassert(term->sixel.private_palette == NULL); @@ -145,7 +145,7 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) term->sixel.palette_size, sizeof(term->sixel.private_palette[0])); memcpy( - term->sixel.private_palette, term->conf->colors.sixel, + term->sixel.private_palette, term->conf->colors_dark.sixel, active_palette_entries * sizeof(term->sixel.private_palette[0])); if (term->sixel.linear_blending || term->sixel.use_10bit) { @@ -164,7 +164,7 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) term->sixel.palette_size, sizeof(term->sixel.shared_palette[0])); memcpy( - term->sixel.shared_palette, term->conf->colors.sixel, + term->sixel.shared_palette, term->conf->colors_dark.sixel, active_palette_entries * sizeof(term->sixel.shared_palette[0])); if (term->sixel.linear_blending || term->sixel.use_10bit) { diff --git a/terminal.c b/terminal.c index 3749416b..b670d606 100644 --- a/terminal.c +++ b/terminal.c @@ -1271,8 +1271,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, const struct color_theme *theme = NULL; switch (conf->initial_color_theme) { - case COLOR_THEME1: theme = &conf->colors; break; - case COLOR_THEME2: theme = &conf->colors2; break; + case COLOR_THEME_DARK: theme = &conf->colors_dark; break; + case COLOR_THEME_LIGHT: theme = &conf->colors_light; break; + case COLOR_THEME_1: BUG("COLOR_THEME_1 should not be used"); break; + case COLOR_THEME_2: BUG("COLOR_THEME_2 should not be used"); break; } /* Initialize configure-based terminal attributes */ @@ -2177,8 +2179,10 @@ term_reset(struct terminal *term, bool hard) const struct color_theme *theme = NULL; switch (term->conf->initial_color_theme) { - case COLOR_THEME1: theme = &term->conf->colors; break; - case COLOR_THEME2: theme = &term->conf->colors2; break; + case COLOR_THEME_DARK: theme = &term->conf->colors_dark; break; + case COLOR_THEME_LIGHT: theme = &term->conf->colors_light; break; + case COLOR_THEME_1: BUG("COLOR_THEME_1 should not be used"); break; + case COLOR_THEME_2: BUG("COLOR_THEME_2 should not be used"); break; } term->flash.active = false; @@ -4742,13 +4746,13 @@ term_send_size_notification(struct terminal *term) } void -term_theme_switch_to_1(struct terminal *term) +term_theme_switch_to_dark(struct terminal *term) { - if (term->colors.active_theme == COLOR_THEME1) + if (term->colors.active_theme == COLOR_THEME_DARK) return; - term_theme_apply(term, &term->conf->colors); - term->colors.active_theme = COLOR_THEME1; + term_theme_apply(term, &term->conf->colors_dark); + term->colors.active_theme = COLOR_THEME_DARK; wayl_win_alpha_changed(term->window); term_font_subpixel_changed(term); @@ -4762,13 +4766,13 @@ term_theme_switch_to_1(struct terminal *term) } void -term_theme_switch_to_2(struct terminal *term) +term_theme_switch_to_light(struct terminal *term) { - if (term->colors.active_theme == COLOR_THEME2) + if (term->colors.active_theme == COLOR_THEME_LIGHT) return; - term_theme_apply(term, &term->conf->colors2); - term->colors.active_theme = COLOR_THEME2; + term_theme_apply(term, &term->conf->colors_light); + term->colors.active_theme = COLOR_THEME_LIGHT; wayl_win_alpha_changed(term->window); term_font_subpixel_changed(term); @@ -4784,15 +4788,15 @@ term_theme_switch_to_2(struct terminal *term) void term_theme_toggle(struct terminal *term) { - if (term->colors.active_theme == COLOR_THEME1) { - term_theme_apply(term, &term->conf->colors2); - term->colors.active_theme = COLOR_THEME2; + if (term->colors.active_theme == COLOR_THEME_DARK) { + term_theme_apply(term, &term->conf->colors_light); + term->colors.active_theme = COLOR_THEME_LIGHT; if (term->report_theme_changes) term_to_slave(term, "\033[?997;2n", 9); } else { - term_theme_apply(term, &term->conf->colors); - term->colors.active_theme = COLOR_THEME1; + term_theme_apply(term, &term->conf->colors_dark); + term->colors.active_theme = COLOR_THEME_DARK; if (term->report_theme_changes) term_to_slave(term, "\033[?997;1n", 9); diff --git a/terminal.h b/terminal.h index 364d57b3..fe39341d 100644 --- a/terminal.h +++ b/terminal.h @@ -994,8 +994,8 @@ void term_enable_size_notifications(struct terminal *term); void term_disable_size_notifications(struct terminal *term); void term_send_size_notification(struct terminal *term); -void term_theme_switch_to_1(struct terminal *term); -void term_theme_switch_to_2(struct terminal *term); +void term_theme_switch_to_dark(struct terminal *term); +void term_theme_switch_to_light(struct terminal *term); void term_theme_toggle(struct terminal *term); static inline void term_reset_grapheme_state(struct terminal *term) diff --git a/tests/test-config.c b/tests/test-config.c index 268733db..f83a9beb 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -521,6 +521,14 @@ test_section_main(void) (int []){STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN}, (int *)&conf.startup_mode); + test_enum( + &ctx, &parse_section_main, "initial-color-theme", + 2, + (const char *[]){"dark", "light", "1", "2"}, + (int []){COLOR_THEME_DARK, COLOR_THEME_LIGHT, + COLOR_THEME_DARK, COLOR_THEME_LIGHT}, + (int *)&conf.initial_color_theme); + /* TODO: font (custom) */ /* TODO: include (custom) */ /* TODO: bold-text-in-bright (enum/boolean) */ @@ -695,78 +703,157 @@ test_section_touch(void) } static void -test_section_colors(void) +test_section_colors_dark(void) { struct config conf = {0}; struct context ctx = { - .conf = &conf, .section = "colors", .path = "unittest"}; + .conf = &conf, .section = "colors-dark", .path = "unittest"}; test_invalid_key(&ctx, &parse_section_colors, "invalid-key"); - test_color(&ctx, &parse_section_colors, "foreground", false, &conf.colors.fg); - test_color(&ctx, &parse_section_colors, "background", false, &conf.colors.bg); - test_color(&ctx, &parse_section_colors, "regular0", false, &conf.colors.table[0]); - test_color(&ctx, &parse_section_colors, "regular1", false, &conf.colors.table[1]); - test_color(&ctx, &parse_section_colors, "regular2", false, &conf.colors.table[2]); - test_color(&ctx, &parse_section_colors, "regular3", false, &conf.colors.table[3]); - test_color(&ctx, &parse_section_colors, "regular4", false, &conf.colors.table[4]); - test_color(&ctx, &parse_section_colors, "regular5", false, &conf.colors.table[5]); - test_color(&ctx, &parse_section_colors, "regular6", false, &conf.colors.table[6]); - test_color(&ctx, &parse_section_colors, "regular7", false, &conf.colors.table[7]); - test_color(&ctx, &parse_section_colors, "bright0", false, &conf.colors.table[8]); - test_color(&ctx, &parse_section_colors, "bright1", false, &conf.colors.table[9]); - test_color(&ctx, &parse_section_colors, "bright2", false, &conf.colors.table[10]); - test_color(&ctx, &parse_section_colors, "bright3", false, &conf.colors.table[11]); - test_color(&ctx, &parse_section_colors, "bright4", false, &conf.colors.table[12]); - test_color(&ctx, &parse_section_colors, "bright5", false, &conf.colors.table[13]); - test_color(&ctx, &parse_section_colors, "bright6", false, &conf.colors.table[14]); - test_color(&ctx, &parse_section_colors, "bright7", false, &conf.colors.table[15]); - test_color(&ctx, &parse_section_colors, "dim0", false, &conf.colors.dim[0]); - test_color(&ctx, &parse_section_colors, "dim1", false, &conf.colors.dim[1]); - test_color(&ctx, &parse_section_colors, "dim2", false, &conf.colors.dim[2]); - test_color(&ctx, &parse_section_colors, "dim3", false, &conf.colors.dim[3]); - test_color(&ctx, &parse_section_colors, "dim4", false, &conf.colors.dim[4]); - test_color(&ctx, &parse_section_colors, "dim5", false, &conf.colors.dim[5]); - test_color(&ctx, &parse_section_colors, "dim6", false, &conf.colors.dim[6]); - test_color(&ctx, &parse_section_colors, "dim7", false, &conf.colors.dim[7]); - test_color(&ctx, &parse_section_colors, "selection-foreground", false, &conf.colors.selection_fg); - test_color(&ctx, &parse_section_colors, "selection-background", false, &conf.colors.selection_bg); - test_color(&ctx, &parse_section_colors, "urls", false, &conf.colors.url); - test_two_colors(&ctx, &parse_section_colors, "jump-labels", false, - &conf.colors.jump_label.fg, - &conf.colors.jump_label.bg); - test_two_colors(&ctx, &parse_section_colors, "scrollback-indicator", false, - &conf.colors.scrollback_indicator.fg, - &conf.colors.scrollback_indicator.bg); - test_two_colors(&ctx, &parse_section_colors, "search-box-no-match", false, - &conf.colors.search_box.no_match.fg, - &conf.colors.search_box.no_match.bg); - test_two_colors(&ctx, &parse_section_colors, "search-box-match", false, - &conf.colors.search_box.match.fg, - &conf.colors.search_box.match.bg); + test_color(&ctx, &parse_section_colors_dark, "foreground", false, &conf.colors_dark.fg); + test_color(&ctx, &parse_section_colors_dark, "background", false, &conf.colors_dark.bg); + test_color(&ctx, &parse_section_colors_dark, "regular0", false, &conf.colors_dark.table[0]); + test_color(&ctx, &parse_section_colors_dark, "regular1", false, &conf.colors_dark.table[1]); + test_color(&ctx, &parse_section_colors_dark, "regular2", false, &conf.colors_dark.table[2]); + test_color(&ctx, &parse_section_colors_dark, "regular3", false, &conf.colors_dark.table[3]); + test_color(&ctx, &parse_section_colors_dark, "regular4", false, &conf.colors_dark.table[4]); + test_color(&ctx, &parse_section_colors_dark, "regular5", false, &conf.colors_dark.table[5]); + test_color(&ctx, &parse_section_colors_dark, "regular6", false, &conf.colors_dark.table[6]); + test_color(&ctx, &parse_section_colors_dark, "regular7", false, &conf.colors_dark.table[7]); + test_color(&ctx, &parse_section_colors_dark, "bright0", false, &conf.colors_dark.table[8]); + test_color(&ctx, &parse_section_colors_dark, "bright1", false, &conf.colors_dark.table[9]); + test_color(&ctx, &parse_section_colors_dark, "bright2", false, &conf.colors_dark.table[10]); + test_color(&ctx, &parse_section_colors_dark, "bright3", false, &conf.colors_dark.table[11]); + test_color(&ctx, &parse_section_colors_dark, "bright4", false, &conf.colors_dark.table[12]); + test_color(&ctx, &parse_section_colors_dark, "bright5", false, &conf.colors_dark.table[13]); + test_color(&ctx, &parse_section_colors_dark, "bright6", false, &conf.colors_dark.table[14]); + test_color(&ctx, &parse_section_colors_dark, "bright7", false, &conf.colors_dark.table[15]); + test_color(&ctx, &parse_section_colors_dark, "dim0", false, &conf.colors_dark.dim[0]); + test_color(&ctx, &parse_section_colors_dark, "dim1", false, &conf.colors_dark.dim[1]); + test_color(&ctx, &parse_section_colors_dark, "dim2", false, &conf.colors_dark.dim[2]); + test_color(&ctx, &parse_section_colors_dark, "dim3", false, &conf.colors_dark.dim[3]); + test_color(&ctx, &parse_section_colors_dark, "dim4", false, &conf.colors_dark.dim[4]); + test_color(&ctx, &parse_section_colors_dark, "dim5", false, &conf.colors_dark.dim[5]); + test_color(&ctx, &parse_section_colors_dark, "dim6", false, &conf.colors_dark.dim[6]); + test_color(&ctx, &parse_section_colors_dark, "dim7", false, &conf.colors_dark.dim[7]); + test_color(&ctx, &parse_section_colors_dark, "selection-foreground", false, &conf.colors_dark.selection_fg); + test_color(&ctx, &parse_section_colors_dark, "selection-background", false, &conf.colors_dark.selection_bg); + test_color(&ctx, &parse_section_colors_dark, "urls", false, &conf.colors_dark.url); + test_two_colors(&ctx, &parse_section_colors_dark, "jump-labels", false, + &conf.colors_dark.jump_label.fg, + &conf.colors_dark.jump_label.bg); + test_two_colors(&ctx, &parse_section_colors_dark, "scrollback-indicator", false, + &conf.colors_dark.scrollback_indicator.fg, + &conf.colors_dark.scrollback_indicator.bg); + test_two_colors(&ctx, &parse_section_colors_dark, "search-box-no-match", false, + &conf.colors_dark.search_box.no_match.fg, + &conf.colors_dark.search_box.no_match.bg); + test_two_colors(&ctx, &parse_section_colors_dark, "search-box-match", false, + &conf.colors_dark.search_box.match.fg, + &conf.colors_dark.search_box.match.bg); - test_two_colors(&ctx, &parse_section_colors, "cursor", false, - &conf.colors.cursor.text, - &conf.colors.cursor.cursor); + test_two_colors(&ctx, &parse_section_colors_dark, "cursor", false, + &conf.colors_dark.cursor.text, + &conf.colors_dark.cursor.cursor); - test_enum(&ctx, &parse_section_colors, "alpha-mode", 3, + test_enum(&ctx, &parse_section_colors_dark, "alpha-mode", 3, (const char *[]){"default", "matching", "all"}, (int []){ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL}, - (int *)&conf.colors.alpha_mode); + (int *)&conf.colors_dark.alpha_mode); - test_enum(&ctx, &parse_section_colors, "dim-blend-towards", 2, + test_enum(&ctx, &parse_section_colors_dark, "dim-blend-towards", 2, (const char *[]){"black", "white"}, (int []){DIM_BLEND_TOWARDS_BLACK, DIM_BLEND_TOWARDS_WHITE}, - (int *)&conf.colors.dim_blend_towards); + (int *)&conf.colors_dark.dim_blend_towards); for (size_t i = 0; i < 255; i++) { char key_name[4]; sprintf(key_name, "%zu", i); - test_color(&ctx, &parse_section_colors, key_name, false, - &conf.colors.table[i]); + test_color(&ctx, &parse_section_colors_dark, key_name, false, + &conf.colors_dark.table[i]); } - test_invalid_key(&ctx, &parse_section_colors, "256"); + test_invalid_key(&ctx, &parse_section_colors_dark, "256"); + + /* TODO: alpha (float in range 0-1, converted to uint16_t) */ + + config_free(&conf); +} + +static void +test_section_colors_light(void) +{ + struct config conf = {0}; + struct context ctx = { + .conf = &conf, .section = "colors-light", .path = "unittest"}; + + test_invalid_key(&ctx, &parse_section_colors, "invalid-key"); + + test_color(&ctx, &parse_section_colors_light, "foreground", false, &conf.colors_light.fg); + test_color(&ctx, &parse_section_colors_light, "background", false, &conf.colors_light.bg); + test_color(&ctx, &parse_section_colors_light, "regular0", false, &conf.colors_light.table[0]); + test_color(&ctx, &parse_section_colors_light, "regular1", false, &conf.colors_light.table[1]); + test_color(&ctx, &parse_section_colors_light, "regular2", false, &conf.colors_light.table[2]); + test_color(&ctx, &parse_section_colors_light, "regular3", false, &conf.colors_light.table[3]); + test_color(&ctx, &parse_section_colors_light, "regular4", false, &conf.colors_light.table[4]); + test_color(&ctx, &parse_section_colors_light, "regular5", false, &conf.colors_light.table[5]); + test_color(&ctx, &parse_section_colors_light, "regular6", false, &conf.colors_light.table[6]); + test_color(&ctx, &parse_section_colors_light, "regular7", false, &conf.colors_light.table[7]); + test_color(&ctx, &parse_section_colors_light, "bright0", false, &conf.colors_light.table[8]); + test_color(&ctx, &parse_section_colors_light, "bright1", false, &conf.colors_light.table[9]); + test_color(&ctx, &parse_section_colors_light, "bright2", false, &conf.colors_light.table[10]); + test_color(&ctx, &parse_section_colors_light, "bright3", false, &conf.colors_light.table[11]); + test_color(&ctx, &parse_section_colors_light, "bright4", false, &conf.colors_light.table[12]); + test_color(&ctx, &parse_section_colors_light, "bright5", false, &conf.colors_light.table[13]); + test_color(&ctx, &parse_section_colors_light, "bright6", false, &conf.colors_light.table[14]); + test_color(&ctx, &parse_section_colors_light, "bright7", false, &conf.colors_light.table[15]); + test_color(&ctx, &parse_section_colors_light, "dim0", false, &conf.colors_light.dim[0]); + test_color(&ctx, &parse_section_colors_light, "dim1", false, &conf.colors_light.dim[1]); + test_color(&ctx, &parse_section_colors_light, "dim2", false, &conf.colors_light.dim[2]); + test_color(&ctx, &parse_section_colors_light, "dim3", false, &conf.colors_light.dim[3]); + test_color(&ctx, &parse_section_colors_light, "dim4", false, &conf.colors_light.dim[4]); + test_color(&ctx, &parse_section_colors_light, "dim5", false, &conf.colors_light.dim[5]); + test_color(&ctx, &parse_section_colors_light, "dim6", false, &conf.colors_light.dim[6]); + test_color(&ctx, &parse_section_colors_light, "dim7", false, &conf.colors_light.dim[7]); + test_color(&ctx, &parse_section_colors_light, "selection-foreground", false, &conf.colors_light.selection_fg); + test_color(&ctx, &parse_section_colors_light, "selection-background", false, &conf.colors_light.selection_bg); + test_color(&ctx, &parse_section_colors_light, "urls", false, &conf.colors_light.url); + test_two_colors(&ctx, &parse_section_colors_light, "jump-labels", false, + &conf.colors_light.jump_label.fg, + &conf.colors_light.jump_label.bg); + test_two_colors(&ctx, &parse_section_colors_light, "scrollback-indicator", false, + &conf.colors_light.scrollback_indicator.fg, + &conf.colors_light.scrollback_indicator.bg); + test_two_colors(&ctx, &parse_section_colors_light, "search-box-no-match", false, + &conf.colors_light.search_box.no_match.fg, + &conf.colors_light.search_box.no_match.bg); + test_two_colors(&ctx, &parse_section_colors_light, "search-box-match", false, + &conf.colors_light.search_box.match.fg, + &conf.colors_light.search_box.match.bg); + + test_two_colors(&ctx, &parse_section_colors_light, "cursor", false, + &conf.colors_light.cursor.text, + &conf.colors_light.cursor.cursor); + + test_enum(&ctx, &parse_section_colors_light, "alpha-mode", 3, + (const char *[]){"default", "matching", "all"}, + (int []){ALPHA_MODE_DEFAULT, ALPHA_MODE_MATCHING, ALPHA_MODE_ALL}, + (int *)&conf.colors_light.alpha_mode); + + test_enum(&ctx, &parse_section_colors_light, "dim-blend-towards", 2, + (const char *[]){"black", "white"}, + (int []){DIM_BLEND_TOWARDS_BLACK, DIM_BLEND_TOWARDS_WHITE}, + (int *)&conf.colors_light.dim_blend_towards); + + for (size_t i = 0; i < 255; i++) { + char key_name[4]; + sprintf(key_name, "%zu", i); + test_color(&ctx, &parse_section_colors_light, key_name, false, + &conf.colors_light.table[i]); + } + + test_invalid_key(&ctx, &parse_section_colors_light, "256"); /* TODO: alpha (float in range 0-1, converted to uint16_t) */ @@ -1444,7 +1531,8 @@ main(int argc, const char *const *argv) test_section_cursor(); test_section_mouse(); test_section_touch(); - test_section_colors(); + test_section_colors_dark(); + test_section_colors_light(); test_section_csd(); test_section_key_bindings(); test_section_key_bindings_collisions(); diff --git a/themes/aeroroot b/themes/aeroroot index 2a0e0985..dbeb2e81 100644 --- a/themes/aeroroot +++ b/themes/aeroroot @@ -1,7 +1,7 @@ # -*- conf -*- # Aero root theme -[colors] +[colors-dark] cursor=1a1a1a 9fd5f5 foreground=dedeef background=1a1a1a diff --git a/themes/alacritty b/themes/alacritty index 14503887..68d1c68c 100644 --- a/themes/alacritty +++ b/themes/alacritty @@ -1,7 +1,7 @@ # -*- conf -*- # Alacritty -[colors] +[colors-dark] cursor = 181818 56d8c9 background= 181818 foreground= d8d8d8 diff --git a/themes/apprentice b/themes/apprentice index 6b67d21d..291ab8db 100644 --- a/themes/apprentice +++ b/themes/apprentice @@ -1,7 +1,7 @@ # -*- conf -*- # https://github.com/romainl/Apprentice -[colors] +[colors-dark] cursor=262626 6c6c6c foreground=bcbcbc background=262626 diff --git a/themes/ayu-mirage b/themes/ayu-mirage index 4646e418..2d9b6b54 100644 --- a/themes/ayu-mirage +++ b/themes/ayu-mirage @@ -2,7 +2,7 @@ # theme: Ayu Mirage # description: a theme based on Ayu Mirage for Sublime Text (original: https://github.com/dempfi/ayu) -[colors] +[colors-dark] cursor = ffcc66 665a44 foreground = cccac2 background = 242936 diff --git a/themes/catppuccin-frappe b/themes/catppuccin-frappe index 44bef16c..3acae600 100644 --- a/themes/catppuccin-frappe +++ b/themes/catppuccin-frappe @@ -1,7 +1,7 @@ # _*_ conf _*_ # Catppuccin Frappe -[colors] +[colors-dark] foreground=c6d0f5 background=303446 diff --git a/themes/catppuccin-latte b/themes/catppuccin-latte index d0b90e64..ca7a7aae 100644 --- a/themes/catppuccin-latte +++ b/themes/catppuccin-latte @@ -1,7 +1,10 @@ # _*_ conf _*_ # Catppuccin Latte -[colors] +[main] +initial-color-theme=light + +[colors-light] foreground=4c4f69 background=eff1f5 diff --git a/themes/catppuccin-macchiato b/themes/catppuccin-macchiato index ae8adab8..8f5ea36e 100644 --- a/themes/catppuccin-macchiato +++ b/themes/catppuccin-macchiato @@ -1,7 +1,7 @@ # _*_ conf _*_ # Catppuccin Macchiato -[colors] +[colors-dark] foreground=cad3f5 background=24273a diff --git a/themes/catppuccin-mocha b/themes/catppuccin-mocha index d29eb0ec..7d98dc0f 100644 --- a/themes/catppuccin-mocha +++ b/themes/catppuccin-mocha @@ -1,7 +1,7 @@ # _*_ conf _*_ # Catppuccin Mocha -[colors] +[colors-dark] foreground=cdd6f4 background=1e1e2e diff --git a/themes/chiba-dark b/themes/chiba-dark index 8727f684..ffaf6cb2 100644 --- a/themes/chiba-dark +++ b/themes/chiba-dark @@ -3,7 +3,7 @@ # author: ayushnix (https://sr.ht/~ayushnix) # description: A dark theme with bright cyberpunk colors (WCAG AAA compliant) -[colors] +[colors-dark] cursor = 181818 cdcdcd foreground = cdcdcd background = 181818 diff --git a/themes/derp b/themes/derp index 45eed752..42af3377 100644 --- a/themes/derp +++ b/themes/derp @@ -1,7 +1,7 @@ # -*- conf -*- # Derp -[colors] +[colors-dark] cursor=000000 ffffff foreground=ffffff background=000000 diff --git a/themes/deus b/themes/deus index 0d52e55b..69c44944 100644 --- a/themes/deus +++ b/themes/deus @@ -2,7 +2,7 @@ # Deus # Color palette based on: https://github.com/ajmwagar/vim-deus -[colors] +[colors-dark] cursor=2c323b eaeaea background=2c323b foreground=eaeaea diff --git a/themes/dracula b/themes/dracula index 008fc150..82994203 100644 --- a/themes/dracula +++ b/themes/dracula @@ -1,7 +1,7 @@ # -*- conf -*- # Dracula -[colors] +[colors-dark] cursor=282a36 f8f8f2 foreground=f8f8f2 background=282a36 diff --git a/themes/dracula-iterm b/themes/dracula-iterm index 249bb6ab..b75ddd9c 100644 --- a/themes/dracula-iterm +++ b/themes/dracula-iterm @@ -1,7 +1,7 @@ # -*- conf -*- # Dracula iTerm2 variant -[colors] +[colors-dark] cursor=ffffff bbbbbb foreground=f8f8f2 background=1e1f29 diff --git a/themes/electrophoretic b/themes/electrophoretic index e0bf6e79..8bc022ea 100644 --- a/themes/electrophoretic +++ b/themes/electrophoretic @@ -5,7 +5,10 @@ # text and the white background. # author: Eugen Rahaian -[colors] +[main] +initial-color-theme=light + +[colors-light] cursor=ffffff 515151 background= ffffff foreground= 000000 diff --git a/themes/gruvbox b/themes/gruvbox index 6bc97352..e44f3ea9 100644 --- a/themes/gruvbox +++ b/themes/gruvbox @@ -1,7 +1,7 @@ # -*- conf -*- # Gruvbox -[colors] +[colors-dark] background=282828 foreground=ebdbb2 regular0=282828 @@ -21,7 +21,7 @@ bright5=d3869b bright6=8ec07c bright7=ebdbb2 -[colors2] +[colors-light] background=fbf1c7 foreground=3c3836 regular0=fbf1c7 diff --git a/themes/gruvbox-dark b/themes/gruvbox-dark index 73207199..c5dadcc5 100644 --- a/themes/gruvbox-dark +++ b/themes/gruvbox-dark @@ -1,7 +1,7 @@ # -*- conf -*- # Gruvbox -[colors] +[colors-dark] background=282828 foreground=ebdbb2 regular0=282828 diff --git a/themes/gruvbox-light b/themes/gruvbox-light index 6a7a2416..6b616612 100644 --- a/themes/gruvbox-light +++ b/themes/gruvbox-light @@ -1,7 +1,10 @@ # -*- conf -*- # Gruvbox - Light -[colors] +[main] +initial-color-theme=light + +[colors-light] background=fbf1c7 foreground=3c3836 regular0=fbf1c7 diff --git a/themes/hacktober b/themes/hacktober index dfcc4c7e..ecdb18fb 100644 --- a/themes/hacktober +++ b/themes/hacktober @@ -1,6 +1,6 @@ # -*- conf -*- -[colors] +[colors-dark] cursor=141414 c9c9c9 foreground=c9c9c9 background=141414 diff --git a/themes/iterm b/themes/iterm index 45b1a0bf..c5ffc190 100644 --- a/themes/iterm +++ b/themes/iterm @@ -2,7 +2,7 @@ # this foot theme is based on alacritty iterm theme: # https://github.com/alacritty/alacritty-theme/blob/master/themes/iterm.toml -[colors] +[colors-dark] foreground=fffbf6 background=101421 diff --git a/themes/jetbrains-darcula b/themes/jetbrains-darcula index e6997848..0092b795 100644 --- a/themes/jetbrains-darcula +++ b/themes/jetbrains-darcula @@ -2,7 +2,7 @@ # JetBrains Darcula # Palette based on the same theme from https://github.com/dexpota/kitty-themes -[colors] +[colors-dark] cursor=202020 ffffff background=202020 foreground=adadad diff --git a/themes/kitty b/themes/kitty index f43eea9d..81fd003e 100644 --- a/themes/kitty +++ b/themes/kitty @@ -1,6 +1,6 @@ # -*- conf -*- -[colors] +[colors-dark] cursor=111111 cccccc foreground=dddddd background=000000 diff --git a/themes/material-amber b/themes/material-amber index 27983833..69126aa0 100644 --- a/themes/material-amber +++ b/themes/material-amber @@ -2,7 +2,10 @@ # Material Amber # Based on material.io guidelines with Amber 50 background -[colors] +[main] +initial-color-theme=light + +[colors-light] cursor=fff8e1 21201d foreground = 21201d background = fff8e1 diff --git a/themes/material-design b/themes/material-design index 4a9e008a..bf1d0a6b 100644 --- a/themes/material-design +++ b/themes/material-design @@ -2,7 +2,7 @@ # Material # From https://github.com/MartinSeeler/iterm2-material-design -[colors] +[colors-dark] foreground=ECEFF1 background=263238 regular0=546E7A # black diff --git a/themes/modus-operandi b/themes/modus-operandi index 2d417bb5..6baca2f7 100644 --- a/themes/modus-operandi +++ b/themes/modus-operandi @@ -3,7 +3,11 @@ # modus-operandi # See: https://protesilaos.com/emacs/modus-themes # -[colors] + +[main] +initial-color-theme=light + +[colors-light] background=ffffff foreground=000000 regular0=000000 diff --git a/themes/modus-vivendi b/themes/modus-vivendi index 82b1075d..9ee670ec 100644 --- a/themes/modus-vivendi +++ b/themes/modus-vivendi @@ -4,7 +4,7 @@ # See: https://protesilaos.com/emacs/modus-themes # -[colors] +[colors-dark] background=000000 foreground=ffffff regular0=000000 diff --git a/themes/modus-vivendi-tinted b/themes/modus-vivendi-tinted index 67cf02a0..6a61fc79 100644 --- a/themes/modus-vivendi-tinted +++ b/themes/modus-vivendi-tinted @@ -4,7 +4,7 @@ # See: https://protesilaos.com/emacs/modus-themes # -[colors] +[colors-dark] background=0d0e1c foreground=ffffff regular0=000000 diff --git a/themes/molokai b/themes/molokai index c3935f69..19e1b6fa 100644 --- a/themes/molokai +++ b/themes/molokai @@ -2,7 +2,7 @@ # Molokai # Based on zhou13's at https://github.com/zhou13/molokai-terminal/blob/master/xterm/Xresources -[colors] +[colors-dark] background=1B1D1E foreground=CCCCCC regular0=1B1D1E diff --git a/themes/monokai-pro b/themes/monokai-pro index 5d9f31a9..3044da91 100644 --- a/themes/monokai-pro +++ b/themes/monokai-pro @@ -1,7 +1,7 @@ # -*- conf -*- # Monokai Pro -[colors] +[colors-dark] background=2D2A2E foreground=FCFCFA regular0=403E41 diff --git a/themes/moonfly b/themes/moonfly index 0dbe0e95..b30e3156 100644 --- a/themes/moonfly +++ b/themes/moonfly @@ -2,7 +2,7 @@ # moonfly # Based on https://github.com/bluz71/vim-moonfly-colors -[colors] +[colors-dark] cursor = 080808 9e9e9e foreground = b2b2b2 background = 080808 diff --git a/themes/neon b/themes/neon index d11a36d0..74884e03 100644 --- a/themes/neon +++ b/themes/neon @@ -6,7 +6,7 @@ # https://xcolors.net/neon # -[colors] +[colors-dark] foreground=f8f8f8 background=171717 regular0=171717 diff --git a/themes/night-owl b/themes/night-owl index 43a5c054..e9e40404 100644 --- a/themes/night-owl +++ b/themes/night-owl @@ -1,7 +1,7 @@ # _*_ conf _*_ # Night Owl -[colors] +[colors-dark] cursor=011627 80a4c2 foreground=d6deeb background=011627 diff --git a/themes/nightfly b/themes/nightfly index 37205f0f..ccdd183a 100644 --- a/themes/nightfly +++ b/themes/nightfly @@ -2,7 +2,7 @@ # nightfly # Based on https://github.com/bluz71/vim-nightfly-guicolors -[colors] +[colors-dark] cursor = 080808 9ca1aa foreground = acb4c2 background = 011627 diff --git a/themes/noirblaze b/themes/noirblaze index 42daf11b..b21055a4 100644 --- a/themes/noirblaze +++ b/themes/noirblaze @@ -3,7 +3,7 @@ # https://github.com/n1ghtmare/noirblaze-kitty -[colors] +[colors-dark] cursor=121212 ff0088 foreground=d5d5d5 background=121212 diff --git a/themes/nord b/themes/nord index 9b988ad6..eb2fdf0f 100644 --- a/themes/nord +++ b/themes/nord @@ -6,7 +6,7 @@ # this specific foot theme is based on nord-alacritty: # https://github.com/arcticicestudio/nord-alacritty/blob/develop/src/nord.yml -[colors] +[colors-dark] cursor = 2e3440 d8dee9 foreground = d8dee9 background = 2e3440 diff --git a/themes/nordiq b/themes/nordiq index 0df5c7de..1efccba6 100644 --- a/themes/nordiq +++ b/themes/nordiq @@ -1,7 +1,7 @@ # -*- conf -*- # Nordiq -[colors] +[colors-dark] cursor=eeeeee 9f515a foreground=dbdee9 background=0e1420 diff --git a/themes/nvim b/themes/nvim index bf629c0a..74dd1ac6 100644 --- a/themes/nvim +++ b/themes/nvim @@ -3,7 +3,7 @@ # Uses the dark color palette from the default Neovim color scheme # See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L419 -[colors] +[colors-dark] cursor=14161b e0e2ea # NvimDarkGrey2 NvimLightGrey2 foreground=e0e2ea # NvimLightGrey2 background=14161b # NvimDarkGrey2 @@ -29,7 +29,7 @@ bright5=ffcaff # NvimLightMagenta bright6=8cf8f7 # NvimLightCyan bright7=eef1f8 # NvimLightGrey1 -[colors2] +[colors-light] cursor=e0e2ea 14161b # NvimLightGrey2 NvimDarkGrey2 foreground=14161b # NvimDarkGrey2 background=e0e2ea # NvimLightGrey2 diff --git a/themes/nvim-dark b/themes/nvim-dark index 9a177770..fe3afb74 100644 --- a/themes/nvim-dark +++ b/themes/nvim-dark @@ -3,7 +3,7 @@ # Uses the dark color palette from the default Neovim color scheme # See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L419 -[colors] +[colors-dark] cursor=14161b e0e2ea # NvimDarkGrey2 NvimLightGrey2 foreground=e0e2ea # NvimLightGrey2 background=14161b # NvimDarkGrey2 diff --git a/themes/nvim-light b/themes/nvim-light index aca4e156..fd8943b1 100644 --- a/themes/nvim-light +++ b/themes/nvim-light @@ -3,7 +3,10 @@ # Uses the light color palette from the default Neovim color scheme # See: https://github.com/neovim/neovim/blob/fb6c059dc55c8d594102937be4dd70f5ff51614a/src/nvim/highlight_group.c#L334 -[colors] +[main] +initial-color-theme=light + +[colors-light] cursor=e0e2ea 14161b # NvimLightGrey2 NvimDarkGrey2 foreground=14161b # NvimDarkGrey2 background=e0e2ea # NvimLightGrey2 diff --git a/themes/onedark b/themes/onedark index 0932960b..6d66e87e 100644 --- a/themes/onedark +++ b/themes/onedark @@ -1,7 +1,7 @@ # OneDark # Palette based on the same theme from https://github.com/dexpota/kitty-themes -[colors] +[colors-dark] cursor=111111 cccccc foreground=979eab background=282c34 diff --git a/themes/onehalf-dark b/themes/onehalf-dark index 1adc9e23..1faca455 100644 --- a/themes/onehalf-dark +++ b/themes/onehalf-dark @@ -7,7 +7,7 @@ # + cursor colors from: # https://github.com/sonph/onehalf/blob/master/iterm/OneHalfDark.itermcolors -[colors] +[colors-dark] cursor=dcdfe4 a3b3cc foreground=dcdfe4 background=282c34 diff --git a/themes/panda b/themes/panda index b02c7e9f..2c1dc7c5 100644 --- a/themes/panda +++ b/themes/panda @@ -1,7 +1,7 @@ # -*- conf -*- # http://panda.siamak.me/ -[colors] +[colors-dark] # alpha=1.0 background=1D1E20 foreground=F0F0F0 diff --git a/themes/paper-color b/themes/paper-color index f158c148..09934925 100644 --- a/themes/paper-color +++ b/themes/paper-color @@ -2,7 +2,7 @@ # PaperColorDark # Palette based on https://github.com/NLKNguyen/papercolor-theme -[colors] +[colors-dark] cursor=1c1c1c eeeeee background=1c1c1c foreground=eeeeee @@ -25,7 +25,7 @@ bright7=5f8787 # bright white # selection-foreground=1c1c1c # selection-background=af87d7 -[colors2] +[colors-light] cursor=eeeeee 444444 background=eeeeee foreground=444444 diff --git a/themes/paper-color-dark b/themes/paper-color-dark index 991bcc9d..26260c6f 100644 --- a/themes/paper-color-dark +++ b/themes/paper-color-dark @@ -2,7 +2,7 @@ # PaperColorDark # Palette based on https://github.com/NLKNguyen/papercolor-theme -[colors] +[colors-dark] cursor=1c1c1c eeeeee background=1c1c1c foreground=eeeeee diff --git a/themes/paper-color-light b/themes/paper-color-light index b8a6ceec..2f7a8003 100644 --- a/themes/paper-color-light +++ b/themes/paper-color-light @@ -2,7 +2,10 @@ # PaperColor Light # Palette based on https://github.com/NLKNguyen/papercolor-theme -[colors] +[main] +initial-color-theme=light +xs +[colors-light] cursor=eeeeee 444444 background=eeeeee foreground=444444 diff --git a/themes/poimandres b/themes/poimandres index b4edc175..a2123ac5 100644 --- a/themes/poimandres +++ b/themes/poimandres @@ -1,7 +1,7 @@ # Based on Poimandres color theme for kitti terminal emulator # https://github.com/ubmit/poimandres-kitty -[colors] +[colors-dark] cursor=1b1e28 ffffff foreground=a6accd background=1b1e28 diff --git a/themes/rezza b/themes/rezza index 56814a77..62a08cc2 100644 --- a/themes/rezza +++ b/themes/rezza @@ -13,7 +13,7 @@ # and also posted here: # https://forums.debian.net/viewtopic.php?t=29981 -[colors] +[colors-dark] foreground = cccccc background = 191911 diff --git a/themes/rose-pine b/themes/rose-pine index 2cae00e8..b9aa7e2a 100644 --- a/themes/rose-pine +++ b/themes/rose-pine @@ -1,7 +1,7 @@ # -*- conf -*- # Rosé Pine -[colors] +[colors-dark] cursor=191724 e0def4 background=191724 foreground=e0def4 diff --git a/themes/rose-pine-dawn b/themes/rose-pine-dawn index 674c7a21..d2742c72 100644 --- a/themes/rose-pine-dawn +++ b/themes/rose-pine-dawn @@ -1,7 +1,11 @@ # -*- conf -*- # Rosé Pine Dawn -[colors] +[main] +initial-color-theme=light + + +[colors-light] cursor=faf4ed 575279 background=faf4ed foreground=575279 diff --git a/themes/rose-pine-moon b/themes/rose-pine-moon index cbc81451..51b9a33a 100644 --- a/themes/rose-pine-moon +++ b/themes/rose-pine-moon @@ -1,7 +1,7 @@ # -*- conf -*- # Rosé Pine Moon -[colors] +[colors-dark] cursor=232136 e0def4 background=232136 foreground=e0def4 diff --git a/themes/selenized b/themes/selenized index cde35723..83fea617 100644 --- a/themes/selenized +++ b/themes/selenized @@ -1,7 +1,7 @@ # -*- conf -*- # Selenized dark -[colors] +[colors-dark] cursor = 103c48 53d6c7 background= 103c48 foreground= adbcbc @@ -24,7 +24,7 @@ bright5= ff84cd bright6= 53d6c7 bright7= cad8d9 -[colors2] +[colors-light] cursor=fbf3db 00978a background= fbf3db foreground= 53676d diff --git a/themes/selenized-black b/themes/selenized-black index 591751f0..8a93187e 100644 --- a/themes/selenized-black +++ b/themes/selenized-black @@ -1,7 +1,7 @@ # -*- conf -*- # Selenized black -[colors] +[colors-dark] cursor = 181818 56d8c9 background= 181818 foreground= b9b9b9 diff --git a/themes/selenized-dark b/themes/selenized-dark index 5d062dec..8ace1c05 100644 --- a/themes/selenized-dark +++ b/themes/selenized-dark @@ -1,7 +1,7 @@ # -*- conf -*- # Selenized dark -[colors] +[colors-dark] cursor = 103c48 53d6c7 background= 103c48 foreground= adbcbc diff --git a/themes/selenized-light b/themes/selenized-light index 04dffbea..c842fc3c 100644 --- a/themes/selenized-light +++ b/themes/selenized-light @@ -1,7 +1,10 @@ # -*- conf -*- # Selenized light -[colors] +[main] +initial-color-theme=light + +[colors-light] cursor=fbf3db 00978a background= fbf3db foreground= 53676d diff --git a/themes/selenized-white b/themes/selenized-white index 5a7d68b2..659bf814 100644 --- a/themes/selenized-white +++ b/themes/selenized-white @@ -1,7 +1,10 @@ # -*- conf -*- # Selenized white -[colors] +[main] +initial-color-theme=light + +[colors-light] cursor=ffffff 009a8a background= ffffff foreground= 474747 diff --git a/themes/solarized b/themes/solarized index 335c738e..f1844b3c 100644 --- a/themes/solarized +++ b/themes/solarized @@ -2,7 +2,7 @@ # Solarized dark+light # Dark -[colors] +[colors-dark] cursor= 002b36 93a1a1 background= 002b36 foreground= 839496 @@ -25,7 +25,7 @@ bright7= fdf6e3 # Light -[colors2] +[colors-light] cursor= fdf6e3 586e75 background= fdf6e3 foreground= 657b83 diff --git a/themes/solarized-dark b/themes/solarized-dark index 4997eb4a..6335fa0f 100644 --- a/themes/solarized-dark +++ b/themes/solarized-dark @@ -1,7 +1,7 @@ # -*- conf -*- # Solarized dark -[colors] +[colors-dark] cursor= 002b36 93a1a1 background= 002b36 foreground= 839496 diff --git a/themes/solarized-dark-normal-brights b/themes/solarized-dark-normal-brights index f0c2172d..7b608110 100644 --- a/themes/solarized-dark-normal-brights +++ b/themes/solarized-dark-normal-brights @@ -1,7 +1,7 @@ # -*- conf -*- # Solarized dark -[colors] +[colors-dark] cursor= 002b36 93a1a1 background= 002b36 foreground= 839496 diff --git a/themes/solarized-light b/themes/solarized-light index 3d750277..db27be43 100644 --- a/themes/solarized-light +++ b/themes/solarized-light @@ -1,7 +1,10 @@ # -*- conf -*- # Solarized light -[colors] +[main] +initial-color-theme=light + +[colors-light] cursor= fdf6e3 586e75 background= fdf6e3 foreground= 657b83 diff --git a/themes/solarized-normal-brights b/themes/solarized-normal-brights index a7724cd3..3bd3c189 100644 --- a/themes/solarized-normal-brights +++ b/themes/solarized-normal-brights @@ -9,7 +9,7 @@ # encoding to sRGB again. # Dark -[colors] +[colors-dark] cursor= 002b36 93a1a1 background= 002b36 foreground= 839496 @@ -32,7 +32,7 @@ bright7= ffffff # Light -[colors2] +[colors-light] cursor= fdf6e3 586e75 background= fdf6e3 foreground= 657b83 diff --git a/themes/srcery b/themes/srcery index 54966707..612c82cc 100644 --- a/themes/srcery +++ b/themes/srcery @@ -1,6 +1,6 @@ # srcery -[colors] +[colors-dark] background= 1c1b19 foreground= fce8c3 regular0= 1c1b19 diff --git a/themes/starlight b/themes/starlight index ed39f277..81ce1a5f 100644 --- a/themes/starlight +++ b/themes/starlight @@ -1,7 +1,7 @@ # -*- conf -*- # Theme: starlight V4 (https://github.com/CosmicToast/starlight) -[colors] +[colors-dark] foreground = FFFFFF background = 242424 diff --git a/themes/tango b/themes/tango index a93d29cb..5ea43f63 100644 --- a/themes/tango +++ b/themes/tango @@ -1,7 +1,7 @@ # -*- conf -*- # Tango -[colors] +[colors-dark] cursor=000000 babdb6 foreground=babdb6 background=000000 diff --git a/themes/tempus-autumn b/themes/tempus-autumn index 74228e90..214478bb 100644 --- a/themes/tempus-autumn +++ b/themes/tempus-autumn @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by earthly colours (WCAG AA compliant) -[colors] +[colors-dark] #cursor = 302420 a9a2a6 foreground = a9a2a6 background = 302420 diff --git a/themes/tempus-classic b/themes/tempus-classic index b35dc5e5..95b37b76 100644 --- a/themes/tempus-classic +++ b/themes/tempus-classic @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with warm hues (WCAG AA compliant) -[colors] +[colors-dark] #cursor = 232323 aeadaf foreground = aeadaf background = 232323 diff --git a/themes/tempus-dawn b/themes/tempus-dawn index dc45f29d..c288544e 100644 --- a/themes/tempus-dawn +++ b/themes/tempus-dawn @@ -3,7 +3,11 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme with a soft, slightly desaturated palette (WCAG AA compliant) -[colors] +[main] +initial-color-theme=light + + +[colors-light] #cursor = eff0f2 4a4b4e foreground = 4a4b4e background = eff0f2 diff --git a/themes/tempus-day b/themes/tempus-day index 1df70137..03454f04 100644 --- a/themes/tempus-day +++ b/themes/tempus-day @@ -3,7 +3,10 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme with warm colours (WCAG AA compliant) -[colors] +[main] +initial-color-theme=light + +[colors-light] #cursor = f8f2e5 464340 foreground = 464340 background = f8f2e5 diff --git a/themes/tempus-dusk b/themes/tempus-dusk index 5b4d1bea..cd27aaaa 100644 --- a/themes/tempus-dusk +++ b/themes/tempus-dusk @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a deep blue-ish, slightly desaturated palette (WCAG AA compliant) -[colors] +[colors-dark] #cursor = 1f252d a2a8ba foreground = a2a8ba background = 1f252d diff --git a/themes/tempus-fugit b/themes/tempus-fugit index ebd082fe..b9dce351 100644 --- a/themes/tempus-fugit +++ b/themes/tempus-fugit @@ -3,7 +3,10 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light, pleasant theme optimised for long writing/coding sessions (WCAG AA compliant) -[colors] +[main] +initial-color-theme=light + +[colors-light] #cursor = fff5f3 4d595f foreground = 4d595f background = fff5f3 diff --git a/themes/tempus-future b/themes/tempus-future index c97d379d..1f8c3c79 100644 --- a/themes/tempus-future +++ b/themes/tempus-future @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with colours inspired by concept art of outer space (WCAG AAA compliant) -[colors] +[colors-dark] #cursor = 090a18 b4abac foreground = b4abac background = 090a18 diff --git a/themes/tempus-night b/themes/tempus-night index 7c97681d..aae80f02 100644 --- a/themes/tempus-night +++ b/themes/tempus-night @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: High contrast dark theme with bright colours (WCAG AAA compliant) -[colors] +[colors-dark] #cursor = 1a1a1a e0e0e0 foreground = e0e0e0 background = 1a1a1a diff --git a/themes/tempus-past b/themes/tempus-past index af408b00..5f90ddf1 100644 --- a/themes/tempus-past +++ b/themes/tempus-past @@ -3,7 +3,10 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme inspired by old vaporwave concept art (WCAG AA compliant) -[colors] +[main] +initial-color-theme=light + +[colors-light] #cursor = f3f2f4 53545b foreground = 53545b background = f3f2f4 diff --git a/themes/tempus-rift b/themes/tempus-rift index e0cea4da..8add657a 100644 --- a/themes/tempus-rift +++ b/themes/tempus-rift @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a subdued palette on the green side of the spectrum (WCAG AA compliant) -[colors] +[colors-dark] #cursor = 162c22 bbbcbc foreground = bbbcbc background = 162c22 diff --git a/themes/tempus-spring b/themes/tempus-spring index b98be3b4..eb15a1be 100644 --- a/themes/tempus-spring +++ b/themes/tempus-spring @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by early spring colours (WCAG AA compliant) -[colors] +[colors-dark] #cursor = 283a37 b5b8b7 foreground = b5b8b7 background = 283a37 diff --git a/themes/tempus-summer b/themes/tempus-summer index cd904010..74c8faa2 100644 --- a/themes/tempus-summer +++ b/themes/tempus-summer @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with colours inspired by summer evenings by the sea (WCAG AA compliant) -[colors] +[colors-dark] #cursor = 202c3d a0abae foreground = a0abae background = 202c3d diff --git a/themes/tempus-tempest b/themes/tempus-tempest index 2c84454e..f1cf55bf 100644 --- a/themes/tempus-tempest +++ b/themes/tempus-tempest @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: A green-scale, subtle theme for late night hackers (WCAG AAA compliant) -[colors] +[colors-dark] #cursor = 282b2b b6e0ca foreground = b6e0ca background = 282b2b diff --git a/themes/tempus-totus b/themes/tempus-totus index 3eb21644..fae6ede3 100644 --- a/themes/tempus-totus +++ b/themes/tempus-totus @@ -3,7 +3,10 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Light theme for prose or for coding in an open space (WCAG AAA compliant) -[colors] +[main] +initial-color-theme=light + +[colors-light] #cursor = ffffff 4a484d foreground = 4a484d background = ffffff diff --git a/themes/tempus-warp b/themes/tempus-warp index 911fb266..906b3f37 100644 --- a/themes/tempus-warp +++ b/themes/tempus-warp @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a vibrant palette (WCAG AA compliant) -[colors] +[colors-dark] #cursor = 001514 a29fa0 foreground = a29fa0 background = 001514 diff --git a/themes/tempus-winter b/themes/tempus-winter index e4307142..dc95128b 100644 --- a/themes/tempus-winter +++ b/themes/tempus-winter @@ -3,7 +3,7 @@ # author: Protesilaos Stavrou (https://protesilaos.com) # description: Dark theme with a palette inspired by winter nights at the city (WCAG AA compliant) -[colors] +[colors-dark] #cursor = 202427 8da3b8 foreground = 8da3b8 background = 202427 diff --git a/themes/tokyonight-light b/themes/tokyonight-light index ffcae689..359a31b9 100644 --- a/themes/tokyonight-light +++ b/themes/tokyonight-light @@ -2,7 +2,10 @@ # Reference: https://github.com/tokyo-night/tokyo-night-vscode-theme/blob/master/themes/tokyo-night-light-color-theme.json -[colors] +[main] +initial-color-theme=light + +[colors-light] background=d6d8df foreground=343b58 regular0=343b58 diff --git a/themes/tokyonight-night b/themes/tokyonight-night index f789e1bd..58037f72 100644 --- a/themes/tokyonight-night +++ b/themes/tokyonight-night @@ -1,6 +1,6 @@ # -*- conf -*- -[colors] +[colors-dark] background=1a1b26 foreground=c0caf5 regular0=15161E diff --git a/themes/tokyonight-storm b/themes/tokyonight-storm index 074b4697..4dbbf6c6 100644 --- a/themes/tokyonight-storm +++ b/themes/tokyonight-storm @@ -1,6 +1,6 @@ # -*- conf -*- -[colors] +[colors-dark] background=24283b foreground=c0caf5 regular0=1D202F diff --git a/themes/visibone b/themes/visibone index 9979bee0..b989b36b 100644 --- a/themes/visibone +++ b/themes/visibone @@ -1,7 +1,7 @@ # -*- conf -*- # VisiBone -[colors] +[colors-dark] cursor=010101 ffffff foreground=ffffff background=010101 diff --git a/themes/xterm b/themes/xterm index bf17f5e7..a9382fd8 100644 --- a/themes/xterm +++ b/themes/xterm @@ -1,7 +1,7 @@ # -*- conf -*- # The default palette of xterm. -[colors] +[colors-dark] foreground=e5e5e5 background=000000 regular0=000000 # black diff --git a/themes/zenburn b/themes/zenburn index bace080c..37a26812 100644 --- a/themes/zenburn +++ b/themes/zenburn @@ -1,6 +1,6 @@ # -*- conf -*- -[colors] +[colors-dark] foreground=dcdccc background=111111 From 1caba0d993c7bac68b555efb5d6dfab2577fcd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 20 Dec 2025 15:56:32 +0100 Subject: [PATCH 314/353] config: remove deprecated config option cursor.color This option was deprecated in 1.23.0. Use colors-{dark,light}.cursor instead. --- CHANGELOG.md | 5 +++++ config.c | 22 ---------------------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae9feb54..3a73893d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,11 @@ ### Removed + +* `cursor.color` config option (deprecated in 1.23.0). Use + `colors-{dark,light}.cursor` instead. + + ### Fixed * Search mode: composing keys not ignored. diff --git a/config.c b/config.c index 0340d418..14e836c1 100644 --- a/config.c +++ b/config.c @@ -1661,28 +1661,6 @@ parse_section_cursor(struct context *ctx) else if (streq(key, "blink-rate")) return value_to_uint32(ctx, 10, &conf->cursor.blink.rate_ms); - else if (streq(key, "color")) { - LOG_WARN("%s:%d: cursor.color: deprecated; use colors.cursor instead", - ctx->path, ctx->lineno); - - user_notification_add( - &conf->notifications, - USER_NOTIFICATION_DEPRECATED, - xstrdup("cursor.color: use colors.cursor instead")); - - if (!value_to_two_colors( - ctx, - &conf->colors_dark.cursor.text, - &conf->colors_dark.cursor.cursor, - false)) - { - return false; - } - - conf->colors_dark.use_custom.cursor = true; - return true; - } - else if (streq(key, "beam-thickness")) return value_to_pt_or_px(ctx, &conf->cursor.beam_thickness); From aa26676c43edf2cfcc3543a443a25e881e34473e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 12 Nov 2025 10:22:17 +0100 Subject: [PATCH 315/353] builtin terminfo: add custom 'query-os-name' Inspired by Kitty's 'kitty-query-os_name'. Notable changes: * Drop kitty prefix * os_name -> os-name * Use "uname -s" without any transformations (e.g. no lower-casing) $ ./utils/xtgettcap query-os-name reply: (44 chars): P1+r71756572792d6f732d6e616d65=4C696E7578\ query-os-name=Linux Closes #2209 --- CHANGELOG.md | 3 +++ README.md | 4 ++++ scripts/generate-builtin-terminfo.py | 2 ++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a73893d..6fa7439e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,8 +76,11 @@ Wayland protocol ([#2212][2212]). * `[colors-dark]` section to `foot.ini`. Replaces `[colors]`. * `[colors-light]` section to `foot.ini`. Replaces `[colors2]`. +* `XTGETTCAP`: added `query-os-name`, returning the OS foot is + compiled for (e.g. _'Linux'_) ([#2209][2209]). [2212]: https://codeberg.org/dnkl/foot/issues/2212 +[2209]: https://codeberg.org/dnkl/foot/issues/2209 ### Changed diff --git a/README.md b/README.md index e8f3c8cd..7ee771ba 100644 --- a/README.md +++ b/README.md @@ -641,6 +641,10 @@ All replies are in `tigetstr()` format. That is, given the same capability name, foot's reply is identical to what `tigetstr()` would have returned. +In addition to queries for terminfo entries, the `query-os-name` query +will be answered with a response of the form `uname=$(uname -s)`, +where `$(uname -s)` is the name of the OS foot was compiled for. + # Credits diff --git a/scripts/generate-builtin-terminfo.py b/scripts/generate-builtin-terminfo.py index 28b31b57..6a6ba68c 100755 --- a/scripts/generate-builtin-terminfo.py +++ b/scripts/generate-builtin-terminfo.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import argparse +import os import re import sys @@ -185,6 +186,7 @@ def main(): entry.add_capability(StringCapability('TN', target_entry_name)) entry.add_capability(StringCapability('name', target_entry_name)) entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel + entry.add_capability(StringCapability('query-os-name', os.uname().sysname)) terminfo_parts = [] for cap in sorted(entry.caps.values()): From 4cb17f5ae65e129765794986d36fbfc33c0c65bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 24 Dec 2025 11:33:28 +0100 Subject: [PATCH 316/353] csi: make sure the ASCII printer function is updated on plain underlines Otherwise, a sequence like \E[4:2;4m # Enable double-underline, then immediately switch to single Will switch to the slow printer, and then get stuck there even though we immediately switch to plain underlines (which don't need the slow printer). --- csi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/csi.c b/csi.c index 7e0cf464..0cf8e203 100644 --- a/csi.c +++ b/csi.c @@ -117,9 +117,9 @@ csi_sgr(struct terminal *term) style > UNDERLINE_SINGLE; break; } - - term_update_ascii_printer(term); - } + } else + term->bits_affecting_ascii_printer.underline_style = false; + term_update_ascii_printer(term); break; } case 5: term->vt.attrs.blink = true; break; From ca278398b101bc6c44ad6df0c48e068660ac5323 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 12:33:03 +0100 Subject: [PATCH 317/353] pyproject.toml: add initial mypy configuration --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..52106805 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[tool.mypy] +files = '$MYPY_CONFIG_FILE_DIR/scripts' +strict = true From cb1e152d995d2430c73bf322ced5e23f40fcb73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 13:12:43 +0100 Subject: [PATCH 318/353] pyproject.toml: add initial pyright configuration --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 52106805..d561b976 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,6 @@ +[tool.pyright] +strict = ['scripts'] + [tool.mypy] files = '$MYPY_CONFIG_FILE_DIR/scripts' strict = true From bbebe0f330fc3606aeb1896af70175d2fa81716d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 13:13:01 +0100 Subject: [PATCH 319/353] scripts: mypy fixes --- scripts/benchmark.py | 8 +- scripts/generate-alt-random-writes.py | 23 +++-- scripts/generate-builtin-terminfo.py | 85 +++++++++++-------- scripts/generate-emoji-variation-sequences.py | 7 +- scripts/srgb.py | 5 +- 5 files changed, 77 insertions(+), 51 deletions(-) diff --git a/scripts/benchmark.py b/scripts/benchmark.py index 5483dac1..fe820d9b 100755 --- a/scripts/benchmark.py +++ b/scripts/benchmark.py @@ -11,7 +11,7 @@ import termios from datetime import datetime -def main(): +def main() -> None: parser = argparse.ArgumentParser() parser.add_argument('files', type=argparse.FileType('rb'), nargs='+') parser.add_argument('--iterations', type=int, default=20) @@ -24,12 +24,12 @@ def main(): termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))) - times = {name: [] for name in [f.name for f in args.files]} + times: dict[str, list[float]] = {name: [] for name in [f.name for f in args.files]} for f in args.files: bench_bytes = f.read() - for i in range(args.iterations): + for _ in range(args.iterations): start = datetime.now() sys.stdout.buffer.write(bench_bytes) stop = datetime.now() @@ -48,4 +48,4 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + main() diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 656a2b9d..7ad1460c 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -8,6 +8,8 @@ import struct import sys import termios +from typing import Any + class ColorVariant(enum.IntEnum): NONE = enum.auto() @@ -17,7 +19,7 @@ class ColorVariant(enum.IntEnum): RGB = enum.auto() -def main(): +def main() -> None: parser = argparse.ArgumentParser() parser.add_argument( 'out', type=argparse.FileType(mode='w'), nargs='?', help='name of output file') @@ -38,10 +40,16 @@ def main(): opts = parser.parse_args() out = opts.out if opts.out is not None else sys.stdout + lines: int | None = None + cols: int | None = None + width: int | None = None + height: int | None = None + if opts.rows is None or opts.cols is None: try: - def dummy(*args): + def dummy(*args: Any) -> None: """Need a handler installed for sigwait() to trigger.""" + _ = args pass signal.signal(signal.SIGWINCH, dummy) @@ -53,6 +61,9 @@ def main(): termios.TIOCGWINSZ, struct.pack('HHHH', 0, 0, 0, 0))) + assert width is not None + assert height is not None + if width > 0 and height > 0: break @@ -71,9 +82,11 @@ def main(): if opts.rows is not None: lines = opts.rows + assert lines is not None height = 15 * lines # PGO helper binary hardcodes cell height to 15px if opts.cols is not None: cols = opts.cols + assert cols is not None width = 8 * cols # PGO help binary hardcodes cell width to 8px if lines is None or cols is None or height is None or width is None: @@ -190,8 +203,8 @@ def main(): # The sixel 'alphabet' sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' - last_pos = None - last_size = None + last_pos: tuple[int, int] | None = None + last_size: tuple[int, int] = 0, 0 for _ in range(20): if last_pos is not None and random.randrange(2): @@ -254,4 +267,4 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + main() diff --git a/scripts/generate-builtin-terminfo.py b/scripts/generate-builtin-terminfo.py index 6a6ba68c..515153a2 100755 --- a/scripts/generate-builtin-terminfo.py +++ b/scripts/generate-builtin-terminfo.py @@ -3,13 +3,10 @@ import argparse import os import re -import sys - -from typing import Dict, Union class Capability: - def __init__(self, name: str, value: Union[bool, int, str]): + def __init__(self, name: str, value: bool | int | str) -> None: self._name = name self._value = value @@ -18,30 +15,42 @@ class Capability: return self._name @property - def value(self) -> Union[bool, int, str]: + def value(self) -> bool | int | str: return self._value - def __lt__(self, other): + def __lt__(self, other: object) -> bool: + if not isinstance(other, Capability): + return NotImplemented return self._name < other._name - def __le__(self, other): + def __le__(self, other: object) -> bool: + if not isinstance(other, Capability): + return NotImplemented return self._name <= other._name - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, Capability): + return NotImplemented return self._name == other._name - def __ne__(self, other): - return self._name != other._name + def __ne__(self, other: object) -> bool: + if not isinstance(other, Capability): + return NotImplemented + return bool(self._name != other._name) - def __gt__(self, other): - return self._name > other._name + def __gt__(self, other: object) -> bool: + if not isinstance(other, Capability): + return NotImplemented + return bool(self._name > other._name) - def __ge__(self, other): + def __ge__(self, other: object) -> bool: + if not isinstance(other, Capability): + return NotImplemented return self._name >= other._name class BoolCapability(Capability): - def __init__(self, name: str): + def __init__(self, name: str) -> None: super().__init__(name, True) @@ -50,11 +59,11 @@ class IntCapability(Capability): class StringCapability(Capability): - def __init__(self, name: str, value: str): + def __init__(self, name: str, value: str) -> None: # see terminfo(5) for valid escape sequences # Control characters - def translate_ctrl_chr(m): + def translate_ctrl_chr(m: re.Match[str]) -> str: ctrl = m.group(1) if ctrl == '?': return '\\x7f' @@ -83,10 +92,10 @@ class StringCapability(Capability): class Fragment: - def __init__(self, name: str, description: str): + def __init__(self, name: str, description: str) -> None: self._name = name self._description = description - self._caps = {} + self._caps = dict[str, Capability]() @property def name(self) -> str: @@ -97,18 +106,18 @@ class Fragment: return self._description @property - def caps(self) -> Dict[str, Capability]: + def caps(self) -> dict[str, Capability]: return self._caps - def add_capability(self, cap: Capability): + def add_capability(self, cap: Capability) -> None: assert cap.name not in self._caps self._caps[cap.name] = cap - def del_capability(self, name: str): + def del_capability(self, name: str) -> None: del self._caps[name] -def main(): +def main() -> None: parser = argparse.ArgumentParser() parser.add_argument('source_entry_name') parser.add_argument('source', type=argparse.FileType('r')) @@ -121,15 +130,15 @@ def main(): source = opts.source target = opts.target - lines = [] - for l in source.readlines(): - l = l.strip() - if l.startswith('#'): + lines = list[str]() + for line in source.readlines(): + line = line.strip() + if line.startswith('#'): continue - lines.append(l) + lines.append(line) - fragments = {} - cur_fragment = None + fragments = dict[str, Fragment]() + cur_fragment: Fragment | None = None for m in re.finditer( r'(?P(?P[-+\w@]+)\|(?P.+?),)|' @@ -148,17 +157,20 @@ def main(): elif m.group('bool_cap') is not None: name = m.group('bool_name') + assert cur_fragment is not None cur_fragment.add_capability(BoolCapability(name)) elif m.group('int_cap') is not None: name = m.group('int_name') - value = int(m.group('int_val'), 0) - cur_fragment.add_capability(IntCapability(name, value)) + int_value = int(m.group('int_val'), 0) + assert cur_fragment is not None + cur_fragment.add_capability(IntCapability(name, int_value)) elif m.group('str_cap') is not None: name = m.group('str_name') - value = m.group('str_val') - cur_fragment.add_capability(StringCapability(name, value)) + str_value = m.group('str_val') + assert cur_fragment is not None + cur_fragment.add_capability(StringCapability(name, str_value)) else: assert False @@ -167,6 +179,9 @@ def main(): for frag in fragments.values(): for cap in frag.caps.values(): if cap.name == 'use': + assert isinstance(cap, StringCapability) + assert isinstance(cap.value, str) + use_frag = fragments[cap.value] for use_cap in use_frag.caps.values(): frag.add_capability(use_cap) @@ -188,7 +203,7 @@ def main(): entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel entry.add_capability(StringCapability('query-os-name', os.uname().sysname)) - terminfo_parts = [] + terminfo_parts = list[str]() for cap in sorted(entry.caps.values()): name = cap.name value = str(cap.value) @@ -212,4 +227,4 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + main() diff --git a/scripts/generate-emoji-variation-sequences.py b/scripts/generate-emoji-variation-sequences.py index e05b6290..17172d20 100644 --- a/scripts/generate-emoji-variation-sequences.py +++ b/scripts/generate-emoji-variation-sequences.py @@ -1,11 +1,10 @@ #!/usr/bin/env python3 import argparse -import sys class Codepoint: - def __init__(self, start: int, end: None|int = None): + def __init__(self, start: int, end: None | int = None) -> None: self.start = start self.end = start if end is None else end self.vs15 = False @@ -15,7 +14,7 @@ class Codepoint: return f'{self.start:x}-{self.end:x}, vs15={self.vs15}, vs16={self.vs16}' -def main(): +def main() -> None: parser = argparse.ArgumentParser() parser.add_argument('input', type=argparse.FileType('r')) parser.add_argument('output', type=argparse.FileType('w')) @@ -100,4 +99,4 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + main() diff --git a/scripts/srgb.py b/scripts/srgb.py index 12056956..a6aa0f4a 100755 --- a/scripts/srgb.py +++ b/scripts/srgb.py @@ -2,7 +2,6 @@ import argparse import math -import sys # Note: we use a pure gamma 2.2 function, rather than the piece-wise @@ -17,7 +16,7 @@ def linear_to_srgb(f: float) -> float: return math.pow(f, 1 / 2.2) -def main(): +def main() -> None: parser = argparse.ArgumentParser() parser.add_argument('c_output', type=argparse.FileType('w')) parser.add_argument('h_output', type=argparse.FileType('w')) @@ -68,4 +67,4 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + main() From 6ab2e2d9ebc247b4c4112d12a16ebc2b7b0f7c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 13:15:01 +0100 Subject: [PATCH 320/353] ci: run mypy + ruff check --- .woodpecker.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.woodpecker.yaml b/.woodpecker.yaml index 843c9afc..ef96efcb 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -17,6 +17,24 @@ steps: - codespell -Lser,doas,zar,rin README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd - deactivate + - name: mypy + when: + - event: [manual, pull_request] + - event: [push, tag] + branch: [master, releases/*] + image: alpine:edge + commands: + - apk add openssl + - apk add python3 + - apk add py3-pip + - python3 -m venv mypy-venv + - source mypy-venv/bin/activate + - pip install mypy + - pip install ruff + - mypy + - ruff check + - deactivate + - name: subprojects when: - event: [manual, pull_request] From ee682abac876cf558627e39415ebc92d2431c964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 14:13:14 +0100 Subject: [PATCH 321/353] mypy: no need to declare None as return type for __init__ --- scripts/generate-builtin-terminfo.py | 8 ++++---- scripts/generate-emoji-variation-sequences.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/generate-builtin-terminfo.py b/scripts/generate-builtin-terminfo.py index 515153a2..c10373d3 100755 --- a/scripts/generate-builtin-terminfo.py +++ b/scripts/generate-builtin-terminfo.py @@ -6,7 +6,7 @@ import re class Capability: - def __init__(self, name: str, value: bool | int | str) -> None: + def __init__(self, name: str, value: bool | int | str): self._name = name self._value = value @@ -50,7 +50,7 @@ class Capability: class BoolCapability(Capability): - def __init__(self, name: str) -> None: + def __init__(self, name: str): super().__init__(name, True) @@ -59,7 +59,7 @@ class IntCapability(Capability): class StringCapability(Capability): - def __init__(self, name: str, value: str) -> None: + def __init__(self, name: str, value: str): # see terminfo(5) for valid escape sequences # Control characters @@ -92,7 +92,7 @@ class StringCapability(Capability): class Fragment: - def __init__(self, name: str, description: str) -> None: + def __init__(self, name: str, description: str): self._name = name self._description = description self._caps = dict[str, Capability]() diff --git a/scripts/generate-emoji-variation-sequences.py b/scripts/generate-emoji-variation-sequences.py index 17172d20..57e881c7 100644 --- a/scripts/generate-emoji-variation-sequences.py +++ b/scripts/generate-emoji-variation-sequences.py @@ -4,7 +4,7 @@ import argparse class Codepoint: - def __init__(self, start: int, end: None | int = None) -> None: + def __init__(self, start: int, end: None | int = None): self.start = start self.end = start if end is None else end self.vs15 = False From b3cb180e443ce108b3f051d5eccd80474a058ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 14:42:51 +0100 Subject: [PATCH 322/353] codespell: use pyproject.toml to define options and exceptions --- .woodpecker.yaml | 2 +- CODE_OF_CONDUCT.md | 2 +- pyproject.toml | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.woodpecker.yaml b/.woodpecker.yaml index ef96efcb..35d52d67 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -14,7 +14,7 @@ steps: - python3 -m venv codespell-venv - source codespell-venv/bin/activate - pip install codespell - - codespell -Lser,doas,zar,rin README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd + - codespell - deactivate - name: mypy diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 4b652df6..26ab32a5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -53,7 +53,7 @@ decisions when appropriate. 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 +that people are fallible and that it is possible to truly 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. diff --git a/pyproject.toml b/pyproject.toml index d561b976..654e632d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,3 +4,7 @@ strict = ['scripts'] [tool.mypy] files = '$MYPY_CONFIG_FILE_DIR/scripts' strict = true + +[tool.codespell] +skip = 'pyproject.toml,./subprojects,./pkg,./src,./bld,foot.info,./unicode,.*-venv' +ignore-regex = 'terminfo capability `rin`|\* Simon Ser|\* \[zar\]\(https://codeberg.org/zar\)|iterm theme|iterm.toml|iterm/OneHalfDark.itermcolors' \ No newline at end of file From 41679e64a84977225e193f4eb734ab0a0eab1e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 15:00:18 +0100 Subject: [PATCH 323/353] box-drawing: fenv.h: remove, not needed anymore --- box-drawing.c | 1 - 1 file changed, 1 deletion(-) diff --git a/box-drawing.c b/box-drawing.c index 421ff54d..e69d9648 100644 --- a/box-drawing.c +++ b/box-drawing.c @@ -2,7 +2,6 @@ #include #include -#include #include #define LOG_MODULE "box-drawing" From bb6968c2847e99ba89a7597ff5b8a31fb5dd9434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 17:23:46 +0100 Subject: [PATCH 324/353] ci: combine the codespell and mypy stages They both need python and a venv, so let's combine them, to avoid having to install the same things twice. --- .woodpecker.yaml | 22 ++++------------------ pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/.woodpecker.yaml b/.woodpecker.yaml index 35d52d67..5b4fa587 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -1,7 +1,7 @@ # -*- yaml -*- steps: - - name: codespell + - name: pychecks when: - event: [manual, pull_request] - event: [push, tag] @@ -11,26 +11,12 @@ steps: - apk add openssl - apk add python3 - apk add py3-pip - - python3 -m venv codespell-venv - - source codespell-venv/bin/activate + - python3 -m venv venv + - source venv/bin/activate - pip install codespell - - codespell - - deactivate - - - name: mypy - when: - - event: [manual, pull_request] - - event: [push, tag] - branch: [master, releases/*] - image: alpine:edge - commands: - - apk add openssl - - apk add python3 - - apk add py3-pip - - python3 -m venv mypy-venv - - source mypy-venv/bin/activate - pip install mypy - pip install ruff + - codespell - mypy - ruff check - deactivate diff --git a/pyproject.toml b/pyproject.toml index 654e632d..f5fc08a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,5 +6,5 @@ files = '$MYPY_CONFIG_FILE_DIR/scripts' strict = true [tool.codespell] -skip = 'pyproject.toml,./subprojects,./pkg,./src,./bld,foot.info,./unicode,.*-venv' +skip = 'pyproject.toml,./subprojects,./pkg,./src,./bld,foot.info,./unicode,./venv' ignore-regex = 'terminfo capability `rin`|\* Simon Ser|\* \[zar\]\(https://codeberg.org/zar\)|iterm theme|iterm.toml|iterm/OneHalfDark.itermcolors' \ No newline at end of file From 53e8fbbdec5779d59341b37e0b630b06ecf6951a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 26 Dec 2025 17:25:28 +0100 Subject: [PATCH 325/353] ci: python: upgrade pip before installing python packages --- .woodpecker.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.woodpecker.yaml b/.woodpecker.yaml index 5b4fa587..900251a7 100644 --- a/.woodpecker.yaml +++ b/.woodpecker.yaml @@ -13,6 +13,7 @@ steps: - apk add py3-pip - python3 -m venv venv - source venv/bin/activate + - python -m pip install --upgrade pip - pip install codespell - pip install mypy - pip install ruff From 42e04c5c8741f739295f7da0b1bb1effda95547f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 28 Dec 2025 11:37:54 +0100 Subject: [PATCH 326/353] csi: secondary DA: fix comment; we don't use an XTerm version number --- csi.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/csi.c b/csi.c index 0cf8e203..87af215e 100644 --- a/csi.c +++ b/csi.c @@ -1644,10 +1644,10 @@ csi_dispatch(struct terminal *term, uint8_t final) * 64 - vt520 * 65 - vt525 * - * Param 2 - firmware version - * xterm uses its version number. We use an xterm - * version number too, since e.g. Emacs uses this to - * determine level of support. + * Param 2 - firmware version xterm uses its version + * number. We do to, in the format "MAJORMINORPATCH", + * where all three version numbers are always two + * digits. So e.g. 1.25.0 is reported as 012500. * * We report ourselves as a VT220. This must be * synchronized with the primary DA response. From b78cc92322dd2ae431dbbb70bc681b4d603d844d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 4 Jan 2026 07:57:25 +0100 Subject: [PATCH 327/353] shm: don't bother with xrgb surfaces, always use argb Before this patch, foot used xrgb surfaces for all fully opaque surfaces, and only used argb surfaces for the main window when the user enabled translucency. However, several compositors have damage-like issues when we switch between opaque and non-opaque surfaces (for example, when switching color theme, or when toggling fullscreen). Since the performance benefit of using non-alpha aware surfaces are likely minor (if there's any measurable performance difference at all!), lets workaround these compositor issues by always using argb surfaces. --- CHANGELOG.md | 5 ++++ render.c | 17 +++++------ shm.c | 84 ++++++++++++++++------------------------------------ shm.h | 5 ++-- sixel.c | 4 +-- wayland.c | 3 -- wayland.h | 3 -- 7 files changed, 42 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa7439e..0462666c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,6 +92,11 @@ contains at least one upper case character. * Mouse tracking in SGR pixel mode no longer emits negative column/row pixel values ([#2226][2226]). +* Foot now always uses ARGB SHM surfaces. In earlier versions, XRGB + surfaces were used for opaque surfaces. Unfortunately, several + compositors had issues when foot switched between ARGB and XRGB + surfaces (for example when switching color theme, or toggling + fullscreen). [2202]: https://codeberg.org/dnkl/foot/issues/2202 [2226]: https://codeberg.org/dnkl/foot/issues/2226 diff --git a/render.c b/render.c index ac8ece37..86244434 100644 --- a/render.c +++ b/render.c @@ -2012,7 +2012,7 @@ render_overlay(struct terminal *term) } struct buffer *buf = shm_get_buffer( - term->render.chains.overlay, term->width, term->height, true); + term->render.chains.overlay, term->width, term->height); pixman_image_set_clip_region32(buf->pix[0], NULL); /* Bounding rectangle of damaged areas - for wl_surface_damage_buffer() */ @@ -2970,7 +2970,7 @@ render_csd(struct terminal *term) } struct buffer *bufs[CSD_SURF_COUNT]; - shm_get_many(term->render.chains.csd, CSD_SURF_COUNT, widths, heights, bufs, true); + shm_get_many(term->render.chains.csd, CSD_SURF_COUNT, widths, heights, bufs); for (size_t i = CSD_SURF_LEFT; i <= CSD_SURF_BOTTOM; i++) render_csd_border(term, i, &infos[i], bufs[i]); @@ -3110,7 +3110,7 @@ render_scrollback_position(struct terminal *term) } struct buffer_chain *chain = term->render.chains.scrollback_indicator; - struct buffer *buf = shm_get_buffer(chain, width, height, false); + struct buffer *buf = shm_get_buffer(chain, width, height); wl_subsurface_set_position( win->scrollback_indicator.sub, roundf(x / scale), roundf(y / scale)); @@ -3153,7 +3153,7 @@ render_render_timer(struct terminal *term, struct timespec render_time) height = roundf(scale * ceilf(height / scale)); struct buffer_chain *chain = term->render.chains.render_timer; - struct buffer *buf = shm_get_buffer(chain, width, height, false); + struct buffer *buf = shm_get_buffer(chain, width, height); wl_subsurface_set_position( win->render_timer.sub, @@ -3336,10 +3336,7 @@ grid_render(struct terminal *term) xassert(term->height > 0); struct buffer_chain *chain = term->render.chains.grid; - bool use_alpha = !term->window->is_fullscreen && - term->colors.alpha != 0xffff; - struct buffer *buf = shm_get_buffer( - chain, term->width, term->height, use_alpha); + struct buffer *buf = shm_get_buffer(chain, term->width, term->height); /* Dirty old and current cursor cell, to ensure they're repainted */ dirty_old_cursor(term); @@ -3787,7 +3784,7 @@ render_search_box(struct terminal *term) size_t glyph_offset = term->render.search_glyph_offset; struct buffer_chain *chain = term->render.chains.search; - struct buffer *buf = shm_get_buffer(chain, width, height, true); + struct buffer *buf = shm_get_buffer(chain, width, height); pixman_region32_t clip; pixman_region32_init_rect(&clip, 0, 0, width, height); @@ -4252,7 +4249,7 @@ render_urls(struct terminal *term) struct buffer_chain *chain = term->render.chains.url; struct buffer *bufs[render_count]; - shm_get_many(chain, render_count, widths, heights, bufs, false); + shm_get_many(chain, render_count, widths, heights, bufs); uint32_t fg = term->conf->colors_dark.use_custom.jump_label ? term->conf->colors_dark.jump_label.fg diff --git a/shm.c b/shm.c index f488d6b6..5c1573ad 100644 --- a/shm.c +++ b/shm.c @@ -84,7 +84,6 @@ struct buffer_private { struct buffer_pool *pool; off_t offset; /* Offset into memfd where data begins */ size_t size; - bool with_alpha; bool scrollable; @@ -98,11 +97,8 @@ struct buffer_chain { size_t pix_instances; bool scrollable; - pixman_format_code_t pixman_fmt_without_alpha; - enum wl_shm_format shm_format_without_alpha; - - pixman_format_code_t pixman_fmt_with_alpha; - enum wl_shm_format shm_format_with_alpha; + pixman_format_code_t pixman_fmt; + enum wl_shm_format shm_format; void (*release_cb)(struct buffer *buf, void *data); void *cb_data; @@ -285,9 +281,7 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset) wl_buf = wl_shm_pool_create_buffer( pool->wl_pool, new_offset, buf->public.width, buf->public.height, buf->public.stride, - buf->with_alpha - ? buf->chain->shm_format_with_alpha - : buf->chain->shm_format_without_alpha); + buf->chain->shm_format); if (wl_buf == NULL) { LOG_ERR("failed to create SHM buffer"); @@ -297,9 +291,7 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset) /* One pixman image for each worker thread (do we really need multiple?) */ for (size_t i = 0; i < buf->public.pix_instances; i++) { pix[i] = pixman_image_create_bits_no_clear( - buf->with_alpha - ? buf->chain->pixman_fmt_with_alpha - : buf->chain->pixman_fmt_without_alpha, + buf->chain->pixman_fmt, buf->public.width, buf->public.height, (uint32_t *)mmapped, buf->public.stride); @@ -334,8 +326,7 @@ err: static void NOINLINE get_new_buffers(struct buffer_chain *chain, size_t count, int widths[static count], int heights[static count], - struct buffer *bufs[static count], bool with_alpha, - bool immediate_purge) + struct buffer *bufs[static count], bool immediate_purge) { xassert(count == 1 || !chain->scrollable); /* @@ -354,10 +345,7 @@ get_new_buffers(struct buffer_chain *chain, size_t count, size_t total_size = 0; for (size_t i = 0; i < count; i++) { stride[i] = stride_for_format_and_width( - with_alpha - ? chain->pixman_fmt_with_alpha - : chain->pixman_fmt_without_alpha, - widths[i]); + chain->pixman_fmt, widths[i]); if (min_stride_alignment > 0) { const size_t m = min_stride_alignment; @@ -521,7 +509,6 @@ get_new_buffers(struct buffer_chain *chain, size_t count, .chain = chain, .ref_count = immediate_purge ? 0 : 1, .busy = true, - .with_alpha = with_alpha, .pool = pool, .offset = 0, .size = sizes[i], @@ -593,13 +580,13 @@ shm_did_not_use_buf(struct buffer *_buf) void shm_get_many(struct buffer_chain *chain, size_t count, int widths[static count], int heights[static count], - struct buffer *bufs[static count], bool with_alpha) + struct buffer *bufs[static count]) { - get_new_buffers(chain, count, widths, heights, bufs, with_alpha, true); + get_new_buffers(chain, count, widths, heights, bufs, true); } struct buffer * -shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alpha) +shm_get_buffer(struct buffer_chain *chain, int width, int height) { LOG_DBG( "chain=%p: looking for a reusable %dx%d buffer " @@ -610,9 +597,7 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph tll_foreach(chain->bufs, it) { struct buffer_private *buf = it->item; - if (buf->public.width != width || buf->public.height != height || - with_alpha != buf->with_alpha) - { + if (buf->public.width != width || buf->public.height != height) { LOG_DBG("purging mismatching buffer %p", (void *)buf); if (buffer_unref_no_remove_from_chain(buf)) tll_remove(chain->bufs, it); @@ -663,7 +648,7 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height, bool with_alph } struct buffer *ret; - get_new_buffers(chain, 1, &width, &height, &ret, with_alpha, false); + get_new_buffers(chain, 1, &width, &height, &ret, false); return ret; } @@ -1009,11 +994,8 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, enum shm_bit_depth desired_bit_depth, void (*release_cb)(struct buffer *buf, void *data), void *cb_data) { - pixman_format_code_t pixman_fmt_without_alpha = PIXMAN_x8r8g8b8; - enum wl_shm_format shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB8888; - - pixman_format_code_t pixman_fmt_with_alpha = PIXMAN_a8r8g8b8; - enum wl_shm_format shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB8888; + pixman_format_code_t pixman_fmt = PIXMAN_a8r8g8b8; + enum wl_shm_format shm_fmt = WL_SHM_FORMAT_ARGB8888; static bool have_logged = false; static bool have_logged_10_fallback = false; @@ -1022,12 +1004,9 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, static bool have_logged_16_fallback = false; if (desired_bit_depth == SHM_BITS_16) { - if (wayl->shm_have_abgr161616 && wayl->shm_have_xbgr161616) { - pixman_fmt_without_alpha = PIXMAN_a16b16g16r16; - shm_fmt_without_alpha = WL_SHM_FORMAT_XBGR16161616; - - pixman_fmt_with_alpha = PIXMAN_a16b16g16r16; - shm_fmt_with_alpha = WL_SHM_FORMAT_ABGR16161616; + if (wayl->shm_have_abgr161616) { + pixman_fmt = PIXMAN_a16b16g16r16; + shm_fmt = WL_SHM_FORMAT_ABGR16161616; if (!have_logged) { have_logged = true; @@ -1045,15 +1024,10 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, } #endif - if (desired_bit_depth >= SHM_BITS_10 && - pixman_fmt_with_alpha == PIXMAN_a8r8g8b8) - { - if (wayl->shm_have_argb2101010 && wayl->shm_have_xrgb2101010) { - pixman_fmt_without_alpha = PIXMAN_x2r10g10b10; - shm_fmt_without_alpha = WL_SHM_FORMAT_XRGB2101010; - - pixman_fmt_with_alpha = PIXMAN_a2r10g10b10; - shm_fmt_with_alpha = WL_SHM_FORMAT_ARGB2101010; + if (desired_bit_depth >= SHM_BITS_10 && pixman_fmt == PIXMAN_a8r8g8b8) { + if (wayl->shm_have_argb2101010) { + pixman_fmt = PIXMAN_a2r10g10b10; + shm_fmt = WL_SHM_FORMAT_ARGB2101010; if (!have_logged) { have_logged = true; @@ -1061,12 +1035,9 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, } } - else if (wayl->shm_have_abgr2101010 && wayl->shm_have_xbgr2101010) { - pixman_fmt_without_alpha = PIXMAN_x2b10g10r10; - shm_fmt_without_alpha = WL_SHM_FORMAT_XBGR2101010; - - pixman_fmt_with_alpha = PIXMAN_a2b10g10r10; - shm_fmt_with_alpha = WL_SHM_FORMAT_ABGR2101010; + else if (wayl->shm_have_abgr2101010) { + pixman_fmt = PIXMAN_a2b10g10r10; + shm_fmt = WL_SHM_FORMAT_ABGR2101010; if (!have_logged) { have_logged = true; @@ -1098,11 +1069,8 @@ shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances, .pix_instances = pix_instances, .scrollable = scrollable, - .pixman_fmt_without_alpha = pixman_fmt_without_alpha, - .shm_format_without_alpha = shm_fmt_without_alpha, - - .pixman_fmt_with_alpha = pixman_fmt_with_alpha, - .shm_format_with_alpha = shm_fmt_with_alpha, + .pixman_fmt = pixman_fmt, + .shm_format = shm_fmt, .release_cb = release_cb, .cb_data = cb_data, @@ -1129,7 +1097,7 @@ shm_chain_free(struct buffer_chain *chain) enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain) { - const pixman_format_code_t fmt = chain->pixman_fmt_with_alpha; + const pixman_format_code_t fmt = chain->pixman_fmt; return fmt == PIXMAN_a8r8g8b8 ? SHM_BITS_8 diff --git a/shm.h b/shm.h index 84eb4386..c58a8531 100644 --- a/shm.h +++ b/shm.h @@ -65,8 +65,7 @@ enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain); * * A newly allocated buffer has an age of 1234. */ -struct buffer *shm_get_buffer( - struct buffer_chain *chain, int width, int height, bool with_alpha); +struct buffer *shm_get_buffer(struct buffer_chain *chain, int width, int height); /* * Returns many buffers, described by 'info', all sharing the same SHM * buffer pool. @@ -84,7 +83,7 @@ struct buffer *shm_get_buffer( void shm_get_many( struct buffer_chain *chain, size_t count, int widths[static count], int heights[static count], - struct buffer *bufs[static count], bool with_alpha); + struct buffer *bufs[static count]); void shm_did_not_use_buf(struct buffer *buf); diff --git a/sixel.c b/sixel.c index 07b97f46..294864fd 100644 --- a/sixel.c +++ b/sixel.c @@ -125,12 +125,12 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) * that assumes 32-bit pixels). */ if (shm_chain_bit_depth(term->render.chains.grid) >= SHM_BITS_10) { - if (term->wl->shm_have_argb2101010 && term->wl->shm_have_xrgb2101010) { + if (term->wl->shm_have_argb2101010) { term->sixel.use_10bit = true; term->sixel.pixman_fmt = PIXMAN_a2r10g10b10; } - else if (term->wl->shm_have_abgr2101010 && term->wl->shm_have_xbgr2101010) { + else if (term->wl->shm_have_abgr2101010) { term->sixel.use_10bit = true; term->sixel.pixman_fmt = PIXMAN_a2b10g10r10; } diff --git a/wayland.c b/wayland.c index 6785c52d..958e535d 100644 --- a/wayland.c +++ b/wayland.c @@ -240,11 +240,8 @@ shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) struct wayland *wayl = data; switch (format) { - case WL_SHM_FORMAT_XRGB2101010: wayl->shm_have_xrgb2101010 = true; break; case WL_SHM_FORMAT_ARGB2101010: wayl->shm_have_argb2101010 = true; break; - case WL_SHM_FORMAT_XBGR2101010: wayl->shm_have_xbgr2101010 = true; break; case WL_SHM_FORMAT_ABGR2101010: wayl->shm_have_abgr2101010 = true; break; - case WL_SHM_FORMAT_XBGR16161616: wayl->shm_have_xbgr161616 = true; break; case WL_SHM_FORMAT_ABGR16161616: wayl->shm_have_abgr161616 = true; break; } diff --git a/wayland.h b/wayland.h index 140c2058..1b1c1f4c 100644 --- a/wayland.h +++ b/wayland.h @@ -502,11 +502,8 @@ struct wayland { bool use_shm_release; bool shm_have_argb2101010:1; - bool shm_have_xrgb2101010:1; bool shm_have_abgr2101010:1; - bool shm_have_xbgr2101010:1; bool shm_have_abgr161616:1; - bool shm_have_xbgr161616:1; }; struct wayland *wayl_init( From e2a989785ab32a39aaa8d49a45b8a94b12a12fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 10 Jan 2026 07:35:25 +0100 Subject: [PATCH 328/353] input: execute: add missing 'return true' to a couple of switch cases Without this, the input handling code won't understand the key/mouse event was consumed (i.e. triggered a shortcut), and will continue processing normally (e.g. sending event to the client application). --- input.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/input.c b/input.c index 80b028ac..e8e84fb7 100644 --- a/input.c +++ b/input.c @@ -120,10 +120,14 @@ execute_binding(struct seat *seat, struct terminal *term, case BIND_ACTION_SCROLLBACK_UP_MOUSE: if (term->grid == &term->alt) { - if (term->alt_scrolling) + if (term->alt_scrolling) { alternate_scroll(seat, amount, BTN_BACK); - } else - cmd_scrollback_up(term, amount); + return true; + } + } else { + cmd_scrollback_up(term, amount); + return true; + } break; case BIND_ACTION_SCROLLBACK_DOWN_PAGE: @@ -149,10 +153,14 @@ execute_binding(struct seat *seat, struct terminal *term, case BIND_ACTION_SCROLLBACK_DOWN_MOUSE: if (term->grid == &term->alt) { - if (term->alt_scrolling) + if (term->alt_scrolling) { alternate_scroll(seat, amount, BTN_FORWARD); - } else + return true; + } + } else { cmd_scrollback_down(term, amount); + return true; + } break; case BIND_ACTION_SCROLLBACK_HOME: @@ -535,7 +543,7 @@ execute_binding(struct seat *seat, struct terminal *term, case BIND_ACTION_SELECT_QUOTE: selection_start( term, seat->mouse.col, seat->mouse.row, SELECTION_QUOTE_WISE, false); - break; + return true; case BIND_ACTION_SELECT_ROW: selection_start( From 3a2eb80d83d59d194a3d07da227431c634892ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 10 Jan 2026 07:36:17 +0100 Subject: [PATCH 329/353] input: ignore release events after a keyboard shortcut was triggered This fixes an issue with the kitty keyboard protocol, where 'release' events associated with a shortcut was sent to the client application. Example: user triggers "scroll up". We scroll up. No key event(s) are sent to the client application. Then the user releases the keys. we don't do any shortcut handling on release events, and so we continue with the normal input processing. If the kitty keyboard protocol has been enabled (and specifically, release event reporting has been enabled), then we'll emit a 'release' escape sequence. This in itself is wrong, since the client application never saw the corresponding press event. But we _also_ reset the viewport. The effect (in this example), is that it's impossible to scroll up in the scrollback history. Note that we don't ignore _any_ release event, only the release event for the (final) symbol that triggered the shortcut. This should allow e.g. modifier keys release events to be processed normally, if released before the shortcut key. This is somewhat important, since the client application will have received press events for the modifier keys leading up to the shortcut (if modifier press/release events have been enabled in the kitty keyboard protocol - _Report all keys as escape codes_). Closes #2257 --- CHANGELOG.md | 3 +++ input.c | 24 ++++++++++++++++++++++++ wayland.h | 2 ++ 3 files changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0462666c..a69f58b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -122,8 +122,11 @@ * Crash when reverse-scrolling (terminfo capability `rin`) such that the current viewport ends up outside the scrollback ([#2232][2232]). * Regression: visual glitches in rare circumstances. +* Key release events for shortcuts being sent to the client + application (kitty keyboard protocol only) ([#2257][2257]). [2232]: https://codeberg.org/dnkl/foot/issues/2232 +[2257]: https://codeberg.org/dnkl/foot/issues/2257 ### Security diff --git a/input.c b/input.c index e8e84fb7..aa6b7f1d 100644 --- a/input.c +++ b/input.c @@ -1605,6 +1605,9 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (released) stop_repeater(seat, key); + if (pressed) + seat->kbd.last_shortcut_sym = XKB_KEYSYM_MAX + 1; + bool should_repeat = pressed && xkb_keymap_key_repeats(seat->kbd.xkb_keymap, key); @@ -1706,6 +1709,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (bind->k.sym == raw_syms[i] && execute_binding(seat, term, bind, serial, 1)) { + seat->kbd.last_shortcut_sym = sym; goto maybe_repeat; } } @@ -1719,6 +1723,7 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, bind->mods == (mods & ~consumed) && execute_binding(seat, term, bind, serial, 1)) { + seat->kbd.last_shortcut_sym = sym; goto maybe_repeat; } } @@ -1734,12 +1739,31 @@ key_press_release(struct seat *seat, struct terminal *term, uint32_t serial, if (code->item == key && execute_binding(seat, term, bind, serial, 1)) { + seat->kbd.last_shortcut_sym = sym; goto maybe_repeat; } } } } + if (released && seat->kbd.last_shortcut_sym == sym) { + /* + * Don't process a release event, if it corresponds to a + * triggered shortcut. + * + * 1. If we consumed a key (press) event, we shouldn't emit an + * escape for its release event. + * 2. Ignoring the incorrectness of doing so; this also caused + * us to reset the viewport. + * + * Background: if the kitty keyboard protocol was enabled, + * then the viewport was instantly reset to the bottom, after + * scrolling up. + */ + //seat->kbd.last_shortcut_sym = XKB_KEYSYM_MAX + 1; + goto maybe_repeat; + } + /* * Keys generating escape sequences */ diff --git a/wayland.h b/wayland.h index 1b1c1f4c..6247875a 100644 --- a/wayland.h +++ b/wayland.h @@ -151,6 +151,8 @@ struct seat { bool alt; bool ctrl; bool super; + + xkb_keysym_t last_shortcut_sym; } kbd; /* Pointer state */ From 6fbb9b7d3b2b43e69ca3ce56823fdb9f65a74890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Jan 2026 09:21:57 +0100 Subject: [PATCH 330/353] sixel: force a height of at least one sixel when explicitly resizing Applications often prefix the sixel with a raster attributes (RA) sequence, where they tell us how large the sixel is. Strictly speaking, this just tells us the size of the area to clear, but we use it as a hint and pre-allocates the image buffer. It's important to stress that it is valid to emit a MxN RA, and then write sixel data outside of that area. Foot handles this, in _most_ cases. We didn't handle the corner case Mx0. I.e. a width > 0, but height == 0. No image buffer was allocated, and we also failed to detect a resize was necessary when the application started printing sixel data. Much of this is for performance reason; we only check the minimum necessary. For example, we only check if going outside the pre-allocated *column* while printing sixels. *Rows* are checked on a graphical newline. In other words, the *current* row has to be valid when writing sixels. And in case of Mx0, it wasn't. Fix by forcing a height of at least one sixel (typically 6 pixels). Closes #2267 --- CHANGELOG.md | 3 +++ sixel.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a69f58b8..ce1a7d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -124,9 +124,12 @@ * Regression: visual glitches in rare circumstances. * Key release events for shortcuts being sent to the client application (kitty keyboard protocol only) ([#2257][2257]). +* Crash when application emits sixel RA with a height of 0, a width > + 0, and then starts writing sixel data ([#2267][2267]). [2232]: https://codeberg.org/dnkl/foot/issues/2232 [2257]: https://codeberg.org/dnkl/foot/issues/2257 +[2267]: https://codeberg.org/dnkl/foot/issues/2267 ### Security diff --git a/sixel.c b/sixel.c index 294864fd..187f1348 100644 --- a/sixel.c +++ b/sixel.c @@ -1559,6 +1559,9 @@ resize(struct terminal *term, int new_width_mutable, int new_height_mutable) new_height_mutable = term->sixel.max_height; } + if (unlikely(new_height_mutable == 0)) { + new_height_mutable = 6 * term->sixel.pan; + } uint32_t *old_data = term->sixel.image.data; const int old_width = term->sixel.image.width; From 0bf193ef8122b2fd412438c0e405fb085cd62a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Feb 2026 11:19:07 +0100 Subject: [PATCH 331/353] osc-8: don't log URL + ID when closing --- osc.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osc.c b/osc.c index 9407e7b8..909fd484 100644 --- a/osc.c +++ b/osc.c @@ -525,12 +525,14 @@ osc_uri(struct terminal *term, char *string) id = sdbm_hash(value); } - LOG_DBG("OSC-8: URL=%s, id=%" PRIu64, uri, id); - if (uri[0] == '\0') + if (uri[0] == '\0') { + LOG_DBG("OSC-8: close"); term_osc8_close(term); - else + } else { + LOG_DBG("OSC-8: URL=%s, id=%" PRIu64, uri, id); term_osc8_open(term, id, uri); + } } static void From c291194a4e593bbbb91420e81fa0111508084448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 28 Jan 2026 09:44:57 +0100 Subject: [PATCH 332/353] wayland: wait for pre-apply damage thread before destroying a terminal instance It's possible, but unlikely, that we've pushed a "pre-apply damage" job to the renderer thread queue (or that we've pushed it, and the a thread is now working on it) when we shutdown a terminal instance. This is sometimes caught in an assertion in term_destroy(), where we check the queue length is 0. Other times, or in release builds, we might crash in the thread, or in the shutdown logic when freeing the buffer chains associated with the terminal instance. Fix by ensuring there's no pre-apply damage operation queued, or running, before shutting down a terminal instance. Closes #2263 --- CHANGELOG.md | 3 +++ render.c | 10 +++++----- render.h | 1 + wayland.c | 2 ++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce1a7d8c..cee4ddef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,10 +126,13 @@ application (kitty keyboard protocol only) ([#2257][2257]). * Crash when application emits sixel RA with a height of 0, a width > 0, and then starts writing sixel data ([#2267][2267]). +* Crash if shutting down terminal instance while a "pre-apply damage" + thread is running ([#2263][2263]). [2232]: https://codeberg.org/dnkl/foot/issues/2232 [2257]: https://codeberg.org/dnkl/foot/issues/2257 [2267]: https://codeberg.org/dnkl/foot/issues/2267 +[2263]: https://codeberg.org/dnkl/foot/issues/2263 ### Security diff --git a/render.c b/render.c index 86244434..3aa7d543 100644 --- a/render.c +++ b/render.c @@ -2288,8 +2288,8 @@ render_worker_thread(void *_ctx) return -1; } -static void -wait_for_preapply_damage(struct terminal *term) +void +render_wait_for_preapply_damage(struct terminal *term) { if (!term->render.preapply_last_frame_damage) return; @@ -3325,7 +3325,7 @@ grid_render(struct terminal *term) term->render.workers.preapplied_damage.buf != NULL)) { clock_gettime(CLOCK_MONOTONIC, &start_wait_preapply); - wait_for_preapply_damage(term); + render_wait_for_preapply_damage(term); clock_gettime(CLOCK_MONOTONIC, &stop_wait_preapply); } @@ -4401,7 +4401,7 @@ delayed_reflow_of_normal_grid(struct terminal *term) term->interactive_resizing.old_hide_cursor = false; /* Invalidate render pointers */ - wait_for_preapply_damage(term); + render_wait_for_preapply_damage(term); shm_unref(term->render.last_buf); term->render.last_buf = NULL; term->render.last_cursor.row = NULL; @@ -4976,7 +4976,7 @@ damage_view: tll_free(term->normal.scroll_damage); tll_free(term->alt.scroll_damage); - wait_for_preapply_damage(term); + render_wait_for_preapply_damage(term); shm_unref(term->render.last_buf); term->render.last_buf = NULL; term_damage_view(term); diff --git a/render.h b/render.h index e21eaca8..e6674ab2 100644 --- a/render.h +++ b/render.h @@ -49,3 +49,4 @@ struct csd_data { struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx); void render_buffer_release_callback(struct buffer *buf, void *data); +void render_wait_for_preapply_damage(struct terminal *term); diff --git a/wayland.c b/wayland.c index 958e535d..59df991a 100644 --- a/wayland.c +++ b/wayland.c @@ -2129,6 +2129,8 @@ wayl_win_destroy(struct wl_window *win) struct terminal *term = win->term; + render_wait_for_preapply_damage(term); + if (win->csd.move_timeout_fd != -1) close(win->csd.move_timeout_fd); From e24334a8dfdd43a5a26ad59bba0d76409b1de1ea Mon Sep 17 00:00:00 2001 From: valoq Date: Mon, 23 Feb 2026 16:46:36 +0100 Subject: [PATCH 333/353] Fix cursor color Default cursor color in alacritty is white (foreground) and not cyan. --- themes/alacritty | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/alacritty b/themes/alacritty index 68d1c68c..f05683ba 100644 --- a/themes/alacritty +++ b/themes/alacritty @@ -2,7 +2,7 @@ # Alacritty [colors-dark] -cursor = 181818 56d8c9 +cursor = 181818 d8d8d8 background= 181818 foreground= d8d8d8 From 1f31b43db7acd5ea5f7e88d0408f043a78df17fe Mon Sep 17 00:00:00 2001 From: Barinderpreet Singh <64461700+knownasnaffy@users.noreply.github.com> Date: Fri, 27 Feb 2026 18:58:35 +0530 Subject: [PATCH 334/353] doc: fix typos in foot.ini.5.scd --- doc/foot.ini.5.scd | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 8bff9629..33ebfec8 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -29,7 +29,7 @@ Options are set using KEY=VALUE pairs: *foreground=ffffff* Empty values (*KEY=*) are not supported. String options do allow the -empty string to be set, but it must be quoted: *KEY=""*) +empty string to be set, but it must be quoted: *KEY=""* # SECTION: main @@ -96,7 +96,7 @@ empty string to be set, but it must be quoted: *KEY=""*) *font-size-adjustment* Amount, in _points_, _pixels_ or _percent_, to increment/decrement - the font size when zooming in our out. + the font size when zooming in or out. Examples: ``` @@ -129,7 +129,7 @@ empty string to be set, but it must be quoted: *KEY=""*) e.g. *line-height=12px*. *Warning*: when changing the font size at runtime (i.e. zooming in - our out), foot will change the line height by the same + or out), foot will change the line height by the same percentage. However, due to rounding, it is possible the line height will be "too small" for some font sizes, causing e.g. underscores to "disappear". @@ -161,7 +161,7 @@ empty string to be set, but it must be quoted: *KEY=""*) *underline-offset* Use a custom offset for underlines. The offset is, by default, in - _points_ and relative the font's baseline. A positive value + _points_ and relative to the font's baseline. A positive value positions the underline under the baseline, while a negative value positions it above the baseline. @@ -240,7 +240,7 @@ empty string to be set, but it must be quoted: *KEY=""*) *box-drawings-uses-font-glyphs* Boolean. When disabled, foot generates box/line drawing characters - itself. The are several advantages to doing this instead of using + itself. There are several advantages to doing this instead of using font glyphs: - No antialiasing effects where e.g. line endpoints appear @@ -281,7 +281,7 @@ empty string to be set, but it must be quoted: *KEY=""*) scaling factor *does* double the font size. Note that this option typically does not work with bitmap fonts, - which only contains a pre-defined set of sizes, and cannot be + which only contain a pre-defined set of sizes, and cannot be dynamically scaled. Whichever size (of the available ones) that best matches the DPI or scaling factor, will be used. @@ -337,7 +337,7 @@ empty string to be set, but it must be quoted: *KEY=""*) Emphasis is on _while_ here; as soon as the interactive resize ends (i.e. when you let go of the window border), the final - dimensions is sent to the client, without any delays. + dimensions are sent to the client, without any delays. Setting it to 0 disables the delay completely. @@ -352,7 +352,7 @@ empty string to be set, but it must be quoted: *KEY=""*) as necessary to accommodate window sizes that are not multiples of the cell size. - This option only applies to floating windows. Sizes of maxmized, tiled + This option only applies to floating windows. Sizes of maximized, tiled or fullscreen windows will not be constrained to multiples of the cell size. @@ -399,7 +399,7 @@ empty string to be set, but it must be quoted: *KEY=""*) *initial-window-size-chars* Initial window width and height in _characters_, in the form _WIDTHxHEIGHT_. Mutually exclusive to - *initial-window-size-pixels*.' + *initial-window-size-pixels*. Note that if you have a multi-monitor setup, with different scaling factors, there is a possibility the window size will not @@ -588,7 +588,7 @@ Note: do not set *TERM* here; use the *term* option in the main option, or preferably, by setting the *image-path* hint (with e.g. notify-send's *--hint* option). - _${category}_ is replaced by the notification's catogory. Can + _${category}_ is replaced by the notification's category. Can be used together with e.g. notify-send's *--category* option. _${urgency}_ is replaced with the notifications urgency; @@ -700,7 +700,7 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 Foot recognizes this as: - notification has the daemon assigned ID 17 - the user triggered the default action - - the notification send an XDG activation token + - the notification sent an XDG activation token Example #2: 17++ @@ -716,7 +716,7 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 Foot recognizes this as: - notification has the daemon assigned ID 17 - - the user triggered the first custom action, "1 + - the user triggered the first custom action, "1" Default: _notify-send++ --wait++ @@ -760,7 +760,7 @@ xdgtoken=95ebdfe56e4f47ddb5bba9d7dc3a2c35 least one), *command-action-argument* will be expanded with the action's name and label. - Then, _${action-argument}_ is expanded *command* to the full list + Then, _${action-argument}_ is expanded in *command* to the full list of actions. If *command-action-argument* is set to the empty string, no @@ -933,7 +933,7 @@ applications can change these at runtime. by applications. Related option: *blink-rate*. Default: _no_. *blink-rate* - The rate at which the cursor blink, when cursor blinking has been + The rate at which the cursor blinks, when cursor blinking has been enabled. Expressed in milliseconds between each blink. Default: _500_. @@ -1187,7 +1187,7 @@ Examples: *hide-when-maximized* Boolean. When enabled, the CSD titlebar is hidden when the window - is maximized. The completely disable the titlebar, set *size* to 0 + is maximized. To completely disable the titlebar, set *size* to 0 instead. Default: _no_. *double-click-to-maximize* @@ -1417,7 +1417,7 @@ e.g. *search-start=none*. shell integration, see *foot*(1)). Default: _Control+Shift+z_. *prompt-next* - Jump the next prompt (requires shell integration, see + Jump to the next prompt (requires shell integration, see *foot*(1)). Default: _Control+Shift+x_. *unicode-input* @@ -1446,7 +1446,7 @@ e.g. *search-start=none*. Default: _Control+Shift+u_. -*color-theme-switch-dark*, *color-theme-switch-dark*, *color-theme-toggle* +*color-theme-switch-dark*, *color-theme-switch-light*, *color-theme-toggle* Switch between the dark color theme (defined in the *colors-dark* section), and the light color theme (defined in the *colors-light* section). @@ -1817,7 +1817,7 @@ any of these options. *scaling-filter* Overrides the default scaling filter used when down-scaling bitmap fonts (e.g. emoji fonts). Possible values are *none*, *nearest*, - *bilinear*, *impulse*, *box*, *linear*, *cubic* *gaussian*, + *bilinear*, *impulse*, *box*, *linear*, *cubic*, *gaussian*, *lanczos2*, *lanczos3* or *lanczos3-stretched*. Default: _lanczos3_. @@ -1886,8 +1886,8 @@ any of these options. must be patched to use it. Until this has happened, foot offers an interim workaround; an - attempt to mitigate the screen flicker *without* affecting neither - performance nor latency. + attempt to mitigate the screen flicker *without* affecting either + performance or latency. It is based on the fact that the screen is updated at a fixed interval (typically 60Hz). For us, this means it does not matter @@ -2129,7 +2129,7 @@ any of these options. Thus, having this option enabled improves both performance (copying the last two frames' changes is threaded), and improves - input latency (rending the next frame no longer has to first bring + input latency (rendering the next frame no longer has to first bring over the changes between the last two frames). Default: _yes_ From fbf430473146bdb6f9ab6afefa4aab4c57a3d9a4 Mon Sep 17 00:00:00 2001 From: nariby Date: Sun, 8 Feb 2026 14:12:32 +0000 Subject: [PATCH 335/353] doc: foot.ini: mention titlebar text color in button-color --- doc/foot.ini.5.scd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 33ebfec8..a9e4f045 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1209,8 +1209,8 @@ Examples: minimize/maximize/close buttons. Default: _26_. *button-color* - Foreground color on the minimize/maximize/close buttons. Default: - use the default _background_ color. + Foreground color on the minimize/maximize/close buttons and the + titlebar text. Default: use the default _background_ color. *button-minimize-color* Minimize button's background color. Default: use the default From 21485fa66d9b7026bfd1dd2764431805cdb17cf1 Mon Sep 17 00:00:00 2001 From: pi66 Date: Fri, 19 Dec 2025 12:17:29 +0100 Subject: [PATCH 336/353] support four-sided padding (left/top/right/bottom) --- CHANGELOG.md | 2 ++ config.c | 41 ++++++++++++++++++++++++++++++----------- config.h | 6 ++++-- doc/foot.ini.5.scd | 15 +++++++++++++-- render.c | 34 ++++++++++++++++++++-------------- 5 files changed, 69 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cee4ddef..3e4c2a6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,8 @@ * `[colors-light]` section to `foot.ini`. Replaces `[colors2]`. * `XTGETTCAP`: added `query-os-name`, returning the OS foot is compiled for (e.g. _'Linux'_) ([#2209][2209]). +* `pad` option now supports 4-directional padding format: + `LEFTxTOPxRIGHTxBOTTOM` (e.g., `20x10x20x10`). [2212]: https://codeberg.org/dnkl/foot/issues/2212 [2209]: https://codeberg.org/dnkl/foot/issues/2209 diff --git a/config.c b/config.c index 14e836c1..b1ff329c 100644 --- a/config.c +++ b/config.c @@ -945,13 +945,12 @@ parse_section_main(struct context *ctx) } else if (streq(key, "pad")) { - unsigned x, y; + unsigned x, y, left, top, right, bottom; char mode[64] = {0}; - int ret = sscanf(value, "%ux%u %63s", &x, &y, mode); - + int ret = sscanf(value, "%ux%ux%ux%u %63s", &left, &top, &right, &bottom, mode); enum center_when center = CENTER_NEVER; - if (ret == 3) { + if (ret == 5) { if (strcasecmp(mode, "center") == 0) center = CENTER_ALWAYS; else if (strcasecmp(mode, "center-when-fullscreen") == 0) @@ -960,20 +959,38 @@ parse_section_main(struct context *ctx) center = CENTER_MAXIMIZED_AND_FULLSCREEN; else center = CENTER_INVALID; + } else if (ret < 4) { + ret = sscanf(value, "%ux%u %63s", &x, &y, mode); + if (ret >= 2) { + left = right = x; + top = bottom = y; + if (ret == 3) { + if (strcasecmp(mode, "center") == 0) + center = CENTER_ALWAYS; + else if (strcasecmp(mode, "center-when-fullscreen") == 0) + center = CENTER_FULLSCREEN; + else if (strcasecmp(mode, "center-when-maximized-and-fullscreen") == 0) + center = CENTER_MAXIMIZED_AND_FULLSCREEN; + else + center = CENTER_INVALID; + } + } } - if ((ret != 2 && ret != 3) || center == CENTER_INVALID) { + if ((ret < 2 || ret > 5) || center == CENTER_INVALID) { LOG_CONTEXTUAL_ERR( - "invalid padding (must be in the form PAD_XxPAD_Y " + "invalid padding (must be in the form RIGHTxTOPxLEFTxBOTTOM or XxY " "[center|" "center-when-fullscreen|" "center-when-maximized-and-fullscreen])"); return false; } - conf->pad_x = x; - conf->pad_y = y; - conf->center_when = ret == 2 ? CENTER_NEVER : center; + conf->pad_left = left; + conf->pad_top = top; + conf->pad_right = right; + conf->pad_bottom = bottom; + conf->center_when = (ret == 4 || ret == 2) ? CENTER_NEVER : center; return true; } @@ -3453,8 +3470,10 @@ config_load(struct config *conf, const char *conf_path, .width = 700, .height = 500, }, - .pad_x = 0, - .pad_y = 0, + .pad_left = 0, + .pad_top = 0, + .pad_right = 0, + .pad_bottom = 0, .center_when = CENTER_MAXIMIZED_AND_FULLSCREEN, .resize_by_cells = true, .resize_keep_grid = true, diff --git a/config.h b/config.h index 9ca47753..d7db5ecc 100644 --- a/config.h +++ b/config.h @@ -232,8 +232,10 @@ struct config { uint32_t height; } size; - unsigned pad_x; - unsigned pad_y; + unsigned pad_left; + unsigned pad_top; + unsigned pad_right; + unsigned pad_bottom; enum center_when center_when; bool resize_by_cells; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index a9e4f045..be8c131e 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -301,9 +301,20 @@ empty string to be set, but it must be quoted: *KEY=""* ``` _XxY_ [center | center-when-fullscreen | center-when-maximized-and-fullscreen] ``` + or + ``` + RIGHTxTOPxLEFTxBOTTOM [center | center-when-fullscreen | center-when-maximized-and-fullscreen] + ``` - This will add _at least_ X pixels on both the left and right - sides, and Y pixels on the top and bottom sides. + - `_XxY_` adds _at least_: + - X pixels on the left and right sides. + - Y pixels on the top and bottom sides. + + - `LEFTxTOPxRIGHTxBOTTOM` adds **at least**: + - LEFT pixels to the left + - TOP pixels to the top + - RIGHT pixels to the right + - BOTTOM pixels to the bottom When no centering is specified, the grid content is anchored to the top left corner. I.e. if the window manager forces an odd diff --git a/render.c b/render.c index 3aa7d543..627da5d6 100644 --- a/render.c +++ b/render.c @@ -4504,8 +4504,8 @@ set_size_from_grid(struct terminal *term, int *width, int *height, int cols, int new_height = rows * term->cell_height; /* Include any configured padding */ - new_width += 2 * term->conf->pad_x * term->scale; - new_height += 2 * term->conf->pad_y * term->scale; + new_width += (term->conf->pad_left + term->conf->pad_right) * term->scale; + new_height += (term->conf->pad_top + term->conf->pad_bottom) * term->scale; /* Round to multiples of scale */ new_width = round(term->scale * round(new_width / term->scale)); @@ -4613,18 +4613,22 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) /* 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); + const int pad_left = min(max_pad_x, scale * term->conf->pad_left); + const int pad_right = min(max_pad_x, scale * term->conf->pad_right); + const int pad_top = min(max_pad_y, scale * term->conf->pad_top); + const int pad_bottom= min(max_pad_y, scale * term->conf->pad_bottom); if (is_floating && (opts & RESIZE_BY_CELLS) && term->conf->resize_by_cells) { /* If resizing in cell increments, restrict the width and height */ - width = ((width - 2 * pad_x) / term->cell_width) * term->cell_width + 2 * pad_x; + width = ((width - (pad_left + pad_right)) / term->cell_width) + * term->cell_width + (pad_left + pad_right); width = max(min_width, roundf(scale * roundf(width / scale))); - height = ((height - 2 * pad_y) / term->cell_height) * term->cell_height + 2 * pad_y; + height = ((height - (pad_top + pad_bottom)) / term->cell_height) + * term->cell_height + (pad_top + pad_bottom); height = max(min_height, roundf(scale * roundf(height / scale))); } @@ -4651,8 +4655,10 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) int old_rows = term->rows; /* Screen rows/cols after resize */ - const int new_cols = (term->width - 2 * pad_x) / term->cell_width; - const int new_rows = (term->height - 2 * pad_y) / term->cell_height; + const int new_cols = + (term->width - (pad_left + pad_right)) / term->cell_width; + const int new_rows = + (term->height - (pad_top + pad_bottom)) / term->cell_height; /* * Requirements for scrollback: @@ -4705,16 +4711,16 @@ render_resize(struct terminal *term, int width, int height, uint8_t opts) term->margins.left = total_x_pad / 2; term->margins.top = total_y_pad / 2; } else { - term->margins.left = pad_x; - term->margins.top = pad_y; + term->margins.left = pad_left; + term->margins.top = pad_top; } term->margins.right = total_x_pad - term->margins.left; term->margins.bottom = total_y_pad - term->margins.top; - xassert(term->margins.left >= pad_x); - xassert(term->margins.right >= pad_x); - xassert(term->margins.top >= pad_y); - xassert(term->margins.bottom >= pad_y); + xassert(term->margins.left >= pad_left); + xassert(term->margins.right >= pad_right); + xassert(term->margins.top >= pad_top); + xassert(term->margins.bottom >= pad_bottom); if (new_cols == old_cols && new_rows == old_rows) { LOG_DBG("grid layout unaffected; skipping reflow"); From dc0c8550c38ffd0983789573a1a0d4417016c2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Klein?= Date: Fri, 9 Jan 2026 00:31:04 +0100 Subject: [PATCH 337/353] Spawning new terminal with --config from parent instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference: https://codeberg.org/dnkl/foot/issues/1622 Signed-off-by: Stéphane Klein --- CHANGELOG.md | 3 +++ config.c | 3 +++ config.h | 1 + doc/foot.1.scd | 3 +++ terminal.c | 12 +++++++++++- 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e4c2a6f..c3760f10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,9 +80,12 @@ compiled for (e.g. _'Linux'_) ([#2209][2209]). * `pad` option now supports 4-directional padding format: `LEFTxTOPxRIGHTxBOTTOM` (e.g., `20x10x20x10`). +* `--config=PATH` option is now automatically passed to new + terminals spawned via `spawn-terminal` action ([#2259][2259]). [2212]: https://codeberg.org/dnkl/foot/issues/2212 [2209]: https://codeberg.org/dnkl/foot/issues/2209 +[2259]: https://codeberg.org/dnkl/foot/pulls/2259 ### Changed diff --git a/config.c b/config.c index b1ff329c..bbac7fb6 100644 --- a/config.c +++ b/config.c @@ -3459,6 +3459,7 @@ config_load(struct config *conf, const char *conf_path, enum fcft_capabilities fcft_caps = fcft_capabilities(); *conf = (struct config) { + .conf_path = (conf_path ? xstrdup(conf_path) : NULL), .term = xstrdup(FOOT_DEFAULT_TERM), .shell = get_shell(), .title = xstrdup("foot"), @@ -3914,6 +3915,7 @@ config_clone(const struct config *old) struct config *conf = xmalloc(sizeof(*conf)); *conf = *old; + conf->conf_path = (old->conf_path ? xstrdup(old->conf_path) : NULL); conf->term = xstrdup(old->term); conf->shell = xstrdup(old->shell); conf->title = xstrdup(old->title); @@ -4014,6 +4016,7 @@ UNITTEST void config_free(struct config *conf) { + free(conf->conf_path); free(conf->term); free(conf->shell); free(conf->title); diff --git a/config.h b/config.h index d7db5ecc..47743a9c 100644 --- a/config.h +++ b/config.h @@ -217,6 +217,7 @@ enum center_when { }; struct config { + char *conf_path; char *term; char *shell; char *title; diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 7058e96f..a190db9b 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -27,6 +27,9 @@ the foot command line *-c*,*--config*=_PATH_ Path to configuration file, see *foot.ini*(5) for details. + The configuration file is automatically passed to new terminals + spawned via *spawn-terminal* (see *foot.ini*(5)). + *-C*,*--check-config* Verify configuration and then exit with 0 if ok, otherwise exit with 230 (see *EXIT STATUS*). diff --git a/terminal.c b/terminal.c index b670d606..8ce9bd4b 100644 --- a/terminal.c +++ b/terminal.c @@ -3802,8 +3802,18 @@ term_bell(struct terminal *term) bool term_spawn_new(const struct terminal *term) { + char *argv[4]; + int argc = 0; + + argv[argc++] = term->foot_exe; + if (term->conf->conf_path != NULL) { + argv[argc++] = "--config"; + argv[argc++] = term->conf->conf_path; + } + argv[argc] = NULL; + return spawn( - term->reaper, term->cwd, (char *const []){term->foot_exe, NULL}, + term->reaper, term->cwd, argv, -1, -1, -1, NULL, NULL, NULL) >= 0; } From dea10e2e48162c6ab48ca564ceeca88dea886a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 16 Oct 2025 13:43:33 +0200 Subject: [PATCH 338/353] Add support for background blur This patch adds a new config option: colors{,2}.blur=no|yes. When enabled, transparent background are also blurred. Note that this requires the brand new ext-background-effect-v1 protocol, and specifically, that the compositor implements the blur effect. --- CHANGELOG.md | 6 +++ config.c | 4 ++ config.h | 2 + doc/foot.ini.5.scd | 8 ++++ foot-features.c | 6 +++ meson.build | 4 ++ osc.c | 42 ++++---------------- render.c | 4 +- terminal.c | 8 ++++ terminal.h | 1 + tests/test-config.c | 4 ++ wayland.c | 97 ++++++++++++++++++++++++++++++++++++++++++--- wayland.h | 11 +++++ 13 files changed, 154 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3760f10..3ada2084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,12 @@ `LEFTxTOPxRIGHTxBOTTOM` (e.g., `20x10x20x10`). * `--config=PATH` option is now automatically passed to new terminals spawned via `spawn-terminal` action ([#2259][2259]). +* Preliminary (untested) support for background blur via the new + `ext-background-effect-v1` protocol. Enable by setting + `colors-{dark,light}.blur=yes`. Foot needs to have been **built** + against `wayland-protocols >= 1.45`, and the compositor **must** + implement the `ext-background-effect-v1` protocol, **and** the + `blur` effect. [2212]: https://codeberg.org/dnkl/foot/issues/2212 [2209]: https://codeberg.org/dnkl/foot/issues/2209 diff --git a/config.c b/config.c index bbac7fb6..12c594bc 100644 --- a/config.c +++ b/config.c @@ -1593,6 +1593,9 @@ parse_color_theme(struct context *ctx, struct color_theme *theme) (int *)&theme->dim_blend_towards); } + else if (streq(key, "blur")) + return value_to_bool(ctx, &theme->blur); + else { LOG_CONTEXTUAL_ERR("not valid option"); return false; @@ -3546,6 +3549,7 @@ config_load(struct config *conf, const char *conf_path, .scrollback_indicator = false, .url = false, }, + .blur = false, }, .initial_color_theme = COLOR_THEME_DARK, .cursor = { diff --git a/config.h b/config.h index 47743a9c..a3522f44 100644 --- a/config.h +++ b/config.h @@ -192,6 +192,8 @@ struct color_theme { bool search_box_match:1; uint8_t dim; } use_custom; + + bool blur; }; enum which_color_theme { diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index be8c131e..2f5fc38c 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -1108,6 +1108,14 @@ The default theme used is *colors-dark*, unless Default: _default_ +*blur* + Boolean. When enabled, foot will blur the background (main window + only, not CSDs etc), when it is transparent. This feature requires + the compositor to implement the _ext-background-effect-v1_ + protocol (and specifically, the _blur_ effect). + + Default: _no_ + *dim-blend-towards* Which color to blend towards when "auto" dimming a color (see *dim0*..*dim7* above). One of *black* or *white*. Blending towards diff --git a/foot-features.c b/foot-features.c index f701533c..8e332517 100644 --- a/foot-features.c +++ b/foot-features.c @@ -28,6 +28,12 @@ const char version_and_features[] = " -toplevel-tag" #endif +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + " +blur" +#else + " -blur" +#endif + #if !defined(NDEBUG) " +assertions" #else diff --git a/meson.build b/meson.build index aa8342ab..b7377652 100644 --- a/meson.build +++ b/meson.build @@ -188,6 +188,10 @@ if (wayland_protocols.version().version_compare('>=1.43')) wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml'] add_project_arguments('-DHAVE_XDG_TOPLEVEL_TAG=1', language: 'c') endif +if (wayland_protocols.version().version_compare('>=1.45')) + wl_proto_xml += [wayland_protocols_datadir / 'staging/ext-background-effect/ext-background-effect-v1.xml'] + add_project_arguments('-DHAVE_EXT_BACKGROUND_EFFECT=1', language: 'c') +endif foreach prot : wl_proto_xml wl_proto_headers += custom_target( diff --git a/osc.c b/osc.c index 909fd484..4dc47172 100644 --- a/osc.c +++ b/osc.c @@ -1460,11 +1460,8 @@ osc_dispatch(struct terminal *term) case 11: term->colors.bg = color; - if (!have_alpha) { - alpha = term->colors.active_theme == COLOR_THEME_DARK - ? term->conf->colors_dark.alpha - : term->conf->colors_light.alpha; - } + if (!have_alpha) + alpha = term_theme_get(term)->alpha; const bool changed = term->colors.alpha != alpha; term->colors.alpha = alpha; @@ -1517,10 +1514,7 @@ osc_dispatch(struct terminal *term) case 104: { /* Reset Color Number 'c' (whole table if no parameter) */ - const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME_DARK - ? &term->conf->colors_dark - : &term->conf->colors_light; + const struct color_theme *theme = term_theme_get(term); if (string[0] == '\0') { LOG_DBG("resetting all colors"); @@ -1560,11 +1554,7 @@ osc_dispatch(struct terminal *term) case 110: /* Reset default text foreground color */ LOG_DBG("resetting foreground color"); - const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME_DARK - ? &term->conf->colors_dark - : &term->conf->colors_light; - + const struct color_theme *theme = term_theme_get(term); term->colors.fg = theme->fg; term_damage_color(term, COLOR_DEFAULT, 0); break; @@ -1572,11 +1562,7 @@ osc_dispatch(struct terminal *term) case 111: { /* Reset default text background color */ LOG_DBG("resetting background color"); - const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME_DARK - ? &term->conf->colors_dark - : &term->conf->colors_light; - + const struct color_theme *theme = term_theme_get(term); bool alpha_changed = term->colors.alpha != theme->alpha; term->colors.bg = theme->bg; @@ -1595,11 +1581,7 @@ osc_dispatch(struct terminal *term) case 112: { LOG_DBG("resetting cursor color"); - const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME_DARK - ? &term->conf->colors_dark - : &term->conf->colors_light; - + const struct color_theme *theme = term_theme_get(term); term->colors.cursor_fg = theme->cursor.text; term->colors.cursor_bg = theme->cursor.cursor; @@ -1615,11 +1597,7 @@ osc_dispatch(struct terminal *term) case 117: { LOG_DBG("resetting selection background color"); - const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME_DARK - ? &term->conf->colors_dark - : &term->conf->colors_light; - + const struct color_theme *theme = term_theme_get(term); term->colors.selection_bg = theme->selection_bg; break; } @@ -1627,11 +1605,7 @@ osc_dispatch(struct terminal *term) case 119: { LOG_DBG("resetting selection foreground color"); - const struct color_theme *theme = - term->colors.active_theme == COLOR_THEME_DARK - ? &term->conf->colors_dark - : &term->conf->colors_light; - + const struct color_theme *theme = term_theme_get(term); term->colors.selection_fg = theme->selection_fg; break; } diff --git a/render.c b/render.c index 627da5d6..c47133b3 100644 --- a/render.c +++ b/render.c @@ -312,9 +312,7 @@ color_dim(const struct terminal *term, uint32_t color) } } - const struct color_theme *theme = term->colors.active_theme == COLOR_THEME_DARK - ? &conf->colors_dark - : &conf->colors_light; + const struct color_theme *theme = term_theme_get(term); return color_blend_towards( color, diff --git a/terminal.c b/terminal.c index 8ce9bd4b..ac7922a7 100644 --- a/terminal.c +++ b/terminal.c @@ -4819,3 +4819,11 @@ term_theme_toggle(struct terminal *term) term_damage_margins(term); render_refresh(term); } + +const struct color_theme * +term_theme_get(const struct terminal *term) +{ + return term->colors.active_theme == COLOR_THEME_DARK + ? &term->conf->colors_dark + : &term->conf->colors_light; +} diff --git a/terminal.h b/terminal.h index fe39341d..5a2a57aa 100644 --- a/terminal.h +++ b/terminal.h @@ -997,6 +997,7 @@ void term_send_size_notification(struct terminal *term); void term_theme_switch_to_dark(struct terminal *term); void term_theme_switch_to_light(struct terminal *term); void term_theme_toggle(struct terminal *term); +const struct color_theme *term_theme_get(const struct terminal *term); static inline void term_reset_grapheme_state(struct terminal *term) { diff --git a/tests/test-config.c b/tests/test-config.c index f83a9beb..05c70990 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -774,6 +774,8 @@ test_section_colors_dark(void) &conf.colors_dark.table[i]); } + test_boolean(&ctx, &parse_section_colors, "blur", &conf.colors_dark.blur); + test_invalid_key(&ctx, &parse_section_colors_dark, "256"); /* TODO: alpha (float in range 0-1, converted to uint16_t) */ @@ -853,6 +855,8 @@ test_section_colors_light(void) &conf.colors_light.table[i]); } + test_boolean(&ctx, &parse_section_colors, "blur", &conf.colors_light.blur); + test_invalid_key(&ctx, &parse_section_colors_light, "256"); /* TODO: alpha (float in range 0-1, converted to uint16_t) */ diff --git a/wayland.c b/wayland.c index 59df991a..1d258213 100644 --- a/wayland.c +++ b/wayland.c @@ -1193,6 +1193,27 @@ static const struct zxdg_toplevel_decoration_v1_listener xdg_toplevel_decoration .configure = &xdg_toplevel_decoration_configure, }; +#if defined(HAVE_EXT_BACKGROUND_EFFECT) +static void +ext_background_capabilities( + void *data, + struct ext_background_effect_manager_v1 *ext_background_effect_manager_v1, + uint32_t flags) +{ + struct wayland *wayl = data; + + wayl->have_background_blur = + !!(flags & EXT_BACKGROUND_EFFECT_MANAGER_V1_CAPABILITY_BLUR); + + LOG_DBG("compositor supports background blur: %s", + wayl->have_background_blur ? "yes" : "no"); +} + +static const struct ext_background_effect_manager_v1_listener background_manager_listener = { + .capabilities = &ext_background_capabilities, +}; +#endif /* HAVE_EXT_BACKGROUND_EFFECT */ + static bool fdm_repeat(struct fdm *fdm, int fd, int events, void *data) { @@ -1555,6 +1576,20 @@ handle_global(void *data, struct wl_registry *registry, wayl->registry, name, &xdg_toplevel_tag_manager_v1_interface, required); } #endif +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + else if (streq(interface, ext_background_effect_manager_v1_interface.name)) { + const uint32_t required = 1; + if (!verify_iface_version(interface, version, required)) + return; + + wayl->background_effect_manager = wl_registry_bind( + wayl->registry, name, + &ext_background_effect_manager_v1_interface, required); + + ext_background_effect_manager_v1_add_listener( + wayl->background_effect_manager, &background_manager_listener, wayl); + } +#endif #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED else if (streq(interface, zwp_text_input_manager_v3_interface.name)) { @@ -1569,6 +1604,7 @@ handle_global(void *data, struct wl_registry *registry, seat_add_text_input(&it->item); } #endif + } static void @@ -1882,6 +1918,10 @@ wayl_destroy(struct wayland *wayl) if (wayl->toplevel_tag_manager != NULL) xdg_toplevel_tag_manager_v1_destroy(wayl->toplevel_tag_manager); #endif +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + if (wayl->background_effect_manager != NULL) + ext_background_effect_manager_v1_destroy(wayl->background_effect_manager); +#endif if (wayl->color_management.img_description != NULL) wp_image_description_v1_destroy(wayl->color_management.img_description); @@ -1986,8 +2026,6 @@ wayl_win_init(struct terminal *term, const char *token) goto out; } - wayl_win_alpha_changed(win); - wl_surface_add_listener(win->surface.surf, &surface_listener, win); if (wayl->fractional_scale_manager != NULL && wayl->viewporter != NULL) { @@ -2000,6 +2038,16 @@ wayl_win_init(struct terminal *term, const char *token) win->fractional_scale, &fractional_scale_listener, win); } +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + if (wayl->background_effect_manager != NULL) { + win->surface.background_effect = + ext_background_effect_manager_v1_get_background_effect( + wayl->background_effect_manager, win->surface.surf); + } +#endif + + wayl_win_alpha_changed(win); + 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); @@ -2206,7 +2254,12 @@ wayl_win_destroy(struct wl_window *win) free(it->item); tll_remove(win->xdg_tokens, it); -} + } + +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + if (win->surface.background_effect != NULL) + ext_background_effect_surface_v1_destroy(win->surface.background_effect); +#endif if (win->surface.color_management != NULL) wp_color_management_surface_v1_destroy(win->surface.color_management); @@ -2446,16 +2499,16 @@ void wayl_win_alpha_changed(struct wl_window *win) { struct terminal *term = win->term; + struct wayland *wayl = term->wl; /* * When fullscreened, transparency is disabled (see render.c). * Update the opaque region to match. */ - bool is_opaque = term->colors.alpha == 0xffff || win->is_fullscreen; + const bool is_opaque = term->colors.alpha == 0xffff || win->is_fullscreen; if (is_opaque) { - struct wl_region *region = wl_compositor_create_region( - term->wl->compositor); + struct wl_region *region = wl_compositor_create_region(wayl->compositor); if (region != NULL) { wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); @@ -2464,6 +2517,38 @@ wayl_win_alpha_changed(struct wl_window *win) } } else wl_surface_set_opaque_region(win->surface.surf, NULL); + +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + if (term_theme_get(term)->blur) { + if (wayl->have_background_blur) { + xassert(win->surface.background_effect != NULL); + + if (is_opaque) { + /* No transparency, disable blur */ + LOG_DBG("disabling background blur"); + ext_background_effect_surface_v1_set_blur_region( + win->surface.background_effect, NULL); + } else { + /* We have transparency, enable blur if user has enabled it */ + struct wl_region *region = wl_compositor_create_region(wayl->compositor); + if (region != NULL) { + LOG_DBG("enabling background blur"); + + wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX); + ext_background_effect_surface_v1_set_blur_region( + win->surface.background_effect, region); + wl_region_destroy(region); + } + } + } else { + static bool have_warned = false; + if (!have_warned) { + LOG_WARN("background blur requested, but compositor does not support it"); + have_warned = true; + } + } + } +#endif /* HAVE_EXT_BACKGROUND_EFFECT */ } static void diff --git a/wayland.h b/wayland.h index 6247875a..9cbd1023 100644 --- a/wayland.h +++ b/wayland.h @@ -26,6 +26,9 @@ #if defined(HAVE_XDG_TOPLEVEL_TAG) #include #endif +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + #include +#endif #include #include @@ -62,6 +65,10 @@ struct wayl_surface { struct wl_surface *surf; struct wp_viewport *viewport; struct wp_color_management_surface_v1 *color_management; + +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + struct ext_background_effect_surface_v1 *background_effect; +#endif }; struct wayl_sub_surface { @@ -490,6 +497,10 @@ struct wayland { #if defined(HAVE_XDG_TOPLEVEL_TAG) struct xdg_toplevel_tag_manager_v1 *toplevel_tag_manager; #endif +#if defined(HAVE_EXT_BACKGROUND_EFFECT) + struct ext_background_effect_manager_v1 *background_effect_manager; + bool have_background_blur; +#endif #if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED struct zwp_text_input_manager_v3 *text_input_manager; From 046898f1b8e89b7ad1677905cc468daa98546b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Mar 2026 09:42:31 +0100 Subject: [PATCH 339/353] test: config: blur: fix test failure; use the correct parsing function --- tests/test-config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test-config.c b/tests/test-config.c index 05c70990..9774cba9 100644 --- a/tests/test-config.c +++ b/tests/test-config.c @@ -774,7 +774,7 @@ test_section_colors_dark(void) &conf.colors_dark.table[i]); } - test_boolean(&ctx, &parse_section_colors, "blur", &conf.colors_dark.blur); + test_boolean(&ctx, &parse_section_colors_dark, "blur", &conf.colors_dark.blur); test_invalid_key(&ctx, &parse_section_colors_dark, "256"); @@ -855,7 +855,7 @@ test_section_colors_light(void) &conf.colors_light.table[i]); } - test_boolean(&ctx, &parse_section_colors, "blur", &conf.colors_light.blur); + test_boolean(&ctx, &parse_section_colors_light, "blur", &conf.colors_light.blur); test_invalid_key(&ctx, &parse_section_colors_light, "256"); From e48178bec3c5fd6bd1b6aa25c28cce2c9ff6874e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Mar 2026 11:48:14 +0100 Subject: [PATCH 340/353] readme: update sixel screenshot Closes #2215 --- README.md | 2 +- doc/sixel-tux-foot.png | Bin 0 -> 297553 bytes doc/sixel-wow.png | Bin 119970 -> 0 bytes doc/tux-foot-ok.png | Bin 0 -> 404008 bytes 4 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 doc/sixel-tux-foot.png delete mode 100644 doc/sixel-wow.png create mode 100644 doc/tux-foot-ok.png diff --git a/README.md b/README.md index 7ee771ba..985c7e33 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator. * [Synchronized Updates](https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/2) support * [Sixel image support](https://en.wikipedia.org/wiki/Sixel) - ![wow](doc/sixel-wow.png "Sixel screenshot") + ![tux-with-foot](doc/sixel-tux-foot.png "Sixel screenshot") # Installing diff --git a/doc/sixel-tux-foot.png b/doc/sixel-tux-foot.png new file mode 100644 index 0000000000000000000000000000000000000000..ce30fe8fff52aa58817ded0ddf44350cea73d87d GIT binary patch literal 297553 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�__<#$&4>} z{jHX!Z)E-VCOo!=ajl#@cikDp836yguhd@^zUO&*>QaaNCmqUt6|<`!nYSKo-uFRL z{bo^d-MsK?yW_n!hBYoMj@cHm{Ni4Y$pWz#gVmn~+|jzgqyFUAjCqHjESA5lE*D_B z=hVFHPo^6BCXWto6$yB#e9`Lc*|wCJ7v{%ulRt(O$jdof?NX=>Tcud}FBS6t1^%IbP{OK?|t+B~UVtF6v~Sx1AW z^p8iYwM!@ zh1um?l6MwH9ly_5oxyt6{O}ddjq@f7)E91Daji2zO>pVXv?*z;xp~wcZP^{Qd|^Oq zo&EI%VH$3`wL{qsbBoA(aca!yUB>C3y~WTybl*PXIm_ACNBiks+Zh(f+Gei9wx>B8|*c|0Q zt(kO?e@2GNrOo9_@4gIB5fR(A{cycM_nsLc&%HPL2X;%{d45Nxzh8fC*^l@07!ALD z%xHOZ=RObfr-><-nBrWD{YtxRF6p!$=UZgG{{OTkE@Z@4eyJ%IGgvuz3TT<|88=4zxtf5?hF&3go-dWmgo;<8B2L2xKfN; z=XK`4Vo3_xnY(H3mQ#yzSEPwN<>hx)6Y6@j@TE%OT%VqqI^~B-4^7&-@PVpFpNF-i z`g)5ODYre3r+nIaYr3`C?*p<{vi1^Huyh&y7kobWD{p(x>Gi*be(iR*o!00yORJZa zanJWRdY{^hCT}};O=E7y#hlj2m3*6n|0v7oo`zi+hhZa+J=EwA#C6v$RiPe6~nsr@^sc!b4$NM+Ej+^!P<}J(XOQg1E zo6NeBxPi%yXTgF!7m^ZWj29>Yo^v9BB4mw$@aU3DG&FELR@w5c7<WpVfGyTfSHZCU6N4@u?%rDOqicMYpX0&Y%Bir?_=0n^A$Haxqq}i`1|fb z?Xzte>Q8?fxe66>ecW&R$6SSRz1XTN31x{ovzL8r=6iPEfA`CG*6+6@-}u#jcgtV? z{ma$u|8jmaK8DmLoA&Sh-X-RL@7zn#yzu+Dd%j#k__|FhvG0Pe{`tMZ{*Z*qTEA-r z31&a%T{$N6rN%g1usyq1@0xwzKbw#$xwk8?Ovx6%+4KG8di|y0{op%Z{{Qv+zu)?A z@0QfpQ)al;J=wqC&%gT5&<;PG)>{#w_N|7>vM z|EH2E>Q@XPG-muMK+Tpt7(NZSMh4~$`uZJDnxUKpB zs(btGg*IjTtyv)UJvjKq(k8#RYuDZ1zTJNRy9F=KzcXccIz!ns@z{;nzlQA{ChPv~ z72rK~b^ZPycar)(J!Z^!+npq9xZmc=b1}iGcR9{#{XMwGQ)6RU02sbYgJp^fePybc_=JWmK-TVIub<{GcD>3)HR@Mq_itYc*pe71OE z!$sDJbz2v0$}f2|CG{KE8of7@H-55;oFS~1-59yUl5IP0@;6B@wHDUZ2F%OiD0!e-=*SxSN>s~^6YV#lJ{a^_EUyu`rBum?EUz|dGX^zn+}OY`erho zTfl$AJyp9<=lt}8p7Z9X8c!2y6XsdJX>Oi@ioyI-30d_L<#i=9f^6i!dQ34|tesif zGCw_UirdVmc1bNa&z_Ok{K!=5B(Jx$DSuyjBAc9G+O^c)gRdS%_$;oJ$x3=NOX2m# zY^JrP2}M`eM9oZi(e!&o=VtD%t1{8%w#Q5*R)0BLb*LrsYxw1q4V%|>7OT(OrnBLk z_Nx-Pz$~|h8pTd}>&Guo@-53ka z_a^HOTk71=IiS^a`onwOkM{-7Y+$VTXx_emY0MIrk53HxtsIxzyyPso-LJPa_Hyd) z!jk9tW|tM~KPy=MJHUxFzMJ@K|7-323!a{`n`s_j^6%o73p}5UTC@6+vd*ua+{4)& zQa$DTC({`j0k6XBb4(P6n&axTx0Jnj2JbK9;BLVf>7ggsVB_f0Mm=@)(qE zz2$RN`qP?pX4>hmtXT#PnZ^#Y(hppER>v@>JnS`(%a59+YHRc&*1Byz-Qt+=j+0?Q z-GrB~dS=XRJIr|Fl8M3YRXkfX8G2U;JDj<$5S6!QkNX)P!3>USgW`(|1r5Dft=V;W zKQK)QUd8fYrJ=FzgJ4E&pXRH7BF?5WR(;vSbs%-Q&E?15s~!Y4%D9{@z36wgz_6U* z{^}x2E0x1M4_7h2EI4+Qy%T4I>=8#3!ORb@J_*RqkYA?Kyp-d9y2^g1FQS{XxEj{{ddKvD zNkqHgs;!Iff{SjBtLII&^;y%qbMxspk0N|mUhR?m9`HBx%J*w__3Ss8w(#jssTLEt zQX^@%H)~sNv-`&g6_3!J8V-_g@+aR<{lcuEbN{sV)n@K_{k!pFMfyCg zKij2E?e_gliHYHf>gChd5?t%i@?t^g<1G&zk6-w{|0{2HTYv8BWoJvy?erJi##mSQ z4}L7beV^~)ch|L@6i&2u?we=56s8BXY2nenvyn9hUNhO@0_ zjBcMuq3eYpya3?nJ!W$XmiiHXh{j&_N^Zsk( z{2XI_?#$ac4Ur9;4MIO+n&z@bi2nC7R$~zj+R&Wc@-Q{iK!MStErI_4_ra}Gy__4k zXV@~tH%K<9OxdWnT1Wd=tG-S{PacDr1-k${ThQN}gBD?rB8``I#4NwQh<6b~vt~&YBKHB1p$7ZCf#4iQ@2Xs=~f8UIp>3l8xW(;@9T!rv;_t)u3>^KlUGqoX*A(CiE0wE_S4+GEQMl((Up?khkv#RwM>WbHGaj5p5 zZSNZ$741^6lpD|K1fJOW%84Y}c1p`@bH`iS~IuZ|lT==a>J#dPn-X#l3@b z#LcxnZjbxa?j~jAq<3n?l3;Gjf7@(lE~s1VbHs36_tW5S7uoV=I9bTKNs1}I@VcXy zRNA`e)LxG@OV>@;QgO<0Up2Rtp>ws1v}`@+CBqp-YcylthfUx5Fg>xws8e~WM2u{9 zS47~K%w(^V-PW-iP0oHiwqRXJ=dZJy-?(h*`N8FN)UBt~`q<10#}lWzBQn-lKQei^ zt!snNkwEX8%dSWWRUP>$bLZ$!NhwFMk8?7zzliVgJ!E=!i}U}pD=tPGH5INoaNIRx z6Ys)5yz@9u*{6CHitS60ZFD^))wtxG?)l)W7p6u${!rEG&Gqt=V}$&Xz{6bHg}cAa z<+cC+Fwwg2$d^~u5jQvNnEIUib>)sLfg$%F78~^)TbR7ju}J4}qWt^CM}EKhbGR_0%qA`i%R1CEE6@i=vmVx$!8X(~37u zZhOwNYuD21rWd|4*dKG{#=jf${|LT)BmLy{>RnbZ8gKtQwPoSr-}mh!T8etz4jOy3 zJ~Xjkd?rG9Y4jh>EYbPc7p7Z2kH1iT0up zgHwhYeO*6$B9F7AZeqQ+ZDy&z${C5=x6kxFbD!OP@yaK->Tyw2=m#fxDQoX3ddpVo zhOU$RZ?68Mw&=cn`~8RYnGfdso>c05xz_*1-nUWncEri>>fN<`Hdnt|zCQMY#PcNw z3{PBZ=vrkMDz|Qzc~;<8rdyRqv`p8$VdmL7dAbYJ(!<}q8J}r=dj9Z(^&h_f8yn>% z+3){)cK(8LfBw_th|2vR?|%*5$@j-FK5+Y;L@2u@f+O(AHC&pru+VpyB0_=%aNq;7Vnzv+0}~jzgJ0 zBJx!~PUNst`qOzqooQO$@y9<-PPryg$UB9P~x z*zpxxxfYsE?%$Zeoha6%V;`i#Xxx;ycqaENNoN5!>nn!eaBAA+)u2+qTaWQiU=%9<$L&?KZ)^rKD*=e` z<@!>Yso>!M*bXL-ciEroj|H6;XK@v*OI}nlscP>U9>eF+$*E#h!p9C>m}xXaO8Vu` zgF3Ze^Z(7s`+F_ia--;^sJkB~YVSQ4uk`<)D4XwnbcmkJiFD-?N|k`O$1^>%&@ zSqFhhk54|`vQFFaSyg#y!necS z;+JGxvRIuI96HyPX?@{kJ@%;jFYCp>S5BF*J)2^z`-=6G{I(os$@31qr5UD9tg~e6 z+V4NKFZ*!6>a^^afBm%=&%Hin|3e`5f4ls$hc)-2s(!Px&z$JCXTc7&>?VQRk9J39 zB}%A>B}BA3CR`{iKPX)5#?Hf_tjADnhTS9vl zw8rMWPA}K{{k-H@kz%dA+Z&(dKek_;+HrBi&%I8DtV`ME?Tx5(-0oV}9EH6gp_P62au4_2$qzZ+j(eP(^}UACxs zIsxg|HEY)G-renIzqdQ+Xx(QWp)E;gHLqNpxT41V&iC^V%YXDs{b>(<(L3qWTNh`w z(#-H|dj>v{j|OX>IqCMq9jQ4nC+_O{_xpse1+d6*c-T)dSozc958wX7^*ImPwcly$ z{c&h${PXOvz1jZ{>z*ZZ>=Wo);&O38{z4(gushR_?uza{GV5q<&(u}xoOiUEhUn~j zSY#+Odu7bjr!w!F8`kLVoOPs%TgWk-<^GHyhVJZDQ?xHli&Nw<-XM`#e*MGOb=O$9 zcRk9R^@UY2;(o*oQkbg0L^|2kf%nvK>aO{$(KXS(N*puha zxi33Mo-ewV{!Cpg!N$L^{@S#lO1D2Y&Z(2X6hAF>y1B4qd2GF)$JQpt-4flUVY{8% zrwZPB#CK`hJq<4In}NxXzb~399XOBik)=YdSJBpY(`=^pTsm}Ywe-QQSHndTbpwk$ z_NXq1IZzrS`f%ID@-zlEg(c~U4Mo<) zSMKK*%qTYs_qZH!uR1)zb*=jY;e!QQilOndS1MRu{+7sfK6@Q&`Wbzl3*51lOVd03 zmj`+m1VfW|^(?zqEV(Z)ar@n`l86Nb-o^^ke$ozws;LxVZQB+djMc zW3Bg#+K=wxo6QzZdl}>N_!@t-TKw|LxNJe4s~6-p>@TnPejsmfTkXpFt4$hRCA=Tv zmn9|3*PkhfO`cjO11?Q|efB~d2R0= z{Rc|R#HTIYuzSXSs3+41eW>{?e&}E>oL^mf%^YXj1NjG_S_kFtKycT%VB&f0B0Z=Uy$IY$??Xiq6=5@`MrvxDvMt+rW*J7&A?$+3z|;$Ev35j)#* z-?hWtMQq%U4Y#doRw!_?czgbM&xW8yi+0-Qu{{2}PgLws=B@H{v4_)6iYK{l5PcZl zBG0k6M(=&({SD{;{>n9WTXA$xL4CZU=Ug{~TQg6FMoujM{ZvA#dv}jQVA@0Tv~%D4 zR4a2+dmVxnN6hQj;Bx5{`1+@kcgh4!rj;B!l8#O-+)}-{hx2-|V9DW+2a5XpIIkJ* z4lLXgc-~$|Bs=lC+R3bs=T)^oJNy6G`=FdzNT2iP-h|y+QRBc*Jk;-*e9_1)Ex7!mD_gw{Hs!X zI?(R8;r__a`C9YMMe_5PKe{mCuAXo2MTc|M>6c=&&)AB32cC;~9lSQPy02ea`mI=4 zqCm_N#tV9ZS?@08YE+iWRT zcacB3d1Gtpb^G>xTenIuZM1Wf-#b%KyTbX$)4vlLZ{@XAiQPEPu8|xUd4xUv@0qI# z5gLb2uHUjjx^MEd=gNA|Hl0h#{az5pJ@=E((a^|w3&o}^PO5fg;E(RzT3t2elQZ|- zs`%*NAMbU1a*dhoIBAQ{sZHAN`yz5$RX$3eb6#jLNAiQ-lZ`L#{p)iyK7OaJt3NfP zKp~msiOl)z^8MwqO|L>Wt}-rUpLc%A^XPtsUQE5Tc3%5T@K2~+>mHqON>RE>w zr)@iM)gbL3@7pC?e%{@(UV!_4bHfjtz+_QE90$Ia}U4&VExBk{a@0u`Ej)`kLQSm8#K%Re4+lL>wB#K%_c!V z-sJ1kKkC}GF^YVDR`4xtFKZ_QPvg$Z+fzLM==CQjeCHLv@r(7KxZc?f-yTUs%hqc< z2|Y?yR8X!I;+ElVes^+`g9rDz#Oo=7leSD)#44{c`_=L+MzuLI+@GD&GE@|tcvO?( zCYET;KfhtY>|n9CS2L4MrM)*#IDK;YpCe8dZNFDWWN!>pTH|y=Wc50Zecoq3YtQQc zA@KOK#OI*8hNP2A_srLlcxHC-oRh)2*J~qgDCT(1Uvy@|#)6#I#J6&~VTo7Sg=T-W zYDwy7l)nbhP3H(t+hKCrs#{4(=S>%DY5D(zP_w56OdB_R zz4~w68ABt>PqSCV z%@X$3_mA^_TXnF+Y~3dD=EUH@+1V!^EZr&?X!oe<;S%}34;H=SopIB7-_wUBcN5=s zoYOG7%FKJFQ0zykR9-tcL5lx7f49c`?@9IjImazuH~E&_Hs5#bcXQpzQn9LDhL#^e zZ!d9a>&H~6E}Yu;oOimikhlIFqvBJSv^VZfWL#Qnlb&2ssdIm7aQU#d3Q@ zWeA0*DQ;A1dw+dt_mM?9(~>7d%$hhMW36D!gJV3~Q_n8-@P^BQJtgnzN>+!G(3N*RIqqFGKw%|KWnWn**;RQJFsC*)+I52F)in&%m)%r z=FbjlV7a>CRu*F<_o_#EJl(m6G_2WHSYL?OZM3v`7WC>X%Tpd#;HFWA9k z6ej*C_sZT?v#Yk*cr^b$o^f*%->r`yu1r&bBn;*D{~IE!zTf+q?x@?azap{o-mDAG z{F*)w*;S=jch6ty@G5Kd*%cof92cGBIDhch?CUrtRZ|jmi|E}+CHrr$SY_6QY=Dg#K(}|n5kJ4rJo=5DM*b=6YqS7Xy zQ(Dog;%&^a`Q(qA*{&}HU#}Cha;e}ve(%)2ZJSV`mzQ z3+`(%xPG-$N%w8cnM1llRxdXh6okqJPjj7fTAF*!4pU{#P{aG@nGDzL7m&)e$<=g? zeYr$5?x@#l))*C~ONaDtvD}&QcSg*Yc8i3FT`BTM3=bQ=|F_}w<}IFEQX(S6!=L@- zo?dju@Erd$Ym3vy8G9-EB&iqtT!lBC`1H-M+`did6A9AWi z3FMj|3QS%%zpO7*=j+;D)m!hmX9@Tv+UDQc+Pf)DZ>OR%i{u^4#XTC5iRyu8iZZ1+ zRwRCZuv17UUFm{c-t^?RY&z4@B0_%Nx|Fv$?pV>r4ZW<_muh}u302=+zlkl#qQqkuN?H<-Y zS@`?LZuaxvTigHZ{#aXhzrOwc!};bv(kGvIwV>tRilfgxTYat<`RSKUEpuA_U|y5Z zLY{=6mYFNxTrd6fd(|b8AD%8yQdD}lqkls(p!}`nBc}wQoec7_N z?aYGzUt0BVu37i@*dcW$vEyY@$HHRf1iyJ?b*wa1b)WdVa|#jPR2FYeDQ{by);+(p z%)nS?MIe{Z^t1^nVPf60RRk^_S|gLadVX0p-|m@hx)ry)Gzxb=I<-nT%~Sfq%GF^T zxXm{lnkxDFKxy9NOzjWL9_?PKemU)$!O7n`DvNxA%cko}FPZG$sBq#?YNXexgtJ>y z#l@mJcZxoY*sXZ`Nyw?Cv(|2S%)BM}#+lj!-hNx^3(UXrIxw8Q5D+h)`e&1~_1`6K z0wG3r!u;lm9JME|b^mnU;ixpPapS3ch9(6Tg(>}xGAcS17HzDaMi;z{=I^a$-}^me z=Mt~BxWb!Ls}-*JPCfWNVTD}ygO^53@^506dH00$XTQ(!ye9Tuf07obVxFkj0nxe7 zWaIXnQVW@+;-%0RZu;KKp&|6-WUb~3u~V{#HM8G}c&~^ES$wDBtK~cU6i!}_2E~Jy z&#^AiSuUWH*ts<~wEp3lAl*Dl#+8Q%Ae$|ip_iFa9J^t^n{(sI~t9oaKuHXMa z<63_X-2bxc@Y{PahwsF7FN->`s%&4pxe2R~gRT_k?*Qdr-ETI#6_{!~keB~{=J$!H zHgU~YiEK?fAtN2HKHRVP>|64Ad40yrH(Os{zuav9f2n9itJhDRucEfy%8x7pz1)+6 z9TOjhCsnnoG#AfFZZ6hYp!itlk+rq-<~dKruU$TPC*VMO~ARN_?rUd7rK`Rc}-1Uy^{ba}qb z`uY4#@r+&r)5#HMcD~3~t99W#TC8hw&iqe-YwGocBOKDNwM2yO=BE0snxgY9cu%tJ z(q!f-Ci|BkI`pm~mH*?yyQ#`8Th~0?7olO~G^g~vC>Lkrl=okC=9=ovNHo9QpgMKR z?))_(65e;3Ro8_cq#ypck*}WBK4#w1IVbo3`C9trp0@sj zT1Z*^v$bB&OU(2lj|PLqp*Ok=ISeKYEevim51+MnzV}zN-jU~o%k?0oE9sZ`eSZnh zFO-yLyckuyK(1)p;)^r>UX2Kl3DPr|UZdw(Xw z{$~WwoaA<3>L#}nDLRq06ECc?H+Ncl?3T1XU-8G8tac|vNCRQ}N zSApkYS?;O~mK&P{^J*80iA6+2yK3lFKTY0XpEHl`PV4sX(yM*r`MEdw=iWRsZL99> zYKzTUYnDGe;_759x_~e2#;WK0G#ZVw>VEB*cD2+hrt#n3-=)8IZkr^R^(X1+wReXU zc5ohk#g)B!+lS5%s!UV-cBNbi+t;wQuXW)Lj{JSD(}a}*k6N8(5@T61K{R0Zy{eNl zt_AEeae4S7cxL~OyIz~r1V0p=c+pq(f?F(@YuEi{pXzLKvUlemcS_UnoBHR>haA(h z6A!(QG-CfW^+ueSmZHP6y=57XKe#{651yx1`|Q5r@3YV{HU7u*hO{U5Vx~&n_ISHU zbwcu6?{$BVGS@vdH4$Wtf3HzrxaigEg}<5Yo(e|wtw@Oq)ZD*z{>~!Ff*-PV?f+jK zyuZDe`|q{tJg$`NeNTGxx8N5wfdg&5u8|Wx@K!zc(AajW`Oo2&9$9hQ#K19KVxwJ!L|f|FnH2KI?YaoUVKu zr28~gAfM;b#@#v_X8l$;!qr*SzUJ$`Mwti)oiS0+*<0c^zTc+plryZ*V$zzH?pKtM~7FO;*4Dmz65P zty6I2@6!OSyj67q*LvinW`;B<9)k#+18`ypY#04`ZMZeEx?TI-iB0-qiGq1sLoIY3+%o-Y{M%{8)kABqu!tQLKa{5vzOwn&w5*Sl z^uO{4n6>)|$LZ*>++34b)?W5Cit$^5@dvq{go+g{o}Is0I-|EFOfE81n0vz`$>sFX zn1s`BdlWi88G5j=PP*~H#rUITO!wkFlk5FFZauC_oMSOMf3*011XyGlM_v4ZcFVgxVUcpLl@5|k5#8Oy`C)A zp_IL?Xm(I3+XJc3g5RSy{%T`g9`$Z>FjsVN&+`xaOFrB$|Nd5{{{F@tt#yC9^*>s| zinsq8QuFIRx`&51Ew8=Qohg1i-X=0v1v=K5YX7#~-m*tGLl7>z8KW{Q`sk>{n$~CcjeM<4Vi2A*+xoaJh zS~l}mJFS?q;Ei~lZ_IQ~m%TX@#=MKywxB44HiM|E8FT&nU+Y~z6w47`b< zLW!LZx-4Q&^7Vf1kbkGYJuoRjJ$2JDrrVq68h=l!U$MUZvGUTEo`{6Qhn_B6(QPFz z@-&LuO-%3UBoD48<)vW-nlq+NvxrtwO$@AFIlnDwuFjp%)Z3vckC}Ltm%*NJ(j^X4Q^l{iumw6Gv(!3q~gGr7rHV(V5taWEBPds+E1S0 zUc&vDO3>g+6QT}d)928m?^yd%UW?ixk+^fzn-pG$;xQD z&CM?0w%RL2a7*p~q~rge&;O_R_we(*5%-S9u6}Xu{~xuvwcl=BTCjE8ZWq36s+MfW74*_l6gNI&yE zbxy~u=24w;b#ID|c5d19>2FqUxbL5pI^)1$#Wm+zS;KapZ&KJ&wq#Ff<5D40pXut{ z$BN$Vj$Hl8K!`c2d*{5qm-Fh1M4!osyyNaMy<^F|aar&cWnSZt!DpA|DYmXymU`|X zsCRqn=e&7xW%Wn)o7(SwyFN3w{8a6K*}9@1&&B^8j#?g`{*mi z+?aJ)`OK}xf3^RMF8Z{HD;2!aFS~I>?wrN4=Q=Iz^5@l7%gwtQ1rC?`D-Y_w=KrnV z^MBguy4nAJt+#hIR@D6?c515C#$?qifxZQM@|{}ro2w7>w0|;R;k3?6 zZm+WOPdoMP-GX^J^H}dc-1j!8aOs^oDWlto#eQB7UwkrapS8&Ac*h4xv9xp}^+Mrm zPb(Vu^A2c8D!n|n3B0t^0Ni& zV%ZHIlCSO7q)b|Cro-{V$imEU0(iWqoo7GCUWM5QZ-PRhy*}=teU8)nBmL{YiUlqC zU!Y)g!hU}4$}1DOX6#j#3sLxB^{3!T#LUyBv)*nvx#vcx3IAH-2g~D(D`qVc-*Iiu z+O)&qFkZ~`|J+8=Ep>l?mK0p^|9h<9*6gzZ=Y^v+tbdq%{UW+z`}Nh|XMLS{B*M~L zgSGp{Nf*P@hBJk}pL#8zc;s5L;@a>NHbI7)3e?4}C#YQ2+FBOLt)r39%4@jS%&oua zWX_v?X`4J#KYjI#ZY;^1QeeHWylazsXmPjF(?^nW_N(3>s5LSN4?px<)cx6Ad)_wA zXm$4eziz)?xUVmTbXd}VMC;rCu-e;i|IZ?y|J2?{|2<+iUwcI>@UNY=Y*Cm)ap-;1 zTdc=eu5v7zW|{f6EsuE<|DtJThd|z|tI~Y*oZsoX^v_w-muPuMZ-08@(yeBx^*a@} zok=Uq(P2pFnXo}~^X`7u8yg$vwwnsSf2cP1h~}xK5*s(nob=pFhqG^0%MF2It)Bdl zu&{-*^rnB^Hd)0okeS1 z^}pd7qj=DX*`9p+55NDg|H#MW-KpCJ6WD*fT;BiR(6;dZ-z(NLHafit2|L%U``#$_ zw{z?%H7&-eSDbWp-z-@w>%CUA?CYzmjZ!OSab61(Ht1g75-Xb+>8So_-VFX@zc;cT ze^dNP{gXsckL$^I39{#<&J-z3UTyU(#q!}LwH1<4eJ3nlKX&w-ylU5yO8vz-TKUhW zukW2-bLQIB;9p^%%fRiMrLFt78l?PRD|6@XobP`h9J~V=0OkI-bAjYk9j*yRg~etY z{!QDpMdYE~0#AQD{GVU4g0M_mp@(ieHJ(qd}jZ@Zmx?ze^2oL z&Hlmh_0y`#_-5=oerDTz?_URMc_nN3mp52muPy%euw>R|?Ztenrp%6fRsY)PWayQ! z6SLk&-%C?*^Su(3tiD^?JvCBP(fm}EP(kS&rJX&#QOs^P(i75Mvv_Q`rERNxr?@)! zMpsDc)UFqx@iE!DcKQ45$K`%%{{OwV?rl-kgKhiQUD1Lh(&axJ>h*N?e%)<8eR1uf z!zzu(s|s)S#C@Bg%dIB%_POm#Gx20=ZUpQ?l#&7PMx_9c1*eNF* zW%84EFW)$&YW0>4y{+L_!daJ2pZ)cuw6w=6hu)XMJ4*aGXWa>yGdmz<+vO;cuKey@ zas~faC+2c)Q9u7nIIU=F>(Ol=UuUqbF*+G=*1vYm@nTS1-G6A$^W*)F*LmlrHst){ zzxVC?m5Y~BApYl;Y?0edyR;AquW~;+20&eLYTv>3yB^A5CR7e4KLo*Gk>TGY<*B-o4_2`IGCLR_!nk@{m?sR`KQh zKl2@W%f6)C0p-w<-ck}W=wMwR~Dz_stVkh?%E=xSJb=evFH=Qm`*?Vs3EId}F@Nu0&?DC-K*tM_c zCCPdBp4QH5atY;33$iPo_35tAVV{L|>}iu8BuOM1PBi>6&+zMk#6 zQ>d|CZ^geiXCqf-h#tDd?i%zXB3*FxRk4)ytIx!LFwZ}&vUdIUZ;bqRUfVOo38?t2 zUD)}+P^sq7(}h>AteS8s23$AAoB#94=a>KfL|XIqW#?9>dB2QVlkdtTcfZY3zO%^0 z-+$RmuI9Z{WKUhwZIN3ey-{zca;?Ma{acoLs5WzY&e|cH6cc(jdD6mZoPkOtSq$r;`sltIDg;UTd!Dk+b@N=>s098|HN=ZfYs;rEuQt88X6;l z6XaU1EWMPQddER9b?XkBeyPvb^)F3%xewIK(KvSgKgZJvbImh2ZV zZ#g&J`o(qWrrlwmxU)MpXs)}-qWjOZvy$7O-*kq<+jGlxg&J9c75>NNGG6?c_e&~6 z>)9nx>Z)u1A90NT^f%}KrMuV4)}8NPx4d*^Y2W`s&?tJ#{|%gbttL+Oco;f4gz?10 zf435Sop(+$i@siZ$cde4cIfSu8d@)`-W<-`eyV-%wD_HBcb6Z2|3RMjN9mk5e#!^F zy*o97|DVCvFW)}z6-fHc);&ih^LV~dOpM8q^d2(Z6PCI76mzwO&-Sz=RuzzprM!#4RAl z%q{qVxxYei(V2%^kBJ%Agj#=8?XPeEwJz^}cs%dYy%`%1h~1bv<5YBJ`LbRqb~ejy z!#f?^ZV_)^zhD0E-(MN!ZWk|)btM+rXRm3?BzMoa8M0e_htrE^vo^E^MorhNIypB> zz&NDCIQZknz@CUHx96mF97?Uu3KVgU2vs&Rc^2|K+pyu#7Tu+B**n3#T~JZ_oDY<| zcz<0q&)eX%{&BrbU3-1qgZ!$yz9IT&t*VP7nh)6BuG?n7+u&)`-kHDclEDe*jWZ9P zIn>b^*SuvGXNrdQ7M3itlZX4SC}>>j)+nC+E|p{Z>ZOjL$c*~2J@@TW>$)GoQTtal z@7iVa`myKhT^jS=%@M5IA$oZ3j`jN!&WWzk{&?Wu+BE5O?lm0ijaM34Pky~w_weG@ z$P|;fd()Q2AUh_E({`9LdSTD_kNtm&j-5GWZ@& zXk?tEvp8e(GSl$bZOx04?>?!M)fP+R*?M|zz>hfwSvx?hOd!3@FF%f6;@ksCOk`V zDcCBfEnu@&&gH=JyYUVmyVt)m4zqq>m2e2uR*w4N`@jBE`2D5-cYNJ9OK-2zpM$35 z2FpK~Kik=-vu2J!N+oZa#A&P0**Aq^_ANR0cGK1D$c)}Z-XLuYKeOz0YyJ9z)ut=V zWjS^1eA`-1yTuEW*D7uO6YUkb&l93gq^XXF15*ItUQN_@rVvrb0KV9mOh ztaDEJ+urfBH?ekGxOCe#r|UCTnVvULGtK|4BzW~{JgChSEmu27@9*FGS(kdh2V4$0 zv|)$D>r2)G8y>qk=<2@tl6B(P-XI%YUAN0(@0au~t&8kjnRIZ)Bn_4`JWs2e&$Qn1 zlMXjy&D_3w#U`mc$F9Fhp0nb|rWHT7XsmYKt5*jt%lZC$%m0tH{}Xp~@6YJ#mv;r5 zeJnlx-{MbbuIxYMru|<(-P~<{+|TCYk^L9M=baAm{%sypt|hCXyYR;oi>L0=D`sRo zul23z;WRV(Jb!QAInlE%kB=E0*>_WyyXo%~qkFbbJim8d+_66+(rb!vO|STZ%tP;r z?@K7lt@d#^_U*aS^8Myvv>O5iNA2*&)?r0bj`LERA+eXkT>3cWKkg~e~bNB z?zeTm|L-4jQLUA>Wp<5Fv##AwxNc2%-tGA+s^Y*N|KlG`_qq3Szlzv$Vs^jMm-A1yLmMRJ zKlHJ5)q1?(MBM&wnjcW&fcq zyn3>!u#c{0pz}fI$@x)-x>*Ef6j)x4i4@q%P{emYa>-47y@+SJ)2cXlB#tk;f5pYQ z!0|6@&+q8So<}D-zJK`2t@!<@+s?f<27iMJ**xWm+VhXcs_to#)zc}#FP6Mh*{UDy?H%{6C@XgBG{3h-^6ynw zXPhk5&gd1i5#O@yQc$t)x9gIMrB^?NMcv!lHcdD3!0oBp;g?RT&p*SY^Y5I^XCJXR zYrk6_%h$%8JoK9LxV*pjrA)CHbzZ)jH{v=G4yUK-F23=ik?Szi+ui36G<@VRR#td@ zi7l;ZQq9`MnfAT<5$C>VE#u5B%n_T?e{fEZiGJF$j^E}z?ORO$&$+azRgSsEex~rR z+|0Oy=L*lGy{a{y?H4x+PwZG?-_vY(Fd~s}$25I4rZb|?5>6(t7MleBIb>rAT8;wB zmHM@R_U^yws;oRO>DQ+2a|_l9`xX4@Km7i~dF4Omb)Pv`O;1z(Quk=@^INW)xj6*> zwQR^(+1V0nqV`qbOEZJQPJglG9c4-%Pd88f_H5SGkQ2?{OxZV0C@cKKdUaNJgMHQI zNeBMC@ow06hFj_S(%v&C9~J$O{ipfi|Erhxv=)jxW$s$4lpr^wBe427SJa(L9>*j+sIpG&LwP$P2jq~glm+svm7+~G|fa|n&U*+y1Q&ZLG6}B@|6vgVD zPD_)lTqIWEIPG8~!)s&RX%8>H%XuatRlQ}iRLy1i{>R4lVb1*5 z%?@WotZqDS6RGVGd`Wmi@3#1Jr)45L*fu9Uv$T7c`N7or@b_e&`4@$jdBxk$`7skb zumvibr`yyeR3yGG)9Mrb@xIG^?<~K{%5F%lC--nZw|D;EgQ96hA3kr}catOI+xgdK zjHgnWuHVqV{au`0;+t)L&ewB4PyYT?=P9&h>c^5aeVNxfNq5=>YL5JM$TeF!+wk(~ z3(r{?Lj}-?;VCdKDf<#ciCOc6)v&-Qn*ubhqrU zJM=NeOge3+jK%RY9v0Ql3XZ`VCFMV!o7R2lOqrzcv&{P6anRxx@qaf1n%66=K7Hx; z$MQLcg{qpl=5LTEE+> zb(nnKUbC;Mx$^m|+Eac%(fMU(a+5M9{5}4&F6R4I=>qPI2|Gj!#XmgnD0FphGW=Q5 z_c8g;mq&{2|1}Q%zroFavG%*o`Q@tL#PZH`PhXqxc5B9!hk2o*{ZA(E$S};iuj`$? zCslq{<`M0UVsV1MrvJ8G(E8w-`WHcOou}Ulq7VQ1b$r&($LAkU5?{`mcJx@#${lN0 z-B?kvNa*>~`PU5-+=ccZnGVkieD$Jrhq-OO{Se#NmTw+ibzP?ro)cBO zm+-4=Y!r>lXL?YJM}#P1NlO*N%PN z66d1L^?R6)xk!l2mwd4H&hlrX1=_)9ns#)*v}?`e;<#~27d)N_T7Fsnr;Kl37_(GM#kR}V&wp1hw{Bjiz3D;Fof*&H{hkngzi@wk&QcE}6S+0M z2j-lSS<-v>{fFz#_TuMnTfUfauVLHm{|=vzN6g?f;M*rQ=lsu}mVYx&v(HLi?sEH` zv6#$nmD)wMYeFQi%RB!PV>_C)({Nq={4}8hD^g6GwH3!^WWt%EgR z_8+#t|8cR~`YT)YQ%*q|Ta|6~+d}qNZx&(y#-3)pJCbAKg`*c8R&><#N7~G|yOeiwq(FvpLF~Ft&DisS{fdkiqkap_V?Xo%))W2D1wUl} zRefCEB`Plbvm&kA@w@J|DW}`5k2rLg%@WRO+4Abpgock1g7RDmG7Iu-S zcgC)jk4~k{WHhj_o2=l&c5Gv9=6BOY3%qCFUvD5{k+zKKjQ35XBE-CIp4;uDun2W; zNl@#H?|;o+@O8#MPC zzG%-C_mAbaS<_MdB_dQLi(lB-5^4`gcG>Zq|SFeg8bPd=vwoBbfJ;@i()j{_6BSd*_~-`F!P- z7qQ7t`pQ#JPS7A(E`Tp>d=d2Nkcbe!{M{qK)u ze)*V4KP#NG=jYGQIzN4$25r8Wt@t`cbLOM3`Nr2j)O0yJPw`v6`r0p#z5fIMURmV) z@!$Qghu=r0el`F9)cMQp={ZkoAu;sl!2PP1=_})Q{rMr<_*i{jMU&Lo_xo4Qn_aol zTW$i=9w(`*l`K-f-)@j7^!QP9$VZNw(U)cR3F)az?%&92tl0PP-p?HGPAlQV2N$I3 zw7lQn_j$spt~p%d>;mlO8yJsEM3Cah;8hvHpy&sd4kd4qawDbr_r^ zKV&x@;AVQfP?GUe@+1e3bB#x26Th!N(ZBcq#BFbn*q*T$Kg#mOY~8|=%o9dZ+DWQ; zhab=SZDHw=r*WcVf&Y|g27fnhi#zI4n8|+FM z(7IOaNCDsfvd&9RYblK)k|{bM_Q?_SeeeNS!I{Q7lo zZ*xfZr^_xXO<_&)3VCZCyiYw!JB$TPM3o;Am$@A@|OocxoL|JFr4 zw7-`&Rcz6RjL+KzUKOYQ@tdmf?*F+(b^k+OGuj>eXwL_pY&t(pOTWakI<`#7U*9R_v}y9g#Gm};cl`SG(@H1Uv}~BBykO#l z>c6keDx`{yyVu7vDE^*g`G@bn$dC8C>$zjAzTRiEyLao7^is#|zuOi*vug9=UKe}r zi)CURvxpGaqIImtzHfa{@b}RYh0=)GE*1||l#S{xd0Qp1HX6*6Vgi+*^1VNd-8>>@ zNhNoun?;|T!TI!hgFd?hN3Z(joXjN?b-1ODCQUqcrmA6~c=o~w=B)**UOs5Kuyd(~ zW7)FLRuM9M{~sRLKcud6q4%Rm?2lvH3+~4LY1>jNz9sdv*sWi0o@G9-Sn;u)H7(*z zqG*2Xo-V%^T^p-Q{LhG$dT|EM1Sa!vIM z-~F;&1{DvnKs71TpA4;MZXVsXXX1CZhBb>??s@cy_t8k4Q<1?vA4 z*q5z)__|_dK!fEYMNq{jQYTntx;A$IJhORqRR^wmnVeozd#o~Lwn$s7@%x3fr*lsK zJy2+G*8r1LQqov?+-Ek%+nuz-IV2D%+50YdC!UG=I`agWj{c})bh^s*IKM zEMJ;!-gwOFPjP$vow=!cN;_KDe%j<~R#o=k(jmXFd0S@OSBhEx@L!C?=gG!1zlZDW ztXQ6?$+~s>*3ZBB%rkei8vlH6zwV%~&AgbnnREW{K3MkkRjFBF$eBNVUeC&x-knul zRb^9Ne1B=oox_ga(R(Tr=eQVpcZ!`4jgC3{)Mk%xgSOeF{Mfx)!w;3bwzzA&^V_X# z&G|RIw%5F`+r91n&r8QM4|}eb{r~ub^`Fli|8wrnel1?L|Gzt9d|q6}{S>_;X>a~b zTgA#@zoNiCtXklji$G{5*P$rRWycnruUaC;a`V6+!$Zk#b~*QT9nN2USpH-A;riEW z&)=RAxA5!o{3VyKum8$7<-o$^r4EVR)5;c$EVH|$wX|h|NA^_x88-|&lpe2QVrr@9 zef(C!&o$~mR^-Ey=)(I~k}dx5)$jdL{a5I^PK%V9wdDEB?`oRbHaVZ+Y3_87QJqn9 zmgW4^4}}^nJECg$g|Hsm`O3J)ZdL3R%av=T%FbSUzGW&u%h6VjR)xZ;x#msEEG}=H z4SZ&P&kf&q^xfg!ux$#R3!hfb_}meg7rZsOUHI)53&+O$1v9et{{ItgF6QxX?~kRG zyM+Ee+kQc?;H~&-t?#X6rsq!PpP%QwHArjz+IjrDKUBZ93XZ&GHf7uKK$WJvA8~em z+in^jy3X`jS^x2-Ez`GENgvRkv0_oi;g9EPX5RXAzIdj?+8s^itpDbE1m5GiR&Za8 zHKM%Syxr)}i)+zu_sISF7k~NY^ChsTsK5L=Z&8(|&X<3$!uLPQz3a4o>IdiUDAl>A znL9r`pH`g`czB6pn61OAYXvRGyO$I!*uIWcbm7O2U3s@Hz{P}oWsP=h$m&EM z9`%Us2CgP={WZ%c1#rCEqqFAFA(NoQ9mi{qRj6INv{OLtaAC9Y-g>^g@e-lZkp^r$w%jHcDKIU z`CRUI{-OSTzxlT$=03G81@|K4EB~Cn_r-J7^g~YXC;Qt)*88mA`lU-VYUX*?manhF z?4I2^v)1=RqI}^5hBNLnjvtO{*}&#-dCD!;`ziU8MNXU+s;aQrf3Rrl``w4%fADYo zuWo-cRP{?&_2y+!)2uAFZS4J3v-Y=hh-yl!ao)9y?jd)U7%bgZuPX2`ruvVj-@fF# zp?`P!PnW&+(7xu!*4yj-8d$F1pL6M!Q$gbbuGL!GoN9NoSupapXy?2xWVXHH^rYmk zyw>V`UGoS*2ge=vHtuMRHBw~@@-X_JRessUeS7Kpwxawkqk=%sO;! z>$|0E85IviouASWdDY!x0z;>P;$L>1nyfS1ekgF83aBj7>D>BE`*WhA%45!itt_iF zpMw^@u{ZADzI}RY+_8kZ!}j|>v%Pv1^%T-QasQG2_}-r@!Tx7`UhoF&EQ@-X6kV_S zBjR^!_FBh%R<5O2nx_;eu5C7(c=(K*etD>zUb=YpTBF<7OGG(-_qK244%DA<`27d> z2lqdH@w57Vsh{SgyR) zy5R5Po1*;8*BAU=CE;IqG1nI~QDlGYG*2ayPEyE33(rZa2WQ<%l5jdXCrUf})xrfY zAE`Xo`slxxX@j1mvRDwu5~YqqcSF}KR!zP+<;;ephaz2CzlD}gm~k#@rQ}}?uKSDE z7S`NN|8n^GSJN*S=jHWoS#`2D@Y;r`yp>-k$!!+{)wvUo{eKbc9QLAh(W}PWcBw7* z_#kaHn?v@|3%BR&J73DVSJ-}^*@Daq|L&AsoVr9$_==G2yR&I$3!@DlSWP;@BFC2( z#w`+eVx^qR5=~ntWubZN10pUz`e6Mh@W*zW*(=4WoZp=d+U0UwHBw01&thl4|0Es_ znaP$*vvpi|Z_Dala8I03Cr9yW;Zmz}#x~PeMRf~am-#34qju6JgYeT@R-JiM|q}I7= zz0MaER180*!B%r>hKs1Y<-2L*11dH7V0K} z_G$J!eqZ^duIkOk<8Suc>i_u>JS`mBLFfOocKUsnuR#$nT+M#&(%oDA{?FzIi;gTZ zF6q41@OQ^n&X~q4v)T(7inlGE_~@oz*C*5dPlnfzKm2m7a;lv4t+tZqGv-d$N%*12 zxxqXP~O-vf2 zozTgNTMlGp*9(OTo&2G+`G++>KE2!9sSjM z+l`Ysu(0EGl6B^W!qBsJi|;rcW@_k5lzH_UJg)j*;4!;1&qDDnry?rkcf4KSW7I1H zuUK{VdZd&FygS?YCfZ6Pa`wv9jc4q{q~9{nJnppY?Fv@@6lre#qMOCO!ttL9t{K?O z`S7S!zHHIzxkl0KHoPtx!Z$;ne6aqr^~3kKr!I7gv*_;K5R$v=hta+DQ++hL7Mgv`N6*sEhb-#MPv>~3QEFwKn zD*gF`IzO=^>yA20>KnhYh}{0@W<<*KT`FE7WidM(nkTSuihPc(Qau=G75de>MwNndjfLVfKD+em|ab z5#P_;?kRc9OPu*T`W=78OsS01IJIT1Sh`nnEpK_lDwCTVZ!If5!)v-EVH106yjJ`D zhwEGS8<@GN@B6oy@gUo^b@%H&-_EfO+2|DYdRp#_>pp8I-VN87W;@k#7tIY*)fRih@K2ywB2DkGWXRE9 z%7(=Xxq@9?E4g>OyxLe=cKw9JVX3@{!8^UfjxzW?ZF&h_XKdL1pWS}P-G9;hz>6R5 z>3@JWhW|a7pF211LeHJqtDE``PpW$H@J3Hu$m4}SM9jKRTkZ{r3Rbwq{QE;z22aWV zJJT;ZsDzaUE?j>|<~rAhYjtlP7RuDM+jG^=pV;TOM`6YnUZ<0)`8CD9*^4|pK2$p!~s-!cyM%vko?T_X0Rgmq}leDEq1 zzIxq10#y^=@c(C5Pp1b$BzEr8F2?5ANQ6#Wv=_MdZYel=iMPyQ`EZF zmL&N19Z%_P+gS0!@WSzoW3z>iO{jbX8uyyd_dlboV_(_py(VUHn!Li%7u=rbzF5%N z$y&p+aP88Cjo*(S-&$H30*XOfey!@^5-50mWL3Y7mgh)vmR_BhSgXv&8^MV(pO zbeG!J#N9eM<2M)UtB5Tf+h1%?O*-?f{pTC)tp(9_L zx$Bud$GbBRa~{9A$#ngK%h#5!eEn1%G|lzmjPdy;q3Uy8#P|HVTJk3|{o>5^mh-2T z&-Y1}-`_B;e6GvOrLkA|8qFszm^MvpRz}Pbo&@j0o+v3>*=LJBb(=)^eBAh_k?)DR z#?A^sSqYgXPd24KySwg%&38Wk8C!F<}$ct#{~yUBR28>==~ic$`U(E$vaj>HecRR-v?a$s z)>W-}QnmKS)eX@bs?rK&b`|JF_2exdb2_q(6yUHcW+rN!MX%i4 zI-m-ZZ~x){K}Y*vm)E?%ZPtHi_Wc5}+K0i{K`B7Jy}s_@{BD=9(w*k@A2#)h^!G-6 z-Zo=vB2)V2T|V0iJI=m-dDJ9Z@BwHzO?SCSX6Yk+_mk2Szg@_-Lu?GdM7ttWN!TT@Y|W>U*Ahi z-fmxaUcp-Za@n5DLWc*gYLAdQ}-7i<3Ezd}amw)@?YyOR=7613uzA2j1Gd<&f<3}y?#~R^D z6XrB4MCau!JDR(8OTo>esGTZ~Z@t3&WXhjc z@n}=~+8u8m?%8tnyQg)bvdDF3neN$g66S?>h1m@3?6Ue(4A=Mp9~C8znf^QAq@K5X6K8NL0o6xUkTjw>8fRx@w=$$H{Tc$b|2a?cLd%jMaV ztENsnXntP#z^#YnKVBcK|HNAFVr_Xhzja;hao0DCRnKse3GW_D{9XdsShlIGDm^60$af?jK zo_c&{_b2P5g>PfFH%?RMp7Ve+?B=(1>pZkuuYG7Yp4=qq^zA}{CeOyEj%?Xq*YwH~ zxuV66_y$A=FS_&pRpI;YL(0XMrcH`^*9x1>sAsi*VZZ;6>+NUG|6d#JdZ-+31uFrs zoz7oWm3i*fmoQ(Ou#;~@zuEE!ujPrmcHQPse9Y{xPi@l0PV29q$*c3h;BW0~=Jn!c zVRQfbJd5D&2K9bh|C_G=KYvT1YeUCz#NF*@Jaw+F3z@pth|?P)odau+^CtbF&$(pYn=@IHQ(jhl4Y zE?=6abo{u}gtN!3Zyt@_3d$HLzuJGjQ^>leHJ|AqD54)1^C zJAX-)rq>JBvoSAty&pV%c#GTqsM3oCS!*LNI9|0%u)Z_@o8xYzFM%Ma>bzyHsBxAgvG#x?1&8#=?*$GL|ZZdoSn`EH4(R%P&vC$rY7 z&i1 zn`*eieBbJ6ZSj4rH&KH3rTvY+`MZ$6Z8NtNoQV!udurveBA5@Ki`D1n>)$Ot zZ~N`X?Ejw>Z`VT$tcfW6#_G=acu9S%xM7;Zo5yb%-EMD7n*EkL z*K=)atOsY?xAVpEmpHm7o}MxJAZz~VEkfzLj@e&GD?Aj%wfn`he=1hTHr~8mJj@WRFi;x?}56pOQrBwH|#Nxi@mGouSO~L-^6&IWHrxzv10I$F)yVKge(C zs_VH6Tg6wsI&R>zL{jbljN3oYh-H8Jdpe=w0K>$@J&V>bC!E-|d-o1M)%fJsyU#E1 ztQQpHNL;eyoJv;gH@=E9veEi1lV2K(24807+kY(O#?cF)ZV0H=W?%Ee@zY=K4~&p$ zy`RnXVplF)D41|_cDCm?m#t@?-U^w!v>~Oxm8J8V(!94SnqE3~&n^Ur-2SwuV9`{L z^UmV=*QB_%m_O6ox7};9;HUWYt=izlf2{xH>%O;_d~LUrx7c6yx9V2Kua}{!4JxlU zZjV2?LnrTq{oddA8WOC97mEJgu+7OqtkG1|+{fAGJ?Gkp;;sCb-W?8%`d1ZmYSos% zr?&b`KX75zPeqL{N=bW5>UYgw>G7_!Xz}W(^*g4Qmh7^Vt!tkz1NM#9$M-ijEj@MV z_ie3-p$#(sK?Aw&JpXexndcks|2&g(@6&{+tXWBhOb=EvU9#J-qBW@2W!dvSg}2=6 zwsKCa6%MU==sopUn3;j~D~m<*rb2S{#N*p9xrYBseQ;@0s%*)_*4b|g*Qx|Rx~6k` z-ap4}A2xLxWvF;}KAW(5L!!`)*c$?m+Lm`+XS!^7W5+5l>2-GxWyx~88k!}}TIZWH z>xO<}->Xi+$rT1~t)7E=J&+l`uMganPH269W*+wf+`^xq4r<{yz0!&-Ib6E@lFP-} zKF$k`ZdPa}Uk*?1W_8ZWHk_oaT6*eEbI8?|*LX}*#jC!1R^{xs-^m|jzs^%D=Zi>PJzgd`As5|9xNl^JDwU7NGrx;EBigwC#tk9@8zBlE`C5Ok{ z(|#{4yp>t=(I{$al!N~Jcguvs_TTw!{m><9+b@x*k4BsSf0`cnz3cIgC4Rd%UUNK&K-%=f5gL>TLIwxhkSFX74>;Z+T(= zy!=309vh#Pic7(xhL<14Ps&2F30mLS|RgD^B&Ic zy|6p|yxcvrpt&l`N@na%ji0@K>&&P#PcFIr+5A)L+h2$5*;BRUdxDQJyI#}FT>o4l zdGr5WFWHa3JZEV3zIb-c#aTZu^8fBRZByHHQ||An8o`LQn->*c*luq=_ug%AwJ)zW z<;lf&XMVgmBzpRo_RMT&Q>W5WBmRv2rE`RY8VZW|KFwghyMLcMhg816o!t{sydJG6 zDH3|={w(eJj5TuVJ}<)mnZ)%rBK%W7??JvUPiXIJNM@-2@x1BPrZ07WRs}~kr5A6R zxX)EtA*wB;>WXo7vfvq0kLP8Zo>^?nnByirJ#gd3)~y__xl-SKUnNaDzPF4gepmW| zeQFHPx28F7;e6V*bWi`z4Q_`nG%6bRFYEM@zmPG*QsnRN)-9HM9aAS=oy7whLJ|MJ zU&=c_*m>gGJquFyxV)Sk@B7wwo|x4d+jQP7mvXYF8{I7G3CcE>Z}t>eyER5WKQ`s} zh3`weMBiG*?Ah{rmYnyUt6%mUuJBMYZAit($@e7TkS^FioxUJe}{_MjQ96uShBRgy*%f->x7?- zC%#VCsJngaq5Ya4*Z1hip4ik=s~7lWa>kB>At9OPg4Z^_nYzbRzVeRpOyk8y+TGWr zzCZBK-hKFS(}J(l?4H)GHuc}O@wLRHW{!_BlMmV%uHPUgufpk4q4&@B)~tdZAKE9c z1n(~ZwFdb1AM0Pms<6`VJakRZ^dIToUn8sT7y3DHD@d_j6mzheUMu@1$CTk;C5v;k zL)t~DZ|{?Q9U>MhT;FIBF{AC`qN`dhe~&#}d~V4W$&<$v-`}_{vchrd^S z?l!c0^>z942ahiwyxa3swIjaiitN9`-T$r2|4iEU{f(-x^tI)h`w}{BkGWgUDHIav zoBHVOlO)H6zWnz6O^pfA`Ps+_de$_pf>@o>VVVolxz1OI)|<$0Y8^ zv##^g6(74AM6v#ufBJ8t{CkJ}Gs?Ta^Jgji-5UR3M(wTI{bkoYX6$PSE-(yx30nV_ z-e7-o(gK68BG$~uFBq_!+}r0Cb6nOjSvM-bvocEj_RGwDQ`7Ec9XtDTrI(Gz9#MZu z!%(+mz2ldr3(T0l;v-PSEOJ|(b_Fufv z_OFSF$&p#*6V6p=7J6GnFP)I5KC95cx_XusZ`u1@DOaX!;?L-}^m^vI_Mm&3@6GA^ zT)E}937uiv|9~g^GkC!~xW=eg`*Hf`+%I>w#n$lYoy*=GexXu42AXKofAGiqthZ;J zxaFw(?biz*vFFupaAsO5V$GI#(8c?p6x(O^UWRNwDO+8~9r+iPUiidcsQ3`^Z{s)T z=}D(zI&RJ{-l)BTbz)^_nA5ZNodwp51s%h-OlPY5_PG4VbKZaF`i`2}d2HL;Ecx$5 z7n`hn*~Mwig%=8UTu6ExvE|;Kd)KRys~;}we)(qe?#Pa~%{8Wb-~ZyjRQmt>`V7#P zx(KzK>1%gg%(}fpy>oNWy3``RSyhttvDL@o&Zlf@{Gq!ud{btU&R+SlXyHA@jq&d; z8YUWnPG|vp`~JiA;`QqyB-wwOe`+;^mOMx7rL!_J9_-(hT($OmMO^B?ph=czE-KyJ zeB^!l$E20_WR}UDI_J9=oJ>>#lv!=3_}lk)4Nyd~MFP9@p1-!IN=W^>J#*How^9K%<;R zVjJhgPrc#&^vNa5gXdpo>2O))MW(-F+1yf%lo+)Bu$Sd?K6zkKSE?Rcdb1LqsW(Jc zxq69b#`n2y66bCD(bm0w+gjGtZ(HMcW~is!|JCKZn}N42!{m=o{IyxH^RrVkrd0)) zZ4>%F%W~5)hd(F&)*1cPow`%Ir)>86D>G+l|Ea2J|Nr9X{<_$51K}kL=hildMryhS z%Zi4pCVj)O zOL6g+Z+&ejT@bSJrAA@gHyM-Ht8{fZid*B?xgVX~c=MI4#J11y?vd=js2|2lmo}RH z+}QNE9<(cF-lO@tSKr_3apcx5D{{HY%~rg_PQ9D=lH&!ReJf`L$NlC``2Lr5^6!** z@kFu9HsABN<<4AU-XCI>5XJtGQTgolGr>(ej=DA+pFHJ;)`jSGpcz7+59Q^h61}0> z348)gYfp#m+f@@-VSj3B&&HHZCHLRnU+ONu^RCR-;28=(9_9btRa$cR|K7%R`yO$5 zm9PJ{EcaMD_pDN7?wt!B{t1YWzxrd-{qPrC{(78bPqZ`8_v2I2OLlWUKhx&#>jblk z-e(N()6`t2k`bD}Z)m6oGldfwQ{ylVDA}`{G*CpO5 znFlti@LYWBa69hI1?KOzKC8o2Gfqy*>r&cbyFO;;Bt5JDrX`0w)dSPccV7v5wKP^Z zH#Ak=VMegl!-wME^)t>HiVFx$-3 zJxtC^?N{-*IXipXt(wA8?(?drO!Zd1X@ym6^=}@w-+sI|?`vcU znQHg9cVC?(($Zd!KD3z}wt8tp7}M985z38DXBOp7 z_-8OrPJR}*5`zx+pp5!oDfMW(9YcXhXQtXdjrOKm0`j2C8 zoQ*uCmZ7^dV@~NMUdwpz-)Hk8-CoyzK5M_A|Lza-EBmv#eO)*Fsr#~6e(7ubPmxxo zbJbP6llT=A6 zTlx(1*$0js2c3!lPA2k?e@u3KRuNu*Y;%C+KTz%ZT>f9AfX-gyu9sV%70gtrG@Vnz zrf(Sd*^c2?_hR3>x3=(Z-gQ;)M_Tch={IA!dX)cjoV5JSP-FkO!GGt3{BP4Y9{N1J zT{*4tHGBSF`N!LvJ13iDvN_qEadH-X$o)xidW41m z%YC1G{=ZMt|26-6mjAC!?(Q!gU)K{q{(OABez|nsjz+!RNy5ABvfCWfPw*H!9#T26 z^IOHyQlDon)dKrfdD?a??p%F4=CSy*bc>zWl5_riPMEO7K5}=cvBZnn}Dp zXMg?Mldw$wtcb4OjNgfI>}Ea8G7|DL4f+YE{?fb7H7g#zQY~e3d{oBvEYW5j$BxXGYo=b}f9JmTp1NFJ%RG}qrRi3_-j}wT zCa+z)c1y)gt6g6faj#sPZ2I-vy9YhfZOW3D?pu`d=*XP{zuik;R=%jUt$)eCRX%vB zSKE2#M)~jTw`L^2J;YvaT{hoX;#jnJ;IoG3dgAMJL30+6^dS9%{n+e`+s5Z_cyZ^R z^xXCBZNB0C9F~SnzTX`pMF`k^bc2{rfr3rPu(|tMWAnnNPaD1|g%oEkO8E6ryz>5&6?^&Lu3dI$KJ(+yTi_lVd;Ok5 z`@f&Vo1F5!l|1$1ciJqNC^o5oN6m(<+hRSoym{otcFcLp^CJ6O-kpt9IpEHxA=XtdLmzXaR4}ZesaxSR^HJ|$xiGb| z{|{TAgF0JZAIP6Fy%3&X-_w;mwQARiXEo<6pGW9c&iHu1eU|;JXEjAPjilqZuFCD; z;Gpovquu_%`G!^_VZtM=D+LTEjDwbxbDWVdm4wfKE6KBOMGs<&D?@Z zGr!#b{(p`n=fzvy)>0f1x1wk7{hTI$MnIxBX``iq`drp81&{fS{cX}yuRbVFoAYrZ z^E~f4n<6@X&w*DdUmm*8Zm<8xT`ha){nq^bXE^r4+M9>!Z`_&v|JV2a?>=|`DP`y~ z%%1<$rs2qDmCUV2-<-9(sdd&gTXBPFkiD3%&waD6W?~2I+mYPq-syrC%ykC%T@2KE%(|^>+iRDkY03~d-tk5xqtuUL+5V3xif3R zxqGUNS5I6K^{AG8rGCoZcGG*Qw@k@RC1Hjss@!KeZ}Bd9?H0d__e7eU_7)%8X`sy_ zGaj$LU!|OTf0t>&Ki=>dN9|j;R^9TxcGBRmfa{}sq56%U6Le-itlM>c&7$me7X6n_ zpI_7$l60zW-`BPI7yaey?_@nt-JKqCf6~L?^yg20A2{)3@pC4tJ3kI5ZxU6An^|6= z(|!2ie6jl3>rL{qy@M^ME^Ln(`>XeK^Y6D?!P6AY?{{B-%vSY(*dF&Qa@BM_`#RSLKW3)S zn>n+3^Nd-FbCowpI-W4fG|bI@%b5PkiR*3aV>1q^TMXCirc4X@SrVPwbYK0Z1Y zG{0`o{QtlFGym_at?@HCk=@^YanaGZH3CA_3g32eviFb;v{(Sk^W-w+-(>H{7zJ%{>JZJG%bW8g8yt2LaQXdxo zeP8>a{&MK_y)!p2lHZ?Xxi)(DG(VfSK2d)+O}+B@kMW&*d%;IS{r~X4>UUUHmd33e z_a^tndy2NbaF;;Z-Xm+$}cW%=T*&)P0J z>K@s-Ou2FYs*SodbujLqC|KWds$Hch( znZir0cduyKwzTfmTkQ*vXPaL>H!n9->_ycF<#!*?vVFh#+J8~hmluosFZr6^bum`` z4nF2`;qm{?zw4Z;tGC^LJw2z2_3qEJ{1%Ybq2Rxp5moc6-&vOB=dF33CKuxK%&B+k z(Z`F9{X1gdsad7NVr(VXG2`4$*}mG#J7-+o$xxVl;?NYXt5RQ|AIv=+_b~Z)LRZ|! z6ulL5zH=$2M|+jU%-Q~Z)y~hSAI<);^nmfd4+^q%?f(-#&M$G=u3MoWaweQBr@LB| zLq5Y&?7D$~-KR3mGwxBl=XTDXDbs%Um`U-1AC(^WnkoWAd%$%41DCYuGAN+PFi_h(|i zOwpeTzJOlhO3?U{jZ7BVt2zPjkHONd?&Ex zXm2yzP*Sx0t?=)}vkng={yx-y&dIrH!Y6^9Ya}~&uRFVQ&f8#P*+d_wBunSp zrEh1R*!G#VKD==E-BT7$vl5Gbtp1pPhk?<|E7Hi;k)!srX})F3kBi4mumAB|^1VrI z&v*I#|EhMqnbTYPcz?m=qov%x-rv}Aj*C$Nl7#*onC~ab^qv3RE9i<)<3F1fIrjWm zZC_oyGUak=cf6%I|255ZoomjU)ofyPN|)lk()H>4QE?f&+c(qJr-jsg6j0^juC%$b z(5ZgghD|@(CvRUdsc?;={h9A4V))*iWWAJCJk3z^#+&)i+0*2Ner=XKU30%!amzs& zP}hFhkJ*oXOD^kvzqL<5zlCSw@%d$83db^vbi_KmC+$#3aLd~Iht!OZ=k&~QW=E7IXuYBeS6}SHM zTI(X~+^RDtahCoMN1^z(8OC!ZMGA8+O+PH(`+x5HT684ZF9kZA zFC4V_f0SFeAcTNEg1`{bA3^lP7<*+_SIytR1;}L34j8SI*w^+z*$}z2@4({MIyLzkf{f z0gkM5)&C!jJ_EjwiT z?(Y76<8eW+>)qvNyq^h8O=)@l+Su$fqrDDq^U*23mloZAS=(+e^?#Y~>=#Yf_f1Rl zUcq~}@{rZ7zgN=qJcbSB*4T`n+-?g>-#c})E9Irq7zgO=+ zuDUnPDLwbiBI|cQK04a}51;$htLppzU(QvJUDYpV#}&8z{4M{bD?Dd`?C)=ZYa$B3 zK`zho@1KvM!WSpUzq2;`FAMg2RRmtTdS3X?=Czl(SDoLxGvMcO-pKuXthHZmsJ#DO zfBW+et+aO$rA^Eq?c4Wn7nr?aD(AgfyF)LS%>L{v{qFXh$%dEa-m@>O$SCsry$1nBO67@KuXy}_b-DoFU zxY5qG#|MvvZp%O7=6aW%`+c?jr@%5pUSXZY|G$pv2fcULn%&v^(2Fhhfr^>;om*RG z&&)H9|IjROU9@bva95Y>gsm(oj~|{^I^8_Y?(MbjiTphe z_e)%x4m#fIIYa%Tqte0ZTTZ2D+2?p(IoRoP#yv9qXMtW zk-O0Tw5AJw&qUAhz4~zdSpBbCS52<7u=3nH{lO-^tA1)?%EYBR<+`f=hv!=?R{N)| zx3k6N#T-q?8DguR|4f$eYFL>($@=Bj{5?J1CwG7wdFdVdW0qB%?4SMRbHNw>chA>C zg5}?X`MeAL@?)L!vspuq>GS1$+uWXcmSKMX>NAP52j8VW`d0TzrH=8i<8_zDgv>|_ z7Q5mwBS!sMTwfbD+*q^lgSc!|i-WhrqXw;%OZPc@{gjb)B4O==j3_d+Cx7pG}XlWTu^tlI1!4?1O*r z|3lpROPn1m^!0aq_}W-(e6;@9vd^!#eQ?_vy*DpZrS4DJE74_(J?>lI{d{~Y|1Zhq zuMZaddKvrfFKBhR!*u;YvY`R*|}=|YdrG&^XTv;xwJ1=Tn@4MN5>a_W{)q{+xCV% zcJu$8@;0*ly|?OaiTmAX$mCm-C&MLH(YK&szU4&T4`n}O|1}@{e_^ry1!4cU35nu` zPCue|u4xWi6CXcy3G?rc$S(h1p3YK>w|kfzGW7gsIHS#WF?a4KfrSn}lM_{z#7{}B z{QrLM`m}2|p<*EftzynxDvbvYx%8FTv)?HOcG9+dWL3&Sy5N zSUT4K`r+~5`3^&SKb|GeSN$&ivHV#5t(D6!wTj0a$jw%5oy9x1>W@{`ub0a;=bx1k z`BxtEODfB^RPM{4S!$qzcKfGUXO#5Y1alB}T_2hpS-?@LkAAkMx{wJ5pFRwml z)%|K|&A!HY6^}UYK`M^x?efXr|L$scbNlqIraD@C@8?bT5#?CtOIefuZjG;s^Tm1@ zf>NY|%O6B6J+p1epWTkv?!3F+#3Wkw?dHCn{S$s#&-*$<^yA-aLHlQXcU4xn#q0T4 zcNR-*M`ZkEDZZKc75BoF7v2Ufz~lM2eQTxU*OHfulIpf!t;zPSyzy1r=R!gAp?8sg zE%n8`A8mYauQglf@{!u#Qu#Z=l>X^QJ$Sp^zy7QJ4imv+LcM#BczfEXTZX6Lc>zwLw0*Xw=ExA|LA zecvZJ?e$XM>A4$z8z1C%ig}mwO#Jifu=bD3mDe~KzuiCYdHU$b~hwC?(7#cb*4O(fpFkGyG{rlh5hg1F@bgVKwHTA)hhR&E@ zfetwnwpfPeyxCm)@zCn_wBsDtOM{9ZvYr#ye#cNJ{jHe)e7xA5`-}G0POSM1+A_xe zELQzrN%W>wx%&dF)mC2HS1Rkh&DDrULArVB_dDk%h`7Y4IEqWC{i?lQKOyU=(!bZ@ z`OA;je-n7`T{PKXv(P;*MKf-vx7yd%F6P$XJ*n>duJVgl!($xf|DABZ(ff-#en+F- zpO4Zn*4zIJ-1Taey~&wBM`adQTn!a3lID2aF13HhW6t0u&*hfQd~`jpW=Hrp$4PTo zzq|B?a(}Kl_WrK<_SVOXS6J`8*k>*L=SF|m<++@tOEz8n5&X79L*vhx`2Cr}XHD`f zx+UBoExw+Q<$C)HyK@5^^p3zO?2x1olyst?BA;25P2agiihuj+3^po z{z0y@1?n8G?U!kt8Cw0W|D!mlr?B9|^4r04M2+U(&3~@ixn{?`g?;vuW~deKvyEHJ z`@G+bwNtqC$HxfgeHlJjdoFWJrMj!+F-L&+om_XH#3z+usCMFBYQTQzz)8R{q<8B|d3Q*1n`DP{G@Lbbeko zFUOvqm2dv;SZw#B$ZpPw!&x_+qq2TJc>T}h;o?3Qi>-H5wjPka%KxsN+i1cV@^s_lN6~9;oE4IrjN7i*}3J#;z9Q8FH(h*YEfspB1&e>T%CUWwyd? z@6PhuP47LzKkMx8`?D)+PCf76@jc?xgJ0)o-QQIGAC&wbe0=?>$$rQ6a3x5E*>Cah z=7pZtIG<%&2D7TOwlbc&DfV7o?QOYU-O~t_x%Z^fDwix2yClWJmVN8iqhtGi&K37w zzPT;)em%!E`8ij*6OXwc*mCFav*L?l9*yY}_Hbw!#QeIb-vDmt*Bb?HJaXdrl*v&~ zwWpnBSh=hH!kX)sD!l(5Ht_Vh3~G%pmj9b^mgj8r_T0t&cPor@Z*IG+ruomcUvu0|!Ad&aAe#P;96Z~pXiezwhyBhf!>9UDW>e?I6S|MmS}0qs+uZoB;I zAJfbAcva@Q#N?aJE{C+_nZe!E1-rb&YX8VSzcJ;~jD{r(w*5}K$e0e+l&2@>%IY%qr95Y`djlYMDtH6CR!A{;^}>gQc6(Cfi<0KCb=$ zg=bZcnvIKO_aBiPAJ@#44_qog?{xf<{ibR}rbl+1{0MhBjk zW^(L&?<`*-9b0fvdZn1{uCI~1o_d=HzVBvceC9m8S|FbpIs_d48o`}^$r zmq+janIl(|V0^dmxa^k$%={PT+E+Kc`}?rA;CCspme zHXg7j{`-et{ohgUN#GfWg=*j^yS^sUSv!2)lIDf(^0iass$M+w_~Sjh?DEOwbNyRS zE|dJPw;<=&%i8kd`FG-ve2(8!VYv61>H38;FU>8#=V?8!I`7u=|83^`XRfQfZv7%O zeU6*=rOd3{+^73XwZkuVE}!f9wma(O&GdN>t3mw`dG8%)Sz zY_ndwFXXQ@m}J@`zsj)J|H-Ls`Rg2?ZnvCp=Ckv&R>N+U($!BKMZ|SJi`9LcP%HU{ z`FlJ^rG>(ko~&2;AGc0o3;+{~I6ky8N?n^vzj4QLCMuolR!RKH7NT!jpEJo|7p_ zejm7b)uypa=ucSpRpYDb>x25A9qVh(KS^2OKU3M`{$kEEp*yZE*)sR^^ZFehAK%^a zVo~?Yf(zgC_nH>O@c*0ml)2_j~Lve?Rlr&)J6eju`%!x^MYv<{r!Xr`Go$+AXX4_kPExwIw(I|Iq!X>n?aK zV!POXHvMJup3m<+vi`TtwEOk#|6d%I*Z+FC_;wlVyS*PSeSSG5bl#^k;3~5I$p?9} zh2Mq4?_p|n&7JWQ$#~}V#>aQf<)=Nd8 zlNNkFEbd=l^SsgFV)cbds@@tqt8DA%d=xx3zh=+cuvpH`31%&U9NVL>njPukvb(qb zc8Oqg{vnoSKiR+E^S1wUuY3ll=9}B6eU3C9bkv%z{U*v#X#}Tr z)!*l4*Vve+xiEg-*Nxwd;$IlS5zxVmsx#^OtUu@d-@y~~M`4{hg&!742XYzi_ zjV-sPuiaAXZ{8MBX|x{r*FHw-3jq z#5a5jyS#JxnuVLsN4y3PR@ondtoQDI`L%ZQR(~^csWxU|}`tFCm8*te~(!_|a+UNzULY~zlW@$L~lZ#;eT=c^mzx9PARo1OgU z;So)z7yiHOBK|a8eaQc){=@z{0n4wKb|xHvs|$2?(A;vZyP&p z9%gfGsoyqZe`3~sbIVh2k6f4LUbeJu;=Q$*GS7R;4$L}SYhRS1`lIpZvr4n=+VKT% zKAYa3FLOk#XvU4+V~#bKHj3(UJhPmo$#zVP*Xe-#-TDK+K<9edNBsHz@5w~1=_{AM zJ$vQvr^l7^?RP8V&fMP1)1SR4>nZ8(6&VY|Q61d;e}Jy)F0a_xbwf zs(H0-zvr~5@xQGp*SmPh`~Mox@WSXtXT{g<7kH*HtLkO=aX;1Rx}RgF`~Gjd)gu(V zbi-NEx-*uij{2=(;QPPuV86wM4Oc%Ttl1envG3(v&bl}De?EwPo_$XGb!L;=C4@>t26$)%$xV?XzGN>>}s2yv0%QG6@6Ec5)kyI=kk`(Ng^u6ps#ecqz~W?~C!{>w=p zUh;d=4Nb}5brTZ{4)8SGJZSsyXU2DOtU|NZh+-p)q*&xx!*Yc@WxtNGvmGJN;1%(sPe>y%d2etEI@ z{DtZJD!Fs(esnfh{c~0}tok=oPHbKMxBKOv^Z$0X%RfvBDEN8b+NSWr`Tsv-EqA@B z(Kx>9x_Hws`+s+P8TtD){@j%|yYTe?j&f5yVRWzT$e$MJU{FiskWd6O{-z0=- z>(#>=<~qDKPj>8|FsJa$nb4XuhTj(%JwDC6Ngv!pVEMQ2{-3sYKX0aKe`rd0X zBE0hH!F^|A9{7btT*xf+u(w*+DObPlWBc#&AN;!i<~^U^zvbIGQQl{>UNllAIi-WQyr-X6KA{Taje z1MV^FPA5${e!0wXZCFCloh+?4O}cW6Lh3H{t-tMX`9Ku&f5qF?e_OW|w{Bx(oug_k z8K-9KFLQhPnTO>+_?iCS+v{BNdM^LuWfzO)#1($>+@s%VbLdRlj=07xVKM4lj!pb; zxzg@lj1mvcJoq$DQ}N*6Hi?-I*ZlrVopL*{l;^v0vf3uy?qz!?$ZqTDy|X(#e&X|` zk}|o14*PG#o_koI`Pe<}-BtbQ$_q~$mnl!#_v`&x|MQyvF4pw0T5%lu{P*YKq(4v2 z&AoEd&h7U0@0ZQr*Y>LS6ph*AS!#8mi9{&z+uDfrje|77%CK(O=??yd8Gxz*V{5kXR z1LM5c?w#jt&it+0m6ziBSp1mzW$QnD{|`PmFLCU_>ihpR=L>INc{F!z$sta4Hi?D} zyQf{NP=4n5ivLvQ&kyQiCegmzO5L@?X0(2EQdQ%ATXM|zZoh%N(}}K~+4VXP_Nm=8 zON}>H3)}qucyp#r^U)Aj=k;6u^ZmcrD1Wnuf9h88)QvZuevqj9x0t#5#mx9S?)?8B zRsQNAJn6k_!;%kPzrClf zfN#c^8=#33gCCPSy~IKaXG@)~-sn>BeA1ekTQ@~|iJgA9~_ZRG39-zx1lKkx69|7K<0d2p2Rh*W8I4S&`qurZNjnmdnX=ZrHJWsdmMqTyQ zfEVsF7^>HwZ}F&XpA>K9Dbe#UW_GHdiSy;XSCaiKE_T}cTl;O=)L$fY$m7JHmXvk7 z;@khfIQai=bpFzyJngflUw^5#-}!8B@#kjFSL2mS=Nzb<;$8H9|Ka*IkDlijvp@$W zZ2mQ?f+l}Y@BR7sy~XimE5$bNyeit#wB|FL%%9UYdGdUoU(>gV?|iTEUitNB-Nvhv z9DCjif8HVgqvt@{>`SvW0(vCV$~dp2U9Pl~IMHi)(B*vX!Uu)VC3Z6x$h-QVPUJTG z{p_ntVxnrm?fKI|=Sw|2{x$df`vunTtJ?N`ygUEm^Zj-De-GRLRsZ`peV^C=KTEA& z=KuRy_G|royI4`>E7RsIxti_2WQu%x$c&6NU742(-L34hLfsEeOMLKpU$n%QD@P}u zlGu@+?|jJn$HJuPDq>z7-xt~!emqoLpnN&m+ex}m`f^^uEmhv{h4M!e&P>T$&-qs7 z-(vRv>*w9^T3>Or&GPKCn#*hLxpu|wdTy`$XXbsLu$}X+7dii5UjL{1+>JM&8ZP4K z|JQG$SMR%ibD@sy-$Qo4u0DJIV&>Z28E+aMMkg6&b?K+vJMys}l(=7fJg>H_JRzY$ zN?Lm5!%wI6`Io%d{kAnW)ZG8ik0}4F?^_uoGR*RS|NdAseYxZZ-=|6wop{4) zJm;Fs_R9Y=UX_K0eB+k=UHj_uwI%bt*x!F&-@5Xe#jibY+MjE;Oh5Us{70eQ=K2}i z-raDG-u`~s?)q;mcmJOdle#1S*KxgThiH@8&+Ym#lQPiNTNc zpyf2b-)vqfmN<8R_3yV|o@~nn%{cXE{<;6}d~|00{c|TaetavoJ^f8{Sb6y+&)4CX zf2PlOd3{}P`MPr^(KEVZU620=oNzuy;dF(0BQ1bp?0MF`Owg39`8zS=v8i?^mVP{?xp0yKF3U5*D^zayzW}c@is+ z{SpSTJN)yDt{!`6Z}!po!rrIb+Fwq4f7dzsx0}ZNyZ&1}>O~fFY^{3wPka5obGv$8 z^4C0A{MMK&*id-ZlejJHkJq)bzSF?%lgr>^1bu z^;AA^%xb4^x_Zl3GqG#@ZW-(6KlpTY<*^sf3yuHIyZu}^di&*X^K!3Tza9O#vN7x7 zbDeYf`+CggZQ00mI86D)y_(D7%VV@5P3M2V>pwY%Wo28vZt}gd(Zn};d(OhUf0<1y z83HrPmn3cZcjWTR+Weai`)faP&Wm30_&;bRN7#>O{l7X_y%QIHf4O}Ay7(8e|NhC} zto8V7b|6xv$R{XM@lBFuZ{r>D3rbfm`L&oxhIYUCJu^8cE@f#e!^4Ly;`)C!2*wDDCaoxPu{?Qs<{gP|fR<*z5-_9(2$J!v~b6pI-G0()uEfQiq ztvmE&(^XAGn@#Wjape2&&29f--}kQ1FXwEoZ|7@EsNM7P+3d{IX6IiS1^?M#v3;Rk zZZ!K^&*h-)O!xLaGrxXQQp7;9;9UA8Ci9jM6~5}M?Seh??+X2#lVw}KhVh@mllKO- zDaMlTw0*!SH}sGF&igg1%fH^p{{vc3c5wdxNo~ivrgR)%5ukJNn{0H(@n_3lo{W|J zb+&l+%a`tThx9H4)*RfFD*MG+TyOd3^OpVRZfxkgwl3~nY{8%Tpl&nA|3u-c?{%O5 zmMxmWz%e9|Q>nZ)? zR-16#Imy{i7k6w+{GOI5F=1(t$=yV?h0~&MguF}Yh;(Y=vprXGrm06ZP4CN`E#Kt7 zd$#%H3U0Vn^M6;y!>!(P4%e4Fes8wvmg)64_t)3gF0RUaeeL(lXVLOYeDeKcEeypY zriZVMbC>qFpIcupJEybk+o3lLw|+P`?_Jdw$6bHhzh5tDF9WaYxZYp?`PuQz)jx#~ zZT~g>{QCtPgC<5Lxp`<*>~U1O=i#iiGGNb)h4249P%rtd8^5gaAfp!lTRx71kN9ej zzK?N`|M#JEOIBgaqdVY|IbQgGPRqUKqI^D&&--55WKCq%5%WnGT3w}K*LwC*k)QsQ zYa$0i&j0>|c4k-piXz=Z^|H0u_TN5O@cqBo9KUAQ{M|p>zxid7OQFk}a*tL82&8{V!VylDWzrI>!p?KtGo1ub+%s$1ayR zxl2d48e7kMdjIj+|NnMssapML>(;lKZTIn@w#MDPw;%z@{_iH&UXPT}@Nduj_x$C_ zSDKXQUH77{O@C&}#q--(of02jJeC@glVj+$w@0^VPIzvLyyWg#QOEY0**&yNdDFB~ z?&h%x2fKgIj7cz_?zu4aoR^sQ%|oYlgo|6cS)U9mgB<6@#$~b^bez|+jLlO|+T=E- zw*Oyo@P0bWh1+%4-0OdxtY0qu|K)Db7Rf2L0iJHkt}iR~l3A;H!k32c{}oep_jR26 z{5>Dtem%Kg+jsBtxz#UrJl>V@@seNEw7hgS6MMTv<0ad+^_mQumt4?4N|UtVwN9`tp+W&bUfuYbo-$aX{g*SAamZ1scu&olgelWxCQ_V)JR^PkUO>RbM9 z`EU8kwr_9l`7XY>aMllLaaniJm~`CFQ{k8XSADmBx#{#hpIB}G2RRqF8VO||%HKRs z=h&}VcZBzq@7kB{92ful%(j*5>@KD~JvFuD*Xnc2-#ikWb>o-BTgliL&?)tQCqD*O zecPG)t;S|!!@Y0owqLSPU18dIKdE`+hrY{;SkLM(_)k&)xg#KJiR($0ul)T%cTB2p zcIoZ<(wo2F`=58eSKQS&+W=!?lx~{p8U4XBRb)v$3Km-HNLOIk{{le=i9;O{f%*d;rvxi3$kat zO680C&2Z^iMYZjctpabI_y0VuInUSXkGtSM{eQ1@L1kI#tsODBZ(YNVW&i)O{^jBK zwdV4#t){mY%@pcAIO)1om_Y9N>Z{tBzpt%rtXse5lhx{Xn(I zK~*I)MU{}kWaU2aeymt6a&`Te5T>vzuRm;Dd2@9K}|hxYEY^L}4_{^?qe zKQlb;%$LY!Nm@Klgg>`=eXWMr={1cTzRv$s&Ce71%CK<0SGIx_#~1dsEw=BkFW4l- zxAXq|_N#JJ+O==7>rA^?wSZ0Z*pZqAmJfbQ&04)hG^0CvQDnn1rl_edmVrw@7W{~> z`cdrm``6O-cO3tJ{{Kh0_Pu*Qo6gRy8HY2aU&hvdG5mE|+Rm#!i{;*Bf2*5OH72G# zatn@CG_~GW`@Hzjw*wIqk9BkJs9k2@7r*yq_q9vf$@7a&X&$@u`tOO4hHcj(vkx6^ z$zEZsajQ^Z%e4fBClY^6q+T1|dind9JKu-eMKh1rK0Gr!D!8xu)XAlxUqzoroELnT zD!lUP8rSPmr8lq7&|%wexADy7hvh#Oe^7pTDSG|hS#{r6U6=NL##!=ag8bo-<*d73 zDkKH-H zFU)D@itT$J?=5*Hxg4^p?)4G*{QTtxZ{_dEZg{s%ZL-djN9Qy4@XiliuyIlOUemB; z(<~mjl&%Na| zdx5FQrR^C~OWt=fv-&GP+shY-!ZY_)w#KTguJ~N~e!2aR8}l#! z%&*cuI;|`)%gFm_#H8f1W68c(Ze8;EyVIu5@=E@eoxHr0LuQ#o-9O28?~xVD9md6q za@zg^B8OUoW(F;_x%+ADZHsyCTaF9V^}n___a`}^@L)sr^gctOx1|xg`;y-zck^#Q zqW&;h-!Sr!K+A7|w^n^0#jhPw&dbVQXSpC(K0o$IRDmovtN7-R%a$B8lbSL6OYn{D z*N>I2&5~YJyEt>T@AasC+&%?JE`rVGcWk!o21O;@BbHEJosqFE4w-N z|KDtWdGfgYa>dW@mw*>C)Mx!^{wa}L{Y=uX{k_S)o5$z>l(PDKrq{&A$Y;1y^{onal0`x9ij;>3rWX^g2XZ`P$>Cfe=PD`IJ1h;s9|H%LI>`ijjYrhoLWu+|k zpBMdRJbSI=?d;26O%<#2Ee=G9-R9pXHepTXhwob><9{8uWHDQ;Z?;)oD~dJJg>%=m z+L_y9GxpodxyPwJ$c##2KJ@tCox=Ht|6e+||LM!QU;f`MF#db^`F{7^zcvT&GFr@b z|90K?@|Uyx|A^+=JTJX6CFY3yx@8ePUc8)A5|WEKmG?^RoPJErOXrG`=g|*0ShlJP z>AdOd@!ybeCuPm<6CXN)*cKd}{A_X7JME)FU-m}tOPd_gV6fcyyv<}uW&`GvDTnGl z9A1~^^lOibU}E^XlP9%~IS7@t*uR-%)AIZC<5^$!76dZ~EByUjS??pGZ0Q+(*iY+{ z(&?BM;n@X`1nPMvsAxFaUwG4e@nT*v=!zonE|Pnn8~HD5_r*JZTD^WT|NqA-FSLbTUn1@eh#gzY$trJ+Y`~UAA%yvk|6hSL7Rb`@cVR;n!F73y1Chi_R^*7TH+%erLV@-oM}f zP1^STM~xV$NVMPfQC`RK!PAx3E=Q;x7pgeK|E)h=``?{3>9~q>M^w9|&a?e;isrt; z{`~AR(@Wv8>srNSiktYY_Ot9eZ)JbyN73ygb{sY$HZ}DRzs|YQzc-A{Xy)X@_Uk|1 z->d38J+4;y?@9IjK3h4~c@-q@`^0bOQvbt0ej)ezpUj^H^**_;YJc4Q$RxvL>kp4a zZe87fE(~&J^1DB7JjKq<^r2y<{x`=TGaMz3{hYUG`Q0{#HQYC{wfHK(Uixx8_PsNA zZ~w;L`Imegv@%#H?@&AEGjmNIf12|z&f^{*ZFv8^d#881J&iA?{^Yh*Ju_a2KVEQ7 zi6gQs_0G2n*2#T)AN`bKQ|sDU_@m$VPk4Ob+?hWMuAi8jZ(8}h$_~^C?M=@C?G*3_ zZBq-J^78hQ8^zgRa@NcYajlYFwBv zR-gN6bA4f${!@KE;Vq`Mhu_@WbD+t`w?fG5fZ3CF?U_6Olw7&8=|CD^ckk9HVH^97 zV~S?ypHKFfGGWWjqc?o+Mf_~%H=SbR7D%@F_yf;@ueyNMn!H-r2$xH{&X4W6n4sYn`Qx-0GDzUim zbNdeYE3c0Je6~!Y_V?`j3pyVjY*_s@Q(b4_wRK-ZGP1ViMepKMirJ#_Z^bE5?L&XL zk91mlEnaie&DVLZYw&TWMwQpg=D5p-D#|WSeV(@EwvhYMyO%GB>E%f5wlR~E@INoD zYH;MLZ_K$1(+}6@Jl_AxHh(eqxjIuzt1nx=?eM$x=f&YaYhbzN`0?W}UoM}|_hom= zNughBx5v0H4Vox*rp9~UbTO7Vj$Q0szH@d~rtC>+xhbUDdtkP{e??)xKpk)XL?fGP z4T+lr9xB?$rF?(>@g&>5L%vdx^TkcJU+_$y?=9SF`0MH7{*Mw5Bp-aM`R-ru_}c79 zX6^NlegBJp^n3oz?nM{7dceij_u&rTcUGxVr_h}j?s3>-rLn17rar=J9f+5Qp#h!?)BiGp4Ixl zQsXOrY)3a>Sw?x~H(zc2^4$Es z%j~%SR$8XIsc*A&t|hcwVS9D{*@`uPWD}OQPT+dDeTMY1HG)ynXTH|{F5c45eR1~B zttKDc`3r4t=yjNHd?qfxZqd(TzE4?hiPXaep6A?#uI#hM@9? z;Ee&QU-S(2&%VCz>D+tU_a-*Ah6+r%B@pHA-nMe-&Y7(Dm9#7h*5r4JDMxSJsVAhdDTz>ccPiB>e-S7Om ztDbqdQ-Ar_bI4nA2aj&v1RW z`TM79=GVLBD;IDSFMe+J;KO!G;ef{rE^_Q3L@0l!g zQtwuz$A;TRTh{*&j*`eX`)~0ky-{2~A?Ks`_t+H*f0_!`*_%80OSTD0-K)8GyKA*@ z%=9HWj4?AlH1{nkSYv3nr9)k4yUr1xM0*+K)Z1FybvxUes;3|E{a7iwUPUkccA4p| z2#rGB$-h<1ymxmt9$KpYWLMwyNrqowYvG1-C>|>#l@Y{R>p2&-#Ep0_1U!i{TB>7e6Jsy!k2p>!!UJoZK0as*9lML zGNgipIpvts3Nz0~?&W&i7;=44BWPj9Z4WI2^J@7se=baz@$YL*_eL+q)Y*H_oG()= zF8}eIvCeq=|NH6n4)xz>uD|e)*|9g+C~i~K#m#y*TK~Zor6*NzDDcH_%AQnoY7?_JF7J1@{#!& zACA8D&HlHvUQ_?!2l?4QF8Z%!*aV`r;#X{ig{t-yF6G-*9&`oVrn4-s{U@p}j0= zua!=2^g5VyjcrCMCZ%i>sYb%W#Gwo51PI`X!qD1CnZ?!$Th zKQsRx{<5q5pY`9v&-44woY8r{?)Po|3#R5Z(~@-Wu?upleUgcOy!YLY2V10%@!Ohf zUDx8+{UiHx`x&w3=zw!IM~ddW$!hs!(+H`i%vbtll}VetlG7y z{KCJc+9z}!Vwlu3Yg>N%{~1UAca^?AadG0p)9Y?LxmVvlU*=!dpOqo2PVM5-dcHUR z&kQf0*5rts#HFoYw{3lMRln8M{9XP1+mkt;Svq{s5TQu|F`;coaf)ofW?L(M|+%B?wfIMwcpoQjz3wu z*7|Q>pW480CN6tS>43h>!7E0QSEhdAmhqmuY|(rDbF-N4r{BI-W%T`);VIGjf{QE} z`-|85YMARht^LT(zR<&nz0_NNAAe0CYe};OgUrA8YiD|WOp%wL{C@qr5Av3OqRV(L zf35#yS@q)i?3H4QcmK=R{>xukJ$L%I`H$01&3m?WY0QG2i&LKUm{pvqS>e`ODsFEY zQ5LCe8r0)atNtk?_uHdBp2-SP z**op-?dte_;knAn_6zqOcCPxN6Mg!^_FoykJN+V*K67VZTCBrpbNyT&1KUgcS90zD zZyc$ASNZ(nuC3W8FFsuEy4L&U@9%%kfVU^eAL{?SyK3I@Wo?W8#vOiCeeQOgzxMQX ziQ4)smo}ah`}a)Q+jz}pkBUyKeF6p3)x?W@@{=kicztY*>l65Fp3{<8G0n6`Dtr1v z#aXkGLLTjSuwyHivxFPliyKc`UUC;6IJ>3jZs(SgK;zu%PvVW1cYp1zei6ESuAg?; zoEJah+yAdPZZGYfuQqvRy&?Q-S-ih}kQUcv zsyVXz*6L#utAEzrKR)}oX4tDKo$^*5CNf2HO%=RWAIPnlUvRD9M-<<|#NQ^*m;Rgj zWA*LV`hkD8o%NRaKW%yWrRsa%-M?IJZ#t=JvhdkE`G23UYfQT%aA8)F{j-Yw>p9(O z^1PMK)hpDzIQB^Q&&GpdBKan_7TjTY+P1e;dx6~ioqf4^Yd)<%bfAvkB4(+oAG@K= z1`X#2M{UZ)#WtB*h_3eAB5KZ*KlPc-)u_;F0r{T}cMpbaJ?1^VULd!8$DS6uwrkPH zZ6DUHHZ|vAh&itEkMqdKppywJ+0SUNIm-S}Ci}PH^Z?y3TYlN*ryu02e~9}BD!E*) zSv_C7WXH2ojbkt0yxDS|6O_;HKV1JnTf8{*UP}M{zbr>j%e`+6a_u<$)=RaLyD3$? ze#0B5jMfjcz4f+*#XYo*IxAnn`-cDby~LS!I%L`Za8-2vb25&uDWCP6LGkyZ@_7I0 zF`wBREff3R|2gnn*`RB-@V>p`fJZR%*wX=X3F~3H+kMS=T!N} z;>k5vmfhrA^7>oC6oZ3}wsey*Rt-1zJ4A`WF=Cy^PCs8#?HEYwm0Xk=&YPbkJQWJI(vG1 z=WVSxlu;pEnBTUeE$6K6SvO~Et8-zG1s5N*J$K^lhA+n~mo0B(O-L!J`F2kK(!56I z#-GZ?Z;cjbUK8n?`si(n>TZ=s{TJM_&uw*!GYWG{-Q{B4xxMLHblQ*h8lP}o! zAiVoP?c6q9r#1PD5@(1lv$70)cG};r`5P}^!2N6KIieg9OWxOhkgR$weSYz$Ltg@q z{+E6IXZ>OMBmc{{+qr!W3h~ijq35l?g>%BS(r;@T%nqLa(29BjYHpc9e7C~BCr)9- z7tNpCZN@wTwJ*b9#vi8RZ!W+4y>+wOp)W^Sr`N_z+IZ%nebJBlTid7J`}fcP^0&FE zt4psMZk+qt?85zjr^PdlYChkPx>f&q<`$J?C6hH5W+Z9f3cI-GP^snAFFL!Eo*oU0 zE^1A9cvPMxE+>(5TJZ+9#}e`h9OuvTidwj_--^$umh2X+`7J#?{42M&zDM@O(mNB+ zm1r9JzfQ8YJ-hvqPxRRxmhw|S26;SM>mU=HYVxdT&DJ}6wynJN*Tc-m)~rAOoZ{y+ zk*`Ouhn-bj@Ui?y?){RU2k+m#Tefq_&n!W^7!%RWeN+1NRM zmbSgUO+q_wj@2z2^NZIaW*P#w%s}Z_{El6A)cwZ zXTSYpe(7`M%|4rm9c(uik(F#v~%0E+9Y*{{g$Q=+V8IZD45!2EVAs;gzwF1YzNBE zpGw&zI^AF?>-2t`8C-?$x#}N0-BTth9IhWLKLiA=(ljct=^C zGvm8Q=PllOumAn?-m)V*l!2e+KL4MW;Op>21`X_?ShB(EiVl@_b{ld-s2PuX4@b@rdi!C-Hw1!*s@{x!t`#0O#{#{(oVO{hF)aGWF zZswW2_^|)WZLf5;gIn8mm-lTn{IIs|LwE9%_YO9HZY|BdGRr5Z*n3j!9LJ@{rmU4< zY^|Q*a#OF;P?8J%X6pnZRY5AJ(VC#L^R z()_sQkCJ}#+h>yADEH~azBqmUL2F&fUX8jAU+)|GcYe+MZMy163V);9ruxEb1}WNy zofN^R-0B!gZLje$R$y7ICdV;TXtEoF z+c%e*6an{Ue!ew(e|{{~zR@>b%_7+NTw|0lw?$w1k_pRQin*g}1s`kIXY@n$#Ov9U>V^U8x64aQ%dyp3O6`@5Za#yOkMK7HM@JanI1sLZ`86n#aUC;jRa-A9W{ zVx2nk4_O*;@7NyJdo*Q}P`m2K01I)s{Tmh-3rM$Ctcbd4wKQz;8OB*(KQ3EhyI*VL z^iwC7POZ>~WT(YvU&np7%F4}Eb$4%zyR&!pwtYVqfwPr;&;6R|^_vnJ2L$jh3ul?oeW*3{Yzz6z1iarN9(0AsIjrz-e zIn-+HJsbYY=d5U*?Y_hDz49M5-v3W_vE z+J^tcj(deYc)I4)GTXkykFsZYHd}AXh-!6tu=U#g30uY2oyp}6zIOKJJoDTmeaQ^!M7Vj|XboZoZKHa7}pDn*!;K6QyN7 zH>XQzXRcp=^2gerZyn!OtPHvwc<25b%ZzKG(HR#G|Lt6M545r_y?ATvG(I!C?-`x1 zZFPR!o&?T<|D=9IM<<2;t$%iT{<7fsuUS=xZ?0cb9{*FW>e09V3)Z>J)7@7yKYqLU z+^7?OIQRX^Ie50((q1LMe#(8&I`OXO)_$E2Qgv+8;&+71uRFK1 z^gOaE(LDtYt^*FuAhKIfX2o^m=Uv+n%Kq~;O>{H{>bqH=DT^4(LOYd!=XlwY&w zlVFpyaZ9*hL*=_Vrqv76_@CdX2#|QD8pvey>u`J5W}jbqmo-nhKjJ!~yYjC_xRJnd zn}^Nm=NdUL@-Tk8y?Yna+ldTEb~Q{*defA4_k@#P#+<;->(^)1+4W4HgU1HT_x@g#oQIRBpe@84yg47Y8+Un{Sh^Lk5d%HJ9#1&y1knW3}) z-(=sf81mt9rRwsIXO`3S@-{meO?iIk-S@QOIh$E*{OsmG{CIBnBdMruRk;k@kNW>V zx_tlBwKL#CvZ>yzMQZPl)u;FV6wbGA-tN(O*0rftc-Mp(i?>?JJl|ol$v$!Cv1J9J z1~bneOzGQKTU=3?a>TfN_UFaQzGh#|et93A#MPQF_Wg3f>G?D6R>~RNHP`=eOKY>; zIe88J=Q=e%mofVNJ$q|HimOIq@$Q-34@!UV%l`ZKPyFSl`@gr8{=b@cAU$?NXWs8` z^-^q`I)yYWLyrC6HJLC!Vf!MNB*xw5hu2E3bNdviG41q~oO2KTW9EERyp_-LaEG9W z_eQnfi3i)+o3!uDc(_bQwR4m5v+ox!oM6h$R`fp1>wCv-lVx|cF~_?L1;uWi%6H~# z_I%!X@{C;7ll|{ft}8#76vVuIP7&Acz=z2^+qH56iyj_$pfEl2z{ejci%WM-Dhu*T z%3pUgYx&Wu+HbarK9v#+{nmJX_E(>u8_!?jH+epTJ0tQm1Fu-{8G$G5o3@?*Fr(() zm)+?lis$*CKj>@wX|6I$b7O{Jzv{YtzW*CL>ofA@V@m`jyx%WoXDs~n$)^3BtkmQ4 zrABLJPLZ~{^P{}q&_=rEt__KF3gSVJiH^*-X!mr(I5Uhb8cUs<8%An|Hiq>&$j)X zdH;gy^L5Lw-aRb?YRZWINDtnWk&w{PdT(ZARj=jkEEcDdbLZPSD9sd%p2Zyv^%OPnF`CZ?#3v zy!{|A^@sIyFE4^0eQM^&2#% z2YE9;ezEAg)yFo!U5d}MGxu+`7JDW8N@v!Bg=Y#9c;@a65~{j!MDCQY@9yS}ZnmL6 z+nH0~^?Ga)Dy-i8`t^P7VsY(;!}U3j+NB@GmgjH0-e2Dv1hbysWd|Ee|*iod_$alP!n#q9qbg}C?h_8NDio?CsJC+^>)jNt8NxylT;vczjP zTmCxzMvwK_!|JQ0zZq0k&-kV({^{&Lqp*s9p9+rXxi&MB`}Gv$KVp$$9K9?GQ@9y{Ls zxTMCrXp^CTpbAe*&5YJIr9 z;F+1I<}riC^19smzVnA{W8NJ~H#j2e8*|qrlkF6!lKcN;-|n>wDol<%KIc|od*_EW zXxozYpC0+|PmQBrvfJ;ND_8wy`P94(0mNz)Lw;P={aJgSrJTl!D_+D;|;onBtaa`6+j zb(efz|6Aj;kAXzTMTVLy;`uwzgNoIQ&HoiRneur~roArA|94{P@=ZHuZmvD}<<0Eb zH)G<~)jXMqdeHWBo}_11;FGqe%!o-bZo0W9;oo1g!!ryIs!O-q81MDBP?y~M<*(J} z=~ucZ73W<0{+!kLZ^p8W!h?7CKU-}`lzDLA#|as=XJzg6?>_SXOY68-^>=^aw}TtF zFP7)+?$oP!tJ+vD_^y6&q;dEY!F>et4|EVB+t8 zzxRVuf&Aqk^0#mK_us3w{+r~@-`f*Xl;5)2{lV+fx$OVU+b~wtY%Vt!>KJ@)um5QC_nT|_#ilP(pr(+1 zCa76D=iqxmMb@u(VsbwJQ%tzLIYV@co1+Qa1+EFJezWAZ@Xq_Tv~J=x#gmI)Y|LQS zSj0A2#Ae&ggBO3y_&R7jF$VXoh==Ot%tI1bExDY8{v zc#7N|ulc|JZolxq`uyw{_W$10U!L~ZIyTjS!@tyT5 zvVpe>72nFK>E#va-=ExbV$RBAQ!8(5JX;aeZ)lU>{{P0Y`@2)Q@0NbQYZCv`V|T2) zmQW0+q1tcy=gNI|>-m2;@BX{{|G|@x#P6xKJd8hf@z-|Lef-@1GT!d1rj$v}?{AGd z>X%v$XkS%US*RA%cd-7z+Q^vMM~k%oF!nvvU8e2Gab9r`%c0dzsuB}#?Xp~aKCxbB zhh=uv+tmEco!gJx>V7-_|6Ti{Ql1H*@^!k_zncPU_6StH-t^^O!By7A0y;f6dvf zaMQcR*ZW_jo}PX)IU@SgW8oZL$EFo($_9PDQ!F-yCMHdKT%~=*BX!o3S!K@8oG&H( zYu>=^DE@rHpTv|!jcsh-FWrsbKT~&uvh(lEg5Skget+0&^5MnZ5F_~+%T!~p2z+J< z&WJ6&q$Q)gQdDREhHqVJV$VM=jM}!Kv+e93G1D0{kBD6`{{Q!E!#7d+WnW)ki+Z~) z%%Qd^MlC{pciULvB_FT7F z&f2(fsa$dhXb*qy|C{Oa77F`WbxvE7F24P7MjB}54K#IapZ00`22o3D zzNJpz_RK05)1#O2$+{NgQn8vlRx)gG=yzuR>M#&`cef4}JL|BwD#cGpkXx^7a8llsO< zON1{RX;Lv{^%6U2XJp>>EMm+0k3j*ORX+y*F*KJ?d-M8;LXq#b8h?-OwZEBewkRsD zwdo7IrC4yTN7*nSDtO&%{Vd(xerZZ#OmDiE$ri>|`Mt27!{BpG?n%e*WM5UH3y_Xz9QGXHq7eRJp8jd5g1m4yP_Fi&jLUV2UHl z$6kJx$jH{CM?=F?OAD`-UQHFP(OLa3HhxXlBAe986V3uFTD1}kw?)spwr!*8mL+eerqQ{l)6Vb?4vbYphq^I^_(be*XCd z%NHz;dE|CfYR{$#%oEzXgE~Gu*>*H~2k#Shja>>SL^{+Ci+ys5>yBcYp_%KzG@*0C z+d#gx-=?siuvAcR*SCFJnyT94@c++y`$uah*USDlp8Ee}fQi)7wzjrcHhaVW{}l`g z&M^qMu}!o=apO~)H3Ab&IM@&wojnnWMbLQV1Tep4g3-)&U*jTQS+;cT~+ghF% z$3N~b|M~vqyQTGefBo9DXXdtRRln-*|6KU##{>RFowmCj8wKU!1#}ktOp1&BE;hUA z(~EDbS=p{#3&?G;UwkTvD|I{LPVa>psa%h1=B!=$u-b{Y(CWEv{z9Ke22$^CE)jXk z)9-0#ZI-{}%jEADxAjVA`rHhXp8VNYZv7NPuHMueuen$2tl4OB|Hh32VR6sGBj=gh zm27?~eC5uh1&8@wFSv1M=doF`U2eX5LeGOTy}x~kxH2X3K;_1S?o{a}#-b@Zc$&9W zJdTTaypzZIUjEDJbvaJ!gipCR960gx)eiYN&Y4^r_slL5X}qJm+lSR&|IPM!#`4!} zAE`>L?)_*i^S`hD!VIAw^P(?%a$mXM`seR^`&--3-}+kC=AznA_?L1e9ty}WNVVTc8j!V|}DwO`--d{i0?u-BYMXbgAsgaA-CkGa_e!8>F zC`QPzzjWV=ll)%~?>%*Tl}Pqcxl=r;MOA0@HTd73;CTMY=)6~8YmUMV`3Zu@J|(|7 zYkF$!HwJT!^9DU{CK!L%sC;IEIM2FNXeH5*Ncb4ARw8ybo>plKM;C==Y6jTMwU>>YSn`em+FrXIc&88Hui? zR$&|RTHKYFnpr)Z^;__X;n&tj;_rUkU;XEP<^Rbgzt^A2&Wru>u)kPiqV9*zN!+Kj zU%zuuJd=3#an{7Vm8*lfy;=@El#&YPcJSUC#aL8p67^+`Yame|#D;GT4bt9dQ?|T;`mFp9f_4l&f1x+0p=iA!?%Ab4P+Vre^%ZnYUuhuU= z8QrnScg+W{8A59|a3rq^$%^f~>IAGN}sndT-UaB)~s*KFIj(m!hYt~_dVage7T@6Uv;JSzwCc@ zng7Wv<$4Du9dD5#*_C=Yj>I> z`@BR;TH!KZvsi-m%8%TV%FAAJDNpicuug8xy|V9xMb(#Y(KSZw`{lJR-9Be^>g4{u z|B@&FryQC+@$&5(H+EF3c1s9F*BV3`6%?N>vM(;$TIRd_^gUW6wCJ(D=RmxcTz)3;X26xLUXtPPwSLe7>W2?aTcypPaVyE`Gjk=JQh)zjv8m zUOWG1^{txkU)9dsWZV46$hB&r<-21$dJ@fFc zQoEl{C+xOA`H}kN?fbgv@AiE6iz@q+d1~{V#G6d_Vy7rKZQ^DXQ|(;vg30;_Uvk^S zInNazFAYk(z50inhw-bAO$x#aS0q)I@(UiDvt-hnhGfn{HqRHE1+*Mz)as@;uw2kG zjbPd$)vFT8X!lh2z24*h>z?|5z6OfWnJ50Ia6CL~xK#d4*h!_I8_e|ea(Q)i^GYY| zxyZe$SI(Fe5 zKF$98$(!;H9gYr|7c}R%WsumFs8q#Xt=G>LdwE%uGxPR%@UD8BktQvgwP?+sMROb$ z1{|)`&zX8vK_J3lL&+7*_a8krx>@F@2Y&i+^vkKu_x+Mz-CYuYmmqSu(`-i}jReIBYMYf9XOkwTIJ-{D-A}U|MeV^z240EHPyvY6+ zMLdoTBD?K1`vq;@|0w@+`%kl6?B>3{7cUAPg#GR?TNCg9`hUUp-~X)t@pJr}xPAAZ zeYZ3Iww-mThO|CryiTwLSJkC3M?*K%meYDIGR zUNV|^@j~t+mCV~(jwwR(b~#j$B8Yu$N$GYiT_%B`mXkAG35r&xs~s%H9Z*CG96sa&7c22 zqwhPTR^#7`HGY-RpKE&fr#w#EDskxb>C2*?8xEa*eBnXxhGSk90(Kuhn62!O@J&r& zSZMK6_We@s{iWQxyFY9)I-p&VfBoJk+g<66^KADD>52u)6`T-Ia!*)$ zTud%*_9I`zJ~rWi(+Qb}UA*Ur#!Yc*ImF$+Ue>bhHPeEVHQGx>YZJTEPkt6yX}0RA z_{40dx8A2x#4ecES8Tso{%QT=dbf}EoK4fqudRvP-FR<};MSBwJ-;K>0@Pf2gI-ym zWMNX|={dMnx8-j_t>%5hUz=Z9opd?nEs|nTxcTwUmuCI4d;T1mWVz@3iGa;kdmk56-OXG+eXjew=&V``;C0Ihr{M6r*QjRTCQ7i z1IvVN?*_TJ}EXqqNogTKNS(Pqv20_hrmnc%bNL!LH-RHR45c_DA=7@ml8FueF!{ zwRz7n{>&#&l`kw3++yKvQ55iTs@2}Iy>T(Z3EC?!TfaCgezNM}rHPYt z7%ka8x$T{LzdPXdw~QGR=QOi?c@+}xSy=>%#AP4sC7UkYzIAJt^oz4U_e^hD6LVJb z^xvG_M;v=3+EOxhEKL@azcgcg*pz$j@j>-kvCpseoZ9j&*yQ$!^UktbU()YOaW4eoy8O_hUvME-jFhR&HTT_1XRNsm*?aualP8P3<|VlJ?q6qWN+C`w#bbmNC~p zzE$n>KVptR(dYl`b~gD{JzXiH!mv9>I;bmu-+_7ee*EBQo$^#T(c^IPyXEB4Zw>dVe=I$3`yirn9IjO~@Y z#KT=x9I{Q1xWBl*VV3ngx!lIbs!{C&|a$4b} z={#p`m7d=#)<3rs*miAFj`8&XcfsErj+-YM3Z6@22)eSjtoBH!gCC2+QNEqbT-WtHob*FR_Nngf7n_&f3Y;XOdyTPd zzIW30p2Y?!F|UjKCaqt}zpZJ7S>OM?$NxXyS2ej_Zpw0zup7eD_I9c%g_V7lz94pU-&j^-S%z{ZR;v6 zj$14VXY%hCX#f4fet+qry_q&7MamMJLVL<8)(se?|4fm!j_tIG%s{XlduKzv#k) z(qk1$g8MD{FZFzUlEgeKv%T8>;?8rRR+{AT|0x;Y-rO|Fzqd#0@a(mmt~J|ug4oUr zM@eWoe=^E@S5UcErPJR0@5HjiXuXQZ@7NCistl@_`8(|S6Uojc>6%BnCl#0R?5pl4?IszMdzG=nn$djn-?&#s;<~z6XsYqP>)(t2AZB(D{l)v{A zd&w2c_Zrggx8?3FIUjE4I`{kCqls6VH-_CWt=Rrp@%_&-KQRv$_6Z!S&mx|iu(As8 zWIO7x?%@veEeF2z-dW-Gy>Y^IQJ(3-tEP0TCO_Vn{7=9lQ$I1cEWT`Z!oQi1?|EmhTfS%OF;Jh$xUv4iE%$c|+vTcGXcTNyQvW?c|D65*nqT+! z_Fs;lU&zL*ShY#PLJGR0wX2HW6Pnotg4Fzc-brWPFzb%^7K7)s6 zLLfJ@$`ol+#WQ6dMyCa$1gB+Bdd_`RR4(r41Bn%97b!S19^30sBc^I@v*T_|dy=l@ zUJ3pUUT%_^w}LOUI|)uJK6H|g>(VLp`DLJ@{Mbi(NiVg@Cgtz$Xf>``wDf{-pWt>+ z-bu-aW_yb@7`>J2VPRj}&p2E6%H*@tk5)DtyiB@lAadc4s-m?1{cTDeTbue*S0~@7 zJ%1sdN7(z_kNdCxtgoz6mSYy5{|*b+)&XSp+rZ6%S2{eRc5N^zWrJ zId}Tsy7`^8^Y_M2GcK&t+7>`$cw3h@irH48N!OlR2JlEbi6giEWwEm-b>h{)Yv zD?VN@itG6HjOl=v&7Kc$UOoJIVD+!pkE;F%-kV4mAr?vAu(rJ6qzxlcb^ zS^U*%e$&Gz@yGx7JgVn^F~ON{;kIqtUQLPH#ucHsge`Nm!IRBA9oO#ata-;r1ixZmPo1js_*}RoK{YVo9;BrnThlO%@C@&+L)Ws#UD`6V|K! zS?BowB|qbT-+9(m`q}R9OYe;IBPY&EZHzqe`O`1)$ICKS=Epo{)qGMfS5%U)_h=Kt z2KI$!TYq#d7w4X#psciH=0&0QKb6ig{o!-7SMy)qwE5h^&+Grq?|OLd_4|E&b^m7a zPnsoZ$M*m0$4k`>3<4r)E)1MeH?DuL_9)|UU!Br;{BO?I?Q32HtaELcu=YzXx8tIg z+kc%BHZr=jxNvZCxmcKsM2Dz7IbrPh;{3bQAK(ADANwy{r@mml_Dc5X6`ys#uPwY^ zfBgTM2lki!{cTFDb8o~f=;V0)YU%ViFZ2Ju%u8~PC&gY}c%Vyf(E*h;lBe{}<$F~! zggm;N8#FJj#boiV!_RW2IM3RizQ=4^5%;N+?wWtLl`zg^czC?&8V{@JsnreshW{6C zIk)qx&E9~LqmQ*u>F?Q_(VT5|Q}NP{<7_)Nb=qFK<;*LoZnr*0kMY#jipP%NdX43u zy0fmXZp`ZKS_-RA>s#GkJvHTO=%dGvB-Evq1!wBAzLHT_ef=cuN)yw}mRAoGS819R z%&s_|BPD<0(_iJC7V1W`qe52McBvmfzF4g?>Xr3F;|uSvPMNeM+h>-FzxyHOW%H*U zE1Ya_w)5)3x_xS$H>P^bQ&i!+*Z2SA=l^qGzguj2e&2!LOsh6zoD}-?!u?-snH$6G zlRdrF4}=~+-ji{)E!$^@keDgQ!4(UyNBCTJST~JP?Zd&H`vljosA=BSHf!&Sy~}%c z>@m3Ede87yk6Z4vsOrU4b8~ZY5)8B%JWS4Kg>(nEWWR5fwkc?Eie=vWXi4zP*608G z+kXE(bK~ISzUkYlemd>a``C0?{PIJ0`AD`)jkOKsGpgDbUJ`R^xe~~7f#bUImFtQo zdrU569B^(CGx=^@!C~*tDRgQ5UGWoamK%Q`75knXbK;^?uG<&p50f*TWw*P=GwCqt z@4EAz+3(`xJai5l|&wqS2Cmy zdS0IS|0F}C;?spISFSwuAx-gG+S-B4`|Ci)qSGJRUFXJv;ixTWnk@%H5 znW3*i!GPmO$MMzEyH$TiaO590J^Av{zO6^&b-tLfuKl6#GXB$^BU(C@VPzUJ?~g~F zI+j)~@hNlv@dF><|CrD7f8{&%m7jO|t(x|2t9tpa>aM?!KVP>k1IO```nSLI*6od6 zd2ijE!%@F`Z_6+3K36MznsuY)=btqv733cIcYpIZ_``ULrfV*zlkuYc5AKTUYX(hn zV2Vo(T6iT&KQ4ZnWW%dDnwlp*yisBgcbc`=(8X)v*{I5?1{ngkxK~_txffm0bg}Qg zZ}J@H#o+@&t~2_*!7E<-eokJ0`ju5=@igom@B99HepH=Q-2$V!f^k9GZRDDx8yB>VW2=iIr<9IxwTFVwxMz25edEJlrj~Bd(w*0$@fp9U!EwRuD#y8GH`nBJMS-trSqTNiB+7U_{eU`ruNzsT$7I9 zzB_x_#)~}dCd%#N7vlpe8ZXRI{vYF%Df09pU%uFtG#UOV3He1AHZ|_K7|gKXfRp7@ zfwY@huG`(&-JEAmwTXS{e$G2@LyM(yiB!w;2a;u;OD5?3Y>|l*I5bPurrK}Q)$r{R z->R#AeLV{r_h3ICcuOjiX8{x96&n7@sa z^;vbWvzq!0!8u=()H=G0WOQ5;ghigN(aSHm)+&BE^8MbP%5CB@7ktatJTcg)@M-DE z%%JDF4;|M0P`Yp?L3brEpPq(fOCWH3ndXxYv~JaElT%|w`^eI-ghT`4;SOZ zFy*eVS6joT z4lnLSFPc)c70)VkYOKDVFx@eEhpzSpr8QH6D+(9hIONRDDkeWiSV8-l?|fm_@LP*| zpZxm9SLL+1CNix_Nhf1qz>#BJd=EnRolA9TD zcj{}FfrLH|Z z7xAFNKIQ>G2e+&klTJaz6}PplytOwv&dN+$(&u_n^u6AE`EP5&Wge|@p49NH=Uc#{ zjSu#`n^eDGv;8;K%_nYb`Ezjhlx?w_8h#(zWAxHUK{z>)KjP(MF^#aLvpV+KCH)H$ zv5|je>G*p0-F^;(q=Vak8_#2|wt4tT!1krShRRQdaGC#4e%!D9w|mzvzE?tS)k0x= z3(p#Zvs2lh{99W(U%wBF(r;xDt30g#eyRDK&uVtlHnj%iJdX5d6nVdHisq?TQhN;Q z9xE^9{{1Pj@7pZl&jS6%VfWU^-qZPXaK$v^Bd@hHecxOR{nVnM*KKn^E^>kW>q#y( z{Xub-Hj&kt3vZk8Mk9a?6u6- z+qO7wCn6 z!xzHlJ@5A29=9boGvSi{r3iB2KN&qe%Ki{td)uWn`Fj;S^|v(S6Njwpo< z9h^y<6B9KabEiJr=CyOG5!1Iy&en6U?kGg`PoG<9)}DCNCRzSP=XaYA3p6zoe{En9 zo-$wH;gN_NTc^gIWA`lDABf4H%9G3I?(8b#pI%!8u8-<>^m{Xad89clAQuSI@&Z2f+D^7OeG zO8b7y3d&RDe=_B}$0=b)PPU}YCpeof@wA42DxVzaZ`oqgQ1Bqkl#er8%!{{5@zCNh zIsGTRE~ScVPj3I$KTRZL& z&jUXc?=$RQVk`Igl!>0@!Rz+Vc4}UWw*vPicplW}2VC{iXA7Ptan{ykCd<>3z2BK1 zc*k5Td^$r#`ks_9lSG!{?WO}~4mGJC?cMr%uKMY=&DTs9IjG6aS)<=3B`vh4G(YH zU21V&(RYE^)`Y{CyN~92OgDCV$ry7~NlmreLBqY}D67}HfDJVh8=^U9ZcvZiFEH(i zYWkk3w)=#h>HImoXz#M(rSsWWzf;tCEgbo0>A%DYO^O9Y^#K)*k{k?kPng#1+C0xx z;$eXThq6G6P-ohF12wU_=ntE01ulujad4J1Gctz!n(q-%1nK>i)OS@?Rb|v)_R%fe zlD9X^1w7K~`y+mLnXc`IKP!#max5S2;lAg7{m&P-S@)kwo?Lk4TeM}9&@CQU@virW zo~ca}zOXEviCOee9qSIR`R{V>wU~E*;$CdA?bDKkpGp(<{`0ml+P3l7iolwMZ`6c! zpUs-{*iQFo*tv&s9|gSS6*SH%w3v23nzOA<*+TWhC$q)t^zZGL%3ri6NagFYEk*A^ zy*{3Y^+sEwAD%ta(7R~q$v4^4w;V~_eEN3MocJcaf}Hu!Z~0XP9nos&oG{D(;y1DN z`L-+P#H{_kGye2e{f`ffbSqoBkyy^rXQQLIp*y%P&|qJ=f3?s#3eCi zZ<_X7KHa?T&D*!zPFnuWMZw-&B=LP-w#sY92*GE@t5~i`{QuCr!F31!o;^&z6n+{M zYHyKb$zFHm@zfYu9kVqY*QXcd2)ybkYg)p=^q@7O{o|g?=PjD=eVOP#{UdY1`l)lX zdqVG=+bZ~M#ebmy|7r8UHg zQ)9jy6j#(M%49d{yvS;QN>XEEO5gXTjus8u>~7dt|5HEze^FoMm20oqqZxAFMfqP1 zIJx@uM-z!}|4uAq;J(0O_u-S$nVo^pKQOHg^b$+F*xg_Cxnqu;P7w#Y*Q%qzUSda0 zEm|8QZ!QqA>Cf7j=o+D$u!(;Im!8@Bi}RQEFy7-5J6CF}DaGsh@=Wo)lmlyXqP8y! z=44qKAi(KA*+y&IrSB!b-yN)E)Z>i$vs=0Q%)W>pkHr``&vhEK=<57*61pX}<5^Iq_Ox$Tb=Q!1*ztG}IF@JdeV%{}nEv*vU8 zFK1>=-I93M>K^~P&IP|LxnG2@{&(W%?hiMd7PhL3Us)aYro=4!oT>ZbD?#!P`8U`L z2)6N0SJ$fe&TyXjs&lu+XC+Pp`X=TjK0-a`u)+H@=NHicToNTd|3 z7WTK>+hhO4nDGR23Uf_^%MF2rPUpOLAJzRQ-xA9-YelW5a^u=72}de9b2lG;enEa- zF1Nms-t8j&Lp?ILn_>%OWXi;~@20nZ1dU7YKK*~z)~kQ}IDA9>-p#Ws&uONvPWr$5{l4ybFJjWqu`fO#rW@&G|KsRgug1BLHMaVV?He4$o9gi99r8;e%6qC9Fg;0~;=i+w3Wm*t(a$()EdW-um^9_g40Y zbYyRPWot1Iel=B$c(&3MD`<_wOD7kIg*mo4R9Bp%FQdZyiMn*5io z6SFHiYx-x|Jf0@b-P*_cT%t(!c%H(xwT8FOPiV=w{YyZGeO~hJKdCai58Ll!>HFXL z)Bfd-$(kPhwl{a)j+^x@ZSL^}I(s?FPrbA^dh$+Tif7k(?v79;sc%j-BBt%^H@R+2 z%t&HMUq5&HZK3P)qBzU99aM^#xmG_iDzS6x+PzM9+%$JZ|GH$?Z8-1ZbAdfBX47_^JJz z>G36IPo>HNwj}7@FfFy&;8A$y_N3=k_XA}fmI-ReLT7Tt6abRhRf{=FD3+@9@8qm0$h%?+cHX+ow$)NyWT*{C98M{fd3J7TJDZc+7Ir zsh{&?FLq6|Y5#dq|Hi)cQvV+NY99aZ_rw1?|Ao2ye;RX6t!!1kGikwQziQr4@4d}i zL!yJ7AMetMU;FFfd2#NN6KgcCUTxDfwpzArtIV}2+Se{zxM6X%EmV4|1mBaa(%O9+ zqV{xUeGCo@-#X*k%Ga!0<3vK2Yd+oBy1mTid8=iA@6#)rxkA?eIdPlfNDbj|M1{!_ccgj6={|9Ia2 z@*(AaU9GJrZglJnlh>N{Hbe1PlS|>1-+`;Hx4wM+`gQGd#f}AUKg{@k??U><8wY+X zE62@_c&(GC|2J8liTfAR!u^eBEP6$qm+^A^bqYAVd1-vial36BcNXu||DrF)Ajy30 z=AMlQrs~Tk@9C>Q{{P(v`$_MX8^;xMnVS0Ay-&{%ShH{1H}kizW`@^(-VN@|1pQn1 z!ONpq)$0GB&o7JPch6H;c&#n_#$xMr*Iq{TE#nIIj+Jdy3JfbV&Wi27`aWR3*ggA{ zFy{ZfdnU*&S$MH<-GdFuDRX`uQ`&Ql`JzSi6~Tlo+xAHo>i14~={#w>73DAHbT7|) zPvfSnyKRfCmu)jhX`gz=aLu)Rvzpak>tnQU2Dp6Y?akV=bx-_)!daIW9h)MV%)eG+ z^@fG~JbeMpMpqW|a(q9rv!?D&&9P07@34~maGdo@DJNpS zdG@qPx_j<3R8+pcQk~&;CDO)^|Ia7AsJ&ICFaP#xwnk`PogV)!%?>>KqV}Qws_*{g zSua=IEWB@67yaq+)i(2J&r_*}4_k$LOm!G0ZV^peBq0{Q<4*eGx|vI!Wt_C-XEBhL zx?Q*INK<#K@(a24&;xzU({Ge*6zfPmbRg+<;m@Q;x~p3X-&_Ct&hi3hbo!0#K zKK-@*bSXiQC4<#z=A=`Tv%4;5horTbb#51rZiq@Wu<^BI}zu(m@6S^$)^}J$G-)YJx{?uLN-r~M4;W0l|f1Q}V|KRVA z2b+p3B=>BpoN&FaeoKq^tCy8Eo97)5KgrnfOKOscdhBYkiLp#kt^C)j7*`7&+Luu5 zaIuw1$BlK(A% z+H%yQuetLi+q0;x>ERW17vvY~3Qy1%HF_pdSSmYN`&||DVc9IBkFPVX--*_^8?SM- z=8<@^V!!AAiB00Clqcu!*EzC-dyCn9Zi#}wNhz;?+_(PIyx<30X6$zZ+dZEIZ^=HI zwfEKWJ>j=^3jAsb&=6U{zN$F8VgjQ~!y}&5@UF||nGM>ETfAB}?@2h}WU=b-9Glaj zJ)+?&FL$@9$z zMn=6E!fNN;=PRY${0?pz9JY7($XDX9IyN}<-|n#WrI48H-}PmxmPNwf-EV~FFZg`# zJ7e;mwoTd$p(}1Yc=|@I_E=$HNv_w|lFd2-ni*Rnu8Mn!DDCq8RAFoPyFxo5G@xwb zH|L9~0$!SyE3OrU6dt(MKPUFdEiVmYksz7R#aoiNUB!(Wo;*8cS)=kl{G7L?lE(I+ zdmisLdy959>8SR!D9q+rx@~re&Jw@YB2B;NS^a4l7w4oI`_JAzPjmL~%8hHlQQGzY zWWd6R1sgYRd^IJ`_+*t(%`~Rn_a^UC<2*<6N+YuE7GSPGr*l_;ERzpSM?u?=n+PieV&)E2K?MA5=({tWk70x}zmCY<-?0M*#n85w%XFl3z{@Y*h zbGC-ZityhL_gDUw<6Og(#@duB@pEs431b6e(```OP~7TdGkxOB?Y<_u5H*MHqB zYhP|M+IP)%`>}|#KP?UIZ>f7Q_#IKwe_jxCl3U^XEvBG=2`fu{y%HAi3OMsF@LI!? zm(-vAcUr(v83XJ2XT|QZoKV%sxv6RiT6ePQ$NB@`54xW0O25A5WeGSkKhF1D`?z$U zQ0=c%`j?i<|DK}ht`Zlw=55n`MUgX|+ePD_e98+q>a3_-YNNP9Utcg@?$F1CGsY&} z)3?XDr${n?4Y(FQiR1O|iZs@>kqj#*g-p^f@cUG`@I_aU_R@Bj%&k*XET`4xxC(B& zeX97zhILPQPbdZ4N!&C4y2LUMos)ujJ3X2{&r7YVsybD?;<>socmP-9pSrW9YVq=` z?}JJ$_tyVT>$^TJqNv8;#g7VKO|=-_yao5W&rT~A`h51cm74V1tS(-e)xG}&&ij7l zeG~ck+s`STU(fOK7e~)ysMUXWu(FTS{r)Q5x=%Jw>9zUUwiJ-v0mxg`-V?uJb3;0 z+2-@hvN(TQC!bo?T@&@>uZKoRa;)stD+>Qtw4Lu)Y>CumIeXD+)#{VQefhPyGm1-< z9%pR~_U-OEZgu6eH-F)~+#UP+l6Xr^3=EuW2A-W5{n&y!x-7{nMWMtT`I)=hYlvQYNj_TrsOgC}dJeEffBe*6;M=xqfZy4TDUD=O~){l~wl zUHn)=Lh*ykQ8G(+?P5E3oj2&id!FA0GwiqK&S-19`YSg0^bgse8%@6Uw`YVfCzxtI z-LT*ZV~dG${q)7VuKf7bc6^1K=B)!CUPwzli-}zG_D6#6`L@@-|MWZbXTb3(p_cw%q&bj*M#QgL!Rj8u7>7Kee&58HWh- zU7V41+idkj`^~e{R+h&&^O`>Xvgzj8k_>NQS)*(R+nt?ZcE6;5eJs5`{p{n{=PerJ z{$FpmhV(V|e_FnJWfa%5FZ&+y<}b*-y)AV9W$x*lWoN%FUMf75OTBycoD&Orem>fF zIi&4b>YY1MtD@A-^nGRPyggf&Y2mU-r|*JEyQ+JW3O*st~ojNzQ*aDjj1(vjr%3LceYHr8CDu`YqpW}nabVIl;&T4n6>S?gAJF$ zH=YQwYDYfSuWe#JFXjqtDn9YXz_N0i?4!0?j=XK=jE{xt)SQf`ZUjx(T0fqDTxgzD ztN51nf(yS-$=mwWCGniA8^D$ojQE1LnN`Y~Gztk=Ngrs7|QJIC?m{I=6Mg zdhYdWD$0*u&aSSVDt+cU|2si`{Uc&8cOO4A>Db2JUp>_ZetkB|`mWvQ-4C1W*FE@I z^WlR9=QtYRb9PWCvu780hV7ew9uIHxlu?hpTHze+qt zW1nsFJ^62QQnX_Weu|%(*Zb$*yLF*^@)-h3EIIPuoRH2lerq22+JJ>mu<6X+pF%4- z=7=BUXtC;5nsbxs>>;BahWjbBj?G8w{^e2W>45PQD~m}R>imA zh}`{X|MXM;>$ET4TYuTQwz%{1%PF8SfU}k_lj}d-m-*N|tJqrX;?w!RG}={?-+nvA z-*sr_{gsTGKeltXPS1Yx%(>@wmL=%1YJp>ufW(TrbtwQx>V9 zF`Z$dkZUN1*D8&zPTQKa=EX_R$=b5=rpD0|A6;wXqxBjopme!m`L;0mW zUjp9kl*`vRw2AM~tlV2Bj=Y~$yTz*>7lAsAY#;Y8pK>x~6@UJ}H?8W`AFfWjedkZ) z%i=eyjx~rDfAqOn+^l`w^3L^wrH|(XB(}3pJuCn8T-x<-9nZDIBl2d7yy1V6K3!q$ z$>QDIbIk6%X`N-Mdc893#DUW8(=vKh?^dsCG&&i`$Ns>sXx>!ib+O+c#r3C2*B0Dj z5`OpNzV{#g)|WSw`Ip{)S3Bntvt;~<;4%f@?C`&h_kLa59u`~2@|5cs<6ZfcOWR`F zlrFkyeaT%M(7fqaUw?6*zSyOmn-_&ha}{Z=qh>qqQ{llvv@qjYf;wr!KcT_E~h{;i-vAlHF4!dDiWErNyUf{`vYH zFYZanUsm?VJUN*LZlDPKQxD$sVn%Y`!xa-&+3a1E$;}vc>&Bz4JV?KObC@etU1O$)Bcl-v(C4ckQ!dg|^;2q!`veA)aI6zuCd}`gd}Q zURZd0LClKV{YJ~)7yo%*`%k%3-g*TWaalLSbp!<;?9q# zj(p7d?T^z#*B$=dRcd{UfAMp1r!ICYYMUxz@Smet?DPf&EA_{{GJLFulrHD&Rjamt z@zyn(Z+cK%e8kkmK)1i~cbFFFXimOzYT7TM|MPczTD>JDuu|f9|dF9-Fs=^0wm#ww+slUfQsdhtI~F}uj!g|`!`P)7mU_;$lNNPy>rdOfV%cC$Dc&^Kl42@ zt)fQIjz?eLW{<+heV=TedObWRG%2S#>1{!3MfSs)M@@5b8?+|$-qK+?uxi2g2%AF+ zoO{^})Sj?26gy38TyFEjy1nvM)cfyWlyX-kpPZ?-&zqn=LN;b%cpL>`D#j> z4;P!NUY0|`X_XnC*Ec>?>zjSTr@=t7Gvdsu&Iz?MO}^;o6+22zelAgN#j55p`LEKB zy;o*5dB?q-T@^U%NbUK_tJOoZ0$GzU%l`k}_W!7N|3ic42`A+)6l=d*)c^Og`YN}Q zWBs;bel-uSwr=8P6@O^QQT)vEQnbc_2!mNuLPfq-Z%j})FaC3D;ob=z|M~@{#J^K^ zU)FPR#imV*{NzMpB6XhLaLaEi(#d^g+O!$8{NL0|D`DfYN3}2a$Hh*&zkBEI>DShM z&ysPPuz^+4nuph^d&+W$jOc&_Bg?d~ojnPe-Cj+7u}z2S#QIf(S6T1eq54#Omrm0I z|IUSMUF{V||8}$a~Y%@*iY(zu*5je%}85 zKkFY?KK~eH@BX)QTV%)afRevkKJTgdBl}e&Rs6M$)2xS2Ww&I#{kCeW_^G^>Js*?g z1EV9#FM5TAX{vXW-sH<~YZpIpw#+te<@#=agOtc^i!OLoDhAdq?VT&H^R#=3ec-H% z`WF^1)z3_C4=uJ^?>D>Y&xI9*-g|7~?AKO3(tGt*yWeQX3*YX_*PuGy=W)IN%KiK2 zPrbM5(Cqym<_TADZ@1WeVe`D|S*At3r&RUw-9B)A-F$m?;IS>~yf4l>JepMe=$4sY zaj#QYo8GcphKI8zEzz%=R%iC>sPW^6m*j2K^ke2E|8kADNwf8%(g0#dPhuzCYyMAn4et@`fj4ZtF$C zd)p5`PT_9MxP8H*eRrr*%=F(&?|Yr!BxtpGV2<;DJNSD9RL zt5ZGTbd%~AtF6m!v$%?7mz%aN^h;%slfC5@ZEPCT|6RY<>tJZMwbHUSx8uGdp}oJt z#rb|0m}j}UW$475c6>nRNvnpL4Ct=#snrr}L>(jS>R>{}R^={p8yV8&7C4-@bkO z>ig#>zOvc7En7Nj|J*4$`ixtr+q@G>yRV|w{&?Rl-$@5z0*jA(2iDGT$@-)oHT$>I z(x#)wV%O;k7d-4fEzk10SuuCwiL&q0WB%<-lVAU6({z^H$T{Yw(IM!=2g}_{@KHS#p~6-%6swewfnO_{4m_0)! z@x!G@%{K+K4Lt;AF;=t|r0rx||9-^+=d1%gYP~X@t&)l1A2j;gyw1B9R@(gj`@UX( z>o)xlAKCKX*RI|ZpxQ9~M%bSP+Yjtxd%fFNpebl&$xLyFb%p~~XJ(6Ma2Patk-m_r#Esmv+ZXTHNbl=Pa`)&1(|Nr-}KK{zfxm_Ec z>~++hR&6;2oCf&*=zFNVc(r=}v5vC~PI0|Ecl~F2RnVJhi0zc-QN3Wb4=k%@$Y5I2+OYRzo+i>*(g}<=U(VR|#b4EC^#tOI6m-KUry7E4{U* z^>3oC^TFTQ+mG(6=-oKAYG2UmaE|8>YS}(kB*%GVWfxCUzUFZ7zUB#^e%qUoVy0z} zPA(5#wDlyYum|mR;NZ%?I`5vgp5^i7XT6m+d1kb2%aiRcJ9lgEHUo!Z^I3cSBE%+b z^N4u%d*NGc?xu|*5v7X?{DgMiOwCgd%Rb$rSld&#bepJ-jOok9OPfnKZ4vmjPIl(e z$C92;ZC_@zrry`6>rs24DCQmLer?lof$_E((dN1`<@N-R8HxB1TT+ZVR!mRw2n70H~D zyXK96kJH5#{umbrPlpZryyv>H8E#zjn`6FkgU;<;=fi4B*GAla#hkQpeq!|hi)?W{ z3)~&{FjVZb34i5rN+MDr>s>fogB5d&P|Y*}oqNlUEq#3dWBy0^(E91i=X|?$mucm# z$lTbSX0qAu!<_AZ-#lOO8Z<}s^1%Pr=X;y4=l{<4TC~~gxmw?)ga@nUne_Jd24$(Z zCucRfM+MA{dVD-<%B8h67R|4I21FzVxV4CJ9C!9o)LZp+`?`&ddv{Bwwz%cqds~~i zDm>fTW-r4WohRHpd7MW#)hBe=?qcZcx^`7sL30mtL*PMS?(;f(wlFLf63}kGmvi;u zQ@i%EfZSm2`|5H@Z@4#z-b&qJSFrY@l(+5uWf{!h8JEgDcD*7|krT4XCHk$w*_l%{ zjFoaKU#k9kX8Ql->h3)Y_V&K}_0aw54AX##;9{fW|H+D~=;-Lx-@iz$$nkDY=W4wH@{BT_QQkb-Cy=R zI39n%V9&1luWue6U7l{Rc<;VSyFC9R5sYc`wz5jzi@5Tq`MBuLeF{&0IX!gUyWeu7 zTi)eG$8@G83)t+*J#~q9-GjfP`uUUjths3z`P|Z`tNZWwvmVaumsSb%{%iphD@;Q^Wy;#P_7G$Gv^mF%FO(ZiF)M93DEx%YrS0jIHw*?F z6xPOVU-;%uWxnOqy?el20T<9%Ck9#jwc(Wv9<-(NR3nV4|b}oGwwY^*B z^OM)VeGi$uTy*qWONaNqo!b*8t@Zt@S=b+)JjwHE;S=`Sb>;8s1L_Sg>`M?W4*T7c zvFzgUHwH{TM!SE>{{P)tA2CaQ?@unP=Vuf==*y6-vk}D-xg5wdzZO%fVQAZ zj)9PX(-|$!U;(>`j?S%9V!4@?s4#2H+E-xpHOAV$H`_LfDa5;ax8&sBRoaQwJLKQG zo_O?Y!n6x2=Z#*>Ph2mzlq1J+;f0Uyf7mD9G`c0~%jT06{@stgB}3ql)b*0GUGMil zp1+Ly|66YHX}0|J`roYVF3#%vyyDD;*wZorUs!f~PBf?AV3 z&+7d%OG>8LUuBHk`(EUp)buT7$=#63O`{G8dM^N*I+&K4+Z4M=ZsZ0*={ zYOkW#mDH8nwoQDd!5qF~-MqsqH|$WFqGccVAV4c%Enm7=0B=iT;>NsZdlL1}WPe)| z_Re~}+Jc2?F`C=s=AB#P%)huNY6;^j)jywqxUpsa`{TS)_2#i_)ts8m3?f1Ia^pE> z3k4i&W4Gw>J#<+`=IoY@44zNsIpz38cFdKzt(fz{q38JXiw?^~s?y)zcFt0EZf#;%obb=&vNL>UB4Hyaw#{f z=)>a->zrcaTa>vYM4hHu)?eo;bbDd+&3#ky9lxxb;*CW5?ZoDOEBw6|A ze4mQqw*(+}fl6F=FQ~R-+QOskow2ua7fzZT z7r13_t1e@jO_46cL*9<;2(^@+pysai+1jj!lcJ{yW#%XBo_f`JjX>Z3l@IOrXt><} zbxgG6p|#)VV^>1v9qB7buc)>EJk$T;x&!(GkLL6o&j^{F^4nB?-lW;)^Wp+4_6EGz zpjpk5#asQkx!`%LZeY{_Rhd0TZVqwB+NTcU*49xch6RDABP8x3@OtK68oEW@txj1p@Q?x z2b0^u3JzLj5wrJhec!s_$ivcCJGZGXP<^=ll%v6;=L~nezBgAt*<*8h-}*}yx%*gn zuYP?0!+nZJ(Z8S~my+YM+dp0d4=SDg&;0$aV|~=-u5%V|GiLF)vUw-OM5sS}nR_w0 zVNrLfc=Qq;&rT)H;FgRvEqazSF3V3?B74+SVd<5ZN8L|}b*7tJ%`uXT*I4r5+9^XX z9#0G7^S(<$4tbo=%VlXQ>gKoEDd2r%cJ}qxQ-wTZ7J1mld&xPqIjmG_h&UMC_hqdY zi-2~gQ1x+X|0U0_eZNpCF8dZdQE6a^DPquBjCdTzLT8H=2_a6?|Hha7%t7wbhswn#PTT|-mgLT}_iSbF> zHfp@cP1Fh8RI%b8*J0jedm|*zybij)c=NFg6_e{9?OFfqf8Bfh!lb^kUuWN&uhP$M zYhvMkmfUaacmMy**pe?7i(huQKAn~#u;9<}DMj4bx}w=ro|_$#i`Q8)Q$VaoDvzCY zhi264;JUzP9M_#Ex>==!O<}uYA@FOClw2aef!zkH>l&x6)|}IrJ3+V2l4Vz1y!d-fGZ=g*)Q@Ws&{Y}~1JPuqAQDr?UJ?00$72Q~>dw_k9tkc1b zw~yaAF824t44MDz`v24Ce7w~)Z^=v-Yv!w~zU>QL9riJQ5xDg*>%aZqm;PHm>g!(! z)bGChZJ&*7>7$j_y0#VF5YnHvGokehefUm6{Snr)-!}F{*jc6uc&7|!BzW6PUFhqn>^ZQUr673 zR`}G^`%0?>$7bD_l*nYi;3?0!73MnnzYpK{Nwn%g`1g-?hh~MZzw00#|G~=Y|J>)` z@+A4^{#Va}ST3*Ac=2*=@TUsC`o~wVy%fE^OQF(pt?J`ldQ-B@PMRtfuTP2+*mK5Z z;`6BEriqhud8_W1z4rcZaoi(*m44agX!|8boiis&-QU-9d!20SzaN&LQkm?x#_iv8 zHTigL-+#>q@ms5(c5Z&TCD?Cq`nw4=c{G1M(hh+TMRjDHk`QqUCD>n zDe#8V|0x^HW%{?CTl#I**;7$=r#Zs6IjAztv|%=}5i|XJU~{7KlqLn9V^h9HvFuA& zG54PxGuz)Q@Z{&{XRJHz?9 zSykq5hPBo2`nUYsS^8yGxV&K^m($bt_56D$9J`TyWme>qRb0wD3x)69Fxov@xT>4Y)ya>?eZMCgre(CCA>vY2S+vTjext-<(p{ya1E7o@xtT0|>rm!V4 z>$}s-R;HfX1(wNskBfC>tzCDn;X|s%;efD(Pt06i^IkF*i4|~K6gWxa{sj-_X34Ng zsz+w7Prf(BsQbC+{t7*tJ-Vy%TTGR!cx=uG%QM8OUHLI_o9NZ6>MieTzii*~tbg@X zb{qfMac`D?J1lnje@rcB>+$1r{=EJ%f8(F}zsKBHMsaEF-8y0I)Q_vRmVVzXJ}>9n zThGUh+`>UA5hrG`Tn;-vnLF8tlPNe?E&e;_S@|%wozpH0pSf^%$8?+WTJN_jIz^vP zZ%(wb?JNi(`yVw4UE?zXO zD(aP=c%kjF`?~j~_PZPB{n~GTdH21qdR#(ctrI?daXZ#_MM?bWC08@WHc z`Zo#nBz+I)aoomavs&>`-2Ifl9MdhF_UVfq-ry&Z!6lxdA)j=#+{ac}F>*&3kNJnD z*br`wZ5iP!KK@$YoDd}ZEZ|jB)^+9t{;Pb`GG(4$-FnXbuWC=xL=D|J^&NsaIR~9> z7zL7CZZws~u_$CR3-vRyXz)!Ao>miGAbd!v?~;_$2eAe5C(So&YAkUS&=!+Ev(?bF z>|4!>eqEMRwg>;1MGH)sW4QDD$NGZL|NF}KG|!r)#$K{<`MhhjuDh+}w(NgBy&Tjc zu8;Z$L7l$^ZQ(8S=E1$~$_o=UawR&ozmE&%un)$}bfqJ$xh;|YwXCa)SoZzr7x1GX{wUiX<{d)z1iY? z<=&q`GnKxmbFG_VDD>#Uvy7g@>s8lO?h7gszGzUr=lOQCjgEf=dS#XNi>@g6I9a}C zzFF>_1D^ud?3^mM_o34H8xKJBCC{^ZBd`2kqpseS`yak4syeJ{@h0NR?}<~GjxkNo z6*m<7k<}?|Uid7;iPcym*=1kEN3V^qih9}px?W?g>BP!qos9?iDh2K| z<)rm@KECnskhZ7;-+@N!f6LqcKYJ!o`*5=OrL*_{vb4YUahX{nf4A!O+L9lM?HAJ5 z{o+!dd8?+>{w<}5t+e8=MHQ+n6ecZcum zcyw=$;u2pk*Bh1*O@BDKo^=%3uT+fMvElBQ#KqOK{w1AF?O5`#!LIqK*@s4hwEoNY+QpfYO@Ag|Gks`Y@#cs9k017RH;w+yo~Pq0{v~zcU0MI< z!rFI1=LyyR2!Cw4Fc7HZcXv5xlkL&&qxK+A{d6JsyC2_VLOGqHZQQg{d3oi_8{)_>!%n_l1Xsvdl#@j(_uE7-3cev zY?If9o~7(dk`Frc7r9E$k3RiFP2c`g>X&!l|L`Wy*sdR6(}P#lcYJQm@z-~n`&8oE)8loU=j{8x`1OjVTU(#$ z+TEJ&-xBbA)2wcb&!sM)TaU!0xM z5OmYv`BxpAHO+OkxBX|BFT6ENX78UH;Xn2;pV-`Fz`DMvcSUA3L&ZLmSIg$eOK#l5 zD{8~TaZvbu<9XwSx0G7?1?}D4YtA3GHu#}u&y7gB$?4H7!KfI*|0oOmVTkh}m>RcDd zrS_>#QhJHnk*<|3#?2NzJCA+-_DE)(<3rI_iFvV(6=JRCw`U~q8~&PPkku@+cHZq< z7Y_V9^?<*v$VR$|WzoSyt%6nGHr~I)Zuf)#%U1pR&Uul)*>7E%{A`Q5B11&&ukL)% zdh-7b59*CxoDEvO{nv*yPcyz9Yj`syveQCtJ(%-U?*7FkcV65)cFERs-t0J&e=CGO zU1S%UZ#gYy(&C3NwKBACcpSNt9rNT>q;;3FT}AFrx$`1t_r92({7~?ApSX+aj(o|@ zmTof|_&7hPMqB?|KBr!0|HmHX7fUvuTf}>QdQojIYXk@Pf%Wh9PJg%WbA4vc?<}jQ zJ=;H4>MY=BXYpWjnzeZA)<2DpxX-WWEm6G}t8wgx)6c0FGETA|`xzPaQ}%TAi3!dc zD+8pHSjA51?AZ~}HoM}Y?DK*&jl*kRD3x%B<+PYzPn)tS=6pjKM|PW%?A2*^Zx%nl zAYcD`!$Z;A%5jB@7!3p)nSVwJEO;})m?QRP+Y)t`qY67(dIMCNd<=Zf@ok*^I6-3e z1m2Dy%@#ivJIoB@&BEzvLy$1<}bEg`cmxDp5Z$PgOZ|C2EdQpS?*6iBskgd^e@4<$ zllU{AZ_PT{7qG}v^RwmpJ@WR>Yy1ByKVBoTh$SIJv#oi9qxQ{(o|?BRx2{v4@~|?l z#6^>Rt&9}!onDV6joY+c?N?1ncdV8RJ@0rfpK+y!va(6roanDTvdYOZ_c#>K1$Z2{ z5V^Vj&!NrSM@75VYU~Kw#&IqwuomJ~r!8t#FxR&~E_06a z?pv^;`F_p)uwQ?jzhC&i|Ci~mXW7>;=$hYiu#WqbHH+=(eEq+tTfc0ruT^*LmP_jZ z59ZGOe^O&FNA9|)mwP{LN{+Nn_l=dFm=e@e>To>a_KWysep6;;L?~rl?p=B$UFq`t zJM3%Y*RcILl(09ztMkYlZ>H@LOgC03)E=Cas2ciMZcl+?jI^hw@BYO0`(6umWz^2a z?k}hlG@bXg>G;DR^W*+#dtW+qlegsFZ2zTwti>Nc1gg&Yri zpk%+e_Tt5o)4JP#Fs$v1+|(iNdQw{Wp?ix;%bZ+kspTPAQ#L)0vOG7nMf1+}DK`)7 zjNI11yopsmlYi6at1c%duDzS)STbAi+mhVKchUD&D|1c|`EoLjximjzh0OAKx3gI1 zA7A|WVGIB9{|Ar$mp*Toy?pDga#!}IL)^bU@~>aMan}lP-%a(uMfr_GttM-Z+0A)n z$R%}c3M#DZvLSF0B1_R#&S-nYCuz53_u z<&j>T?S8Y|b>sK8DL?Z1R3rGPM<&KGip@fF&)%Tb;&S(vWi)^Mw}^MT<)z~ack(|~ zO+D0I^Ju~?_fA!fJr55l{QY@UWbOaE%_Z0S|IM!Z^EUsI^7s4h(c5w~Cznpx-Cf)* z*79Q3|9|1TzPw%jt4L!msFnTlPyElzw(j|Ifo?i2=GXW9eVhM%;m%}J@T@qh0oR2RX0_i+1vO$iD^gKSZ`tO^Zw!mC>2I?+e>3ph%7bBh4%XDKo8r&%RNPCq-AvnG z@A=~ut)kHaMU6}4^gTW`FMQ+Gr~gj(>9@8Qr#0LQ?r!(x-M4wl@#=*_8mm+pwHBsW zKc8B-F6@|oLg9=#>og_LJm&hAdc9R-QgK=Q!GDfry`I14b-r`;ko$C^>Gy8uzW>b4 z|5ZPK`f^hL-vqw0Cc&JapPq(p2cH2m<kXTUlZNW2(>(|fyJZiV@q?OipGc)x{-S>f~v>cS5 z-YYq$eVpl)q|CGGs$l0Dovk%9*9Wd|)7v(QSx2R`d}+wU*!kDby*>JOfyRMVQv?-- z@)v!}m~m-kTvXxr*e^G1_dlo_+@<_(qL&NrD(nexd~5Ws z>dVT7YkN%{#mljEA?K3)d=c zOWQa{MIo;&Jw^R!y)65f_clAm;5&OZErjh**b`h{qPhqcS{ubSO9Se-PpPeGsK zr0^sGJ8Pc&^go9mH|{Y#{=f0y|Ci!1KHdDW4(!GUBg&8K=cV}11C?!UUF@Hk)AtlbBu@El=}5RY(f>})i0<1lYi zFBkJ-dpRfmO|hnf>9ew@KN}f$*M{}<&9Lv+wp?(9|6Q}8r0C*+X&GU)N3@$ozV*3Z ztxztkRcb4|IJ zCoiMqZg0HE^FQF=?e5PCw|7UsjN9n9J;J8`*JJr*@psFl-_NiA_w!g*e(;4uaW5qv zXf*wh+*7ag{_weZf^V+R+5GGN%?M+P%WfIL2{=M$~k?4m%>_7Zqzw}1kzWV7F z=a_rEhgTA9nS9jajsBgWh+o)UD=^HQ)SQ zDI%)$)uf}yQ&1s%`kuXKx^KD1<@Eg9k!vYl`_03`*dkOD7D}82cy1g)@&0k=(g3L{)d+S&XkN-b-@W1-{9Zi>omQ7NS zdC$MJdeQR-fA;_Te7yEKcq~l#r+fU|>}wa=N?&J`b7Ys@%SIFg7*pb zsnyjh%kN4_y}9Sn{5SK|x32*eJKC#$yVVx47Zo&fy?^(i_F`rFCHtCBK3=Qpx79oW z#ZTj@|0z@My}iBNctYq^uY6VA`#e{IjNXc2L> z5^~Y$VYQ;d@1-m!rTQP&r+wbvQ3*$Km}Kh3779Elua<7n@dm?+c&R_dCTe zo4C&faL?MmFY$jy-_-lQR;mnK>l-4bo?m+ZF}GH5!Pe5Kb&GWvv%WPgi8DB~VUnSL z;l3Al^Ov{FRdwj?{Sx$f##~O|i(=JE?`LMrnXEWl{z9kve82ZKcORDA ziWUoWU$b}3-pT)E{<9nXFTVddcGa{dulL^idqehK`u%9VURw4cmvzO3y@y-R3Trg4 zzhs*qEy&}WV0%C!$aWrgO*-dgg?Oj$EV)l);_CFS_)aY8S$}(vmp!2dTHU=MLy04x9MKre&;(w?VrEzmlWTx6r8_WE;LBf$>%VjrpBY=2ffIW4x&JJ4OD=h){(Yl= z{&mSh{`yzJpao6(wvQPegN`P<_2o`feAT6UAzJ0vuCHTQ{B2p%?#=rcUzyhXP0_14 z9$9kH+J9+E^}&<%egB)Mffj-^R_wd|;;d1`ydMh9jg9ZRJZC!^&t$ddikMJ1`;FbH zH5}h8o^mUN37yW3f61+|$B;GYzU+T?vHvH2zkTO&vaS`ycU|r9Ay|F7WYG zo8;N|KD3!nQ8Z^SUMBD=ZEmiiH=(7MLwz8luDv&!x?6P&do=6PKdEjJDFtleok`6%}cFU=J^CvS4T%$tN*wB$M5#<`}#|}CyOie zG^iErO=s%7c1Lc*%^mCl*Cri^d1#q7>Eodi>&yK|*0Vpp!0o(mMv3rszYxP|wiXxn zvE;6v`}qHXr}oodEj+jN^~&mO`L#Fi9ofpgJNT=~ipDk1RNdu8%V&MOzM zd42A;x%TACq01@NX=lTjLmDT{mJaDDUn3UwWa8|(ft_A9jj?SFA-O)k&0=CAso(rv7Cp*o(*?F12rPx8vpUGu%QuFo~ z_yy0repqZa#|G%7Y+WP92yuVeyetx%i-@A|%lzi-8{E(ON3QRj) zyv0;_>AbJmzAD$|Jv;8_;^nk&BS`|+rIcjLXBt1rK1zWvs6Gb2WUp0D+n%Ke-)RHsN* z?scd+lfG^CpJG%=EmLI{M08v7^E0)4@p78XTN8Bcyx*ZmYD&ldH=e9_ zcW1Btb$R}>=DGXI-)`NqY;*CU*Whru@#FaCUAuN=TwbOpb#}@3h{(A*tn*n@_vp$k zTRi>D;-#n0Ei0d6);>*c^MRA~pnd3aAM7Rl^UKT4MW-;Y)>>MAd++?u(Trc7gm!7( z&rI5K^;}(GQlP=_%ukNt>=#co)ixZNBq-C|Y5xA>_v0#W*PPrcy!Ys*glK>8K*x=H zn~a=KpR?%oi(*Cr_!0SG{PK|6}u>wY7J_gCI?ov&+1gJeCG*d+=AuugW9yR|AjyEPtfbU(8ZU(=w00V zD!s)ijV04IC1>r|JGSD~&X~rchiY9<+H0aUR(C$r$US-G@}C_g?dI=HUaJ+EG_>!@EWhT_a;Q7`%AC2*+_#Tu1WsSfx^|wNyloqS*Aa0ds`)c--_%(+m#vfS6vPSE4X|?yMwJoOHtgVTB zh5KH8o>LsU(r!`9#J5`KyIKWOdAXaGu&^pxf5~xr=xkeFSYf>Oz5j>OmM-UnM|U>( z-W6?(XiA>>VwP^p%~VsZ-lHGu6F$t3xZxOHUv|H`^82<+d&+;m29@Gci#2w<-w!(C zIsb>g>(nm+t9DOL?YI}1@9Wp=8qs;wZ0_se#~GnrCSF{9LG@9+>X-aqMobZ!@+{l? zS$nN%z}!F2f0X}uFZqA#`oDsq-i}MfQ|%m~r&hE+4}Kc;OZ;9jQ@-*;!BbbxEsJ=& z<9*c-0xQ*I>*?RY;?>Xd7=hPjQ8pz5E;e@+zW#yeMk z5$BJdr&ro{d!^yS%1;l)SN!gO5-hSZ`@^kO*)w)D{>ibk514UWamCDAZ$)&^t@>eP zDE9FB$NCQ+?bmOe$2a@Fm;c{P;jbn6ORugDzdZN*9ryeH&XvD3|NduA72BpO`#uPi z#?O&@{YoSL%d4M#z2ZND#rZahn|t%d@vYxvqAK-zrT^aw)2r7%v2*E9dDIxAd-Tf3 ziiI8%qn--NNiUx@OX+Ic;gIOy!(87KH%n=F?0C*!|01NTva4(H;u=0~i*K)sUkQJD z6SYZ}!~LfX3sX)jo6%#D>vCBy*{6N>xVSq%?{C?wvqvL0ocOZbcmBcjPnB2Sdn@wQZ+F6mG1yK|q*ul=jOe$OL%_byPw^!<!k`U3!J1dro5X61T9L=;X~)ns;{2nwoQY!LJP7>lgmC zMOkgio${0qRA0&dpZxs)>+QClS1$!_{_F9rwzgL0p3I`V3iFpTgt6sDPLRk;WDq_r z;y>l9U`hLIy9HmfuUo8PSaH6E^K&H25__BTb#_9F)*9XrYbl97@m(i^H-+bq&R0es z^Jujx*>itS*%z|k;`tBzil6iMH@v8QKHqQuzK8c;te(GnhF{#amc#QO{B+%We|`PG z;*wLR`xkGH|D5&f&v84C?XmxzZ|!-?`R&#tX2qya#o}sGzBi1&Ii9$Abmy^WD%)LU zR7w*h1JiN0ku}tb)wyV&&p}tzI=K6w-%q9n>_98Js8$kdDXT+KQ-=nU- zwJ6>fzx&x)l~>O>k|b}j9M{)p*B7fjDbA$GyrZppLe1MB<$o6E98kCamR0rQn*N3N z@BjLPR$EFeampz9own=iwdhFBf448+cg?T4y77y=e3iXz!FAm$cQ<$1?iAX_vsCPq z?zhHMI?;-6yl0q*#0iK!@M0=GKW%Ht#ohP)?(h6&6}5fYBy3H^DBX9BNanxtIYw~x658aP6@~n25{88k+PvXA6+irt{xq8~; z`um^er`OG{{ltG-Cu0BW*S1T`;%>cpe7JJAA|y%w?)h)4qF^50dvDL%XZ+P0?}XJ< zEUkEB@W*QR!V@f5}*%fq{rr~gxPlGr?Xh4wvpk6qvHE}p!3DWB+kaf^cP z?Oq!m^wl5Vzr}Ixebe(fi?Y{6d%m6*y*aqWL*vBfNyQq5>*rcoiCx~b->$XKc2#p< z{Eit_nuogdrraqpJN^4(jatKv9@ZO^k1Txg??vC#FE8$vU!HYt>XqB(r&df8yLnK9~@&Eoka4n3dxLE+kWg+u9EMO+?kd2M&={Mr)v3?-KH34c$_ zwY~n)-uS2e+b5G>I*ZToa@?@I*7d_DHQlx4s}63Ii}-52>#zIuaBv&8{`miU&*e*w z{l4cSKCjAX>AdO-C+$Ja3ls4C(RthNF<)Pt)&KYEQP8I?YSl(>Z+RP8+B?b{Y_;xX^(kMckMNB;kM9#rzy*Z)GY zzn%MQ^SnhqieaI559}IZcjn7Sd{ITOGE3gASMyV@&z zj#)x`*tnWju~e4+k&pOO|K`-{mouI`ahhG$F5qPoo5kqN`2U&t`o#X3kmknkdH=V* z-u+^x>FS#vwLhOuzifTq$?qTNME&KA|BLVcQ3dz#HlMePwyoM6yZn0EB=$wI*Y)#1?}D7N-^*R)$jM&2ye4^)QnH-ZmuaL6(jrEfjtuBxbZ!|sM!#?k4&W&X4C{Xd(yX!ZJkQg8j8A{WF?b-NW0 znxwe!>FMc8@QJs5|7%a%pR+HxFPnDunAM*T`ulvkt)9&^gZi!w)FGQ+`AqU&SnIu- zpRVcBF1i8%TTw)P}w)MWgbIarIpI;I4O5#PL!|O-I4zid3 zek^@$qoz1d;-kRvf}DlzhE6l=p2u~ zqo?`eQ7-4y$K{{{E05M!)bzv^#<16Z$dC7*zUR44)mzQu7YyC?76cqtG%+!8I2vW@ zzSx?5&&ia}Q)E3Ze<*0n$}Tdo3e(7Lp78SWxk}}|Iy0C2`c+jN7of#@QK)1w>}{lU`aJ)Qezz1s6R%U-P8h$~GmJpI>$ zGBK_d3(_9V>4`qpSyyOhR=#fI3;m3D%X6G3t@o7P=l)f3&&~5ipwdRZ=tND`+3dam zT`m0s)w36*l<7pdY!k?Yf7*VpN6}=`shOR<=1Vga*3MshTq9m==lqA87$ug(+2wzo@$=}5kBj6%tzQ2h{Tkh& zpFSKdxuwm&`1+g=Mz79pyOiiI=l%IsROX5Z=ThIoRr)DCLE7DGeGewLxR{@t>%RW~ z>+1ox*F@fHx@&S;SY^wBByI&I6^?~-_daM=xZt4C*udM!`uBm1SFp!Sj^MsSVg_sW zZ8)DQ&or%c+llD!2{Aj;*O;ZTXa;D!n%l0R;uKS``1a^?-%>T&33=v54wJ9f5}HCy`RQLMplo1 z$iIqqm)^B)rm6Vt{rgKeb?0q|7Gxry>#v?&y)tT+$IH8mKcvlDS3*dor{cfNsH!Q!Fb z!mU$3Ztkpcl8Qd4Y?AmQ%HX-yjqNhGe}?b-t@WhI*JN5}M)=P($D%*yWB%D!9u>W; zw!bVsSisYX$~nvT zLRSADoqlnrJkBM3PN7ulBE!WWk_`E;d70=u_g<>#xiwa&M)x6WH~&*y&N%yrm# z#LnLPt0vR=LzeeyFPZ-`Z94lvA^+`ju7&lAAN0>3jBx32iVwn zY`c8WY1^USGtbv_Z3hRK<{$l-T~qGWKK$+B6?5L>U-HwYn)Q0>FP?zYe%8io}S1tYYxyze=GI{>rv|sz* z+XBZo?1vx9{AV}&zrOmf^mh%D3bo6tO3zJx3v;_0(SG$tT&ao531 z3bo#6)px2Ti@)xSkVskMAP;WSo~{pHHhbOf#gh+D`B<5Dw<<2>s1)z2l#6UPr)B8; zE4wi>;_lNMZkt$cE=>Ja>9+1llZ(~$v|nP{J8ouWS9@2Vyq)$vv;VSN-!@JM4aa{i zph|Y`$NuW&o9y0&1U~PYng*^0FVFqo*T?tkfV-Y~?v7=9;;~(nB z|C>Ch7h-AMp0#v3`<$<84r*t1%ui)X4Z5O`Ymt42+at$-?fSL}d*dfAv41;jdgqks zCnooJZocyd>Gr;YJFwgP%GMv=+CP24yXm!{;`P#>{%ry8r|F$L|Mc@qJFN?s(;m9c z2(Mo!vMu$w{7E|ow;8Nk3z7w=2N+aqt2}Q}*`qyUw(4I_&li&0Eb_Unw;fQZz2(&Y zH)nQ0e8`W8JCj!3xc2e=kM$?&YyMw-Hdl}3$TQQqbD!M+?FqO%^ZyYi|A$|tmruGP zGi&D`cFoJeMW6Z~-``UH$6xk;b@QsADno}Wew%ceZrqaEm+?YqbF8Ljq}t(o;=J<0 zyxj~M*Y6}xnlF4ZaK5Rt+JyI4h0p2x+`T`iYv#r!*K+l3B@P8I6fw?=R(07R)ZKoz zdkd(2q<*+Q&BA+U!Q}OE@0X?Tsg0PPnES0U`l#2nLc1{T;`0f`Hy`PB@6=UcS>KqG zZRy`$p*n>jIbOO;_mOVDd-T<#i}cPP-OJ;qbX=H2$jxKZn&%G`JwOdlmp}R$x3_!g zH!NNz{`JD!?RTHu0~Pik`1#*oo&UGwXhu(o)zinR%Rb0wW@^6Ho-SPq^6E!>-XHg+ zL#AGueKbO6brZiLPs-cRE*(=B7=$>z;@WYXcXLtf*M9L+(-u(x8Zk;(C$>3U)~Adbx&Tu)V{|86pu!Kj!P|mb9bxe+|OQ0EE9Dq z6R$u2wR?9hx2;&w$>2xTx{M4>jV+TnJ4^RWa&Kal-1;M#+q>Ez#z*7D&IyLyN9}Jc zzoYD+K6Qds<({Yw;E;P%AAh-odoCNpg=ZnB_SPT&zva2T#3h*}&z`kpT2KD5-X*#^ z2%7yE|4&{zYt}3+(0yd0xp&$QKdY6E&pXF@o5kbNdEfdau{Wl1-;3Znu;E>|x`RL4 zxlUFFF~+)SRe?q4`V!6cL!t%Fm>Kw0F4OnB`|&;KKywAo+-ZL!&Nc7YQas5a;zesp zpxN_My59R*<|S$;<{sSV zQxT=`KWo<~{XZ7}zT5x5a>tEbIJW$5>ASzy|CX!%|7M}xZc;r_nBV4yLhbu$w^y!S z9g>*f_EDZ|>e{RIuk(|4ov+7y zt*rWgf6v^1f93yA1KoHTXPl<=zf4N#+u>HZ;y1AB=zeh1^zB*x`q8XLOXZn5Z;FXq zdZ@|fl{Cv-d-gt#E$^MW|6}pA6sNZ-nKLbpbQx>SG|->M^_n$tQ?K?tN%O*z1ps9PO&H=~MUw+l;r}(^*4&!p&{CTlU#{}27 zZI`y|vfMb-_GNd~Q?IIb{_~FhTldue_nJ>ad}2I?&T%0DCnlyh9awGq?&TSK7Bgvw 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 F33DAeRbB|^YQ7o_IcM@ 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 D_T*7t literal 0 HcmV?d00001 diff --git a/doc/sixel-wow.png b/doc/sixel-wow.png deleted file mode 100644 index da481ac8dce811601d377086651bf0d5e62514be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119970 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�bIpf++}=HfPWAto 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%0Dw`QB;&u3LZpoM$xo|8xE0 z;QB2e^VXd{#(ctXe@sF&zqR?<96`R@mtI#qd(USzulICG))%?G>?_Wszp!~`{B!?( znXi&H)hrA*rMkayBnz^iux^p7&Au9A`hDBhx~VS?-MCgYyZk$2&OKM|T6u*TD-YdB zSoZGT&4gvYn2)&&FW72Pe51VVhMT6(<=5vKRJN_#ko8VkrQ*AuYty#{bLO4ice%Jy z=6>Yve?fQe&)hs;{O>~klGLvIVlSevCtovj-_H^I_|R#Ouz&L9$L9avp|Af}U!FmD zk=W;-Hao1o|NZlQv4+KUwK?z4h3hly@rXb2P=_zQtc*{Aclz4-M_;Yr+wXb!X{O!D zZC{NUOs`EeE500lk1dfYoGo$DnT`I_+mlOeWX`9>E;X0bNt!GBac|VQTf0C0)p6RN z`{mdlqv+gor`E@Q+x)rwf6UAIGY|iMcW`^f&MUdkpP&8^Q6}biP4?aO$fT+l5tZ4_ zq9&Uiwyg|v%}@Va&wp#1ZOY5q9olhQj@RygyXojfeJ2LaoXxgJx@Wdt`tV8O-uXWq z4(Hp2v>pOWF7zNN6&={$ZEDu}|>u&g!lFf>EO9axX9ATe$Uaq{W|< zcW*E5Ud+i#(&m^nUd1b^7-GjcN52(Qka8Kf7pC_Rb=_@pJECn_1E2 zS3WN&l!*4bCcD;JZKBlnYqGDeMVH(BeaZT+;Mc;9^TU#C?_X4Z-Ocbr##x)oA*`I? z82{~dQPq3jxx&QcCv{K1q*Ty)znsBGKqp716r1nmA7Vn{COziq=Dq$N$REcXD%}toVj^w zw$-)YyBaf>Sh6lK|G4AwYzv>Ln~W36UvsTC+dom4A-Vff>TC6_3g^!i)Re5Wp7?L3 z{N=2_?W=s3#HY&dnUcBT`NeHT2X9u%S)5E&*1QE z6WfLGP_7r+I}<}M#__rezlzFBUwTXBdj66X{0@P8_G#W{aOUugpTA|=ymK8=+F$&h zJN*qfIPYhRbdY_7o?@WNcT@5EN=yo;RZpzGEp>faUhmy&67rw^yxfsvGY_U*7H<)4R3RXkV(cPSOdN$I_GUy`8l?Hu(A3eR?Q>H+y=icTJYIY?E?)bj`}&N0T{_;aC*s&53gRz4Do6PycQ6-TUS1 z;;PR^n#ZS1dc`T(*W`26NmI0zVcM>#f3ttC7j$aT9|Z>31J`kJ+|D%xCOr@J*SM+$@=-h1+C+gg71`qqRRo8QfUH8j7zc=al< z@W|rE?H|uO`W^8U3fVR>P-DyLZhrmuDoYQQ{@>UpXv6kTOJXcV-%fqQP_Xf$Y|OSZ*RE?X2)aJ= zoJx`2Wd3DVC-a||#9etQaOKAP@?ZXbwMRtP+U-i`d%fqN-gbtvoqsoPo&NXy#kWPj zEq7gBaL75Y_xzl=B`5Z6Is8P@(ugVCqxlR=*$WkVmtR`DUc63r%G7&3uYF5$ITO~r3l@*hJEJcZBLDe;xd!?e=pB<}mOA~j>X|Zc zxzt?un4I6-Kev?jFfDxZrLtn;p&e`z+g|hgzh0jFF!JtQyE)5_1z-LU^oPwq_1p}m zsjK!lJ!?y4G7evv&Crqm#4d>8)g8tko)=E$M~0?8XbXM2J?)2V?{vP$1y?egUzXh5 zYq(`zTyD?W75*D6zn<}W?&9JS{J~mB+1thCi=n}yj*g5)5`iu*CV@Q~N=mad+(ZNg zeMOYJIy%(41YKQRgpYP8DJ|+uauF1~=wdkHm0zi6Jhauuz48C~&kPI<44$rjF6*2U FngFqdq+kF5 diff --git a/doc/tux-foot-ok.png b/doc/tux-foot-ok.png new file mode 100644 index 0000000000000000000000000000000000000000..5d814623eb7d968a03620ef279bfa8d1cda07903 GIT binary patch literal 404008 zcmeAS@N?(olHy`uVBq!ia0y~yV12>Bz~axr#=yXkvVyUmfq{Xuz$3Dlfk96hgc&QA z+LtjfC@^@sIEGZjy}8RdXGv)3_m7va-rc;h`HezlpvM&j4K1evMp4&AGdQ#ir539g zx;ggBGZj8I?P}0iuh7A-_M@Q-dBVDZ9^X)|MPz3|F@BKzi%#PVt|5cE3Qe^nm{=W2^=pNq3juqEMK5Z zhBFLIRWNoM1EVdBZQRfx2W2-%Iymq{*#~$O7O+Fv32Xuvn4xR~=27DrMgs?w8irBI zxnR!R&&j~R;B)%Ki4|L{WnaF3J$2r^d6{?9t;Fb_oY9)4}6wR_as(C;@uAtM0JBOh9d&j0N{<*&axtgh(g`zL+7 z{nrou`g(1q^6Z=~`*OA(b=&m#TlC4|`CI4O zWVh$0++I5`Z9gdMHcWK{W#h*gbGe+*JF<^gIyLWHV z-LvHs^;r$~U%j08@1Nc5P4|m7^FEEV&e7JtSL*(AwfLW% zZ+`TwO+EMKpXA9zlheqOWOKf(U)-vvB8e9;Hw z7NzYBY|0VPF>FWC%*VjClAAfzuqU*miD*oHw-|chm|IPfT>#F6ZJ#BU_o5S(s zb@Do`yHl;D?qs{3Ym;81eE!d7-KVO%ZN1~ZJ$ioP^Zea=*K9v`C-mzEup69afQq*R zQkx4d2K_wfzi!(9f8SJ#{+=zrv7{!}@Um{x*NGPeBkc@SxW#lQm!%(R`tVz>!e`&d zy{Dhdw*S%k=i@r|q66#0+!gFE?ril-53!mnSovUK>$m6A_wRGH`@Xa|b#LkOQ+)r^ zy>C{ZpPO=j?>zPM_X?x43PH9@sDX;;4N;j9b$5NsPk6@fKCmiXt}bQc)w3&Jvb?;= zz5nMj=~M0em5Y6s%h$i!S#&f%f2ykYbbqN2?JL&J%vgN$*YmHIzR&+G6FT0<`McLF zCcLcf+WeF8`(Lq&-N@g1^jNSgsCGCIECMd*!@5&Xvp>Bv|6i2fzgzuJ+M?f0VO%|T zW}HU+<%a5Wb8de6H2t08>-FaT=l)+jx~b&mCz*NvB8%J&-V3TZS8M%C&T$> zQnzmEY2EEpc+GA&JUcfxd;jIfI?>yF#B`%n_-+4eu=#aD`O^vI{u|$!nVB!;ZBIQ? z^~T%z$tji}rMc&=KBt}LyPdDPzWOS6%Kz~FC)D1Wo&crN4Q1ep=784M<-6sk=>I?8 zy6xrbzx;M-Q%$smQ&xwqR(-u@bKjfo_v^$~%r82nX=L$2`b6LLQ2FmKU%%e`p?3G% zZIi|KeQ4eEd|vgb&-1?9{ElP(Ibm_x@1!j?^It6a-^9xOWZv#~)1vcthpzki*7Vct z`+wY?o!@`<&~J`+k7L4~RUTtHbm!*gl-;>zQ?I}IYh(Ff>+aKj=WqE{RY$M>vK3U9 zemLR;&Pm68n_sS8x6{||_tN4go3!&M9rZ3fRC{#!)2VFNex?8YdVTud@AsnnEFN*( zEWIB4_WaiCan@=q*KdFO_U+WJ*Xy*c-)v~!^GJ04gumbK$Cus8RG(>{e{aY6wUTGd z7ysP2Upr}Atolp&nh%aUAGW=II>}paqPPA|muKha>vN0iZSnj)mG{lxo!w{VUDc1y z-?;J3=S{IseAnN0p7;Mwdc=D>1_p+P(*lKRyUyuCVC)H-3=#KxBWclF$ z^V87vRa0~Je7sWoPPIYa+R#4IB0hV{g(dl0xSpGb$Ll_iFKCP@`gruyar=LP({v&y zoiBG?@7eNo^RM42-Dk}vh3|W^^V77y->2N2x4-$zSD|a5Mua~rxJ?p~IQLvf%#Y8n zpS;ko39l=EET3AiBKLU2Oo6DgGmYKn{e6>uy88a_ILikO%sEo~ZeLpJ{q&6S`6;j0 z?LOC3F?Zel+V6MY?zf2~K^?OgNUH(za9b@-=2q=&r8l>J}7@1Oqp z{C{`>Vh_mQ0)?*=7q>5Gx}>1F%h?D%`_Q+fW4V7?!(Z9%~j6FZ%cfq~&b z79>q+W;Xo!9Di5m|M%kilkUg;XP-SIeqw;7OZ=a-*|}=@^}laFt$klTKWFRJuwc7? zkGCp4+_Osk_438mOY&>tH_re5<%Bc8t&07w2TZHq?ELj=b*^&M+jZNIU;Fegvui_L z^VjPWZR5X)e|oolZ&ci-+nZM$043;oD&QK*U}bjRj##_8JLXS6th-6u(pi755H@KQ$^^zMCR{LNoqo*3Kho$c&IR2ew7P+-IYG!Yuy7Avtv!99ltuUEm|NqbD+)e*htzI|jXt((Kfb`lN zo!$3#eb!m=|HN^1i#@j}vaiZYP zpU-AL?J+(#;eE|>>!SO$-%oz{u)%(NW{%!!$G>gTYqayLAD@1*q5f{X*}aUtp7NmH z!hu(=pu%y(w99AX`G3B6S@int_fscMd|AFJl;v~%&*}A=@;^_wKl!@8e(#rW-|Q6E zy*QP_XFYeq?%8q8w^ps$IrGee2Mb~f4zd;<;#5CzH9TJT^fcYg4E0)7`=qYq@4V^u z>0o{3-aVgnt53g)%-&bW0g6v>hh)v#q^y6d)8n@sntdSa#A?Ss-)`sY%m4pz{Hbq# z)#aYBn?32L{#Cxc=0Ewl^5VrtlYSL^$(MJ#eza@L>g+#rzbuj1w>0#Vt?jk2%a`Wg z`d+oTq`T0pDOW~$>CeZ$H`jMa=k0Lpv;S9N`F_vmCl}r2r2H$`f;v+TrtO?n3=9ohFKjjc=lSpF2K`gL+wW~X6#O|VcJs?+vrjE- zm(yyODe73W`J9z@4Xf?{ZybBq&D|JLJt?5twMYDdJGakqrk{Z?dw$&ib&~P-y{fx^ z1Hb%!CnXa+cYf<pbNpXpZwey=LxX7tsIQlxYjo$`iRb>-{(aU@ zWg@p!+??_-*SGmj>9v{a>@o!lWJ<3DZhAT`+VA%%SB3kMqW5deSRXhZ&gNZH%XGIn zcebCueY9k$!}@r4iS_l{m1mq^xX1rO_6*tj<0Z8+>dRie_L^-~a{XoTUwiJC(YBXv zU!Jrt?(gp7`}4QH$xc1L+3Q#M*E_!+9qoR)<8j|){o0q_Vz>O$og;1kFVEexPv+mf z{Zs3!UTZIX&lwcT!oa{_Gga^cGXujK)pt97EB?HGcz?>=&u`CNnSAx&>zt@fpU>?+ zA-?~IYvn`kc#F5%%NCZUm$YFh<5dv<#v9m;jbN~5%;ftwVNrl zs`|mYZl=S^%8Nwbn%Ty)elgRueUMqWjltJZBMb~A6U(wJ1IsNy9O{e}kH;_;=Jc?YnaP1xwYs?wJpGURllJxNh2W z{d2~Zn@bep^qbp1%RM@N-R|!D`>)N*bpA$`-Yp8aA9`GP^2d$S7I5=?wE3FN{k-~b zKJV+d{#+l!Qy7z6&(61x2NiZdKlaz_oSv>f|I=h)E8qPu)uc}xxBsE`vz__-qS>I4 zfe2r4p*`=Am|mQkyv-+`pYMK#pL!5beJWwwve?hJ^LDSDQ+6x!)2Z-%MN#3N&bho# z*>B5A=JY>VSvmEa-&yH3RWoF#n!S9#>e;d7&riQkma+{$?6P7V`}(TKQ`tV-nZCFE zEO)5-!n;!arN8aeUMOFg|E4;0eg2EuMQ!R%zboeT`m-#Fn*Cz=d+)F2QWwr|TCgwt z*IK^iQxE^W`fyRV-X#9o7tY&OuE|rejR(_za zUBqf|GO}@f>9*nhp9kVkzHh&;J@?#+E%TSMZLY1Y-E`jW_lcLw=j+YR-}iIM_uhoM zp9Su0ZPrWAsi{5>d?YmasbJRckQ%O6Tlepsx+?Dy@8a&bOony$ZyN?5n3tWo>R-j$ zD;N8I?qSTCHQ{@5$XBbYo4Zby@6G*Nde>Rr)Z*ifvOOWq!sorkCA@0=BO5xNODx>$ zx5C}L*5~s1SFfku%v@z>XSl(NhleM^{IB708D)N(4+o}fHLqIdf4?f&^5bOo)7tT$ z?p!VO0;Q*@Zct8spy{?c!q5J-)6XC2`!%`6^kNsTtmHjrej|bT=LvT^rFOZh6|7$; zXR>|HojtEV!G`_nONP6$Z!NF%S8BNbEkncpmGpsU#VZc| zT(N<3V@lxs_?!S~#l3nG644AFR{q{KJ8u8=-{xXUtDe~B7g^6TYg=3OO}S(W-%g!dcI)J_XY>F6 zdG22ox+vLB?HRkZm1%s_$EYhergY6-bxkew)(VdGr;k+M++%v=+1jf8E2eig+{@0b zF}}6w+>p^*R!?@ckHjZ7vEg?dP0BZ_bF43ht%4sM|*rojyN#C zE>J(<%6yIww>y1rzh-jK;hNI zw1aD>z1j8I_{o;X_rTrFu&*ycUdomQwI>^vcg)V)9sTd)Uv)@LFx9O1`MIg(`##V8 z^wPgR>RN31UC#9#&n_HRR^C*zBlqE-fZda;?zu0SoRwB|&o+(u>J`oTWnsB2-!-3{ zQ>+Sgy&UU3nfYso?fU-ZwV$OfycVg=`H&U-Mf1sa&#K+dKljdy_c?l7q^9c3j4$UW ze|@>9^wC|XlD~1s?@ivtZ0}V0cdOj)cX!W!wfmM3_f}Zc{-b77)vP$@%Jq9+PTl$U zR^WQazprMl*#G&Q_0tE<{L{Sk_lC5!wW<9TUwA!v^YUDU`F4-i7G02z`y}%7I;bIk zh6n6RbxYo9=l|S4{iMTxzGlB(#3rZU&l?5bfXd{X<@amV_XtVZWOJ-rxTozi^IJhl zzfv=)p69+1uiwoy(Uc4RboRjQ{o0q`oP8xRJEwnB`TGecAC~8QT4jIz_~p)@H*ReG zXJyuU^|RhrlS#{t{9SNYdSBW6dX2Bvc4y@h_xes~M^xAOBbG5JfZ9la6BUHih% zTiY2FW-ND`K#*aZ@+gr*N0y4P42h}w;H0Utt>oqcnYt_2pPu=@H-xSI)@8ou zrabT7zn}j1+wJp*ex5XIy6XAmV43^0ECc&H8x!JQ=a?{DzyI>Tt3zJ!V?)dQ`s$uJ z)^~$f-FY!#d$Pzz&K#BxQU$Mnnk|1-`{?egT%D5d?|$BKpD$Et?H=tQaa(r%qR0FA zo)s^cr}iZ~@UZag>sk4GuIuiRgS#(fHvCBoC^Tey{C6VJ}m)D5msm?xlGu9&eW&e05AZU&md(cFWaUFGT)U zBr)GebzXVSt@@eu;!A#^Mx_pN3(Iu<&n@o_3~l)?*5f`){;QGZoqhw`DIaWDe`e0I z6Jt$UxWIVTb1`5J(BOg@>cDO&fPb$=HZI^N!R!Msr0{8?+YqB&%F41 zVj;VXr7=G*14F~q(zm_!lg#C6*2L7GmJNGoBpY`8(rxYCZ#H$$DZf|w=~eiCtLy8o zn0V!tyQSO=a2Mm}yZvNO+1cj>`(73HE8MQSzdAQCdBg5|8lm+P-+Sj*pLln$`pfy% zRnFmm)#fd1fBW)xiCkRvW5w#0uXd60!TBi-Iw~#mCWvV(`upF=>KqQ+x=}Tv$$3(U%BS6*Jn%rv-OM3 z?b@FN<|aR^*j#p8t728v>`j?DH$}GHE7>;hT)xlwaOqQJ-JX?u*WX*dU3J^c)w!=` zExTvy;(zz^PX;?%`91%R*Ne@!>+q}k8>jyA?+;ru&-ML=yVs}Q{qp+UtIa2WUESFE zMtA!irTpsKx7YkiTXjmhcHjD&uPcjQZTzn#owwoKnQwBV*m&Pp8{&iJjwrrM5wHhWvV?Wqi+1KdF8h zVjjjUEUGVe!2OBb?3KqfYS(SQ`1@)6vDz4)IqnYmm*1{rjz4d>Z~H81?pL*&;`%Rn z27me3@T*ow=Y?<5FZ=bTH$wKizxugmx7mT--!4DoY92>y)cmw!$;Ah^n*OZV(it>s z)r)&gKiy;}#9QBO36r-nTYObuAK$C=udmoP?cH>4jor-a_tiK53ad5$J4x013A6l< zhB=kbW*S*oSiG$EuYNiI>zUjq+3GS%piyM?i~IFJCDz%+kJuO(9(X!fKeGo7V${q2 z&yCzw^QpnaW$D>I>vtOW|Gq1KTDrda?vANuR*vgt?{}Mf)*;%w@$1T;HxjnKtN!j_ zclDBsuXojjIf>!Ik4tNPlZ|iOah$*GmF}8%Criz=n7%zTycANiZsSLXed~6v)J{>F zbAGDf%R75NEwkyj+VK7MGQR578$YVAsE=HbFZ|=5@4XH)U8N^yi``X2@BK(}RQYS> zeLq^!ag zKaT3_EScXi@BZO_y=m|NzX;!S_uA}Jo6BxI^ZcOC!@$5WL(&J-MX))@%fokip}cME zxqkd_~*ZWb(Lqcy1~RU-mjY)I%OL ztWo*<_I!PvhY`{pn$!%k@2Z zMN$j5`F1Zq>$6zi&i{P);tzHn@9aCc#Q)#A{IurW(uc?P`yAXouc!UH@7?oT=PlEh zUjFLmF}02RJXg2ATiI>L^z(+yHlJ(1Eq|E$KA&VZYpr=+@1)t!p48_(>%2GD@cBIE zUtestzH?(PU0Z)~L7iXxde*OEmzKYh7PbBI^3v1SE52W5?>u1tE-j(Tr)S^)Tl44LF?%of>ZoOo%oop^-S*k{>+1SjU;b2- z%(BtSWxFMtS-$1cGOlOEu|7U0%>Rc8UT`;v?>}%|vG&fokhRrb@{xOGwf*N+m-_Bi z%)X~`%_{DX_3XCm276~M_#P0aooZ9ie>SO>`G!g6d@<>y;2SgE^mo?A_k7T}mz8r# zc&*mQ;-$SS#pPFJPinh!S;?h;%O#WA95rd_J-5_0t({hS%zghJ*=_UY`0FoV`?zt1 z;N{{Qniwcqxe#CN^G%*C~ztt{>DTwM8Ckbg^XX;0{pB~}tsmp?ns{cQF8l|jXi zFK+sNX8E6eeowz{Q!PIKW@g#lee;&||NCAMvvwD!)D8C+@BV%a%$|JcTZP`vRccia zPBXd4Y~SZw%lj&I(x-^jS|u@+SF`3l`}(=HWyP~jDY^Y7aq+_3B7W9CFCS{1yZyeJ z81JHR`|?}i=RMc=+WF~3ec9q={&&@a`DZS;E?92+HnZsG)9L;o!RfBlNd8+Cc&nssWdZ?*Ct&Dz(OHG9US@4t6d86Hyme7W#3OYB+L zT7Wgo`?H@BZ!n_4DfSbF+_y$^N-FU0(Rt$CC}8G#fcy zybjp=&uU%}%QrjL_uV|-)XjEpJ^n$sXVsifK7V9p)&AYbA=Isrdhy^nv;3Cm@P99I zx2(Tc{U+Sk_%CSK?)Tg6^J9ums($)#nBQOOlD@peB>C8y-dw5Ow?CJ~q&^2nv?L^= z9d)8M#qj-k{`krD>u2Yet@W+1-!D^g!SPdXe$8RCwzta?-xu*dXKzcL8G38V$)7J~ ztv7Gln)uUXW?cKVM4u1q_OCd;BXkzScZ2)PssDH0z2Q`PzEwF-kFi+KQ$P9f&vliJ z3Ugbpo(P$J!)>jPY*F-&*jN$Ut1<_cKVSJg-2c>U6W8~XfBakX`MT}PZ?WBMFZ)9O z`q&h3eOmps_lK3)zPC$~T_?z0zqjn=?scww4?jCK$KT<2_4r0z>*>?Um%l7tY&1u! zCuvgL|2WYtub!RFuiG3``}Jzk^SR}IQkVF*TNnKdH9x(Wd-{pE>OV_!leife7}oSc z#$Xtx>B>*p|9ku6r<>yMnyf8|YrcJRbNXq;b{Qr0c@>M!%rebBv-yHUSoJg0Iq~=M zsx9W5HJ`QpEOxDG#hb)3_3?Rq+v_pW!D(N=rdRRUi}P-JY;x-H_4C$R zd0r9k;-_+cTV|!vZ*W)KImo>}N~a@h-aFsCzmqxtWj1aqT$dfQaMvz%eZ3!lYohKu zSKTi^uzo)4*R^|kKJ7R&+uWbe=7Yn|r_-XJT#L@1Dp)&FZNdA!+3WVl=IwvXSo!tz zeD&Jez5TB@g2pe^mBC$3Niz!*o%g@KPk!P)J;wNLL0t0%|GH0;LF4LYwDNXka_r7o zk-OE5`LHZ^is_v7*||zv!bX>yLI_@-Ka}|Np#^l0d(H1zq#@}bjbEoF#OHI}{##mduKgWv((8sh+39zZtr(&P2W|a>W|DKx{^!Ucpna6WqpG{LQPfQM0`)7Ff+t}JK{}agc|B2JyIf9|KPosV;a;$l^@VZvZtjqsDS_u9!pBL@( z{kG*&tvO#}zRuW|u~@P8^zN78uKKm{@$*YB*?&5rT=aJ9bw9aF{e7q8|CQ{I`Mdk) zr(Lyi8k^I<`hPtS%Jvnfzy;k5MX3|=|5mO)eSKf)#x2VQ&#nG?JwD#g{%^_ax$^zj zZsqy|ceE{5^Id#oac;=~s^8}({?)Jl>HV^##QM(1 zm!EfkZ*+e;DgIuD;@@4b&&>R=4b)o?W(7@d9pJI}sQvu(#rPlVzMl6#erIR#Q{(wR zdqCZ|-r3bBtg;^N@jI(8mvuT|xAI}xxjk|l4_NIw^LX21*3X6$@7e5N-yND~?^Yfm zf3aj32&J9kZZ&5|n%mv9!${^hr@$S19z zb8_w_o6wE#o1*#x1>SUHeM*E3emQUp@bNK`itA71bZ-Pj~yq zv1N8*?aQUxpHDHb*X`z)3c1b4uyMi!p*KPu%e7M%?u(bN<*Vzei~an0{XA!Xs|m5c z*A;%*5&Lta|Jm!;_vWxk+nUvJ*Z%tzx^j=^X^lFY`qocrdwza7ee-6u!q-d3OHT^_ z3fp;Qvsd-Y|F72nJ0&)&?%(J6{)V3VpB^0MetJnef712$pPgRiF)%QEnBoQ+h}*yx z`=$H!)A)any*H)2Jg`MBba@}Yv z^>;q;?)4IlaKB?6IoJ1(-v8X$)7>=Qhw4-|+1cOr*q0o&m*w=*w|D)Ih_fm!^?z7u zwXXk>`%&MhiWTa&J~L+>_1o;ekMH+u&rdr{u7+*g`Av=Oe=Wn_pE**Ol8>5p9@jd5 zX!%QTtN7eIQtX=^?BBhA-=9m`%`viLP(KK~GTYZirT+Tk)rKp%I?>eug==_X4C0q^On?SG{%%YJ-g}uYR;F83=AKxK;~83?o_;v zExJ%`r`~U~&7yp+|FZS#{lNo!&+WcjzI^-ktohtH3wg$!$Iq5}$d|0L_K)ZVN#rL)pWfez}gYOC+u@ZYv-(aF~g$=ca} z{(aq2JM)43M!BCt532k3wErzNuZ`LL>o9lf!$bY)d%wJVAJ+fNl16Gnk#tQ;tKOEp@i!PYh>3PQ(K-| zw|7QH)jsPb{RQ{Un|u@Vs{RNWm@hwDY!LtI-fDKe$?v2%j`zN1*n8#jkK&$%?+h=0 zUb^&n^|js0Uavj3ENk0(?W`Ow+g&eKv+|{;ONd(TIv{B3P`Ep!I=IiS&N85QuhZ|B z!J59I{>jg;dmP?MD7kPv_gV_F9@pb0zPKVuSh1&J<6Gse1nP z->P%TpHnJiXDpX4@%Oqu)lAIVeQ9so=UdhD#o3Co=E|S9zJ2*crPYML+tROm5A&UT zPuMo?ZlZkq#rdut?-Pp_*Dhmu`IGCH+uG8l-dU$S?|u!PFn8V6j=Wd1UZ-8!Qe7FQ znIS(_koTGH+J1+9^L*c1tyh`X9^1BK^_HUj{yrz4e}2#PzB}lD@S(3Wn*7&q554lk z_SBbJoBKAG@5h|*;1=5V;}N(0s`XDx%_|$zRQJAc)!+1eyWgd9tGS&0i`E~t{cfxE z`G3j&J$duRZz+M6seA|luVCCD#ml?>>0EgWPraRw*kY4!Y6|Nc-xk3 z8WUD!T>5)@%1*T#aVvwb-~F++_RSvavR(%ZF{V$;885l6eEg}T!;o>aZF)TChV_g4 z?U*WN|INy+aP!@ykC&jxbW@k6LmJWw!dzL3coE}+_~&| zaDHvA@qV+Xd>?II{rejcqIvzueHP(;+K+?TGd~_SU-Ff+miOfEsI}`j|BG%vE?0fx z!-owqg-1mvw|)6#^X&geyXuDN`+nTi{^tOy1BJoM_zq+(YW(xiy*BpTuYXU&9F0YJ z7nR>k7618=zg|N+Z%5+3oop*WJ$cEAsfTlC&y$yU_aw85-_LeIe}Ubhw<|I-U+_IS zF8Hu^mA?7cyNj~atxhT1^aosDb~ri8Hvii#T{F2}&)-$A?(9C7thH}lRp$enyt}e@ zt_OehI-Jjav)I(vs{hN%##gtu6g-^#?dkK>syqGRzUQ7lf96=u^8fC_8lG#{r5{c+ zE8oAVmvypsd*;AzwAG0Mu@qKxcn&)kps&R+Sg&wsk?_WQ3s z+NgkPg%(KSm{s$zhxMnD0TXQX~a(D9H z&!^jOi=A_ikeYq$*@9iKqSu%DUtYZ_G9x@op0k)M*H-^qfkpVOj0kB_tH)QK|M=*= zyw7)LxUTuvmP*^BbH&A1eeS+Lz1Q#Z!AYNDiugBH2cP{ZZU3wfAhX9h_laJF!{@`@24((|EC@Q(`%-E{k}UT zmwhK+bJ%)Yx#;Co^HX)}?}VD&%-B1%)`Nk8;Q?nelRE=L!_^1B?SD=EbN%D}X$Kd+ zZ2detciYUG*U|SUz1? zZt#yN)`zC+T{;hmO}w~8W5M&q+-%zn0&=-#YbP!eQuWQheP7w?plmnS(#>)9q6=() z{n{=U+|G&NdeM0q-d){IFB9}kBymd6C=FUFjSL@sNnMEa=+PPhpXcU-7N9;j*$0|S zPZ0h6b?fJ+d*$tXr|C$~xVCJ=>$ThG&8hqK^3$K^_WKLfq|Rk^SlxJeVv^$ets8w4 zm1WjjKUZF|-t9tMo$7T0~BKY4#~w+xr%Mr5}$Q1{=(mv$Z{Ek)wFz{d3`uasG>S z3bwa)Z(%I3nfmAVC0=>``kGS*zo~Y#P8He4zt!3(V*m72ylu%B)x|tt`Fr0u((fcSuUyZZUH@BdGB7uT5?W?^6cF2?fLi^Zw?|9;D!)5^BkZmUg_@TsWm#Sgb$ zyPMImYU($^3)iLRKU?{Jot0Q9Q$l~B@}54=D&Dn6l`HTE!&`AKZTYaq79+L!YM2II!I}_44WT>+7cM4!75PwPoL; z?O%J#g3qkI`_pj!e1D}nq1U{1zK^zcy$<%jeEhe{q3;hT21-g@^sK0l|EacrW4mKz z?-ss$ar?h?wpZ}}4d(x4Rr==NuakYOr`uCL9@wyVS-<*{`1-$JpMn+x`I_G?IW^(m zo%u1k>h*8kSAU4t2hT`dbmV7X*wAIOEunMI$N7CzZ|CjLk9zoLP2}b$7o7R09_F{- zBj^@oF`p@i_jzg6zIk)vgKW_ynpECxLx;y1>#pF?qNIjq$hIuvf8YB>JRsR zUdp$It9&mO*fIU#=n7r;s55KY3k!euPrt8p3!K&b_%Z17oa*I0x7Yiw z{JBx{>yJpTCAuYj1y=Ul8@py%{rf(%@_x>?yx#A(P1Z-&HFEj_a4i zBHX@C=Cr+Z{qfu{+R@(YAO5)(ouB*LaLaAsaF>_KabI`;{4`12)_dE{qRF9O)=Mxj zFhpzy4{wDr-sSiv``$jVuJEfasO^*fyMdW+g0R2M#)~^;_db-8$-ciQk85@C6RAnR zr|kT``1zn$a7jpCG^naE( zcU*M#1^ts1Pu{&;wPaEC>n;AV-@aa{D?c%B`3q;RXQxZ*N-p-tifqh&Z0;lddRhJ_ zBgZJ64gS_sbklML6~Deszxw@de(MjrZ#)4zXWQ@hd^O`<{Mi*YUi#sWFLUiuSX}-# z`>RL3fA&)6UvtZDdA{5GJ?`h*{Cew``e-?T|Ef>jvD_SXaDNJTat}PY z5y5Kr^XK~$Gyhk=++wvcdhN75Z&LLqZ$4*rS}V_q@A=yPy~^EdZ@HPbe$A3xueMxc}My)e6^`Zr?GFH_Po^_m$&OZqVMUJskT=3lARtxa0kg#2UVi z&sS=Gc}ksFe=4Ua(^p5}$B}LRH;&&=^SNPPcFSf}*)HopQ(3=X-MYCqynt;Ypz-STHTv|`Pt?u{w053YHdp0t70V^_v^;q)B6LnlOyZ0=KIHf z7fiZ6r<&{4SBt%~PH(STa`koLwr97@BJH1fRd4NaeYNu8vW*p!ihdT!X|k;M(}^nl zbD2?fonNAU zn}b%<+T1h%l}sNj?iBxD4DM<2+wRIZl_kwvvHfP6wB_?T#VNbrZadwdb@!dw#&?N- zHZAnKYW@D;3bxypcl^G~1V>)_+w8@+YPZmnT|!5feYvdE;JbAB%O5f7$#zVt|UqH|MgKe@%8*zk1Ev!W}>5!~5F#jc$wqiV@%dR?b$UH(36_7-`^uQ8t%FF88pUFNS9o?Rz@ z{QCQJt6Qqjz5C7ob{+l4xQ{{T@|U{m{rmpOee(}IqVjix?Ae zvGslU?-?f^4a;&!9@!hp?#m>yQW{dSDc~L(n zuj@S({O@Jtt+eHtr(&Hm*Yb9?GVaaw*%r<0yLD0`w}hqr_sp8Pd$xb>I-9?G*Yp{; z%J)57U3NA4sjtY%x-UAW@<(I>p&$;szA)t(ny%w{Uf zH_j}(^(6Bn$K1=09Bez~Ccm*ujazooc1EvjtDxi+&ewhIhws)aZRm9{b9x&S8SHy( zdV;;$rrqXKdtXm5YxLc}efjit8CPz6+2-=AI@)OWdezNU)wz=|9hvZzlMpOpg_0c$C^ibC!2rQonNsce({Ws>l60tzv#?uy}xc2TYu-58BN=5V}s>u9&Zb6?WvEwCX?iJ*DN=8 zYfjR>^~>FVRYl94$u9N3a_`36zx}0qZhu`_bu~BV?tE{l0Dtqo+h0#){5s)S^78q- zKVNc+3+AlO`ZJ?&_lx@Tci+9-X_A%m%2c#^)4v~J|KbH7ok*+4cXmBh&BAdHJUs)Xq=i0gt$dpB|P4`K5Fmh^A*R8 zpZvc6f8Uz@|9<@pG?~)JoA@=GcdzLj`Hbr4PWxl~yQ5~BT+N&OHB_^vX4gU2g4k*5pVT(3e5YphRQQKW?ndv2pTw zHeGy|j>DDX88vN6>mu#W?wJ)JCiJR%VqS*yceh-BpRddBFAZP(>XANshd;-Y-sH>6 zt+%k95DN_}czi>A?+e$Wr&GgEz1@D_?&bdds;AkWYyetJhA30kq?p@@cziw*V1Kq#pWn`BBu(X{v*ZGh8 z^<{hiC9glca`)HMryp~3E}dmp8(XWjAguiI@0#kqubV%Lx$FsBacA2{&Ym4~D)Q?IfvJzM{av)`ib%F`*=w}jk@5Z`-EeW|V3n}50M3!-cca`t{XeL0R> z+sVEaw1MT8$=?*=A9K%j{Q2=H`$_iweHE^J{GhNa1XmlEOOEE(>Cd-(%5G$GXWl2d z+S=M{*8e|E-*0pOTJsYV+xccvR}Tc6U)s|7>&xboW|wp>Kb<=1$+Ni0R#Ud0n$7V% z*!672trwTg9eo|k->Ld5=5p;Rb#y#`*=%!ff;H=7v2$V1-yYn{ens$(n?sKD>05?- z)t0aO^pWrLiv|9o_AfUk3170vljW(_(0>{6YdhP)d;RMb_lB}9ZSAjld)#JU`(?9+ zyx>Pk|CKGfQj-*H%6Geq*Unn={kiy7mpa?h#Di@gR!`UD-Lbs$|Bu+rne%gI*w|@K zJh$oRKW+7C&wdA&*WI!yv-zs7pY%|@{mV~bzL%GUze#n+djFj)9=@jjzIgB(u8@cO z>STWlWSH-*o0WJXZg!uJbl&Mnjfs&vYA)=HyV#oyZm+#su{h=0n#jrjtgrl4?ECY^ z{Au5A`B`7eKtqW>i$E&`6Qty#H@NWqng006J$AX-pRQRw{oNJ5=TO!2>gwvZ-)1FN zG00ASC8zaz+04?dzVp)hCRpFySzWa+T6TwD44%GMcJ-}4rfRzuA3N%Nf8%rU z*iV&3dyYG&)Sjzqv)U?q_g~!M$@7A_=7xTMvY+k9SGP5RTi%JjuIpL0>a^|OL+eVv z3vXkv=StYsHzBKISO1c3=CgjYKFaN!^8ZTnzoml9wJyJZ_hCJsxbTamvMZ`B7e={w z?C)CsVyn~2H^+88TYT;K`;_&bnddiVub3~_aeY;=u3X05vt4DE-tYN*?$hh||516n z-){3t^*Du6bdsdK4;_G>R&u=n%(|34&)Zp*))9rpguw%d8y z@jp+6e`;CZ`q|2Go$!mm`$kr?vLd#W-&;QEb<_+Qhwxp^k9K)2?F-X&@nzg_*G>Ic z>7vN&gil*``=6YBL%>X*t0&5}qwSMb>$=ABlJ?_I_S{`NYufks%74DSoBg9UM=Lwj z@?|hf<>zZz&UdP$?89HUwEC9ZTmJj_-#?m5cylhdY>!J{9nq<^@Q=hlt1H4kQ;YW3 zSRDwN{AJhAHR{jrzWwxX&g17&FP7H^e{O32`rEzM=WEn8%bJioSLXf}{+N73UC!%a z{dJ$(TQ9eLIpw~|?wnMUeY;jeziV$%MSRGm>*vkP`{Mq8nRm5y&Zd=xSLCMl_SMF> zh8U*IU-ZG>GUM)9rMXM4@0$Ja9GCB<*Gvb+_y0JmxjOGljsD(8ZkAuq%=fGC1ufi| z=>nR-GiZH3r@VE}=gF&=UA4B)zh}eExBXt#>zWW=&f?bI+!L=9mzSN-&OLKu<+m*p zS4yQP-w5!}50_khe{G=gSyoJSIm34PCk0#V{Z1xbDTxLMSqyx^m9c*XM&wp zg-w87{j0MTwU1+B9X<$HZN2ehNkwRV^4zZ%CGT(jxt9OV>*f5P1nS~0oCyAXwOZHi zVSw50tMOGO_MvxuMK7AGf4^+m@+0L?#lf1Y>ZRsBuNIu2e^rCqEwEO2zSD)*%R)`} z{ajZox423_jNA9>*1p&8Or8H}vhUqrdWnBq>cLN<{4clP%F?!cHX}JlF=vxq)mn>R zUpntkwZBvNKji-vP`H^wh9-sh?RR?tQjqcLUgg@&mG^(&Q~&*LcfQ@Cd@gC> zPjdBH9xJz+UF(;-{POCPjZ*ndc6IOKE80E2KiTtr#rG$C1+S~FU$FFF;4bjm{qute z=O0@NbGiI;i}t;({b;y1PjPkYkI=6^TMe^v`!u?nOEcv{C_ormR1trU0n|2p>%==NYckwp0 zrXQ>N)Z=EqSb6=ra@=W-HOWkIrqwU^njQQ7zIO4gk4?Xx>C{)9u!xxE^{)8qg&FhY z?2oVWP5!*3_E$<%?XE?uoKJNtEl!3Ho2}AeYxcS?)|^5E8k|vTW8kx+MRcoi`MyH^<(8VZ&2l7GZEZ`_IWg8@;-IY zoZ^(LX1A>OKc829YNET`%>HEu7hJD)ad@+{D&)9}#eC)`?-J^AmfdAAhddc>a zAGZi3Jj|LUv%Y_R^E;6n(HmwhyOUge)#^sy%ap`Ce~6D7$=J_}<4KsgLf(2hCsB z?>bRvUiH)87W!eaZjgZ_w(-QHT|_l zQg^cM9+OO;qqsT!{I=Oy7B7VlFEvtCTVdL8Wx~FD23y~0t~Za_`eUnCs5E1?)cU^p zKcCdRitwL(v1+X^*W%T3<@aA)H4poGHo-Py>&Z&n?Ozs@8psP@lKOk>Nj1y!vfmYN zR#mB;Q*Qbwb87kVegO-$yUH*6ZoB*T`B(p59T2`eqqej3cew1QuO@r0Uo{c;c=cM~ zc<=F*+67;39dW6MOA);P=E}BzTUBhEJ{anLh<&a7)L8Ia=&FkskH5Y*E3{w!^5>Nw z)XrWB7ykZuui|QRciYtb;6IrgmCZK2jZ@h0dRAX#zK6{Fr@y{-u3x;)Exx(x@m(X& z_t$OpOO||3@ws$7d%5ENx9gjcl-`n}(%b$($ ze;V%?{g?Y^4N7VL65wXa;|m5K7ayOe+AXF(`_r#8GmYK-EFQA_+-(2bc=y_8T~>iI zj!WD($MG*te3Ec{_ubc=MZ4pdT+>$gb$V+4p;wnr^>TiHvE=;SFRD-8MeJBR`$U$- zdT#c)HSyd_*Balyda%OoVMulI!rWcGZ0jwjY<(VUzN|d-%o5g)8AaQlPt1-y6;c-) zDZKsSafx+iwYrIQR(rqf*eD@5_xs|-ag0-(f8J@0f0Z#yv2Lqk-X1?IU;9^ydrQyt zf1Y*qt68gkN3O=~*Qs7YvX?*qvcF`zLSK4^nY^l8ptyIE2m4nVx76eZ6jxaPSTaB79TsiS8#9o?!@&j`IFr@{tMsd=gU~9T>j!b*YCR0 z@gtyxewBY;$J?h~J)6uu?L>8-MQB^w@^x!KN0nS-0WWeY{PX71>nDriW9*+E+Yq&} z@PZ@z$@_oK-hZ6p^Q!nZ854_~XyE$PVadOi8` zzKPi_uL|GTOq7>>s422OJGLY#lKEnl^7S3_^Z90>g{tnD8pF?aW! z*SBVt?7!0}pICbJXgvFoeT>;Fy)G8_XRkSby-u84vGji3;{1E^SDJs{&@t1Mcyg4R zf5KPB;um*C@3;%dD%R$6?b~gx^>$v6{{8-2D!W_1SVmlzn)OUNaR2rR_YVEqQ=3z| z_??ICy!h{5_HBN=oc)Zur#92C{~|Bed%c;jSMeuwzur~TuVz6Ix_Sbf5?)JN3cAsaSe=@tWK<2Bd<_*7toAPF?3O-~R zk;iiK?op+Eff?I5Y!|POx%s4K)yjFeuWC+++=&077=592elOug-XUF<(~ zOgcZu{?y*jfxm?&AJ%l(_VVYJiJ9fHo&IO1A29Ekxyo;U$Al|~izj|tQC@buq-Gw+ z`KgAQ2HW@7UW(g&+jNe+#I==m^*QUOXMWpO-Cnu*xUI{L^}-jfG0ShyE}7;2A(r)S zsKMLIpZ6IEOw}l}eDyp^H!VZf|E}fxr;dL859^nod@Zu<^~I#(%QbgaJ=x>)Zn@Q> zc_yCo=Xw0A<$9CsxM1DAi!1hQpUU*v693%|O)+D_kg7TXu8f5-1nT;G3t zb#OQ9BF-;Mp3Bcawc%*niz|}8r=4FW{cLKoU$Xbv%Spdqzn>p>;X(A_uT{ZMev}L9 zXXrRzE%gjMnWFf&qi#~N3tPMClKg0o^;^wKMi9w_l|9)?3;Co%z?Ed1aRRzK+lO>qB>*tJA$y zzwG)MG5esk!Fw$W_tZZY=GgeW$^Y#1y+SpK>bf76JbrPHY1e|Kihma!RJ`qCW~Mjq z)%~ZQ>T^}1^L9R65^7$xFZ};IGs~y@>up|s1obzr_JSs)7Zja2d4GDfd_~Ermp^wF zKR>nH{%_{&Md7{oey%&2$}7fh9hLhy_?=)uS@|?~A^WRq3R<4$@;-Indj8(wb2e{t zg5LyQ6lyG4yP-_Z|CUKFGLLwfeTY4g6% z*q}dEy)wgQZgDa5&8kCIO{Kf{y|n!4)TD3pqtoR6zFq^{e2?h7U@_NIl}jpYd~aT# z{QKkVhzT0!SJ%DRAy(KY*Yulh{n_gkIWhMwYHwR?`>EL`5d7KD^ZxCurJcX#tebsI zdC7C3!_Rv^#JQbT+amVg-Szy9|5hK8mnqFLagE>nI>*%fpC;?ykT(5AFMb=>W+~}s z9624P+j8XCvCQPuzvW4DzvTWeIX>y&HXS*MZB;8?Ir9tOs`-5O>59dDr~aF%FX}Eh zKKB#z&gcK!f4&DToH-)_u2i0Uuip{=@3-}IkMypWm3Jo;&9D77aqqWVr{B%2e!hC; zhXs{O%FZ88n=W|o`N~i0JToUdojJx*I%~?cc-Of-W#_NoeZBLt9n+UA;~Og`Y!oQD zlI_CQc3W)C>yswGA9{!S^klieJv-xqjj#Q4-~7)^E}REGZ_QwNdNp{hS%<8C`Sstf z=PUP0EKJ;beMxnNjPQ$%GXj=>-?-x8<(Cr=J%8E|b2TV#JIC|W>Y9t59gSZ!L)Cw( zL#5#YmP@+?bBZU~P2->S=#I~+gNx1F?(3&&N7yf4vFqj5eZRL%%=vP1xw)6m`Toh< ze;Y1QpWDm+=c8utJAdmbnZHWbCMDmFSmUwPOn|G)Q7YsY=;(p;S(Vm5m&!a>1wx#E&A8E55L?OcR-zoLbHA+8Tf6cpE6d(u zl}|6{e69TcB_m*coZPf;-je*b_nDao`(CDBsMx=>b-{{*D~11c z9{OjIT0MX1D;3`7rYrj2{kZf${#EX$Qm^lsZC|1|L#r7^BJz2?XyLgdWR?Zi;%&gl>eoenIWK^7ka`UlDhmB-ab?w+3vi zZ*Kcy<+{G!e|dl1?zh_}^Vk14{4KxCsM$H!@=tL5^rPO>{j01&Ga;qmO#s<@7e3rp z^Jho?sZWgyJ=eFkt&Yy$yYscRI|ea>IEy>ro6?)&!rN0-lh zyzghnOLnoCWeNApu1pd6@q2Eu!QC|#A0OOl`or=3vdRAk1;>~F`#fc*eUgsFk?W5; zJ>PTuQ5NYx`7#8v4QS4cE>y zZ}+`^RQ=`Are79SGS^o%2>q4+U%2jT_4c~^ZNF?S@11^jT_N;o&#UF0r>Zr?T9b?) z1>`+mAF1;tV|9qnrQgdork-bsopLGj&%$pVxl*eY|CaoIasTtY?~}m72(sj)FVP?}a?wH@xEJmur0f^2>@zfz#Bg)4ljU?Ot~3ZIp|x z_m%BWZWis=3*7$b`myGO`v=T)b$%S@_@fw{B2&G5T4e6^zkC1dteeyQVE>9`U!;@Y zolS1rRkU0u;mFRNF_#z5-ui6yg(>R)cRT+IVab0#@5`rbhx^yFJlB_Anr>fO>$~&D z$JFAUxcA}VPi~aHw|c+BmE~>R_Qm#k*X{Sme<{6GP#D!Tb-GU5Q5HBg z`QaYFi?^2@la`TjpUui{bXrgytu`&D_{p1*wl-^@P``+hIC zPy&^OS)i?G4O?$rN^LH>qiH|kVOjd6*9#h7owI(Q^VRIs;fEg|JMx>_Mr^6-e^s`V z-=_7;$L+Gla~)+bMF(WYeta5H8D)KSTSdg%`bDqnmKbX(+m?Rclh0$FF;)3qy!}#A@{yn z=+ko1<}5|W`2Byrc{A?J7ke%IW$)Usm&>-FtopyW)M8_0PR#FLJEyod9}>5jvcGDh zWR7j@{Ql7SHUC`N{Ps`JTK3EK`TxE>yH1`zEZ&h*Wp`Ej=%XoN#y5{`oGTc4Z{4cA zWf$H5H;L~#VAlKg?c2E@?(eWWcgWTI>Eh$_wub%xuE@Z^aLoo(L2n5AeeAp5Wcxoa z|7k9N{`}lrZP0lWXJ(nMHsqamIBn{ra9%CtDLogA{Aajd^$usQWVx(pw%{t~BMzO{ zC*M@<+Uv8?eTAOfxonU5uj0R0G~c>Am+36WXWy&l@=7z#3P0C$(GC8*PeLHE%5LWk zPqTcdS<7C0O}u1M%h`Lmp|(ooK8q>uz3%U;-EF;nBA!2wUE6y^sqyOFkm@zdmQRbD z^}{t*-8ifH{>6{?_ujiSW7>Idao6vke3V1huS%&=T=3=Z?dYYeOuD}N>74nY&h>{^ z=(P1eJ1yb&`xGYadiFJ#ZRMk5RxdA`?EU`r`;Fo+|1ZAZ{{1L}eVEL0vG->yyl;Q? ztbS@HF8+t(qh{YH&c2}kOJ)llR~LG7S>~eq{O%9llbKyWlOIFZhE`zw%^{wH%s?V&d%FW+q4+8YihL|s7n)(ts7~kF#rFj z%4_+yGs9M^uCKbfI%WOdZ=YuSDhqz`>c8>jlIij-mt%_V-3V^JT*SRMzuLvQz`pO= ztqPB|3#&_|LfNaY?7MNLmdj~x(vsx4ccX+a#YPKF zSrPO1T)WfpcXvjJvMFDHS#R$J`&D^K%_+QZ7O%>@84x{U4JbyU2tq`;@wB}Xt{7nnuO4&Dk4cXY` ztCu`$w_)j=b!C6cOFFfAmwB&k&wKW2s;*V%oWuFI11|HY1i#VTx@~33?!D~4lou^r z!9J_`ZP~3Grn@qfc)oI`R>v(}yu>H(`OB*LPx?K7&APA4dM$I(?wF-FI82xD7Ou_` zn?LRPgnQF9ygc4M7nJUNR=m>c@oNLSc~O64ukVO!F{nDYYT}1GkK>JA7i~+v^!*om zVBD3zCnm3Ue!21TD?{G-V){~(-)VaI&u824Je}KI&#pC5v9{!ryT|->+wUzCUHko8 z)X~>w|Cn62)`jeC`nqjf==MOXiFfACzbD*#@<*1}z56Ap_Ki0_nA_+|NlDgf0$THxi{ z*SxsJ^(Rl>|4sSzws}2mpYp3N&wgTgzOwIb&L#;1rbJ%u+MvCW*+Jb6QBT)b+w@$q zH_Ke3SZy`!{Q2D+D@v=P>cu8Mv3vOL`{wn!7p@fCTkZN~p5eqf>X(bdci3-V(Yf+) zm35le@z<@5JLGIbx!(g;mP=~_pj^E+SCF=wm+J{&FnKm-hYf%k;T_J=FYb+OAk_i;L>< z?caae?2=P7o51@sU}gWl{o7S*f2sRkeJ(XOM&G2d>XV)A?02opX6)(zzT~g0V(q@X zm%;LyGp>s}-k%(HcZ#|8H~&_bx4wIxe~pNHAM$sjf6I?W!6A2bWV50Z?60y%f4Tq3 z@VHEJX{c~rkfRG5Tl>?;$LH$K)wGKQtv_1B1?pudto^cd{ePEt``%B~T>jkrW{U97 z6Yh3F>jJqpIkVJOe!ea;X?kY9_}s@|-R6Wnp7hH3O*Xg6)~;ofcR%+&G5=BK?UHTl zBtFjj={L{h`+p_uvwD9O`#+EU`sDiOSCf}*D&>${-@ISyqsyh_ zDvx|`&Z`Z7ZfNYQj@$29fBVdLb-vGU&A&dZS*u{XCw}X@s97Z+|LzTa-*P)6_7f}r z>v{g~ch{}m{qk#X^54l^UpFQP*>C;7Bzfvl#`>*~D_{QqyrBM((a+>5zBRcMPwEv` z6~*Q=#G7=vE`9Z@w|HgE#ue{YeE(j`^4rSO{>HnAt7ktwd>MN8&A#97HhsTW?QiK? zf0nm?%IErTv$uZQFU`onupteSzt4e2Bv+>YQ^}uUHSylPxXRbj_qF`%e_dW)+G~>h zh2bKz$JFM^-^cia+jm4+F3M5%y%NQdA>~ciyUjYK9|XnM^1k^+anA;i+cS_-MOC> ztNNQJ$SXfKw3Mr?i}_>m%;N87VfRlz{eJHJk^Qd!_3W!*?|Hdz{_U?lwcytL19|r+ z|N42n`asDc!8Nb9@8U?h{6+V^#kT9;Y&1%ppMI%1y29tjadnwLrhVx z*3(sHk9)5#e^Wo<-uai4CI4;fnSZo;ug*Ca1^H0(efG_M7yEDh6WqmMdRMb*X=&)^ zXJ==Bx&P%tLG-`Z|7)Y$ZqLp9dmFR_Crk`nAHAMaaqMT&e`oGxbLQR8*?jiNhr|5y zuYLU(5aj%6V`34f>FJPS+e-PppQX&QEY_RKiFI9{{7En*Jk(h3R&~V6ga1~PT`S*m z?pXig%b%lIlcZ;cDemrk_cN<#AK#9>zjyoSv_Jb&SH2=$v6-tf?%6X6?mjk+wY5lf)y?YJ4cE)(|2LC2-g|D> zf-5hl)bc*MthBKuFn_$OLagQOS`Gemq4!MhKV^{B*dFAq3#-(j{1FaW7eA2FTD)8@kscg*~GVdU+i5wZSLf&@0Yy%`TzXA*$p=%r~cgcUM}mLje`dh zr{Wn7qpg>^?jBNJ(V(E!A)foSiTRdCy!`K%uWj#sFZphH_xRDgt@~m_cdv4C(mMJ3@TOJY{(UoFlVAJw!2iGN z|7)*i5lB~l-Y#s;Un>+nx$q4C!3D3quYCHl@8I5b8EN}ewsd6MyuN$<6sz^-Gj&0i zM1^M_at|uuto*bvP~ZFJoiFCMcQ>#WcCP8%wWI4v<{N*bmnG5r+&vi&%KZ8hS8&OD z%LLaeDibYwbhll2Z6a>JsPM<0;O+aT&V5sCR^EL3-a6Ht%TG+Xt`wsC#hF3t$?-al z#Hk)ZDVI3Zb0t>$PFQ^2>lgIi0zvVCeS!}!`;oH{y|4qB9f6vZ)ZqfG` zgz{O$U&tOWlsRs>;K%Xz_mz}twKB{OIXzhux%tJc>~)ewIZXn+C#1^S)p>Ot)l!eT zgh#yf{Xg~m#sd+n!d9O7Xp-@SYvP@dh>mYksfYP0jgId%D@eV*=UH{MPv6dN*E=mQ zdNpK)vlC^`bKK;ed0*g&tBI&@#L}Br=FjV_+Ne`%SbclC)l<_)ZSj-amUe7C_)+JP z%X)ETj*a4rDzAy!%XB1py8bBemnwc#IbTxo_MdxY?;g2s559C(N29KzV)hq_S()co zywJOCZnFJqv~=yGqW)$^n|bVPF2@6npGaRk)HZKLPf*7^#})y#)oWumtdPFB{428u zyUwm3ruRI`W{Pl_D94}nakjND<8l^yLfcTl8 zU#$N0dBGzeW_S5olh~4ruFAV!gs;8lbN|=Dwa)P!;Gpb#{Dv)NLAlKFLYe0ciFey_ zzdW9AZ=m)4PiT0oDsK||#EZ(0^#x|ke9;q@8~Ww!gN8m|OIHK_zVB97Rqm--wKsF$ zYze%3?arrZ_jMQ9xIf;IaeiIW>8Ellt{f^oG%-hA+~}?|^Q|T3S+C;Udbm0ajRRed zU;W^b5yO=f{3d;O-oJC}eqK|ueqMU#ieRqgo0FbdTl~vZn|egId~8{0bR)RXjN{WY zf%7%f>ODW*Qp|bH5vgYruhm~4Wxip?Y>9i564+goLl@k#6K76hzGtyPZB_BJx-0)# zMWl~xl{0#|`J$6WzMOZYp<>ha)a%jCH*fq1Kdo8Ay(3X)*2!x(3+KnQrQLshXKJ<9 zUZyv5)_53%OyvH3xcB1kX1Dzx=RNx8mC}3W;f^e4yTtk2+I&WmU#0f8h-z;tI`U{v z)u}rM+pHcLpZoJQ{=cd3Orxclha>cAVt*gLe`%SnwNWuBeHO|bFDw(+yI>7Ux5WoO ze0~3Kl3D&;%SF|7@9ymU(yCvx@aGPdpSO98HZ3%E{^FKW`RY#C#VF|od^4)|Wu1*p z_Te+SS$t76KhW&Wmh^`cjz$!oQ~uW0>L>P5RnPtI51&^B;RlUvrEeeO;_MJ?d~DyV z#yP+1+lL0LH-&q2PRyFl^JP!hp-VhrI+vScKQ3ZUpVjk;+whX>J-LHQvPaT3f8>9< z?0WUlb*0^Ek1CY9MJ7ooyjE9mGJUuGZN`qrw*^%6J}zO*5173Xadin(W%E|J@ z%E!KHRNT!ny7^qW^V7joonN*+`gg!p{#jDQ)5c|Iq+W-t3Q>L3_W8w9^}`(XH=muI z?G8FS*sbK^W{dyj|G#V&{Pql#>-!#C>~7=!0t(84r8uF08PCl|c+HF9tE{>SHDFzfHxq>ayxD$k!c_gHA*t=H*WJk^dj zdn8A6Z-2N$Cgq{~^gXK%2i^(!X*%^v^I}=c_W|L}zvoErUBxZ)VEG2kMFCDndb>^v zWhZaT>9~DxqONtfDF2I(65F?Az7lw;*St8{K`544-G0K2X#$*!`-*gzMRkeFpUk9=P745{W#|=clUY5rCS;kmR~>i znen2D)h@*h^&ateqW5PT*mWH9-1IN_QuRTW<26wo4<64{TEEew;L-78n{UMJe=Rfp zAGcU^^YtYM5`0q)^)KsGy}Yk*RCn(GJw+|m`d_B4RyJ!)10Cz-HNSIVQu861^}@0L zSGT`-I`#Fezb^|wnf`d;oY+IGRp7+R$Fc8cb9CnEZSR&{Kc#f{VY|Fvcue8Z4YxBx zIB)IN%7~b`Yt_S=u$=1CN$E!?Eza1nQ8nh{Efvw3Pt>fQRopK)mCbyEOVD!6cfZklgdRAX`21kppXta9;CjYwZVGh4qo*6=5lU-P{E=-rs8GIj!kUd2-u} z;pXbE4Wj1Tdxal`CUsTM(x05FaWJku=6Fos{3ADBJWY8VBcQza>x8HbmhQF?)`{)9ZR(NrD7kNsX8rG-tnY#Md+Mq>$fBt%=-Guttz%q-X?sDf1}m&rTUGc zCs-PjtuQ zuQjI6*lcCl7PG49?D3et`$F}?u*Ltl(l1V58S=2&@<^Ug+POKN@qZqPFEucisX6o3 zy6WZrnzz56Sr@|scfk(s`JiGz=J?I!H#U{3-~IC1Rry(vc(?1XQ{nrZVq;?$@3Xu1 z-{_;+Np;sZEgG5j%1@W5%)A*|Xtn&&4vx)9pU%&;IKODORgY_pDC11m2(f%kmrWne z>zwIcEO*F!^|nQ!*IoZylPSuqmO5u7`eEtJHF8!b&t3Q3oRsrxcGsVRWaAH0m2bHz z<@)@&*7Ns|L+oWYNFW%=2uYdY|;14<`*A zXIxwD+gf+z?h#RzO&$li#0y@Z{(1U&#hNSA^lYQDcjao;ovw^L!+Ts&l*=;f;hi5( z++JBexUsAFp5L>cr|;UQDjuv~UoN%L_hHhfJ1Um3-G(CHEPqd!;&G_-5r@{L=dVjM zm3j`j_C8teM{Lt%r(`lAx14?pjHEc0uh*VHJkpBE-nSghOkD{I%gU9U5ac8U7N zelNHEAY%LLi1!zEb$QD-`ykOGlfGt$!+TJna<1-tsremWY4f)dhSmR8Ebd!$T7Q2{ z$m4|*6=y9zmt@)9+ab~@aH?thj>gEoHN`z0TrZffu6mNX`NO*T=Et`jxtcM}dVkoY zw)F+PhXvP)GUl%9tPrp@o0Zz8#OM-N=DYmd3$}^#1Rc$Heln?3d#5k>O;KWYRQLVR z70N<51jxT4fJk{sgKG*Zoff9?54Obs` zSH!Hp{NFQ1;x{c@-+_sgHQ+#-KLbK#I&1-^e3 ztcg6r;+4h){Z|av9c8#PMZqJyl;224ahgZCbjIS?GoBl-pZ2t07`&Vg4g$CmO2o!QWv%Jb;!f{df{pIXK~4ClIYBy7Iy z-g$FWwF@_Py4F;mRjLe7dA#xAKkmOzwZ2X{6;zUWUYu>`-{2hz#{x__ zoN%!9?H4BhtV-HUlxmBl z{7oKM!|JMr>!=#iRww&X6Jnt9=STGy#vJp1|0lJi$r zPPqQO+qJ51Vpgj7Uyi0<$L;?W{(5G<-_zgr>y-_yHuF~Z+kZ8>Tl74+%l*X~&O&|VUfKfcM=eVPCLa_Il>-&Y)Re0%sn(>ufCGS2=sA6d3;-P-%= zQKZC^v^7$pi)VhAv{-jh^{=H`O77(af7rNp+cKxVS|7GmGcD9L=j0^w?Ik;-7S^3S z|LpIq57~N^CtpsBkU6Vz{e0#V-;;CxWPG@JGiu4q*taH27yH(wEu6#}YUfwK)XDny z=`Bmk8?L>7!=I)9e)?9Qj-sZ%UVS8Rw7;0v`c*XLwM5O2Kj~Y4kbMw6N=a&n}+7v!nbXxl1_x1UU*u&N= z&)v(s&_Rgz|Ht^P?0>z>!xrZM30*J4S1R4E5OTY8b@2Rf#UocaC3tivDsH))`q{p`*Q} z^N##oDH5{I8w^CnhE3!nJ9qj@FcYnI@PWth_4f-8P98u1D6dDxN3% zQSH#@*`GGwTx_~a#p=E4_I-X=t_Cex%s0!2dztT&!^`{@Xv(f#ylZ<*NJVWm|K43o zXHUz&<+wiQW~P)DOVVlE`MO`RaJ2G*BaH@ z2?e$l>lMB4w`AXRITp70$-*7V-_$z(Z`<3~FP3viA~~k`R!JzArD)lt;=2^Ld5`ul};=!*SYHJa*o%uBR*zvuId-27^3+Ybkr zePh4quZ{4&9$Rl-`*8Am*|#FlD43I2$i5fcgZiyoe&@eP?5CUSFFRzuh{PyUlm0+!ycsUEZ#^#4nmZ`PEg!ZEBXr-M>7; z^<9K_i8-k(wv$ZW!=Gs5a%l#$oS;KTo!~^f$+MGp8zQSE9BE=N@`=wEN|o&F7cRn>R0U`=igZjkBd}%U=IqqB+_9e!wkg z;I}RM5Wnxs!~d_G!7*^|A6xgd_+O`AgB!MSQ40eWtX{XP3v|gse-($xy*A?~rpj`b zA0FlSH+%V1JmLQ`P5Vmps+3L7RoA)A`M!()$vN+kKF&W{6>j3Y(k^ZOJnzMlo53&t zJm#P5^F=cD?P9zC6EpHPFZ{_Ae$n%j-SgeAXZPwlpBgA_z8hf%D?|zB? z#g=jST-{vWPNshOuV>oX)-Ug?5WSPU#DDP${chd`eX}|Av<1&Z%13QE^9&{#dujDon^j_^IMC zW7#wl&Azi;pRZgCl9<{(ue(&H^y{C`=go7q&hjRI{%P~~qx`%L?$0NKL$Yu4|Hpig z&cHYCc@=4_b3b3)zwq6?ho6_se0h1fck&6#Vz>9Z(yrIk?^FsjeKRlr%pCW}5qr)> ze!czc)Rkzh%MOp`r3=RWTRGA4Q|N(k_kCO+H@2^`{Mz|q!^+q%>sU9doO*ip(Zld- zm*?uQ^K>~q!?^#2z>bBiJDv)rHcN(1rY@%q(@RYlI2 ze<5s1|C|a7S*y~Toip>Jw{^+Yl{yQ&2rjIfF=aYONXC=x?S;ndyEs2B+a9fWU>1wQ zJ-52{c-x;b8f#Ykj{Wo`^3QaJs^TZ+J~|?85_{SYaaiBo8PVj_8#h~wJ!uV3+}U@L zcR%d9{$um5M;o=AKfn80d{e2RPAis|;kj-4h0o#c8y#YEbxjs;_U-bODK!>4JzanK zWqzU8Kcq+fW1WT%^PwJgZ!L{m5_MgY>?!Z{>N7sH|NkD zg==@dzFMVT_5G0Zi^ch|AC`4}K4Z;iKjYt*xyLUalGJf?E3{*sXp(tRX~r&HHFaI9 zjtM!24v)V0y}7t`SL%81KHs1<4HvSH_O9E;RQqO%^0B!V=l8t7;r#LpD?7WZb=Mo; z2_6c&?=4x+7?$qvY}!Yjz5HK{9i)yaP4hT*eHPc`wLexw@-xp14(*M9^mFN=p@;_SQh zWvg55)2^Cy!N4Q06^vFh#IGxyR%_~V`-GfA#I5teR<~aKwJaPIqeDOKUp zwSTs>e$f+~J9EP2q#(mt&o;f@eX!~5x$hS;u560fvO!VNUd`b3jTL_Df7<`Dp0(gr z;?c8nl?$EYe_dlW$xb@{ZqJ#!r*^uZU%Fk^%$Vavb8_yZ8wWqmlf1=N@gn4WTg2Mr zg^v&U28SlB|6D%XxzNi?uXkVGcP?8YmxT41$#Lg4yMLKqpuzma)#%w)?ZVw`1+EhJvFMW8%v&YDJ|R}`@KS9McB&yGW*$1 z{h7n7=fHC`{_oZJi#?LYv&^PFE&kI#|E~XQvz*&&i=piY%MAsM_dw}$&fmWL|F3hm zB&X+J=E>|^Vez=f_(hAb+qnsClV`Fziv2rcD6=Tn$H47c`6Z62M-SW!x+=AQmPJ03 za*LZqo%W+Fv*nvPE(;{SHc3Cl^6Q2scg8u3OpnTMftz14AD0(<=37uS`?2&Qz2?G= z+MDiAe$Bn}Z^6mzb(I3seeYepp^!a8`=egzuM3@@Gv8TAPELy{_>)-j=3sD+szchA zN5>Un)SFE29hG7_qx*4Zw9bT{X;~lJdgHUMPoE*9+jb(nDE>$BCacBAdb_uJsF(_W zzVtZfvc{|m){K-jtG#~goWOS6BCz{Xr=fXQ+^V-nXWc&aGRo1hi!JN<^!Yz6SGAl_ ze|>O*Ny@cH;dl7*s`|QawOQn=tK1fbe-@0>6+WGFyaV|mKe1`2t+?}77`!DhKo8>=O_h@11e7~92u`HiV zj94}r3ls<*5@NZVAZE^JcXbuBRaukemY?NMZa-I%>Ugn6`T2|&d-FdW6n=jB$+v4~ zPMUdr{j{6&wZPNE60d@~1eS}``yJDkda{^<-_UN^p4k!dvOm^*%YNt~`=goVO^U0_ zvWME|m{xnZXsBx@Emhj9%h_3Oars4PXiX36JVrS-k<=%X#W+>ZRdw2K`k^jj@?eMg zbX)F^GLlEniy!wcHqB=5tctZXLau`}4gHlhYh|nfzoybAeaB3Mqy2 zWOwWEbwy+-X}k}QIaMlZ^zc~!xn~lZM&(}OwI5n4eOV9fJ;}Lw*`=fTGsRis=lDEg zI&sd077$Hd%?+WX+Gn9qEV`aK^e?5%v6ylnMWkLU9BGoG$p z-uJyF;--Yr`gNQqc3*{CP4w0yPLr5@R7zc2m?Wz7@2_E6UkOTPI%?Y=K*TefVuV0t~q`RJny>q3+Ub?PW-&dkBBc1F_RWr5R#*!TZaSu8LjXv-0SkM+(bV^Zl6rU9R|rg;e9k z!-nR|WzB-$o7U^__$UP(mtX27=vqZ&bU*SlD=pISKaokvXR)lif2cXtG1znK54 z65PA5?6b#i*l3$0d{pPK>~~ww_3LgKB()sfQ~CMDqi%hv+K>F-glLy zR~9#}uFU+~`t&2uhc2J*on0x{6=)M)X?LrOf8P5| zhnk<)hVFAIcpk?!&6)Y6?Hs3!535}pCUDRHl6~UdV-7CPqO5I)RC+I7Z#xwD#$TN` zX~hK}vDHBj)=cNA&Xzj;=+Coxb+0r-jg~HaIK!e)>F>+=|1?FpS`)WF{CsQQ;`V>n z?eFh7f6f~`+$)p*{vi{z55uZ6Dfai_+bd&Mn(dVnyY=shzn!P7>|DvlDW1y44}M+E zsYp4$`m=4<;*BpgTm0@#lMenieQV@0izjD|)-ol}J@r*;>Keu6UEd>(O^MY^J>{v%c$)9(((4<3eC6I1!l3e%L(j=6aKL_ou$gUK(4lDmuB=8-_QF+A8elg zSLf_Z<8&ACzxDr~m2b&B-B)P&9#XQ+`Pj`3nT~iPt`og))-;`1!$s9~zrMUIIXX3* z#a&OyM5WB_$Pp7W=f#!}RQNVZc4}}m3MeUYf0D^%K3?0K>=mJ~H84$Z%2B6|X#zot zMRHb4yZ!7-p4pZAcm0&v`+bJx$2}Ksm~J!P`dvX<`5jB#&ol16lh<{3X#YNO^`$I# zn&-6N`_AnalTi{hdRjD9>uFcVSDXAxIi<4Zu1CMTSmN(|`=Oct(TW~REt#MIFP7i4 z&bZmli}*Fi_O0gXmmhA5ih<~b6(YJo(%!c@-n_`?plM7X}*pG_O zWvPjsqjp;dMHST6LKE5g0SK=>v@b0K_ z7HmkpIBREv>aDh=LHF20G8UD~*=5h)EAM`Gj?T<43j$5rHmv#UxRf>X&vB_wNo%$| zSSa~sR_Ub0_7-mz)RyH0S?mIJ=vI52|7fUI6mIc*Z`4_l-bWHy4?VnWb;`U;gywwV z?fWHQH(B+_O_u4FRT>%kEwhf?{!l(irt)9pJodh=oa;(^4?QdvKNwr|Z1tlR@(By~ zopQ2``Bd|N5|hKF^N!)uAIvh@$5Ch-5vX)FX|n!HuL(Pz7uw!v`+mITO!bkFn_gml z`w|`Jg>NsJern6&{VsQz_XsHn^O&f-Sv23ax-3}SHvaqm`z88czwy2i|9k}!c$LMl z7F_MTx7O7!!^>^dm?Dj>tgK!X&;MUjaB6v8WyeG%m#8Ve9ori}Uq9k_ja z!miwyark^v&aI9wGn$uO__A(WZQVMiOfl`xozI!Ozr1qNzga#r*+%7=%5^)|s0S-Q zWOq!OIDhhv*^bR?jJEH35vp@O(mA5=>h0H=%wcxw=Vnee?L)UeB8~Syb!OK09$K>80uNpFWi;X?}{0{d8&fHLfJZ zdb^l-v(pYXwG`m)K+>c&&sjt!K>y%T?&oY!_V`IPkJYOHzRde{@^$k^T#eTg! z%m3aS7IZg1!_uAIdEf0ii+X3zO1FPrWNGzuO7KhFd`rXPy=z`?>X6@nibj7guTxZY`TQQ~1`) zCp#~jK09I(es-FFYj~_7LtWfgH_g~=9QOW$(l3OYcIMu4U7`2`$7rbt&=HBu<%eOAQymn{IvbvvIo@;ov zl-tH_o1t*rDZf3X&-FjXXowee7FC9UDeB_(^o8C-md05%jLLS zwa(nqYmp`QDxdFN_}Sv`1<5TbSC4JzUlYLNzTNWKPrfTAyIS767hIWbpud_&!`)rp z^yV+F(R;nxQHH19MfSOw{9Ebsx-Lf))IGWwcUnEI z*t51X>(&Pe%kR5pFqZeOI8ff|?|bNR=aEO36Rw5}#;I*FS&}+^-;u{6*;j(H7fwr> z$5nZD`t)mSOc&Yw&r@xau}`ki{gdji{>wY{CAyIvYdBDV79u1Tiv|M+cqD+ zCEYbYX|K=sD=1Fda=Ym2Bg;1~jz7PCZG^(UUYo^a_)am-p_t!w>iMP{@=qd zH-!Cz($mwQMJ?f6lbU0^a)sX6#I>5sUiFJ@O#Gq5cg!#nQ@Jv?an z?aeLS(2Rz~&Sy+}mTlhinSV~fjFcVkGxz=by8g2HzK^_7+w<;jXph=ap!oOa{Qs7* zl~1ScuCcoR`|js2x30ZYu{y$%G10U1Wy7=gs}0UOX`ep!Xk85WW z=Pl1h=ax*}%qK;qf*%7*E+w(oeA^W-csKH_xOnxZF6PgX_qHoB-WM0V@>60@eCZqS z-wf8iM^3*ePX6yHxQZzz*}w3lDdqeiAyR#~E6|HQtEKVM=ZTJldmckP&e<(^ZpRn*dt1=ZdY)wXZHaHY>o zSB2;Bsj1p8mrV9s^!E04{@uLt@6T+P_1^yfhyLtcZ^3Js3T4_qc1VNc=3C~zzxmsP z0eTO+=s1e0q9%xAUJ( zr}b_>-C1~O%}U-!dlp?ZzxH5D+Pi$QOku}sOI{vG3|4>hsj_EUVC#>b3zj<>x|de)-Ja7Y5RFueHDY=`e!N;w#U>mg~Rdo?5RqBl}RpLxuscoL5 z9Cmig^_a@sFI}OEU#B~xs^}hOxEoazkzwcZj!5S(k)|v6Jr?dKtTtUm(>ifUrc6~l)eY|9j zbl#3e@No1?V+ZBT0FFeHD2{V)1=N@1XUf^TFP@=nlX`8*n^o<$jPBF#6rZ=lnXy_q>wzq-YcNIN6UG)3i4G zx|GlD=4jyZG$}jw=cdUzHbZl(aK#_5_e`y+5`3$8E&AV{BMvJQ^yAg`XCHnVvLb!r zyv3=fE(vT8d%C$huhcf&I{wXRKb!g9spV^UI`;Bhdd>Z4ACLCA>r%$;hi2LR);V)J ze5;!4wz+$_mR+*x|EK@&k^YLkwLdJh?A-F>JO5q(o_BL;n`l(b3Q8;9V^;=)OS9|_gg0}Cpijjw`Hj-dsOso zkI9uS&t}eAyKa$!^&7$GlfLwdMkL+2%=Zzr?)b}($Nldw)$V({?e+`r_+OW1*jw*i z-J4djwf%^2srB&)QSVKLocEb7tG;6M4Nx}Tb?aEopN$7L+Hb$J>2;Ep`Gmi-_y3%| zQO2m?LQCJ014kT<)U2ZS^l-g>mGhi!P3XMrH%`-^9i8+{Zq8esiq!%0r@58LEec!o zu7a;-x_?{!j2jiEW)5xHa_8GB-mgt({vO3~c5?HA`0LjnUU~lh>M9%mnte~RTG_l( zU&(3&)-P3JvOE56$*0{*-yBK4ttI05;NoRIkBtf}%r7gGSsCt~USqMLcHsvxuEIBq z)?ZVZUYBk!F3|Qqv+&i*<(D*<&oNrm{qG$6^h?Lio}C>IUP$)Xq7T;Zn7rXc`Qi0) zQX4IEqNAm2zpc_|(Kolb`zWh@!OV^_r4_R|);le+D!93^M`F$cmhB!go$Du_;6EE4 z&b~ft!H=A)QfHMO%Y8JfvD>Wv-m?A2`MPh$yFMP1zJI~?-}3rj>QzrBx*yxqUm2_( zaiqiR;4Itx-G-01bgd3t;96F7Dy3}Zd&LNaf48ph`_}gT-0vG3liwu0xpMB>5zBLr zlm8XZGiXXZ)ID|1(XzhT4{yxY3f1}*8?O4{6mRTN#fuiEwXyBd$7T7xDqM3uDZYH4 z*t!(!vd`Ym5v7|?8!Jd(?aXG1YRM{Cb~{_DJK3*d-sg_qNyVEEi68$Kvg(SyQms~7 z_MzIe``h&Gmc=-xnK}ejOn80Wo5S#j=Y0+r--EIJhi00TZOhP-Gyk`9`8lRUkUc6SDD)*W^uy2j)13O2mA|uW|B)d*ukQ%xp6iKbk}U3*S9s*COS^2XT77TZ zxvN`Gu1whIaA%6ij<=mFHZQe!mfSy0{_hL-Et!{>UCgu7*}He|ru7>y-HZ|myuC|! zMf1HdL6(VkW-aRaah5%SaqlUo+NrDFEZP2ZRg=|u|GvD$?R$^zI-sr3RAwWz>DAfb z`8&f8mhy?(r`(EOcwo-&jtPz-erosHV*ke!IIL&W)LYR#kM&ohS8LU8?Z6YZY|zjnKe?7Q*uSEqm7tgfTed>*@t zeO{ut#`S`NvfLxTJ$BKljk{N9>^dip*!IRSZH>Aw+j=6KmCe%Db^Ucos z)V;so?LJ-;dw8Sv@2r#0o&EN3y_jYz|0v3&W8b{(h7O-YH2;LCep`0vzUB0LN3zZK zFP^Av{buV%o!$4F=g-R$(_Xx+Hu}@SpZ~9wOlz!;ZON^UoxSdu#>|i}{C`DvJYaCQ zoxeQwal0IUkjsT)arN4jKDNxvLU)C`BYM`ZKd{B4=-br~@}FM?2gY-yMwlkd{Jd+D z>u+a=7t;+_Wi~$-C{NX^@Axvu-(KX+y#1d}X+O`+-z*vY?O7l-UL6vbIihI1`!| z`Ssw;yu9L_{GAH(#W=2f&f`r^-G9@{r)SkhwoQL>J`|qi)IZ8v_Ivv3kVi4U{t59; zIW2in{)1A}gN@riJO0~y;>RqOl)Zc+k&+2p*+p!EoDM1+iFm0c*}L~)+BVTwx`h`L zUReHE>0jHm%x~_kE3W5RJ3td2yL0<2A*u4WJZy0KeBtA>{NDSDjVHHKi@Nw2@f> z-Fk>cX*;Lb{l{(rE zOP=P$yiHtJVG`<_oV4kq`9?u)mfGGH|4+}>9#HbDt+bsM_F>+hc_K_t$|DmGJr5Tx z+*iLq=l;h`v3pOoBXbwd{5@?t|3wOkK^q$ z?$5q>>zdpAKlb*E4P{T-E8bFj61cix-MI?U7Y{zPTC5MWD0qCVgGcb|1(|EJA`UT~ zU6i?W-9}{{^;eZHCM7K^cCbH=wqQyvH@fzBtJs*8g?Cv4*A1=RaOmnMY>{KF;lns8E?(@#&=F9_JL^Sx#L6`Fsg`#HHfA z*6^C&3Tcdu{jl+3_L)oDj;+twli8&8x?xYgT^mRJqPLME9Fw;C-f-D`#rEJqcKJQg z<<{5!9{j7*W%NHf-OjAVb*ECP8@u{mj*FJXp3gI)<1@F%xt|hrm)Usx+unkQhYa6m zXXkr9pT6^UD&NY+xcxpo;!b+@;Wj=J?*2C?M9RBXeu-#^h+P@#^5YuY3C|MVr2LXu zQJ166+{}pO*A0I-$3LakB*gD%y-C*9o%!?6bv%jMA*8{j(F>b7VQbeUzDCx&HjTV_y&0u4lEZDO|&w{h{LC#;P{fU-ODuf1R3o ztVHo4s1LP$-`BOiv0rN5M8B(=Q}*pkZ)F{%*=h0H9+si~Kd!gm+y3jD$c6@x~ zt+&)^5ipJ*SZ$ZlRgp^v6u6l&eQH@yXlM1 z-u=7#vysuB*Rk(!<^H}mwRY|E6>77Y1a@Xm+MXx*F>ChuFE`G9e*bLQafz2&tSj!n z%;Fb4Zdug!`TkOUr=v=RlWKkn986j5TQW`Qlij)0O52mO&vX3U_Q|o@aKX`R9kZ6_ zC%BlT3J>R%3ZG3~%K7QbP0#p=Y&*2}+`i*Ee}2kY);r%0mdc;AJ#_upd6O>I7vhag z(nqSl%)B76+G?tn=`G8Ei7~(2c1$SwdNn+FYdaP4xl z9zL?z_U>NYuCMd%^S?~-ooP|16#M6)cha8r9cgPLmsvay{Sa(8$$010%e7-Q$kd6&(fH=bkGpTE(!oP;V8_>JO{+*WA5mz3g#ctGE8%5}n!R zscU}Ux);7k_tC1pkM0>JYxJs4NBSQr@_m2(Do4ZyeV}#ry@Ve=10_-zxp5l zq#U=XZ&l>yW%y#(GFAR!=J^CO@5{Hocy9~-ZZ~gr#r#s$&ZK?63wHgoT9kU^?s-Au zsajJlChrSbB&_{KdHxu)|=Vltz(#gVjoau>}q^UMT38+S36E}Faa zlbq~-?R7hnw!f(l|F(JY{yCEFE)yME_>*OhSsQj8&h>ZE-d^^8TDC-&GoNM4zAsDl z_dfjl`G-v9lZnZ<&Yzav>X;iV^GU7eGy`F@R6lyY zIdp!e0pHtLrT8nE=Pkb~d9}N3+?b%ce8LpoO^3G6^WRgxBk3oY6Q^JNIimE^>q^zfQ$+MGU1cyV(ptylu(v5*F!3Ij!>2RF zcKbQMZoIi}ozaCAtE6~CZo0-#J2Bf0G<3b`w4Su$vt@qX^Wq#|znuqd;qW06ctEk2blNZ9A42(R}JbUHZd-@8Uf7m!0!| zW+m^vD0YkFaygH;hlKVEN;SQT%zrC;WAWqjk3MHEpZl$?ukhc`=a<{<|2#B&pIlsi zUqJ7*hMn>x*W;REqxb&bhr|5SYkQ`qzTUQ? z-iG7kGd=U;)mk%Oi#WB}N4?HkXI1hmW0CuT8XM&!#*S6#Q=>jzkJNBqRwHnEtApq% z)rTsN`HY$BGS=m6w29F$DBl|+my~+wd&>Q?k0)k`FT9tuB4sMq`kIvmhK;k2ee!CM z2ox6QOgXQ8`-bCv=k@IZ)>B2E1{6GgT$Cd4sKn_$*E>%^sbgVJe&#(nrM+Heqi2D? zzy04=eaDYk`GRwG;heuRuoX1w5xeWOb?Y826}{B)~8LSB-~izV+egn2gpfBSc-tyJ;hn(w>s z%NE=4CVZS}zh8UJ-J9uxI$AoXe{?Q7Q~g+N!!kzopP#K3sNGQizwFnawza#9xE3#3 zy0o?K&trLSpOXf z^R1aR_ila6N~OGC0XH5!U%N^$@XwtvtxG1`LRO}@R`zhNm-2A8kK}DQ9hh9dOYA_{ zp$JEXT&}gPjXS*5cKlpz`bsx^#r`1K5?5|9oe3T*H>^tJsMvq~|D%J3=Y_$8bTaAx zk3l!ieB;df^;PNC&9jwv4@fQ!W%iAog zu6?ud_^Id5&dfAkcD^xG%$K=*i_7{GJ2KAs^YJF-Zg`Nz*ZJmAM%rF0_w#G+7Bkrv zU-DGHxcB=#>t$~1^Q&*)eqx+Hr|{T}En&8*))^r#$V(LJ?c zcHDZ6jwb@ont$Z?)ry=IYLwCYm9^DrX^^W+^@8Qgtj}9~d)oSCnhM|V2Xl_6zu9x? z(VSY5)!8Xa8*8pEopH@LU|Pf+H@j~e`#qaqrP+Lcq!qO_?edK0-H>s)&Ho=k=cOOZ ziJh;j`h57)Udgv_WlK&zoqxgCr{Xf(go5MdyUNZg_WS%2_?ozW_BnB>y-sJga|ns5 zK0I)~HKQ-&0MA9)H^x$>pJ>&qwP<-{Wn_|BOA-sQ`owzpeWF0Y?{E_I7c z>6O5e|9`(<1~o#KU9UZ3c>IF>zsL5Ml>2R*)aO+^$|#@z@Zr}%zwxXL3dvRx6 zFt`>yulLOEn7N;RT0On#s2A_4<-1|Z;+KtLlNH`PFWvBTj!Q?{L^&=4MshyJ_9osgQi+9TD=Kp*LB=faR z4Kgo1UfQ1ZN7C~1pYkSY7xwRazPcUgk~Hag+t|JD;UE27FBUD+4y~9|^^5Jr^X+z< zykV=sZrYy&t^NO5{}Qsc=}qdiy6{yg}FcbX zJp$OMl{U!~ZmlZlgE zVQT^O_x~yCd|jbzvpr8c_WKDV!MUmpEOWhmGs7xx?D?W5v--Dn#9>`d_C-Z~_ezf_ ze)*ToU#q;_RBf7GtXIGNKZ~_-XOpU8l?p0M4}q?OUiR1Il5M~;{h0dgI}Qg=6mOND zciPJCS@ZV7hu3c|&`sKN^!A7IkN!O~3VPvn=4?_aXM4)3VBI&~&o|_)_<>J3mtTIU0-EnFTFNx(Y*ROdR4xP)9uem@7$J>=jnKu%VE!bCq4JKN0~~uh^^Qg zv1Hw~MSITO{ur*f{_&yswMT7Aw~1sHRcrgc&sr>2XZ&#P`xcH1zh(3%<9h zLGI7d`fMraM9*Ux3laV~-rz;2>G7?1KiB?Ux@>v8hRBOU{V`5E_f$1Yn$))#^zl^I z$#aSp9Pl>rJ#obBztJP(CgliL?>xEF>#8z;X)!J7nyYiBy{4+#zW3pT;;z8EO!D(* zFUWiGZgI0($sWz^9g8D>Wcz>ma_j8OWAlxsSZbGLFzkA^M*ZbO$-+3D2U1U&#P)9L z`}d;5*~OvOIHCN{I;$tM3x983-_|ij;got)jn(@_)-gxRWRLGz=hP6e{Mj#h8?;10;q&~(c@bx?{dIrPuzjkg!7KK3oBL6B?Zm{+_}@=3 zK7Z_SO1EJ}*cOL{ef5?5c1%dT{cN?RR=C!uInFZjPt3Sj`DDd=qOGRCd3jmMEv|oY zt7-4%iAAhgTs3kLsmZ*_mfia{$1F(NXS(^mxBlJ`u7o$=avp4Ga_$gh&uJokHMj9>5fz|A`OXTP^~%&c?gvlB49yyex>>}@@- ze;bHdsHL2IE&bK*%88<%S0Cyw%HBJzkYCw4cDk-ZUhk#79baP@R$hDg?`ih2BCW@L zp#5L3mxo0J9uEsi2xT+RdAe-Hk5wPCc&gV1Ej4<+VfyM2t=Hd+b}A*A)_;7@EP8KR z{rXece`ZRp=X&^FY2Rnt12-Z+=a&evCAjbHm=Eu*68y}`^v@w%%6#o6Z?YY*Pkb|3V1SD{rP1+i= zaCOp`m><^WQ{3$8I<{}@sSM_&vI@P^#ky{gQoZx7a-L5{r%1XB8L{90E+10X!#yu~4mH0L-Q9Bf^ zbZhZPZ5I#0PfweF)j!%boo$1f#HVsGiCp0_x$X^|ikDrEOFS0Z-nxwa;>7zu|Gu7m z{A;ZQcmJMUjb5sKe=k-qe3w<4x!K{2lL7a(u9I5@qLwBl=AS%&@%>qQ+Z{==Ywz7N zo_1L>{Lh>3`avtBChjReSNohPBy?ZEgNHmNc{j3pqgNMf`}|vL-sf+B?E2NE_I#@F zy3%V>bM<@nRjsL6T5rO&ep%Ydb$?Hk7kK>B@5F`0@y&Mf3V)`sJUu-(Q^W1$`$+%l zGfmg4CA|!`&0$H}VRQP~-Q%kmD&s1zc72)jAp6S3M@p>m?vFQqSo8XO%2BtXCj~Wf z_SW@w^VW12PP7x@DCm7?D|g>&YL@9M-OwYR^Yw%tw4G9)Q{Z&j^7!`;;rnY#V$OO) zTP*MeUyo<~|MBy$Nr0BQt$W}0oj(>&KWQd)ePPqQC;Mk_P;1(`Gvy&)no9-$*`&1} zmwHwho;u7@d!*sU^9RS)Z(5<5G=EpY(ZKw7alMBFHyVU~I%N1pVUG8{il=Rbwfb(S znf2FmO1>z%TjRe*c}3USs{-na9;ZH5OKz-D6n+-Gc>7wj9l1AGU7UAn$D@x^U%x!} z+~Qta>|3n^-4gk&@?|?u*F62mVfiseWYSW0vEx&|7zz~5js2)|<}}OoMg3>(@5#TP z@Lj!WZ|Cn<63>fQL^Li~rdKU?U}?YtC5u^0maVI_G5%Ox;ibeo(^%kCA@4`I<^3&o zf|>K#_KLk$$`O^R3|q@^(68L2kMCm8itjD&4U_FvrME6VpB{7aK&|$deJAcuRJo@n z#{J49U~S;{0}0}`$L~DZ8F|3q!^Ua4@xIa9-pQ0frfFrKAAqM97Jpq8Ag7!W^0|BGM&`=pGJCXSX8o39vZ+rGym)8t zt2^fV3k2pV{>r|$>&TuP9qj&lGcWS*-BfoZTlT%pjwIdOH}Ci@pRY`hI`cs+dy&%y zuG0}G74Bu9UebWsbUeEL}h0yr9v$;Jf$EY3+FKye8z)2KJ~E7t*_r z7WG+8l2GXWKC#fp_)qRx?_F7JM?wu}BvwsQ+H?DJzuN_d^Cny+GTF+HL)Ll*zi<=( z+bSOCAuD_Kx96KXH@*4ocIC{Ubr@O>%tM50zuiZnwQ<{1k5tSyzE`Q{JIf|=owCbC zm8=CGdzPJ9IVJt)t&Xi8E$&Z_x1T-IvSwX{;IlK4(WfKL_FB$6x>n|qosVZ*!mMW% z-O4fkd~Jn_?pyZmclo|=&-=7B-=}Jq9N!dLRXZyy`NgU>4ZG+o6OMFVQF*7@H_`1O z%csCc)5EvU*1G%kQC3yj+kKO7*T1dxzj#b`uU5(I8EY@2L2Z4q|zo<-7Tfs$6?=VaST-zeQ#KhKU@w zE2Mbu9si3OkVdLzHI%a%#V79J7wEq)Ps=l{>O-N)bF zkAr0V=VuSY3RYeFe?OZincnT0b}ekzm&fx@bnIGrTkKIn##4zc@n@G`lgSch6@D1E zX~$LJ^&6Ett?km%PVZKGvXR+**L&kmvy7(9rd5(NLSs`8+G*V^x&LYFmbB{kJNBxd z{c(1)_Dc6h9XivGiIiTPqPkl?Nq0)B=pX8zuZ`zz4oDIQq3i$z)K}J^aMVKv7`h}(>-rL{fukz6=nB%=_dX2uene7 zneWo4|#M+(nt%RGn^FPwMxwbr$6gLAy0lbgC^thK&9OwVMQ zBeL{M#iQT;d6^>DE($YUG)}bHkmQ<_XVK(#eyQQ6w$5|qu`h)j6m@l3R1HI4EepTC zuWyN{!-UrSkCCsQtq+Xf>GfCiU$!9VN|Ea!3)1VhKVj9Z0ayNyE!fSVBaOy-FJH^HBhT`ZE{}J z`7qJ*+?A)qKRxPjj}b{K+mg|6Lgw_P={6PnE?0D|Np$#fz57AGm%}p~6TACdVGlKZ z!V*?&(tn=l%A_mv!tivMj>mHkt!c@6ON}+Q$!!be_|Z`N+%UC$PsbdS-&O{_a!Eb3mdb!+L3{qZ)_q|Nh}FFn84>Wvw+2xx4Cj-K^+%T<*~=HA${ zH**iKc~Z^!>ih1wZ)H0_-!j^9R#E>+kQoDNp`7@0hwn zMD)J%wp}~6ggW&lC*)O`IJXAPT9?;g(`&fqdS?1cm+ODuu~wyi?khC!umAMIXxH1B z;VVugnTsR_E=xYr@3ULslyivx(u&@#6E6xWNCy!bDm#(H~9*@b~cVU_t5=Mkj}fz z=BU3;pCj~yrk-&H%b&NTE6ba3;%XCna8FfzfBmg%(<&1(th3N&KZ}2j3vC>l}bTQ z8K!gNkJP91v}f=aJJ0@ZdGR&7Wu5r?J09I}cMbI3`+C*y^s=w|;(Bs^`ow9A-7UT` zKPnVxcwH4fVdA+H@0azp&6q!RL)2~Yxd9-x52k?-m}k(aBiq_ ziJuZJxoAO>pi*sHbFsZhtx3|di>~V*r~UTe&M~{67BihKX6zZOp~iwa9NYeq1; zG)YI*nI}&89yi%ScrVi&VHc0PD><*7v^4v4`+59w)%E#O<=@#iR8-zoKK)|rTI-dk z43*zZSwCyBRcFyj5%-{pc7gR1YxR=TnQ}5N+`f{9*+&v)LnT?_a{%OWnz5?r#tM zGjqAr)L?qUl-*XGp~5_u{?^myzUs|U7 zIR7iO7yS7dY;o7)zWcWSlydLvS)93tw|ma>_!2|WZrAShoC`Iky<^n0<@)+lhPx+| zKlpdg%H~^JCN-VV*}w6GR?;fY=>-?tU+{{4c(5n@;SCo%my#PCk8RaT`_mWYH7$IQwUqoGd?S1w8jx5LfIU*}AuaNF`a16e;A-ZGd z&9+sJI#ZLp=Dw?ZSo*7d_xuIVujkhWKQ|TE&WriP^!IzT_~li))~;Kg9%_7Y_%_P} z%h_FD?(~Fmn|vsEePZE{0}p;F{(e+0cF^?zA9ISfWfSjwl`yyG!8YwvF3dAs@mT9> zyV}Nrd&<9BUEj$ZcX|1F!Tq;$CVLA^lzT0!!ggv={(~j_M@_6}r>x4@w(no_om#z; zOgp=Eho3dQ4d)V{bo6|0ax!0yg(}D27u5@#C%JI5K8>4SUv@rvq2s*7?$eK6yBazt zl?jw8C)LGD9W4^G?P7Uf_kDNfp%%`=$L@T%QBwZ>;%5Cf)wb%;LFc>eurk~lG(dWC zdY)T)x_E5O$)%18SJq@zJd|cha=vrs;MF|8+|K2lonL?Ga+PZR$@(31Bql<|{cOx{ z&q9Zzp?lUvY-(Rozf;D#@~o@u-v4SfQS;9n`FMWzpJzKmOWuDnef-RB?cICM|L@m& zzx>G?{fJXJc;0#2lkYeunsrLd?(`M>c-iLL4)cdz$= z!?x4S%d2&Y9>ia|Y^i=}bM9ilQf-!*1-!X-e+xQJHy)5Zr}XmRtS`l`4|;gFi|xL$ zO+Kl6$%TLipVR&KzFls}r=EFC=lGUTzM^}b9ewT2=WTa>whD3T__|Si!r}EwJD+_h z7PE=@a`F-H?;4qVQ?}e#;GFHsXz~7asn+d%KUFzCCC+h{+xP71W{>;PY2tguI6+4c zeSUsE-|Fv=_jeW@kH2v_7`jbB^0>(Hk2}Fj9f}z)U5{V%yZ(F5i6S{JF^+zlUm8|5 z{~k_z#wT8FcG}jhCi`BG%8d;>PJA>~5^-}B=vtC7QEH3Otuu$F%D5y6w?e9b9#=n2v9S8OEa~_uBcZqxRyCwRX{EMJkj$J>#^*m?Heq-8}ny&k2cyf6&gWuDm}Qyi0H^s?qLugbHd zVH1}qZF_h7oodmC!!3MVd-^uZsJqMD2|EA$%1!-=&Q{%*`&*-B^lfEB(pLx$d@Zi5`Q&yV_KCkvl&C)JPelX|B1^w7vB{!}*)I|B) zY);w^@1);Pe*gFST<}o%IRTr0SBh?J$gIA+@nOt!Zu=jJr=H%dJkzv7a;Naxh&hKn zb{}}%`Ri=HXNU-WR;ZmJ`FP#@K8L|UQ%%Ke#f?&(tERKXe4doytpzUf1`C_ z{Ieg=Eo6mX`c4-;@^${{i<@6>yObTh`{kX*&o6Mc`_u+M=QlsF=jqGvjEmaY$tm5u zYkG5Y`D85Ler~R!)8C2gz6+Jk&o|t#ad-c``0Z2YMeUt9FJhX${Mrk`ARp_pEGB3B`cIzBroZziHm#kI$z^c#Frj76ICv2uHZW^VP9TK*oee& zZ`;Yew{gLa!mN(56-~GArsnfxCYh$+a%}zaWe1>*) zoz8yp@7S_~(1ukbY`I&HcHW*;!KtUFE_;6E;ziHr@f8by-Esa`&2vKF;8_;^pYz;~ z&ET7Ua_$_ur^$zflY$ka@k*)?V|%f=QDr#SWbk?ycO^nXvh8U+$s`flC{wM!wCqJU9P*U}LwekZ15> zCULN__XtTAZcm|Fg;J`A!SP?`LDa#F#i-zU8H>uJ>zS z(bOnzR;!;+Ca>JOb?UEQzuId3uIJzKi{4gx`O8Y-?>{!bf%N`*>|-E@W~SdU2++E0 z%Wr%4OHD+8^{W}l8UG&2FKK!QdBLoYOD;$rLVoUHe3BL;t!v9{g(~yk}Sanp{&as=ReV;`9WuDvpOn zG9D3~FIB=cR?gI};&>=NQRQWObCi7Xk;s;*i#oO{@%p+<4&{>aZdt@T=j$sL_ook@ z_TJ9?c`SD!$KfXZdYvz-9N)$L^80K~de(5IpI9v>Kl_^6)oBNd6=bG`*F5w#{afm$GHf`q{xp}?9Da##^++bzdZT6Mb>2gpv=rc$c_nxL$deN@UZ3Ll7a1n4!phz8HA?%^ z%mtsDj9;!6@}9ih>6zA}zn7%B?(gfFy>`cis~lB6_tN8jc}C1|jB{_Z(u zeY&BmC%N9g8nuL#Tg+qejfX5%_sZ+*JL9TS*Fsw(7VH|Zu6tg^riQP%rC+$C1V+ZpJ8?6K@ipJ$~x3s9@3Y z>B>14%cUMIcP&5h`r=;wrX@2UD!QF|ug2r{xgg|!(*=H=CgqNMmW!TW%b&bHQrP9( ze^w!)p_9cnQE^uCx<}6itTyy|8^s;@sK01k`y!4czmOH}lVkb&{?wo8*0`hoUj6n; zf%Sq`w}KDE{1RI#boA?-n9Us9e_Dxhh1qs)oi@Q$fBr@$e(AlTC${Tr>vS^TSr>fT zN{vl9N%cxu$-RU1^3R=TNJy9{r(JOtxgOlpRR61SNr=camKQtHYM-9G*PL-yuk@dK z{Dtf%=|bAlO)q5E8F_tBIv)L|PssS$)(c|k`@24KDo%2n`2$SqraR+0AFd4T zfvy0lkx&K}K#=d1+ zwqKZYGFIt||LdH^C*SH`xf!(Nno|C0r$ufBZ)6^DOlOSRzIl4w{cFzJ+e_42*?N*i z>Nieu_Ou%5&|Exd@lfl=jI{?^fS={i4bJev_MH?DxRMRi^r_;+Zm0r-S=gv-e}`6ZO-Jv%ehNu zgsThQTPrs;Gw%#5=el_(V)QaDPT2l5DLj9t7Vlhtn~yG;BGxiC?sBzRKR3LGF8BFu z4$GT*3F0sF-&bE^IlC}%<2#A&?T%_S(^lkl=cNg%UJP7Yl~`~udfQfxbvhd}|7>5n zYK6jz{i`x`{C+7|tbW3Eehb%@kKf(SOpq?|z8U|h({FZ%+jB#ivjx((<8&u2zF5Z> zSoYju=Cqlc?OZk_s5TziQjlhT=gzZF;uEC~?KXWh<&A`G@aC5GsVot(3s(NUW)N~D zFL%>P`9R?9smF+WlC_-(DdtHdE4j0-utVdi#fHsqmz}Vqu}3Czb0v9cFQC5ttNu=IpbBn_tK* z@p9RbV)WRwI&OBP)5k5|hK>HoMH7}5$=zEETMNKzc>T+XB^xz`S$#zq7e&XeFP~tx zUS-kqf)jF)M|iaPh1fT~ozZ$$;7#A1y4&>`N3$#M2z^w%`ErBe=EpN#b9M^l{+V?> zrd+pZ#VlUOSgB99EMBZoZfmtaZs_Rwt>aIRLHX|W&vv`V?b)$<@+Qvr4M#5v6eoXn zV6D8{&h=F)d-3n?vk{?B+y#}>&Upvlnc#o!Q0~E3+;h(IZ?sj}e&f$21?i5HAEvZF zVr6qOyClRCbMKe;pSDHJ`&DX1OT04EY|k_AHjBG^VE^U~I=eRQN$2|%Q8lqzq~!0# zghOU_D#hy47dsjHZ`JC4vdh^rH78qq;rEzs{g)xf_I94vBi0JMY$=_;gwq2T2C!+sB;+W>jRztbyvZK>)PMv@Lb#Y#;K*gEJDbM$@ z)ZG7>&z^sJec=(``lVjW=a%jI^EONE!uq=#daHi3t=POc*JSZ~NZaO(yU7+=$cY*^ zt^fSlcJzvUKvvadrp#CFAJvxKrD-)P;K{Xwetiln3byXv!huYEU}Zlk>`ZM{a-ytDIkpYs>a+kg7s zr1C(QhRJmt$|VABuM29O!}*oZGn(lvh;rL5tI)MlLRdO+_l^(xQy(gA$yDc_y0qx1 zRJjtH2RDnrp{sZEnyQbbp1b|BQ}LBb#|gD*-2%PmyRv76dlU#BOGa~5|UZ+t&d%<@wD zk!^kRH!f%C?N3%bccfDI#bwK`xk~dmw@+xT?7Y5Frb_HfwU?;XqZ6%HE?%6dDYE3l zzQ{T9D;>Z)uKOOpnGf4u7uU|Z>h`-@C5~9)ukSSepE%W7i>F|2UUdM+&o(RJogl3KMy4kGjh4J{=X()^$VaM*3FI8O^OB zvwU5>FCT9<=uGB5{3X=4zxmjV&ak{g+pMl%b(Ak(_S?MNXw%!bYAzS;+gg<6ST5RE z#g(1y@O0d`By6cv_w}h#q1vI5nFr#Fi$v`G(+UW z6ZOwJdw-f8(Au-y*#0N0E0<;9>NiV7<+OvAJXTfOE2`r%gFo{3pIu$o6(23%$j|hy zR_RRomNF5R3B^-HICkXgnHNo4e!}klg7@u}GFjRh-&!84P1TO6iQ2J|`IHyy@7CjO z;e`oDoB#1XXgYFXqfyj@tTl=^PBH0RI^b~QK7Z-O@WVS!)@)4jh)Otg=keR$r$t}Q zGHG4rKmXpB_c=Rb7K*N4d;fKPw=8(vcaFI{Y`Nv@J)5$a_iifso1mV{)LkdP^NCQd zo923Val<3dh3lMRlbi0^xLw(>pe=2wmxz;d4A-V-iVsvPA8Ms!PvJP{q#RCEkhj56!Ie!g{P zM_nf#Iude*xpP}*^BSR@Z_Rf;*V>f+`T5fyMK;}2Cq|1I|7y05zH_B>vy^Yv&I=6J zYqzH5{d!T=uPo{5`Z{E~|8w`pa|8t~HX9br`H=Kyp4-0@22pC&Wv+}Z)w)wW5*;7S z`5`}Lq0quuMr z9SkRNXp1==j_+M~QEhKwD{s=ALm}Qt>)3Wad3*EeRpE;M|NCC-bxyvWA{W)ze%@>I zi`OA(6ZZuzt^E8fRd20eo!Gp}e_NN`h7@cTzty2_i_O=6o{jZhvAur5%SxYFCY^R) z9`biObhgdwJr^Uk^T7$R#%nF6F7ZnuAAgFhy#J`^m6&Am%~y(x4!0C!N=YX9UFkf3 z{6TV8-JFiLl^s&aEK%o9M7?uAg5IUX;s^vtT~kdOe-xe4|~dpvjV_N&R>A3a^@9Gr44`^ju5Q zt}0;W`fJ`ypPMFL5(`U>ILKbCtovi(f7buC2@~ff=lwbrYW3>H;`ZJh2Y2dycH0OU zKVet7r*aM4sMve%Y`KltoOR9zo9<;UpX*lq{LE4zqb4SKl?uBz*VI zYS*Db$)soX?`WfPCqH_`?TIGmo9p)m7N>1`Fa#r<*BFKA?w3e z*56K(dhvGIInK$MkG{OTln{L0k2NIon5HRnY0dI;-yf8Y86_+TQq= zJ%`^%Rcz97F4+BA^4Z&af%9hHin<(Ot@$RFKe>8xr^eg#Qn!{f(r=dv%ro8n_>NEH zUHj6=kBfSzB~&`<{{^-UI{RM#V$x;$H0PA~j!2zA!}F~+``+(a z`%U9P+D3Qz@|xhrd6kt*q3h)y%XGjit<=BAa=*OkUOnsBZIN!*U5~o7pV%z^Gn;ph zMtG=2_eG7~rnC~z({+=!Z}gw~BuUu!Wbk&ii8W7VB%Wtw%$Aa3`LE&aelp;^#3LJ# zs~T%&c}S`R?pqe6RCjBhPQ9(wn$71Ph9tCf_3U!$NZHt&A*=M5`@GiP&KvS9mwOb> zFj=;oo+BDo;@!!3(2GfMgYGu9*!Y0;mz}TwD;Iom|KrZ}LDAP)Bz^4e2ko3b@84Pf z%%hLkS6sR7-0C!OnT%2Uy-)M_FBs<6n~O$8Kk=BO)h8e(_|lc*+Rp__tK)ROBy?(Q zoUQk0*WDV6m5L{vuk%$OD4SuR@hE{uc&g1emjK7v#~b~7`_DRU5ph*0@xJP$zOYaC zoQ1d5yTF|O`IBGsrLSJHS?)UL-o0wGHr?A?1T$D>O%@qL+|Q0$TKeEZd}v&37!^B4H>*6HFNvFFDY?L6ZU{dVE! zz;H8{>$b-{yHi#y$~>rO#yyQm=A3G-NH$Zgx5ko8L#O9WVL3nYna$6vy;k&S!Sh9r zCLWhjzg?*O@L|O6y6$_kkFDDS$(!fudZAIeIeh2m#^o8$&+S|G+@ScGkJa}(#i`OM zam$;ecSkjU6cpodSC6d zto5}>Q_t&Lk)f9*sP{Ad$Yzgc=2Bs^GFEKb$GIzQ!uHP|+T1!Wi!P>@m_%RUc;r)Z zrNl%f+b(HVirDUV3DdR(a@~G*`G@qm4HunSQ))K{-~2W$?a^t|*&H!1--{O3JwAQ< z#lHW4r-!_I%jw$u{N29Rd3!&n-P%}Z`t|({H`{3Q%dehKyEOOyKlzf!^?PTT?fJ9F zV*kzD^Ga!U{vi$4cjx@FS{Ilb7QTn`p;+qS^v*xkD?ZH#k9yGg*;D3<;yE@a`?f1# z+Bf!I+0^c));gzJ`%T8N-Kl${&-gr!$dn1#6Ms`k_@2}st(Ke%KX!B7nPw>eDE^B~ zq|dY0Q-7;BGi!EJ8L?c42tt&b<~s!f;Ay?@adzU<;(Cv=eP zO=w-^@5Qqo+wtg3n_GO&(xmd!6UQdi7E7@Mi?nTabtq}Z-;?B5zJ`X!Jlz}9@lE-k6q7gqEdYr0mP z3~l?=enHDyWo%`#b&=b#BWc0mV@yTuBomF}ZcQ7)>zTX+wZnp5~j}&eT zftQQT?L}X0wao3Y@=~f>P#Hau;i!n{&W*biW||Al6`K1z=!9oW_D}KhwI5#Sde4*W zDU|^TB23P>JeaXS zL(oj9U+BxxgFoLto8{l1yw)-F(3avvg?*>f1$gxXrDui9?)Tx^%H-gd=PFhva9Oyw za)QX}K&=W%*G>(GTfZ|NNVVJ!sWVaOQq^U9P`X>JW62+*=Sw|oy2Nyi&OH}s>{DA9 zQ*h$)wX5qdy{-RWANYOQ*C>q@*E7yu?!UP0>iQE}%wlibk8FGU{-)>mzb~S8J&vw- zKN>aDDk^Hl-iQU)Y8)RqZ+PbLyrXUQYlGJeD<|JSW;=cRp2Cv$qpzBlFy_qgxapzD z`l%?SZ(@Fb|ItG{EVXZ}*PWX1h+$uwz`b0t^gfPDlP0V=v_an_(e-?D&vQ9pWuFNV zGG6DSpBFvev-$IhH;2{ax#BY(Y1u!~h<_leVrh7R{bFpFSJ}RAHEkE4IC-W`WlPFP zdh6P=M6rjhhcKEIym{_&&={_YAAt^E)ga zzvVu^ca7YJv`aiO-{!c+pBB&fb!MjX`TWH}D=&W5Uz@k97d-3NxA{M8?Lu|$ecj5& zZ8>)>-+YWznQfjgxA*VceEx}wJadlrbi{Wawt3uRTB%ohan*~pPL)UQJWyg<^?T06 zib*fdO_va;p37=Gtwmbd{L-A98IMhij>hbDyyKH|Lro{^aqyf_DWSzI`}35hME?)n zt9Ebcj`p|ZyWKRoZrC+t-{j)|LH1_BVnhK=HcFk7wVGTR6OV^8Q4-erfsT z$L2ND^ZxJFzkDrp{gD}RW#ZGlx4gTlzGCazxoPsh!wPTkx4Uc#IrZgCbNR*d|KFWo zd2M%dRCi@o_Q~$Wajl`E5l_2(j!aeJ(7S9k;ZXCs)%*gtg~CM_2()bEdz@nJ^?YX9 zZf{P7jyq8%jwwB7*FTwdc&l!)ur1@H8>)Bm1-ssA?_pbRDf0gR{JozWwy)7S|9A8C zTTicO@-&yEWiT@*btox_z3C8~n#8&{!Pz(IO;_CQUfpFgE8h3cnk}*Tn8?wlKnK@= zRUwWnZB_<*&(GO+`>$<&`~UmTdDrE-r;3yn z6!Be~bL0Nw4|a3=_lDo!`1PCD;>14*e`cJ{R0+TLPCxDL>y%6Xw+HG!np<*(>p^@7 z$CfLPBENk)xrk{AZ?>t`@{-%$h6mPf4qYR&rr2Un=azH*DQCR{6*T%6?=j2#9N*1p z={AaG9_Y{%$Z>n4FdsfAk%2}PUPq=t+^SRu+ZtUCjs|(uSbGAAy4A2nadRxck zAkPKjyf9z(KCbBR;bXVwlwONmaW#u;H&239O0j_1S)MzOW6I38o0p2J_T~33*`O$} zd~f4DrM$&GzdUBFp1rBM=0)a*j=Fn~EG9`vPfYC-cw=R8?M|M-quBe81m(X)NYtDS zc=Kc4hscQ<{hoghFP*!OWhv{{a+%qao^R3$@bdg^@_j4se8zO$h#AZ6C4*c4-}+O0 zH+K8ONB(<-%m2LCEfjy*dE3)~-7{`%@?7xp{oE?GZ+2fe3J?ZgwtlXUa%?6Y<$ZHfWrL;S^AH!&$hcj- z9sN6y|LRL>8v^Nz=hdbBhwk-%6LaEr%G~v< zuC~Z`o$&ABPNtjB&)jF$uR4Cb;6dR2^<~Ti(n5evj<`U!C6-v;CsvYFR58 zt8!HxH|}ye?(}@(u4hwUMSZ`J6n|^JL${XIJXc1SbJt=&J-xB&`t8VCru(YZ`@f&q zdm`B-PPtsjdf&ddV>~D2cxAk0dX8@VQG8im_L2DKo8|KlRJ;qG=Wq1xbmjCj`jS%G zmn!m`4!O-gcf{{vgy~*U&yzpD&3SybLNfb7^po?|kDn&}dugh9xa*D8%^k0IR^&{& z8*OCvZN5oS=9cdcg$iZsUoX9;cIN5dR70CEg%_otr?<|2G)pz!xa6q$O^sW3KHXWm z=9y+!nANdMGB+M=<%=v@A~wHl!S8A7?>>AD&TFks3-9_fzHkO}{Xr6>=X<%oK_4!pG*8lef3BdI<@=5` z-9A~ZH6i{_+!=N5@~#P#>iJRde1m*OmE4q%zYejzeOA`lyHPy&!K``fk7uoLj`6=W zJ4sAZ+V))r`-`VPmK?Z$aYJ$P`sx!A;tkRIF0X>V>C`s8UAZxL$DjW-Lf32mex4yE zKUeU~Z-%t1Qs1JBM6xRTs!Wd`+WYNLW&N+nIUoL9o)DhizkA}}a;qKxr*D5CJ^!b{ zrZVBu{+%D2zZrk7P_!)X-)uM`J0Nw>UfKdYRGjr?b9W?9z_fal)w5r~XCQ^Xk%bhM#ROAD_7SatWCxID@(oaopQ7_U9qXZ#p2WCx2N__ zy~Ar2_cV(s|Hi8YF|z{2zo+gLo_qV`%A0muERuGcT;D6=za~&e@$VO&+IiFK?|T?W zcJEbOaX|V(51srp&b;`ePmhM0F!}iFO+rC?Gi$w?SyO6!Z%p?EjWvShXmX|#*hfi;?b8_kKDSW5Hsf%FZ}KcC)VAj1(+_`3JUx%U48 z-~a#NDv4Tq^WmP%cX7Wt7Q9~_Zg^ou;=crWzB}2Bc0bbVTZ_;AS2p?etIWc_GN=4r zfvMil`uVQ9s~1d6f3;})C8qw1&-=tn_LxkpdVG3;!sHP5OILYfi|bxn{H`e!i5D?i zCY#&TcsgveYmU#Sm`4H6_e_{mGW)_y`x~+X-{b6c6_(^x%}i(e$a?4VcbjmJyah4R z&#Dwxe*gDMQ|iW?kB=YRxnh}f)IgKzh8IiZoP!&+cCB4>s&alLOCVde40lM;;n$q3 z-$f9cNz?+b{lm^@6Km0s9jkK3?+jYmT<_j^4G(k8f-$Eom>dgGTC>>dfQY zmVoNPR;PurOXUB)3ELvcW8W>2U;J!lx>cI){m_*QZf0@I2+wd=FIw)hmL->IV)wDx zTx&0P$W*S)_z`t%bFsGUA`|B6C9T2ta<184{qk05$5)HqyOHxvriDCve>-66y;n>FxKl8?T@5@mwvp-gNKQA2YA~e*3=cr-zlpl@rsh$mJb>8E9U@EpD){ zN9vNr>`RU|hE`p#|2^f7__o=5>BrZ6o4b9kM#=iyeC0VKDR;qQ%_0v`|3ac1pU19_Lhxr|HXTO4H>#c5qi7d zoPON!hb!ugub?+*$0SFRqSteFcAfVr8Kt+5?tT3EoV49(^TxZ*?@mqsD{kI+SJilK z^dhEH(M1!SD)xV`ITzKrjNzY_?bwaUlY=8RrYS!=FFot&Y(rQ~@783f)c|vj$CSj!*Pp(- z`Q7vluTLoVA7NydxiER*yC*wuUR#sidZld-=i#G&ze8BTa3&0tV`M0AuM<)J@Wm#uI+*vKUTSv?f7b@EnV`> z<;sh?3?6X{{fnRX3d(Ahlt#=yG%ChRfjEz4ZT(_AkT~@m_<5KrmQPt>)?nZAX z^OUxKxm}x9{gt75_WT;1xzD-&>|ELN_~k}BPno_mUz1!tZr-?>JZH&#C)aSPC;Qr5 zvY2OGnjdN<;PT$v=82Hc>xT*3(+m5>xKmD5NbjGT!s=DDD)UM2dI!yYyj&XVpPx*g z`a{yG#B-ZJm$2OZ$u0JtjmO`W#`Tx{W}Wf8^7PJE-#P9-HniBYKH%iWgokkvo3oy- zxnH|B%)RJEe_YXnjs2~=?-(w(T4ejl?J0xF$<*h;Y+tAFYNeDcTkrQu=*rK3 zHkaDu)g!)j@Hu()Dm;9yBfqC%2A|zmk2`_3va3!hY90+0m~-Ig70LKhv-e6p(=FO^ zxADC;_Z8Lkg=Sp;uR7YxN>pET{BE!HPOb@`iYsFJ~66k2=DAyx)F(<>T2;)7@u&VOcRbS^3Ai=XQ)feG3CmFa5YC zp1*y2p4ImbxyV>WR{3bl#N@|+*;8bjh~!e%+o2o_c?LH10i3 zh>YB}YvbMpmx^o88phALVbcEd@X6YLuF6q;dt;c-?zsL#eD-`H-S?5-qiQBinq>LH zlzG0O)wfwTn=ZI8+!Iva+qeIm_swm0cGQ)beeY_Td4Jv;-hg{6C)vC&h+EBj;?lE? zqWN!EN2hH(`~K4-JIyfpDZejN8}B^sdzm{f?!ZG^W7XA_C8FH+>)-v_G{1HIKE)%? zKmV59k%q@^_zP_bV~F0pv1z~*~xK>94>kE89lvo z=f*UzB<4sFRh{Z+HRKB=vW=p4@Pg_5|yc9Wq)sed=m+i64`!(k* z_x^mYi1)?)mH&E{Z+tZAOl0KE2gQdT)IM5sT}ddpWKTL{c;$o%x1)qlPY^tjcU8~) zO7)T{_pjHT{c`WlzJNuB!ECQ5-(I5LfA17y-O9gl-`IBjlT9vNKhH2g;J$UzzrBwh zYib+i?CD~)3~Tw=pJnK=znjnI$An1Z7k?$D-`)Po@V_4)xW^*G_4k>GpgE)gC086< zbyQ#FaHX1Ho^08T#E3l=AItQGi?+TfD=WxbaY->U|Jt4=(|7KV3ns0pOkaLRNneYh z$|mwqDCdEvH;+`lJ8(bQ;7Mxt^_La#{>7Isaju)f|03c`yZJ2LP4}+~>3_6&rB$}Q zZsy##0rwxLU$M+z_V$qM`R~)U8Q*M3HD^uHiTSv6@xCc??K#(y^0{7|T>kdySN=P_ zr~m(3zv0_F>kBWplxt<*WAEhqu~dCtPxzkC&JjDF*YJD~H4MmKJa4-}!23n}`Ak0P zeOl9NbNSDwH9^b)?qP2aFaQ5-bH#_o--WNwn!h#Zue;fM{n4-dT3-K}mzGxQ5+{|D z7tj1ud93FHpV7l(ZxR=18(ryH>&x>*wNr4@3vsn6OZ3Xv58azH)phQXz3Uuq9^b5E zax?Yz!buPHrZy#Q&)z+0|K;=FEDHa84m;r`J@wX_i_KHc$lcyBfA*hAyWhvgr7b+% z?p>$NV7d8*^@$}_DaE(1iKgsXcGV|*Lx!G@;d|bL;*UyCSKKWU3z>BCoYIt9KW^5t z*f;wU84s$(1TDR^X!g8W^X7=3nWU}1zP!ZQ_2MDp_kVvYua4lE_WR%N2`6;p-!6RY zC2qT9gJb35%sO#hyP8gum%6J2j5}Zv>$LE`EGw*!I%Cz*^Ns;vdZ>ij_pz0by&)`EIdCupqYO!)X zKRsFJ+{P0Om(HHL{WL&lcI~VxqYGGRsAxFiz>r16BgJt z=>K`V{KKL2{Y=v4d9y?>mEX#Z`FM7H;`?U)<97ZQarev>EA};OX-!&x@1?*Op*z)l zRgEvY)mHH}nHCAko7Q*vyBfojm^-RGt*N?2SRZu>!uxw$X(ymiNR3(0JmoH^0?hWq(bzD@ra zvQPiL%)-7>)~7`pd(?Ny>aL6U)5E*TRcva|^*#5~doDdRo#-Pc8~JkWg8wg-dE;_J zTaR007Dwy*P0h|dwL0nhmiwPS$%Z(tzU}HK6@S%jW6_;DKC$e>PO`^acmDY;wy>}^WVRIeR#(ByiVWAgBFS5n_bOo zPPTDgvFTGgTURz+N1pSGt^A!oXIiVnCN6e6cH!YUa}I;)8E-Z{T)u+gfvR6g`PQbR zLMuMzTV>gvpLHp15mzf`)YrdUEPFGTGCO(o?(p-nJ*Uola(jy?clY$|(ml@?AFtuq zAuf^QaNm7f$K{7wOk1B!XLQ^fRd~C1$0P6h?q2@8`jKyK^WMpxoOSSB`Hi4GpKRq1 z9&DFy`^g+@lI44)c)>LPgdK)W5BKGAJ!d`R?`a%Zs1+SnAD4LfhSa7tZOPNFeSFxz zPwaiw@#hAnSLYPIxB36<3Cq5}`~UO&f9(IC?QiwFZoMD(#BLp$T>2)u<*TPX^ZBap z9g~!VI_3*TDNrbR=SrFXN1EaTo{03_-Y56;?o6iYe*H;5P1iao zw7*^Uxx}<_umAn^TVffvJo^3e!~IK7k2?A<-f^Zh`-oQYgrlpr-R0luc47HvzXy9n zccidSYR+#~@SRfXVsh`;Z__F66HnW{t;}yh&b<}pzm`1wm~+d@sBdK&cdu1=e+#4?)mm1L z$k4Bjhu`b!JUR2A*23$G?J`ZO-qUi*eOO{jR&%}Z`g61;ai078zJ$z69vailtlyqo z;(6v-+J~tb-6{;t4G~-na?y|SUQf9?Jz|^sB4-BM&AVqF&1Gfys=WI3wBMQx%Rd$u zCU`IDWB&R{!PY$bv2E4oAQ|7R)xO!O*=9eR-bX+7wmp?6^f=kjs`c-F@kjY_9~svD zdG-9`fwR{ae&r~7mTEthr`FlI;?~Rc$3Dl`%6*%0xqs(hS(7Z;lhfX>&pkG0pQ%J% z&#K9Gvz?Y+a?1NKXF-_JV`Dp^3!j%aa%EZk_jP}4uW!XQJ*KGJZRr^Wrt1^WN`G8G ze;330pO^O^b@;y_=DgbMs;>H(XT44JI?tu`8)=29NwnxrS+h-JmD{%bnbBpcT<&{U zW;~k3^ij@Yp2E8(zSU(<7M)KKb<>ygkG+0**^hIXyB8h&79FT@{^F%)>veJpLz~=w zy~tykBP|i0;2W@xRV#Akq>Yj~ci)K>C!ecn{LS+FQ^r-JdFs6%wjDZJRG1?7Tr&3c z(#^5Qz4;Hd-{6k;H%ET4$cZKIojLDqowodYi=200nI>1kt9RRLZF;^#*b8&CbTU5FJ+CM=v`Lry#m;WHDfji7&t*wTT#FYA!^Wjr>)a77uvPMZejJNj zsO9)6bNO7gLgBruJkD)4_Xy=#%g|fkQ}pcTjkg+e%r|MC`*Gx=&zfsu$9FJ&V)`QO zQd05mSt*lC;c@F-S&H|%q^ zUvqrHag9HB`dk@`4*CAAZDPHAU8nMhV)Dtx-bK5TZ(J@}bHpLdYFnY~x_{T}4{Tbk zfB4Fe8CGw~!=BB$|Np~Mi`TOMh1+#3g^Jl$&A5EsY40sZTlwR2jx|fXi|#x4%i>k# zqgm-|c$srI=@m)tU-d`eTd|1tR2c`+$gO?n>;IL^sd)4^Vr$mvHRq$lA`}1n zg>~V#ifY-%mrtZ6+wsP0grw(s8mL7N9 zxvBYcP|S-x?3|~szFYUM$Zc22t`n7l-Baa$%|2rsdG+AO>*AIC-{qL!3(udC8p3O? zID1O+CBX|#9lM`xRd{wGF52OPW$(U^m2Z;6dx|f-YawcYF`qIxF$qoyv&)XD$9lHI9pY} zu>K7jkMQNnWmP`^v`%QxO5wYkw(It}-CrmWlzV-zUP9&CAhV8=!)u>qyxy33r`Ph{ zQ(i6^&c9bU^A)E3o_1FHV{wAchPeLdhkJK*9F2`X;QM%|=-Cft+t=sa@?7l{e|&}V zZ1V@V{{Kk-^Qz0+z;csse{xjHX}-{v3+f9v{Ow=M*u0;*{_viMjAvv|7~WCnkaw@o zRettO_i#Y6=&!8c70+}#w|-1&bEqslQ*6WWWI5M%vjaQ$nX3L;emEfY?~{IfL-F~) z{5}2W_b560ukQ~1ez#e_{zL!AQ~b4T#n0b$PCTh;GFw;km&bo5Z{`zgttGB7=Zz@~OHf4Ez-m?A6weP0? zs*6p0#M_@M^qRx(w~x{BKQ)rVQ=T)e+pIJFqoBonpWE{u3C}(nbvVxIL+Mh^@})}+ zS>qY-@FSK zKeFxrRGaj@T|Du}k~OO~&J#4t36e;W=h5doVa2RHPi}p45=T*ONu71BOd3W~X ztgTj{fw61XA!F06PP?ZwU4TqGRGlfgEz6(qcv-ITyZ(ls51RRpU0Uj09Vh?|n@2j$#UbTgK{%QZStgmcW-O;zYf+lj>i{Gn0zs4a|?Z0%< z?~N>P4()xT7k_%j^|i0lug1Qy{C024rH#AH=UhGHF!R!(2$|v=+yNmMG(O7Bn|S{F z?!UJJ_BkIqw0HiI#XBxv@7MjBu={hx<5N#}yqg_=a8vfW15aL@P-gplOThfFqq|(= z=KFukPR^QdZP5Afn8>f!!a|9c!|%3O)_zY|u#f9&4Oiu{71C#CT|bkTyH~r>nAP>! zRHX^eg&%*b=6n_Z@006}M_u<1Kew-EzO}{Zywag>ru=u>_3J<6R(xf*?>)Hp`*D`< zCwI*5bNc1ARQKnvU9v|{a&`0Gi4y6$=8@B~_u)E~eJZP!&iLy74g32@rRe{6AH9mb z0rRR;0_v(J9!Z`)>3aIzFvdOQ7fP?(%lP%zJ=J^P*~x9X)ckqMwAL|9~O4~1gFn7MN`2Mi4H7nA2liwcu*i&_W`t_-nRi8iQ+!EClxmL{k#`AT8 z-15iIH@|0OKW7!LdG7Gm*Xex+;B7j@grMPN{<@;gCiZhYd$v}d^p3kEoWJT=zka^&}W)+yhwbIEUTKan9Eety;W1gZ8Pa<0lN&Pwm(ax`Jzx3t&7 zz2wDOLGxMrb!T4Qw_iobJs@e8xqd=mH_J2MYdm?)zs{vq&-uaj?WwKY?nfWZ)L2`} zQrHYQSDnI5BccEQrQZ49J^SJuiTS0ehx?A?@}H4Bv@(4^hrZqKkTa5} z47b+r6{eme1hP)>XS#GOBb znrFu9kCx32dvq&1{G8O>CC(QQ9t-7vC~yD6-Qx3G`9qHW7JQ2*2TRP)T9n%NVwwq? znT21({54lDeCnBAVX~_uA+qo8qQWOu)y)A`n^f<)%>8~R&QL$sVK2Yq#Lh`g71>Tv zJ3b0u|5N7Gu=n%Zm!B5(o?HCMCg#(wweK{Zrsq5lJ)^5DCCbW`7i4w3=-xA_r%J1> zPqm$@O4uglTK86DeR-;9=hWGgZfNXfw>f?+top<&E?thFyz26NT0Kf2VF>H!M&iL<#<@;xt8ZKSGKL1(8Y`rt8 z>kHy$9lfQj?A3Bsoi`=;_@3oW)8ADtEq#}8t*yW0viAZnL%xz{LZ>**O*H&hsNei3 zcK&T%^7+l>23ozoM`z|2T+5iteBn^-++`gVt9Bl*6PMOcwy5&GDz`WJqM6+NWXs3z z_|m3yO!99!xNXOW-ETGT6hGd&rxOTF{yY-9qUvn~DK6^*!zE^HvuF5$q-o;jV%_Z$au;Xgk`6oPe<`-+cf0X?v zUHk5rnpz=;*ne^oD;}+mujTWz`{`u!{Bcee<}|2^{iZ(}IoUclzD9 z^zq&8nQRA3J-i&^&Ty7LJzsLRW~#?dmW2{GCFgIh{q^Fv-DUNfdzP2N53DaV-e>1} zP2vO>f9toFuPt{sb-AR6zkh!Ark*Qz{=Y0y4Lgs(#V_s#Y^c>__;A%v-8t^6lJt4Y z=^IPc_nz0>AHMh1g40s7-kR;2yGo38O=Gbkx9&Nw;>BP3{MJo58nlrm@u-P3^RW*X z9M4wS(SJF2gQ5&{Udw5}Hljaw@tN-89l!U5 z8Z(6)TI0-bE8;F!`K0W)lY^Urzxn3p7nTQ_-%_}?={axIvseZAuuFD;- zUizD7D93tY>zy3-*=oJ_6tq+omTaDGpIfZE{bE+%<8AX}+E(u?RlH`mK}NRt{lD-7 zHvVbvy9s4^Ux-r+9I-SzXsub>hWp!os>51WC3xj6r2z{%b z`^h!)@B)pAX5MNoA8PvYD!W=){#~A0|G9MCk${Z-6@{|@zxba&5}6*`#4Ei+DB+vt z+0Re28ol@bVO_K5H{YHw{rV1j_cwN?aZg^mZO6RLVcanO8aFkYrcBf z?iYXWzFB?Y^?~g>v#09JJ#|mvs`;r<8QBu8nI>IdBQ$3J*=4`+%(l_W!$c)2p~YKR5oXck+)_<+}P$cMmFm zl2}{9+I|1znX}i|$XNYc`>}Yrk}GH7N%jA&Pwz8tI&i?~>-T&A9a1XWB6+?nGJS1! z-mVi~{ZEBg|2`$B-+oU{DM>n3dsZa>vCwrJ#=WLZQ{5ZC_9$E||7h8(tXcd@ba9l# zj>wdgl6NQj9Is*IepzJtVZ}rJYXQ^Fg?Aob*HIbHd#I(xm{bB%d*>GO@dzKOPQ^-NvC^ZnbUSL@H^^TuUzFG#nuFTT6{ zm|MYJ{(b$c_ty%Vt&det_&3q1Qamwj@&3NY^9p(HeVHfz@W9{aNgM7Ro%2uTaP7_m zKeNJI=lL#}wbm)Fl4I&AD~31L4+`a8^@ru}{QquWW-ed0;*)QZW(`m6BW>@sCCr@2owP-f5orpSgSW%fd6CE4BY|*rf68jt@`VnHspF;9+#x|FY}N z;xR=xw<@nXCLy`TuTEU`Y6sI6@q6EbST{|d^KHwTB|;bW+`YT<{nL!@ z&$>4CS}dN#_*ZibKBzrN+sLYiyQ zW`IqoxlE7$>}?Wl{QUg)@88eM%M>nxZc_{KBXKU?DUD?@*AzN*r@T(63S z*FEBr}&qewEAC<&ENYH%-)4OnX_U*0cJT-;vChocD4GAU(M-@A3bCrZi)5wOceGjosiwxc+r&WMXB_xwEI%G+g9(Z6-!&_{`{N#?XZLFC*ypD;{nR&V`Ht*-E>kIean{Yxp|I6(+8y+0AJkXsdU>9uh zdZNptRpt}sttvV6TBYFlZk5f^Zo78B+8(SI5o3CITetMVxb2x?`|jsWNxH)O?e*FT zGTrIVe!B~wIiD27`B#_U?&aeLt*U3c{2A<@Z&|BwL1Jc~$QG6-)%MTUH1(N3dAj$Y z*37kG7eh2ouTM9Q*t&gn@!Spb3&Zc~W!=eNSNOT;eZ)jx{#)l_9+h#NFg2X#%3{X$ z?CX_h8&VfX?zR7-H|hV$@@~7w>FHa=vX|&&hpd?~$un_{ue$B76+gM8v?DS;y6tCZ zdv?}6{y)R98xtz0J(bI5hISFS{+`o7q=oBu3aDS9>tkIKer;*OxrZyA^VvTf`=?XZ<`@3;!m7j5-d|h6mz>G<#zRbX(&3~H ztgBWpjr&!vb|6^x=)2RQ&vOngd>e7JCFkbTI+b^-<>gx&Y!&-eI&z;_Nl&&mRx= zkL&qY)wWN|;7<6_JO9{zWzTGkal0gNY(0y^bsO0Oe`c>%GkG4TTB-hQo_fo?f4li- zxZkOG!W*dZ|K+LgmqXvbmcLV|@91Ljdd*)(8{zz#$Nn2qPd_;Jd_H)pU*+xDYn2MQ-ueAJRQvw@R}Pux&zddQ zn=e$}{cJOTbEt}4`qrg-{r6>mTCuqvJr&mS;*>!DJj3s`4-3p^o&OyWw)%#LhtK3Z zzjun-3>loKqxd8!X?v}Ty02-IlV$# zI;{B8=Qy2w<)+%w@1O51^<5*H^W?Y3qLM8umQ`Q<|MS$$thl2e_Eg^3QTKL@`J-dL zF{MJ#`Mh7T0Svz&?aSRUYs;ihzl%0~TxWAHZ8uL@_soTtLz9$Myqp{}ejUE zXzp%Nzo_C3Tr%60J!Q|bv^<((9&uh`X0Gz=4+l2wHrQ0E&DQoj>ES>2j^9%Q?lsifvFZSo9m}v$VHXZAJA+UV?w2f;$BJcj~*&ujAV}5A3jZxDNv0HbSHT_uERC&nT zF6&4a)34s_>yNtfYuN4g|1j#wx%cVCftoPKt+SFZCRRV5nqOlzul|(~k6G4=a7Bl@ z4S~n+{L{(`owwBOHN$JhHEbI=56n1vOW>UFmet0QPyH%O|7XYWvbXK`=2WR%Z$F)9 z4%@mLc0c_i`BgvFU-NsoW;375;zF)-*6)*Y?(BH%Qzagryk=MV%pEWLD{pNsIJGnX z;DtXS-*6WP~k&g;$tgkstd-wm5*Nr1OU5tEhdV8`pU5l@^F5Z3n zP8O4^PT$`B1q(J?u4Bw&eDKM-_!aMy*^}oL>{k68;pX{xe#wCxza7PKv7cC%Oc&MJ zXZjbd zg*P^~!WtX5p6>eIXnJEu#Amdl(@6j=Yi`1R|~I?7ayyeG&w0oB`whZ zx%6nKh0SuCL&qBDGdZ`mx4kI5J9YYF>Hj}i*F^pl_+)eZ!JM5DTT(Rps=sMkyy5hB z@w>3HBzED7Cx@IL)CzuoxMt>AXVyR3OD3mH72E#U^4HX^=6BO9o{836mb&BSeZZ#v zdQPQm>$WE+^>@D%`}5=e{^nHk`)!>Y4VxGC-!as0*uiA-E6d^$C;NlYd<#Z(yRQj_ zr>?J(%-VVA=fizcZBjWctB*7EL{H(pd!T2#;*@d&|K+=P-<91|UD+~e^(=>~Q}N&H zmwoy?&o@6g$f2tKtntg2SHd6E-^yfvH2Z!HXWY+I-5av5Zj&|ZD=p;n_4RwuCjXbA zJf_}s-k&|<4<*y*HUC?v*tu8oq`KkFHw%s?@y)%F6EG+9poh@MNDD*rtv2brKDBkb z_IPeMBR9YNSZO}`;ATYFk`d_(o)U#G6- z%6BH;TkmwVO4Fht{Z-%nSelPBNfe#(03KFgExuk%i8ort_M=^od%sXa5lhXrov{~&N<`=V3#7%o-pYW8{5 zeZ}>BZN`zQbq-a`%WHMJG_-arZEA25ug^cJCW)(=xi{$^e0IVgHaoH9^w-a`-ygej<=fBCEg9_cH3oe)pH4{j zH*Pd5*=;pp`kS9BQ7sdE7c6cp+Pbm&h%3_vhD6v}fSsO{;ZiK7gu7st!+Zw;%t#?-QN2yxt(R$>-KTW#?r@bG36(p?s#M^ zo_w}h`(oa9Inf!I(7R zwUVaS|G53W`#9&ttmm&;Ufh$JRT`Yt&H1os^M>OG z7>>>IZT$LbnfZil->$n4?@bC7y~P```08n%;`YyTw)3^MepNeH)%d~8<<&9uH`|{` zoL(Hh!;gQLob$^A*F|MN?FtJ#wL`t}INMj*>e;jJax)qo%Hr3bRPbcmgxp`Z8cG&2 zEAHI+DfQj7`M%-XesEN%1WfNuQ@wU<+PsP-J&grx?O$x%RUF?fB(?BxxK->iyU58= zyPoDo+cp1L-B@2O*lMdhOSh@gPW>s5rT?*qmtFG%e{^2iH$Sht-diQqz4+@<$NW}y zF~+E!b?K?1xni%ibGE#15c|ew^W{TPL;8g$PejaeB{`wX-8H^gFMy49lA&I6 zy59zNR^MJE*E1OFS?&UzI6J$&hR~tlwzviY8CE#e};*&x6l4$NW~>7$dv0{LkAcg$okeb?G`*HJvs83q=q5JhF-1p2+>} ze)_?l^^hTPuGTVnM6pwP`Yrz%4%c##d}lSc4mSsV-o&Mr%b%?iJ}2B@nWLSQ6O_E| z*tiKb}L`*$3a$?eex8Zp+mUv;VMUGyS)?Nf_{i#2m9&uz4Ldel9sV1<9|bQ`SPa+=b~-7OI#W4)@uuXyT8Bjef@uvJ#TaE4`1K^Pus_5OBvUt-5hb-b0Ydr z@BdmFQ~j~L;K#<}4_Bto>;1_t*R|0`*yH{auYNPfy<&^DbX>XmL$udYKdkyojBWXD z@!b4Uy^~p-&EKWYt5jUS^YK-b{_ojYoDRYZDrCQ&h^z=QT7UlZe`$qp4(f#q)CG08 zo{OG1{rm0anl;Z@{+PXbWcB>#&1JkBc0a!Vp50zN)-qt<&BhwecitB3oHga?Wi%vW}vTxh-LIP0<3wkN0Z-Pzr)Ua1ng`le&ppS@5~CH7I`fy;R4%wwntZ^dpAxL)Y<<#?@ry}-W{K( zMHl{``#s_GG2aY7Q}64a>jlpQv$TA5>r1Yym*4ZO*Z&Boe{@gQ)Eh?A+qTHe-4o;< z->a~#Y2U20v!5qEU$|lR+4!4pruQaD<~v*XZ?{UcxqtZn^1~J9ulYZHzwnNwzN0?p ziSm!1`@(+iPQUhf;?Fx$!HbShe|EdL`rkdC!-qp_K6al!c2fO)H@DsnA?@%@>W7qP z|2k75FE4vgf88#b@_QAsV!F0$`achUPfB>l)Z^=yxajCUhNyrf<;Z!n#V5)<+w{C4 z#{Im9`Ns@X+xxRv#btxN_q{VTWO*U9I_2OXQ}te>1rE|V@qzwmSk_^TX<<&Q}piFtA(s$4lLLhc z*bVG#ZEXf-r>?5s?tcFJftOhvY^mJacd&8TjOniyhDvWJy}iRKLs;q6N3)tao^tt% z&xY>RT$5zo{-v{W`+@3zGEe_pz5DQv=*K-K#Y=C;-uWE+=V$x9_V{}>j}P}TGwROn zxL1Ar?T?G*``V7%zpRicIPp+ILS{>?=7%+1&2hyme)j7!9WtBy=`Ej|n_2SRRZ}B& z{;|7Lf8FR#`SZCIAMRCeEWBIQd3B+8e9_Lkg?}}koXux%e^qhInE%M@`&BZ17F7#P zjZzz%B=6N+x$`IM-qeUbH{Ig_i~FklG+r&JHf|_odp^_fy}gLz4(IazEgz46@yak< z^gZk6>y?N1vFz1q|DHKH;@xFM+wCv=s_OT@U#%naq>^`igN9x)Z6=| z&d0a1_~Kmg=Le76(0I4!p;XPYujxq{?`m``->J;`@~L_vLVYd?Xh>Bi?Pj!K9Tk2e<4jOuMqLqu;a5%J5=zjBll8%4@do zYr{8m+z#W&4K&}D-e;oE?EV>xnqn5QW*?0ch%C&p9 z=eXpq4%?%VdcwmFNYg>O85Xzzx^x3eOpkLtw> zLTBRxUX(k+)@Zy9`kQsrIm2vwe_@7+RK?ZRai*{Ly|hz(pms;(%jp>c-F+`kZuZ;s zJoeCao@JfU;dl1Wda8RbzNzZa@jUkW&z=1bt^a@1H+I@I^T>|L?eF%B&;P% zMfnK=pT%_!%;T1CKlm{5N|tHmJnOl0W*m5Ng2QcF?$H+~%nDEU_4h8cG!1o$UgFyA z{&+&U=ts56He2y;kEe*sHeH`vuk-D0Jx46_43>Ly^L&pt^=8eSeO~_L?DdP*$BCM6 z42o{5XsY~o!SsBGwAC}&JInhoC+DyPs(-k*{pFI&EAzPC{xxOYrq%xTpY_w5ci*jA zK6~DTd-K(geS5r6rg3}00Y=`Kiig}E&i}7bxBK%jdqdLE6Mp6sd{sAkuNB*t6VX0D z{!i|m4~M%AO!E14te$yV{Oc(;*l>K_r2KWO9YijOr{~=Dkp1*Yyw>;pP2=a?7ZvV& zciSY#l~P|lIpTHUnu%U>yG-0S0UpM7Z< zdDXdc-}=^QAwusO5o;q)tgdpe*VVS%MrNY>3;QPckjLZtY3doDz3_>-K0rH zA=~uh%l|*)YTir#Zws4zQO8=p{ENS@@5%GqbZ2k;bFW&ijCnsFgZ=C!91}w?Tn|4g z=>7ch)O7u$E0@bQSr)&3mX_!JrG3ZJqp53t-smw|`P5-=lxzLkuBx?J0W%iNcDNV& zxo3Og65jdhTgnae!&Mt!{M$1nw2NW#r}Fy!ryo|&5}0T8;&1tU=gaZligw!%&R@91 zz#-$k$Lv_lxb{@#goAub;XD z-@iV-XYcpJkCOclOjO^`(ERzkzzZF|+-DafWPGna+x9KeK-~PetIUIzw>pbHcunQ) zv{l`waYBHhI%l`Yf=T<>jYT$UBpws;e!S*p(gL0vuj6G)Zs~^1nzr`*#?z};n7&#O zQk*&E_PY(!n;f(IcNTu%e=_zB-{#A2YA2d^ezoV?zH8_Grx7o&d~Z9Q!*Sbj`Q*%e zE-?d%@YF)vLr1yW@-|wue7a;I_QJzIvUK<5+WT46aeGR;wm!a)w197cnA00k`D`)A z6b9{#?OO{Jc#~6Ed#zqp#?;-rl@b=Nrel@Ex$eh7*UfePYa`COoq;T(c3OBh7O6l; zYGd2T@rc6D ziDjyImSik%?L6Kl=6hdbiC#PbBA8_4#eOczcF~?7>;p?h z4`!%~?kg)zo6c7qY4|A6eWvTNh}#kN$>&q2-;nWo^ZseSQmvVJSi{@Jb2j9co+}n@ zt=(`tEAj8A)E)0{X>a(qZ@0m=ROP72!c2#M9`>9bo*h&pF=s!+Xa0zmy?oU+-|QZ7 zuK31(Vw=g@W9OFch|jHUP&xlXkmFZaQmn=0zYk2FoqX2$x&wrR>wd<1G zyGdLygYWol?#FynTs!lrf>ETe(z)vHgBQ&h@4Y{?uY1YEHP`(_ zzImVVUDgmE^Ixp^^4n*hVvjM+*e?6w^W&GYo4!c=J)>j(bJy2sxwqlLknKwWFSa|t zd0a;GQN@x8+|tH1{Tk0I$ft5%-XH(wjy z{(XMEUf$l9Y@Mrr=1$sO&KWA*D^va6wqf%8o%(TKUmY&|!^y4M91||F!FZX|1sml# zss-2Uzg_%zT5WO5m#ur6s?I%Pl|K6EFU!r6nZjp`i|4JYbgDd*bH3)e^6|6vUv{mK zIb|L3J=eYc@F@AgeT{{Fzc^RM^$ zS!F8f?S8ZL$E)!7$JFgBb>HpI?{0kSZtlFN;&SeG1IuIc19Kua%54AtBq^n`FDhQ1 z?fKirpTA`q_VQZ`{g&G$!p(0dQ`>77+nmr>bx+uCwdtIrw+~1en6Xz0DhJ==xU^zQ zu~WnT!hAcE<8B*%or~W1Xu5gg;lHO1b|uQHxo=;1S4&#>`-%l;c5O?#{HlG!qwwtu zJ}vIPU~VG*Tkh*`i*HNUJO1DE^Mx31^65j(K8CydpT1IG9n&?-+em+j&P1^%DzDtQ zx$e|moPCH(!0U?S{>!e{&R#fcy<&^WtK|_B4j3jVNA@k(HCA}A{%OhO&$|zWiHQjQ zoz*?x@A{@0Nxj*rI~(RTsXUl{J5DNgyY-6aGhLpaF!et7^MEtI^V*B;Wz{9elRhayrzuB_zowzDP1)0G@z_EZ>7(~GrwUAJ5!UpcRGX{1#6!pM7{ zzU#~{$KM5YJJpa6I&D>AB;%#qdnI66n!0SEj z%<+}WjSvsxF< zhRU^Oj0=jVM}F3vQU1J!>zVA5<#|6}T7GGb-(BJ&9)4kQ`X5t!v2y?5%dubTzW%+q z`RU*MefDQhsJFP=n=#;P;guD=Z%UlshL` zc+qw2)X4pCbjk3(?cNT6xW&Buo z^SxyPbKeU6RPMRkEmE`o*qac^efhzih5m2DT<`qy5q{mY(yomXk3O0`UDcuT%q^SeM)CT+hBoE%Ol0Qi z)#W7>alJOr;f0M{ZJ7<9dW~InHQPKnscGju)jLaO|NU~=|GwV6W3wZL_W4Hb={3Ka z($ez(-HUC{4Lt)j7v=KIFmzfpD_!E|F3I`(9^8#6JO0+1^*QJ9&sn1C_P;GHo?JLw z@b!FM*HivG`o8hEbD0YCCPQ zw&)F8PMSqfYDK;L1XG=aJ%}E@g6ziM_nMk^c3#Xi(-#R_UVPL&XZe$N-{TM0-1QTa-FjWo{lSXodtKHkF5FT5 zZ)cWsx5s7UJEzsQalI&wZCJym!R7wTeWQZInO9YNRaYHb)Uf5{YPSzBEQ?Y*4%Hm0 zF8y-5i{<2!=Z2bZzn`gyuRXxF`%c>#f%EIPuZ%o%>Izr>xjL(_l}mDbUTcfJIH$g5 z`g=u5^?SW$<(s*_MQ@RK9kzDHMC81+?bIP9_zx!#@OPvY1&-|uy_G`<2X|3MZq%}J`tiiwj z!`>f1L+$UbJX0x`zBTNyc3dr=-M^XpUG~OJer~odzJGS!4mtaO50~!vwo3csgHKN- zv=yI=Yzp7|$>Yx>ap{7Cto8?|hTCeKO|g~# zxAD^Yr3zcPAK1L-{`aQxfuhCJ{{QN$dU6t7MA?t++OjEZ$=OcX6V>~CD}65)_3nPv zd*gM?mKyb@y4Ghs`!6nPHM#dxt34{bE8t(*t{0#A9P5rvYPZo()yy^7+-H`)^!uix4>u@WyqHs!D|Xw}*8KVYGZ)q!sCxBbao@qS z*JEZr`8(xNrS9&0QNNl;la6Q2g0Hn^MvCa^<$B>0(%!|tY7zE3u*kK0P2rOmFNcWH!sc zr}JRn)u0`*t3z2o@$-C1ytgOu&G!3`H)LGg7g2PC(c;a8${YFn^~ChvEQ(4!!m~J) zVa7eR1 z{#WxVqkQSPa{F~xYVD<4=I=W@^T6H`@eRdFbNn9{Rm{>|a5G3N>c}mVMlaD7K8Dj- zGm2Xs_@1zQHC|NBi~S8+oOVU(BXQlOEKR5PzIObS@}x|Ex_0cJf7{)=53XD> zH}39dyWS%U*ZsKT^XZ%Qea?4m+@RFUdx%hFy*f6@dNvh zzvS5Qeu}o(8*i_D>r>YMa=Smz<#_~i^n2!ql0S7APJFx}9$LtASFf+?QQ?XGUs@L( zchK7PVfx+OS>pmnxlFnliDr~KaLO~*sy)?fd)ZTsG{4Aablr?7B4$4R+gGRkS+KOX$OC&lk&%4HNe%P)$ z?8DXb_L48I6TG z@Bh@8C*Kia*%iKfr-;n#o}DKvzC4Y2_EEOv$HWy^r87(||5qKYT^q5taC)shY--?N zHGC~htlBZ-3Mm%FYmV%)E$OzeBVCyzOqWjURhG;(PW@>1ZcC_n)~f~AR>*QW)HN0F` zb>HsklPTvv&f$3ecmC#Ymb)GuKQ6jU{K@sVmv_3|Dv;s0uif(Xk0isbntR)h=6=}A zy5PCXR)^xBI>*=cCHJp;82$fCh-TBBg3}gu|H925&61v$eE684Nwn3pewA-8_HVAq zyPjqF%|5~IT-DRbA1{U5NHRNf8{CsTej`ObO7@%8g_0i^y?BMs%ILP87KjtB*oD-B6LYD#GeJzo*FdrEOpSe*0~&q&$^Z$9C^}ReSQ-hEKH%S!5vvj4s*KIf>qUTcB=gUpk6B*c{#oRQhi z_ndXB+C_<3Vh`VCOl5X)+FZIi{NbPJ^P0{|o4+wp$TgAr5ikE=<=&5H+s|0Ysm=)h zu{^J~_46kWh5dKcgLn8;cnJ6#>_4YqtDGw{NqCCzirus4zE^cg=i$Bk?%oE|(nY~7 z@8Vc4v~AesyX(}?SJ&n;oY55k9W7rBi{F~ytDEA+v?&QCWnrlueQor z{=@D*dw;prlJ9@Nilu(3y(4e`xL{trdTVo;w1hnCtZQe?VzV3#&PPZ$mAzBme3j*{ z;MoP|*&6sB=bxE-UPDGsJ8|Qm8+9o++2=p!?{9t_b6i;7z9IkL{^bl)H*ZMw7O(%= zy1MXT{k@jo@^@N3b}l=U`)WJqjp`DSx%WRm|NdCh|E7HU{8|w|i#qubRl`hWtEq-2 zyPGr4NgoTzmWX}!Cz;`GpO~v!^v*l)bI%E%PAz}AA?pwK;Fg(Xtn8V z*jfPa5g`}et%1)CcW%p(JvuG7<@74c;%5);e2!D!DE_V@^uW^f!bOH`PD;6x&Q$(e zw`2OgnA**oe_q+Bcb<92pWpY7rt4Lxs~tODCe?N=%e3Z&vi#B6_kUe`%d_j%B;AF3 zyk)<1-85lJcR%dG;iqwa{?>+upCWs9SI_$}=Uv%9feY1le5Kfxt9{Cz1O-WTnbJW$6t)#}2^ z?9UN}g_m{DKVSQBrE}HR;M=d5wIxd28&8=n*vwko{{5Ky{f`wjUu2In-BqhAyUSJc zRC@cPSJL4R7ltZJ$Yw7(xonx;`Q_J@>Y6`Q`lMGbp7{8LYVpTu<}$6%?a~>4u}ep7 zKcH5!yR;*OB)eT(npo_8<36D_&w$GYUR9euTb^Dgi9?qg||~ zXI6*@q{qtKb$z`qlxh3^pD)-8F7B^mU3=|Y<+;r(qt-qWU0)-5@8h}hcULT?M1JwQ z&XSSG{`@cJC3~ljq72t=y#HgJ%i_)9pK#u$VpnQ#!THZ0BxYXe^^=U4_Pt`=?~>!Y zcE8?PyRZMYSwQTG+53Kd=6fD|;_Z6NnSTBjrKfja^7&)Jc06&<`^jG0Umln*V7K&| z4S&?Om*<`-+rPg)W5eB~++}K_SH#!V6n{Uhm}R!p>b|wg(P{er=C+PD$+;erUvAni zBVyL~v{2-%&<`o~g>RkJHo0@&G~4y~T1%P40fh@Q4+Qs19}1rTqne>lbbHszl}UDJdN-1 z(&c(bxaVzNpSB}C`r_Igho!gOcH8EBzPqk^seJ60M2q=H0@gO?{+zesxv0si$19%J zu6TYxrE!m|Q|YdI2d=)G%XH%PshLSjd+x+o%~w%s&sTZ!UGuSCmDFR_Rpl4+wqJa? zWn${vXXw_vHk^3Phi z4BF3?kLU7L$=NJ#`*Qul{l8b1{J6n7ui%g8rQeH;FWy_X!otd~bK^%%9=?Kiu!_CR z9WlhdrRuHP7EkHUP-)&d|EF!|m?O^dT<@yOnfdu!MX&6ZzdiShl?Gu0JS+)s&o3}gBBEp_3=h}g)P2i|ujZFKhfr8~(>>fonx=Fg9orXT4QXXe@P z`^}e9%{ulXQwOz+X0y!oALq-3$wE;#mtb_8*PzBjM^+Z8|}tAOK_QN>6O5WUnkFR>R`Vc`zlP8 zQ~AQDvW@Q+ygxfPy{Cui1(W>e;3^yEu7{-fnB# z(p|eJTc2J0{P&Ld{>>jbq}kv6xo!NQHu|2+we8RIzI-X&S!HG==RMi1{%hbzr{?3I z1NffDYJMpntk`?-SAW zcfMDQ_?}Z`^|JC^%d3(X(GQ)Iir)O4=l@>p!=i_pj(?{#{WH7!#d%k*C7*Uk!R<}8 z()^4+ba}p(f4n0sUGc87TFv1;m!Hs1=i3(YJGmC*2I}elI(%_rC}Zepm*vXyj%WVm zS(4d&R`u97i>=f5)Gq$s6E4_b*C~^ma<^~Cy{%ib`L^tO@yxU&FjZoz<7b(>J?FnJ z49raO+uF$XEAX`Rj8jEluBsW_lyca7*Nt`Oxr9Tx_foGp9h&c*KDEwX#5`e>-jT$; zar}SlR(LozUWhKbzthQm-Skp*k7;rqFE!3D(cfS`^V+d3GesG!S3KFWdo}mI8BVur z<6o`_u@Sx>H@!o8rwz-$UzwJ2Pd?o}?o%bEu=K_XQ+`V|F)setUlnVjw{F^D9CJ2| zTiQJL@W0%-&0=~T)y+$fyzbRjM={yud6)U?k6$# zb8%~0&*~K+1*gAWfABZIO4n^$Uad>PJcl=_`qh&Aer;W!JDu4scy;)xWXC6^&EEqch8u;YIOrzkH8}2QL*3Qz8^|a60dV7V{lI{75XBYZ&HLLwxVY=h$uee(a z#F!p`=3BVu$JdKFTMehCt~j-dsq}yGuGC8LL%Hi4W z`oPN8t<5X;`;_LdTQ08I-|rB!Zi?mG*mv6B-1qqjoR=@2d)jcxzsDk1et(mlwEXth z&rkNTuaR8SKhga+Pp0$rje(a!dY1|w@)pWoSJ8N-Y%9;NR}Uis4=V0-{yQ&u!S1=Y zm3M4g)cEAu>dm|E^o300`6n%6{(M{7o#$dR55JHJO1Jt_>+x2+^VG)^GWK?-ca(46 zzx!GBvCHe`&RHy)m2xX;-@Y|`366_q9GUla@pHwqv5`|UA}8#6d;7lrZKa^RnV0vr z>K%7bsw-mrbdP7p>0`S}j)z-GoGAM8eDR5kN1pshtNGWY|M1`K_Y==v{r#)(l4bru zNB_DuvmVnAdRzH-Ur*n2@Y408H}meZUFx`6^ioxAd2?Bq-~qoCy1A^A^rg>k*AL%c zP@}TsSe0-=Xe-Q%Tv zqGo4Pm2;S?-!2baX>@p>lI54LRnD`bmp`%#U-Rs{7jz~tqdXKopyaas?#7sHzjuB9 z*M8uXUpKG7TFJ*-Y=!RZZoX%F*Vu1``^U}i?f=L8dDA}K!})~Cp+^rxR$nbR$SVFo zS^lTT`DpGBcDl)R#~)1;IAgpjZCCQW&rc;IH(py{o4B{Rk}0Ptz5P7PR=z7oFU2-p zf3!w6>FtNB5-a#|ZIvK@Pjl z%bowW{rTmp=9+sAs~d_}-{H_v|FbQ5>g6x*mbp$3RzASC`>BwGpx%R>xySjBa&Bt= zpA>D^cXC_ovswD9Su+(|e*T=+{r>#&2lFnPWj<^__{At^n)&78~(owVUh*He}7uAt&YAhSLTqo{U6!Z&t-kVkCS&idtKjO$u84#@c7&H&I-$C z%I%L|`ouxy)~Q*Ctqi75ziPRDzp;_~;=GcneTP0S;+SBv=<5=NlSiMMaok;dLa|&- z`fSm=)Rq^&y2N?A-~4uw+x+-?K>wXjFPDDQ-GAF^g%@AF_*p-91=~d1#V?|B<+L~S zKfU1Z*OUK!?zS@9?mFm@;ljI!X+EL9uimv^xW3}y*Ziv0rtPVDn@t;Zf6P7>z2oBy zEuZyb(tes+m#@cFzuH{zU3NQnPqoUc*RK!H|NnFT3?cjMQRCzsFN zbL3qtvC8++?f896r}OrfTVJp-+$4Isu1vGf_Hm4KT<_A0cc%u#Wf!J8`YBWd+Fb6C zS-W`3wu+N;%{~0t_d3=G$ffUpFf(F**;(bHv%)pJe0-0x<9|!VeOV-Kd#YHqK~3kg+WIqd)Yp9L`Pr`1 zJnzE2#W{O+uH601w%fM!%<>7h>&q9sHZ;-e7hf2pzT3G@KIZ=(=fZ!!H+(D4)p#wF zeN-*j@ZaZ46XW)`JHOpoez@3v`j=X{i<5V(;db9~{7OJj(?B9vv$CoPKe5d^`y48DMv9#42ZUf(z zv)R+{>b)y8^I7`(?!xbfPx8l4uAXyYy0(Z!?cw)Ahu(#4Z`S(!S@?s#VieP>y)KUx zzg*jJ>)5W_*PN0}%dhV{p7Zi~u0hwzBY&&nyAIjjSA21usmbES?w&6vpU#OCNw|C` z%Fo@F<;v@wsTF@F`yV>Le#aX1_IbOXlxM=q>F@AzdViVWrH4Ji{s(62*Dkbh=X@$@ z`>-fod#P`tciGFD;0cP8O#=>IwcpG0zVdRVgtTnsk%GE=e}3-x^Xc@*AB+3zM4rFo zO_-^8UcRZ)>elOE0Z;qTo3&L(bN@~tCgt?ys{T(a}0-@(goe^>6<^XAb+E0)hC z=a(lZUYR|6<{TC^w>1;B+ov|nTUT&8dwODGnvHp4Z+LagO}6CJ$*L)#0=ehPnNlrv zCS5&bl*gW95NDUK&Gg4)Z=93+{v9_jyO*E)&GOyQ&?5fG%O!ypLhJI5GO@pKHC?pl zPf^CZ5A#z^JD+Y953BlVZ8>dAg5q;Ev!6<4Zn2-YSvBTA6ZrVYNa@WT2iw_av##)@ z-;DorHLEA*@tygRkE6|{b)LVwVjDka>UxO{!Izy))%WtWihVe8G4CsP?dsAE34MEN zu5Y>cF2m4koBmOTYX2aG6i3Il3-gj6Iwa*+q}|O6-1JFvvfOf=rVS@#|DL<__>1?e zQssC>mD|RL>o;E(6|dbp%kw|4Y{Y{X>hn9Y)~Xp=WR_e%Rsh@b`t}-Z>h8t$byaJ- z&e#6oJ@Db-j7`hdCk3PlyEcbRIb_M2&bq>!;W@|m+yDRR|2V?>{@4+V#E<8?1@hn5 zeml7ST}<1dG2Hbvd#xn$M7J%_Kn6g1## zja{ibyGZEGxz{D~m!~$>8MD=PJ6bIt^BXW zk-~iT0E_3ongP=vKa4ox`OU9ko>iaq(aG8Id*l2Md&jmo6`VI) zJL%yfL;dM7mR+aU-&s>=ym`f0Ii|N_(aX5brXA_MCjZ9RsKre0^0_q?-A|5vPX4p! z*x?h47!FlhUArB-Wp$bDn;4lZr_K9z{!ML5bLj3X_ucC5Dz$5q&WhGaOowjQtuTo_ zQ7s_ue9~LnR4!Wc$e{*#@#2q?bAw+dc@@2wX%XUhdQssws{%u|_U8$opX{1<@%Emr z+Y58x`7{XLp7MEHuyZ8I8J-!BdZGN`=Z~*(0%iEnTC-^(=c>bWeq}TfK-p;l5{>QEAWll*me02}< zJnwG4Ci2;Zik+?ff1axwzvq1Z^YQViJFXt(z0Y=RJ7e(VXLHmow*Qctt<17FN_j)_ zr{HNnB>Ha@{>WZnC9AjV+2vb}^C#u+FSs*%b&SS3(`8@JEoQnRZ1Mg;q}^nz2cBiQ zhrD}_&iQmS_2aTFW-6b|b6C$a5jdinN1 zijVA-=(K$~-s`vjylrU3w&!uOw1J^z_w4&i9PZ71*I9e-^WD7*!eypeUcP+u$oXiY z{yjPMTYjF1m|5mNYwsn&_+78Bik`0xSFxUd-%vF2?cxLRfqTB}-f)j0?)=9s%7^Of zjqR)KKgS-SazMbEo)L)6Vs5e(Kd403hnOULf&c`cih6XJ@` zfBiD~b9s5y`G2xLw|}ejsm(0deB|Kt>8I_VZ~T8U{qhN!`uP(g?zbL)T%p!wzHIBq zhn7#v-c72#&zjX%yHj?j{F>xV!jBIn+TX62{`1wZ%KLGF2RFF!pRAdpImtOp`Nzi7 z|K9Aa)3NhUeRH<#{@Mr0^}9vO@7>nXKkDvNYFXC|D^DQ7C&y6Jb=&g{L1zevtq_G;htU(NGM&aExzdK#ABvmscEe=h_s{>$<#Hb;-A(;jf9}kk-LvnW zRlE6R!Vw1!MNW5eztRVYioPtB1HIQxKDokdqU~ zgnJ(O(fKn!{kO^gIrsY<>*DusHnmLoe)74|_j7YTS?@1Cx1;XsAc(VSE<9~ z!d}O{qw5yK`R%Xg|NIrwGwtE;e~@zeVpk&@001rEpu3N zixRkwKfL`p_e9mbH&8!r;rb)Np! z{c`2i$!G7G>;8K@;+uWq*0P@rj^W&Ge_n3LXLH=!diL~7jScY&_nNFVHQ#rSX&e9S z6-Kc_XBSV1-6z)GbMxXSmB*Do_HMl|`>w@w_qyZ9Ua!-fY;3;2%<4leA>&q-HEvnRV#XEc?HmlRq3vw`KnP zxn!CDdeiLdk7VCh>i>JdFQ+--{^H!Qvh%`AE>4w`{rG2Y!J8TeamEBWlh`l!WTu_| z-EW@tb&qM7ovVxwPtVKT(yQHf`1;4Ee>Yq3sb6{a zAYMhs+#}wFA!no7JCEsH9jOi7r>&nXwcA)|4z-RJiTvvhXDJveY)0sXB{$k{(76QC!L&I^Xt~-r(2ud%y#LDyjI<$U4G|y zJ7d&EBi;!4S!brbm}SNwzUTIy4?A_9FA-7Q^W(twS2kayV%iTi_ne*c#LnbU%DsY3 z(#H-<E$baAea(U!dPcixCSH-0v= z|64WRm#3>$j=ivs*#1K5*%slMnMoFRJUdTl=zUPrV3zX;C=lt-}s9(49 z*-eFM?*-58&YpL!vZ-?4Bra>6?u%ycn9I7iI)B`jzC$+ia@z~T^L9<8bB`}mO6mXI zoxXte(dql&_U(Q)bMM1m|88Sz6QO@+zrfZ4@Rf3J+|lJJ!Fi7N*Dmipm$!bGIda_a z=N`i)yZO)LvPz_@6wEWrxOCd}>@5A>OxwOAkMd4^Y_IGvxcgmwch^~u+-r|Qf`c+5 z*tS*o_{8YVYbyLYX%oL)?*YqAbG@w_1g1KBMY9QNF~0lN|KR?CJvN<66ZOn0RFB`% zU*NJ^iI`|-KN;ze|@2Ly+B;Ihcj9*ANYRO)?d7q<%{91WhLCE4(|=kFYc66tnJzQ@w@D) zZH~KzAKWO}HgW5vrq6{uhGoaE%Znd8Tw5i!H+Q!^!#3WApN(#FIAqKwMLbG;^JK@y z__n0eoLje?cURfJRdL_xf4c9M zO?)fgEoNtN-pA2Wu{DM|6j&EA6M`GW_w-T;-tH1UClp+^EFq^&Ya!8{{GXgS#Q^sSsoP* zI6tSkG;`Lg<4M%2z(=R{%R#-aUDhb`DTKR?R%+|^^&n}*RY9-N?9h0jXoX?n+JJgT}XY?BPK6(EMD+kfjS3 z9nGmoGiJQzZvVw`&j-u%xALC<`S)ACp;&K6z&2Nd^|g9$udciqQMjIOE9aJ%ed)|M zgSFN3 zA9FG6>xrzoTC}FnjA^3tyDA-(ike>L^!~pKxTDQdeO;DryU@DhdFJ(LG8LOVyN?Op ze1B%j{cVyF3C0m(tQ9v}zKEqiUJ&cBQ8xW%T*vgz&u_(N9IljK&!?2KW%isaeaUCM z<^LK4|KoDFrD>$^RLOAY;hy8~+uK$f#hbOO^3Uh{p{J7O!LXU}`##4^zF4VAuXYOY zyh$$ly!TGJV)$L2XGN)9w_}u}0!j^&*7)X|u6n0)VZDJAU%I~y_MR(mu1RzLEkdoW62xRR^2*tb)C!X@b%U&uC9H3Dk$vF+@>O0PPEY59X)?0k8>`<-S=@)ZKou1XaSb5MedWyYb z&Gy4aOZ(2wJ`#3ko4x7dvm4iH1W1-VPkgiO{l6Q@_x~0iZGC>zQu^T{?{xwW{%hay z7~FnX9t6q3A+N42VEFbv*J{y+2Gv~z;rqI3J^_eE7!cC?(Bw_DCU z+UU;P0+uV@>VBn_r%f!DsL%a!rt;R_y$yoKKXlvo?PtiD8_eOm+-CNnuFJOnZa8o; zew#6C#@}~_hKhQ}WY4pn3ub$6%XVyf;ZKq6{;h zTJ-k7JqFY8PnNMSZin!whSzR6vDJPwtGnC2kHL&_^eD^ z+-9|$Y{*58XT3l$EjSMSM7+M9fK?RoFI6_Ve5 zL?0PwcVBwRP`>B)-h!vDIRR@8&8NrL@jh#hwQLKWE4k*aUfLO7NE}~T>%#d0QX9W5 zxmXx=c1H8OH=BZ|{$M{@w65X$zq{HWrqx&3iiy7zoA1Z+@%Ov!^5^zGU~0?~nwXq$ z$?SFx&rTP#B|s5+Y9b|zo;j=Mfc{x zcRKrzw{6b7@V?OGpVY(M|K+}$^Zon%qm_m2`W|zAUO_#lO5I@9L+Onl@9|t)xJADG z`NoJV9w&L8H~l#icaxzaPjFK4`Qq~#vYAY;4b8%MI}-LOTAL*uett9EyOZNnt)%O;GwP)W&5xz`Rd=r3$RXXIeo=EPFXu_# z%0Ei&N2>B)ZsKY)-5tC68t3Du%2ua!-=(>H|HE|taMKoMf$uevv*t!ehAqyU_uoJI z<4S$~obY!wwR1$Tz1up!R{X{jE>V6>7uBL;-Q2t8h$-1Pxy^l97xi_)Yqm{>YVEH* zqFFMpthz7Bbc6H6+8Y=7{o7s~mYimL-a=;AtLnR|<v!B}&<^}XTxpnuI-8qgE$1R^{ zyXUU`5x3;-zkP=*`_C3Gdc9zOEKfnN@~M#7cGhpqyUk-So#EBjw0#>P-Moo!($oyC zf+_d8SDUtEDcD;~oxQGp`HYg+`OMFF&v~EQ(^z>vL4V!Pik`Yjj|;bU$*P{V-SI|R z+^ylW_p@}?t=Wnj*Ilxk%X+R_rq921_di*$YLyAc&T{al%}pv?u)Zf;Fe7*V&#kdl z3vYeX$vW|I&#uFB*NeBTt+*{UA*<#L$KrLCFT{UK=<2Q7U2UPy8~k&p)phM$wbLhL zLn{8-pMRJue@|_BuDc7v-n{pJU*GDTn}3tPv7F&Uk?Qm)m%lCza=YeDvFft5X;lc0 zGw*s@cGNdN;C{8daM$TQAAY>qbK&ASCAQ`atFp7%i3@7CoY|Vrd)oL#DdY1`TD&_h zzE{1yKV0NSMOari&z_B?JHt;iz3y+bc+nAmY?rOsgSOS{rzjo%di`#LtJ@Qv(%rM! zj3e$YJzjtEK@g5CA9pA{>zt?uyb{(tyvxBMU z==3YU`2{UD&2#?!z)JtNbTQ+v%+Hr!XX(Gb`Fr1->&1VJG=Fm&*mV`m71$S@xPhUE z=UjFEH6zhDiHWDZl@m`L(d}HeWtXDgqoH_lcI;Cf;N$NtM28;8zJs!9I>NzL5 za8h;9^z|xS%hdxdTE4vhcf)MY7v0&0=i^wdSLEfhtu}x2v^{81-l-7Dn)o8iBR3Wu zIKB1VL-V@YM>fgx3Qb#H1gVKg#BFa9VE;Q|qEV@%v!AH+$ydFht(E-KW#Gm_Py5}GFz|q?2!3xIKe`*F8o#5E@R^zpZC6NfB)yZx`7PO zhL^{BBP2p3PJJvXbgmaId##%gn*QtkMQAJd%GqFeSsTLn=k?l9^OX^9vNnwV6?c+9 z+@JsVOxfX|d%xe4ejZ=I7`EQXBHk@$|5}dcocHyaKG-sd8yg%_%Kc$y{G9W#ZN6R8 zPYGS&ms=*keep!@d$n}hw#2>7Ket8K?|im?oo7kqnddE^9?jyYN5Ait%)LA-Mmeh6 zK9l*5le?{TJ?D?@mR$yRt6~cxn7;howQb>tNt(a8ZtICJo2%{F`O&A;YIkOoeBueV z4L0Uxy;(Z^j?boiXa10Vw~PPPsj}}o-`xp5f0iR(ul980bEdhHnk*sQ+`qIwZ;+U# zuJ+E*HWWc`Kq!HWo!$v`8Bcs!2g<$ z+_zc^y$YrIo|i}N^)6mJ>#V-=yKcQovqoEPf$KJ(pWU^5Z~6IMQh(mJ8$DBtBK$4a ze!DLluv=-3`nhh0`}Ut}Etm8Qn(y1oxg_|?veR!frckX+h?n&P~+ccoS@ADhWUnfnzk zwXN8>Z})d^D{tPer&l~4hn!z*ZgFO+HOu>+wGSO;l-diF*K}5ITvMgFuAm_H!To>P zeE#)E-EwYjvzZfby87q0&y&xkh9*wutzLNf^~sH|pI-f?ml0c6H#d7p@7xoUl9o(f zJbh7}W@Fruo#lHsBHT-?&QthY^j^>RdTG}-J8EvF6723D%Fw%J&$bvwj@%MO>`ltLgQ` zva^kwdUrbV&;GSJ;(qi2p7&c}Sx`4j%xxB=zTEm*r6G>z0@o6`&nxV27+UN0$Njz?5AWwb z-*fu^Ow|*5J=0DX@4hqt{|?sZ7hiW=$d0a8y}rlMC+NXW!3{?hcG`#}y!*oO^mt=x zaoTlWHnkraE{?2-$%o{t~=Rax*}e9rZ>Cq)};>gFFenzp0f`*ULLqWFdO=1%o;5@+}g z@0cAaaJ>83yKaVazWu&f)}Q}GT1>-pV~yVJyMFkB#3I4Xr~g)MS;Qdr)1-Nxm1Voq z#|^iG^=0?`w%_(p^MAgwS?)}p=PUEJAG%n6U$nc1D?##Y-2wfR3wbO!ivP7YNH#wD z)6Wp2u;aU$L1cyh<9`<(U;TUR*@k_*Vk}QRS+niD!}r)1E}Z{z%~Zd~v57LoxH>t;)Gd z9VHC!yZ-Mn`o8wW+%Iy=5C1)0yZvCzVLRC;AN2R_+Px#_`mbvf)xH&c-uUbS+eyYS zvEwz0z5Bdx`!};xFIAHplDcE}cA$ zyKN%fy$`)Ym+#Kqe&W%*J^F2V!Sh8|#D1>3pD{UN**)ty&z-9NmF`vUw-np`W>?%b zi`QE&v!46zE}JRg?XiEQz4CkLu))&2ov=~dtmR#X!cv9IHgA8ZKi)k5=a;hdbMh4r z9Cti;)b0K3&lja*yDn~ewx9Pw48!#;vktl$yg!?;xO$#Zc}4S=UD0+=rkRK|R{ptL z{QR!z`?DE~t?ir67Bbk`=`Z+qH|m=tf2y{^R`yJmY`4FcI!qbzR-FynD|cZ9_Z5YW z=L$3Trx%({x__wPjopUS1sAROo=s=?dh2UU$g#_9Z&fd=2>7-CT)Sf)+ex|N36J^G z@5H$LeEA{CJx4sCPgzWAOPNAKo#2bl%k3}iXP>&`QTsv3_=MdDf26%tK9FHJrP^h} z)!;uBxiYib^FFIYey@4bveAs?!x}LjONqaUr3pJu^vvC*;81p7{?Y%vnSA<{mu0q{ z)qg)HebGhR_q)W_SKoa2^lmHdoTPt_^4fl>-aip+pSVZ4u4O(zUH8!`(58__RrbVxq6m&@5|S#O1pTT zH(AjDT;qjkm3jxKL2JTWD_2!zvvBSE!K#0t-TuqLw{4$6r+wG`m|T3MaewW;fJv8E zGtNm&m{(|aW>&gzgj3<9*Am*sdY|{sHI_HJ=K6NcbF($&8tw_tZg-`ar``20lQ#EQ zvZ9xr`N;FUBR%yl2JRE-U&;KIN+n<7( z9&ejg^U-8|W&gXVqN_Kn?)drFT{k}#yUS`j*R$8RJH$C3cYIOgal0E5%Kp9L!{HBy z^Z)AW?SG+Ecs~59z}!G~?!)VLyfEia+vr`+5NnKxEGF2@@e->xjaqWSP?$egf^Gx4=OK;NofA{!{ zg3||gJwLX)cwW}Qmuw%Oefm>q^|sLRV(4~%$iam-2U@z;a30BAJKJBza3fob}a+P zq_;H}Ci;8C+ilh^b~*LVdetkny>{)JeAhbV+zEQbrxjk> z{jR5(?-uj_e=ohCR7{Uf{9DJq$apswbG|^%WUI`rlPZG!lF$77*u8n$nZP|$)LX$=Ux*J+iy&j*>if`o1*LXYhEa>e|B#Bd$pahYaA4M6KkJJouBTgx3}c= zx*b6(6YV*lem(a1gyl2qf2V3M%#Lkc8*P?cXyCGamBAXV&YchWH^2BUQD$)aV^b1j z{3Ybov?cH{wpDh2=EomC9rx>dL`=-RKxtQn(&yIljqm3cwjKI;A)zc)@PP5DwsSwb z8Sf;ltF(H)_ow#pM}}tCj)Zt@I&9qj$t9^||8JvzmC~20=h=wf{S;&O?1r^s|F&;G zs<*7(eP>&2^U-C^adR&`3Y(r-b@{@E8NvSTd;XOxPP)fxyeFv8qIaRI#rp^M8s6_H zJo49l>&g=83jKxG98;~{Os~*+9CGL9>D-c@mac6=wJ~phZj#%6iy^G|@XH0$ZMpt@ zQ(LpG(L!YA{bx5$&1b8BDsWfx{c`s^pBXP%XMTIUwKCC%JJINW+Pys%@1F7-X5QYW zE%|urb>{jncUWt_uX*@s!QDF#*yRnH`}co0kSTrgZpV{Vf_~PIe=!`I-u3;{79 zy9sO?$_^IJDc#ev?|#F#hb7l9uh>7+es9bVzO$C%R(r)RDocK-YW}dl&*c174#`Tb zg;VbRnE6k3!>)DNy(y{t&wS&2?&m+9sqFTbNx{B15A!F!`5JiQd1~03lN^0(nW{E# zt~eks*WMc*rzU9r=cQMypQ3H-m+1Dru;>Ze9)M_ae0iEbuQT1|JMWD7>Jsz)SW?_& zOGWnm-g~*bIrLHPvZC{wU%rlHs7vTgm}!6h`-?f>Eu^=XxqEMMF3}3g*>}I}oO}9i z<_fK*%5{hT?$o{?`(@u>kJ{MDa~`~%A$w|?YD($$s*6IBZjbFw$G?=&U1HvtE?(XuwmzWoAv{h)*+T@Riv*l;4=G}RF!oOu#c5M8xcj}`C zpG(nG3+~13WnXXc>~3oAZ-Zqq%a#{BDLgK3nOott;6R0S_&f>yp1ZL(9~KF&$Pden zGkZ7Z`t}|*V~t8}?5>Q#0A5Q@1zCy0@nGuUewvkw=}2c&?rP zb4zfFv&i-O)^#F+TO1VL{d~xAxKAwnhvmbX{g)4JOXTNX%oi3eSv(oi+bZ=AMnud0 zzdyp09zT2A{JH(^OFxUhJZ_gO_Ut_La}uj2aIP(%CbP_e|gM-=k4Et|u#X`-COudK+9% z&+0d@yv9&d%zM11OmdROylOKE*Y5Nv+d+P_5 z*2XOh_pE+9W4=k5A8Yl8Pse3HFJ#PN{KWU{wt^*7_5N2mey>wz{csB|l4`j!-Q$bY zx!uyyvyN6dM=snR<pf#druju`(h?2tCoz0WgEY0m$vyU#pWvR%IA@3+Wi z!7%2#u?62+SARIXd3~JOy|!DQ`Pvs>p6z;H=hrWpF8kB~|7$@H;_9ynCfNQA`Mp%D zT|>~EYva+@dvR-Hen-8V@LqcBrQ4HOeoSLdIsIxf`?dTA?~i4lo<1$Rw5a*$@zeLK zy!PCRxF7S>+L0)tQBmY4@qS zj%qT(*2zSE|F`$~ciqu^|KN`CQ_fqO>LqfS!m%Iy_9`!V{(heC`CP$M#r1;w+>7q<8O|3u zB`113P1a3(rs?^^oDO%|@83(A?c}B)FTKp|dyS=HnZ`!HK&eAg63--Rq?B*O<;gs5 z_`c~=qmR<7%PV}3A75k4JL9`{Yrwp`pZi>IeJJ1UVsmMkj&wGYwv3bZ)ujnfjulG_ zo~kbJF1#eovo2rjt^0wBFO0EY z1?vg@mHL-8g6=SU-6?4nKlN~?Y2#Pt&a%fVUd-z6ntZ$9)NQAOXQNe4_s;tG%kGTo zM1zUj^s1Gn2R7eWH>KFbZSB|FUF+(v@2E66Zo_-O<#WkdZ{~>V`boiYY1y6&m&mpF z6jo<3wS3nOjk{YRd(PW4l5;{F&n+Xp(-F^|oHpmWC)RVF`O&$(mN{NLi`o7C-&YooSnf7^LER_b!BQc_f; zeO>1Ci{4ZArS0>#y1jJi>NA~DH%?DEHNW)QUgxU2b*p(280N3%di^!DVbAk(-ZuU` zt1mfm%)WeW_jbbwX@<08mqp{hiIu+&W!GeS!G7z=w$h0=9==|g`t6UyoX>~by7x8j zO!S|1Y~86Dv!NqN(Nkcrw)ZM=JQ`y0vywYY5j@$n;+$F@nQD{oJ;DPzM zGe1vf+F@tXta|j>^ChwO))+MvIhDTap1<3B?t8&~zkl<+k-hNyx8H?54|8;wTy9yP zRE)RTsCe1#&@nODXY;xFjyPYdF?kX^F=f}SLbFXV9a}q7Ztxe&d@s>>AC}U8V|u&b zo(O@OQ{H^(?hp55+H}u#I(?wB?n>Q?#6$nwo=s4nBL3Xw?j^-@X*$|`7q;!atGegj zy^MxmoX2g}r768X%FooRmme**@2knw(>0IP9(b;odB}aA?U(kEDaCt3jSmF>%V4Yj z9-e(+?pu#NUdv}q%#i-_?SQ&nou=OYq|2Ya*M%mYzb)T)@0IB*2mb51mu>wuk6b&w zex5+&(QbjKkHR_Db6uYuzk8`gpy~V~j>+rfO3q(fwQ%3NeQjUv>@(Wx2n7k+(xU3f}!`H9yhN_|P&Y>HpiGdx(%khkG`(h}dE zO%JVDK1@vFzQ#4-p6rK~FOMJP$~-BSZ~46B$~;T?gEh0K^eeB;UA=QP>+|WwXZ59( zjTO`Fj@=UAzV;>H=~phvwx@L#ichtVKW=|huumpV<XQ(O1oYBR`Aa=?1R~i z@3lG$pRql%J$G!@fiCV_v-fYiCb|3chBG^~6<5vKwj;qNZSh<0h8l0awWgPjeU6?L zJMH{#5sP=K%5y5$$vrQ6l)Cw})`O$RzkldCes00-r)Jfg9%Wo{HQmfE_RRgqbpCn0 zb@e}cOLxV}6wOaAl~bLTs==FDs%u>^^F*3ojb`>t-o1Loy0(S0%;bYsPm3}CxTK|8 zRpU{m-rJd9Lc{mIG5YjSpn275!>`9~|GoB5=-3U_-S@;5UDn@NF})ym){&@#3v`Pg zT))p#a`AWhgm?a4ZPyaY5A2Z6^j!Ge^x&TZQpYa2?w9NC6>@`hXP&KvwU^60j@$fc zy?s@r?CdPlAAg?P_usz%?_2P@{uA8>|4NF_3!lqp*>II%uRyNy*_x$F1|_pMJU?Sy zYW6Ae-?BRoPt{B9RGz=S=lsLf$M0-04!Y&aa9!nRlmROw#+^FsloR4W$ZsAZrv%ZHZ$*=vi-S!p8cn& z(?Zi@Dw{to{kb~Ui#4Oa_~drK?_L4>)*ib2xa^oiRp5-(yA-n){3w)DDR*7}I)5Ic z%}Y_`n6j^@DnHx&eSMv2eRY|5e{F{UtOb1SvaM(K>bcF0HIOMOyysdw=dbd&`Utns zR@2?@J8R$9Y?rzqebG!Up3mF%Ip3S)mNS=j1x22Ie%R)X>5=cdXMQy1`X@bo=3VWr z$LG$!E@-k(*6QZkr=PiOm6GHd3{ zgVy&x^4p_n(f9uJzx^)Fdro-P?ya}yetH-5{n-Y-JAbE6nEU@t;emHs zc0JgUAlct=Z+iaj2ep-1L3@0oA2srZ#dmkl{JiF;!5*{k@kug#7fo{&^y_!8wpD33 zVYan1W1{>%d8XW*8wKB7k`cOkp66Ng7R#(C3wzEpzwaEqer$&B%JzbJJ3p12HSWK= zC|Bmp?8@ptd53;}(9T(Ni?ze>c$mfJ?32=_33)4e9A~dRwJiGf{{!{CAO37-GS8dG zckcaG>ksS1%RVlsE;o>xaH^d3k}#Y1znXM6DY5v9ivs33ceds1;cYK^aJSDby-f61 z$$jYy*A5pK$bGEry`h|wf9$qSsKf8x(^2>GZCr!SPn>7Gts~S`HU<9QD)KZb43QEsYm<2xzLDOA3=@=K1$ zZi82+vh#2K)`0dzzH*1dn!LA{Uf=ii@sDHWbta|nY@~Jn9gY9PI`2b^y3bDeNv}>b z&f$7s!w}ARBc)(oomn5O`TWc7cWu^O6nyV{{%40xC!MEne`~z_ zWZ??CT){Kk8@9JT+Qe&c-|n8M$nlFaPCG2QT{^YGOZs)s#QE1eB4V#?xU8Ig#PIyD z4)gl064LV@xt%?1vQAE5eTQoOkIam-_jkCRn0Qj3`_1Y0Y2o**eDmg{m;F0^k9p7Q z#|O6kochp)_dfeO?mhpX%HDZgaNg;CSMqGu?yNMn#aUtrOU}NLn)9PCKhdFKf6uPX zs|$|D|IY2%!Mp!Y#mrm5+)Lw9)norWO5Akn>YIySdyn(#Oxf4+_=?G`&p%o7XE*QD z~yQ{GHjxyg=obrBBxO}Bh)+q`S#vcvmCm|wa4z96`x zvith)wd=WV>Mzc_pg2$5;{3`?hhK@}tBuE8XXQ)MNGy9y6vl94~kB zHr3r<=6=h?Qgi+k`}=D5E{4l4+$U(!^t15iyM5A23+^Ru@{IWVm*cbaX{i_;L-l2a zfj{^@RGz5wlKry%XZ7Rv-`dw*`#o7KdqwKkTw$M!T+&@%CvDiVdI zkH6+Ys_46d309|xs)EK61#U%yYY%P=?`-+>qTvJ*j_iEc|+cQ=B4hfPtJR* zc-J0by3;+YAnv^4^^2(~_j$eE9kI{Rw|H{;>WhBI6NWQ(-0gZ-X|{qd;l9+CT^(0< z^W9kc&s3uK!t1Q$Y}ux#r?UIQdDe)TznPJ|ulC;Cy6mR!Cm$8F&&qfoyy*B@C#Knl zH~l#HGxi0a;hR;H_t{@@+qhe=c7YN!zmyt>!tzV->+kb^XywdSj-2PmE>pm;@9Wxp z-|ZV1ikyC4W{AlssN4MXz-3GSrZ*q%#9L0|IILNIp!#z-`!~P+$r8JptMdBo1veh6 zkZ%9Z+a%Yged(}SBHxx$11IH6YI*C9oH^3Yc*gFH%Z2IXM%uHtJucU}vyQhX`Su*9 z=1x`B4yp7mx$Sj<%%0}==eZTdtZkQ z&k~xfus54${`#x??i^lP{_yF6LVf1C0=W%M2Vz_ONaT{N- zoWM8t%dGA9R$hC2^88o zRk6%jXD;TI;o&!*J-sI5$i+6PP@gxcakIZI>++h9n`gXa*#B+8@5hb&JEo^xI?un` z$jofHFLY++?w_f!8Sap(Gr{rw-TJ%JT%WJWetT={k5l3MjIM6G>Q~oyu+r+q%zS@~ z^Cu+B&+WcEw`jL{XIRgY&WUEZH{N|YExfR{+VGEU(~lQdzW;SS@S@3W!uhMlU_*QPPevKwF@xl`_$!YaiCH%O~P3^!*8j zjHycXu%4yh_cw>X;!Sg7xV>n^UaM=KGD+nIugaD>u32Lcp}OIpaJ)_Z(f16;ZvVNo zw|^>w*xY)3lbe&d>+bH3GW-8%&UQ0iqeb7Im?->}D4d_wtzWLCoY9qi@0z~PokzJ( z@}rl(cG%x@nEBz}JcoYkyZ@`2OupB(FiO7;Q;wY9u4bG3_qEfdN|Qy$V}HCAelzp? z*>77HR=(rd@#+zG;icPtM?}SA75vrb{G6Mg?E8M_j{AS>Si2YZHB?RGfYsjS^+(MT zp(}xszivEV!#)4^{L5i1#p~kMw}9Nj!{KE!0+}kzn*Oe{d@g%#v!qVFjm+Kn zyKmp={qlJCBW_E3|K#Z7n&(&b|J1+3cKpds{mHWq{Hoilabes3pnDu;E6)9#^f_RfnLqqoZT~hDsKJPY^;8A&&Hqp`=8VM_-TP`_5 zcAjKKUX1*8X8NA+O?}G+8?V0q_wN0p7Gb{`Nrg=xHZ#_o-}F46gE#TzD)j~Gp-+pB zB;9*4wP@Gb?=q)?=efRDu(P^bYpH(gpXC2%c01k`6`C2=t0iq&!NB`lEK^wd^V~2i z)~|{?luY{n6v~wJ&)>dXnD_5pgMDQ^`wrjSq}#l3;-z;vCnLg@Zk+wMjdj9q<$V^9 zUtYL&+Ho4^*C}Qb^n+z$UkJAuzWn=k()9whjF>CEYmC;pU6Nb=_wPh~#`D$O8wyXK z3O@Dr-B#;@N1O-p?;qc>NBS)1+M_plL(b$pzwg|ebufeNGdFY1%h}=UE%bA^YNxl` z`aN!4GILehN$GuZ!JLs*OvmO6sek{rlKHplqVBs~4BLEawO265O)hhjDD60&d}?=B z!rb>lPrS9;KZGv&s#5w$d&2e%Y3u8Cr(Uj2SF5=`zl<}}@1Rfo`EKXRYzOVDt95F>=Zd*LpXb~A-Qm~~!SO*}@A9^_?pqhul$>>cb=r56;F_Jc{~9DM>CG0Z z_R?>n>0{`q(L+@WnhMY~F#&D(!>{T+E{&foGG6OVM3 zSSWr~G%YkWzV`Y}8S9NzhS$qK8$43%R4F=kf9(yItGDm&>b|#4$2Zwqsn6>9?w*y2 z3z>vI2dAIWelnd!*7$R={Fx2kXULrF=D(fy>~g>N>(1f?ey%Bt`)+>II(Ko_*WEWS zD^#`I`F`k;lHJ#h=O0|>c+oCZu$g&!9IUUW76z-Yg}*MkUMG=0ulC}Vi)OE{tv$SQ z`8=zmJ)2&xKKye&(+<0obJa(95)}?w8U`=9B6vl3wP5Q{qd&Lp%V(V1pZspm<)R>8 zG0r`VxeH$;N>9F$XY%@!hxD@S8egriA(Bu0@>*b$8wuN^d&*$$qlz zv&jj6H+6lvT;KoMX6dnAFL?i5V-Hwu&$6#n?ZX=3#?KMgXF27t{g&R(wdZes!`R4^z)z_@*w)`nfSDN%czxta#-Jz=a4d3IlTkqRlvHHTkH<*HR7G2=2yRA11B-(D%Jwj(O5H%_DbyhVTD%HDZ6=UoP+6 z#s5kcFg~{}wtIGHck=bSU#3JCd5X&3*}W*%e3H9+>!0QPcYo&Cot774SQLCeai6}w z>yj7yZm7lSoZcX)6Z^T>L%O|0_uYN7SN+|)@`DS{D}PLMHSnMLvQjJH&26#pVhbPU z)vRZ3?@(`gx1);thINu|-%qgvc?`b|+Em|fx98SnPFizz$Hr5u-d39U@;+1UDc|aI z-uql=OrH9Su586ZGx>iu1@BU|kKf!8cAlyJpTUEp^;~Cu?Emw<Y;d&4YVi>a1F4 zb%Igm`}XNcjn}^wtA3-TO3a(vEuyJL5&8mYw{uH*F-T>f%q{+)W^ zN4Z{)!}mYEZ8U4$MiU*c9w#2ZGh4p1ZB#B%ZI2V&`L$$g^?^@M*D|=7?@qfNT~;$A zq<7xN+FLV9W-I54+}@SpWPSS>Gqcpv!%d9ynF8GZzU8y$T77MeP4D`jWEA z#}c?^Izp?;gQr=6rpG`dbh6cor7&-(R{~cH+0dtqtb8>T0?E6dc!V`dQJDbmnT>0^aYH1s6}R+xs&s z=Z;ie{H}G4KGNIIBw4T}&SpF?eaD1~IY&eHe%a!oW^4Xned@niw|g&JZB=TR&vJfx z^ZVTNzapPvcdgsU_~XFyD~9zar_M6ezigoT<$Y;m@_Wub-!cz4{?;*O*k9K4dUipT z%lAEvuY~P)2r}27N}9BaYq$Id?;F2A>`uJ!e!92LhSZ)VH`mPDaLfALyu#QZ*)L@q zbxddU&t`8}a_)^Tywt?y20-S$yw)HQYb4 zbNT$HiwoZI9opIxys$&-M$vS;G`j;xIzbQKXEG+F8+mbIXR1>pQ zB7iM4zACrDR`-Hl)|t{BOO!M7R@cr9n0%n_%8b-=yBI27A8*i~$8=|h4ICoKkejO5E1iys>=G?55p>c7_UFvJ3G8Ap}g97$NS0u*$zJp zP>K-Q^7_b$|3#PMc(kTYuR6nYfVJ-UlwVJmuy07=T3hh+_*!va*>vIko)-K3k{!Pu z`n*<7`iNDE{`YoK!J6Y?sX`X(6Tf~E*7vq7Yy2*__c)VlaPR3e=X-blP!(E!M_E}@ zNT%-n@8ql1*Ai|&^X=u$y|+hn{_i{2KTP+p_%P`aJPSQr4jU$s`gQnmeEUzT$mQ zp2gh0X=Te_Y?NH{JOA;vS@Z5x{ohxsTqd*0`uR-(P`1*mI(0Hyr|QU#KP#VXxhnR} z;?K5QM;mt3m%rU5eBDlX#;bQvZ)lrc(Pud{d-6WUr%PP|9B@*8p#!tqMUx@*T`2q-p_ph zui%E6tbCefygs--7YRhV0La0zB^Q3OI5hmVeISmj=^3it-!I*W@y)&)>J< zQ%#uYH~UBLZsh)Nz9`T0q~a8-e9Rk_FI#C$$|9pP_cpb|QhL>*x zIgRGbA?D{FP8i> z$yL7S!|j`&lb^Qgm0zgV*_)qba_HUfcm1{e`)u{BexEsZ*-vN6pXciT+5H6_p;I@7J~MddAI5u8A{P@E$nI_Wb3ylr_9_Kg->+ zzW%MYump( zd!tul5hf>f{j>;IL&amU2dVEF{eE$6C}lrrv-SVH1GoRN?f9>+fA@dH_dW4JMK^zM zeJ?JiZErj01HXB~#Mb4aCc95d$^2dup0M$}jGUgm4y*Le$w@hTtY5x8VtxBs+N!f0 zvp?Nq(Oucvl2e(__WF0eN!C~AtQyf5Edr-DzqqlM>ubW4 zpS4+i-{W2I?&tJ}a_w)8?+VMpdXn)orj_YHS4GUe@_+6(;hI<6_ag;+c3xW_-+$Rp zxnjP`p^w~*bGSU#^jbgXd}e#C?eviwl0w?;ul~JaxVZM^l%@k|Y_>-|W^-I$JaZiw^eE-#%`{LaA znD1q~{#-qNqAk3RpC2Cor{}hK;hzj4tK!Q)gX3>JJ<0j=#^L0W`=6$ZcR0EDJT}WX zoANkca(lk_)5vd|Vr=ez+v$Gpit@$#yUt8VKOA#<*7VcU3XML#6uS1rDeyRd@V-)& z=D5WD%g*1f$g%FV`6+rO?fWIILuy{%Qh77pe|x>Z^=kGtv*Z#t4Cd3b4NPKe z2d*)0SjwLx%Xr(3>!t0bV-NSJfBvySX+gP^MEr5dNw>cQwn@I(Y?s1!F`Hj7XnTEr z{&_q7f_n?s?0mv`zrA+BehwaSo)edO&VIjZng3xA<0fy#14=egyeY@!>U&ORHh(i{ z-)Dbw|J*A1{rdwQe(kX2U3K>#o2hKxws*fb+1`(D&awUy`;Mj0dZOd8L;gS1AMo`v z#r=|d@XPo1e8czCk6(0WTE3sZ;#%^nH9xmKN(~9g;;_5Nx6)+QD)y4_$7lbW9VkCP zOYc_N+j+kqyz8%;WHM28%QX%0`3}wR)lO!vpIN&%Vs+DolnULSx(cUT=keUJc~~VI zx_JAp)KrB{4z82sKnt2wSMBPIUR7t1$BCA)5L-qTH=f8XJgWOwwu^8BMZ?=Lzm zIInglChVTJx!>nGJ;6I)=$^iJ?Ah%Huh&(-Yi;JY&+=>&{QmQ%eob^zJG@-XPd(xb zinJij&TT(mPQC8`be_7%@2l~DLv4QFJU`E%c*&J;CX2fd{(NQF*5A1eT2P=E_ z6mF2OT=aJH!x`H@vP@eMEEJL3`2Fp%dAj#vADwo7;x{w?{&_Bkd*5YEk7@<4?$e0; z=<On`2WveUl{MoWZh`^hPm&5bn)uVsVnD|9NT|{WkE%x*O#6-4rg~gOy~P{ zySzO5n8I>RpQ>Fq8;&_-!7`_Kbi4&L+WEI_uffK8Gkd|vy}B`YH#zux#ghgeO}Sa_B9%X9onDIF1sV;?YcX^ z#$dtTvkq^cC~y6AZQ^$~&%YB=1dT77DW%)69liUC^-{I7|J-h-V~0Pkn!Mfg>_N}? zwio+dS|5L8+wj)cblS`$zvYq7mIb%2EmoHKUi9F99mg5{KRF5euCu-?bAP~lo~6%X ziR`ub)mMrRt&C)9>gJ7=eJ&ctc-OSzLwsE8&Yx2k=X$K+-*x-qjn{qdUe1>e%~x$Z z>ij%cz4XGKqRNl+U;l7-*2~rVTJWFaeOls5!-Cv$t)K6U=HF;%dwkb6eo-nJ2clo%` z!J3xi|90=O`mp(zUu~%Lt~oEhULlcbw{G4~L)Vs1VJyeW<{bTSo9##M8HJW#OD`f%y-|}z4v9#k7XaK zZ!N37IxDvD#@%|+<+WeAdsp`y{{8Bt{efQT?FrkzW|u$U@_`MVik#wsYz@deT=mhu zMnXKk>}-g!!}pi9@2lfw>i>Kco|eAtk3Yj4<_EVXSf0OAna}n5T%}Vr`**p-#|d|5 zA9iE%6U{HkcqNsyo$0k?sj&6RvMB-QjlJvmZdmc35xi91c#!A9`<31EmUzEnOY`jA z%G~%_u{c#|#`0SeA~>Q|Rz560-&DR;VPApYzli3r%qv=krngp<-gwUcUoNw6&lT|( zRqr#H!;ck4xY=zF?BY4(e|DMwhRkG{pBtwi=3V%K@wx=dxb|;+4{GbCO}ygr zW??5=ME=^{LM8o=cWQ>txuF|dVfjJ2-tOCMXsR8Ks8OuwN1{+tg<65Hqh`X6Y>pnuvcqDJ|c+kLb0U9!ox zV!;-IUng|z*DhiGlFjINsy=AXh9;+o^U5=7dScF-ANy-8*M5{~?^Dlvk#{cboanYW z&STU27S(wE$0_C^pZ{kDZ{H`*#a5T10&;Cky{%QnfHC$y52Wp63Y)E279)G(vGc)E#KYN?vvm5 zT;$x^modj&-#nR?dpzLZtvt5V-(N2H{`TX1Gq!2JnG*`-By?6d-(&pJA@+pNVAFE8 zl+f*`tfy?=C=!uzwDH^hi=6W9UO&_Sxifw^9XVh0RBfWybv@@7V7%)@Jf}d)&8@d>AI!qhtB+$tR#LCvpaWXbGu6Qhc z-y+d7Nn_Us&gVB}$}USBw_&X4*|l);yw|P zn${!v!N{$7_QVl|}yOF5gEs(g>_KvtMHO#Qy7AAoq0&12=)$*?IUlUQZ*pk)e#Sc+PU=nFC~UB| z;?}kGjQ?KsHweqcT3>p5WtG~2pM|T$udb1K`lY96ChMxZANQT-`S9)k4k_m1O8wQV z4!zs{TUF-slJw*Y@)^YunGQLI<_|A&);@62PuZVSQTjS>OJ-K`s;K9o^BGpSuTLvG z-Lx^O+3Kz`({~BU?g{z#i_7k@oVe|x6}SGvwtHhhMGyd6=+rgGhuhy;; zn6<|%t;T2ZHQ&-1gwM>b0wkni8{QElDCuHWfX z)=SI&dskw9L+?N>hfWUjwmU2Ai)S~svZnt3J5lmS^??FYgP2s6sgl}$hn8+m<$Zkj z-ff1HjOt=P(gN(WgluHL*D>yux~XVT_u}SHQ8$%N-7e4ajVVWq=PTMJANl^|FV~A( zMu$XlZyxmc{PgFmPnWvy}6fxT~+?T54FOy~bBUb?O_`F8Ni znX8t}{LE`{|E6rb?b<`!_bM#K#FkeyEnBrA>#dc|o2U7c)$?Z9`mb2eo5Z=+^p)G4 zPc`!m>MWe9v%ekZ*M0oe zx^Df^mad;)Qv<9x|IL55{ol&taz>xlMl8(xCHU_D2KPnghnZ4>b&hP z0=%<$7jM_i&wn)+=YG3W>|gWqbo@L6?TY0Dpt?se2>WG?#5G zX+JY1;qkX;OO^KJpY?fK-t=>yS~%jSYMckC?unfBT<} z^S&Lehaafeb{vlqmrvRHVp^H_9@Cdo-W~}S5bb7tdq3KrZOTfXH``^xy>s3FPGVUw ze|xrFpRL`;g#Eu$Kb8NtI@E5*A5;DKJl~Vpu>6CJ)r|R-dNEQ5@+90 zuGh-h%TFfkz594=)r3=Xe*J1`I#j9I<)PU0JH>K|_I~-2AAvWD|1%dHbv^q?yIWQM z@0pVo|BU4{Tf1O`$!EQxgUP#kU*^aCw2P_y${W1%xnyqfqfT`_W9RG4I~Wd>HkdSj z&0CTb&%WX4J@qB(N4md+uh_L$+~`5KjGWa+wTM>?wPuw|o%iL>E@8K@e;=718f579 z@WXtolCz)7U+;Kj67XX7u5QzR$G90Ts`)9Uf4D!}jmz-!h0@+hfda2so5KF>IeaqA z#hr0w%+-ZE|D1cMF-PZYjONO~8yhd_z891cZ0eT2_x6?5#NM6{A2v9ho^!wCubV*3 zgZjCWaZl#&G`?rQK{Myi-5_h-6>G8{Tt4{sJg@q;gEh%cUn2B>Xnt5@&UF7br-AwY z;-nl6_RrPt8TUNkJn&d<&!6c5&8OEhygXgAvp6nq_Pk@aO0GTFb$0gy$K&xWXQThl zi;kO{C+`0DoP5$@**=GQ1C7X6vsT)L9N*)R)c%d@>FcH*-!+VBJJ<0Rtxk_Ae*X9K zvDC0R|I6mzI(U6qaqF91efQ4yPHMh8yEpp$v-zw|aqFG7pMK9~SFy-g{q`os=~DA7 z?v~DrV4srDwVF@hMt0rachPhH%S^u0<{Xl9D@N|pMxEcA)=!w6{C)SvU&n0K+7o|k zERVnQM<*qtG3CbFgk`r6+V0C5f;b3u!)_! z$G$X9-lrCjt9r4t>WtxW9pPN(@IS2#>#{7La6bOqKH;9B%u|6ohI5>&>rFZIxn6U& zMzM&Ac`j~fycwiw+*+_cI^Dy=hsV|6{F=A1YZTus3N&qhZO6P%zWuFi>z$HY)(4)y zPqkRP^YhykykSp2*n24?pSY&?+S9J>*!dmG^G<)V;JM-b#_n;|qDS+WdI)y0ytC>5 z+IGy!?i$1M$5VP3uT|xk{^oXjS#*PUf!wx-6HNRrs;~+d7jqrI!m-VJU!&)h`_JB( z$}{|X%Y7j9IUnJ@4nNV zTzTH~*|E7V9CuYMcs%94c^PA6a75QdvpDJEWAXm|_ww!Hx%TlMI$ibZ)c3C@>i1&) zmL+OuuUAg3c^oEvx!O@^l}Y`*+qc7lHW>K-FNl!3aLMt0^2D=Uq9xCYrC+_ezVT`+ z@6_vRdo0&Jd$Re&!9y)#A9wK0K3K&4{omU5hu!j(@?y`Py=vRGSn!#+*vEJL_3*7n zSI!2&@+C79#KUYgUsuP^-Jme>!c_)6bBpsYr2c0Y?|8Xe)s*>_z1bsO)ramvo}I(vX|uWtT>iFy&4b57Pz6^!~d z<)#S7j_+%DZ-y_px8!(^*lETV4e5uAa_7CTKfdwU_cs1cz1D~fx-;=8_ZH? z=B$@z`nYU;QRdYD7wtGxH4D$ZUixaHam@OZv*MHD;`az{t1eu83;Sv}a$i5LWoRUJI$3L(il>Peex46d?aiLk?dqvbO(jP9At!!F&`13;6w!M5dEbKBx8hU>&M)vA{ zve@@w|MJJje4{tsUY9*>?{-+{#C$DuCcIQy(DB@IyZTch7tQv@@#j|@mYz0K^4QIo zG(od>)6^B08DCG%Q!07>UangBpiStWgKQ^tf3*pkai5tXzbbp)mhPWIkN%XETQl02 zJ1V@Na_+$%iE!L z40niW=FMI-ZCA_T%HpHk_di!~=LSWJ^G@NImLc`;t-p;&O|SXwlI?q(j~47Y^C3QA z_wJ{Qv+cBUPAM0A%{?T#>A_<~>mBB0J05pE&Oe*n@=>^+-?ekXj3E8|*3O0=*Yow# zA6DiwTz`8cr&9d*#kUNAjG#vcP_pgSR(k9}ey9 z%oKcKP&g@2zUX;s?(X?s+M&(w9%<-b_pep_`TX2J){f~+ZN0wl%w@T4UtAKn_P)@p zc50S5%Oh`I9UDib+tbyyN~x-TwX+U3iW4)su<_TNJolm(w&D9+{_N z9sNX`<4b-&bLLd$+3`TY;+G=-;iLAJ(Z5)y+%7jcRC+!Cn0@_|(-q$)*XJL9e;HN~ z`%SlJo(G=LconwpxZPXc8=HDJnw;+b@Z9eEPX2j6j;Q-ZOqkJi^Rskw^}U6Am={W< z3M=Qo*wi2PzG+TYfd8qj7ucUYYiMOnIIyNNn??J}jRf0dqhH-p(-j3~&sNwF&&_jF zVA}0%5z>vXmt6az_G2}xC-;lAB|O^?CN%_@{Jw1`oRrSWaCS>ZRJ3wHUs&{|n{nZd z`~L_WEC0Iprrd^A>}NjNs+XFXv+k14nX*w*ak`Rg$EJyoC5l(EJlVd?n!&BL%53Yu z-RUQP8$>w-IV^jB`bExewTF*2&pqbk*|zZ8kK?lb(Q$t|vfiIQF1_ve)qN#g1#gTS znC1BXd`@r3&b#q5(9Gs2`+M#^&sh(Azqh==?$F)2nx!G$?7_XYg3tf(T(~XT{I1U8 zQ=s_6S<=f&&Kb*OyPLn%!FX!5o9n(~zjumWwy65ypL;ko~OK)AOdoNOV|IZzFrTYeK zhioUcEm}QS^4;IM_+#(1D?U2%xAMk4KD+R};Ir4OgG%=ODt!6t+KH=DCw|4e5N6-U zzPJAC++X&&Hrbmaem%|FczAhu@w=~X*Lmvx{+2s#$FlnRx@?o4B!WS;P`agKDvZL{k zPJ5l6Nx$iidkfYx260wOY0Zg$qpe|HzFIGhH#bV1L~g7sG>Bm+?0EY@^O_~6{@Pf}6*{e4mv<4|e7=?JJVnP}m(CbXkOH*_8g|GPWI;9S>yB_gp^f=Gr*~j$9zj`kp{y8)D+5^Y>-~AB^9r9UIdavwH zDm|#?r|-G<#;F)97IXGJ?v9yny5mxIom$bi>*KRkEuYx7+B)2;`ObRnvT59$k8Z0^ z2g@t|I(qeM_OV$fi}o-6$HA=j|AXhOhXDud_p?2zXuWv(>AJq3c~>tMdOr%C`*U4> z=%fDUGCY57HU_Fr*e9%~$NIxcbdvM^t8DA#N|jkTqziV=``b1pMOQcB)yJr9`_3)t ziREIPRJOEucV7SAf+Kt1bqDYN)OxI(?Re!)(d&NuLCA^)Wt*>rXBwTZm2SyzNUZMPt+ZnE znV(sWfeK*_62`Gs{KxEbvlcVn@7cOboBe%Uc0u}{<1OvM7glWc_~;xRcrBT)c|OCA za;-V?%ui3N8npg<=zPF^A4g2x+PEld;Zwo$LblZDewh88>E0)q-mRH2a=|^t$Dj22 zRn9F9EKO@X7qwu!_`dRcRyL0$?;l>K+wxi1FU0%q&Cng^q_r zeEp`|$t@e{%FQmLy`L-RcaGTVt34lT!{)2If17t_>3X^TXD9!6|IF1{a_v!N?dSb}#Q%LUJf1W?)Y!TVK0Z~p3RYihiz!~Q-}85}#s5dz z;Wl1OOO*Req@1l#qvF|e1qBk}@nDf!HCDpl3%qa2B zL%Hv<917;y1-=Y>T<;0!tlGNu+|=p&Z?9|;o11(t(XsSV#55T$+06%R@^@wal-=_4 zR?(zehYxR!YC2pVU%tlJuHlzz%mvSV{-uS>{MpNur_A5cC4b!F=bo(@+w4;eo*KCP z|C{?@t31oOoh&oHv#Tw$?pb;Caca+5$KOon>r6h}VE5nqiZ3(s;^A)loJm&Ag55!T z_ZrN)DL>z~vSaz5{lObbZ=Z6t3@=X;`)>12@DTqrIdzczrnAvH!Q{)$g%f zM_#jsaI9VW@99K?BI`1f?Q`wQ>YWtqDh|C@(Du7ndtY=z+``*0PxwEs`pz9aY1@qT z@Ab-aMYJD3`?l-V!RgBd+ZG3%5R~x0Am4Y8aj*SCg<|DaZrvF#!+YdZ_Qyp;tY{N$ z`?Qo1yiD%Py8TU?_y5+tTYf=xdTcNMpXcTUe;)VUU6^eM9R=B14A15p{cIoG?0LU9 zUhu)TNo6-v#cN)A$44a^9x8IGXtH0vw&v;mk~5#Dp0wbGo<@7teDYbfE?Be!Ee&3I6|Mtr4$-biv5;fCTayrD_+kRDg zePg?uLTsJ+yvUf7lX`QP+wh!Ue&gYe)qBq#`|=~LjHTuGm)?!D{CDr=)@P6V;rHNw z9YfBotkkKplBbP|w*Q%R^}ydc$%@PI%+lA`t7<2AUYAfbRL)H^c=L|!%$xp;XW}Yu z*Wc*~-}^M=jImahm*Bbke+`c(|KEMr{o$UYZk$f#+jl4SS6_pz1=w!C^zfbc4l9{{ zZe2I&^zD0-&(EFmTlexjJ;pOp2UabP6Dc~LJJnm|&H5*|#plFo|M+-Xm_cZBaA?)) z@H3~@Z{c`$RAVLYE8c~{56q8p{LN}P7clqxr59VH6GOwRV_y_A{HlGyFz;RM-m9;b zueoiCng9Ik1KaPaA1$q%f6d?ZG_>UO^{J+Pq3^BF^Q>BO;XtwJn+<2z%K6kA9Ll$S zc+%qg@8wB}^Zc!E{(Si3<)Zrc=>>Nd*YRejB`d-Xiu&3e0A0kLceLuRwOwoPlBwD& zF2D2$zES(_W_qhC=hl~f;pHoLJ=s}Xe#ZDxo4`Fqi>K3alr-w^KeXF^&|`K2b9uqr z*E@UfEZo_!az^>VzqzIrcIkeVRyTj1m(e?|Q;|2HuX3Mjg8%hBZ-eiwZj(71`I=Ew z%xe8@y#TwQCwa2|2Mu=y-YeGp^Wn7Cw9WZ9KQTQ_$&ji(_)_tRG@n+_lE}3}<+J{z z1}hf2H|(9SYb|?GQU5f%!g_;H|8@q?cuR*jpH{CIk3A{)j(x#SV^h8r`r^|MYgYfe z-Eh7}ct>7x#g7Ni8J@kIc0Zr_!*hP-dH+_V&3*BF?K2sTInTe^zIxM?SkCNb{Cm-? zyW;1o|Ni`Op?+SgYuZnr#jBi_-##4s|ABwu?rK|s=83yciUv)uobuXLJ#_ZLs4czm zZi@cZ>B&{w_1_!xE=O$IJFhlAqHA~Dy>FtI z*p+ikf{qt|`+Ab$_GZ(9r+g~Q=N)CjkwRom@33E0vY6@|5Ft z^EDEV#xCYZ-bctTu@Qa1S7W=*>VEv|@{I}$m_^(7El=!idvR(qm$OXGk-G&S=Jp?| zOrLYD!}3x2!B+A3#{QaDw|{K5-*bP*t_aw$wOR-)zL);GxO4i$)$0EfT-l9e=aug` zEc@Zx_WiY8GZlUm|8T$Ir{JG){{G|iBd&jLZ=5|j)w7AO@{7pBh>k6qr-S4yoh6@Y zf4Dv8bIB2In-X_T$DOS~+@{Jd-%FV*r>)_UkH3C+rRW{&3VpW9^6SLl@djVxBOcF&ztd6uG_1x(>S}{)OQ|ZsK2@XYRs~W{rYV8J_tUToz8Im z-jB~Gr+zmq+);A+{&T$pujet!?C@{6X1$ZcpwRB|A8wAcgPEdkSEG2Ur5k4MZ4709 zWO)9U-oAftKQF6_I>tYt;NOaNdpY*|?+;EouDA2evw6?oPjX4V8!MXixp;5*?RNds z+xeW<>+jVUHCerQ-hIn=b>YT4)=7F-ezdyeA-(sg?62CN;er=9vW z7Vj>k*X@3GqnLeHjz-GQS`qD_M?LbH`I*Q5c!cyGyH~PXcJFhZYsc9+p4@o5x-Z_J z`QH6M%Qh~W=k`$G-|KI+dbRWJcIvyb{oghD<&x%md1cR@dSBan{xb)^<-3y=-@E-B z>I)Y#zI?gJ{$uR?1B>PBc&?r8(`0J%&y3egt_~L&s0z8HE&+Z@$7EiJHKish4VoV-}AlF{dA1c`sqXgzTML6+wQ4E z9ozKkwAHf`aiw>8Wy%$w{O7GpbG~33zbI>mY&O^F+(|#Jr|7S%(->P~E>OyUau; zqHIr7Memt(pAOR-A`1C_QH`ITC6#mUsd@IE^~SrvFPD~F*4QoVv$*W&!#D3LBG;8_ zoj%Ma_Eqpix~0@;QqZ>qTzLd6<`z z)8Wf|bq3#0^(g}EOo}^~Oql&dXwn5m6J|{n0S=XiCskvj3|hoEbR8HbOY~$OOURMj znQ(fWj<@@zmByRD|C#??JA1utdY_8;OojE|A6A^3x#{$+?EbUwtFQmo{r+d+tH1Nv z@BNm1aB4oo=Ff8WMHN!}g68S|ul8rG`|MdYC9#7)qS0bQp0}dY^U^JQ-P#CQRc(+Gj-eNYRtLLy~fe5>dT|c+n;kjkvyra8FuTd=wHXqO=m9Xp9!3w z{^{kOiS0-2?rjL`n|1TM% zFnvoDUAA91lhW+P0%MRHy+ycc-t3UWI*xgvUm+jk}z4;FoUVj$Gmor7_ zMR-zV|IH7zJ7o5Fw|z5+>JQ$S8@%!GO|#g?`@bEZWNG{)k3U?M;f`YE#|HxGS2PUQ zJzIZ|zviLPgJaQ+a<^Gz*c9GB+F5*SjRgBSOX-T&+zlr`zB+MbVQ$Kgbw<*=qhlV$ z%#zByEa5!UcR@~`!$nKE>R-zrUF`qw{^pix?E$Ad8$P9Rf3N(N|9A$wom1>*Bm3N3 z?>X;%*Q^VxMOHJ?nnYyQgX5{YuUVj}I9V zyW6)M%whfgG>FxGUsBoriFYdfDjl01>@BP1Eoi@eUW-*H(L=O}7?3Odfrtg$fYv`H(rrS5mQfv+1joy`p z4u9@tW~|#Q!mIOX*#)Nqi|7B0`LqA}UJ2Lz;j`8M)-`V5`}uLjD`B~osdE?3Sg-^J zcKdK%P*(*3*~VX|TJIHf}^c7)uVu^Jn9r86 z^Z$7(o^U=-)Z%ISN`shpZi3{ z($f8B-Xn{PH}m?cnttiH?>_oelehJ|(UMeAeskg~{JO$Y6i3&erZ@ zYyRUs>T`Pg8ver)z>?ijNGae+eT{H!_hJcEE1&aLuXXmkN!8!YoBsaERpS?Tf1Y#i zU$f9g{n-8Y>n*Q*-!J<+>R9vV+U{VRga(l+-kcjE27cQIn z8_f4-JZsp?HSt6r>w@{o0Vxt9E&tDSEVrKjF!THn|3egTWJnJFKf!Q<=UmODbAS z^_S(|UtvB8H}+b7{weEue9xK0?%zTFcH0xru6sMj;+D?75^3fwpJkr^c~x9+Fn+I8 zt)6@5mDgWCUfcga+~(n(%^S;l`2HTQ_zcSXVB8zR^u-el+-Gp~?fd+9H9v=N_?n2u z{eN%oKYHWFja-}GH(2G<&kDcVa>g>>H_UlX@9rmSS7z%@kCR<2wZlhU({qYA1J9P< zdaCQxZv8j#3|q8ZuUavpCMR)%a8Vy~Zy^txFOS%62g7W>AM*~`^lzPbYz@n|Un}Zw ze{A1TbF}A`+4ZyyO^T9+iv4m%?+f-dIwlINUo^w&-9Fx%DNp6EP0767eskwh>-|$a z+-_9;fE#BI`3B^1YTsjLyezN?! z$@b>e{1)>)f7NRK+1qv9lstF;wn0WO+p+L<2b1GIikU1EN>%C?tJ5|@6r^MIuPW`&V z=GC$9!O^LY4&48JzN)cd<`=nYW$Bgip|$ZP>!X(R>BBn4NECAb;J5w>HJao4A)ede8Ei@9A56U5`dq8&C1jv)zTqYZs&jes^YkE$MK5 zB|}>FjZ-Tm7Ovjsuhnl@-? zKGjTFP}!VaawY0WZ%c{xk6?}yzLpu`73=Dyx0Ewv%)H0-rS`<@EB^NDdM)QFWyv~x zUc&l?Gj_{!CixzAOH$VzRC*O^e2AXU_Iydug~8Z=l{?u z_^a!1ooCPXR{i^NA0s3ZH-FW5P;W0<@$GS`_g4wY>bw)Sa{6aB%-plYq0n8l&sk6T zpp9U@-nyT5Y5`BQFXrYQ{9ZPvWBQ(Fr*G_gYt_4CCvUU6$KIsFX*SP9^IaX?ZKP-M zSw5Z5MXGty8Hd6*0HC##5S_}aZoL?n2krQS?-+c;&<@%!rw3=Aqxue$#I zYWwY|G{zhTPzB`5Gm?t@d?z3b-mY>3>>^`o_&q5faB^K7A}$o*=8vM&1J3fI_D zrgtydX{>To{hpbQ+EWeb+|Dg8Q(qh0wi1=!Tfmh6?{$B2)%kl1XUuHwp84i=pS@`R zkHyy)PVkr=G`qv9g`ZXZ{xs2fI{_Ew|hZ=eV53yniQ|~ciE?emhXA@bh{tN zg-@^iYv$j4?sU*k9XS#EDn6a>)oL%NCkwV0KM$XB-#9SP`(|Cl z`$f-{3-0Cq!s(b_wIA}oo4KOE?f8JmtU2gQ_uhKHE}p+CibA(sPv}y1OuS_H~I#>Sst&682w^}POjdA;~-^Zb3x>9%-*xO$w9Cc0tFMW~oI1?PT<#lw#cH1I)`$tIM-+em zIeOrJy~vInYXjr;zH$8K6Z-vUZzw8~*>T^t@%n$gf=?S4^P9u>M&T~}$@uXFs` zZ#$NB```Q*^}P$vw^z!=Cd}2|#&l%Xv11H1KlVCz>bL8ZpYETs)@bkdG zOj{GDW5`~+WuZWJ^7_n&r{}(Xm3F;Ar+4nARwnQF{Cg*^X-Jk%G!B#2FJB(s+PmsT z;N8Q&_Nv}jFZp;`RKI@nzZJGRRd<74&GPNsv(o3^yJg~U;-{5aRh?=1a!F&_?CDd* zbaq61j{Ir2c~OgL`?uY*7RAhxFlQy5$kKqE=zH0sTA$3{`d2I{lB+1$@j$=ZSQ{mbDksZ#(U%I zHpY)U55Aa`F0gs$qoos%g z<*bW37v^yayB*L9-y%2N;_*_o1Vbya$$`ByC+67aHqU6BUwv(JN=5L$jN@7tZ4Nf% z9Q!WSDL<(^vsa+Nr@6MemGKyZa`LSx#&ZvTiA~Bp{^;71irsSSlk->4IXcxQR{ft$ z`S0!WOm?4AAIy?|zssi5(r5AJl2hyFu$;4C{LviGc5d%`qhRYegM=={Ju$ZBG48kd z3-)<*ZzKded@D2uO;1yDiK(wxBUT>J?b_erw3>Oo_ySq^+1j7Ju$fnj zE)O!?FA;hxy)E}vW6ST?a<)N@ZgR8vdd2mxL`pj!j(S#BqVu)$;YFFN0>|3lx1U!2 z`AGFZwH@3)qfwfyk<9RP7JTjtA7XP+cDqRlBZ#;8f3HPu*1FhXrnKS zpG+^CQd_gr+vDWZ6;d;PRjZy`c2UH^-a&eAjq0=L*&EhZ>esw{&0TT%_5J?na^G-U zK0eoHX8Hbp4;}Y^PFiF6E=6Qg{<_sJhgM1TXKzvwseEERBUk_Y%RfEF_Z~}DFA%z% z8^m+)?LYD3d&Hkfm9)FxbW+xv;`Z@|t<2ta+c%zltrNWCEvqT>vtK)3bsVnrRIB?^ zXV16$T+oefwI&ghC)=*%vzD*B{8rC;LOYw6)q|ar4o-SoS+aNEx@VcD#ZMyT9QIoL zldM$Mu6}Xo`fH_+HsAgpEe^WACocNGU(x;Qd4^`~^B<;53l#=$D9oAnP`v&->yP8p z|1wr}cW+hF+y1q;dw;)w?Zev@zaF1ERAFq|;|r<$y>@9&LDc@dufra`=*`|xem|$T zach)r&fQ&K{VbxJetMRkpKd(M^!&5ce{CH$%4lzw=c3K5e#bndg>()j9Wp4~Qaz_{=V#4@{Uz_*`@+ljU*}ljy!BT!Ys1dT z_6>h;bjfM%cpz(#wfp2#iOPVA$<1Z$9fB3+8#EtW?{kjZrZ(}G`F5si)93q-H@CEH zn{q7egZwd}(oiSPm=CTG4#zXqRhSysGJlE4-r3z~)1R5dW09);A-kV--sdB)cJUZT zmrA%Z=YD$LKC@uWZSl}zhLdc{Rr!1`7#G$oZk^Pd@VW5I=czL?^A4Ky9lrnnv02TV z=J?}}{=ASAI^88;nV)p_n(&7Y_Ezx+)|fX<&D7thXC*Gl{IuZK!*%YrI4-QcWqIH5 zsJGBGLB}02ddGIUTwnFgI?DXuc?sspiXZ)4;*C+Jg$DFFpo2PEXHrTSQ`H)z8$nmDb{QLbj>%?BS z-|3bQP1Basxpl1QNvchXJ$9jSm#EHb)D=7AD_=_)vnva{3Sbx#Y{WQ=h2tT z{`;BJR|j~w2)S^?`!N5#>)>hqRVU!nPRX*|-dRVo()Kh}{4NyVoqpiA8?#2abvf$+ z(RE=3zI=y^71wZPZs@N{+sk$({Mnxea&nEQ+LLu-K1SR2Sk-NGt5r^T|G|bQVoB7K zQxkM=|5xdJ{GRRll%Ja`CbYV;7Ui6&e0W)i`H|(%SG%?fUQV3)HsacjhErB2?%A{d z`MkWr|F3w#RsV*WJ9l4GeE+D>v^e3b$cNAA4EjGpADj_hy^(vdsY#C2`M0%#;@_*f z{cqp;-6moe|Mka})AG_gUmtrd$S%fq`@n`nsZJ?QKaJ#_CvLrvtbF5{))^DCG~np`L9KDW}o`Qxnp>&`kx9w?f=^Q_xUwO4zl z&5-<;_j`)_{L9`kuNfKpZ4OBkuuIsQJmBhI-4zid&3!(4O3%6b64!r!e7$_j1%XuI zoA0j{x|~gVTOAO8x!&vZ%?#I`zR7iNkCV4>gfSNE&94bOUvSjjPN?trqwPhkuUcMS z;t}J0Qr1v%Sa|*S(@s*c+oOFOzf`TxQt`{XS99<~(cZ6TY$s`KciS5E=z~s`+3c%~ zizd&z^r(B+tDX1bYwpP>-!9+ZIdku2@0c&vd3#v(tIyg$od53`|AtL-W1u~LPtW#f z#4u3Rz0LCN{55Z!?raHu$i48~l`A5B_WyoZ7i_zCq;$R2^Ot?gmFK$je>7cc6>?B* zqfq=|3+d+1j61CVJg(+-ITjEX*Ux|PvC5Lei;h`GFuQHtY;~ePDf7xRsnpXw3ajLn zZ`R2*z1pYxA@}i}d5>btZ)qRw{wC>Av3G|;Q9yjF=WM0pdw1l{bbTAJsj(+{?c2Ow z1?@nQ>+4)wSXwgmp58J&A@$?hQ|6ivpP6gkRv+NCHWlnFd|r2oy=eCRnV+m&R&&a1 z6{~pE+!+3c&EVWOw!@F@mpqMme{QPwgsj85E@n(WC$_!TF?_xL=wqE=|BJ6z>b)!| ztc<<+N7V9E8vEZh&6_T|tvBp!eU|m+wE5%X<~@(E>i^VPx9?M09Gxm=7b%UP|y4vrp%eBz!eyKR;dV~z~x2Fted0z0dMeKa2Ve@F^`6KE7 z?%V6UEIz<=@A>`$!F!)4RzEI3S1n=n?e1hy`w)anDi8(5ie$6(hfn|ekXdo@=lg?) zlKc}c+_)jJE$3zt`?+a)KP-z+p4So;`kuR+?{urks@1#sF7!ra^Yyy=Rmj!c$t<2R z%l+XCKk3K0#|>=S{+b%dJMMMJZD-u@_=q5bfz15+6KhXQ-M#D7+P+zqxAxs&F=|LO zJgEI}@>&ZA@dH`DimV!0UwCA=FZmYy_hc{gp7;0J ze|%D9j@g{tu(xOR#T4F)v8<0{`JUd+eIVz~xc`&JgZ?_xS+nXmr_EY^Rn@y0widv( zaINxzir+gvUU^l!>&1egy9Z*em!%$8DtdDI-{H<5@xOcu5F9T#8Rq<)@_d2qPOk?WudGwK z<#9MvBt_fyn;v(|-g&0-yPtjC(}eL_wDLmOB>%WSuI@=68xtNR;}CmIoEO;+pKSsM6_*o-&5SXreQ<$=>xmBFTMKx zMcp6s`LczKPOqJ=?Xmijy>3xqimUgug~wkTzgTFR!L?#~?(Ho0_Z;3O@3~uQ4)$C# z__Ft(>caG8=YQ|`@jFsPLt0ht%Sy-1>g||F6G4I{4jM)V}fK1M20L(D@+HUBeA~|#cO?Dh+w;3Q-f8=F_i3pSd#k>_wYe>%@hvjs zY>%70L3NywiHA*eg7dWrJ9Q`6ZU0m0?D{W)`JLcT;Tbfuf{Kwi8O$<)j{G8m! zTK8JKG2JHZi|YD|zO%(d%R+yCn{%CQ@mBY{Ho*`27Ce)ej zQ2srq#PDs!^X(u0GS=<-z3q44H*a>Ui913nw^%s1?-Jh@^DAZwYrd<6JoCxA>C6G< z_Q&@{nLPf!F6m#>{}Z|ruSI^${b?1cS1;wco zI;Hn#@GQAdd{2I_%=?Ou|J}+H>o}LppLeg{zUJcnhxhOQPmi!YxM412=wQi`C5IQX zynxIp_9gs1dU{@O_5IIs6;I>;a)+&nDExhXOUmZ!>(WlQyRB#WbZ5!IO1o3<|3sR6 z^4cL2Ic<9BdTCSXWi?92o$|haJJDCM`}Evd*3s@umRd{@-#OQIn|uGS02RxW;^l01 z>yL}fInemVOlSI`gzAFRukQD0*wYW>*$eRz4m^1H0zI>VozugnuP36MQ_ zapP?3dp#E(mSz@CxP9iM;fI&TB87{mZ*V$fC4Bd8j+CdG^HSIQy4>zXQ(8Xv@}1h# zyXc~rga7qEPTSauBU)z`soH(pIQzrA?e)B$H@9El2}{UkPS(Eu;L343(c^#r?96!L z%edZf2EV|)Wkm-oq^*|no~eG*c}BWC|M*qj-?nG&o(R-e7FjfD+O+L$spYOe=3Kj{ zV{f~U?^8|b!NVnAn;b3nWi5Xf_k8Eoh|(+99i@&}mF-q3o|^GQh$r}|*UxE-(l%_W zkGyl)Ve{21&$fiAJb2Y?P*!H68ouYp-uznmiW8CRR>yDo7A(qK8@JLpF8=BBy;Iv< zgajX$znG-|RH}^SQu>L_S*a~o{@gLVe<4)(s_CjeO`mw}2`8RMY-k%ZPKPCG4E_A{t3wHXZ!oagqJV8U&?*)oU>i5 z8+)VAojf-Za`+i)ltod>OdEv?L zVjGgv2G3zv;zmw+TLqTeb7}qZ4Opn61B?ZpdHO z_?Mga&HVF*w$qcVwjFr)w|hqHi^OHSJ8m9jJ#+5;!80{&+3W9h&$a$0d&e~2VUMX@ zW3|nPi56cA{hf={++_JT4J_VoZHEwntv?^ z*Zuez_*K63-La;|J=-}ZCG{ucRuD6C`hrXSKX|7}~YajfSJJlc+cqA1|CE>aiDkI%6O9{3LO1>9F1%7~AG1^-=EF2&{m=HB zMSfj~F`L4*YW?m_SKcS=ZY$Ewb>ebi@6M5A+v^l{vn*=nx|$8&i`}J;FD~AdCHGp} z+vc5{_>&iV7-q#xOujGLvr{u*Q8>f?5BuxnZ|^HMTzC81V%5)+Cj6@v|HCf#Z$b6L z%>KVSpYEFrDNH6!n)I$g>ho=Ti&y*CLWTMm?pA!3`*V!@UR!?rF4?_F4V|5wciz{1 zKkOazQ*?g-Q);;_P!jUa7p5Ec@U{azlJ|4fFobQ(i>z{h$6!@z3<< z26NW`TW3|Fm9u@bxPg@Ai`IKl+$`1eeFPg9Nf|EstoHAySsLSgzRKn4Qa9@UGT85_ zYRI)`TzAiK!m?evzcU&aA4uwVopgg$_I%3Sv>Rs&%uZ=Y&Ny$KZ1L(!_2Vt^6>4hN z@-NN#sS$6tMWf6>|9N&_rDf#Z>^ZvcyJW8$O30UHPHX;r_>=d-uUdb#&rRVsG%xev z`n33{cG#A#xxWf|{P)!UfAPU(;-UR!n>TNn#y`^siGAp%+b}>@PoxG(LUd z$tQlsw=)}F)m;>yUYT(~nmG&=EsiPcS^2U)qISPZ-3k`w{9Dvl@f?3cV0i9|Cepv_eteRX=QOTlcyX>?dg3u zvA;&;-j8GEkIopMxB0-~k$mGX+l%jvPn81Rv7L%;EaDFky6|~o{dw&*>Sy$yU8&T! zXZcziRsYp}?|wl=*>fq9POVA1@;izx7^=E|C=`@B_=j$Y^Y>Pq6&2&W#GR)+nKdH6 zJz{g|>PKo*l!|x1e${XyP5->MT{eg9HuvmK%M1SKkxX;SvRFQck>Scg(OOnOjH9S1qkEw0=c*VHz(&9Q!?LIp_ zi>G%D<-HsBd{yIE@1Ah)m5BF)Zx`H3>Rh>_Qlj)9&yo+jvt?_PWRT~gh??8)7|c)F zDT(h%TRz*`=32o~wO>9wj}}jg?6`9Ag=FicTOY2zOJIEYZLY?VJBt&gie#A9*Djs- z^y-IS)0ywhURpEXOU1XtdDB!wwr8s)q*=d~+`C*Vzo%T>SWm}qc%r}aH%)1|j}FAg;8C?UMUYKUDaHz6e#KFtf6&|@*_DrQyUu~R%MC9zo>hzskd#cZxbDoKP z;#a;n$@KFc*@*{AI2Uc3+r@hI&^gtFTM2Ov+3&i(_iF9QGT7DTq4WIba}C43^)Z(Y zKi7Nn@X`Bs$4sx^@36gWt5YU=?DyB7^VKZ>vN@=2FB2_TKUu2cSF+TPtH-0?`Jdq2 zZt!-GqPAR$3D33DUpL+mkS*TTaWf<0^DpKXeMRR|xy`n0nt94*{Z_u>vzJBn8%&iu zo=2FAEcraqRIzT_M1wL}(|gMrcm5O#k7cj<{F`-OVP@m$ZK6AVUSoVFek0_oU(Tx6 zyg}|KpYbGz91CB6)bM+?#x}bj8#C5Ns5XahSgGxA`($QznZ?qE8#Q0HrYcQsTUhNI zSbFez-P#`l`Og)mcAPSduRSqe`rbBi?G(R=Cw=#3RW&-NoWJmodBV4Rp5`xBM@!^h zM!4E4ELtxlsU7`RQJ%Lq+IIh`1^XHcGmpL6zU5%~+0z%}1XGW_zRb_c_5IoE-EklG z+`W8HB|M)?)9d7(>&6>}8k?#={P8|}Vf!nt&u=W%_QuKI-P3t;V_4|=3F|FvJ<2b& z2X#Mv|DjSY`p?>JyR3Rj=kVflze{62&h7qqw_9(@oqrK?^2KBSIPdxR z-mWiv|AWl`m-j7$q?}2U>XuEFfex&=ZxGvUYx6O3{c-bs#iQt zNoSw>cK6@6iyvrr$y@G}ZRh-(rS&gK_>@~;(uG6edhfJ4{@+ttcl+Y;FZ-CdyP1oR zzWyM`Q}xe8WcQ`xx6btCKlqn!_Th|rTE&_3)#l!N-HhfL?g(vKwd4ZB5(Cej=gQ0k z4dp7Q>Z;#q++$Fs+S4WZ^M;DTS;GVu&ucFGCvLu*Y7pAJeCF@xd6Dv2JON)CrSr5t z+&#_x=enwG&F@W2*YAfU-9N+d*Dm(O)eQMn+W!_vmDRp>z1}}F*d~9Q!PcJ`bR#S64HUQNo7IaMsO z=b4!MW*+xjM_ii@{9AmdhW%cm&)YRy-5VRTpIx|J;eDpJF|&NrZS zp6(@|d$nEMt!58v-szg{^B7`&COx>_9a#6awzT0uO}vf5?~656<#$gTR@L}kbI=Pk z=k44TX?e|Y`PC~Y)S_F1W@o*dGTk>LY2WdC1=0V?=WYC`vwiKlUwpA&)~a>Q+*@(B z`268>vgSHJ`yQ34sUQ2GU;kgd;*0b79m<#0z};vNftZrnvF^@3X8+oE@sD23zax{B z=hnHP?(MCu7Vr0b{;)`VFGuwDysO)%+&=WKav-euR;e4Wjzw|i5+tTktiw~o+}?%g}B8m)PM-RtaH zDEBz)*l{U~D&-F!en0&Y?|-iF*-Ex2Q&+Vea(h!Y{rOaVtpz>0w@Y@OKE+UbuXyLz zrmK$+ACB0~dqa*>g8f;aczcbT!F{L3ZD-|vO|gA^yL?{a_jz`8C86@-wVS)U_g_A` zZT0URrT(3H;fa5Y+CE#mGxDt7Tpu99Xrirg)?izaso9;+b~k=~?mxCFxlj0v*L0J_ z_01XOr+()KcDGB;iZMK~?V)Vp?}(4D&RL#T{i~f6pkIEegUPkySK;?vF-EA4@t4xH48X?taiNYp*Z(4 z)4|U%>!L5Fo7HwQyjUxt@!@W_dSIhl_{5Np_E#$`cV4d*T9W;wPmJ%FwXRQJ>6Qk8 zwQDZCKDY783yppAq7@yK>R5ig&NO`2{=U${Kj8cgX}9Y>TcUS7tDgQqKkkE#&YAPQ zEnkky?3IiEdE)hk%Ezy)GtFS_nY(-8D>d#qw0znBvuXd~?{yVtH~CeaQoQ=)iHh8Z z2KEP$=c~l(9(wj4{AIfKl%xOmYDb6r#sU86w#F~MXY=iCsQTx7-S+O6&w*llovR*M zJiBPUq+h9E+RAG_~^czAS&g#J?xMAG*dqx4TsRPH@r3)PGSQn;(S7 z{BPN_@03#2Ub~ZTZn@l#RX+GT;MwoFOLt}4ExNAO`uywjzxuHpiw#YJ+BY6QW&7Uz zQ{(oBzd4S|c2DAb{KPkX@q1G-MOMFd4OOS=-f0i_JQh8H)cziL~*mRwogZ+D>PH`}JemphhUSaUt*%fi;Rx`q#GCa`UPR;2Ec zZ_fGR_N5!4F@G0MSeRh9QRQ|HM1<$qI`XN`yU>DUm}p}@DaMaMSnMcuDB_EzaKQHe@4HAUuvV$ zy3;ntuUrxF+x`5c#rKK!{q43-bnd*~lDzTfiqI1WcFdl4sG_O7b9bS+ZE_*UOU@T- zGjnz(%=}VvQ8-5Mn)C|s>l(?nbM9E}Sf9dqS;AxSO|Apl9p86+Hfs79dN|_H7u(-# zH!5EshG?r!?&8bV`?yrtu-~Vfg=bCv*M+A%?{1)>J_Sm=0Y;Ovi+0LDZTxBB`?hcn<&G>i<^#Ic$b&E+i%+9btTspmON!x#y!uzHD!Xi>aJx8i?6TIe6Y_xoD()+}ye6uH$A~wSay5E%cY) zeAAP7_1RjBXN>&QIcBf16>C^~I5Pj!s+phto^Aahvf#H--_O8+e+6#pvXi{+tlmjn z32!TDJ>@O)jct~*-rmXIPk72Z|2y$OE^OZsRYvwJTuV)j+~&U6Y4P%Gz`BQ~a+Xs* zTld%Uta|yjuBM)Uz1N+B*dy+gUtk6)gThgGXHjmgj5OX*9muTg<3rPan|DRyy(JbFQW@?os=%Pb8YI44QwyAaGT8V*55Gi zLmi*Qo9P|19d{-))L#7bqME7Ys;_^=(GLENInfEt%(~fH+743=tQMJf|LxQnb9D`E zgOlEuG``N)D}2k#x^Cyb{@3|W?z5J?)!dk0-?@`@U;K7;Ux%fOmzdvP;Pu>tYV*?*27n{`EWgk*0=SLGpgB$JR+& zX@?~i-F4GB_s;$1`-S^=D10a{I-Dj`JD)xAbR@r9`qeB;^OIWB9ak+_d+hCo6-zQ7 z-ST(jk6Rxsd!zliZ@cg@{?`fXeyYBo&bjP@#g03?ZR`&6)_)fNnEtS0_J-b!*>>9( z>CF1|TZw(=AHU^$7+%M4_20U!usY_Ir0mn3Pp*Uqg;(vlt;KA=rIFXrudcDmRq^c3 zNS`aSy4~Zh%3q!M-tb4;>)fZEE)NWpI^P;TxpDRH`TLU2#@F%|Ue4=!eeboE*!uM| zf4`gb{b$y@%C6fVWoq1RXRPVZw?CY+yERl;*602nX8np|lQ*Vc*HvDer*_PG#{>C) zEd5n4W_quy2dy3i;Y$m(VM~g3FU-5US$_S$*W4c-`v2Q*@%pY{)rz8s=Y?D>c0KMA z(%Feci+Vq9R15PlGqjtQoMm+UiA&$dk8HPc(;WCGJY_bS%{6aUrKexpnm~>hr@^B9=NC%oUsw0rsQ8@tyZITNF}plqY{gzGooY@X0lRl16=`Sxy^Cvt38 z6KmS023z{sCCc>I-QfE3Y4Wv4GZq`0c$r;DeYm*MEiysjLH~xit-?;8R!^iQOd`HK zI1p{VGE`_CG}3|*Qg)TTD=^__h*%7s@t<<$H;cHd7WUpygYC+2fh?`rpY zDUJ!x+sod{K3J9Q9rLSMZ{P2~tU0^84ECvCpK*QLwfG0?!cG{SzaTaL$g5=k1HUHw zAMd@tpQU(rxhIcjx1gE&+`eS_3cfJ;T7K4@W^RurnLFtB3!P7~%S`^n_aym++&N3D z`@4ls?Vfhvr^K@U3qcm~jaB;JUfRCW)?Jk3a<(bH@=-GTKEJscs_kKohYH&!O?15+ zkQBsUXxMV;)|Z(DQ3q?)3hZjO-&}4Yb?o{?gWIuCcOz@Z1xreTxjHHBWM9Dv9@t`=c-?d%Mw^n41Sb9$$aJlK*zg^1qkV=2XS?U2=iF8SzV`uBGIxKzs8+%h-&b5PRfQ=<92u``X|&wD3O?99HG^{#;5 zt^2#U(+bQ3%!+ur*t#6`e(SNfv~&I7e6!iHGwv7kQ zuHF56)1iewG7nc+$~w>MoveL5;cVgZ2P-b0KlDrZUFT{0&x&t$d`a4(rnd3r zm(8mC((Zm-zUX33W3uomX&sBs%lC_(dW)&7x>KoHR#3mq=wO~h?FR4VtNGr&N@ZH5 zI?>E-uG^Dy?bCDqyvpzW^la^U=5UdgMJ?-}t=PxomVTrE&ihX@zfMW2uomy$xv=?O z?%$*(=ErwhUt`_uZMOIMg=ii=Zo@Mh)EBImtq(0YwL{9q?zo-kQiorYdYcaKx$yX_ ziM}L@rjUdQ|Jkh4(*Ea9v(NZCH`Hk7b1ZnjWfs%Rw>)ocwhC^te)e-`OvV0p&3(TD zB1|LRvvM7(`X_K{y+n5M(FpHjkKUiJXSJ(6_&e~Eb#jJY(HXga2QGi?EZ=WpH4Q!| zbbApjnkRjj+FjRheg9YGAK$*$>-_w({c7O+lHVfC0Zr@DCa>nTy0N2>Yx&%5oX_n( zvDm!iR8Kl9CA2M)hwB^PjH3(QEN--{m5Im^n0wRd=;{?BnKoK{anH63AGy5h^bYB& zTax9;1(R2c>+CxAn~@{HJ<~Vxro`k6wwFC-86976=Aw3S<2O~4Rm-^cJbd0TbEaM1 zzMb8vBYxh6IqU6oHb2lH9@+7amp7{3T@`S^h z=}B)3m!G&_x3;SI_s75e`=qn`_pA5qynpzDjP&H5LjBASDmLfbHXZ*fsu>aCsnWNu z)2~XTcOO*5T#K#HF zk&L%D?Q&ndbPtR0HPM!~onbm1QZKS>_~!nunxi97aP(5d=1#xOongC0W&XzHEU+n` zKdD1T-{pm?r3*F@g$%4sp59Gd34~i#(oKv!~lHy`ikyP3k4zsl6Jf<)oh0P02YG zZFIR_uz6EAo7mQi+Ph4Sc(1EVK9JEX)9Wby*HO6FG5*fmSJ%{ZmOKvod_npb9aS9iPn+Or=uO`r3FG4``+S{nD8ySp2r^CFLYTb*9;_3H0} zx248AQlHy=JOAZEtWIj4=d=BbPtH%vnBECn3&2+###&y(9J#sm+Ygm6nNpK$b1ST_ z%`LH7)_1S#+!3zccN=$zwqAYqD$KjO;q)fE1Mh@iPgvk_?~&ggj)(8Li+0E`|61r- zAMtRH%aIHdpEo|&S1exm{LhXWnJkMtZC8(({aa&yJh#jHZnxsX@6M)ui!?r^-B=r6 zdF5}x*O{stwYMg(?bDU%K61<8T${ta1E&9*p*>e((9xwvFv-WxmwqaQ0d@6UoBI6O-nO-O|x| zTDc-S!>I3ExTW*4?JZX0O{TRsZYF z^2GP=qTSu@`t+55__uvOLx1g&+P)|2-@)?rVMMl`mQnS7jmhNN`|kUjy!H2s85?tR zUc2wPN@1yckja*xi(S(NI=0^S_3_zaV0ZD*tkC3$-RqjSW?M&WU)K5c_3en=YKPml zS}qGqn<4dnvGKJ-70VqqUv*mTy6!lK6w`OPU(IGxsqwNOSm)=T;^J)j-ZWwF0^xm~ zi{>Y4F`WpoN-T9+0Xa)?WRvh z7$2|xvUqC$-ct_WJ*M7xyl(R0aG{XDtTqOkmo7Yiv{onlxuhL`>6X{_M{erfe7%#Q zQd8yxw-n>!_m{U{?~grxQ274hw)KDJnB{NSA@%oex6rKOFJ z2)9ae+`@_w!+ZDj*)-!dZGz(aW0Th}7dUh}_?pEv=_5jSKAWCh zaJ^!7nDJz$0}-n`S8nWk4*9MzZvd%dOofrZn}2mtHtME*S?wd^~Wps`2OD4 z;e8qpRbb-{y31Lhv-y+wR!HaHQ||j!|ND=5;mM`;$L771EU69^`*`L1b#@@Y>wb-GW&X^Xe&!{mcrR^PM()0Wq0UE^1)Qw`(Kw)v{-TDc@ueqHyUd&s?O)8ox)!f#J%`s7-PW&2x*X8YU72G8zu zEUpQ%+mKS@oYi&gl1G=t;_Nwo?&3yq3nO^CAS!z&gsmuN@_VBvW{)~@jzV8SS>QBz?sX3!-w(M4g?3?>>4(qSqne*bB z$g~4dvTwMauRfl&)Kx~*SZAhG+=MQLYS!P{2mINs&u|K9Y{k>Pch1bPKW`zGE8^W6Z8EK~vh%^jP-d;OE3Q9IKEnIZe!nrtOI4@k zSC5=BEWL2L_}YqZ@8iA-)jUj=cMB|d>KALX?fBek`#sO!{}YilV|@N<&ca7}Gk)_vl9(Bk6=iK*c>iP$(a&H^h+VDRoxJpV}hz0Z0v#aBaLlu6ou zzTC~$CG6EU$%}8r{FSH8Sf;=1%P%`-u&=Cn$K&hsDx|{lZ3Q1^n{i0+iWPXt9J2~v z`D2g!!H-(Y?|azZSGnlGR=TscamA|@VR5VVUOs$#blzj;yXxi7Y(<#gYyDH1^~g+Z zu?f$;&(md9ls$k!PsogeQhTbzrQz94zrzmd*;a8 z_}u8QTN@OvpO|P~+;h?Q{uQ^4g~5?x6Pt|hb5)5*AA41pmFR!$P!~(j_J@YvM|?MT zZY;aZu=m6pdOnBqQ4`yc`TKgum*3ML9+S>La^_6km+IOnC4$mB z%J0tnP&mbn$&o$incDeHHYowYe|%QoJ$B=W%CfNa2Y;P)fBa(Y@rO5r{SOu{pVzmz z-`X+uwVUA8#?>sn3O#&Zb{^H+7XMSZ=IdnngQ?=N9ecOmYpUD7{fOh-66w(9k7pj9 zo3P#bh0oe!rxtHdI$O9r;cMaYgrdV~TaFzQt*P3xYuUvs47Ty>oxFIKS?8{MdT@1j z-0fbzojY{hTxL&nE3|VCs9Pfak74HL@@KY63HKiRO=Em|`q1YeJ?;$Oq;`C_5WVZk zdT?>Y!i(lwn}07BmD(3{SoTZd(oBzbmKSd;G)^XMSKA<`?JV*3KKrwU*Y~n`rQ|{tn_ogpt z+U(P-TAa6;F$tY`ygYTk$Ke|*IM=#8e^Ff1D==^7x00v*hIYqezUvmg%RX|>H$1*I z{O>hmgU#htDOb+E3f2Cw@%vu3<#S7V*1bE)mXTWWHaqw(tlnC(OK5>VWRT1%B{lfC za(wlF)%WThF)zLv+TK69Ztw4ne`jTHti3(;$3^~q`WJp3d%gbnzL$3+X3TdlFARJn z*i{!lTSw?Y^fGfr!G-(GZ?Bm!*{NjF28MMUH(vZmDJ!}sVxHNp_x)qxni)}@ohzr# zh}z8;y!u+g)nB|3d+U_L)<^eG&Ay(P^i-?Icb&`byVEp8to%9Wy~q>g+)#R8<$mvN zI~MM++kA)fW|z=iJ9%!A9%sXdo9{~tABWl>N)@jYyuI%)uh08EoV8Z3l{sdbI2m3s zmGDouN&Iu=+1iNO{>dA%UU$y8vx9Z*$IcCmrR%~KYCQ!{B&TU@iuvwxeCHcegEtf6 zOcQiEYA#)<{-VJ5D~K`W!}I=zkL-E@mFs@W21oEJ6<2BsiAGcll>b_B{^wf5KeGybmwAVrbRy?3?Q1yGgMu71x#W7ER)5_js_c z?)`__M_%eL)}IxRZhd&+cAwv?@`VWtZ%^E>8egYyUplVm!skGT_t(!XPW&RV#c|Dk z|CleA9!$MoD^)BUFI@Wd^>@+qoE>&E&!=aLygts}`u>(dSpUYeX&ZHSz5W^3eJ$~f z?=;RG{~xvAZ?CVr{PSV8joAAClFMcFC)8yvQhx4q&fnZNIbUcAG_t)`!G|W6I8Wa3 zd_Kby?P+`JH^wKNuMs!ywmnl;9q~f+#nhJTs=Hh}?TXKxW{TZsX5WAGr{$URXOrs6 z#5Vu_mX!A2PUp;7&KqfWSY~T-&73rY%{8uabLYp8g+6N{8b!6k6Q5q0a_GlNQ3+}J z!%v>5Sj{z)iWDxsJ5R>j|J&*vQv~ig=A8&tuP{Hp?zy@4Z1eDvFV*`m{CQCqnY8ix zboXc5HeXcj=CX-9zglejn#Ws?G3+jn>Ce6YM?a?U=&MD&G)`^g@k$j*IK>8Q|_PBBTmQXm;Tdlyt3W#-K_ORr`-=!=H>5v zo-BJPUHka9=I71klSF4~&rO$}wam*grzH8IYI@>vvHqt;u8e!tMJJrD&PhHKdVPiT z@f%SQ7u8lRG8?;n%)ps=QCEFXh+o)ARfP zB4zao1$}$dD_`|*f4oysx>Nhj`9o)}?{a4LS^oXdq@L!t4Hse+_dAvUJU2;gi@}TY z8!!Hlu8n(WD}ADj_(&H*03vW&OD` zL4yq-Tpo|8LOo6`?{hahWa}^fbKXzaH}P*0_l2K$d?!i%ZcyVktJ9`jZT2k@q6*<0 zYU@ANmZ`j|SlhGe)fbf;fhXKNmNaYPORW-`gtmG^_$bu{#sLlKZLs0e|h%2;PN-^4R0rP z7rafqf9%Uy>kSzP>`dnTnSZV3>$i*(OV>Il2W+|OR(;fKjYSrBm~6FDm~1tZnz>x> z#?Wj-gMt?`-&IYVHtDw2i*-f^T%96p`S+dT??0R+v+vj>NkOTz$6lLj73lqz6%BsO z+O#5I!yGrKU(P@7928;rYb3?Gac_iR!%L%g-xD{yuYWCL{-f3)^5LoV&3B5gtydIL z<6D~W^tgm+fNY;)5$p6FZ=)6WS{{{rpCqWasjB_JEl1lQ+v~!A?2%x7;e1EXalh!x zRke}(_8z%$VdwgF<$Fb?f*xEBWr^bC@GG~TT`=og?4tY1_a1C5I=1@o-F=t$$fbUF zFbnv;_dvZ_Uz%}*Q)S_s&6+uR?`rxw&APs_ZQ{CIYTxqd&UUk&vrnqa6YrbE{hYJ> z(KPAuo;jE2`f;zXKU)0p%yN4!%j1*us+zxOzJpDou7uB_da_^naxXHb{&Sv8^)dbA zzt5_#-18Ryxvy1jnfR_pv+nP{aP;0!J+WW4osESW{^eB-pN{$eHSKTfEZ!T-xBci| z$-|mfmS_L2T6Zqyp7(Y;Mq6%Hz6(bs^^Y>%UFAM;f>rAxhc8kKZMykvyVpIe=6LZ` z=Ebb-PE8`xJnJ}3gkL=6OJe=}Xz}#xB6&vzLVkPkEqVE9t^H0m^O#D>vY3y~Z+1PD zI`igc@4Z8N)@X1r6idX&aho4w)lv#_CC{%OpjMz_jLQyb@q6|(`kLe;Ba`gC%qftzUk8YJ?jM}Vxea&YAnS)7ucaobSnR(5k}{}w~{z4IHd zcHQZeTQ>3T{W_V0E6=^{w!Sy#&ooB=6?(Q-I`^`71TC7d@6^KY#?v-#6EMgvTK#-& z`+8T_zw^H*UHJJ-o8!-Jue+ajidpgQ5`~hV)-()5ButCG~d-D^LPAP6aFZbW8=$!a1S~6 z(=XPsR7LM;Za3fYJ3MwtpjdH1|0?dO6FTyXJdW&--g#)t(K^k@A5trwm+yY7&z9Ku z^Huk|d+#r)`fJ2b`*b2VL#NN`X{Ft)6ydp(Qxh{Dd-isJD3E5K@o$zx)$RFD?#>pD z4ilH~d2D#OKmObA88h!S|9lyHqxkaI8T&r6z1jU#?altDX=mQvR_^<~=(g*{_pA-) zo4es#Uk++sbx)!%cumbLJvoO5vv)7z6%C8GC=JSS?)bHfNu2{KE$U zm$&Uv$v=9qs^O(_!f&DU{n;I-pJuBwpFfS zyDM(|7HIt5qR0)L-XDOHd?cM&U@O*`C-KON}TcqBUSXG=!mh0w^D`+%HnH%{gdrPll)uEp^ z^X?lxKmEY3|8WjSugE;@A9{C6t)Hx4%P{Nst&opqHN6FK4wGEX0!lt#cJx2t`{3w) z*=a&;r|o!Fd@9}7Z{PB9(qw)6)*VmoetfU@^H=iG+^wZL%IarK-yLObZnzZ_^m(Bs zYw9hZDHd~&KRK@bpx;`oeO>zF*C#Tb^x5)xeW{8qnfY*ky}sDD_lNJyi=8ohpD1_r z@{Ja|63)3clZ<}AbVjXj_!ujf@5)Ar(T}oKw;!U3X zx#qUNYT~O2yNVvPzwPVVXvoX_YX4*9VD zsEwZbdygFBmN`i~-hZl8Z&zTo-W#ske(zbQIO7xBO;g+sr}12Vy{v-A>-bYsdnNr7 z0#?~(yG3WTg?k<8jdc$b`SR%K^{!wqnQx*O*GqY|Gc{G;(Y<`{z`jGdx&k{k+imw^ z3U6Hzy?2*t&6__z-kv`ApnmVd4K?YvOrDjuoG+c*SLW^}`K3xGYQcrS$`yR&^(%cq?Uf4DBBXG~GO_u=>cM){i8Nx8plRr21?Tb^_D_`Tm!Yhq0m zf4@x8F;Wq)1@*#%f`TUfNq_t28^7GG|H_bVgx9XYU8Dw-}mb8 z`!YZNm_mNi`8$hD+Ei!EkG!&M&6(QwqAee|GG95wFv>hmW?E5}V6{X*K5?TNOK8cd z>L zYT+$ zw#CsRhfDT9&s(3pkL_9RpT8SV70D~z^e%d;9k5U3n%cS)37zThInKpv-{M@~_lu!L zRCC)JH@U?x6|diXoO&j5Jzur#Q?66L;}5Uqy3%WJZv9{&Bic-{P>8e4hO6VZ!9CHddBDxUhX+pAACYVVWp&E^}@ zE;b*3aP#{;mT9c6>pYYueBV}^LZ_gCi`?s%5I+g4|W@ZH6!kJRRuoDseyd?<5E_s)}xWvzL_+E?v6FefQr;Pm<5 z?_2y?6L*_VFHLxNVAk*AKKF1d^W)cd^=x?geWQk;*{wHQb>3Wbn9W>i@0Q7u-J5o_ zQ)=gj*siVIKZ`pYyJVhaDSH<0J5{E2e9u1rL>q2K#xJ{=!#bli@-hy`_CJ{Df4lhB zmTRBQliXKNaqC%UtuoE9`|Wkb?&$T09p}o3>aV?X?65%iCNrIR%5qyDtm@Y?UHo9r z(JmeKH?l9<{^Um|20pF7{+r-jG3RVmU3dA|wWTehB8g{Rw&X5;R^%~tb4;FV z_u+|!o!UWV^)D@N>~#IUYh9h@>PL<07h67F`rXlLBj4O;+WNFIZQ-E=-errKrq}dW zw(VVO6fh}1^=!LRYwgb;`IxB>1TEvPJ^JqCc0)eqXH><< ztISab8z1K~yf(`{)cyaDZpByme`ep_&y8bxxpS|6-B;_1=ezBD@7KTkA6~nk_i8n4 zx#zn+FFxq$DNAO3JAdx6;{G2-F{NjHMI+LlIPUJP-}%6~;(%rUv2{0D3ojU7YV(bJ zUggSLCG1rB&1Ru`>?_8G?%=tRub-_qoxbIGoQK*b*@^CVEgIIw%#%8)P-uDW!o`QC zN0k~)Dz_YB_&+V@m)-2+(_TF?m(cDj>7Hx&zv8JB|IM#)0T(KyQlEY8;5;Rn@n%sU z&wef;<|$V`@RT23HK*hC?Ja+{&gcJiCMU^ntu*JO7^J z$vu5>nyA>!1O6|$6=vR-GflbfyXM5MD~>Uj0_~alHlGvh^-4OZ{zXHBE3@Ow@qiww zmk0XUs$H(8Svu5j=$Jk6-K&EAGFESS-fub2bGR;c@1pNXHESNu()oJ)da8BD^V5wY zT@hF33cPaTdltE8(&4N1S8nE&ELhmN;m(wQesjYYb7efVd|xYg_^05i-c5mp=gjX< zxBgknV`}GC{6#g*E$rWg+I4zW=b}5eC&u{jo?E>;KASU2=EB_U#6`P0*B)@Zl*ek3 zdr0*Mwf?_frKb4_AA48?1F(_pZvUb1H{S@6JTEy(SY%#clu3y>RN{qx4NO zbJN=Vw2xo6|E{^` zdMkhY>h-rVOyTD&KKt0b5PY7r{9tAKr!)D>jbjs*G8PxAdZ)0e+>VW%ajnGEW&a9} zS*8q5jgxmgv=y#g@Zn0bgoc!3>g|b#BWG{U3T%j;q_$OKslkRDuep8Re&x>Q@P3f> zF7kBK_h;+R<*d!s?sAIVJ4-k}A z-HX4$wf2`p*MGDv%m1BUHu33(hy{=ARKm`rB~A%EpS!74@Bhl^@Jz-)?sl68K}L^C zOPj=0e%eky`FDcwlh9!KK;_P?&nFrJ+GVeIVY3}KHK2dge!$Y_3TkJ$6x>P5W_%U?@+etY6sm$cTB)R}$US zSSPj}u)P`dq^UgP^^~Aa)_zv?6y5E_q-tRP`9$(?7hpI zA3s@8$oRVjjD8&BsuOvloOK0+?64ds)HgQ6qnPL)c5pz>l7fAMCyO z`omre4QYudbIetZe%d_WSr>Qq?Y1bl1A86+Wz1{IR*LVr6%a1p;RntDdtNhG$>}_R!Ue-o~#+Se5=^k%6?< zcYV->XA_n8-M#Vmk=DP=WF^83YU!5I$=i|bks}8T^ zx>XtCU;3hKPnl8I&X7lJ>q?`%~2?&%;SFvI0WwQ)rMqJnEH=YQEz!Ct9)nw#;( zalVB1!cz9yzY=E}b=V~WGM-H1OAjo*ex0F)?_84L^Cx>ugXYD)Ss4G~_w>uTjUV~8J1ZVt`N^ozyeYm?-c4I3 zWt)paeBasUYrjiy++Hm%*2eh9@igB<=D0N*LXFh=YrEdR{UNtXD~WAxuf%U#G5_ej zkAJe|E?Tu*pz+m@1m?=HAG*y6tKDl-yRSqj{jk-E-MaAmN{jPr0|eyuK1%a6(YgO| z_X*=Ke}9;0tH*^eSy%b_+3D|I48LXEeqP%6L$2_{YcuZueqCM8K`kHGM?^Lz{rtSk zJGN0MK6Iv3+>3yh_pJ;o=KZ-VT@l=Ks`6#_x#NfK=-7RG$iMLRTesCMY%jk4viN^i z{)n)AY5baK+rkEGMOb0<4!*N{l1u!~7wkU{h3oV7hG{m<`1rB#&&AX6tuI^i8a|8m zTz?_%^}y`z=V>?JDnvin+mPQn^IzOlr)>;dl_S)5Tx(eEtl#){tAEyKXEA1p)WhK^^AjtC@9!wGD1Pal_a!i()S_>ROxcvzlCO8VOLu&J zGL`j~@j{IS^FOoyU(vtNeErm8o6F`Ux~%RGWo^*vImA$ZH#l1HFV~zXH@{dv?c~{c zAD(H=SsXprVBWef8^eO{d@MP3S<$~@o#dUKHJJ+26LQWzT=v6mZZ#KQC0ohX&$EQS ze4fZKf5V)DfTZ3x6Kdyu2tK=P-?ekamhp1T^2d30^_v#ooM!R*XU(zeibt6>MNF3N zQFv|kNoTsmazE?IQk#gozLQ(J`|mzl>LfDRxzxJDVv75$Ww*Z1s+$~=ynt)5 z;G=X6Ki|7+?j65c@q0#uPLHe3#}6f8I4j`>ihLlb&|mQ_1Ix>wLl_TC2m9%dRYa|9sb$ElQm+lN3^)3R%0wWgbeReEV(V2@8YjlzfT5@7e$6v*dcb{T%#Ue(||uKi*II$fu@$d_&Pui!eXmgsdR> z>+9wnIrepR;gz<~8ym`0udSOo={%%h{JREz+?`6{oc(|F?G*m2TZ#Se|G2Zi*5+N| zuf3|*)IP{|FA(I@J6Fj%X`c1_Jr^(RHFb);t<7^l0fYfBQwJ=Dg3fGXWmO zk1x6JdcnQrjPLm-=I!S9@=j)TZ%i|GNRg}G9$;>G-+5}YPnd^5SJBpnJMul(GR>Eo zA39ZN^+xo@`O@kcif>+JHF~O_3QDMo7GeJRW7f~F-xft2xN`N$#~6{@v3FzT<-;vp z^zE)bn|aJn)8V>wj6>~{E8nEO88#fXdiiQX)^*cs_94eF*st~wZ~ScJ|K?&t)r{Bk z?#k8QZ#lTJ{}tPz^67ta^J*=bldpc9@oQ(`rFT<{Pu){oo~Hg}wcQS@Bi@2SO{yPa zrKNAb+j?-LbM@+k6YKuUD^9<^&w452+P9%{CCB$MdG?*{^NxAn_N{blcfaNAL$B0Msc_x69n=j)&TpZfptTcsV5d((LTJvEL$bmf@uLEG~u_?+^Y ze!FH1c!wJ%Dm!W@E@}Fu@lCDi+TxIn9^GCB(^nT+m)7n2d};0ism#S^sJym+#w!F)InmO#I%rV!|Ci!7~4zts9Eg=vsa%S(sf{qjz9- zz#P3wC!5kEuZ!PJdG>x!7|Tx~*U8zBea=q1-sF3F(}SmbnQj#~R`=`@e2~{KmA?2u z?UdDZC)TVzEFDyBVs_-Wz>h}^onf2a3R;{h+uU~K!G|3d_bu5n;=|c6E6?VmE-{&&i<1DG2>hU$z=*ym1k@klNPv(99`N6V1TVmgww*KY&R=={= znfKtL;jN3>Gvu<~?0FLLIz7+P=xW7`+t<0}tkOd3_E??%dc1zRsLDm#i)UtSn!If7 ztqGy3Y)3>t-1ZX8?#=#b9r|#=s)aA6e)_!ay=`vU+WC{BIz>)>>DBT0`A6)A-s)0d=Oht-dD+>XwU_P**7U9p`eF6Hb?V${CgpPZPiN$qA3il>=S;~zhi~6+ z`~Uk~{gJiX?{(Gf*FXNH`h2C$y)UPp-#M4RNICpnh`Pu8`R}_v&v`JX-*_|kBcJ>G z<7Ho$ta?9x?)2whzq59(ntgA%LS0#&;*P7_|9;Q@nD6Xh8^>90a5Zf0pB{}G{##t@ z%69j5c5<$X+R7zuo_FNd*6h@+3e{J7N{i;yuGjssHh!Od)LM%zdf+z1%MY5C(A8w` zp55A=FP2|(+nuH4_@n<5|4vP}Wq)m!FK8jJK1*OhRZNf52d!NVLB~YRwbll&Pi&Fp zS^ubFZO4qJjNAKmteyGZmpy)ApxLy~Xa5KLDroUqr_QWlS$nke{E5yL3s)aMdBoyU z>;ch#M!il;`rbai{-{`H+b=1>Z8DYlN-vMLCq6g)-?uoLWu4%c%FlOh?OXMCSMttf zHzwK~n)>#`z2e91sxs#tey1$&4egOoXi;9Fyrp|_wv@nji=}?0vS-UC^G3ZaQcux; zYAj^Q>~^Xmr?;uBe0I0=rI?)?Zb+Z=>ewvdXv_M{q}AFi75pD1s{igwdHcOe z^%LL2J?Cyau3H^iG3Qc1SeGEn7fG(nX}7055AP{m!&n@jRPao7*UC68D}gm-9`Ezg zr!RiNb@sPt(9f;wo^{?o`Hq8M7W-R`4xngKtQJ-{dG1rp=hbJ{ z?5%nqR-Wzb#4W_6C^5mo!^uf#w?Rypv~*9dfuLN@fp5JAX7i-w?i(0hFW=Mfw6WSZ z!QdFTTAIzb2HrzDZZ~wqb~`Yw-FtoB%&EVPKGyyJKljh;FDI9-+PrGhrhD!aPpyja zn^#=)a?SJl^S|e>eH0#lX_CC(w`k>7&3!v$GEX|r7v0A9#=&HrkoLs`jq%R=9n}Bm zuZ{g$Yj#xZ zcINlOvv)`|`$(7^`pxm)_R75l*X7y_xcj)SU zR*yg3*eKiaE=23?rEhkY`6qve&X4@|v+?>WdnOnC%O}%6#(iqKSHAD@);Dp+dY3Z^c9oMpeRV!T9SIq32Z#bjvdqK$mN9_G~FUICENY3w9`1M^gY_Zs~ zE5ht?p7%NIwclolUon1rcXxQ7ob4)gmAB!w^VlBMU0;`3`SA06-TyC-UiaR->CKVa zy?3^MRc;a3WV~R3dW(QlnSuSEkDNQdUVVS@-_3wu5Bo2d|9`(<{r}7Kduv`+hE&a1 zaNJVQ`6%xzix}5-;V%>TOxI2;bnCRrxTM`eBSnu z|NQmT$LaC$%!cn>_bz=GTK9v!OJz^zfv^SlzEnD0x_)tzYTeo9sr^}tzizr3bmjid zyKeINrAgbj`sLZ~J=VYL@7sSnWF|j!Tm4Qu@6Ia=!#h#G6)xS@^t;K%!R51H7r*dF z-@e!9UraVPe7`Cypm$=I)swI3_qX=BU-!$3|J*qLtC8>S+f$}(TRkzYyj0HbiDTbx zFB#$gw{qVmp9%DBj9<3;Od&JFLNBw0qDz}W@HAK| z#uv^#A9rQ;2K~_2TAzJCxvsadUnDsF^Xl(`tC^1qeO~>fcF)pf?bic-oZIMD9DeeO z*8}(U7cvSo?82RM40JAiulxV|d%N_0%hefbN;T~3no}Q!Gc0%lrT9x3$~%N=ryg{B>yil5KyQ z%jDfYdWZaed>prV)FIhU{gvcb>=K>$hE^1zSRk8l+o`mBM z)~{|XbT7QQpV@Ygtj6{2is`j!Y$-;PJAXc#{q)1b!?D}X?9D52@%i@lw(|O#n`?`H z_xcAF-d~~DZ4zbL)FR;I!NF>)*dnmWx$#Wp(er!5?S3!KUoTdxtX%YSYWOMj_dge1 zaW~Rpj&6zzky{s%{jTV3eZCuKmHOkmC$rLGk}PJF7^Epd; zwomaq%S&H(UHM`4>eAmcebN-^cfVe2ecZZZuh`efjn~sA%-QyQud3YL>fwKlhj=lqhz%6@N7aIBto@%e@I zOZJAp$>@EtSH6~Q2fy8&bG83=CECdHeR%y?Wv{f%hFQxp7Wk-_mrb^M{P*pyXU4aD z`#UFntZY23_-f<1ACfC8eJ=4B+PB_*b>u>x0K@5{Pk*>>zt4ZB*F-wV=3C@R{rLeK z`}|5vmYvsqvgKE5*}b_6x1MihQhTpvsXHZfUAyqKU9*g>7oFl2X#cJHZ)cXl+I@RF zPM3J^WuALqan|R9XIq=TEBJB9sm#6L-hcY-)+AAjr$%hmTvhj%+rG_=YTO+6Vb_tO zjd|^SZzj*Ld&~WFrvJ_Z-M`m(6qg=VP;8knm)-gWr((+l*;Dp4-=sest^fTmveV(u z!e4#=|42`gl)e5gdV!{&Z;#c>i4hwg?%lB7`I^Xb&2$dQ>5KAb+Wyu&QLU{uyKv{X zGv+aKd4rBgMU?&je6sNM{_Vei9gmSc*4wdM-Z$WzkAUtED7<<^)ZXmtX}iINP7MJ45O&M@=um}8#re?@(_CaKW|aG z-P%pEorxV^HU|3Sb=R^ljPI}duyN-N^A$S|?pmIGLr=ZHa7DnnWhq5A%1^Um(^e&0 zMTD*~QHfu5!fUCL>zC-?Rx;m%4i-QEHhuEOb@4V8;y;uv|EMhfwdixvz5VXN5?{FP ziKMQ;tYR?mBIBtB&I?QXNi&27gYeKmW>D=6)59ewe(+P1sv zGURtWy=fNpcT)BHM;bieRa`b*SpQ(vE2*`U3g0U)xwQ6|+ta=CqZNG~C+EiQ-(BC^ zChX1LTvOHJ{^2Ia^Zfa@%UC+ND*xXyuw4JXdJo%MTk9sz#JL|#IQze^^<$aa)FGZ3 zaym#OY-!xu6G>Zjrg}xrdOB%}w`=6dG}Y;+z4UhLXz$$f1mlgr16MQQ18Yg?p|F->kA!yDytI=L){oZ#kKb6n_ zwKC)x- zC%gVv+BI9WZ?kd!F$ytj?=1H=TFsxkPUvlwwZ8F}v~}5wLOGL9cfMJuETeDX9l*Bz zz-O*XS+yC8t%1p#^NXBzKfS2hdoo6Gp5obw(^~nDzKtp0CjQa)`{r*aR`--mx_xzb zPlw-Mu8r+S{(1x-3)*qS_W{>Oo3`IK-p4J;zIFHeiRkdX!gtj!e2j>TNsbPi{`AM4 z?c&So{v4WD%u!#vFXvH$?12rhl;iy$uRomoQROf9sk8QpdtY6Za(cy_RhF4DS3m4voMJFhZmsdOSA5o)d!_&UKE9XbZS14CJ7MQV zYQv7)-+u9Rn04K`c|Ri5zm>W>xSakL>iBKrhN$VkkAHbBv98>s#TZqJnmDbci{`Cb5w~i}(mqS!tWfjL z%)Mf@e#r+;?Y$q>JmoHn#nB(vlHQ*VShd}Im3n8X_X_6;(O!2ay<8a3-(7Nt|9hwX zH9wa5|0OnPiFI$C;Bm*4pg+@23BM{r`_{<+;n}E+4T~Y?<(|g|o_uLotQ@ z)4lKYL2`9(&O`>WWNx`%asI#B%QG5l7QU1G9IRLQl4VcayWPhMwWoh7ndB9^nE!cJ zlliQe%S-JI17Br*ea^|owdU9178`?$@+V)t__jKA_ti`d!_0TG)%{ya7QR+=>yls0 zpLfvj-p{Ka_q${Z`JZTCbT90*(EDw&*Rth&K1i-S7;umM(1j!Wu5ZYj{P*iJ<+mS$ zl=V3KSAX~EIl1ueWvyq5Ph+jFojhFF;XJ=<`;$GPhkKS?(%N9E`TptBvT)_MuY+&D zD~xIF{dzjvN^Yt|+*hTU-M14el`|)nTwc0wqFKL-@8*@SmxjviTa?B$Io>PynA@dw zamP-VpST{TZ}ZAHW-WWrmnhB2g__lC#V>V6U)H+yG3wxgM&5)OcaD8tGw-Io`MW#( zZ!5m{J>BidtUkBY;NaPJ1=VbZb!}9 zcYpb=rM`ad;d79EyYb45m0vZ^zl)zdEw6uxRhUq+(29c{yo+AEKJ(1%!|^#=;-0PL zQr$iGcTUOrT3fCo^>R#m-hG^r7jXJyneWoq=c7uL7kiw^R^Ppw$Gz-t>RNU``|7H5FBiIN-Mr{^kv-!#qi@&Og9@Gn?VY9G%U`D)vFnxVDw+IpVZ!$L zZ@-0!CO@CXwLB)?e#PE{f~w2?=1xjJ-j})5kC%C^na8c1S6|ov-}zrhzy7B8lm7b8 zt*`41oH!Jjc_Gc>!$1BtAAeF`_hz#eqw@#%Z@uPsRnAxcdmFTB`=?E22je3qCr+O^ zrTvYwE?dg1EalfPubqxE6`00j&M3At@2j0((L?^ACC$36lAVsRe%?Lparc~dKWuee zQTw&**Q@EF+#ezy1;^%@NIW#18g02(b=%3m5=XXP{o|Tyw6E#7@LC^*U3<>-K55hS za$WAdbJty=kXso_+oy_{M=yW*X2tfqwrMX+eO3JW)1Ew7r+L%Xa@i;8sb4SAQ=x4cnUZ_dwMD#LOgpJ;l#| z7c{J%TiDONaP9L6GkmYdzFl^uB=!fFR@#PrbwY7I%T|m3HmaUGNBFzP&g)z3gr;5I zz$N4@YMt5t)=jkc+x68f@)ujIgU(HOxnuq0Pw(n><-FZpKIz^$XUjBwsk^>f8566v z`Tc3*iu766yg{5!(HjrxNjywPFsa&s5q;6mR z-P>PR?jRy>im%nL^!C+ecwN3-Ao}T7U16wOUjEj5Yq!OhHeJ*B`7S;+ zZ{NddE51MWxRIsfEzd9dzD~jWzE_>)uY%RL7vE_JGvRrd|A1ZT?~Qiv9nsHAH~9UE z@_pDLV*kGB{oLcb5)63e&#;=$+_G}%%i2lzY<2TeS3R8+(p{o7(ENW_c?viS@H9~k_*nyv;B1M|F`?GtFHMw&E4e!TMLku z_R#lwD(}x5UZ?UqfGlcPME zq$4(6FWR*){8-SQ#L_n5s=5h^oATD_-#eM(?fm=J+znlf>)RFP-uF6M-S}L)IBNT6 z&iHc2FIk(u1|>0uOy}dvza7HJyQr>e|N7EuT_t(eNxt`fw>MXO?X2qw8hl%Nr(6B-(E9)j`^NhC3J83t*yUr*&o-t_|Bzz?Lx-V7b#P3 zS)4j#yUx1j(B1p`-oLLI*Uhc5w|cT$d~M$5IRWmoBr=X$UwZy;2uzfEj}4&OBv z*=Z&x(osBr*Y#5Ou2}7~JF73dtX5li@z|ru7wsz6iznQ^bi=4x^EjK+ou}_SgxCJR zxMiPj*>0=UweOAJh}(-?{?e|HvS-S+@Pf^1O}1@zzeCg(KazFlNvNN~%FIzA=Mpqk zaAmg8?sc`_r$#xe|6Xf0E2^`3mCO>;#nTtp@BO)RYUqURR{P(k?x{L-_a#}|hio2SDn)rp5^AF|3v1kVi0oKD;n4HW>|-C_FJAG*_Sc(? zrO)2}G%w7*{h<9x!HPD!S-*9-*r%2loIIcU;L`WFXRoWz_jb7MeQ&pO->(G>v)dWp zE8Fu-DQotBr_tYLvMO}tGw&d6(ItVJ-kGY}Pv2}3nLd5Vl=!kH+xb(I^j0hF3({FJ zr#;7gm+Z+OZg61@CU34(8`s@SKq9-Q=ElXZZxKn<=cHOTvVUyeC>r#G{F)6l8 z;NXI^5I)-5eT)40s+ND=)EO-*e}BgRVBPs@)#@#mZ>c_>Td-lvp042ev$j3i^ZRaS zHG{>mSzSK*pH)>&eacqF=}znTx`}tHxs~FIF8}MXGCwmW=h$sr^GI_0XU{a>jZrtQ zt&SC5wbo?M`-9~=+}h#Zbtujy(X zfA?MP!NkdJUH5E5{vA9Yx5dr7ch}#!EAAMZsbpF1O4&hYx2RjP z>l*{5862GMb(gN5(K+eDgo(TKbNa12Ef<+;3VhTv<7Mpott876@O;s8wMD)uS(k3O z&W#pTE)!{7z2)LUDIXT;($%~7CT8ZYU4QXa@pP+l*$-+jX6t>tcO-_>Z{On=f~#|y zf*Wb$@iBTuM6^0|Iu=xj9Eic_Dtq0tB0J2xAk0caZ@+GZ`}f{<*Nc6BPpr;BN8-hYR}MO?W+ZlF5bbaeCf+Lnaj!x~^I(x^nU4y&+dlXHQ)e z!o5RA)VOk`;@Q4+WxiX&V;9BlzP0gu?f?DqAF|(0y;|b4eg4~Lp3=KQ543cC-Ec}Z zj#=0*NJ{N;q5SF^&iZYq4|z6oGFE?oH#ItMr>m+ho4>B^{`&uQQ$MfR#u>kTx!>Gp zeabBp6x$%Brs9I)f8XzYFV}kR^nv|>>hu_;_m$_XW2P6cS>~wD;ktc~+Ka;{`P^03 zP8Qnb$>rOS`Afn`^kk~a{DtAMKWkKeAM!Nu{q{&EkLS^aP*>~UOM1okWgWV-@7n6O zuZm-e;#C)Od8$7MbZ?5^AXC2o#*)^?2eG;*!`Sb|cHNK72)Z}*<`1jv`g=m(`aI5U zW0Ed1^8>!MoonknDwzf*qZ4YlH9E@~=Ia8k@{>Wbfq9YJsJ{ONB!0IT}md@9(ke zTYu_fXMRY9!yBh-&H=BEY!tX{Rk1kw;i^eG6W1>0U3-wjoS)IJ&fs`=wfe-nOAh|p zb7MhIeb5~lrghWh#3So%>$aDroPa1X>ZtNi-YeT{jRGx!5{V@X^k$MMa+J?s_(BSNu7LAWgd7l zxcqv5>V~&r{!dhXKbhhEE;;(EEra9x*NY2OcvnAI#=4l3T7)_V(7&_1=GNa?DB}-3Z=%y6DM;dA)I_P8^ES z43KsvbHjw+OUvs6o{1Nys~Rb4)O|0n5C1Qr6BaoC;gL0S7rdXq;kWusN$shbB2VTD zf4?RB*66@RmPJ2q`ks2Z+G)?yw)dB-IODC8Y~QSlzVkCvw5)XgweO#9XZhaAjd|Ov z_CVT$Q`xu2NLF3*Zp-v@``m@D>U|czG3(Fwy28-!B@wpC?z|BK_qLtL+9X(aZT{Z3 z@u8m|iY^I#yQb)M+=uqsPwP(KiM_lk&v5VZvO9Z&LYa0m@|TA+?QWOeQeb}5*i^5* z-1p>}BP)7R-AW&YzX_kqbIt!j=Mjrn$scO6-dn3w37p(&$sQt5J#&p_mHy1bjhxd1 z+6v0MWbeLMv3Fx!^;{Wa3oYlVyTe{>nRM;eZuxJ!cWdvJIR5%<>9W~NLOI_H-_QI0 zyVHMh`pNejZ?x=P_i=^h*ImLldG()t{i>uS+7TJQJE_05Rz@~3iJ9SQ*Ob!&2U9h} zPhDDZE#|)Y6TP_Ub(hY1u^R1IX0$U%*L!W$@8fBoUcuYVWVJD2Si{?;_lojJvC-|p|rLr?Bd4!?8z z*t3`q*DE96uj8BkbXqcJjkM3k+pZ_yCsZnywQ$^9zgDlRdM^9qqiUURUs!G}-QX`} zZ;-a1Er0ga!+CQv*WTXg{k1s$(1#0tTiPf0M)_MENx88!uhbxnQMl{thUmFTb9uNW z91gwS7o+!D?P2jh>HD=YaaGcw6vLw|0vRQGz%2i_r{-t2eaDiWj57;guibtsJN|dn z-i*@HUh%IpE?h3396d{J({4(SpUGLIVT4Rr0-|glry0|;?=mNDx(SI+hy*spvCxPjD*n!mYxeItMe~hwB zI$2^Lx%%Fst7jg*I#s)VuiBZ7td8n?PNm;v`+nkLX`OX!)E(|`SMSQKeXi;?<@c8z z>lr?6y_Y9jYFL-MHc7)+sXcAklLu#(`1CCeouF)LzfSl-Qo_=!-94|jtU9_X?M+96 z#?#)DL9y=EyN;C>s617ZX?@)O;B12A3>P!z&i7Ss3TM9F_I>XCUopEJ8!{e6-!(Pk zxj3suJnK$Y*oM3vY^RS4FL+s@e|_<~D(hW`)Hi?4;N8D|4O^`9gMTl$LO7;)v96qQ zYLfmMkDU=y4xQ=}0T27_{=CLI{^Zjo6V9LJ+I6toRrArerHevNrHa0ppepcU*S>#8 zZ%(>ecBHM^>#$#OdFX=N-5wA6MAc{eXsp|M=vevoa;y76=MMJqU#h#$bu0AHk;|{c zc!k4u&M??s?7gAA)G+b?uF#_oWWFo5Sfr(;ZDBh8FZbv5^?w86s%{7GsOC^i;aCWs zLu{M#-oC=;-)s4LrH!|q9cX0!^zZxr_@`YbUxqF1o&2QK{$lzr&Y!a;@dWMpakD(V z-z@l6;oD2^p!i$F<-5zEypfkNLw@oyBV(Z*bzZ&s-kXR$+Ue>*lX@#j(qN-#leu@{*r3 z`(ov5hwq}+(`w31Ey5l=EUj8y6~FOquh3VqN&E$09n5N%z1R6ZdH*Y;HyzCJty_i#n^~$Bh6Gf`X^Et$PM^B;sB2_g%!H+_tdVh1SAz2@J&R7C-ZbgFccQBP z(;&~wZ)SO|Pds#GR^H;-1)Hz#&0AR@e4%hs$`?N+pIfj0aZmd0xqtFO1(|@t`HBZs z&Q|RSc8TFO%Aos*KM{SnquvqL_Jv-yd)%Jv&D;^zGy}HZes`#S= zYSFjuW@-7XbN-hs6cxKH`_i}UN7=@Io@|=6g8$de#{9`2V?V|{bl+-t&GgEnm)76b;CX-fV_D-nxq6qz*>|p~_xnC!*ZlRu%H!RO4(9!NaTi{1Xtdk%`rGk& z_pMD=PBq)D=ANo#ysES8M5*_>#h+!jyYA(k^g`?2+ZT-0f0n5}oc$|i!KP!LrRwJm zGqvWOHdKkJ-O4Y={vx%GDRHifSZB3;e*@pN432wO1sPYbcTBeodigUaENoL`WYmo5 zH!rI{J+pGMk$ttB*z;#8nT3u;H&cB#PVZv%Js7UNqR#QCcP#5}+x=D@k378`)-T<) zB)G{+Zz<=^oJaGezg1fVt}I>C{?%Z@yUCL$SLKNs?U?IX=5*pgLtWMHxQ6<2xww`TX;&g!q^l z(_42&&a|#|dvxU$-? ze_91csYJPt7T3gei|&fsScmpl*r=Gkn}6+;^og*Jlq=I6&Mm*}=aQToGwBv*)spq< zhPyTN)?W|3;G`1$)+2Yxx+Lk$x*Ly6v~;(o#b)uUZ(6t@+wSMOe({ivD{i6{I^QX@NBnKsyXtqb{(Hs07w*Yu{}JXX zzvOeSb;DWShSXOq8CfDf z$E;!8Ey`LYc8wW_t}aV4T6c@h^Lvn?{?4VUnol-q-A(tOJ1Nq4*ZgTajnhs3_T4Lc zo29?I)35YV&>3+7-)EI&nm?j~-nQ@veY_>1{3f$qgFm(U#__)M@xt$>6>UHLXw%%u z^My8jo-uD;*?+U^rkx5;y4bexE(cgc43 zrC4?MwPAM@y!bEG>Ka`2`{iq0DR*h5n%bL;?dwWs9}#(Owo!8(cj~UIr;g}vP8D$S zSpT4yxim!L3)|MH*nNM#%AYtL|6@&@xFd&R%4`Qme-6bK9?9kBbWYa)d|$8In)FoX zW2eb;+44Jz@9Vzr7T#{PYG-^6vw^hC<^f5-j?1Tq)) zdZv^z1x%m+t<<>AC}6(Ld^zb-rkl&}WtUE05#yer@I+h?O(52KlR{So!<9HEVN4hEa`G&?d5JU z-^v~Tz0dz&|BtH9Fxgt~{@VgAr5BQSe<}QHwRqaGpr;i&F}xSo{WhO^DdczM?qw-e zH9qQ1SC9PZ@K_r%VQP}2*UvRircYPhZKBV;-2e2arqxp&583YCvPAPcQ;YSg)K%+F z?`3?McV5(NmG7mwYv#te6bkLVqd#v|-~HvczZO0cez0wEtZMh(KHkZ9Jg2|5J}$rV z(&@)7r?)(P*^p!LT z+4uFY`?jo54#k!n1xN#U!pG(EKex=W*jjNd%+V~i^lIp*iTyP$m*>81;3%8i(Pc7M zKY!_^(og>C`*mac?rU$|^;#i(;TyHLE~jcu-cI^_QDtr1k;ir(HH-DwPgeS_=wFhu zXd$citVixmcCqfPeqLRv-`ihSELMElHu>fbU)k4_K3$$`7CV2J>e<9Y(xu;bKRv$j zR+#eQ$+MDfH*MMV?^S*I$>NJAZ#!lsEYop9Q7 zX~>lp>#S6TI~VW2v)TQ=IDM;+`=peLpj}pLv zcA7IgsNruO)1KRv=8u&2%RYQ8v9N_-U%5q~=-q<$e?LAxzStyZYj(S|V6uSIiEqu| zK@dr$Q&U&{x%&U}daILJ8mWq3^maU8`gwc*-)PH<4++&#ZJg?BPe%~-M|%$=9drS^)#nthOw4OZCI|S;aby_DV~= zu6llPbN1T0Ye~RErFBWizg}E!!q^|OSM}`1w-qMl5ec=Q{4TkNnufI}Z#A3t=y-Ca z;QOlEcYm#FxuRPZYP@f!d+_sLyZ4{FpA_ob_jRB7>(b0?T0xr6mWJthvVJu>%^egm z=lbWVM_Dy>^s|`uZJ9J#tEGDBn@ewR+26jBH8bPdEr(s}w;!*au`2b}(p|o*W^3?Y zzAW(i1p|Ne!MEje9;cM5h2MFe&!(fwonMLaPX5ZitT_`W z_B?TS(fYcp$J9vcpy9(@;={O;TBEf4*FHr9|(hpyWllYID!E!aqDdTEAQ$ zYI>`4e%{p`E}vv?x9%$sSATp>b;a6`Lh-xbrMd*aUNLoLLeQ0|qMA>qE!nU|;pm%5 zQ;JUis@9x7^^;fJs-;aKr&TkrWCg7bjX88z-m*bU-u)BHs`SEdXYcG44*GOk^!D|O z2RP?VevlHXdhL5}r|HLHi;K%*(_$Y^D_S0*-XgFm_JGUAixay!+@`E9sw$fa3aK_8 z$QW!(Kk8i#owd(zirdOuuz(V0KyQH@KGzz1dJSwZXj zO;l~|v}Q)dW@o+Xo#K-+E9hwHzV>^Uo=wYhkL@j8=e!_WD9+{m^QyOjwUZ|$ZC!Tq zXUJyFn?bHwGX%{hhdK72%#dDjXr;MHE&pe`TSwMzEqBY#3pvBBHdD6H);wZ)R;bj} zO(CmNS0-<|?By!_YU`!0D_*LZAzne+VoSpGUabt$vDz4?<+(SgsPkKqXVwmVzpblG zR+-K%5Dhh27k13*-C>{F?!-wi_6mtVGRsIgbXVw=z^fTN{L4?C^qe_s)}>VQJT>sf1@Xg1`pNoE~x|bb#ChT;guMreL9Fj`^-u?gG^-r<=$ko*8 zv1<91r=y?v=2uPrw@=Q*lYgaM6KC|qDF2la>rG=yd%O=UcE6`$tCe@jZsR-Et?N4- zi|z#_%*qHq{!VSe)+}k8Ff~)Xok5Xdr$T2+$zMI#5VcqDYgMdUR;uRHFqQeIJ@uwf zom%6!shfLJeAEP|vdAurr^P{9PnOPc_w@-}b+qY9wrQrr3g<=6D;Be{zix28)j_sCk{ zK>4C}IcweX)#ps_#I^_+xj-9zM^?v|bzeEAH(~q#PwP*pPLH`XeTm@{%So@IG%x4u zdSauN_O(ea@mptF6zjgQJ1m!E9eSKs=s%g3s>;7}-qaj3ZEtVyNvEHtRAzc|y-1Fa zocQ!<>R!A3o0M+m_;Iy1P4QCIoPIiJEoiMyhWu{77OsPNriOWLA+>v4H2YpJ`c!eL zFK)?h)w{miqA$rFx_jxJz}FT5Ck{*VoPWa1ikRmSn{=uy|1b7xOCUVi?>%FF6sYs$*P{ELf=C!I}IoqpCUlC|13>+3AF z0~6xhC&}*MOxzpsewv#*>$wvreq|@^(y70ZVV=1v^t8gn_9?qdw<|AYKgjnnue1Dh z$?;qbP%ut7%ld!X zfBx*5V^}$WP^&6`xZwq&w{2ur6deJsz z&)9blkF31*G|%%}z>es(K6}p36>#EEZ07wJyS;&FAG@smPJ6iwKmXh_$EGrvQ1rOQ;n`Y?YlCEv)p@Id+ob2Tj6)06;1_B_5YopiETQ(b770Xre?^{ z$2QFgPj^n8G-qb?w98ACKRx#UztiT!0p?E^ocUL_xL@UAu@U+-;eFx9yfvY_-aiqk zR-dGmdQ0_=;@!ub<$wB4TUKf*wg@=&>{t+gYMsa0xamCVk?JiI7D6*yfX{*X^I|7G zUM}|Jc>kP9^0q}ma<*pL{qtu`zxgxupP%CzbS0l^5x-omxio5b*IL!Z_f7ewcScB0!9lU zK^M)kbEmoYpFj2Z`18~{!_`dtf>wHNjhfuHes1Ua z(p$dk%6Hf8o^<6TUxB5c=gZQLB9GG7+1ra}>xk}GKA1N>S?TJBl&i*`N%KK*w&cFU zg=lu$71v%By*i@=8hzjqgrpNTM#Fh$9S`WA-Z=U5rw51CpC0d@Go@YLTKTxY{)sy^ zVV_zjO*tL9^6D$Cd(ysN_OFjym?-#W?xj~{b5DNVk-)FI^JK}AYisA<6?&s~y-@m2 z`^i!#P%2D+Bx?TE%~{UgQ2n~?(@SsnM79VxF@gFTEfY$a3i%G!Rh32d&7U)!UCv6; zzV6Ewn?FA?Ez6SPc0ar*7{AhjasNG*KMO8u?!33M*mlYbwM&J0O13uqlVAC)235H! z%bRZ5a2q_>t-gv!-qLJ$a5=kT3y&(;OCHh%93THU`JFqno8^yl|Ga7L{c|U}%h@Zd z`{|#$;}phfBlKa#f}7_!e^k8RrFx?<@6jDK1J_B~bDoIQTRcj+YB((pTup)Udx1&9 zkGWj;%|FUc5pX*30o;a9InMZT-g)uVL`!||)7nK<=Qt~t+$%g`;ODV_@&COMI^Ij8 zCRgpYou+b}Ypbf=(Qga^P^u zsm}VkYmLaMNzEnkr%pc#y63y6D$DV_|9QQWZ)(DxO)r{sS~T|k?)ep=N2?d_tzmDz z!6@HV-Y~`b$!^~wu8Vf2rlNDx($c;db(DLQ-9pC#2R zVtCyHwie*(=5tb_8>6FyJPid{8z);;ke zSdHtuUb&9Ju$-ev+d7|vfDV--<&dW|-nG_WIB{r+PdZ}3G$%tB? z{jI;l)b32qP`|(S^W91Br_~+bS-LNHA1EESOsEUgy8dgch7*S(GkAuFM{-(`dn%8b zPiuyKU6Eto{8`h5my17r@{-kexAUIlO;?guYEAVDeSbZBm+YFYhifmi?9OdYWV#ph ze!?q{ea@Sl_q8+Kv$`K>?fes*&YaA;Dw^aT{I&)ywF4J!RSchg&Awk9@$cFHz2WQj z{^B&UHPuT_UTkD+s#jg+fBN5E<4x(`r}+5wcrN3gT=MASggpK5RY4k4wyLoi$xb!m z?yj5}eI_gaw~Wxc(?3^4u?5*}54tkNTFqx|NVe87!$PeC=RbPQ{Ce#q&#nUYZ|uGr zcPCb~M;zz;beCn%)w3;=*KYa@u1H#Vq*nB+vh>khkD=S^=b4NsH{(o6>9ZbckL%=Q&%Uq>x>hznAjf%*`KvmnD{juM zR&vbGw?@TgeQoP1+dFrjLE@^}U$%7q7i!tPQLJ&_wA*A z?)t8IY~!mrhb(&or_J)%_<#4MOK%ebt)aa^kpI1SFWbL^gH$|t=|+$75+d&qXp zq>XarbBwP-;?N`iNVBHLh3ThFPx>saj%HA7na~KHjlRWTSa*K-42XuFsDu zwFqqbyKsSe%LFq8)A~PGnoj+!=#pjn-6oxP!Q{uKOOtf;^}Y3`Pj*_U@U$rM)z@9~ z-qhU<`ta>&^ys2W_3=P;AN3KeU0r?$~{SIqK5{oZ8qy-G@wOKG~yB zyx$sl%Ci^Uo%Hhe_xJC2)F0Nry=_mK?R#)Uv`m=G`Y+DIN^1K|soRm(O#)7B5@4;) zi~CA;Z&|xMQ~kwtCymE53!wJ29C>Q7mMTx6<(YyA4@=ARNd66w zo^P}a?`JLojkn$gJ2Ug}jSRP-mrwdVYaEw>XP7`it@4q@mdVBMR{oR4pycZVPQJX0 zlO;Wu2j>QV0eJ#s;DlZky%&rswe8HcAkP^?l+7%Ox}-Thz_SV5ivfG^%Ing<)hVT4 z)j$i_AqA?3bU@){O~?4PVLRV*fC7s{QQ9r2Np1_bpr8}S&AOCr_qBwbPCRQk>^5;x z{`aTinPOlo9aaAQJ*qZ~r7X7Q)5#yXhhD$6o6zdH-MBO3`t6;zahDz+pU(Q|`{vV! zo*t2BKFv4%>f5T_dj*$Ge*1j0@adBgt1PE_z2w}l{#R!IxjO&lmyN8fv}XFa`Tvld z-l~2_Z2xV`h`Gzu4)^Tce(1YyJadbHQ4F}OYPa7pVbS)^8IM3b4#j5gOAOT&kugE- zLFRfDkFP&0ui@WN{M&Mm%^yp>3C+KU;`tyzUioeX?&U^l_Bg1vucYX`Em{;a6y3g|e#*G>C)YQ~cii(VW z-DP7f*#EX~@3W8pHcZ|tmJ=`ea9?-4`HB#&shi_&a|kUBQf}ULszt!*8hFQ6NkWOq z-EUE#B%o+LSH)0l`t={OpSL}@fA)h-o#eXy`IB0rtxxhzO4)kR*YA7P{UuMo9!fj? z%2CntLq1#d*By7`^6y?-ef#U&7t>bpKi#+VkK!lYo&25oO7ceaSy^7kjvo*A{F~Q* zk^h6`gtKX>nVFu`rcYlQqP23#vGlguya@ejjVF(r^Az^&TR3I={M`5(3hi$v|44BE zZQhAg*6MwlIw7y5x_0YsJ5QB% zBYU5@vpd>OY%j4>+1FV!`SuUlrh45umc?#zl}`jCBO)%GU%KI)#**7*-@5afZ1>pJ zO>wqhV45f|chtbvR@d0rxav?HQzC27pF%?iLG`JAC(oQo*>5&g!0CjTGiXFtRzmu< z&WmcxkeQ%j!DWV1zxoN^8M9rESNdJKcm&slg?(|El00+3T&!W*?a!+ zbVsSjo7Fp2Ch|7cUym(6InTDb>Xv}Y)BQis=6mgovGYEY|LpmND7*iYuC2Z-xGgbv z;?l1h=Wpo$_V%{=@9*!g7xUyeyt@6odeQ>Q1xSp0Zo@?qL-x53fb-Ia7BiwS1W$P*Tp- zxW#)OV|b zuwbr%dBkm}6TccGcwI{(N>eUvoxWH!*K)z^HNLAI=MU+xjoz2Zm902FJ%4}u>m(!FJvw*F?|yse)=+Bs|Ig>IEq`<8-!|)z ze0TrD^NOuLJ7gaoNVm}Fn*a7bGe^;!H}|>b+@9$5P|(|RvR(Usw~}Axihs(LG9RcM z`FHlsUvm$rG`s67Rq8TXE}6c>Amk zW1QNXEPOI=zddxkWBHE5xi7z1sBL^axq8+YzN6(8vv({p3%vV!pTVQ;`RaR4{yOk) zYus&&6}LoaNB|MK%|a+%`5!waBt27R;Av2~}06seNKU8p0NL_=&rd}Ra*o$*&Zx!UE*-T4pg>FYC$Xup8Qz$ zJ3p(f;$eQ39Vg3Ft`{~HY!_~^KC*l1E#0GX;&0vdXD9sHyC-o`^zzD>NzvE0hpsyE zI_l9?orMDZ*SE|t*v|Ri>g%N2k3p>n#UFhWRbG65lLjh{+c-h?ykrpi^41{Jo#STQ zjq2+yR{wtJif{#uAOvri51Jv(5*9K`n}60 zzfA0!d-pl0?31i1xAeYnxauYC2CgKuXRVNE-z9i(=j~_d@O6NQ_F*FohdMS6&#R8`w z@1>wFTuQD&jsW}TYMRJa59*=I^|h-X(73IZ}P&W!~KP zAG0Kr)vun~BK-Q{k;R^OU#guv>|#}Cwo&f?%_qA&D?1?LC$bzWNA`byaR0gO?q%EZ zmU1Yze31nQ|A8whzxk>Tu-Gc{t)98@h;>S}-TdqJIuk37zn@mIy-ITSmz){D?>Kkn zMd)^`o<7X!dH9q#N3r$M;MZz*9}7>OH3ihJJ@JkG{z9ia4Szb$`1t4i2X)ki9Kp3& zfvEZ0d<&E}aeQ~_?(46=YJB8t^WAg&_q9gfh^)V_9=0r5QMd8( zl`B)`si~`npDY!Ij?Jj>?MpriE=eAOo2FImxd#q1x(0o&F{xk29 ztf`6FO0Br}*FMVaW8Y@{k<-n0+hdtWHhQ-euPfdulIHL3`?INe9*Fq};$rEK)jyf!xopOBV(p!ajci)6*JA?Z|6V|fazv=YhuF71`*EXO*Do9u2<$)I& zZv4u*?5wsobtc4Z-(tOF_t)LWv(rAz+R_=?c=Fp@mAj7*-L2!_ed}=Ew;hln?w4gM zk3eI6PTPbF9r@jiGiORH2PKL`NTM*XGrLvze?jr=33UzEg;@4{-Tl1C(l+v!Y^d?a zzVZ#SN2+TUg_s|VJhk*d%~YNi>y}+tk1cRJwkl=K_PZ_Cmp=Gx2dB3io?Ohm{iS>Y z;D#}%0qVhiNBlv^?Pvo>|0>od*{Q|??>zQ9?^EXNpX|G->xs3>p9RbBO>WtJvW%nL zdwcYzyDqzZr(`XA-YwdFeLB*B{Z`mQmFOR6X@>}gZg%D28*XauP z-?IGd_buG*$~qUMKr=f$hsSG+N?Xgik_M@2{Zl7|z!_|;>0*>krA?K{47cj&1H zHggP2*GzIeRGXHjwrZy1Az5{R#jg+jSn&NQKsBjys{UtqhBH!dAkGhRj zbbfEx&R(%k;g0V%=XKFE50*t zeI(f6gRJ*mC-+^OJ9D1IovCUq0-N+8>1t+)mr-1r+Q|s6Uu#*Y`wSO!Ub|zs{$ZU(NN2>QEwS6@L8*j~Dquy6P{pgj5-ODsVD>-g} z`garf6ddgh@5SwN=5v+`zOem(ywDBxg(3aww;r!7>nOW(zRzQWz1uC$Z)MZ-O*}Kp zC%+3bZob~6dQb6gAy;-s&nO3y+7#l;OJ7u)INHCt zQ5H3~=j*n1;iXQyqRV&tW?l8$l0P+pKU4j-?~Zm;W3$Lro#h^7vjeXFx*KWi49>{E zSoh^0zqsEwqR^Q`(HWB5694au&CO@L7^5MQ^8er8yJE%y5$V-43bvmNoV+TfAVb%0 zVZrv3L60ZZu3l1Iv*~gLXf~p@q$Ff@Y5~{oyNb8o8VP^ep|2jS3J#UoyodBX-`qCd zd~-FZssYa!Owg0D+_L?#uELA$T(1voKP55ew#rJ2?Rwt}CWXAtHNVKWEo$5L$!`u= z-SFMA*o#fQqpYK3@~gyMvYh3UKa^RQ?F41MmI-f}_8nJeO_%}7*O2tRt6)d>g80bD zm)kl@nP=w3zT@WR-n3;4Xh-x(uAFTPDn9*mwK5*}p%b_$9u6L{M}je=bTU-s0_S}?up6Yp)`2UoZ~ z4n(W(S(@UrNZzg{V%@%9S(cTbmgsR!-uh_Uv{}dBuF+$Ao|wvWq{`-GUS|26;0+MB z8SU6&{c1~C_Gfoa#g+-V+~6YU;e*DzMpm=(W`{Y7+bpRJI%PZMeVKI4O}^mQb9X#! zlisyf4l+=Xa>7ICi*KgaRFH$(AQk+feRnU<@)OrozbACEEac(?vk70W?d)T*wpIeC zYYxS~i^8m{KSh8>S){=g>!EjdgN-Z=wQk<=c(i2Aie)qB&6^gxyR0-?1Y%1|$LhuD)rxP2%b+2u~lMC~i5Ra{O>1EocPULM}*5(rxm2EMZjqn zvt_?>sq=^Gy%%59rGxy<1@?Dix1Pm~ZR|p8)~xxmK?GDDf;zH%EVV_T0=q>3n(6st zY7Q!TmUdha?>;Iw4XRA>uBX?9W-qXV+jt?y9cKUTp*A_M`gGAw9bUOSFOWx^I9@XU zxUDpAf%{`$$aFs>mKs0vy_>jkaWf`Gw}GNtqwQ=Yb|NPON!(4RjXG&CS(bY0HkO&em3`3p^&C@#TVZUq0*aZ*M1C zzuV!wZqFw#NqPC}r9Thy*D1vRI3%8unVG2_)(HucO@6PwRz0%qK6=V(x(A11N&vXg zB6niR7es*Y<1S|_wn&LaXS8A(av|fUVllq7Wp>GTW{j* zyj`B4Ye-Mn|GjKqs1ywenr)nKS!!4MfZ9}t*|hUG6qmT`S?t>XR#)-GcCD3lFPBcw zD!TW?hQ-!zWr$bV+gqyT_bQj~d2ajuVvFr1@MN=M%Y=i=?*0j5RRb04FA_izT=nEq z_qoXv1uveQImw}^PTOJc*(Xm@RGvBeTzh|acX-^_RpBSsZojAX{Z4WJo$B{{tv2ey zLNG)9h4<4}57z1^woE8)0&Tf}Ini6+PUh-ym5HaHR>cI>pM3r~m%q_eC$Rp*1INm@ z+40)@zeev@-F~O2yQjDJYk4XpCKaFi?CzRv1oBhL3vgv4xAV;;2}cXgjD@>V4tasPi^KfU|@-@Kh4k4dXee#jUKE~{I3xZVnV zdFquL5H?Z3$zwjm$jU>l?J9Hq{@rN#u`XaCc7>E9Jskfz-gN}s8+nB{QH~V=`%gEdrUcNI?ttTw$)qvUq@H>WV_uL z#mf7?@2T_K|JiUQYpZEzmG_m!zn*Qc|GoXGs(xkXnKNg;)Vo3pq-?f@3L$r@MXT5wk?Obb_1sXIyb|8|GQ!#}% zrs7LO&HKjVx9=raR#x(Q-?mVAy8qkO^(W2u|MaaqS3FM{lrCj#DgyRj*>26Ga3pVg z_4mA;kNd1kly3fhx7+{U&-wo&&&{#iT&NALA|`I!xN-A0CMWZ&QNGW_ii>^-Fq^ap z80mnT4kx5`yqR>^=#%TjwAr_guU)gllS%k`-N)|uDfR!q*H3?L_kCy4@3-5tt?Je; zk_$CAzgsfd=5^%xNua@|cRQcU`Mye4Xndt4Ux_|v{0L2J+ z8Brv2cwAYd{{nUCUu$QnY?FRk6lwQ;=lPS<_y0+)y!UyY`u2NOtKZz-zCJyWU*YS= z_Cvw*e}zw*i($J5QL zOF_fp3ZU5E#I|nN8z#@?L9=2ynJ)&f4EZvp#N(9$Lty``S^Bjvy|rHE?|drucKUnM zK>mW;K5EMD{c>09eu?jW;rgjr{*S}Ir}6(>)t4Vl+1Vms^djJ#uoH)5$}QCw(r4Rj z>XPy%gwGXlIw1uvnM?~F{$x)nG0~lxrTZ~%MU7)uT>$IeKpnBC!SjDjsrmPL{`FT< z9iVX?53Q-MRxXyCapnHj?CCb2XP%$(dfje6^`(EW*Z&Rw`6_(B)dqKANH`k3c<{cn ztk_-F+S2~TYJU#J6kbS7S-gKBH&a4>@>D0wk8vLTi`(T^etFB_@;BY?v*gc1;`1E0JMRE`N+b>=W-1*@U_tVn#)pPfJS*kxZzy9~_S*z^p{#Z<21+6DP z&+k>gziRh0d;Q+9xDQR@CtSs2mxP+Von-A9v8wL-ySrAJx@uEvzg%>Gx_SQJH8t<6 z?@x}>{ds~JQWkr3mo>?q(M#F6nnN+A16;nC7X6#}_tRr{fAxFZO?8=>nZ4dJG65Hh z@7I1mxqaVPUC_4TC$0K53uCHYE|ulqn9H7GtG0dDqb}_yo&Gh8&YU}!_1%_@X~G>) zZ<<4~Mdh;R1%2K1FYe1`b6x@sR>y#foRr>}x|4xFFYUj7|6AOO9XH=CSoQq({r~&! z)c^lmWpd!{_WOF*V~Trye0|quIz=W-J|$;aHH*LYg>zJN z@#9|eQ^oT>%UD)?c(AML)kk->cfV&{-{Y3I<00G6)BFE;SN{2UygjvC5FA{W#6Rq{ z^t|x+cjeNct0I3Ve%^L`52(LoHTe;rQcHDuPd;odz=W-ZS4G9WtgilhEL{8U@C=^g z6B+0K`?CCLdEN8!Cx749@AqTsKFG1;?ZW*s32KU=)43w=D5d*@`ojN44V?wtu)+eBM^;H>YCr;U`a)Ec+f7 z7IR(qX3U2FhBJP)sM|O$TfV&XU*BYP|5N>SUluQ15e%v(W|lSWyO?q6&7W`Uud1{N zJc6Z^%HJtDIU!w3y&l;r&TNmo(5|DWck2HCzxPisxBIGD`6_t+s?3TGl|P0TyH{-Q zOqu`x&-2sF{5A_pty(6mef;6!;ZIN0?IS^r;hldzoet%aHsk#E@$vDkvtC)>|JnEF z$8r06yQaz}85}gT^Pm6w&hu0E_Ewh)TwZZCYtx=RI`RL1T`x^Ao}Sz^Nx-RXYG-HX z5#QD3Qbmuh9NLg)rr7ev0+Q6;%$hl6=E}mzbL&*@2{*}odh+7pVo+$YPPP00QU3qJ z<=pyv0^aR-%(wH~t!&f3oSgT4uf4yu)qCEjDa!*c2yZ&6AY1?M=kwE-=Y2D&d}ciV z=Eq|0_sKG*&BFKA>(_nkHoe%(Io~iKTyMI0<=b}Qnznyu&j0C|7yZ59hoqI2)vDsk z2gTnuoz|P&U-LxS$lBU^j&-wuQ<=rLSgV&B+mh9vm;bm2UxRSX!)orNy}Oq`J@WGC zwyFFAU#zDlAMZQaZvQ87YK6x!``Fs#X6w3K_ zD?=eeU=3(aA^!i@_|tp8-`hQHH$TgYXH}Ob&s)-2%CjbELTO%}pWl2tU2kvis=me5 zA9n7$Xlid!^$@gRGHl7#-)FX*p1A6GAYXpdn z1vvgo3%-1?`}wc=xX^pf#^!y`=JLJD|MyY;f1vQZxlgQ9gumVNle?mjviaZ~LHpmE z?QaLNtY?*J;@?Q_|9wy$*Cg;9}d?@MCHGFV)KT)%SNV{XI#* z$>V>f`ip}nZyx<;59){Mg6EpA9c*>yKXIbNW4EQS<_p*A7xzC++rEO~^^|$-49@BH zYI9Xva`-&r%DpnzPnXW$6KH+;|BL>A8GE~Lh)kGOfA@8qI;frZZ%)hA;)hi{MoIH} zwb$2Ge2?M?%T$?wL+i?JBH$e z5i?ZJbq4?aUEU$!bmH2iKjNUyTwhGl#Y)-r`cu8EtN0gHF?t55INd&}&)oFy_Wgfm zzn^p7bjjQM^;#cu`g5jx-$6CT`BXt^u1o*+|2e&1=X~|O&%5gEB-LIQ-cPrAELl}w za^ySniTWi!%HQAHyY4y5>$dYZU$5U^cQ@3vMZk$|^2(JfxBWLeqnl#Br>a-L>BKYe zlC9SdE_I)ue8uB;e}K=duFHNblckcHoPDfr`kq^C^=M(c+$8_HPm}*JH&5_oa^Gh2 zvD5#~1^2@$o7PudU2XQo@EY?frU@bYU#(hwP4zIN-SvRR&)2r^3+0ow+A{BIM?*~g z*VXZBProi%5@=p<|KrC(SqV@Yuu1V21Z!T1D;*b;rXH?mkqKB1}yn0pc z33nEpsJyc6jfp~M+~-->Pi&s|b*vAi9~kqyD?KpgpL~3ssEz!OGe`beUT0)xK3(&%J6>o0uPe*1 z?Xr5!^yr1Bf7PUGQi%tcn8jy&`2Db5ej2ETFtYbAbm35Z&h(^a(gOCYS-x^rZ??qL zy4Gu52Q|B7!ObqdbJjcDj~zdrvNXVNk2y=N+3cx{-TT)izr4+VV(Ik#f1YaXzx7uz z(c#?rvu98KzW=}Ol(duC#OZsUsNO2yH}k<}#!z$dn1aTdKab_77g_!^E;j!0bN>II zeX;wpS#P%;JaIdJe=Mk5=dm#&=Cabfzi-lC?-%?#>GQGEao;xaihq&dIB=0eV$Ojj z2X62`EPJ5A%Og_rb#?sJ(wm*`Qv{tn><^@CvpIkM`6seF+hq2qzwC0;r{DXx|I6cr zAU`Os*`e4nq13?CQq4H{aqdwrl`npRLLNRK8?RV($4&ao&CNY)j(gKQflZck>i_*L zx}Cc{_gwSQhL{}>+oYd_*MALvdZ3XxdJ`Y01!41$)BnY8p5xmTXZA;}-^y_C@1N)P z`?E!FR2_K!!8`ufrLT9ks_X6j67=(H{Qpwl|ME5!0orvR-Z%Iz$z~R}XyUouVaab+ zozNlR)Ykd(<;x>yGfifFI^e9{o*B%km~z|+R3*P=V0v_5-`(8zJA+wlT`qt&kNZwI zCOoBZgMUD6!S}o6+23sxQ$Fu_y>531SH9?vGkabv>V6Vl_cZ*;p3moQf4O?#dB}Fg zBVU;;YajQTuk-x3xLU4g_x+cZ4XJro5_qc zhoq50*mfgZdG=E4{Hn{d&6eC#Y>(_f08 zuYFg1-FBhwH~y@G39s&-c-|=B)F%38_e7DFi;IJcSx-LWI=t&;i@>I2NPSgw()D?% zPp|WZ>seb%^=bK+#!Yiv>JI@)DZRkLnBzdMIw3OCeYnk;)& z++Qdz)1G?jg0Beo&F_8Xdz@S2ciW5bykX`E0%yhF?{??Ut)3}Rr03AWe~V?VS?7!H zxG#%%A-@{7M##a@1Vm}%GI`(M|UpIkaU?v|-* z=E{oZY_pNH;0;NY>n z(R;Q|`QnX^6N(v)I419YcYptW{oew=pEYsoSxgUAYMCI**_V(N9(p~)?8)u#_Pdn7 zbAuKYo;8_U7|kS>`OD$jN2cVdrmS?$Pn??(XpZ3X9@rJvM(n9RBp6nV;`UmGCAxg$Vs! zFBW~hQySdhQg?0hJk|dn`2R0>zrQ!ZxYeNQTA|kT)4ArXO-nDQ)c;*_G2=zL+_~$_ zS{(t$i}kje6`Ez==2T3%ap1}Q)2C0T=ZZaR-~0by^5JbeTLd;ehD0K0WUQyR|CGnZ zH9=NSF$rc)|Bdi}0bk_tp1rfAQUZQcAJ$+uPgAndDCXcW&of+2MXj zR8z&nq(l4l;bT?F4y;MJd3k=?mH*z}|K~ex+BCCTX^xlT9(Lb9?Ypa`<68gqYWV5x z`@XK_jpXIo$17A}r|@!j?X+*qHOo~0-oC>9<4?^&?zjuzJ!5yvCOU8^s!0B+E_-yO z^T_$H?ef-#@49g)-j;zB5R-+IjZ6*IxH*gNMMOqMKEK4?JUf41q&oW>R}Dv&r$v@O zAF$i+SjbtzA?Km@XCc$ix+fK%=hq;Hu3pXrbI-Pvz<|)v*gZUkv*Oj z-Ym|$68Gw`%3sQjoxF};f3wKH((AG6_kW%%fAa75`}=+Vii%nN+;m#+^7rDk zVXuF0ZewgX%jE7;x_a`>HD5O}UjEu}{`PYFzb~af-HMEeh&Zn$=yam*I7_We`IoFn zxgQ@nD~|=w-|_gnuwqM2IH*bUM8<)?R-LQ$kfly-wgc~(+4ujXt)2VA_1#1M=9@Po zcm962`{|RDle42TEw?s_#`vj*Sj~~YbefZ0AyLDcb(>CpjdR{9Dfy3#1_x>O6 z`?}wG`}Ff0>Iz?7S-Gy;=}_I?mEX2DoqzhZ;J9r03x$mR2Ok`L+;4yH{ZS#O6MYQ# zJqty|UY&k)tl ztLwjd(!vg<*eEAXStgc721P|hflC}JikvUsUfX&3|NO7FtLIy8Kl}Ei?rZ7eJIkM) znQ8o-&$xR1-n!Sv)iy5CZvOu9oWSziTz10WZY1|_d$-K7>{`0Py!4x!QhoQaaeT>e zyL&M~@WEw&``nM0YaVjPU+8N4_D`nFFk@}$S79d(!Q2OTCAz1reWt0@GC_}rwaSSj zllgG_m;5gm1uPy&yy=K}`i#EnK3=>1-mOP&J5Ee= z-)HSrI`{B)%kwuUS|rLX5_9pj@DE=f=eu3~WIV@KA2Y?&_tFRZo1IJhO={;fMw&LLJAIQ6RFdwD00mp%(# zZCQBU>aX0bAhp}&A!W-BtF{PSVv;Cy=1|N{IC9~i5D$y0^4HG_h7oNN%;~BwAI@>i z@`x+B=&E(fg1tI8;o!r&<@cwu%hzl;D060+!?{1}1fG9>YWLVK#o75x^My8vHrH;k z(w!YjPJbL09Q`fzC^dP+27op8)u~0@Wi3x09Dp?mXKH{11`u0qRzNZ)6<#pBPmP|66l+zdRM5#Ki@~LR$x0~s+b=|JA zo@Z@eHaliZI7|EO{`yOgK0TSs(7&HmNo`yH{dch+%!}&hN9d?UZ_k^m>OE}*^VA9J zcv!qcS`;>Kv;5!t-s{867Or{U#9nNiQE^1jz2wI0uoUyeUw5=*{t zpQU?^zwOtM+4Cn0I9Un*kZF0jd|}(;3=!-1hebtRa4Ih8c50j_;B@Q2fq#2dMYy_5 zlCCrxFkUo|PkVB1*@g`Pe%5bIMAydrs}6op>Tsj(%SHFAtT!hVFW>3F;J(dzPXCM) z2Z5}09``SNwi1*(SH}@ta8TL4?(#CWb}?3C<>6yi>(|L#rR=_bxjeyv%1JXx!l0+1b};1v_#m&gHAw zmnd^TpjE(b_x=AFuFI{{oH!KEHZ$2OwrpwqSL>}R#p&&9b92Q9?!?`j84{VD*k^4y zwDFcqxGvkDj5puU*Z&JXx^Ej(qnwicolO}+-JNd9Pj?;Q{LsODsD(wTIx=$RJNDzO z3%Q+aT2w6}Z(Bc}F<~9gBfD?+>;Lc3SG-?eZ!P@C{NB9>pT!H+O(*Ugsm zizJQHwq)G@(Q_zSZ{LqcWoP}4e@s}I@axOVwQ(IWJByaeR6d#L+x6fq>+zN|f3F?8 zogn!AjPdy?i~For-F$wkcKyPeZ+||Yzy3VGyRBkN3A=#s%?pbP%(OS9{JizkI=qiND%9B`E$STe$1$C8k;M@!^{(g4$NRYF%n*1k$bDJPVa_c3`h8sK5gHQ4 zX+3h{GFt>56k0z@eb7{{WYbc?kvL<*0bXy8Ou3GVoe!G0XO%2BPv5w1@ym{*J~eHM zlelZmZdt!#=jJj_KX>Km!D*lV{eJ&CNmGpP*5f<7CG_`%=|pT3+wp$mg`3;+?_Ybj zNX)`rfA5z`$GW$QbbNk3w|w3_^^mBoUu+Vqk4&Fie9p4yvafmJ$7uqWbRFhgzqOT@ z`{Qe~yvZ&`OP?t^aVW-e*A)pnc}Qz?v@Kn-!(m%q{*J`S-EA**6vEgJc?UbO_eme( z+BE6??)Ur3&bJ=hcd@1P=B8Aw1r8mJANU&k_$r)kiREL`$3$Z!7x8JMsPCGm6>ZJB-%-sv# z@A=#}r{EB0u3fl=UWZ47N|xr2J|_|BBR{v9L>{jzyI)(LyQU*c+lk}lD;_D66&K$x z?K(AM&Fu}o$9iP$T#Z*#Z24l*WcyB3*vZ4X>16%2RrY?yx4)~G#IKlg{nHU)|1DlBcV|8@=6Lt5k7<78 zkC_|DMu@>s8Q>X8mX-(%5lwdSn2J@G*1zZ1&+C)DTHD8>Ig6`u0p z!NFgW9q0EiD_Hd7)z#Hc*X@3n6@0EbR=|}q_V#kbSNf-Y-hFV8wsX|U3{g6USkmE~Z!;1ww6mOlrdPB~^ zqOn=zdWM*F!2<`;#)2O0ur(JVq&e(cCcHZu>~E`T|K}lpw91xuJ_=6nd)O`%Tsl9` zHd;(4LZSNI&gF4#{c^gy-)@Uue0KB4O3rZ0n0G65UUjU}*i&cn4U?tcxg0rPvTVktSVpbY zYCba*{{H@+|Ka7w+NPsCPtGd;TdsRwJ3{4U2kR1kyB`VRbF7>=UJ4|v%k25oH6!O{ zn4UA+*S8a%tF;JRGAxLkD&Ta>O5LBIN>PcPG@%dRdIbmGWVEdj^R%iFtMnhrLYBrOP6T)c3OhZD!m zn>L@%7{6Nd-bd7yAy8|{=T8^iwf3%Rco2>&dILPZtlqwe_FMlerQ%JnLgc{L(!Ir>r0EkCAVK+PR*b0 z-8fI+Qe?&Fv*u4*g#9KsHnWv_O!3IRxv#c5Ti&ZoNZ`eC|M~0QOUFEzm+x=2$eu|{ z;lVrgmI?1%_GuWLv3{l>b8&8*o?^=v4oDMp$&MWkhaLtju0H4>*&E5JxWxFw_x=BC zr>F0`-pW*a{b1_3?dEUa7KKmD`uE3A`n3y(;#$@F&sDkF#Ps7Y--;D>;<)JwNen?j zF&YOQ6yC?pNcQ1)siP2I`|wcf+@}R084ku_mo~3lxiT|+qMcax$sS4Ls&AgEEhXNw zvX<>F{LHtq^Rf)6&BzJyr&l+(iU^;nRYcnl7m#mf?s(j19U_vSf4wN%F*)qgW=G@e z-&UC&{#|u%Pvxe%zrW_y25~ChowSVQSA>pY^!D(Q)7Skt6z__HjdL>ixPHyLiN4XN zQr`Yr8zk%Pp(1liyCiUddP|8}-p;4f%7k5RGuZ`C%=fD@yL$EN+fQ}}esgl$Wpwy( zDBhh|(|d30_fRX{RMD$z#OZem#o+_8M&b&9PX;2aJEZuDK%*|It$mV>@1iz!Vwq`&5`F#HLV1L_Cr-jFUXOx%} z_~zfDZei1?Z@Nt={Z7*OKxAfQeLJtb>h_3=BHi= zHZNJSW5Mlt>`ojn{XhXP;2l{xwPwc? z#Yz4}&rfTwQk{JAlA}Mz%c4CW4sjc$oDi^lGQl}Yw`<$ft}ZUKPb#OZ-|ta&mo1(0 z<>lq&Cpp@8?Pz$w=cYD9b&3z+ZaDdS|cFFPQuV%26o49mh?&Kc$`{umVvGaPj7@nVtZTKzuTu8jv(qpO0T))>xLo0Xw z{?q?fM`6~ptKsoiSMlvppM5H_%;LGtk;;(Pl4`V?LQu_ySvSA$8n}2 zU*Fy^qc=A;IzMhR%fGiK$uckd{Ij)Z6Q7-#X_Rq6VeP99x5btFlka99EL6VsZ14Ab z+2?`?p6;O^gZ-|B27MsGXue182pQGRK&oB+Z7%KL7Am=nC* z@9OtsPfkw$I>-EqfZ%b7?xRnd`RyhwpI7DOTIIy?lJiONh2+HI?;De!p4q%l^Yyj0 zlSQ?|PCU=Pe(GCp^inmk$thZzH+}LxUOQSo>wU|bkmEvCH+Ssxd8QMgSAOUJBoQ%} zln)2&Hod=>{N%^U$xoi>zxO{lPr&KcOoyDK%B4b6ITe?@-cLgA@+qO<&^;50laW_`U ze3;g73e-J1vH85+?RWFf_cor-<=Dr}#xvpJ;r8r*b^rhU4ee+*=s#c7A^opnW=&Id z@zEdadj40R{mYXm_sF8<*HZ84r`+XhmpBQ;{CvxLPWPW3sCDKu!@%)=zra$32hMxk zpB}xlvpDtgGT*mfr?d!MQk`L(-lykxZEpF4;^I$+$4^{YK5w$gozDel(l`D3-1pWx za&>Caj-d z(!nEjVfRW<`yvxm<4w>zf9mS&Cm)~RpQs*R5pEZ`GcH!%ZA$+Bug7;u9_Y|^XgRo} z@bQx`m;JAA`lUX_?K3(uCL&ou#-nPWA6sT?-?Eo{y$ps_etyZbWf|vQ$wtjHFrFXJuk%Z zt*owPe)83;n;tz*HOl{Xd((rxmYZr$Cf(eWIr+$A(_fPJE-IJoJ0T=hvocNs-cvCS1;`+JtnG5J3a*k?VwbEEk1!pdur>7^%&eK}t4 zba>Df(66OX?^zq|^5*O7>!-W*_pM-Zt~R@H?D>}&hRG@K_kIs^l$(FE;K=SZaqiK5 zb4~hw|7$)h`_Vk2q4M)tb5pr?0Vj{&jjNV|R`EGD)}6Vr@zbk0-&JRx>o|69v3RFK z**%W0Zfo3U_?|n_^5&<5gNlYywAs4pt;P2yZjY;WeRh7f`fRhjD|V{u4(>Qy`)v37 zD+L-F%wH*dwJ?&7fsk@3L1Q7 znRqH^E1Y;dLq_CrIqy2d#LCK@dw##${pr(b{p`b$P8^E4>{q=a7;;ZI+E=_VsLcK6 zF2unW+T)*Uq;>8w)0fXT%Q^lvC5ygfX6$Tfy0lwCskVF3ro^}DpN`f4Q$BC|drjRu z<)kw^U;niYx^w|)x{lON2OUw%#{5f8JS#l|}^66Rr zr`LARpT3jZE^vEIb;>F0hiplHiHQq)q)ff~tg<%Esrr>!^kQLqrk^3(Du(9JKb!xr zK7KVkUKh0JahJCg({HQqcZ#k5?o?};u&jZj@4SD?3Fi&%*|I<1o=|b;vlLUADBIWS zm($$yA@%gM%*hwOG5oguaDe&g3FZEfbbF)6*3AOovbOAw0e|bnc860jDT_sr{e@oCho2_y1!~dA6rjNKk#<``xnHI#F9ZRzHyX)mOy* zP4(!}qAQBRPp;eBc}t%Wa^lFGW8lxHxMcID-0IdpzdOyJtZR>-8k%k9X%uSBz0q_B zqjJor6H52;|NniO9dGU0XZ=sxsOXBs>j~z}p($oe8DAP5%)e0G*;)yoWpQ-?MCyO!-LwB9YH?sp)t<9yYV#m+6qzBBdhUTYFM^Fq{| z1@YlAnX5m%KQ3!6mg{HvR3!JuuOE;5PakUKzP}7F|3le%e($R^{`v zvzK2zd_KQEZd>l{XSK_I?+?~mz3R!)>(5Tley=wvcwfGvn<-ab`YaC4f3w7P)8G)7-0e~X7KKaZL3Q)HK|>2TrV zx@!4|ZMnprN8&vG%w-c+^_M%23*)TsizKbYBmoY zzV?0fri6n`Gp$OslJhwfmx!9ET}a;7a`aK|?d{%$_v@@CAGFbW*-|;%mjB?r`R?}x zRw(%?$t^$C@lD`m_hQC_F&y(>cKqM-e&6pcR`2xde|CR+R5Z1yZlauhc-YkwSJLlI z3cvTqVc-9^!cUIhw+{8{;*R=wic@jPZ_r@grOY2+HuHWuetZ6;q-I!}3 z5n^5&tmkPSuoN`3)7$*)cEKs-Lu}mhQ=T_}ekbNKK`}lwbZHa+KL5hZ*G-m3>Nu=B z+^SwIY)?5kN!9wltB{k2__Dmu#r7{lytK5nL*}^u+Y`nTJ>BM+#k$BfhfRCd-nI}s zu&Cwj_Kr?9>6j=DRaQYx_FfPE3qJqar1K_duiN3YZudK@$|n=um#kT{W}e4=U%GK%5 zeVvz+rxVa#tuu4ltduCFg$u;J zn;DhZ)*TCFy1s|)?hA*lWn#)QN@)*P`c9q`F`w7NV)E5mw@r?kTAJso-Aswrl=Zv4F9R^Bg}8Tzb`kG*JNWjJzoib%`VKT8}Jbo}R0*lnlqc;5%s z$~PO2Z|QssInz?I^d`BoFIt!o;FK|sWxAJ{?#+O( zMXW!KUoe8E-%b|ZNNhiOR6PDn@TKO1QQPF{k*PrB$W+k*__D?-@DFzTYY4->(lEF!(9+Wk$l=8{5vF zi|i?u6X*CmFZ-VGYhQc4<<}3(t}m90FWq>!^Yskt#JteSUf%y7F=<};IgRmlf}I#I z+foCeX^R#mfrhEJ2A$jaGBIM6_LalF|37>&Pd)eh^wV$B`}N)B_K5%g*D^tl9n^Mg zZ+5(K+4$M1{JeQur^DA=zw>>X*-T^M9}~XFT6pwXe)aizt5V$izhm>4Z?96knlzOU zxHma+w)<$N+f1Ff$ipMwU#TF@=cVdA>uFvu9|RUZG3~y$%PHsYa_Nj&DXyO_Vm9bE z*c|gw)14U*ut?z5;){FMuY8v<1yt{Kn1EKir@Xnb(e~rh$!-PbygR;#ii#SQzPcjT zFU+C1WcdR-V`-+(b3V!5?vc3PV3GeT>t=-in*&{6)qWjwsk7R?_g-$kcQT)PF<+WqaK?$xDl`}1R!_Set0a{bqZUw-`H zAC@~|^S=7YG1W)SpKS8>%e(Z)g+uW!XmDu)U+Tk4SBt*#em{Ntz3yZeqpOcNw-;aI zm9UEOvwAON`D{ym);;TzA|AfjH#~QwUivspYV2vb>^R$e{zXTXJ6|L$W;JRV37usy z7Cqr5qI}UI@54w8R3Z)$HSw8;zXpeaH;_uH} zD))EalUVI@MSatY#L%G6!B#7Z4P83EY4v1n>8+e2_|tJGOG>ql;!@Vcm$3l~+wWIS z_p$qxvGduh)v2$qt^NAUZP$)~K;8X+9v|M6^;T`?$LHc6Ds%pC6>{<@Zv-_JoNsK~ z>$GlnS*LoY{WTQ&XV_S7>j=Pcj1{Qk9nc30A!^&8F2=F7cEzLJ@LCeXmuaSi`+-#fg| z(w1J_kls|pn<*RGxn#<9m8B+P)7H-`UH4}5`?^nayY;6m^`3sM+oYH`<^Rw3(^J3h zT3ht}>~zn}!bJZ+!op5f5}>)`yt9Xn;{h!D3OZV)VquwsyRI|V{XW#oPtGbH(6Rtm7A6D~ykixDo(t{IHkU72S1tN>?Lp2ql?Bfp1z zK*4*PoldKK6rk<(K#Wb)DJ2 zKdo*MxU?2DrIl$Ow&tEjc+7A8%FlOItyW}pDy(fj0(By9Q`@PejcL+Gun5bR|4-b1~b5ehYweTe2=dpHsyAOOkQl))w_Ra}{pG+Se zJG*B~m!*Q_jHwF8lBKn;9KW2Y**9ZVs{XrUv*sJT?o!-tH}jHrrioJhkKeJIuG`g5 z-T&`pe^6{}Z2mfy$JeXnYm|=9ubyjDbiDrb5nK09wGJFFw}A#=w@C8L`}g(2>Y~H( z%T9jVSD+UCt)8H*4n7^>>UCPV2tzx~tZ9 z%;n;W201<{!+TafTV_rYeSFbE%w7E4tm8{Iy^P`$y5X~?@rc{)CrKq+?gU?|$|-p4 zyyEx+@uzd`Uf0_Ctjzd)juRX>GIOfm?JRoG$Zn>oBydUM$Gu}r=cDuYs-B*%|GZPF zML_zj`zv`8-LD>qv{b%s@LyW%S8)N`GVb6i|8r|#FwqR(f|Usp5;ICadN4DO8x_8(Od>(2C9ae9im2Y<+V@r`xj1`$_ft!38ioOixu za=ebbc*$qS!_p0%dsolkyC{24ShhAR`Bu-9M}yqqD7Px9HQ{&EYYUizm+9pV;@b zrA9n^@&4$-dzKGkKSwMm{L-_$K z7ZN{Bua=)Q|L=pKn;YJJuIfK%x9$CkDc*H8tN*;+et%;AysC=wf5JjeRSKYTS(mMO zv36bQbQ|Nxww@}%x-n+sURG~qzE~YG zURO2SeZA_ms@Uo&KAy*$p7|K>d30@d!1irS?6zh zKKJ)siK#_RHR}v+gqX2!Vl3YO?BZ;TfDoN(HjVg8>d%5RZOY9wR{C7x`Xwo8nJB;W z<-7JLv*cq`=CA*^Chn<7-^w+6qV)bBof{Mxt2}*9vCe&=mI-;xpax%e#D|MsJD;d; zKlN$q>t2t0XKsj3-b7vEf+DZ>5U zHsCaMIiTW68Q7>MAW0s!aZ^^y5&p zQG5DgEx=f?2+3){m+|e)lVMHzrWy-%_*seGyIk0j&*Mq zobB2%r^47H_@Z3%wlz2Tn$7R9i`%x;ym6j=@>SiXZHd{p-dv8GG^?;9%_VcCpUToT zR%+2&NkL-W-Zq)dPX^7lS(om>BjQ*jCNg64Sw zmsCVU4S#-I|9)cT`8{j>zCY_aEAYJA%j@Zr{CF+(`rp+;JuV?Xjyaq+l@0oMSNMit zdU8ws$m*-R1lLew+OA^774*I*KhF9)W>@kDn{fp1M`r{$o%=(aL{>>F8h+>!@gJ7 z*Utxy5QnXex+>`J$e|e96}PwQ=QGW(k^g7@U9%<0#-Lc~b<1`&mFJ7fi{*{~8#3+N zd3I|@X=}%)^at?>fHgUc!vXrN)e&?3;yn7cQTEbN%e%%%J{SBXfI`Wy{ul zk+O(b_y0Nbr|$Rj^s~e2ZYA9D4K3}B`MaAvHR1ogPg@`Jy$Py!cH;PWxI@~>WAT}q z?1gjwyp?`(_~`0cy62LGVk2U7)aO(${PXBsd+HCl>dK9V9ZiKzJGI+gRxZx=ThiNk zy97x)WpCz|a1a`j_N&iYjo--a5l^qeIj zul(z`OXcki*HRzvUbgMmte}-@$~mBJ$u4csDyQ<=%q5c>-UpZ{skI0wXn+;ZlR((jw`g)_cQy>y#x(=mlqy$*HLV#|y7 zk~>Y#1ZH-sPxS1cadC_2`?E2pUu#Y~xyqy8r|P=!n)??Xewtlxqu}h`m-WH)@z3qK z`qR1B?THoBi#@!ZU2%ywXec+RfB*gelVbjLTQ6O+XP4*2$m4m94K9lUEuZX|{N(y~ zJGIB(>~HLN`ZnP-?=FK6MM|}I%o0kUPCVDy*ch`;arJrG{N$`T!FJO_Gh{z)OAszJ z-*LY4<2l(y{l)*cFs?1UvV6gPk=`e{v*;w-nf$j+JRwszge)w-!Z z=~pBlx>P@Zav^Sm#EXTFtDo(f?KnSP3f)?3gjXaLHlE`OGO7g`GV7+3)XmRIC9t&sUvjrvh}W%ax@qt4ymiyedQ0JH-Lj=q&$>-$_m&eq zA?ca6X7atW#|_jsJK1hCT&8FIUdpO!P0Zit)=RdO&v$eEb^cn7-t(BU-8nbI?p?kQ z+9<%rTIJNi^!(7({q+w2?&tb@`P$sHDYTQAvQXLmwCVOcs_~Y;oldea*&TPuOGw+q z`8wX1o$CnSse@n|hEqWs=Uc31A%p&j7 zSGSKghaB5s98lMJ>2!pWrs!4ES>E#gyM%nJZNANz^V#Kb$tBHvf!hM>9_lEzl$c*w zp(oT?vVFR%m1M*dH@?3c5)WJ5oG9pY;@t8D3mn8xNUP4CYAUZk&8{_2Q@6t-P(_9H z;RF}wXI4UHi)t(+zPKow7K^ z)4@fm@~3xB|L?4|I=k$je4Bt%6elR;o-xM#JiYtreEu^5>^rqnb9jtB`%W;n`%k-n z|NoAf#QV%S>vreVdhl$9vV8xo1A_@TuBidFATDrBgI>Q$pfor>~kNCHg*A zwW0XZ?i12`Wfxz(^X|3dcN@heyDu&GmpAKBwPxMn+Ri7tD|lYFfRmN+jOZy-rkGT9 z%y_;ga$`)DXvWtD6HZPiVd-N{p(y=T+R z&+81=tn4sbw5MD6>Z**}IbzGy=9b^odR}s@S) z=dI0swx0V+Z~B`gZCz+7=JM-ivG|Fl_Iv%GZv%}(FL3bp;AemI=c)bvV7ZDn_P>gY zkNnJVY@Ad2lK+YL_B*PowZB&GK3II9`W%2+`dP0^ERZ5Gn~myGdytjS>5`jbAB(*+w(%&<%7b$V-`P6jqT@tm68lS@+e4d z^~4CHmESXu^iAKHanIpp$*x8*udOG;+q^gwW2ZVcvwi&B`^E%R&UY|!KlyoMOWs@_ zjlNYL@h^%mI8W$jX_m@eyzb|MoeKSO*9BhsZjN|7?|XCRgl9)?SIRvLm}klTXivCn z?jy&ufArOFWb+%8o|R6Xa(YI1yX~`hy~+2uzT7goX7xIDtMQ%h$LCg0oKx_PRfwlm z%7(RKo_PFBRr`Nuox%NpH!aq`8HJpz_&7d=|9zELS@CPOl|xpaMf|PR+hhK%mVWYK zvVX9k_2W5DC0Z);N|X;J7d)Ty>0hn1c&qUHx#9cDSol~f_A4JU=`+1nlfA;6*-bny zzqRJQ+TDHM63%R2xm|Xf;mRH*M@JWbHhT}V*EY&K9|x6tB!+PvPV9**ZN04P@!~Ou z;u7{t`|In&I490M_TXT1`K@9>Cy#VCTgwoa_0}ghwypg1$%yl)R!Tso{K>Fuebt@` zdTmSzw^hw1oBCDx%uwihoOUMPq+jm;mH900yVp9kNuEBte~bEznZ}l`_azt3u{`~4 z-qwqjd3>r(%Xv~0)1>FT{$rJGA~!&Y~*I&!9*%|7~v`}p^>r~kQ|t^XhM z?A#p1nLK@J?AyGT27P+)c>Sq7=jxOH>UpUA7$>*sn|bTc3siyqH>G!*4y`ZV` z8%?WMX@8q7{WflcbhOpWiPG9t{pC+*YaMyrae0@$(JtnbY`%822HftQl}$c(EPiip zRdQubG+uPX$;Ck9Wy|(XO~jsnO{F}+7Ry|e6wUws)%DfH@3`k7e6@JNA>f*$scYx^-f6;a`Fgg>$T>* zDY@g!(>=X{oQg}@8`R$ZM|&->{0HX8`5W& znqM^k=Tl{@zR9n0?(3Us8@FlQl3ZjneQ&SORm-VnHLmvSx1U2>?cYzD zT~}9~ux^IzXYk;E(qBJcul(Qdr)lZu`fn%u|9r0Of8s~2+RFnxGmfp=_Ojo9PWktl z9nu_cmo=`o-+$rgJ%->-8AmE)6K>5fb$M~5ZuQaENe$5~A6#$jNL&9a=GC2ou-~_G z6Knau@BA8@&}Ye}e9d`@xo_u}tvA&+XuR}UW)w8p!_(C_Ml1V~+B0?SNh?;KlokxB zW^590+Qt7PMmm?M1e%;_^%&V2cM=zQi}`<0LHnKo^`UMhW5>bUKu zmz%Y2t?pP~d%$RtS8B)-_iHw@_8b%PoSeH}^p@ITyT(dMsWZ!rgNzb3`0RXmKIqQs z^2JJjujM{1Iw)E{RrmI(>R-3p1TJ|l1Qq;`FD}17Y4*DcFLrC+)N_khz7+pHSLM6? z&z>F`>uV-WTbp*@SA6eMec8hF`6Tmm<;pXh^UM88r@wn=@M!DLv&R&)=0)GjdhzPa zck|x0pEnogJ!x5V$M9B=m;AmC7yk8~Yjw|`v2=Ry>qJH?!yNezcBeXfmZ}{uA1?8~ zc4StK`bJM3#U<83p`llweCJer%ev~Km$&!r?D7_YOR1eMNGDCKg&u)(wlv{qdcgOR2O~DE2 z!nnI{U(Gqb3Q`udr))GKlEQ}X{l5f|!nkrsVv|2L;Orsk@ykpGkQHEJyrVnLJ1RRZ#QajN!v z|JkkVQ=KIete_@eal!G^hcD?V7al0i+rek^@K%!W6H%TSCKoSQX@wMgx4ilHqnV!y z|Du=u+KWXdYp%HY!S43nU60;6oay7|-Ix2Po9)IioAASr_Aozo`@G|pqs6O#7Yvu4 zO*v8)vEYQ%bG8^+ftRbZgn|TDPmItB+wHS}&tg7!O|rXL{=GX-%DEKZvhMSr=nR^p zl07s3)6qK*J`44xi0ekF?Emvr|LKRre3Kum$!mtk)a={y;{~%)=L{ET#nMyNh31Tp z=eYl~IZ}A}$9);tcg2G_U)bg9SIJa;dU-2go8kwX{4;Sn`~QC4 zGVk9t#g-B$2`MiR{>0yXUf_xGX}O}lT$&PRWD5>3e!6nm-+SAN=^^StTiMc&sqzcn zZ%*rrcGO!Ejga~ePL6(b&TWR!h`_!e6?q6XFFY_*>?VxjXPC)?a38s z^L?F{K8h6O3%umNmdQ}Pz4&`|(>^^m7Sqph;xZRp#8NJ7;aGppq}?=i26z~3(ITby z)}5dg(C<#&+M2C8)vHi&6144+c%(ycQ{v&as?XNf|G%2(E;ms+f6vCStldjeWk;^uP}>xBBIjG;w(J8M zPs{T%k7VZQwI`pu@gsP`k$=INxpLB8OHJqR(~!8>m1o1p&1q$P`0sg`a z>b|p7{O$j4;gPZMn9JsOfK97yb;jJ&RpybO<8tDh&~=e*eQ|kpRyGJ+P?|l0!;%@idb)3ET0^i>-Ie+iOKfUYwuAPtj9P#AR z@};|W&651mGC}U-v0mw=Yu2ne`Ci=V7I)eOVW*D7J39(zxyFl2tjx;FdUBZGK15ki z>e#Ao*^P!Tx4gNrG4@{NTT#yCkAq}*GuQYAFwOeSR~)94x+u$F`>EJ7n{O~>3a3vl z`@73$N^hpz>*7n%&eIF8DZe`AbNkNGqYJH^rx!oS-lC;vmM(Yov}xy(bGzrPzJH`& zsp+`YJL5=Bagmch!{hZ?U61y<-3FU*0}$Uga}MNwb^?-Tym1WwxvQoS55`}JS-j_x@T;%S?;H>Ht26&Gd%xAed(W4DH@JP(o_*fw#n)oBE>_=uE+#x- zd4{<9g@9{ZUpp)Q@l=JV8h}PBWgDyIm>hy@eK|7uW0o*0wg^nTx3~JV`2HV9-B#Z) z(cgFQr+?U?g$sDz#_H5^Eq>+JZXxfzz;bo(;*Faf=gz)qDdiJ2qubHqIBV^$JrY6T zm)D%D$}ZV7^~$?)@#t5+4`Pn+7QI^gSkC@o{fcGREM`wRxuo49f78oe4gTr-PcA7B zwCUI{D7p3fzM?bc`*j&_KiQ^dAuqo^LjC&x);OuG+rewerV9@pbzcjp~Gy?Fc6$y!P6 zJrmaM{Bp3N<%`6b$A1p(`F)dDCik?``|nW)zlR+uKOXtq;LL@~a9rypushBZw4kx#uk-*Dl$Mu$Lyk2P?xt)FH=bD6fJ%P7m zuF1_PJSlzgZs)6CGG=;Lzc3g#+28q@q4s)Pt3>}}=PfI?H#`u2w$*<|>C5E%>TxEn zZaud*JBr(SEH^GXue|`z!C}w#A!H{+PP2_IH-s z!*{#iznd%gJmos$%+Re5XI(h9e%0*VZR@_7<-VJBTWqmG?VR(T`JWyY7h2Sn)HAia z1t{IxppkO4YW2nF<$DEhy*>Ku{BFj0W;Q|o=Ueg%q;Fr@7BGjPA z6SV>#N6W16f4hp&iQ{FQ!ZSAC7aTVw6V5nsDB4PLeExB-_Rq)TPuFh0H_1im;+o3~ zKd(&)i2ZKA>k-#VuVqWusAV4g#C>;j_fEX2 z`rL;nv%_z?GJdbnPYcnW-ut8fg!Gf+v(q+a?&bBXikT#}Ia#UFj@GBr;eVZ! zSXq0|9Tq74yg2^e)g=>q1)R#*KzZ!gov$BO8&%&s?rSBr_~ME`|Gw{^ewfdC&9$2a zXUh#vbhR`|Su;EB+aW0&n|$Z(J@ziMKPNdq?>KK8mYla{$9v1?f0^xL88|#NWTrHE zcql!b!1XQeO|P9rP8VglTIJF{(NQbz4c#rpAP&oIXiF3-M*~(;`PV& z>cw@aJpNd9(Ynq_aV@Jt%Y;1cM;B+kI_~IEeL!tv%Y=DcEkF3}emE@iovjAi^ZTvg zV$Sz8lY58$@SOX3aZT25{|Hv9SM zUC+O^)cWk2GyC52HP>S<+?s6a-?Lp$yY%pEvCVOOdkZf~3*PJeQgG?`Bu?Sd$Hl*| zAKLZlxX&GbHD9}XXJn-xHoIy+{Z<|SW@Wy*lZQHJgzWB=tMl_G+TZ!rV#V``@!^j> zpU+KxU;qF2SF^(ke7wyCYiEAYlb5?_toP}d<*T*3WxvmKjJ z5qn*{s;>FvU&hTh8jd!XC{4WXc)mm0$)lWI545kq+*?IqiLQv4*tVw+TLdmWZ7{H} z|5pQA-k0+3&dysuLLVp0YAinc{r&xT(D8niHNP$^sn(|EMo!IH-RGTZv+|?ijLR-- zHBXzgnTlTKJKKCcZL^y}`PrzwCjz6_<*e(Kj{om?V*Tgy7OPAa-g_7n8#)^J^D}0FPIoiB4@7*OKtSMv<1o zRl5Z*zIAHI`cjv;FRWtU*$d|_-^^X`e%@-mt&hv+=**X%@oL?h;$O3GE))8==e>&6 z3r-aG;m`0lR^f#IT&vJ$XJ@NVojUcb*t5HL*6i33an9l~ z&rI9ju|Hp)Uq5wiXU`h#)zdabIIWtjDY{!wn0cf0*`&pZ!F%7Fy_wTmEcIwl@5YOj zb4&L>Z>h?ja$R$V;LF&}>tlL+*KtL?4|CslzTm0wzrDvc-RcNaS=v?O_W7~!$|<{E z_)d;czOs8yO>sn;;KJ_*4i-I;ynpJpLD}nXTswc2^M7gCF08mj*cnv5op|%}^pm4A z->(js-w&#uV$1KUE?v5mH!tl`bkF2N7Wzz98O458e{Z^8N)ULy>7^-~dbn(4_!0AF z*T*Le#J8`NWNUL`)ZM7l(rhO+?U6^s+%HF213TT6UM=}9#%g1|ck?;vm-gx3Hh44s z6J@k*h|*@+`S$f(qsgn^8)p$)+V<#9_!n5qD|+XTXHq`-JQ=ba~vLL_?*{^5vWdn<}>fayW+Fe zlXdT&==y%|@!HAvgukrKzc<~tf7!->M}g9Pry5<1bURCK9l3uaLgxCKu-Rv;&Sto( z&97bfMc!!7MV+dg|A)@}H2S=@_3ocoSB{rwuH=99<#>51q2VKFPA+s!=D~_nQ#6Y% zII^4msEWNOzi#ov2`s{zrH9x-97Fb zR^Pf(ZKAb_*?9HN*#{+EsupjXeQ;6nL22>*!jE$2?Co^TQCJ-(f779Eub$3=NeoQ4dHCA}u<*)s*4c1iunxxFQGvQESX zhimKO^-rd3Iy%36#^RR4i8r&??|rg;|KGK9s$Q*}bgCm~RgYxiJ~d54?L6lh?aZ=D z+s(pC&lOuA_hjE()r z-y(1F(f(GJi6MK`-dl4rr#f-G)U^42r`V|GN5M>!%%H`6rcP1Z;AtMFz7JEi!%rP* zcejT6PvHcs-2IoZ`JvKzIamC z@sa|ug7e%9<;AT{*4QeTNSdEJ6L|mr%hPke-_2CYJ9DTd+T+0AlWImU7GKOTuUanX zkDzVrMT2t^It+w9Rz! zE}c2TriM57B+cCO`#|oeFaPV@?^S;=TX}EOK`XP|&q}p3Iu|v1yk{wYe$7Fq?{oN$ zn415E46}3APqg^8+ke6F?3343#BOg|D66<+dE>FN@;im?(_RKIQGd%R)&N>r(Nw2f z{OrudnxEzOYr{{SIu*6&!;4>qpe?6$|4zrByz}$@=_&tr`7CN}oppBK(p7GDx8qc2 zYiBP}nl~jvD|OCiy=!XOTFd^n>2O@pYr1kLZ_~@tz$fRsH z;>yA1zRSY)^z6MX{3bWRPq%&FS?!}ujp?_IibHn%zj}AR>E|%xa?|6_=7k-t+5LB3 zyNsstw>!Hpp0dl?o%h&m=i^4n*sp6{IbI%WS-1Don%BENd=z%lIq_h2(N}xW2LH0nU#E<^)BPZ?vqK^3ul|X*tmP^MB}>b zUDsX09<%k%mOJ;`<3z`IzB}*Ox|8>pw0x1@xjW}a%E}|FS4)XjUYMaFxqjkW?dd0% zZ%#XLP1Jj7$$ftut)4#Wa4lH4tbyP2Ei^sQ{>GGPInu&LKGsRb}TD9doqhd>m zs+P8P=#2W;Yqvl7eBSrUnS+M+)n)jd?Cu2%XgO`Yf~ zYIu_)ai7{kLGzCxZu(j~Zts+RR({TO{q>n^W^LWTtF_`I|HP$NKeim1toeAyb~e3U zr>%kJQq|0n`~CL#`b|IXpZ4$RqP!1Mb2#|-L@p?Gdit#An#?Cf-sZJCF>LKkmFe@U=eQ{KXsEWlvH8*I>|y?}_hvV! zP?}Y8zW(p)`00DU-`jm{^|hRevmz#-eC#_ffsdz3!Dn?$ZZgxuO(#;AgO&D;IfZ0Da(r%MZOeEp)ZWa}0a zrX~TWGF8wHhWdXsy|vlCkHa6Vs!7~t^?CO56aQmA7fV{@tnfN7<*v-wsXA%XRx3U0 zq_7#+(>6$o-kW>)&Y7=04E?ti_9RR9F5Y-(&+JPMYuXR+yW7>7^>5azJTrMyBxf7b zB+s2{8~Kz^J-_qKjOlpPgW~PKt7{m4Tjbhg3%&N0+q?P7ZQX=B*V8su#4`3)@;WX! z-?3z4Z9vaUFXK0N>%Quono-L?B~bX=#HrlR%`NheW&f%9YgYNGwfyNHWA*o^w(}}3 zX?J9|RlM8s|L57go4#Bwt&I2GU-$RPL3a5o+4dTRKFJQrM=Q@tY>~D|Z!M18(Y7*C zIFt$ZzN8cMw$At@l5f#!#n4NgvrXw2l*`C&hTV+J@@e8DM7_M?S~X4 z&FbWTKKi)aJMeworrN(!o+07awKlKeo+GxAo+>HdzO+l9b!FCZfUN~sc>ML93H6-&`(p1^Jnjv94>~TMZH3Zo>+*GHW|?|_ zei|)&{%On4Ntc3t-g=pSLiYPT{rfi0ql1E?7G;^fJ1)bQ_tST(X^@zA#x5yf+>H{VM+BQ1FEOV-g`fj^H|PPyr}IHllPPS{@2&s!(m>ioB_ zLUQ*?IA-4L*tWBPjADb?pPr$2HjrYr|XSA*zk^HaA!OTF`W-{4X6 zY-akY8yk~f?@hWKah&Ibfx+d4;(42!b=&X9_GQevl;HCD@!^FHA;*G)L_KD04E0iZ zT)N|ghe0+=<>iVKZ;rY?T(Z~XV1t$7gGVc5Hzv=!tHY3D+W)U1`24iac+1dfd;U#2 zGyC6yDY-G~GktdLYrbn6uBAG4w)u&sP4TTtqhAyBZ?kRETYSON;tcPx zwTwP9mrM)ra*%$ytCc-A#o=F#(Z!U!6h}u#^9oSy{`%&Q!o_#0Ua$SMSpKiaukY{A zd+KMJW~>2K@}eO>bs{&pNFNA0957}1DKq&Q`xkP3Kat!wb^gCk*Ec=?!MV!F*~b09 zehAxj72_aD|5Mu{?r?oRX8Jz$>}rMn9a4|yv|K8GN~%TM=kNF>^E~n7C(}>o z_N0QAx|dwrE~&U=HYBcQzpIG4H)F5H`v>P!&wqb=d;2x*V-Clkp0`fC^Yq+o#VLYU zA8y&OH*#-w!?F#nf|qr!@x19_b~@g(FUNs_-O6Ed#O9g4EL)g;pTD?paACBDY_~Y`@?+X@gN|u%R+M9l5w)o_!E~QJ?{4Q9R6ZdZ-kBoI3SJl^Z zme1DCb!IR5c4M!Ovq_aJ^PLHTuMIW$Zno`M8L@TG?>8m~ogADK3VI_HpXduadHA#a z-0eMGFI3&uh5gvw3D-W{OrL+U`u*PPv)YB6JlY#AdFRk zcKNy`cWRFBe{xAaPVX-p&z56~(%f2%qK~$R9{(DY8vD6p^TjE8h z*M8B9v?~{9DgCVReBSq>r)!ntww9ST+|)~Q3|`7BzW1DC?!7(rJ&m{T>@gDI@+`es z7PaJGE3xV7^XAXh{};oqeq6SEPLHJV zvVYwr%*LH3HIbGU(u(}<57-dY=!&dPv zjiz;Nt9LI=I4966c7l;zUa8#9#`oPkv)FI<%I(+d&)|AxroAslIX7w3UE${s=lEA& zJyB6*KK=09jVV#`$wp6o*YvKAX{&gD=Szi;kZi6Ky%n$1sW-u|l?~D1u`|d!@>7x^Vh1Bl$c#zh+E?(-~*0`E(p1izn z%US!M&**+)`QgZV?(liob7sGrs_(xu>X)hFmE`-K5mR-l-U-h9xO;WV<4OEQ&0h6=`(#D3p4A-C(U|dY z(a#?ymK~Pu?#V5OeMFR71diX@njN}CUt4>zhkw+WHeTtdKN3zHFF7uJDLpsG^3#=I z|CdZ9*~QP4C$4z2_r1!`*LT04{(E`;^kzPLBXx`4NluEZ!WAwpv(ig7^%TALu&ljU zuGo0)^vskM!Vf2^&Y!lb<>s}WuY0xcx!hZEAy+vkVV2jD;8~XaGi1v5&(?kw!+X)_ z+JV$tJ7?{jpeWw8%D(bf)!QeP{&UI~?X8}(UHbK+@84$K&R;8EYkQ?6@W?#v?GqNX zuP%;E)+%<}Q0MG^qQmxE)|N^9zjfEIm^M8w{(>NAWB*FfcF|o15*DCg04v;NAF8xn>0rdqFZ;8VP}>y7Q@X3~ss6(XNp{=kv2gi^~0y!+nac#mw1bKBc&u^_!S6!)M+rYZlAB`}y(s9G&$4 z_ic;LOquU_KT%m#g3I*HisP@(rlcI-b^b~3{=ZT8{$2m?xhKheZIRddDM8+Hfi#YvxPY($DpO)U*&f2v+e%5a7`)Mr`0IKF z*kSYF+X=O`-iK|^g%@*NjNbfiZa_YJNd`m9!39aLg!b;Am2H!KtX)sRT}6QJ!|ZD6X4QQ7ayTeBGI0OIHI@RoUw+-({ePnGX|YrP?(c9pCi5+IeTXnO=Z(#F zy2rZB<{kegb?JVoK*ssh*a?pQps~bPo2$NN?fm=g_S1IzKMTKkB&ykFa_L-9emqma zsZ8|6@=xDx=f8F;&X)abAkZtfd3VkGQ~#%}ulwu6>8>>I@NFOe9uHmxYv+P;y$a#f z<_M|B1?HmX+Nm;QW?`R~)Im#rt>ZoQ>p z_UW=o!q!7IR_1q4rCz#TcK-U|<*TeWPm|=2DBoAxusX-=`Em2_PD{7WU}Zfc$-M3U z%+3Rb$F%J8tae^cJnrG>x$>N#bac$j?H_`Br|9Zw-3ZM$ck; z-_L(XS*5Ct7wm9wt={o+@52Rre7^g3OFGR|v$tX>_D#=~+sk(>pe<8A$4fN!-U89* zYdhX~IW|A+cvF6>vglUn_7f9qzvW%>jo(|_eP-YD^PisOntSZobg1LqEsm2s?jNn= zCR>N~?%6o~7+cwi`Ny3=m*o6%$l3e#TC`-j^DNdQ8?L2T3p;s4AB>RiJqJ1&@P6!C z!RLv0Bu!E}{=C^G{bXbK{VBoct=ydwmCwD7IX?eMlI$bXlUqWL2yYWyqrdD}_lm-# zT^XsxI#Z`bD6RC3Q@oO0va)wm;mtSevTnH?i4foW?9y_TnFpWi$R$6IyylQP+5f7| zQ@L~>pES9)S4n4H78#oU>zIA(;ukZ`yO_=OIyLKO{^{cDv1+TI?@blw?(ukkj{ThEtfOuZ z`nLOKN*C9OiHcqn_2N*hESUL4RJ3>Z4GuRzdKC} z&5o+779HDuZu;@`bCW*r`&`@UviM%zpGTXs>$)s?T)v&E2s&D6zTW7wf%25yC&VnC zJe=Y#98v6eIl|b!`17(klBcsDFJ;>KYqp8l@s9t?By--bO5a?N@I0q9L`7!z?EcmZ zo4t9%ljeTiX&)LZzqj+~$K)fm$qhw@mfr&N7!R)#`tki~Uwpsi+>h^yMVWpD#9!Se zF0T@K^RsumxnS|+Y3lKFHebG<-!fqyxR%fWO=izu6EnZOfaCeM@B9D%Rf|{r^k6PW zo@u`93CqAcQ+;U>R~}hjRZP13^~B!mL8or{zq`2Hf4gxwjMobL(&qo= zn^152Ye~%aq!$^TpX+mzZL?nWJh-TVS1nK6;#O3umbHp> zn&`3kz3H6h|9id)9#?Idzy~@_F?Ndjy!pCUO+W5&x6itwVfp2P^X!_|0;?9A?nc4g zbA=tfmG*A(RNkVjE~jM6R=4ct1j#jt*0MaSCg0of%5Z{9=2Ei>ThCSNaD372TICTh z!YR4%_VXtOypg%JyR&ZZ>yVhS?$V6oOLO{mcZz(~T66c#h39eR zOnc6E<{vHc_&kqUZs(V<*zer_?|#m$zW;yM-+L?Pd_Q^CH#;}W(EQn=DwF!--~Sn~ zKZt8SteeLpXB*a4{lEU(IcA?rn@;`PUQ{~ErbTjwB2VRsCWUSxmwHRs6EiQ|TzmQEAYFhMWQ}(|* zM)%lQA8Ib2b4c&kLkGnvXHQrx5-}+1R%wr8sVqLZx%p6+RkYlaM|lyH%-~H_SmutrNyTVijx&5k=d6wDt83GhJYMn{?Tt~&n{=D0_|j=Z^Iy*u+jRD%t(|ap z{bkepf74&Ream?|mwTs;=&f5)_e8#1CLinZ{IUK_ zq0IBv$6ea%7VLhvD?235HT_ld{-@vX6raCRzo_r=o`i&^*Vm%+p9&xCxFY3y&8Fp* zkD9af`%T)lU+wq%-?#bibSv+;Z0L;ZD<4>_cU|5g__p&L`&<4wpF6f1E*9qglGU+E zBj`)ejqg|c`Actn)-(8$W-!6|Kzd<(vSYT)=dF)Se<=!o{rT&lWq-1ed6mwk_Vvzt zdK8Mb?|6Rs#KP;cNeibx&c1q=gFEf(w;go{)K*qTMV(2uU{naSK6&-c6Q{TD3}uBm z!lz2Nc+8!!b+XLGdHPkJpF6YO<+Hz7c3Zyc;6GohK4aU0#(RZoir4QIn1B5`eYTus z*WuW*o2`6SFBS;eTU*@N_(LRO56>yF(~?tGNJpNLijjz_h@9KpB*3&+)LY<{;+fOu zm+Ww2XSt$jF|t2L(0D&4Ga<3Sc@ zclYBR_t}pZ%CIvXW{52~$hsxzXjfpT^0_O^V)QP*y1IJ#x5*_k&smcXxAE3A{dl+g z{j>Y#iy8lhM^{_#{V4lB>H43K>T_jMvcCjeJFarkz>0m1t=O-n^NL!lHt>Wk@6`jjzZvz}JL;|Py}0(RbnoK(%a-o+ z4sjNXTfOA>WzLToNsfK8>ON1t-Yos!tTFkdS?%tRGVgc&KY8&_ae>TnNmIQG*5_27 z@SES&Hq_R&`1xe=&4Y$PI~H6zy1e1@752ak)zr{hCLZ^D5o}7_-#h9A73KJNI^JvO zysZ{|y7qv`T^k)MDTyaO z3b~oC<=DpLcjdo`KD*VUv+tKaw)+wJYKH0ZrQJyjqL!w=+ugnI`^WDWHXWCH^!nX~ zjKagycdcEU8}h#Su|=P9LgX9%x)03pQmE+NObs~Opu3bLR7>Ao%AA-{^R=$Z{n)fewQpSi2| z>0EQOPwv#N()q<`+T;Hd2OSbJFZl~rZ{o4y=j!Rnm+x=9Dt$w?8v6%OqmF%zDpU>d+y&%^|4u*wmm^MG% zo2(_7SK0CT%^ZFHfcTYOwO6&ihOOGNG-TnwO;4^azTa8%&N=S(iC;&)_rIx}S9Wd9 z?^(^}uim@yMq1t0xjt#`#y>TMN{&heUpBA!R%F_}D*Q&y`v2S$m;L(Xn8E+iv`YS3 z`8Aho9*@PVd}sdhdsO?R(RB|0F}?1Kii>KZ*-OvOQ)b)B_CUfc=Z=$h*v~}~#|yW~ zg0sfsm+99p+$xsqHGd~s3Q znTH3bEB4EIcbPn5dt`S;0ydS=Wzqg5FYJu{FmQxKD1~RN>RB zVtRdN)+>XIlk3(;O`p8vhDwo;fAC}G>Qh_Xk39Xz*_gKBWq0+3A5WYS;Mu(Nf)>S%z^BCrp02wP?TD(S*J0c7Ff(cTcPJ_L~3IzrDVE5I*hxHUCJVOuESq zhxZnfd-mV^{bR$=&)(B?9%f9v$n?BQP4#qAcI+3UjuWLbtsWe+g))c4gCUXe>=-e`HY*Q)BbW!|`Ny>879z)jl5WTS9{O?}&KLtBp~) zmC3I6L?<_}SRweP%-o7^JHK4ozPJCAL}kvy6$d`uc&-22QhedFJqOZtI_}o}e(V0* z%%bnHg-i>%<^j1RB>~wzQ;Y{Kjs$wd^$aNWr)B~z06n)YVhd-C1!)F*%Dy*hUDKXF|8QZ(oF3P^N5UT;Z=9IbaQE|%4&%J6ch@`AmJ3_P z&$C|F&r=f@cG`A+&4-07s(ZerPTR+6`K5@-`ParL&ha-=*dt5kH3WasZBn-}+}Si^ zy$WAg&!3`Shi+LG-&bFfas2K#nX?D4``A6Li!?Fw5LW4Y?3&W!wL`TLn` zpK1T^@2ggwcA!n9RP;&e#}%A6U#KpzpRkxCPFbn&T(aUD2BkEYDbH=H-a4jm&9s;- z_t9)`s>`jAAL(lI+;eBzw7q-n6}E&+t?u~060s|S`HP>ac+W7OYiF1D`-Fsg~7P7^w1#f@jzG2b%W6YCPS0CB$A*8Y5 zv+jMG`u@V5*6#KvXQT_iG}X*qG}Ac!lO4nHLYef}J3pP$&b+&;bg@wIoR!RCpv0e} z8)4SFa*e2VSkJ5Xo@Ub*E5}vw#D005-E1L0=TnB3$u+iEh8{by%|dtH3PwEZnlUR# zbfSS!ZS(Cf;VOrgt?yaSzG8RCGTz$MTX({j{mW{7oOXjp#OI~i!#fw8kF;paa4cLY zU2yJm$>v?pBaa-vzOCl!o#%mlE0(ps`g6{}|WPL}NOG|SVi{O{V%cWiK zMdaD8kW4i>c#kWHH z-}l(UPq!c-zVvFS4wJ=Y?mxAA-|zdq?E2&~ne;n5zu&8VnQs64=88|xE-v4)b^GPt z_TMzm&2Dd=*BNuy>|yl9Kc$6BUzbiY@q3r*GiQ5D<++*iD}v%#{VIRniC(wCf?sgH z`ozVYNt5rm2KdJ*%``T%v@ZIleen3YB)j|i-_s^l_MguFx9i%|YsGGR>GH1s#m~T(JyDV_-6;Km{7hLxyot|cQ>(1TVpE`E$ z+Ldu}k!x~Yn85SRFH4kKvJHg|u2-6@%Y58yCe^8=V{!3okcG3)fxgdAduPo#JmuN- zV!4Gnvqd+?b9t^@{K%*3w3^S9s>(mN4)?k1KTf*Z<+*#8>(dR@&39ENEx%!!F1Poq z_kX|NGNm&5ZCR{xuU9Ug_vlOtD3#aj`1x#h<-+*4oPp`9nkO4wkFWn5Qa`KjagOu4 zz2BmCJ)IU^vHqlo>~Ri6+PBOyT~vtm2RJ<(M=lXHkyfk z5mS=eY!NROxNmvP!_yxnZ+_m<_xAXv{nZ<<%zbm;NOh0cwWSZ|Y|jX|xaj6u{=&Oe zt9RsYO5S$+!)K4O&6^(S1)n?F$s8S!eSX)y64MgbwH;4wN)wW*YRV4A{PI<9Zv3?s{_jtG7q#en z+`|THatdu+xqJGr-=}q3J1ajwEBy8|V&aV1CQC1O?mi%PY|#nBi`Io3*yY>B@{@!g z>Qvft2tQ%8_PX*Z&o!`P&&H$9R*Poz7!}Q&=5lX^YO6TwN@gXI@~{`K>ng1y1FFxQ zENril6FGJ8qmn;Et1-*3B1!H!tM(Xt>z)5o-RjGcV9m*Ax9U7H%eiqN&tLqd?D4{5 z>^fYoS9tBLFSGxVy}P6E@t4X&eUE#zr^gg=+I~DDym;1uCJX!OcQ(I{{S?0xcw9#P z_~P#PwI?0tCtNnNJElXA8_r6&mpU`eQ=Z6gU zs?*`wb3R%v)Zw3O6Vb6Ka7TXklFvK(oppG{c6~D07uqNXhs4$GPk%1#4}Naz zuas@S^q9s!yG^^)C!hQ3o_=ES?a!OfZ<@?AUFA~!n*;uFua8VnO{=mDR8;rM37@H_ zeYyJh@2;g=5)*U0)V3Jk_|t z<9~jB-u_GS>E_H+%O)P5p0rR-)JN$O_eSfxGCYMZL+#y8vd-=L9Atmew8D~Kzg~37 z3EsFGy?I9->dAAST)cGA(KQ+-GaqEFjOUttr@q#3Mv9JCYG!1Ih030H9TJZwbI#P6 z`|IKPOWuC9$=(y?uE*{!TigAm@3F<>6SC!Z9J8;jkvv{qqGt3X`q!P}^S)12PdMc3L_9Unyw2g<}>_L#BVGv$l=-=|vL z?*+apUQ*t-Y=Q* zSmdDG`lC=OczR9tLu<8Tr{AhbY0TW`7JZs&#=gjk`U{^oWz6@wc;;qt^gnMm`yF|I zpK<+=LOSeNKdg=T6CRtCqJ*y5=UJbXSoGd zHZBvyzwezXt@5eO;(XUDl~8Wzp)yDqXvV~dN$_N0I{I-gGK1b$e%;fcna zg3cH_lg;<|d}8D^!X7T!-WAeS(7EfSe)x`pb19cfKK-_HJHG#E?3A-lXIs7WU9$hz ztJN#lug}lEFLk_dPA31I&;2%^I*#A*QO?`*@mS`U9NFWN{BpHl0)PEHUvKxyGweu& zT-_D#m$U5eyKa7V#&^!OTYp;qMpaF?CUn$x^4l{`{H@a#ZMSh)+Q@irZhNrYL^Gvp z%4u%paVsm%ulxLKkN5(?1z)|&1)e!uFYbI-Sf6~ZJ}x0Tb>h94XO}cOg;yQpUix79 zi|!vPOx^dC_C?!=K7RN7_oV;a-?XFsW@kMAC8Jz;kE!$!%eFiFB@cx>N?&__&c95t ze^;+9J+9Pdwd-k)84{ervGius%Ge$)N`GE|HC%bQbQAAdZ5 zxlksZ15&#GJ*In@>&xSZ3!cxIy+%YgXh(-}W9zS9*S))CrK@>LBcIH9;OHF9AwB2i zN!f=f407svADj$DS;|&kYATNJ-v6XT;*)?YCZxMO<@SDrGrx`{1vd0VO9Gl{$D*j?Q=a1c; zcQTF_&Y4(rJ9qoXdtDc=+f>+g;Q#a%dkpdckp{mreet>I{p*dYy8HU3#}n)HWm>yFeVi0wTyyi{vCG^-6IXqYy!_JBUV7P)(z%kmU)(g9 zwy4tj%?+b{B0BLq^OpSQkv(4McJxN`D%sY$=#rR3x$38?zO+S2f?hg9~(UOhHE%}H0#8Y&ZX-u8=ytgUv(~IDcQ%hFr&V8E1 zr}rzw$~AoMf7w-2eo1$^?ReWIs{Nu(I&VR8pJm#ON9PM=j%V^2oix1JvY-Flm9)E7 z;?6S1B`x>=Je&Vw55=K89Sr@Ajr=J&SZEouBuI z!IpR5zJ{ypW1IO|=}Nb-Y;EJ4os(QQBvx5S%-<5ud8jnWa+2S&Yaa7&^ay?5 z!};FA=JnnU5@JfV0(X8o32r_+(@w*zv~cI^(q==iUCU>gJJ+f0E3sSi;F|xF-xIjk z+jl>o{Zx9V-uFzg_{#}fa|$lK7WAlJS<0~A<;Cp+@gMfDqgAiDsU3b=|4rmZ&$7LG z`{r4nQL&QCU+aFyqHlBWhjqsa)gSy)#JGURKHDdro~7O8eI({{f$@gLj*=v&H&-03ob%%D_XEu@c3I0W zIjVK8eea^c_e$Nap^sdrS$@~KrJO0>9LKgyO=9i^V@0va_7xwqABFj|R12OyI(Pld zZxfa0?Uv{+3N*L={lj$j-CrwQ0>t;uU2OIB#$?rP7Mcp&ak-l;@7T4){$bwZaV@Fn z&5bqjt8!;=iSAOHd-mbE<2--o>#r`|dfVyd2g&Q5-zF8#7n2R&yI8IxI&Pk8jpd@e zH$^)g?y0}F$vt#1DdyupQ6tG$uVzi$v0bX+_^mBFzgPY*e`|5>%K^}+zss4qcTLwg z@E<=>(Gaz4f^V?jTvm;QfRbgwFE`vU4SbpGzjtcTvj-i?sY^P9)Z0_fzHU-fI#u{> z@3inLTkUlUmp(JedtKl0W^Ku1_ukLXz8W|srVCnc`W&`mz1Lf_g{!7K_0d>#HnT}7 ze3SI*E74k?S7_b8`anVU`?KNUGS^ng^Q#j}OTyMRdEn#b;OtqfZm`z_CeE)~ot{>O;KYrZ1IB3ZM@$XyT zK1xcAlf5qU|Ky=PU-WmIwm-+nb;JTj^p7x#aDZK1$KZWCtn^KRv?c^1o z?yyIS*+_TnFjv$H*Hv=2eVgTD^rLFWqI<`Bd_S4Eeo0T1Q|HYQTywYGG(=~C`_E4o zJzJ&HYhN68d9(c_gJP$t@2+EtaVw)1rEWA7Q&-9Fv7LNNr#Acdo4;OFQI-#zzVoYx zZkj1lTDIl-?SpLwlRC?e7tY!EfNAezAEmN5gZ}B0w=s2pKE^FFeGeI{|y;^k2-PFvg?qgREj*dOw&DPogKXWYN?^oyMN zwwrf)*jso!Et>i7$8q~h$9koYzg}A;b9`o5OzG9ocZ=pfDk(4Tzn!w+u|@Fo`G20M z%e04TvzW8nxNu$WnDfdaKCa(xY)*9>vTJhcm__@cmL9} zbhjRhE&`JVcq_x6P@%XQQci%0HGo>I}GEAviois-+c_)xp0*yUD-fxdk0Q?2w%#aDWd=B)DI ze<*8zSas9)Q%Q+JlP6d_eaiey;oQ3CwlSAZKKOAnLqz{e&>7qFhQ_=2&&VYwELc_B z=Vux%SDUh|$wI!$`t^cWN2)DPC+gMj_~6^YzipM`%1?@mmOY!Xc;YjQ|MqRdH75RX z599o$eKXCMPCs%z(P8$6-#mMB-0thY%q*O=RC)f=ZBCyxGxn`_Ty$^WrH=n=E#bYd ziRV)@AMzDP%YB@t8+~l+Lf13B9UnWBSzLYMTuY*ND6iP+xcT3tnNFckuO3Wz+z~WI zHGK0ep8k`5wI3Z@o_D`woTISe&vCaI0`e_?J6`N@JnONv>hIH88L-Rpd0ulo^II+Yer@5O>s&2UBC&LqjzPVaqB%gxf6Lbx-f$I_D&JLGW_Vzh!lS94M7)G*3W7=W1D}y zT_Rp`MbZ6(d6lKtXWy1L5qieE#sgW?aaa zx%$@An^#f{ZHf*#R%!pSh;vmFwA*H_)3u#*-O3|&Q)@zhe_%B2mMqlgNW6C@MZ(E_ zj+TGh>)?qQi+DtfCGAd5I-l0oyW`;uh2w>DavMN>fs+;m_m4V%7F6C~^8V9lefjO_ zsd^Jr1SjE+X12bZp& z&irSN@dwj>yK~JwD@%`@E>}CJ9xCqg?N_wx(R7t_w--g8?(E?X@7yi9WM0tBxnHKS z&OG<*<=pxI#b<4Q?5C?PH?QWC=dRD^tdGC_3o5Pr?v!4S-BS0rs{EqAqd{hyP3G~! zIkUQI3nsiyPdWGfTVGA0x>_{9?IYEam-6{fk0~6Pw)*!Sm*{(yOTFXITJk4~*ZRBL zc^h|EzDhX0M`A{j!X#&h=S$Z&yDV*Fe5Dz>V!zHIE2TV_8Pi|JbT(dmf7LYqFI$yz zq+0Y-{&O4Gzuwq1bA{Wnb*)FsWrVHYeXMNwUj5-v#9W?j84E5vzS8|H?Wg(q(6gdf z-mc%yDQI3L@o>V=r9w;N_I^`VO7*vjnkPL!K+rk-Q~kC6q(ujlf^+j3^p5;KvvHM- z{}jb{nS%MjC)E0{Hr-m8d%SSY$HSoNIz^`N#5|oKb7odCmMW2i$BbNZIyReX?6jQ& zcYMl7H#>c_m*Yo}Z0n-V*Bh4DOn!9n$S;-C_uNzhS)%I_Eesb}P6%UoTv*g`_o3mX z2NMId>w3lGJh`4fNu8W*RBjh0dsRSue^sN%+#;cakMeo`$h`ac&*)K})*sV#cMYrg zy1%@QHIC}~A~0iK#iP!T6}v$VB~by%e}A6a?{=_1df@)8k9Sr+wm5g?;lpG{iy%T(+pc~N8IU)JEf-YC3eVZ%5l%>F>a4@xgzWGe^+he`CXK>=x(7*dNjDU zHtMUn7%mD$W?IFK*OG3fvqe!zaGl z$}H=sP`}h6QF+~u50~2hGPYi;P}!e(YMIJ;Lr0lEe6h1Tzh|XY zXgB@z<#zkOmcJhD|5u&#?qY4kqpyOO15Ql8-cu`bv+jBBgp1WPJo1h$jM#rH z*3qZ^MBJ*7mAm&Jx@dM{$#$74vD;S-`@b>X-)uZ@^W)CP1uF4zGVc%GO7ZR6d48+7 zB%e*#%8-RZ?uCbr+Iuw1lrI)OR$BSlc}4uKRZTX>pKg%U>3V*!_pB;^i1&2PjF}tO zi9eI({CYBXvA3!4)ia@w4s3ruVaKP7ylrwuYpbt(`Rnq(LzClO=GN~=&TqIO9xV8x z{CCy%Eq{KuEWa3bhWGm>>%PZ5oS<>JNgHo3dYgIu=n1a=aB&WsX?WzHjYEBb_th z>=9Rphd4zVbkMyAXPV=E@OS24 z_4mRTY?*%ae3w=;_Gv1A@|5}g=Vf1|qqx3QOi0@JRVfnG)_nwO>ly_l?D_XZd2-T{ zz>h{29{YbB)xR;*HqK$ecAfnxnK?q?&GQ|XSOe1<>{Fw%toCT1Pf;C$hCid@pRX;`26nd!=8-ApWL@fzh9gC<(6xdt@ZP> zoQIBXsL6M-jnRAK()rGPnx$Zc1_|`2LqaPxDg#ogEYF{{Oz;A8j-7 zvBkNPhud!FNiSPjrp9%+^YaQuxvCcnJBxPoJ@#QJNaDX7G}}C1?&8G<-+eZ&?cMkF zaQDk2*Z1h%Q7hXwyZcrZuf?<5pCV_4p4q%uiYc%5Sk0#Yrxw{R-Py3B>E6=qK}&o0 zs=m?;cgou%SvJ92-*D!o=NmfK{rq&z|3RJ4jy~6_?){9tpPm&;-!b=bTDr;NnfFwq zsV2YU)Ebtm8*1k8lJZ{i5^N&4|%h0=_ZMFx{of9!p_;#y3b|B-~3`Yyjqv#;|+ zd!Lzg(Qr|HGidViD5z>_ne*?};uVY6Uifk%@QCf_GsYz^mrn1yy}6mY(Y5N_EdQ8S zri5P@KDXA z-v<&>o+(IlGyIzR&ui+n?_Yw=l~T2?Zg_H)CAWrA+}iqC#aF{;crS zvDb4?4YNG9Y`=U{&CI!yjuCu zyw2sKulTp^d}r6v_G{1Ifbjl@CC#>Nd%1J|opP8Uo-FyxyQWt{dN!MTeb;vxr`mAC zZ-G|=n`Fw8iy?gfx ztJ`vYW4<)FR+k=2HWA?y5!v0b(6Mh%>gCO+RZdlzr0=nKH2LVppc|gsthq9DK5oqU zy`e{}&_ZV7a8atp2eEPQs>e%rYpZ`LnLs&>Epo$vi^W_}xoe*1qvcI?gXdu$Ob`{u&| zkxL#&q@TAc&z7y*d2gC-v_<-@LK*f1B?U#G8}UsN;mdqQYo zlEd_ap9(*GpVg>;&`fG=@NC(5?T+Jh-*+wDsJmkC)V+0DbIk=_w>!sm7Px=r&pmgL9(c*M0+yo%OOmb^7_d z0#j~&iMo(gmVBcA3aj0o2FnIeD2lN)sc5HT94nh=nLle(Y9c;mRa$8`~5=3zYnH| zUr2P96J2+1(ORp^me<%Os=qWjKKD%C>CZwDUlg6LBxkS(&1RKfC+1Sc@h-dSSElii zqO!H>U-xr-W`4A6eG+5N)?|x^-S@JX<+qa!=(Sp1nHz+N9H>tX|G$TTK!h`m(WY&NwJ}38@UVR-esorxBpv^ zwJK%>^$5Okyj_bGye8;Rqtq1 zruHvouUhn#joe!+&P{2Ti1(fIN$1dO_T5i!?u~yvMf~7hvwdb87pUK=IbHMfVMp|a zjUAuQ#(e7e7WZlEpZJ1z{HK@_e8Q7INzL!JjQo@>EIh|`{@H+ZMeom6U3=_#lkEI1 ztcl!Qb;EOylb!$V^KWI|se+bL3WH}gQr*&}cfSa%F=2MScx!9+%VhcAH+I;1i!43y zG1+{#Zl|xBBXdR0pE;kOZkEhC-<{m(T6T7}yACgxX3yjzz9r9Cl*Eo3$(~KrPqLbJ za@mfKuDM@}CMlflpM26IPbEIyU%?XF%dY{(0b|urP%eK$`+`lDbeQlIpZ)@|vW77FD+rOVHlu6gxoON}TVJG9c zZSBSjZ{KdORnTepBCOl@Si;uUc1zvgU(@tEES{Gyc^_9G_;yFZ@(G6W(#02aH(H#( zd?0dGcgEyha#}m<-mY64v4p?xdq~&OA6M_zZdtf}e$~bqqD{NHJ`0>tT(mjU-8HrN z)Agl!T?dmKYhh~vEIXzjTXlQis_mhTa!h+_6`bSF*axmz*7fn>^I2!}xHE+rqvMXb z1)99MlV5goMdloP`z)!s9uL$P7&=t7|BlHJns#pH^)G*&KlX0C z=PqAca_64)@xnPPx%{#|o#Rh;GMe;s186aLi%r?_!Z-=D+*?T4WHt}RF^P+-PM!nj7Kb~a&>6yNV?_}Tg$hhLk z**1N?AJiUxYBuTk8@F+vlO`T=i zi*Fjt%)MwyTK`x+3MW`uPnLU&kNw zncQ@TYoC--bZw1q99Q>@*&Av&{X}~AGzzn|ZoN0LCy(z^S$EmO^4e$KAsN}(8Zjq? zR&d=dI;|^H`PicGvB~nQ?@xXd;F*)l{YXY{@0UxtzZLo(`)E~FS4aQ*rzg_&rM3Q_ zl*)!jYWiMXN zofY%4s@`IQ2Ydb%W!w2j_N`?5RO1k3bUSz2BA%t1)6WJ)+qwVLS(N?ce&g3E)2!{+ z2QSXu(eHEb@$9@^nKz;m&)+Spv5V(AUbsvWoF$%bRhRWUF1xkfJ6)>F{Kr^lkFY89qTwg zK5G=;C^!A_;X7|VDxGt|}#;us&w;Urr=A80K zpDKRbOHIl8%~{X$KPPYH?&GuiWl{As`rg%|Lb+MOPcH5@zEUW&+)~2#`~At2r}Iji z{WzI%yfEuPNse*B&D-~C#peF{b@{5MzT13*yQ?RfnN95OnA~SkypQv%lI`pXOU|Sp zK08adc8^GzcYSNsMCMV`mYu_v&Ws`PgoY^PO$DjUl^iw;C0>iyXD(!tahAb{H-JRe)$`V zK4I``4@-?7|Gs`szc43qu5#C|pI?^S+v=T{cq(*^CGbt!#Jl3UX-89y_Gzjvki5CJ z;YjJ(qe>Iar)jT`{IW+wOlfxvfB(de?~l?S&T%Y$5PfZr-h`u^%scn)P>6Y3s4N^`}z;<&T>Q%UN$_ikW8Yskme&%60zHf{C4Tqv_#Q~5XdQ;Tyg zY?fc-tG=Jq6+gywWNFQF>-!7e*S@cQ{HZ43LCKSi24SovvZ_;5*J|B*-H`F--GfPm z6K>pXXj-W|d+O7TFME#M&3MA=Qg%-8Oya>cU2k74@Q?ZO=|o%{b2zDXCuMSm_mAf~=o^aY#v2WIA&Im*k0T=kCpDp7N;|KIxG zz4)2wef5S%vX5Aerh%3yEZk9D_o=C3cKqL0;gi2}TJ&x9ZE(1saj?W}`r_jMA5Vwq zZ1empH?wBbt2zFgo~w!pUo_)Yz7d^ZWq9Q4R@=W@R_&f{_I;*>zFvjkoy9d3Q#U>} zI=STi^P}ru&FEjpeewP;#xJ}z_J0;=Z9J@d-s5|sZHT}r-IH#Digr6)i}t!iJ)f5< zvDCKiO5!>#ljs*#OiAm5n{4mTTJCRRA#msOwZ)Ee+83^#ae8CNxmrb~{DdD~%Wv|S zt=2vN?q}L=_PiYr+oo|RIfkc8zulYikYgTbOyUM84=m#lc=7#y`o|64Hv||zob<1o zv~KsiU4@m;e(D@4?wsN|=gq5m*LkW0V!ou^z3|3G%yhjIEREXlehaF&{rGlr>ceyAQ=PB)o;cZFe8?_jhk1D0%$H|dgM-D7 z{rs})f>}?5+vg2Gf;PQKZPYpN{ZZY-6Vm6FwtK99JiB;%yhD1;pO{mkT)y`Y2d#7g z-RB#5qCn<&Vb;O>ZD~B)7hI`Kyt$)L`Tgland6-Y-+zrQzq|EWxkcUl_j}LpE4lMg ze|_g>!{C6o`?LxJ7l%u%tllSgg5DuwSiSKHYcbWgi3-}d*#4*T74Cl<<|DySEJ zE&HtZQQi!(enT~-t@rl%_=%nHXLH`%_BX}IH1@>P9qC(KxBtjdPu(C=mRH?aeJw4f z_Ul#M-`acPy2Z{t?<@sPd$fY4JzO=!ioU#EKgV-(d*?34iQ6+Tt5w~;eb0^G_REDZ z`|2rPCnmQSE0&$>UTu4FDZ9WLUq=4N^Iv&Qk2xVD8sPAGQuxUU?HT=xq?EhgR_=KD z@P_}p=xP5|D!+6V$L~>lbEy4y+uxm~I( zxxdcwoS)*l$mIBUjVlkA9jsX6`cUR$*W{NggyXIny-GVf%k&GVQ?vTMIjAknkg`f5`y;!eSC&yPsWv7?F{44*YA@k&wKvSM?o31W-c!m43vXt+Nt-sCd zt(Cslh#03F*Rj78%bX)HdEUi$`in2FyfO9K%=4H2^eOr0m{;5Q%=rKFe0|+Y%0e1bny>|ckmj^5M|JnO&0Yl8b8q2-EuC8B}KCd!ubv&cULD@>d zmp2*ncfK!W+IIB9k;T*BNJ_RO#}&O$JaG8ofz!1gjCGIvG}8H@`RoUCtJu2Zi+eQo z>8_63{LHl@yxsP`{JPnk-8pAtlUetqhRvF6cV*447iN81ANExJeLM4*z|vhmfBsC$ zYUsX~F>~@W+hfVgcQA2PiF_`&{vi9$n={4x9B%7J+}Ogi0{xW?#?y`umwh zpYc=;v2NYiFXxW1JUWp&J=V;S|6Ji5aT%98Z}xt_mzb!-@+*8!$)t|=Hj%R>$^#Y` zZ{hU6?sR5)W=yA<3_ZbpkvjSi|#L5g#9GM z)oT@JKV;%~yf{cBdt*wVL6pfgo!+fZMyh_rQ)WJzHhqr+#|Mo=ikFJ_?G?Kr`%~wv z#uF1Mc0ub*tq0A84G}ZMulohP zon?{#A!F^W;(|yG&0wWtw@6DWq+G^cZ6SKVb7*Eyx^!+ZCrCg>=-2(r=EVr+` zx9PEkAB)SJhsVCn?PUGOmt=J2ajwkq%%h6!GEUa-_iVoM{-sH2Sy`*q>}r;1uGLAl z|HM8NMf^Q*eKBLKXa3GZkN&jn6RMY0%3U?1Wog1D!^C;4f4rtD>o_mz<$NWTFTDFA zt3>spz7|QW>4apHf?yW1~rES}ai;|`NZ5WoE&gSi!tI!k`P-JbtdyHTdH zM>we`WX{`ZY?)hQHd&bMcwyRo@4?cQD@E?}x0Dw?JzcB1j>A&&fYM&8mm7V=PR~En zC@=ovhim&ISti^2(~sVGuvzTavlZ#?Y~quGJ{;7{KPOXs_VM$L7WtK5!x&CoQqna1 z7}FCFv+C$QKcyQ-)VnJ8sJF}2%KSa6?U(n8g^JR^7lN$G@ zUyrMPt74sXyl`IrrkgsoPv`%e`Rv?W_s!|&7hZXgaKm2ocvhFj`g5-o?ksMea5q$$ z_2)-z^Y52-$#r}d%5MH4C6P73k>9W`IKET%TgMj7&B<}eI*F$)&J9j-2wJ||_sXmD zJJ>IAH(yxysJwShZ26sQ&Q~oiJpTA(%9Aa(Yt{Z0Up?90oPR%||BXT3&mH$SCU16K zcle^l)Z_EE`Kmqs_|vwvvrF0aQ`Z{N``%&PY0^~MvM6m({ty(03t=0VcW>5uNpxzGI~R-b&RM&MeupUsWW zQ*zE2SSFm0U7aF*^~?+BtWCW76GhekDp=>=`~C9#WJi0o+#{Ps*}Hz9);aTdGskyFjF9;`eyW4(&6v-0^$f#=D4cTU`ud*#I?tx)retCHFOc&x0?Q(M-ZxbGyh zX~TL++x{nYXTGTXo%$srvuqOl5MI`2yO1?6PTh*l`%39VH@!>eTj9|o{!;9DaY7}P7{mG-h%GJ#_ z=6k`#@@GfS@)ycG+1u+Gy%lUe{AllvmrGYnKjZZEy2qW~XQ$d%$mKFT*?NUZ{9V;v z+Xue`CdR)C68san=6WVm*|qlF6Hcpox&J%9o>{Q;>@45(+CMR6Jf@46FHgU*)UEHa z#pTwW#n1hwFSk;4c>D1HGrv#g^G6ovwxu4IEq@|a_x~^dg2VBD9?g0ob>&9k{zUJj z%F2uz-KItxB^LesHLot}>fPfPO(VFv``-o4bp1Im!&9fFGUmR~v$KxpH%=1^UA)U! zzy0cTk24;eCt30qWnTZDC$Qvj#XAAFZ+BW2D(&U`ym;ZoT%o{y7gx4U6qfmXOR})N zC6@Wu-Y>aAYNyUs+A22xPNYi$TV(PO`2MS*TIqd*EDp&>YMD-)9_4+5^_BGd}2XPpeHgJpJ>_ z52+@*t}RYtr&EG!tfP~-%UQ)<9^0P2z2o%zPcR%$}(hiV?pSRentE4kBypFG? z>WpnIdu}X!c1zx}_sE+IuX`t**gR#<)(guz^f%9CdQ;G4e)QD!q9Ql7wbopgc9zYa zY%!vjPky{oto&eJ{#5(>7sHnL*FGp+TmxDHkbD?48S!Y(qg3`w*X_RZ&ANEgOlhLr zM9ulX?>v7o_kE3e?!7&gXV&xGc`duk#7xpS^x>_TFHyB!4!YcRI-3pj{#i_yn>k7A zl5;~kd(9l@%C+cI-W=yjm{F_EEmi67i35i;7j=sd2p7BN2AT?AeitPVp&a zWjRYajl%x8uagmJf7UnsXvni$6IHi9n{YfgKlpji zOyB#~95Uv2OD>1a4Jed3K9lRrOyhJdo8HXEw?Q{|6h7Wkv9IrOkL;DJ`n4~;fAT$& zioL9PIDPfqR3p=ww|c)jnljsRJj%IN@_keEzV$H+_j$E`es;QJUFV~$h_vlJr>e51 zREd6gEp&JCZSRBmAE&$NESp`Z9wWB$-6x&bOY3G|nOeQ_`?c8<=6#7wy1m`v{KE{9 zOkrN}{;mg8`uA;k%q@II%sEOtxP>#jPE&O+f%deo7F804J_P-9{eEw9RcnBfBDWlqrTgUwwY`!mi*`tB>L1mOkIy<(o3u`-=IqVSl1FYRoO3IB zrg&$u!(k6)yMiKy{@WAJ2fF)xewpjIrtjdRGMz`;pRUxM{@CXCEekopmxcE`4jqip zZ7G|_abbsy*v+du6I&PS9B;MI*PpFpyvcR*H{+fdwO(fH`Ws38r`PICZ>qld&ilob z%lDmL@~mGc%f$86H2d0zP4UMI=e+cCcW1BX5r6s2hv&og*R!(M&6Hq&3>psjm>N4* zRw{f_#a7icqn{hNZn@O_t82MvR&ad%-AnGXf1TbWAa-bv)w74Qo_mN-J=}icQJK%D z2GjMbv-_2J_uAcSdpP^R+qoTIb0WCTZ~bQ1{3h;ma&OT^_w|CG7Qa3jbnDoW{t9M1Jb91K$?8*or`Udfp|`Z&&r_bBsnlsbTC!DQ-6Q@lg$>nf z)b13W*6mei3evn)_fO~7Da-uK%4*QcsUx5)p$3}Xe$kX)*YWJ^OR2z(T}v&??^UYH z*8llf^6%&K{*w!PAF+AIC@wiY>H3Zizb;kHPfzM4+a`}o10qm_WgY3S^1{#vCV#+*j*tp_f&STTr$;v_;(HD+|j)xLoL<=m6e^Z^i=t*#i|3kB{n|=g5I?k?h#(h!U zi_2~mpB772#VIX3cXEEtbFaP=N2)tMzc_qj&e7nj5i+aUG-8(7rfcuoz58XS`n(K{ z3(g<@A3A=|!}{%u-MKQyEB}F8M}i%@cfP4Vzi_Vj8QF=_2iz{$e`w^tFf)Cg<+2mf zBA&6Xf-eJIHr|eneG}$0yHxI;2zPU*x|wrXaz~wK(JRGM&N1f8_jP>vWbuRNpse37 z$yS?sv+K6g&UXElYmJ@7wNobjn({BUru{lgP22a0)M+rJ{>-(~*?m+$@Shyt?+p=Q z^O7&goOl!&SFrkYm&JR#SxOHoq}P6Xth#l<;?3RnK5s0J>hjkxnz$G=z$bgN_`I#= z<&;Y&W=1U7w&VG{YPD;QmVL&FmhW~vRtn#G^}5!z#nZ0+5T1QWHZz9XA)oQM&5@t~ zx;Q>h&C)sJEuN%Qs=OqxUyt*C$Ku_Vov&@nPR}k>FI;(jR{5D5M_$~$ZnMWO;q0Zb zc^=zx0(ah>AX6iHqVRCROwP|$*Bbka*UU8g=$&$0uB=>jH|rCn>ZEPAz1>!+x`p@z z+ze^kbg{+c{kKb*SzmI*DlaRrb{w4a$;~jk;%|K27sf3W4{M_K75#1b`~FbjoR#gM zLg}nU#ml^spZxaD)$g_?98t2J_B^KGAZy8$K=+He+wYpa3gsAB^EL)-J)&jvA`&M3A#w<7(Z+EX z2t8V1E^Zqi-(4*DX6c0Ow@mL`UaVHBFGlwq%tLH z>7Ki>=Cj+P9ii{O>V1(|{gp9)C)Y;3i(A*IcPuZ~I6X{Uw7b&kweWw(>G9RSuh}LZFPswyUPI`ta4x>`;Lbf?*pfbZ z954!ca&dmaAmd`gNS1(y0(Y!+T_+5t!(@sbjUr-laVtIb81CQT?U7;RDJ5SxP z`qXt}wx7k*Vo5EBlIwqd)L6N;`n36roY8pp!%E{pz@3FN4tuhkHcCmf(|%XY@lD|6 z!*%(mPkp+XWGDV8H>D+Lp0lrt-;-t2|GK@J;U8$>d2o@9`|}fzix=iqeldC>HLu!g z^3wVrf2OZoyZ5IY;~bAd@x6~NF7vwFR(N!5=8p&Ozg_mXzgwa?|FOlnPft(3T(x@L zqTBiV=YG4f_-B)!aCLO&!?gzAYy4LpDm0VHHtyKqYw_Z!>G`>CQ~ZVJGj#NP+Ld8i zbgA8Lff@UKCF{u*a%+Da4p}>^H0M#+d!`^x?r%Apq+a<4srY-Amvu67?~PKN{&fA$cJJ4lZtVGzQ}Or7 z-t0P`{lBiR&yttAXfe~|?Y4^QsBJa6XJ6zj4wtLm@Spvq#ktSNPE4^pUbsw#yKKhc zLi6>NM-$)3$a8AkG0VN>694|)o*eMyz*4@(Id@;bvAJ&OTI%Fs zn8mv6$_#-uYiH#8)H14l=QP~Q>{luJO7|PvyQ)7j6U|R7e}7_SvR%=g$8%V@Ok$?@ zo6dgtyYU78LpeiB*_|?_3i0y6j54*#i>D+;hECZoZ~a(tSM(NM`}`;8ve%?UWG(G3 zT>D{rTEh9i-4IkTSq{rSiJ_V4tnju$QyV1?B^mh)z%{=XIT=a#UZRcp_6hwnX(Gu+>wDm2SF=5)S| zGxJMD#ri0BZbAQ|tFvca*uD4D59bxD7kl{YM*HShy}f_o9QU){>)xOR5IUe4Hj}ip zaO=MxX3H-EwPvqey=azWczEf(2MX5r-|c>HXZ7_;@XOfuRpM(SH>bI*SLzDmcxc>R zsrBsD^kYe9E{iF5dYn(5Z?$S>im}_*!V`=98?Ljqn(8&5=sPj{oYI|75h~h())s~d ztug6}+D?0q-kT91Z)x=4U;FQQbHDtQDHZ!vvxsrK<2qY4<_CAqNA9aDIDLGh`6eDQ zz25?@$=n%VmoV-A=zLJF_r<3fYj;%GeOOt%GW*z>W$#1l>+SumKvlIkpTz@)Q)%vH zi*8mXMsClO1+Ajff8%J;XPo%+vHbrNZo)opvSl|CzdV-zKf^5N#s=TM?M4P3OS7gb z=dQYF6!F@YA?tHh)?%xpK2x3~6-so%)&lHR&-*5|eea#@9r^S2qzc-d44uwX+Idf8 zVVsQ3p5t0i4$pqF?&5+UYn=AkG#zc7Wgs6`yYfSBUsYw;o!BGis!rF)FAtb~Waa!j z$-OIMbw70m+gY|w^od+t?zBeZkw>x4h0O-*gD=dec&GI&)6MXFn`Z9PL&@e`w!1zY z;$FFZyZN52KWqBVS^SNt`ttE<$!ppE3ylzV64@_z%;M z7cSEm=<{eJh$m7SZPFHP_{=CX8y*EHrr##LWu^Lj>YOwX&{ZR=7fa(xd^*m2jRwo2s< zP3K*H=S0PPD3grQ)!=L1EqDFwGEVN5PwY}B>g;}7c~i%aX~7J!GccA{kGo@$#p!o5EhzqVE5;9*3FU}f#$`}&prJc-Y!$b z;qTQezGAiaRITH;r>tTQdBnQ0`OPx-6TDSZ8W{yQtbVoX_Nb) zv&3r0y7iob<~8L>j|2;2kNo^#p?UI1`sTiG8ZvtqJMF4n*T4Irdzj(_|LzVGwgEk#eg>f~?TzWs8k{=N2fyFbcBZOmd8 z?XvuJtMdJVz0-AMzEy&b=uv;Spj@Wi@|&vn%U$zp8~gsx{=1VyN;BdCi}{`E`@iF? zzTHTE30f}mZJ9=!0I%_C-SytF#yT#WC&(VXr@|*%*rL++>qu+k{Ns!ER5>m&IGP&e z;jG~PkZpTSx}4-^$$xTuJT(h0ygrd$F;mA)<>8L!7Sg>N95?nj@iSSA*UjUiNCH<9k62E48R zn3!IrPLDknygUBir|FgTM=P4n{ER4IPn*%-`+3EE-YcKK>DI3mpYb5?qjAi2_lxF^ z6T;hfOH`KBi}qN*&_DD}=Ow?B%d(W@Q%el1Smy1Ry8f+Ijf~bO{X4^)K#kLlwvKrsCzN)07izeMc5J_Bb-wG_gA#szp=#SnOtXwd*|!Q@+vBw3-rIi_ znv?X{CatN`?5cdJ-Tgw~hHrrTvAsK@pO{UP%)Bl-on8On)g3aCaVpo3)@~M_uUxW3 zVSCaf<-8v!JrC+Gld34sif{k+jj!rir~Au?zu)J+Sakfeh ztI@He@cW-5Z1vOM?f+j_cF3ylu?f>gGXdZE)$eu&+)s)UKlJC<*VjKk&#Qjt`T1FH zt=^8O=QmgHvpR6D(&9yG;^ml-a;hx5TNi9h8AFJ4!zeD~Sw&dXgK+pc}HcQJS_f5UH$ zhrhp{hl`+*+B5TANpoZ5<;>zlvcDfyS1hrg$@TfftoCfVrkd)72bgNz?fi`+XH0M1 zV5XTHd{)N#RP~XP#GEg3pk)CIqVsmPn&sZ|m~B=byw&Q!`iqzLieK*E|5;E=tmvQo zO3)5ty$jZT!OSsS$7OcPyWhK0b^P#_#LLq*=wzx)@lwq@RR3kM{KpkqEx)#H+qTTy z^8wEj`}ic`Em_mnHQcR-V#pdTzSK=fFkzM^^GL@8Wp4&Be1w<)&`_&;D00 z`WIK;IN_wU;V|cUCEIj|t^;{adpJK-$ef%aQYas29bt3&okXl!Hea>%z7RkG=U%CLh$h;GeT$_p{me z|ETSL0Xk30u>N1oJ9)FbI}$EcGa5b^A4#_TbV7N{_j}c!pIo`tP$kWsq}pEBpmmjT zW{7JI&$94PNpsOCL_O3_2-Nq*F>l2%rwk}`ZK9^HGzQ%Cr z<;-)-^VP%_KX4B55s1iPx!m&WMy=2^m7|d#A5ED4>mp08WmM+@!S+46{l0hPl`bvb z&@6oHNTJxvQprH0O{*e1E_$qda`?1SblkHgN!HyzH5bX;k$2qE-W{h@bi}s5F!9;*K`V-}3t8G@7p4BxjzgM&Cp?UblS)tm}`*bV#ckSAhvHR^d>6f=b6O}6)x<5?` z_Dj6J)Oe%gv@M`D!p1Ty7M9=2RR1@(F?FM%#li1C9`}D%iuCDxx9xV`;k&WH0qNhT zt`3p&xbyi1>kV7`89Sa|^qdhb_m}b03+7w%Z}cD9bmPR{yhy`BS^amFEB{{<7b$(I zIlIk*$*wcikbB}+jfLkY_DH{T{PC}3PlQX}7agm&*|WnY&Rn@^|C2|zH&3|TyyW<% zd0Jxc+qvGEx_nnZQugbKdQFhy(Mq?))mcY=${hJ>Q-8o#|Knn%bC0)bDb$6;TOXOC z##B2?_N%~a!PHw5&llCVb@r^U|NHvOzVCbc_x-;6{!%l)-HFM^4lG;oaK``d`g>=` zRUO%v{LlQUMc?MFph-2q;9Ix2zPu9VTiiVTom>6R--e}^`)g7Ky5{-LHoKT#|2w*G zpQZ@cmiu+TTWd~Ah-4Soo?ueee$sSmomyW?%w?{-0<4SIO^R~g%lYl(3$<4vbFw;K zdqiH!`X*4DQ+BfZ@m((;O}VoV7tZVp<7k)hD18|H@$Bh&lRTt5&rYhq}R%G0Y}oq7BIefOPybdkmV+V6M&#_xD+5j;uQ&!Tbely~#y-#GBM z>S3$+rB$J;)xI@a^%*OkNHN;-dfo0zo6p;wuAZ9o;Wx{<=co1e&-vXsea{osWSeIq zUqc@(5PxoCxA=cU0{`j0Hw{9SG5f+-#(Z~X6ux{kCHZW(<;BNOH%t?q|Kyl+gxi0{ z?dE5ke$1-oFPnV&@4|S4>$|>Mi)}o)d*_A8FH8(#KFw(IJKVW`UD2KE4-{PL@0Fcq zc^vd&Hu?>&@f{h` zecJ;c+Rkgxd$MHyquPWUy3;1cCfF$2Ml8)2{wbcYV8+oq2Ya>6;nb z|EBz@`tYP=*1vaKre9LO|NmB)|E)rq^x2bN)&5ae4!CJ9nJpf86qaar}Qd zbLsalrlPI04{kKNsq=Q@ak(!q{p)8XbI#f+_gg#k)w}fp^`UP58zr^|P2CtQa^i`n%LdbScQ%k&bi1i-B>Mu zgN~V4DDM~1cKJGsx%K~kF21+B#V#?pzVkOP$Jg1vy>w3GYlg3gk$f<3@$#65^A)@0 zY&q*~H7vjGxMTEs=PQ0&uTNXIF$)DJGg(gmWK@|QsIx7%Yx=dUZ?;u}U+gFK$3IPe zFqyrI=Y{Ex%MG{VtvZh=ovyv%*&&vc^l6~_dzpici!g}n_e=;=oje66dn~VIjK7R!kL-I zI~TY$$|j#qUb;Z&qtqi4{VY@V-FFC;sNV!x4#9;jvV)>)O@_;*}Z-@ z`>R{?1sE-cQBb zZXcMKuT+@|p9ZI=h`#n{C(qoHrAxZ!LeqX-0-!AG-+qo9j`tSOC zzXVmiTDe^9jiFVav7uvqgM{>xi^pevVXND(6TPix-~YewFEjJo9N<3u{ETE|okdnL z$5nxQj4cJi7ZqEKCwAK%TUCB;BEPsweEZ5M(^nB+dR=ZO@K1HwsIbm;&S$Zyn)Tvh}u{E22_p~{AqHi8U^_mrv&j%V3)z4`a2qOd+hdE@Pn z!n2$6tKKR$TA0?))m>!b(khf{v}?BZ?4?e2#~W#DqnK^?e>o>+n=5`XIgI=cRaa2%FF)WH}?#; z@ZU%N^dBgklL=Z=^|+_YmHYYSX#Z--($Z}g_ww8N+U`B^A={GOl10Qv!oM}3|An;3 zgWqp9`}?}t>i3`ZxG1&zq3@dQJ0yiy`!4t2C%(~k;`{bxie1-TI_AFqCS08Ab!M@R zd*KoFjV6=+YADarJl%UmFs%Fb#-=-dj4C(#10&b7Yq9Q}W4d9zvs`M;ewEW1>E~y9 z@8A0;C@<($V?pcntDk4e9-o=D=j+=1r#w~V=lyd)$E-b?`PkxIM8nCH>9J*;YV+N` z>P@S^SN(qPm&5Y^CV-lzc8%6b3+pD_us!dzr|Z^=^+z@JP3`@!yjrBa^6}C#asDcy zgPDSQcfwalue^M8xtG#$!BzEVaM)<+IM>7A+Ia<|w!$+y~ zQF!RS#?N+w&mW!=H~*txZr5o%kKc4xvZuz-OIKHn9G~2`y z=5P1G#9*GXpGk^l`p%CQnT=~I<2Ytdf6k{E>+e#aBOvx_S?9dUCyu|K{{JUl`}*|! z#ohm(+rK<_J8voP>+7HQx4K;xGhH8~#q+pmtWYvuSl$Pi}>+Rpk<)xcRqGV zA1^$1=F`;IFXi|DGyb(d{=U=f?LBti+PckmxEV=C?Wr)_`~UCz<>mW--+i;D-|@-W zX+f^XZBy4=>C%sTB67@!eFsCz_Kv>Ei#S{+>L%6X21+HjuIpXhS|xb45g2 zZMx!jvU6_sTy-bl|E(JhSs1(s$eMRMoic&ELz({avbD^KN5Ip8Dj8{dS9IJU)L-{X=KNi5bnu zS!?cZ__`$MO7yA%`zyycnhM1RKRfQHRDQ!Uer?dopobN+UpY)=vrrPb%qS$8b)22? zz8agwt79%TuQQ!aU(tEB%u?yg?7wo|IjnX&4>;M*`5;+#;qcME^_I8PrYAP;Q`Z0a z`9<*!)APYB&0GFlY=7}=wf^$`cM4?J#{JWo>aE)QQnE71xG(V`U-8S`|G&sp{k+JY z^VZ(;c;TGFqq1N69`{J@c;R39v-`!7>9Q{VHk-TVMep=0=5=SERk7m@=%A5{tl}{T zW;Pboou6UY{Jz6o!g!{DyYjlyXo=N3-|RYY^_8Qvy6N`aPxqbPv~)p}ap47@AQ%2? z7V%52T?q{ET`}?fRo{HEifzlUEHk=x{2@Q@wu#XjKC8@<+4?@$GUvpG^oeov%he7$ z{pqd$aaSN*a9uyg!fo5OUEHhK_t-*shWY-VXSWnQbSgjZUvO=0^zoY)Kxgh9ytA|T zXSw~amPuWkzdGa`OMSM&eaSOW|L=%ineovZQ}kFWuYBGlQKle~souBly#F)<%BuBt8yUdPOCG3Rrx!2FB1Q;rJCO*@))S>|rj>yl@iuY{iH)^u0T z&|TP8UYOaUCcpSt=_ZczY)UeV-(9}cQuWg0(Z3}JzZ7RU9m+Ux;{LW3`zOVDi$8j_ z?vmJ7&5t&fDaX@Ym$=`nFK@4LdUpK&^6r0cwZFWU|F3Ub zaXmic@~=|oX?ywD`#2{$=v(IG|9bj!{-Wk@w^Mh_w&;6o^L|J3y}~(#jO)I}$CtC$ zzLd6K&MqJ6_SCENhT!2eg;}h6zd(&XyDtm*FHUrqJ8AEn*r~eCwqR>QWcM}?PVNOCif!Zs%)56auetEl$wI0zbaSmn zo4eDJ)2m;!2(#Q`Xn7RRRjX%o)7L5H#`Nmymsh6C`K=CHv!e2>;=TD*uU5)1KeXsG z7LC}Dz*ze#c>bcKi|&`yjVwO)?dXio+nK5)zwEKaWC5Ljd;WgA{c>TuT$15FNyT|W z-7eNv7Qddo&9ArK`)%v`rEj<0?#pM}{_EI1qpzV_A?x@qO$&;YeZ~AH?D*415$(TY z?!4M`AXMzwsrGNIrnc>0*cR^dKN)mmT}>Zr4XBMculS|l-ANO3syM#x@RvC~?H^a} zJN?boa^Bg$Pd$2f{CiCfbMu|W4=gSyAB>DYm@YG4cblu2Elc%W2eqT?@Or) zfBO2Pb>hw!k6SmKeN)#)jpIePJhh0zTm>+uP@^^cggL19JcHI zv)3*4KSq?JWGrqR;rIC1}OX1!w-! z?|)d>xgVal{eFkDZc5){pR^Cv&yGpwKiM1ByXocYhn`nTW+@kZyIUS_^>WGN7e^-h zExP;t&TOrhI^7>mEIH1;Pj96Izv7M_o`L zxA}3>!+k+hJEY(CfB04KrSDjI*E2mq%QPLYq`W7R3a5-;rniZ|OqHKeVo>n+@z!l? zDv#Xk@X$YG6UXmzYl?aPf~oy?{Q3X?bgTMvZ~vuVSG8B(xi5e3{Kjt+9PYgrd23KF zR{M2x{_@LlrAJc!J%49$&IEM)S>ZO$2)XKms@E@X-&e0{`}fVPwL-6M8qPa=>74a@ zpY%C}Y460;Dc8HXeg&O5sH^`ynNPAN zmrt^W?c2_c2KF&xi`<@G+51VxH(q$Yv&?g4@2Qg}MoLe2E#%)Q$NF#A)AJWK^{pqF z<=>O(isbmRzVd8##=k#5Z$GgulQ~{#ckn(#%K6>ZbxF*M9Nmcz3qVV5r#<+77PRHM z{_pGfmD{(MA8A_m^y>4^m2n!o@Bd@JaPhdDyZgQ`5>Y!U%%wb*H*1{V%Uad5isNg} zrHgS&GuLc=^K-%@y)@~vrh?C%vv=k{4X7fhY(buuYDE~|Tm6vyY8JP-SRo2^^ed*04hTYm5S zFuQL8Yh%Bel;+2{PYs&M#wK9Agu8OP(Cj%G`KK=z))zDUJ)M5vue-T#)%M1D*`$Se%RlQg?J8x0?yvjDMsi!(xs}A4!y0d0p z+SysY*6%lIzb(ITaLbnm&Y9b5r(N13TfQX3O8a!;;(rZ~%}n39o&LVz4wstrl~)DF z4*N87NFTYb5MSqJc>Z*f;;q9u$umC7{p~sW`hX$-!n)%4&Sblbiyc<9Yn=D1=lJ!zBumAQIp4ufTmR|hB z(fh?6{{8d%ta9?#OUfLdX#yHBIrsU{vUhR64*IWO{Quv_{F{OMO(nlRt2)^6YU6Ra z(|xDJcEF53On)yr?M#XV=msOJsOpL%H>S$yJXdy2JJVa|5$ou5q~)jUy@ zoLLx`se9owS4`Qt(?&6IO{@DvPOojT;1}oX`rTJE?cM&@epPQ*$2sn`wf!R@@FM>E zuIm@y+}u2W`u7{46MHrum#hBZC$vL$ZYOuk^!)nYw?XIi_}*zQkx4(*#JZ{e=jr&2 zqg|ppSNd6n7wo)$!039-yj^MM7PWSlE!_LfzTw-g{|-@mQv7FaKkTGcR#LQA@8Zk~ zlT0?1^ths@t}~`b)utKx$40cwnyPstY(_-?Z&!Pzk7fl|PN(m9n;6uzc;9aM%CD*a zcqZO+soC*L@yX+I~uWP@&m5*`dx7pKU{CfMcgvU$0k19RCQvab@e#zy3 z-||;pxF#2)Zu4uUQgy8lulS5#ZyvTaD&%`lSGcs3pt?wd2KYLDj~ z-q)Wusfe-K)V~N?siPn6CFUi%Su*Q%r}gtK;d5Q;cRlR?GwWHAW!2x;@&0q?&Q-QI zJ6<@4ldbN2a#GfL+wUdncUaPIZc6>KRKGT}Nq#Blpok}z?^iyb`^TEoJ9YcA>kMBP z*)B5g&dt4IT5P5pv86-o{XX`$8}I+y@>;I?a?@O~OK&1KubgoH^P|h(V%lUs%l^#i z*d)oVcBRXprPx?l5dIsA<_3*NcDolPrM^wH#u`HDa1 z-{v?y9Ov9OQpIV9&g`=%}LiJtNs<^}V`PTO*`9K5vTAJh^H5 zpGud9DyP#OYsDVVSSkGaZM5JE-A8JjyA7|`JUZbS?5-z0&G&A}U-=ik>+O4sEean! z^3AUjHW%!BY%zH%GoMAnz0dQ$fADlqf7G=9{`~)cp5Hw9%A(I$@Ic_Qwfn;IH!G~WnN?$Yh-)KxQl`fu{=RUf5#Pud%*1+MQrX?oK1Vs`h6h@>?O znieT=1ugY>&y&m6_OkfdAG_DKza_u%6rVaEIn#LaKKbv@x72>Smp=dFhdtTr%WXqf ztXOxfvCAtY{NpyJr|f(W9B$T4d3UpUQ|(fVfT*TVoBpoQx!vBi$tYy%^o7edF*wd~__HXu@NcNF;fDHt<(q2+ zoVIYPT;Nvh)6)4iZT-C__htK9r{9lhczaY;?_ZltUqrs-q@6o=T710{{PFMmdim#b z>+OPqF3)+~5^TG+Q-`%m_=#oc6`3#7Qs2)}owr*>=UFsgZAs(4%$-uTOsl)Azi=GC zxnp?^)86hKo}9a1{AIF@{ao_wg5&ua()Z5$aPBz2H|3;IIbX#_Cl19)qFrL!?^S94 z`u0})dyRDa&Cj63ByGdy%nn-%VDF{q;@)y>s=xhTk=5brkNx}mTabs1KYNa#zD$3? zod@E#d@IZq=l$OoIN^Ku=d*i{l~y#)+8*%8pj*LR*j%8og1 z`Sk8{p8Ms2bK~BxJ$9?=@x;>)d-g{O3qQSl`O<~e>FF^RA6;U~ue$U|)%omTTr1$T zMHm!Vw|GRSUafe4cxuI;oslza^I5&T_$`*#T3p|ls>iF{5qaU;%ge`)?g`kjFXfa- zkDN7MCF@*iDeawZ-lY%kRAw98R(Mq1J6}ic_|F%6ojac_vbSlxr+)6Ubf!?D^TB(j zZC{I?zg%$sw2ymS+AQ%x;eAi5&ir4x;Og3FbI_8t+-JW5}bba1k_BLxbyJpLa4I+gbk{stazk7uq)OzuC-< zhBAw7HyayY*j^q!r}&shjOkC4IVM#CmoA6!b12?Y7kbeukUOdN_vw$%-l$eQ{L26F z;i0QH_LkeP%ATQES&?LWRMxC=(ds#uz1Vr>dd~aWurK$U(Gho?r{rCwS^T2pd3x2y zZW-Kb`(6C5a(&>Xg}q6$&o4Becu(eI&7C!q?b@p6wHUv6c%;(m<;!X3J^h{Q!mPNA zF3&BV_w(~MO+K48KH08Ci~2khgTj-T-e?&{JK?O z(O%`oIF5_YR<2ribd_neVevVmGjleJ%wKb>XYbOdyu4|%cfFonJm<&qsk@H(MMmmr zPYSlJ7WsO}K7MASL)Eh43!5EtES@dfr}+FLOVuNnD*x&Bi_$h8*?ijE<#xsSpVCKu z-Tf??si}8snS%D=&MzewKNkk?`}cKy^4(pf5@tCwq#HOCT{aayY!y#BHAOS;)P7LJ z9g+X@fPKZPRp6)-2uxTT6BP6)=BvnSZ*OmDqu0?^-Qh0B9%aS6-*6=)B{g+{`*lAF zyCw4HY<^bEDSqa`BX`4FuTs&a-GyndfYQqa3H#Fymz{~zqjOfU#o!Z>^XB}{``4v@1B^r@XqI488_EE)$jM_ z{?TX^P^z2p*zaK7=VxbcY|EW3{AT9QyqoWojC^B1%3MrITc)UQF1@$9>|n>jzXoMb zHcFU(ir|q7XgSwf`KvsRLy=+0OC1iyNxf^%l*}>MvOiiz^QDKjOyiV2jyIx%9vKyA z`WatSGd8}gt0NW}Vmx_Y`TKhw-u~{q#m}1^pU+6&BV?<;Nk zyRY_l%$)GT!j0XNQyxeDV~b+G*5|VLhUNPFFse@o@>lbGV{%gx{0KVPxtp<>IGE6+Ad9`BWI|M~g(aT|Fjj*DRrjvl@2Zy)=pM#v!d))o~D zFU1xMBaIuI+eN1fD!c9I4Gj;sPPb+KJ=eP2>bR|Ib=Oa|Tk63!efu4AgPAln6F&z; ztunSuO-ohWA1qh(?L|{^|I1cE=U+*0I{ryqTCd%;>-p{!>e$wji zN6&oN`!LtN`FGZh^5$Z@J3P~lPnb9Bk(gPluRm}1ciyCZ`Ijf^F$=i;EKqYakjOE= zzgb{W?P2cj9;M*q8{2pn=cb*Vb#)tm5QkzE=eY}SZ*E>5&2hBi*n^OekR3Dkq^73c z*k8YYcPfkeWA&d_*+u(b@7oy{^-<{k{>kjed1d@r1UxwV8!Ap~1oVkk2rAhs%SPUe zJaP8Cb6uk1u9DYn=kD%a6n$Rq#jU;CR-$4SiTk|k)OMvY$8Kt9IwQbk1W+NAD(%Ad1Wp#e|CYGTc6C!7=7L!NAu#Io~SPZ|q($TTSnmf$!3v&T`Sk5&lb-EBIgE6Dw2jsKzT|!E?h8mDcAZcGW9K z6a>FWvfD4$T>U%bDko zx>UP8EqM5P;-|%w&V4&=k6-19 zv!`A2>g+as{xo6Qyv8$gbW&2%6m^xEJ$$+Qa}G`J;Qt+hJq!-M~OyCmQ1IpyXGWWL<*oR=rpXZ_B?XQmNrcwD6_ z8?Tgz*ip?RmtBQ;6m(4+t|Txhay|NbW=i?VPkR53S-#j=wD-5Z%*4k}Z}0e8V^+q! zqwvS3XVSL)ANFywR*2oq`Eg38vg=dH#pOSbR$7O>zgBSWx5Rw~`|T`MiY`VMmix

9*T3j-=Irng_ty)^lDNpW&yNl1YPna+0#1{Lx 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�G?$4t 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

B_<8W(GvI%ye@ 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 literal 0 HcmV?d00001 From c9c5c55745ba067809b26fb98063f46203611955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Mon, 2 Mar 2026 11:38:10 +0100 Subject: [PATCH 341/353] Remove metainfo.xml It hasn't been kept up to date for a long time, and no one seems to care, so lets remove it. --- org.codeberg.dnkl.foot.metainfo.xml | 57 ----------------------------- 1 file changed, 57 deletions(-) delete mode 100644 org.codeberg.dnkl.foot.metainfo.xml diff --git a/org.codeberg.dnkl.foot.metainfo.xml b/org.codeberg.dnkl.foot.metainfo.xml deleted file mode 100644 index 1b7c46a7..00000000 --- a/org.codeberg.dnkl.foot.metainfo.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - org.codeberg.dnkl.foot - MIT - MIT - dnkl - foot - The fast, lightweight and minimalistic Wayland terminal emulator. - -
    -
  • Fast
  • -
  • Lightweight, in dependencies, on-disk and in-memory
  • -
  • Wayland native
  • -
  • DE agnostic
  • -
  • Server/daemon mode
  • -
  • User configurable font fallback
  • -
  • On-the-fly font resize
  • -
  • On-the-fly DPI font size adjustment
  • -
  • Scrollback search
  • -
  • Keyboard driven URL detection
  • -
  • Color emoji support
  • -
  • IME (via text-input-v3)
  • -
  • Multi-seat
  • -
  • True Color (24bpp)
  • -
  • Styled and colored underlines
  • -
  • Synchronized Updates support
  • -
  • Sixel image support
  • -
-
- - - Foot with sixel graphics - https://codeberg.org/dnkl/foot/media/branch/master/doc/sixel-wow.png - - - - - - - - - - - - - - - - - - - - org.codeberg.dnkl.foot.desktop - https://codeberg.org/dnkl/foot - https://codeberg.org/dnkl/foot/issues - -
From 3bbaa64caef285727ff63dcaef614a01c231935c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Mar 2026 17:35:02 +0100 Subject: [PATCH 342/353] changelog: prepare for 1.26.0 --- CHANGELOG.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ada2084..c9319875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.26.0](#1-26-0) * [1.25.0](#1-25-0) * [1.24.0](#1-24-0) * [1.23.1](#1-23-1) @@ -67,7 +67,8 @@ * [1.2.0](#1-2-0) -## Unreleased +## 1.26.0 + ### Added * `toplevel-tag` option (and `--toplevel-tag` command line options to @@ -146,9 +147,20 @@ [2263]: https://codeberg.org/dnkl/foot/issues/2263 -### Security ### Contributors +* Andrei +* Barinderpreet Singh +* c4llv07e +* Johannes Altmanninger +* nariby +* pi66 +* Ronan Pigott +* Stéphane Klein +* valoq +* Whyme Lyu +* Yaakov Selkowitz + ## 1.25.0 From 739cf115e6bc014b895bf945afccb88be6971be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Mar 2026 17:35:20 +0100 Subject: [PATCH 343/353] meson: bump version to 1.26.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index b7377652..66b3d6bc 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.25.0', + version: '1.26.0', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From ebacb14be80d7d10bc4f0a2c8eb9238c283abecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 3 Mar 2026 17:38:46 +0100 Subject: [PATCH 344/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9319875..0f02e550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.26.0](#1-26-0) * [1.25.0](#1-25-0) * [1.24.0](#1-24-0) @@ -67,6 +68,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.26.0 ### Added From c05bd55029b8a9bb57df246fec9acc786d71d16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 5 Mar 2026 16:17:09 +0100 Subject: [PATCH 345/353] doc: foot.ini: fix default value of initial-color-theme Closes #2292 --- CHANGELOG.md | 7 +++++++ doc/foot.ini.5.scd | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f02e550..30a73369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -74,6 +74,13 @@ ### Deprecated ### Removed ### Fixed + +* Wrong documented default value for `initial-color-theme` in + `foot.ini(5)` ([#2292][2292]). + +[2292]: https://codeberg.org/dnkl/foot/issues/2292 + + ### Security ### Contributors diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 2f5fc38c..a1ee326f 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -392,7 +392,7 @@ empty string to be set, but it must be quoted: *KEY=""* at runtime, or send SIGUSR1/SIGUSR2 to the foot process (see *foot*(1) for details). - Default: _1_ + Default: _dark_ *initial-window-size-pixels* Initial window width and height in _pixels_ (subject to output From f49fdf7ca3a69d987407c4c1965e32697fb671d7 Mon Sep 17 00:00:00 2001 From: Roshless Date: Thu, 5 Mar 2026 19:11:44 +0100 Subject: [PATCH 346/353] themes: paper-color-light: fix newline --- themes/paper-color-light | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/paper-color-light b/themes/paper-color-light index 2f7a8003..554aabc0 100644 --- a/themes/paper-color-light +++ b/themes/paper-color-light @@ -4,7 +4,7 @@ [main] initial-color-theme=light -xs + [colors-light] cursor=eeeeee 444444 background=eeeeee From 4fd682b4e8d985ce25d2bd599c1d855bc1489650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 10 Mar 2026 07:59:40 +0100 Subject: [PATCH 347/353] meson: clang: add -Wno-wc2y-extensions Recent clang versions warn on __COUNTER__, unless compiling with -std=c2y (which breaks other things). "Fixes" '__COUNTER__' is a C2y extension (__COUNTER__ is used by our UNITTEST macro). --- meson.build | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meson.build b/meson.build index 66b3d6bc..16e8e3c0 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,11 @@ is_debug_build = get_option('buildtype').startswith('debug') cc = meson.get_compiler('c') +# Newer clang versions warns when using __COUNTER__ without -std=c2y +if cc.has_argument('-Wc2y-extensions') + add_project_arguments('-Wno-c2y-extensions', language: 'c') +endif + if cc.has_function('memfd_create', args: ['-D_GNU_SOURCE'], prefix: '#include ') From 657db18a4ec4df93689c3eaae03b70f851724001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 10 Mar 2026 07:46:03 +0100 Subject: [PATCH 348/353] wayland: do all surface unmap and roundtrips before waiting for pre-apply damage The pre-apply damage thread may be running when we destroy a terminal instance, and we need to wait for it to finish before destroying the underlying buffer. c291194a4e593bbbb91420e81fa0111508084448 did this, but failed to realize the thread may get re-started in the roundtrips done later in wayl_win_destroy(); after the wait added in c291194a4e593bbbb91420e81fa0111508084448, we unmap all surfaces (including the main grid), and roundtrip. This means the compositor will release the currently active buffer, and that means _we_ will trigger the pre-apply damage thread on it. This introduces a race, where wayl_win_destroy() may reach its shm_purge() calls before the pre-apply damage thread has finished. That typically causes foot to crash. Closes #2288 --- CHANGELOG.md | 3 +++ wayland.c | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30a73369..30b3e1e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,8 +77,11 @@ * Wrong documented default value for `initial-color-theme` in `foot.ini(5)` ([#2292][2292]). +* Occasional crashes when closing a window and + `tweak.pre-apply-damage=yes` (the default) ([#2288][2288]). [2292]: https://codeberg.org/dnkl/foot/issues/2292 +[2288]: https://codeberg.org/dnkl/foot/issues/2288 ### Security diff --git a/wayland.c b/wayland.c index 1d258213..1ffd62a6 100644 --- a/wayland.c +++ b/wayland.c @@ -2177,8 +2177,6 @@ wayl_win_destroy(struct wl_window *win) struct terminal *term = win->term; - render_wait_for_preapply_damage(term); - if (win->csd.move_timeout_fd != -1) close(win->csd.move_timeout_fd); @@ -2236,6 +2234,8 @@ wayl_win_destroy(struct wl_window *win) tll_remove(win->urls, it); } + render_wait_for_preapply_damage(term); + csd_destroy(win); wayl_win_subsurface_destroy(&win->search); wayl_win_subsurface_destroy(&win->scrollback_indicator); From eed2d668ecdb0705142d27950a6d8c1923df32f1 Mon Sep 17 00:00:00 2001 From: vlkrs Date: Thu, 12 Mar 2026 18:47:50 +0100 Subject: [PATCH 349/353] OpenBSD has UTF-32 --- char32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/char32.c b/char32.c index 3d6c2c78..be5bf229 100644 --- a/char32.c +++ b/char32.c @@ -34,7 +34,7 @@ _Static_assert( #if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__ #error "char32_t does not use UTF-32" #endif -#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__) +#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__) && !defined(__OpenBSD__) #error "wchar_t does not use UTF-32" #endif From 370adaf6975c7128107d187928c7f9cdff247930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 14 Mar 2026 08:35:15 +0100 Subject: [PATCH 350/353] changelog: prepare for 1.26.1 --- CHANGELOG.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b3e1e4..b62f5c92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -* [Unreleased](#unreleased) +* [1.26.1](#1-26-1) * [1.26.0](#1-26-0) * [1.25.0](#1-25-0) * [1.24.0](#1-24-0) @@ -68,11 +68,8 @@ * [1.2.0](#1-2-0) -## Unreleased -### Added -### Changed -### Deprecated -### Removed +## 1.26.1 + ### Fixed * Wrong documented default value for `initial-color-theme` in @@ -84,9 +81,11 @@ [2288]: https://codeberg.org/dnkl/foot/issues/2288 -### Security ### Contributors +* Roshless +* vlkrs + ## 1.26.0 From ef15414b301513a75193fd872de79d6379f41a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 14 Mar 2026 08:35:28 +0100 Subject: [PATCH 351/353] meson: bump version to 1.26.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 16e8e3c0..a0e602bb 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('foot', 'c', - version: '1.26.0', + version: '1.26.1', license: 'MIT', meson_version: '>=0.59.0', default_options: [ From 2fb7bb0ea4a240c6a8d921698d89d6044dea16e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 14 Mar 2026 08:38:15 +0100 Subject: [PATCH 352/353] changelog: add new 'unreleased' section --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b62f5c92..a0654b08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* [Unreleased](#unreleased) * [1.26.1](#1-26-1) * [1.26.0](#1-26-0) * [1.25.0](#1-25-0) @@ -68,6 +69,16 @@ * [1.2.0](#1-2-0) +## Unreleased +### Added +### Changed +### Deprecated +### Removed +### Fixed +### Security +### Contributors + + ## 1.26.1 ### Fixed From 037a2f4fa2c6fab014248d62efa8a6e14f617832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 21 Mar 2026 14:43:27 +0100 Subject: [PATCH 353/353] term: enqueue data to slave if there are queued paste data buffers When writing paste data to the terminal (either interactively, or as an OSC-52 reply), we enqueue other data (key presses, for examples, or query replies) while the paste is happening. The idea is to send the key press _after_ all paste data has been written, to ensure consistency. Unfortunately, we only checked for an on-going paste. I.e. where the paste itself hasn't yet finished. It is also possible the paste itself has finished, but we haven't yet flushed all the paste buffers. That is, if we were able to *receive* paste data faster than the terminal client was able to *consume* it. In this case, we've queued up paste data in the terminal. These are in separate queues, and when emitting e.g. a key press, we didn't check if all _those_ queues had been flushed yet. Closes #2307 --- CHANGELOG.md | 7 +++++++ terminal.c | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0654b08..f554124b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,13 @@ ### Deprecated ### Removed ### Fixed + +* Other output (key presses, query replies etc) being mixed with paste + data, both interactive pastes and OSC-52 ([#2307][2307]). + +[2307]: https://codeberg.org/dnkl/foot/issues/2307 + + ### Security ### Contributors diff --git a/terminal.c b/terminal.c index ac7922a7..8eafbcbe 100644 --- a/terminal.c +++ b/terminal.c @@ -120,7 +120,10 @@ term_to_slave(struct terminal *term, const void *data, size_t len) return false; } - if (tll_length(term->ptmx_buffers) > 0 || term->is_sending_paste_data) { + if (unlikely(tll_length(term->ptmx_buffers) > 0 || + term->is_sending_paste_data || + tll_length(term->ptmx_paste_buffers) > 0)) + { /* * Don't even try to send data *now* if there's queued up * data, since that would result in events arriving out of