remap_modifier <from> <to>
Repeatable and off by default. The declarations compose as a single
simultaneous permutation applied once per event, so
remap_modifier ctrl super
remap_modifier super ctrl
exchanges the two rather than collapsing to the identity. Only shift, alt,
ctrl, super, hyper and meta may be named; anything else is rejected with an
error. The last declaration for a given source wins.
The remap is applied to the modifier mask BEFORE the platform produces the
keysym and text - in glfw_xkb_update_modifiers() before xkb_state_update_mask()
on Linux, and in remap_cocoa_flags() before UCKeyTranslate() and
interpretKeyEvents: on macOS. Applying it to the finished event instead
desynchronises the modifiers from the text for any modifier that participates
in text production: shift on Linux, and Option on macOS, which produces text
natively unless macos_option_as_alt is set.
Because the GLFW modifiers are themselves derived from that state, this is
application wide: kitty's own shortcuts, kitty_mod and the bytes sent to the
child all see the remapped modifier, and every mapping in kitty.conf is written
in terms of the modifier a key becomes. X11 pointer events do not go through
xkb, so translateState() - which only ever sees button and DND events - gets
the same treatment; Wayland pointer events already carry xkb-derived modifiers.
A modifier key's own event has its identity remapped too, so a program using
the full keyboard protocol does not see ctrl reported together with LEFT_HYPER.
The press/release state fixup on Linux is given the remapped modifier rather
than the physical one, so press and release agree.
--debug-input reports each remapped event.
Notes: on Wayland kitty only detects hyper and meta when
KITTY_WAYLAND_DETECT_MODIFIERS is set, so remapping to or from them has nothing
to act on there. On macOS hyper and meta have no Cocoa modifier flag, so a
remap onto them is left unapplied rather than silently dropping the modifier,
and interpretKeyEvents: is only approximately corrected because the event's
characters strings were computed by the OS from the physical flags; the
UCKeyTranslate() path, normally the one taken, is exact.
Generated option files and the glfw wrapper come from their generators.
A high resolution wheel delivers one physical detent as several VALUE120
events, so pending_scroll_pixels accumulates fractions of a line and carries
the remainder to the next event. Detents do not reliably total 120 units, so
that carry is load bearing: it is what lets an undersized detent still scroll
a line.
It breaks down when the scroll direction changes, because the residual then
has the wrong sign and the first detent of the new direction is spent
cancelling it rather than scrolling. Measured on a Logitech MX Master 3 under
GNOME/Wayland, against a terminal program that had requested mouse events,
47% of detents that reversed direction scrolled nothing, against 2% of
detents continuing in the same direction.
Discard the residual when the direction changes, and leave the carry alone
otherwise, so scrolling in a single direction is unaffected.
The accumulator state also moves from Screen to OSWindow, since scroll events
are delivered to an OS Window rather than to a particular terminal window.
Wrapping and tab_title_max_lines already compose, since wrapping runs before the
line limit is applied. Cover the combinations that were only checked by hand:
explicit newlines wrapped independently of each other, a wrap width wider than
the tab bar being clamped to it, and wrapped tabs packing by the lines they use
until the overflow ellipsis takes over.
A tab title too wide for the sidebar could only be truncated, which is wasteful
now that a title may use several lines. Add tab_title_wrap, accepting no (the
default, truncate as before), yes to wrap at the width of the tab bar, or a
number to wrap at that many cells.
Wrapping happens on grapheme boundaries so a line break can never land inside a
multi-codepoint grapheme, and SGR escapes are treated as zero width so colors
from tab_title_template neither consume cells nor get split in half. Wrapping is
bounded by tab_title_max_lines, and when a title needs more lines than it is
allowed the last line it gets is truncated with an ellipsis, which also makes
dropped lines visible for titles containing explicit newlines.
Only vertical tab bars are affected, since wrapping requires more than one row.
Each line of a vertical tab was only painted as far as its text reached, so the
tab background ended mid row and the sidebar showed a ragged right edge. This
was already the case for single line titles, but multi-line titles make it
obvious, since the lines of one title rarely have the same length.
Paint the tab's full width before drawing the title over it, so every line a tab
occupies is filled in that tab's colors.
Newlines in tab_title_template were drawn as a linefeed without a carriage
return, so each line started where the previous one ended, staircasing to the
right. Titles also collided with the tab below them after two lines, because
update_vertical assumed a fixed two line stride per tab.
Set LNM on the tab bar screen so a newline returns to the start of the next
line, measure how many lines each title actually occupies, and pack tabs at
cumulative offsets.
The blank line between tabs is preserved: one is inserted between tabs while
there is room, and dropped automatically once the tabs need the space, as
before. Previously this fell out of every tab reserving two lines, which is no
longer true now that a tab occupies only as many lines as its title needs.
The number of lines a title may use is limited by the new tab_title_max_lines
option, defaulting to 1 so existing configurations render as before. Lines
beyond the limit are dropped rather than bleeding into the next tab, and a title
is never allowed to use more lines than remain below it, since drawing past the
last line would scroll the tab bar and lose the tabs already drawn.
Only vertical tab bars are affected; horizontal tab bars share a single row.
desktui is a VNC client that renders the remote framebuffer with the
graphics protocol, one remote pixel per terminal pixel.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Allow terminal applications to detect when their view is not observable so they can avoid unnecessary rendering work.
Implement mode 2033 and the visibility status query, and report changes from tab, pane, window, and platform occlusion state.
I forgot to remove FT_Reference_Face call once font management was
passed to cairo.
Also fix an additional small leak of the python options object on all
platforms.
Fixes#10290
drag_icon_surface_listener is registered with only .enter and .leave.
wl_compositor is bound at version 6 whenever the compositor offers it,
so the drag icon surface receives wl_surface.preferred_buffer_scale
(opcode 2) and libwayland aborts on the NULL listener slot:
listener function for opcode 2 of wl_surface is NULL
Add no-op handlers for both version 6 events, matching surfaceListener.
The drag icon's scale is already fixed at drag start from the source
window, via viewport destination or wl_surface_set_buffer_scale, so
ignoring the compositor's later preference preserves existing behavior.
Fixes#10284