Added
-
TitlebarDarkswitches the window frame on Wayland under GNOME (#1192).
On a compositor that leaves the frame to the app (GNOME, weston), go-gui draws
it through libdecor, whose GTK plugin takes its light or dark variant from a
GTK setting that the plugin sets at most once, at start-up.
Window.TitlebarDarknow sets that variant and repaints the frame at once. It
was a no-op on every backend before.FollowSystemAppearancealready calls
it, so a following window's frame now switches with the desktop setting. An
app that pins its theme withSetThemecalls
TitlebarDark(theme.TitlebarDark)to match. Before, a dark app on libdecor
0.2.2 (Debian 13, Ubuntu 24.04) sat under a light frame, and on later versions
the frame kept its start-up variant after the desktop switched. The variant is
one per process, so the last call sets it for every window. The cairo plugin's
colours are fixed, and X11, Windows and macOS frames do not change yet.
examples/system_appearance/follows the
desktop and pins a light or dark theme with the frame to match. -
Key codes for every printable ASCII character, and
ModAltGr(#1188). The
keys that had noKeyCodeconstant now have one, valued at their character
code like the existing ones:KeyExclamation,KeyQuotation,
KeyNumberSign,KeyDollar,KeyPercent,KeyAmpersand,KeyLeftParen,
KeyRightParen,KeyAsterisk,KeyPlus,KeyColon,KeyLess,
KeyGreater,KeyQuestion,KeyAt,KeyCircumflex,KeyUnderscore,
KeyLeftBrace,KeyBar,KeyRightBraceandKeyTilde.KeyScrollLock,
KeyPrintScreen,KeyPauseandKeyF21–KeyF24are exported.ModAltGr
names ISO level 3 (AltGr), which is not Ctrl+Alt;Shortcut.Stringshows it
asAltGr+. Linux sends them (see Fixed); other platforms do not yet. -
Shortcuts match the key a press typed:
Event.TypedKeyand
Event.TypedModifiers(#1188). A key down now also carries the key the
press produced with Shift, AltGr and NumLock applied, and the modifiers that
translation did not use up. On a German layout Shift+7 types/:KeyCode
staysKey7with Shift,TypedKeyisKeySlashwith no modifiers. Command
dispatch tries an exactKeyCode/Modifiersmatch first and, only when no
command matches,TypedKey/TypedModifiers. SoShortcut{Key: KeySlash}
fires on German Shift+7,Ctrl+/on Ctrl+Shift+7,KeyLeftBracketon AltGr+8
andCtrl++on US Ctrl+Shift+=, while a command bound to the exact keys still
wins. Ctrl+Shift+A stays Ctrl+Shift+A: a press that types the same key keeps
all its modifiers. On the default US layout every existing shortcut behaves as
before. The Linux backends (Wayland and X11) set the fields; on Windows, macOS
and web they are zero and matching is exact, as before.
examples/key_inspector/shows, for every key
press, itsKeyCode,TypedKey, modifiers and character, and the shortcut to
bind it with; its terminal output is what a keyboard bug report needs. -
Custom shaders can read textures:
Shader.Surfacebinds aPixelSurface's
planes asiChannel0-iChannel3(#1168). A surface can now hold up to four
planes inR8,RG8orRGBA8, each with its own size
(PixelSurfaceCfg.Planes), andUpdatePlanespublishes all of them as one
frame, so a shader never sees one plane's new pixels with another's old ones.
A shader with aSurfacereads plane i asiChanneli, Shadertoy style,
withiChannelResolution; sampling is linear and clamps to the edge. This is
what video needs: NV12 frames go to the GPU as an R8 and an RG8 plane and the
shader converts them, with no YUV code in go-gui. A new frame redraws without
rebuilding layout orShader.Animate. Shaders without aSurfacecompile
exactly as before.Window.RendersShadersreports whether the backend runs
shaders (not the soft renderer or PDF), so a library can fall back to an RGBA8
Image. On Metal an upload into a surface texture waits for an earlier frame
still sampling it, asreplaceRegiondoes not. See
examples/shader_channels/. -
PixelSurface: an image the app can update every frame (#1168). Create
one withNewPixelSurface, pass it asImageCfg.Surface, and callUpdate
with RGBA8 pixels from any goroutine.Updatecopies the pixels, so the
producer can reuse its buffer, and keeps only the newest frame. Every window
that draws the surface redraws without rebuilding its layout, and each
renderer uploads only when there is a new frame, reusing its texture at a
fixed size. This is what a video player, camera preview or emulator screen
needs; until now an image could only change by registering a new one. All
backends draw it (OpenGL, Metal, WebGL/Canvas2D, Android, iOS, software) and
PDF export embeds the current frame.Resizechanges the size andClose
stops it and releases the textures. Images without aSurfaceare unchanged.
examples/pixel_surface/animates one at 60 Hz. -
InputCfg.OnPasteRejectedreports a paste the field refused (#1187). It
is called with the whole paste, before any cap, when Ctrl+V, the context
menu's Paste or the macOS Ctrl+Y yank leaves the field unchanged: the text
would exceed the field's 65,536-rune budget, theMaskhas no room for it, or
PreTextChangerefused it. Until now only the reject cue marked a refusal,
and that is silent unless the app installs a sound player. The app can now say
why ("Paste too large: 92,359 characters"), keep the text, or open it in a
view that can hold it.ctx.Layoutis the input's own layout andctx.Event
is nil, as inOnTextChanged.
Fixed
-
A Wayland window with a libdecor frame no longer quits on its own (#1194).
On a compositor that leaves the frame to the client (GNOME, weston), the event
loop sometimes ended after logginggl: wayland: libdecor_dispatch: -4, with
no user action, and the app quit. libdecor's dispatch ends in apoll(),
which a signal fails withEINTR, and the Go runtime sendsSIGURGto
preempt goroutines, so a busy app gets one now and then. The backend took the
interrupted poll for a dead connection. It now counts as a dispatch that found
nothing, and the loop's next pass picks up the events. Other libdecor errors
still end the loop. TheGODEBUG=asyncpreemptoff=1workaround is no longer
needed. -
A
ContextMenuopened near the bottom or right edge of the window opens
next to the cursor (#1193). Right-clicking near the bottom of a tall menu
area, such as a table filling the window, opened the menu at the top of the
window. The flip that keeps a popup in the window treated the click position
as a gap from the area's top edge and mirrored it to the area's bottom edge,
which for a tall area is far above the cursor. The popup now hangs from the
click point and flips around it: with no room below it opens above the cursor,
with no room to the right it opens to the left. The same fix applies to the
link menu on a right-clicked link inRTFandMarkdown, which had the same
fault in long documents. Where the menu fits below and to the right of the
cursor, it opens exactly where it did before. The tooltip on anRTFor
Markdownrun had the same fault: hovered near the top of the window, it
flipped to the bottom of the text block, far from the word. It now opens 3px
below the word when there is no room above it. -
Keys on non-US keyboard layouts get a
KeyCodeon Linux (#1188). A key's
KeyCodecame from its first-level symbol through a US-only table, so on
German, French, Russian and most other layouts many keys reported
KeyInvalid:#,+,<,^, the numpad, NumLock, Menu, the AZERTY number
row, and every letter on a Cyrillic layout. Shortcuts on those keys never
fired, numpad arrows did nothing in a text field, and Ctrl+C/V/X/Z did nothing
on a Russian layout. On Wayland and X11 the code now follows the rules of
docs/specs/keyboard-layouts.md: named and keypad keys by their symbol (the
numpad navigates with NumLock off), ASCII letters and digits, the digit Shift
types on AZERTY, on non-Latin layouts the letter of the first configured Latin
layout (else the US one at that position), and every printable ASCII
character. AltGr is reported asModAltGr, so AltGr+8 typing[no longer
fires aKey8shortcut, and an AltGr chord no longer acts as the plain key
for focus traversal (Tab) or keyboard scrolling. A key's release reports the
code of its press. On the default US layout nothing that worked changes; a
test pins this. -
On X11, AltGr, NumLock, Caps Lock and non-Latin layouts type the right text
(#1188). The X11 backend chose a key's symbol from Shift alone, so on a
German layout AltGr+Q typedqinstead of@(and[,],{,},\,
|,~,€could not be typed at all), numpad digits typed nothing with
NumLock on, Caps Lock typed lowercase letters, and Cyrillic, Greek and other
non-Latin letters typed nothing. It now applies the AltGr level, the NumLock
rule for keypad keys, Caps Lock as XKB does (Shift+Caps Lock types the
lowercase letter), and libxkbcommon's character table when libxkbcommon is
installed, as the Wayland backend does. Without libxkbcommon,€and the
numpad's characters type too; only non-Latin text needs it. A layout changed
while the window is open (setxkbmap, the desktop settings) applies to the next
key press; the keyboard was read only at start. AltGr is recognized on
whichever modifier the keyboard mapping puts it, and a key an IBus input
method forwards is reported as the same key typed directly. go-gui now enables
the XKB extension on X11, as GTK and Qt do: without it Xorg reported a second
layout as a held AltGr. Key symbols come from the server's XKB map, so a third
and fourth layout configured together type their own characters too. Where
only XKB's key types say how a key behaves (a layout's own level order, as on
some keypads and oncm(azerty)'s digit row, ISO level 5, a layout's own Caps
Lock type, as Turkish declares for i/İ), the X11 result can differ from
Wayland; the spec lists these. -
A paste too large for an
Inputis refused whole instead of cut short
(#1187). A paste that would take the field past 65,536 runes inserted only
the first 65,536, with no sign anything was dropped, so pasted JSON, CSV or
logs ended mid-document. Two smaller pastes that together crossed the budget
were kept until the next keystroke cut the text silently. The field now stays
unchanged, sounds the reject cue and callsOnPasteRejected. The budget
counts the text after the paste, so replacing a selection frees room. Typed
text and IME commits are unchanged. Two cases that used to keep part of a
paste now refuse it: aMaskfield handed more than 65,536 runes, which took
its first matching characters, and a field whose app-set text is already over
the budget, which now takes a paste only over a selection. -
Typing or pasting the same digit over itself in a masked
Inputno longer
counts as refused (#1187). In a full field such as111, typing1at the
start sounded the reject cue and left the caret where it was, so typing over a
filled mask stalled on every digit that matched the one already there. Pasting
123over a selected123did the same and now would also have called
OnPasteRejected. The mask accepted the input; only the text came out the
same. The caret now moves on and the selection clears, as for any accepted
edit, with no undo step, since the text did not change. -
A press anywhere in an
Inputnow places the caret and opens its context
menu (#1186). In aScrollablemultilineInput, only the lines holding
text took a press: a left click below the last line placed no caret, and a
right click there opened no Cut/Copy/Paste menu. A field scrolled sideways
past a long word took no press at all, not even on the visible text. The
handler sat on the inner row that holds the text, which is only as tall as the
text and moves with the scroll. It now sits on the field, which is where the
I-beam cursor already shows. So a press in the padding of anyInputplaces
the caret too, at the nearest character, as in a native text field; before, it
only focused the field. Scrolling is unchanged, and a press on a scrollbar
still goes to the scrollbar, a right press included. A screen reader's
activate action (VoiceOver press, AT-SPI press, TalkBack double tap) now
focuses the field and leaves the caret where it was; before, it did nothing. -
An open
Selectdropdown now closes when focus leaves the field (#1181).
Before, a press on anInputor another focusable widget moved focus there
but left the list open over the form, and aSetFocusfrom app code did the
same. This is the checkComboboxgot in #1150; both widgets now share it. A
press on the dropdown scrollbar keeps the list open, a pick in a multi-select
still leaves it open, and aFocusDisabledselect skips the check. Opening
the list now also focuses the field, asComboboxdoes, so a screen reader's
press opens a list that stays open. -
A
FocusDisabledSelectorComboboxnow opens on a click (#1183).
Before, the click did nothing: the field takes no focus, so the press counted
as unhandled and the window closed the dropdown again in the same press. A
Selectclick also took focus away from the widget that held it. The field
now consumes the press, so the dropdown opens, an option can be picked with
the pointer, and focus stays where it was, asFocusDisabledpromises. A
FocusDisabledComboboxno longer moves focus to the first tab stop when it
opens. Picking aComboboxrow with the pointer now keeps focus where it was,
asSelectalready did: on the field, or forFocusDisabledon the widget
that held it. Before, the pick cleared focus.
What's Changed
Other changes
- feat(gui): PixelSurface, an image the app updates every frame (#1168) by @ollex in #1178
- feat(gui): shader texture inputs from multi-plane PixelSurfaces (#1168) by @ollex in #1180
- fix(select): close dropdown when focus leaves the field (#1181) by @ollex in #1182
- fix(select,combobox): open a FocusDisabled dropdown on a click (#1183) by @ollex in #1184
- fix(input): take presses on the whole field, not the inner row (#1186) by @ollex in #1189
- fix(input): refuse an oversize paste whole, add OnPasteRejected (#1187) by @ollex in #1191
- fix(menu): open a context menu next to the cursor near the window edge (#1193) by @ollex in #1195
- fix(keyboard): layout-correct keys, text and shortcuts on Linux (#1188) by @ollex in #1196
- fix(wayland): treat EINTR from libdecor_dispatch as no events (#1194) by @ollex in #1197
- feat(wayland): TitlebarDark switches the libdecor GTK frame (#1192) by @ollex in #1199
- changelog: add v0.94.0 by @ollex in #1200
Full Changelog: v0.93.0...v0.94.0