Skip to content

Linux: drag and drop, pinch, IME placement and clauses, slash key, sharp cursors and the channel icon - #435

Merged
RyeMutt merged 9 commits into
developfrom
rye/linux-input
Oct 10, 2026
Merged

RyeMutt merged 9 commits into
developfrom
rye/linux-input

Conversation

@RyeMutt

@RyeMutt RyeMutt commented Oct 10, 2026

Copy link
Copy Markdown
Member

Description

Fixes for the input items left from the Linux port audit: drag and drop, touchpad pinch, the IME, the slash key, cursors and the window icon. Nine commits, one change each.

Drag and drop, and pinch

  • Text dropped on the window reaches the viewer as DNDT_DEFAULT, as Win32's CF_TEXT does. A dropped SLURL teleports, and a URL dropped on a face you can modify becomes its media. File drops are unchanged.
  • There is still no live DNDA_TRACK. SDL reads what a drag carries only at the drop, on every backend, and the viewer's hover feedback depends on that payload. The DROP_POSITION case says so.
  • A touchpad pinch zooms as Ctrl+wheel does, as Windows precision touchpads deliver it:
    • one wheel click per quarter octave of finger spread, with fractions accumulated;
    • on Wayland and X11 the pinch scale is taken as the ratio to the previous one, since both report the scale since the gesture began.

IME and keyboard

  • The candidate window follows the caret in Script Studio and every other ALTextView.
    • The SDL backend now overrides updateLanguageTextInputArea().
    • It places the area on allowLanguageTextInput(true), after every TEXT_EDITING, and from ALTextView::draw, as LLTextBase and LLLineEditor already do.
  • The clause an input method is converting stands out in the composition. start/length are honoured as characters, and the caret goes to the clause.
  • A composition shows in the focused field after Alt-Tab. Focus loss no longer drops the preeditor; only a composition in flight is reset, as on Win32.
  • The main-row / is '/' on Linux and macOS, as VK_OEM_2 is on Windows. Only the keypad's is KEY_DIVIDE, which keeps the start_gesture binding.

Cursors and icon

  • The 32 custom cursors in use are RGBA PNGs with real alpha, replacing colour-keyed BMPs.
    • Most are converted from the .cur files and are pixel-identical to the old BMPs. Sit, buy and open lose their grey halos.
    • On Wayland, nearest-neighbour 2× and 3× copies are attached as alternate images. On X11 the cursor is scaled to the Xcursor size.
    • Hotspots are unchanged.
  • The 16 BMPs nothing loaded are removed.
  • The Linux window carries the channel's own hicolor icon, 256 with the 512 as its 2× version, from <tree>/share/icons, in place of ll_icon.BMP and its magenta frame. macOS is untouched, since there SDL_SetWindowIcon would replace the Dock icon.

Related Issues

  • Please link to a relevant GitHub issue for additional context.

Issue Link: none. These are the input items from the review of the Linux port's integration and desktop support.


Checklist

  • I have provided a clear title and detailed description for this pull request.
  • If useful, I have included media such as screenshots and video to show off my changes.
  • I have tested the changes locally and verified they work as intended. (partly, see Additional Notes)
  • All new and existing tests pass. (the test build was off locally)
  • Code follows the project's style guidelines.
  • Documentation has been updated if needed.
  • Any dependent changes have been merged and published in downstream modules.
  • I have reviewed the contributing guidelines.

Additional Notes

Behaviour changes to weigh

  • On Linux and macOS, the main-row / no longer opens the chat bar with "/" in-world, matching Windows. The keypad / still does. A default / binding would change Windows too, so none was added.
  • In the default third-person camera, a pinch changes the camera height (CameraOffsetRearView) rather than zooming, because that is what Ctrl+wheel does there. This matches a Windows precision touchpad.
  • The play cursor takes the boxed play button the pause and media-open cursors use, from its .cur and TIFF. The old BMP was a different, older drawing.

