342 Commits
Author SHA1 Message Date
Aram Drevekenin 9dacbc1982 allow dangerously disabling certificate on non-localhost (#5694)
* allow dangerously disabling certificate on non-localhost

* add pr
2026-10-08 11:26:00 +02:00
Aram Drevekenin 328be6919c fix: client stdout race (#5693) 2026-10-08 10:35:02 +02:00
8LWXpgandAram Drevekenin b24a7a1e3e Add support for mouse horizontal scroll (#4860)
* rebase on upstream/main

* test: implement scroll_left/scroll_right on mock panes

* remove faux handling for left/right and add tests

* add pr

---------

Co-authored-by: Aram Drevekenin <[email protected]>
2026-10-07 19:16:33 +02:00
Aram Drevekenin 201c1593da Interactive configuration, popups, notifications, right-click menu (#5685)
* ui components

* right click menu + popups

* settings screen

* various fixes

* presets in config

* zellij prompt - ui elements in the cli

* context menu shortcuts and toggle, keybindings in popups, info popups + zellij prompt notify

* smart config writing and some leftovers

* keybindings/right-click/config editors

* some wrapups

* feat(plugins): add prompt() plugin API for zellij:prompt, dialog forms, and Text labels in widgets

* theme editors

* upstream by-id action types, config env for new panes only, smaller saved menu config, web right-click, example config

* upstreamify

* rustfmt

* fix windows and theme editor layout

* add pr
2026-10-05 18:46:12 +02:00
Thibault SaunierandAram Drevekenin fc400dfef9 feat(web): add WebSocket heartbeat to prevent false disconnections (#4633)
* feat(web): add WebSocket heartbeat to prevent false disconnections

Idle WebSocket connections are often silently closed by network
intermediaries (e.g. Cloudflare's 100s idle timeout), causing false
"connection lost" messages even though the session is still alive.

Keep the control WebSocket alive by sending a protocol-level Ping
every 30s and closing the connection if no Pong is received within
45s. Browsers reply to Pings automatically below the JS layer, so
this keep-alive survives JS event-loop blocks and tab throttling.

* add some cleanups

* rustfmt

* add pr

---------

Co-authored-by: Aram Drevekenin <[email protected]>
2026-09-25 13:45:16 +02:00
Tyler KellerandAram Drevekenin 2fce032cce fix(kitty-graphics): zlib-compress images forwarded to the host terminal (#5608)
* fix(kitty-graphics): zlib-compress images forwarded to the host terminal

`emit_kitty_transmit` sent every image to the host as uncompressed RGBA
base64 (`f=32,t=d`), about 20x the size of a PNG the application sent,
and re-sent it on every tab switch, pane split and resize step. Compress
the cached host payload with zlib and tag the transmit with `o=z`, which
the kitty protocol defines as part of its base feature set.

Measured with a headless client at 429x77 (8x17 px cells): a 121 KB PNG
went from 2.4 MB to 163 KB on the wire, a 943 KB PNG from 19.9 MB to
1.9 MB, with no measurable change in server CPU.

Fixes #5596

* fix: conditional zlib compression depending on outer terminal support

* rustfmt

* add pr

---------

Co-authored-by: Aram Drevekenin <[email protected]>
2026-09-25 10:33:28 +02:00
Aram Drevekenin 3e470a43dc fix: stop host terminal replies from leaking into the shell prompt (#5655)
* fix: stop host terminal replies from leaking into the shell prompt as text

* add pr
2026-09-24 18:52:13 +02:00
Myah MitchellandAram Drevekenin 0591ca5432 feat(nested-sessions): report a guest's input mode and key bindings to its host (#5589)
* refactor(plugin-api): share the keybinding protobuf conversions

The conversion between a keybinding table and its protobuf form was written
out four times: once each way for the ModeUpdate and InitialKeybinds events.
Collect them into `keybinds_to_protobuf` and `keybinds_from_protobuf`.

Decoding now drops a binding whose key is missing instead of unwrapping it,
so a malformed or foreign payload can no longer panic the conversion, and a
mode whose key fails to convert costs only that binding rather than every
binding in the mode.

* feat(nested-sessions): carry input mode and keybindings over the protocol

A session hosting a nested session has no way to learn what that session is
doing, so its plugins cannot show hints for the session the user is actually
typing into. Add the three messages that carry it:

- GuestModeUpdate, sent by a guest whenever its input mode changes, so a host
  always knows which of the guest's bindings currently apply.
- RequestGuestKeybinds and GuestKeybindsUpdate, a query and its answer, for
  the far larger keybinding table. Keeping the table behind a query means it
  is fetched once by whoever wants it rather than pushed at every host.

The table travels as an encoded plugin-API payload in an opaque bytes field.
Describing keybindings natively here would mean restating the whole Action
schema in this contract, which the codegen compiles with only its own include
path. Reusing the existing payload also means a host and a guest built from
different Zellij versions exchange whatever both understand and silently drop
the rest, which is the behavior this wire needs.

An input mode is written under the name InputMode::from_str reads, so adding a
mode needs no change to this contract and an unknown mode is rejected rather
than misread.

Peers advertise the new HintReporting capability, so each side can tell
whether the other can take part before asking it anything.

* feat(plugin-api): expose nested session mode and keybinding events

Plugins running in a host session can see that a pane holds a nested guest
session, but they have no way to learn what that guest is doing. A status bar
in the host therefore keeps showing the host's own hints while the user is
descended into the guest, which is the wrong keybinding table for the keys the
user is actually pressing.

Add two events and one command covering that gap:

  - Event::NestedSessionModeUpdate reports a guest's input mode as it changes.
  - Event::NestedSessionKeybinds delivers a guest's full keybinding table.
  - PluginCommand::RequestNestedSessionKeybinds asks a guest pane for that
    table, which arrives asynchronously as the event above.

Both events carry the pane the guest runs in and the guest's session name, so
a plugin watching several nested panes can tell them apart. Keybindings are
requested rather than pushed because the table is large and most plugins never
need it.

* feat(nested-sessions): report guest input mode and keybindings to the host

Wire the nested session hint events end to end.

A guest announces the HintReporting capability during the handshake and, from
then on, emits a GuestModeUpdate frame whenever its input mode changes. The
mode is reported on every change rather than only while the host is descended,
so the host already holds the right mode the moment focus arrives and the
status bar does not lag behind the descent.

The host turns each such frame into Event::NestedSessionModeUpdate. It answers
PluginCommand::RequestNestedSessionKeybinds by writing a RequestGuestKeybinds
frame into the guest pane; the guest replies with its keybinding table plus its
current mode, and the host forwards the table as Event::NestedSessionKeybinds.

Requests to guests that did not advertise HintReporting are dropped rather than
written, and frames from panes whose guest is not currently live are ignored,
matching how the other guest-to-host frames are handled.

Both events and the command require the ReadApplicationState permission, the
same gate ModeUpdate uses.

* test(nested-sessions): cover guest mode and keybinding reporting

Unit tests over the contract check that every input mode survives the round
trip under its own name, that a mode name the reading side does not know is
rejected rather than guessed at, and that a keybinding table keeps its modes,
keys and actions across the wire. The undecodable and empty payload cases are
covered too, since a host and a guest may be built from different versions.

Integration tests drive two real sessions bridged through a pane: both sides
advertise hint reporting during the handshake, the guest answers a keybinding
request with a populated table plus its current mode, and the guest reports
each mode change while the user is descended into it.

* test(nested-sessions): drop unknown capabilities without losing known ones

A peer built from a newer Zellij can advertise capabilities this side has
never heard of, so pin down that they are ignored individually rather than
invalidating the whole advertisement.

* feat(nested-sessions): carry the guest's base mode alongside its input mode

A host plugin that only knows a guest's current mode cannot tell which of the
guest's bindings are inherited from its base mode. Zellij's own hint rendering
leans on that distinction: bindings the base mode already advertises are
inherited by every other mode through `shared_except` groups, so repeating them
in each mode is noise. Without the guest's base mode a host plugin either
suppresses the wrong bindings or suppresses none.

A guest with a non-Normal base mode, such as an unlock-first config, is where
this shows: assuming Normal there hides the wrong half of its bindings.

The base mode rides along on the existing mode message, so a guest that has not
switched modes yet still reports one, resolved the way the rest of Screen
resolves it (the configured default mode is the base). It stays optional on the
wire, so a name the reading side does not recognize costs only the
inherited-binding distinction rather than the whole message.

* feat(nested-sessions): report the guest's mode when the handshake completes

A guest only pushed its input mode when that mode changed, so a host that
had just picked up a nested session learned nothing about it until the user
happened to switch modes inside it. A host plugin has no other way to tell
which of its panes holds a session worth asking for keybindings, which left
it unable to act on the feature at all in the common case of a guest sitting
in its starting mode.

Send the report once on contact as well, and reuse the same client mode
lookup the keybinding answer already did.

* docs(changelog): note the nested session hint reporting events

* fix(nested-sessions): drop guest keybindings on control characters

Nothing a guest says is authenticated. Its frames travel in the terminal
stream of the pane it runs in, so any program with that pane can write them,
including a keybinding table claiming a binding on a raw control character.
A host that draws those bindings puts their keys on its own screen, where a
control character is a terminal escape in the host's output rather than a
key anyone could press.

Drop them where the guest's payload is decoded, so no host plugin inherits
the problem. Zellij spells the control keys that do exist as their own
BareKey variants, so a Char holding one describes no binding worth keeping.

* test(nested-sessions): expect hint reporting in the announce ack

The host now advertises HintReporting alongside NestedControl when it
acknowledges a guest, so the assertion on the exact capability list had to
follow.

* fix(plugin-api): drop only what has no protobuf form when writing keybindings

keybinds_to_protobuf failed the whole table on the first key or mode it could
not represent, so one unrepresentable binding cost the caller every binding:
a ModeUpdate that failed to convert entirely, and a nested session's answer
that reached its host as an empty table indistinguishable from a session with
no bindings at all.

Drop the individual mode, key or action instead, which is what the reading
half already does and what the writing half already did for actions. A table
now survives the trip between builds minus whatever they do not have in
common.

* fix(nested-sessions): keep a host's view of the guest fresh across a reconfigure

A reconfigure rewrites a session's keybindings and base mode without going
through change_mode, and keybindings are otherwise only ever sent in answer
to a request. A host that had already asked once therefore kept drawing the
table the guest had before its config was reloaded, with no way of noticing
it had gone stale, and would not have picked up the new base mode until the
user happened to switch modes inside the guest.

Report both from reconfigure, next to the shortcut broadcast that already
happens there. Answer a keybinding request from the requesting client's own
table too, since a reconfigure with write_to_disk unset rebinds only the
client that asked: the mode in the same reply already came from that client,
so the two halves now describe the same thing.

* fix: adjust protocol + API

* Update CHANGELOG.md

---------

Co-authored-by: Aram Drevekenin <[email protected]>
2026-09-24 14:43:03 +02:00
Aram DrevekeninandLucca Huguet 81f56e1aed Yazelix fixes (sixel/kitty correctness, explicit-swap-layout change rather than toggle, border styling) (#5630)
* fix: sixel image reaping/id-reuse

* fix: kitty scaling issues

* fix: stacked pane ordering

* feat: apply tiled/floating swap layout by name

* border styling

* fix tests

* fix: complete Yazelix feature validation (#5631)

* fix: retire client-ids instead of reusing them

* distributions

* rustfmt

* fix tests

* update changelog

* fix tests and race

---------

Co-authored-by: Lucca Huguet <[email protected]>
2026-09-24 09:26:20 +02:00
Append 035876d7af fix(web-client): coalesce concurrent authentication onto a single prompt (#5542)
authorizedFetch asks the user for a token every time a request is
rejected, and getSecurityToken appends a dialog to document.body with no
singleton, so concurrent rejections open one dialog each. Authenticating
one does not release the others: each parked call is blocked on its own
await and never re-checks the now-valid cookie.

The standalone session menu surfaces this by polling /session-list every
3s while logged out, stacking a dialog per tick, but the mechanism is not
specific to that caller.

Route rejected callers through one shared pending authentication. The
first owns the prompt, the rest await its result and retry against the
credentials it established.

Requests rejected across an authentication rather than during it need
more than coalescing: a request sent before login is answered against the
credentials of the moment it was sent, so its 401 can arrive once the
shared attempt has already settled and been cleared. Track an
authentication generation, incremented in the same finally that clears
the shared promise, and sample it around each fetch. A rejection that
arrives after the generation advanced predates the credentials now held,
so retry the request rather than prompting again.

app.js, index.html and integrity.json are regenerated by `cargo xtask
assets`.

Refs #5540
2026-08-26 15:12:32 +02:00
Aram Drevekenin a9bd448e66 feat: allow starting a session with an initial command (#5543)
* feat: allow starting a session with an initial command

* add pr
2026-08-26 15:00:45 +02:00
Aram Drevekenin d8e58e4f14 fix: focus event handling from external in web (#5527)
* fix: focus event handling from external in web

* add pr
2026-08-24 18:05:38 +02:00
Aram Drevekenin ca316433a3 fix: various snotifications issues introduced in 045 (#5523)
* fix: variou snotifications issues introduced in 045

* add pr
2026-08-24 15:21:43 +02:00
Aram Drevekenin 69954b5b6c fix: don't require nested reading of env var to detect nested sessions (#5522)
* fix: don't require nested reading env var to detect nested sessions

* add pr
2026-08-24 10:45:17 +02:00
Aram Drevekenin 070b9c3d1c Fixes before 045 (#5492)
* fix: keep single-pane tabs when serializing, stop the busy-spin on IPC disconnect, and make client-less CLI actions either work or fail loudly

* fix: some tab closing/ordering issues

* test: integration coverage for OSC 133 selection, scroll retention, paste-read opt-in, theme actions, and input fragmentation

* rendering-width regression tests

* fix: forward alt+mouse events into a guest pane the client is descended into

* fix: alt + [] delay in non-kitty

* allow disabling mouse hover tips while keeping the other effects

* more osc133 actions

* fix: base mode race when changing keybinding presets

* fix compilation warnings

* make releases drafts and remove diagnostic logging

* add pr
2026-08-20 08:04:12 +02:00
Aram Drevekenin 8c5bc6f0b3 Implement host focus gained/lost and missing notifications (#5485)
* implement host focus gained/lost and missing notifications

* add pr
2026-08-17 13:37:17 +02:00
Aram Drevekenin 98a0837077 feat: allow opting-in to reading paste buffer (default false) (#5472)
* feat: allow opting-in to reading paste buffer (default false)

* add pr
2026-08-13 10:19:13 +02:00
ZrjaKandAram Drevekenin 1d1d5c1101 fix(input): realign raw-byte buffer with the keyboard parser to stop stale bytes eating keystrokes (#5446)
* fix(input): realign raw-byte buffer with the keyboard parser to stop stale bytes eating keystrokes

After a paste (e.g. via a clipboard manager like maccy), zellij's
current_buffer can hold up to a few stale bytes that termwiz's
InputParser has already consumed into events. Those stale bytes are then
attached to the raw payload of subsequent KeyEvent messages, desyncing
each keystroke from the bytes that produced it: keys go missing, come
out garbled, or drop into vi command mode.

The root cause is that parse_with_consumed reports consumption relative
to its internal self.buf, while stdin_handler drains a separately
accumulated current_buffer. The two can drift apart whenever a read
leaves a partial sequence that the parser holds under maybe_more.

Reconcile the two after every parse: trim current_buffer to the
parser's buffered length, so no byte the parser has already decoded is
ever forwarded with a later key event. Add InputParser::buffered_len to
the vendored termwiz for that purpose.

* fix: remove parsed bytes from front rather than back

* rustfmt

---------

Co-authored-by: Aram Drevekenin <[email protected]>
2026-08-10 14:17:09 +02:00
Aram Drevekenin 3239d37b40 Native web mobile UI (#5441)
* initial chrome

* mobile state

* feat(mobile-web): implement browser→server actions (fit toggle, single-pane fullscreen, pane/tab focus) with fullscreen-truth reconciliation

* panning

* remove mobile plugin

* fix first load race

* improve web handshake

* rustfmt

* cleanups

* moar cleanups

* add bundle asset check to ci

* fix(mobile-web): tab-scope co-presence, yield single-pane to desktop clients, fix render-mode desync

* make mobile use no-ui fullscreen

* client side welcome screen for mobile

* attach web clients to first tab on tiles surface

* fix: opening new pane in mobile single mode no longer exits mobile single mode

* mobile chrome light theme support

* fix server races and flaky tests

* rustfmt

* fix flakes

* add pr
2026-08-05 15:32:53 +02:00
Aram Drevekenin c3c7e65965 fix(sixel): only advertise capability if host terminal supports it (#5432)
* fix(sixel): only advertize capability if host terminal supports it

* add pr
2026-08-03 09:58:42 +02:00
Aram Drevekenin 0e6e440402 Support the Kitty Image Protocol (#5428)
* initial implementation - kinda working

* tests and fixes

* fix bounds

* tests and compatibility fixes

* fix: cursor scroll, clears, deletes, key tolerance, and host id range

* fix: keep images alive across resize, relayout and history scroll

* anchor to lines, not pixels

* fix image/text alignments

* config to allow disabling

* fix pinned pane issue

* never let host graphics replies reach the keyboard parser

* fix nested session

* properly handle fullscreening a pane

* rustfmt

* cleanup of ignored test

* add pr
2026-07-31 19:19:49 +02:00
Aram Drevekenin 19befc2118 Even more debian upgrades (#5424)
* Upgrade axum-extra 0.12, axum-server 0.8, rcgen 0.14

* rustfmt
2026-07-30 10:43:00 +02:00
Aram Drevekenin cab75c58fe More dependency upgrades for debian (#5421)
* upgrade insta, rand, sha2, strum, thiserror, tokio-tungstenite, toml, which; drop unused semver and typetag

* upgrade sysinfo to 0.39.5, bump toolchain (msrv) to 1.95.0

* remote legacy yaml config converter and thus drop serde_yaml

* upgrade unicode-width to 0.2.2

* fix(tests): run ipc roundtrip test on a larger stack
2026-07-29 17:08:08 +02:00
Aram Drevekenin 9969ed10b2 Dep upgrades for debian (#5420)
* chore(deps): upgrade base64 0.22, colored 3.1, crossterm 0.29, dialoguer 0.11, directories 6.0, highway 1.2, prost 0.12.6, strip-ansi-escapes 0.2.1, suggest 0.5.1

* chore(deps): upgrade miette 7.6, nix 0.30, notify 8.2, notify-debouncer-full 0.5

* vte 0.15

* clap 4.6

* workspaceify plugin deps

* don't send empty fs watch events
2026-07-28 12:05:38 +02:00
Aram Drevekenin ea07e2d5b6 Nested session handling (#5417)
* fix: theme query and updated pixel dimensions

* add no-ui-fullscreen key/action/capability

* protocol core

* integration test harness

* fix integration tests

* fix flaky tests

* nested ui (dimming) and directional focus initial implementation

* integration tests for nested ui dimming and directional focus

* initial full screen nesteed implementation

* fix: keep hidden floating panes hidden across nested no-ui fullscreen

* fix integration tests

* no ui fullscreen integration tests

* hardening behavior/tests

* fix(nested): re-derive descend/dim on host tab switch and new-tab so clicking dimmed chrome ascends

* initial modal implementation

* initial ui improvements

* fix integration tests

* improve nested selection ui

* nested session modal refactoring and tests

* improve integration test speed somewhat

* some ui adjustments

* simplify full/auto nested session handling

* hints in compact-bar

* guest stdin leak fix

* allow floating panes to be fullscreen

* make nested session handling configurable

* protocol reannounce

* refactor: naming confusion

* improve ux somewhat

* rustfmt

* merge fixes

* improve integration test rate limiting (fix ci)

* add pr
2026-07-27 18:24:37 +02:00
Pierre Kisters d9682a2eef fix: input spam when moving mouse while typing by splitting the input bytes into their respective events (#5323) 2026-07-17 15:24:52 +02:00
divens 39a75c5493 fix(windows): route Windows Terminal through the VT input path (#5335)
Windows Terminal does not set TERM by default, so zellij on WT was
using the native-console crossterm path that reads INPUT_RECORDs.
That path can't observe bracketed-paste sequences (`\e[200~/\e[201~`),
which is one of the reasons multi-line paste has never worked
correctly on Windows (issue #4885).

WT sets `WT_SESSION` per-process. Use that as a second trigger for
the VT input path so default WT installs get raw VT byte input (and
with it, termwiz's bracketed-paste detection, the SGR mouse format,
correct emoji delivery, etc.) without requiring the user to set TERM.

Introduce `use_vt_path()` in `os_input_output_windows.rs` as the
single source of truth for the gate. Previously the stdin handler
had one TERM check and `enable_mouse_support` / `disable_mouse_support`
each had their own — so adding `WT_SESSION` to just the stdin gate
caused mouse setup to take the native-console branch, which calls
`crossterm::event::EnableMouseCapture` and clobbers the
`ENABLE_VIRTUAL_TERMINAL_INPUT` flag the stdin path had just set.
That broke all input from WT until the helper aligned every site.
2026-07-07 14:41:44 +02:00
Aram Drevekenin 7b823f972a fix: more verbose startup errors (#5334)
* fix: more verbose startup errors

* add pr
2026-07-06 08:55:09 +02:00
Aram Drevekenin 6a6376cebb fix: occasional garbage on attaching through slow connections (#5320)
* fix: occasional garbage on attaching through slow connections

* rustfmt

* add PR
2026-06-30 16:48:37 +02:00
DawidPietrykowskiandAram Drevekenin ebd731b0ff feat(panes): FocusLastPane action (#4241)
* feat(panes): focus last pane (#4142)

* add tests

* add client id parameter to `add_floating_pane`

* add action for floating panes

* add tests for floating panes

* add keybinds to configs

* rebase fixes

* simplify focus logic when adding stacked pane

* formatting fix

* formatting fix 2

* fix: make sure expanding pane always focuses it for all clients in stack

* add pr

* fix rebase

---------

Co-authored-by: Aram Drevekenin <[email protected]>
2026-06-25 18:12:51 +02:00
Aram Drevekenin 8c7e41c9aa chore: new test infra (#5269)
* initial implementation

* refactoring and cleanups

* port basic e2e tests

* fix tab rotation

* migrate client and mouse tests

* migrate the rest of the tests

* remove old e2e tests

* rustfmt

* add e2e note

* fix stale test

* fix: cross platform locking/cleanup

* fix: change mode race discovered by tests

* rustfmt

* fix: attach/detach race in tests

* fix plugin test assertion

* add pr
2026-06-16 15:44:22 +02:00
Aram Drevekenin b6a5ad0e5a Mobile layout (#5241)
* phase 2+3

* initial mobile plugin (phases 4 + 5 + 6)

* some bugfixes

* implement stage 7

* mobile interface improvements

* improve mobile pane/tab picker

* more ui improvements and fixes

* various mobile fixes

* some mobile fixes

* mobile keyboard triggering fix

* some cleanups

* initial plugin keyboard implementation

* improve keyboard hit zones

* improve keyboard

* pinch resize

* pinching fixes

* implement scrolling + panning

* initial keyboard resizing

* fix: mobile first render issues

* keyboard responsiveness

* fix some issues with the keyboard

* implement fit

* fix mobile scrolling

* add tilde cell to compact

* improve mobile keyboard

* fix starting mobile terminal font to natural keyboard size

* fix mobile resize blank screen flash

* shadow focus for mobile

* more keyboard fixes

* improve mobile status bar

* fix: render plugins to mobile clients

* allow opening new panes/tabs from the mobile plugin

* allow configuring

* fix dot slash

* improve mobile native soft keyboard interaction

* switch to native keyboard - initial implementation

* dirty hacks, but phone keyboard works now

* adjust keyboard help strip

* fix cursor in mobile

* fix keyboard and help line

* rename keyboard to modifier bar

* allow starting new session

* make clicking session name bring us to new session menu

* allow switching to desktop from the mobile ui

* mobile welcome screen initial implementation

* mobile welcome screen fixes

* various welcome screen fixes

* more mobile fixes

* moar mobile fixes

* mobile ui redesign

* fix: swallowed backspace bug

* switch session screen improvement

* touch up some interfaces

* refactor: move mobile state to own struct

* refactor: move fit resizing to the server

* remove dead flash code

* some cleanups

* refactor: clear fit on close

* refactor: improve fit api

* refactor: pane sync functions

* refactor: mouse

* refactor: break into modules

* some cleanups for the sessions screen

* fix scrolling and other stuff

* refactor: sessions screen

* refactor: panes screen

* refactor: new session screen

* refactor: menu screen

* refactor: render

* cleanups

* refactor: plugin structure

* remove unused welcome screen path

* Rename api

* refactor input.js

* cleanup useless comments

* fit/mobile refactoring

* take out the garbage

* remove more comments

* remove mobile flash on startup/attach

* fix: various mobile startup issues

* fix space bugs

* cleanup

* rustfmt

* fix windows build

* adjust modifier bar ui

* add pr
2026-06-10 16:50:55 +02:00
e9173cba16 feat: PWA support for the web client (#5184)
* feat: PWA support for the web client

Adds Progressive Web App support so the Zellij web client can be
installed as a standalone app via Chromium/Firefox/Safari/iOS.

- New static asset `zellij-client/assets/manifest.webmanifest` with
  required W3C members (name, start_url, scope, display, icons). All
  path-bearing fields are relative to the manifest URL so a single
  static file works under any reverse-proxy base_url prefix without
  runtime templating.
- New icon set under `zellij-client/assets/`: icon.svg (vector),
  icon-192.png and icon-512.png (purpose "any"), and
  icon-maskable-512.png (purpose "maskable") rendered at 70% of
  canvas height on a #080317 backdrop so Android adaptive masks
  honour the W3C 80%-diameter safe zone.
- `scripts/render-pwa-icons.sh` pins the rendering toolchain
  (librsvg + ImageMagick) and the maskable safe-zone geometry so
  re-renders are reproducible.
- `.webmanifest` -> `application/manifest+json` mapping added to
  `get_mime_type` in `zellij-client/src/web_client/utils.rs`.
- `index.html` gains <link rel="manifest">, apple-touch-icon, and
  apple-mobile-web-app-* meta tags. Placed immediately after the
  existing favicon link and before any <script> tag so the manifest
  is in the browser's preload scanner early.
- 5 new tests cover Content-Type, required JSON members, the
  relative-URL contract, index.html references, icon reachability,
  and base_url invariance.

Closes #5094.

* update changelog with pull url

* feat(web): trim PWA icon set to PNGs only

Drops the duplicate `zellij-client/assets/icon.svg` (verbatim copy of
`assets/logo.svg`) and the maskable PNG variant from the manifest.
Net result is the minimal icon set most production PWAs ship:
icon-192.png and icon-512.png.

- `icon.svg` was redundant with the canonical `assets/logo.svg` at
  the repo root and offered no install-criterion benefit on top of
  the 192/512 PNGs.
- `icon-maskable-512.png` only benefits Android Chrome installs
  (desktop browsers and iOS ignore the maskable purpose). The
  Android win was small for the hexagonal Zellij glyph; a hand-tuned
  maskable can be added in a follow-up if Android installs need
  polish.

`scripts/render-pwa-icons.sh` now reads from `assets/logo.svg`
directly instead of the duplicate copy.

* feat(web): use existing assets/logo.png as the PWA icon

Removes the pre-rasterized 192/512 PNG variants and the render
script in favour of serving the canonical `assets/logo.png` via a
small dedicated handler. The repo's existing logo becomes the single
source of truth; the PWA picks up logo changes automatically with no
re-render step.

- `LOGO_PNG` static is loaded via `include_bytes!` from the repo
  root, mirroring how `WEB_CLIENT_PAGE` reaches `assets/index.html`.
- New `serve_logo_png` handler is registered at `/assets/logo.png`
  before the `get_static_asset` wildcard. axum's matchit router
  gives literal paths precedence, so the explicit route always wins.
- Manifest declares a single icon entry at the actual file dimensions
  (2269x2620). Browsers letterbox onto square launcher surfaces, the
  same way they do for the existing favicon.
- `apple-touch-icon` retargets to `assets/logo.png`.
- `test_pwa_icon_assets_reachable` collapses to a single case.
- `scripts/render-pwa-icons.sh` and the rendering toolchain
  (librsvg + ImageMagick) are no longer needed.

* feat(web): drop redundant apple-mobile-web-app meta tags

The three `apple-mobile-web-app-*` meta tags are backward-compat
scaffolding for iOS < 16.4:

- `apple-mobile-web-app-capable: yes` — iOS 16.4+ reads
  `display: standalone` from the manifest.
- `apple-mobile-web-app-title: Zellij` — iOS 16.4+ reads `short_name`
  from the manifest.
- `apple-mobile-web-app-status-bar-style: default` — explicitly sets
  iOS's default status-bar behaviour, i.e. a no-op.

iOS 16.4 shipped in March 2023; the cohort still on iOS 15 is
essentially devices stuck on the iPhone 6s / 7 / SE 1st gen line
(2015-16 hardware), which is not the zellij audience.

`<link rel="apple-touch-icon">` is kept because iOS still ignores
manifest `icons` entries and reads only that tag for the home-screen
icon on every iOS version.

* feat(web): restore square pre-rasterized PNG icon for installability

The earlier swap to serving `assets/logo.png` directly fails
Chromium's PWA installability gate: the logo is 2269x2620 (not
square), and Chromium specifically requires "an icon at least 144
pixels square" -- a non-square icon is rejected even though the
declared `sizes` covers the dimension threshold. DevTools'
Application -> Manifest panel was misleading: it parsed and
displayed the icon, but installability is a separate stricter check.

This walks the swap back to a single committed 192x192 square PNG
(`icon-192.png`) rendered from `assets/logo.svg` with the logo
composited onto a transparent square canvas. The PNG is served by
the existing `get_static_asset` route via `include_dir!`. The
`LOGO_PNG` static, `serve_logo_png` handler, and dedicated
`/assets/logo.png` route are dropped.

apple-touch-icon retargets to icon-192.png (closer to iOS's
preferred 180px home-screen size).

Net effect on the diff: ~13KB committed PNG + a single icon entry
in the manifest, in exchange for actually meeting the
installability criterion.

* style: rustfmt

---------

Co-authored-by: Robert Jin <[email protected]>
Co-authored-by: Aram Drevekenin <[email protected]>
2026-06-01 15:35:39 +02:00
Aram Drevekenin 0f6cf0664e fix(webclient): handle host query responses for pixel dimensions and colors (#5156)
* fix(webclient): handle host query responses for pixel dimensions and colors

* rustfmt

* add pr
2026-05-11 10:25:11 +02:00
Aram Drevekenin e927b2b659 fix: occasional stall when querying host terminal (introduced in 0.44.1) (#5149)
* fix(client): unwedge termwiz on unrecognized CSI sequences

* rustfmt

* add pr
2026-05-08 09:51:20 +02:00
divens cc86e31b84 fix(windows): bump windows-sys to 0.59 (#5139)
The workspace `windows-sys` constraint was pinned to 0.52 while the
Windows source uses APIs and namespaces from 0.59 (e.g. WriteFile in
Win32::Storage::FileSystem). CI built fine because Cargo.lock pinned
0.59, but downstream `cargo install`/`cargo binstall` did fresh
resolution and selected 0.52, breaking the build for users on Windows.

Align the manifest with the code:
- Bump workspace dep `windows-sys` from 0.52 to 0.59.
- HANDLE is now `*mut c_void` instead of `isize`: replace
  `handle == 0` / `!= 0` with `is_null()`, initialise HANDLE locals
  with `std::ptr::null_mut()`, and pass `null_mut()` as the
  hTemplateFile argument to CreateFileW.
- Pass the process HANDLE through the exit-monitor thread as `usize`
  (raw pointers are not Send; OS handles are safe to use cross-thread).
- Note: HPCON is still `isize` in 0.59, so its zero-init is unchanged.
2026-05-06 17:02:10 +02:00
Ivan Smoliakov 50bac0807f fix: incorrect interpretation of unicode keys (#5110) 2026-04-29 15:06:49 +02:00
Aram Drevekenin 227142dd56 Support CSI2031 (dark/light theme switching) + remove STDIN cache to avoid stale color queries (#5105)
* initial phase 1 implementation

* various fies

* some fixes

* fix fragmented stdin cases

* some fixes

* renaming

* guard against old clients

* implement csi 2031

* allow toggling theme with actions

* rustfmt

* fix: finalize trailing characters in ansi parser as well

* add pr
2026-04-28 17:14:59 +02:00
TMYandAram Drevekenin 902e0bbd66 fix(web-client): prevent IME input text repetition (#4975)
* fix(web-client): prevent IME input text repetition

Fix bug where composing multi-character strings via IME (e.g. Chinese input method) would write the entire string N times for N characters.

Previously, parse_stdin sent the entire input buffer as raw_bytes for each KeyEvent parsed from that buffer. When IME composes "你好" (2 chars), InputParser creates 2 KeyEvents, but both carried the full "你好" bytes, causing it to be written twice.

Now, when multiple events are parsed from one buffer, each Char KeyEvent carries only its own UTF-8 bytes. Single-event buffers (most ASCII input) remain unchanged.

Fixes #4974

* fix: guard per-char raw_bytes with modifiers check

Only use per-character UTF-8 encoding for unmodified Char events in multi-event buffers. Modified keys (e.g. Ctrl+C) retain the original buffer to preserve correct VT bytes. Also clarify the comment.

* test(web-client): cover IME raw byte handling

* rustfmt

---------

Co-authored-by: Aram Drevekenin <[email protected]>
2026-04-22 15:09:25 +02:00
Aram Drevekenin 2897ab4898 fix: lost web characters (#5080)
* fix: lost web characters

* add pr
2026-04-21 09:17:53 +02:00
Thomas Linford 7d647f7b78 fix: kitty keyboard restore on error path + 2026 DECRPM state 3 handling (#5058)
* fix: kitty keyboard cleanup on error path + 2026 DECRPM state 3 handling

lib.rs: handle_error now exits kitty keyboard protocol (ESC[<1u) before
printing the backtrace, matching the normal exit path. Previously the
kitty keyboard mode was left active on crash, corrupting subsequent
terminal input.

stdin_ansi_parser.rs: synchronized_output_from_bytes now treats DECRPM
state 3 (permanently set) as enabled, matching states 1 and 2. State 3
is a valid "supported and on" response that was previously falling through
to None (treated as unsupported).

* docs: update changelog

* refactor(client): unify terminal teardown/setup sequences

- Extract named constants for all terminal escape sequences
- Add terminal_teardown_message() helper used by all three exit paths,
  fixing several pre-existing inconsistencies in the process:
  - handle_error was missing RESET_STYLE and SHOW_CURSOR
  - remote client had kitty keyboard exit after EXIT_ALTERNATE_SCREEN
    instead of before
  - goodbye path had goto positioned in the alt screen before exit
  - RESET_STYLE/SHOW_CURSOR are now sent after EXIT_ALTERNATE_SCREEN
    so they take effect in the normal screen
- Replace write() with write_all() throughout; drop redundant let _ =
  on unwrap() calls

* docs: update changelog
2026-04-20 21:32:34 +02:00
Aram Drevekenin aa29cbaf66 Some idle CPU and disk/io fixes, new CommandChanged plugin Event (#5063)
* initial implementation

* fix mouse cpu spam

* prevent excess metadata write and prune sessions on startup

* consolidate CommandChanged and PaneCommandChanged

* tests

* rustfmt

* add pr
2026-04-17 14:08:48 +02:00
0532949bbd fix(web-client): use base URL when switching sessions (#4981)
* fix(web-client): use base URL when switching sessions

Session switch redirected to /{session_name} instead of
{base_url}/{session_name}, breaking reverse proxy setups.

Use getBaseUrl() to construct the redirect URL, consistent with
how auth.js and connection.js already handle base URL.

Fixes #4980

Co-authored-by: Copilot <[email protected]>

* fix(web-client): encode session name in redirect URL

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

* docs: update changelog
---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
Co-authored-by: Thomas Linford <[email protected]>
2026-04-02 12:05:51 +02:00
Aram Drevekenin 7c68623580 Move from native-tls to rustls in the remote client (mirrors the web server) (#4972)
* native-tls -> rustls

* add/update tests

* rustfmt

* add pr
2026-03-31 20:19:50 +02:00
Aram Drevekenin 00c6b0f549 Error on socket paths that are too long (#4957)
* error on socket paths that are too long

* add pr
2026-03-30 16:43:01 +02:00
Aram Drevekenin 8ce05f0bbd Layout string flag (#4956)
* allow sending a stringified layout with --layout-string

* add --tab-id to all the things

* fix windows tests

* add pr
2026-03-30 16:02:09 +02:00
divensanddivens d12b75800c fix: resolve Windows compilation warnings (#4890)
- Consume unused CompilationResult from embed_resource::compile in build.rs
- Gate std::io::prelude import with #[cfg(unix)] since it's only used by
  daemonize_web_server which is unix-only

Co-authored-by: divens <[email protected]>
2026-03-28 10:38:37 +01:00
Aram Drevekenin 608cc9905d Support OSC-99 desktop notifications (#4931)
* initial implementation

* tests

* fix: always report to zellij, only report to app if it wants a report

* rustfmt

* fix: always report capabilities to panes requesting them

* rustfmt

* add pr
2026-03-28 09:19:54 +01:00
Aram Drevekenin a6a6440614 Fix cli blocking logic and update dependencies (#4881)
* fix: do not unblock blocking commands for other unblock conditions

* update deps

* allow trailing : in file path detection and change log format to match
2026-03-21 20:19:37 +01:00
divensanddivens 62633f930d fix(windows): avoid hang when share plugin starts web server (#4863)
Use cmd.status() instead of cmd.output() on Windows to prevent pipe
handle inheritance from blocking indefinitely. cmd.output() creates
pipe handles that the grandchild web server process inherits via
CreateProcess bInheritHandles, keeping them open and preventing EOF.

Co-authored-by: divens <[email protected]>
2026-03-19 08:58:13 +01:00