Needs an SDL patch (vcpkg registry): a link dragged from a browser still drops nothing on Linux.

  • SDL 3.4.18's Wayland and X11 backends both prefer text/uri-list, and drop every entry that isn't file://, with no fallback to DROP_TEXT. Dragged plain text, such as a selected SLURL, works.
  • The fix is a few lines in each backend: in SDL_waylandevents.c, data_device_handle_enter/handle_drop, and in SDL_x11events.c, SelectionNotify. Send a non-file URI as SDL_SendDropText rather than skipping it, and don't let a text MIME type override the uri-list on enter.

Tested on Fedora 44, GCC, Release, GNOME Wayland and XWayland:

  • The build passes with -Werror.
  • Launches to the login screen from the dev tree, and from a CPack .tar.xz extracted and started through its alchemy wrapper.
    • Under X11, both windows carry a 256×256 _NET_WM_ICON and WM_CLASS org.alchemyviewer.viewer.test.
    • The tarball ships the six hicolor sizes and the 32 PNG cursors, and no BMPs.
  • Cursor before/after composites over light and dark backgrounds show the halos gone, and crisp 2× copies.

Not tested:

  • ibus or fcitx composition on Wayland and X11;
  • a real touchpad pinch;
  • live drag and drop;
  • HiDPI cursor scales on a HiDPI output;
  • KDE, the only desktop here that would show the icon under Wayland;
  • macOS.

🤖 Generated with Claude Code

https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy

RyeMutt and others added 9 commits October 10, 2026 05:52
…pped SLURL teleports

SDL_EVENT_DROP_TEXT was thrown away, so dragging a SLURL or a URL onto
the viewer did nothing on Linux and macOS. Its lines are now gathered
and, on DROP_COMPLETE, sent through handleDragNDrop as DNDT_DEFAULT with
the same start, drop and stop steps as files, which is how Win32 hands
over CF_TEXT: a spatial SLURL teleports and a URL dropped on a face
becomes its media. File drops are unchanged.

There is still no live tracking. SDL reports where a drag is
(DROP_POSITION), but every backend reads what it carries only at the
drop, and the viewer's hover feedback depends on what is carried, so
DROP_POSITION is left alone with a note saying why. A browser's link
drag still delivers nothing on Linux: SDL 3.4.18's Wayland and X11
backends take its text/uri-list and discard every entry that is not a
local file. Dragged text works there, and links work on macOS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
SDL 3.4 reports a touchpad pinch (Wayland's pointer gestures, XInput
2.4's, Cocoa's magnify) as SDL_EVENT_PINCH_*, and nothing handled them,
so pinching did nothing. A Windows precision touchpad delivers a pinch
as Control+wheel. The pinch now reaches the viewer the same way: wheel
clicks at the cursor with Control held for the call, a click for every
quarter octave of spread, which is the camera orbit's own step, with the
fractions gathered so a slow pinch still makes its clicks. Spreading the
fingers is a wheel rolled away, zooming in.

SDL documents the scale as the change since the last update, and Cocoa
sends that, but its Wayland and X11 backends pass on the scale since the
gesture began, as their protocols carry it, so on those the ratio to the
last update is taken. As on Windows, in the default third-person view
Control+wheel raises or lowers the camera rather than zooming it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
…ry other text view

On SDL the text input area reached the IME only from LLLineEditor's and
LLTextBase's draws, through setLanguageTextInput. ALTextView only allowed
language input, so in Script Studio and the other ALTextView editors the
candidate window opened wherever the last line editor had left the area,
or at the window's origin. A bare caret also always took the 500x16
fallback, since every preeditor gives it bounds with no width.

The backend now asks the owning preeditor where its caret and preedit
are (placeLanguageTextInputArea, also the updateLanguageTextInputArea
override) when a widget takes language input, after every composition
update, as Win32 does after WM_IME_COMPOSITION, and from
setLanguageTextInput. Bounds of no width are taken. ALTextView reports
its caret from its draw, as the text fields do, while it holds language
input and the caret is in sight. Its preedit bounds now lie on the
caret's row when a composition wraps, rather than on its first row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
…n on SDL

The SDL backend handed every composition to the preeditor as one plain
segment and read event.edit.start as the caret alone, so the clause a
Japanese or Chinese input method was converting looked like the rest of
the preedit. SDL marks that clause with start and length, in characters,
from the highlight ibus, fcitx, XIM, text-input-v3 and Cocoa report.
The composition is now split into what comes before the clause, the
clause marked standout, and what comes after, as the Win32 backend does
with its target clause; the editors draw a standout segment with the
thicker underline. The caret goes to the clause's start, where the
candidate window belongs, or to the IME's caret when there is no clause.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
…e keypad's is Divide

LLKeyboardSDL translated SDLK_SLASH to KEY_DIVIDE, the keypad's code,
so the main-row slash fired the start_gesture binding DIVIDE has in
key_bindings.xml: a / pressed in world, or with anything but a text field
focused, opened the chat bar with a slash, which on Windows only the
keypad's does. LL mapped VK_OEM_2 to '/' there for EXT-3650, and SDL now
does the same; the keypad's slash stays KEY_DIVIDE and keeps
start_gesture. The inverse map gives SDLK_KP_DIVIDE for KEY_DIVIDE and
SDLK_SLASH for '/', and mapSDLtoWin already sent the two keys to
plugins as VK_OEM_2 and VK_DIVIDE. alKeyAsBound still reads KEY_DIVIDE
as '/', so Script Studio keymaps hear the keypad's slash and keep
bindings saved as it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
…from Alt-Tab on SDL

The SDL backend forgot its preeditor whenever the window lost focus, on
the belief that the focus manager clears keyboard focus then. It does
not: setAppHasFocus leaves the field focused, and no editor allows
language input again when the window returns, so every TEXT_EDITING was
dropped and an input method composed blind, its text arriving only on
commit, until the field was focused anew. The preeditor is kept now, as
on Win32, and only a composition in flight is reset: SDL stops text
input with the focus, and what the IME does with it after never reaches
the window, so it would have stayed in the text.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
res-sdl/ held sixteen BMP cursors nothing reads: arrow, circleandline,
cross, hand, ibeam, llarrow, llarrowdrag, the five size* cursors,
toolpickobject, toolpickobject2, wait and working. The SDL window takes
the arrow, hand, I-beam, crosshair, resize, wait and working cursors from
the system theme, and LLWindowSDL::initCursors names none of the others,
yet the Linux tree shipped them all. They are removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
The tool cursors were 32x32 BMPs keyed on (200,200,200). The anti-aliased
ones (sit, buy, open) had edge pixels blended against that grey, which the
key missed, so they wore a dark stair-stepped halo; and one 32-pixel image
was all there was, so a Wayland compositor at 200% scaled it up blurred,
and an X11 HiDPI desktop showed it half the size of the theme's cursors.

Each cursor is now an RGBA PNG in res-sdl/, loaded with SDL_LoadPNG. Most
come from the Windows .cur, which is the same art as the BMP, pixel for
pixel, or for sit, buy and open the same art with its real alpha.
toolplay takes the .cur's boxed play button, as pause and media-open
have, over the BMP's older dithered triangle; toolpickobject3 keeps the
BMP's own drawing, which is black and white only. The hot-spots are the
ones the BMPs were loaded with. Under Wayland each cursor carries 2x and
3x nearest-neighbour copies as alternate images, which SDL picks among by
the window's scale; under X11, where SDL sends only the surface itself,
the surface is scaled to XCURSOR_SIZE over 24, or failing that to the
display's content scale.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
The window icon was res-sdl/ll_icon.BMP, the channel's 256 BMP with an
opaque magenta (#FF00F6) border meant to be keyed out, which nothing
did, so X11 title bars and taskbars and KDE's Wayland showed the icon in
a magenta frame. The window now takes the hicolor PNGs the tree installs
for its desktop entry, share/icons/hicolor/<size>/apps/<app ID>.png,
found from the read-only data dir and the app ID SDL was given: the 256
as the icon, as before, and the 512 as its 2x. SDL sends X11 only the
icon itself and Wayland only alternates at whole multiples of it, so the
smaller sizes are not loaded. A tree without them keeps the desktop's
default icon. macOS still leaves the window icon alone, since SDL would
set the Dock's over the bundle's.

Nothing reads ll_icon.BMP any more, in res-sdl/ or, on Windows, in res/,
whose resource script embeds only ll_icon.ico, so neither is copied from
the branding package, and the Linux tree installs only the cursor PNGs
from res-sdl/, leaving out a copy an older configure left there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018TgkpVXucG17x8kCrsXMjy
@coderabbitai

coderabbitai Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 65c67bee-4843-4dbe-bc45-e41ea36858af

📥 Commits

Reviewing files that changed from the base of the PR and between fb5ce83 and 8b3c140.


⛔ Files ignored due to path filters (80)
  • indra/newview/res-sdl/arrow.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/arrowcop.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/arrowcop.png is excluded by !**/*.png
  • indra/newview/res-sdl/arrowcopmulti.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/arrowcopmulti.png is excluded by !**/*.png
  • indra/newview/res-sdl/arrowdrag.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/arrowdrag.png is excluded by !**/*.png
  • indra/newview/res-sdl/circleandline.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/cross.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/hand.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/ibeam.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/llarrow.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/llarrowdrag.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/llarrowdragmulti.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/llarrowdragmulti.png is excluded by !**/*.png
  • indra/newview/res-sdl/llarrowlocked.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/llarrowlocked.png is excluded by !**/*.png
  • indra/newview/res-sdl/llgrablocked.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/llgrablocked.png is excluded by !**/*.png
  • indra/newview/res-sdl/llno.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/llno.png is excluded by !**/*.png
  • indra/newview/res-sdl/llnolocked.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/llnolocked.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolcamera.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolcamera.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolcreate.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolcreate.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolfocus.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolfocus.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolgrab.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolgrab.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolland.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolland.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolpan.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolpan.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolpathfinding.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolpathfinding.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolpathfindingpathend.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolpathfindingpathend.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolpathfindingpathendadd.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolpathfindingpathendadd.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolpathfindingpathstart.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolpathfindingpathstart.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolpathfindingpathstartadd.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolpathfindingpathstartadd.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolpipette.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolpipette.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolrotate.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolrotate.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolscale.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolscale.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltooltranslate.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltooltranslate.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolzoomin.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolzoomin.png is excluded by !**/*.png
  • indra/newview/res-sdl/lltoolzoomout.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/lltoolzoomout.png is excluded by !**/*.png
  • indra/newview/res-sdl/sizeall.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/sizenesw.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/sizens.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/sizenwse.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/sizewe.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolbuy.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolbuy.png is excluded by !**/*.png
  • indra/newview/res-sdl/toolmediaopen.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolmediaopen.png is excluded by !**/*.png
  • indra/newview/res-sdl/toolopen.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolopen.png is excluded by !**/*.png
  • indra/newview/res-sdl/toolpause.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolpause.png is excluded by !**/*.png
  • indra/newview/res-sdl/toolpickobject.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolpickobject2.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolpickobject3.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolpickobject3.png is excluded by !**/*.png
  • indra/newview/res-sdl/toolplay.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolplay.png is excluded by !**/*.png
  • indra/newview/res-sdl/toolsit.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/toolsit.png is excluded by !**/*.png
  • indra/newview/res-sdl/wait.BMP is excluded by !**/*.bmp
  • indra/newview/res-sdl/working.BMP is excluded by !**/*.bmp

📒 Files selected for processing (8)
  • indra/cmake/ViewerInstall.cmake
  • indra/llui/code/alkeychord.h
  • indra/llui/tests/alkeymap_test.cpp
  • indra/llui/text/altextview.cpp
  • indra/llwindow/llkeyboardsdl.cpp
  • indra/llwindow/llwindowsdl.cpp
  • indra/llwindow/llwindowsdl.h
  • indra/newview/CMakeLists.txt

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@RyeMutt
RyeMutt merged commit fd333dc into develop Oct 10, 2026
24 of 26 checks passed
@RyeMutt
RyeMutt deleted the rye/linux-input branch October 10, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant