Commit Graph
Select branches
Hide Pull Requests
main
Select branches
Hide Pull Requests
main
-
582f9e1a37
Removed extra debug prints
admin
2026-05-31 02:14:34 +02:00 -
042652ec73
windows: centralise input focus on a single FocusTarget, raise+focus on every window interaction Focus was scattered across three stores —
Layouts::focus,Layers::focusandWindows::activated— plus a hack that smuggled X11 surfaces throughLayers::focus, with the keyboard target derived each frame from alayers.focus.or(layout)precedence. As a result "focused", "activated" and "has the keyboard" could drift apart: a window could be raised without taking the keyboard, the recurring "click a console window, then click it again before you can actually type" symptom. Newwindows/focus.rswithFocusTarget { Layout(Weak<Window>), Layer(WlSurface, app_id), X11(WlSurface) }.Windowsnow holds onefocus_target: Option<FocusTarget>as the single source of truth;activatedsurvives only as a reconciliation cache for the xdgActivatedstate and is never set by hand.set_focustakes oneOption<FocusTarget>and is the only entry point for changing focus: it brings the target to the front (z_promote+ desktop stacking), refreshes the MRU and stores the target, and keyboard focus plus activation are applied from it on the nextfocus().focus()resolves a baseline (the active layout's window, which carriesActivated) and an overlay (a focused layer/X11 surface that takes the keyboard over the baseline without de-activating it), mirroring the old precedence but from one field, so keyboard focus follows focus by construction.Layers::focusis removed and X11 no longer rides inside it; the exclusive-keyboard layer path andreap_layerdrivefocus_targetdirectly, and every click/touch/activation site collapses toset_focus(Some(FocusTarget::…)). surface_at popup tie-break. When a layout hit is a popup or subsurface, itswl_surfaceis not tracked intoplevel_z_order, so the layout-vs-X11 z compare resolved toNoneand the X window underneath wrongly won — clicking a GTK menu (e.g. Firefox's) over an X11 window pressed the X window instead of the menu item. The tie-break now compares the owning toplevel's root z, taken from the hit'sFocusTarget::Layoutweak, so the popup's parent stacking decides. Raise and focus on every window interaction, matching standard desktop behaviour. Client-driven (un)maximize, the SSD maximize/minimize/close buttons and the maximize keybind now bring the window to the front in both the render z-order and the hit-test stacking through a newraise_toplevelhelper; previously only the stacking was updated, so an unmaximised window could stay visually behind another. Starting a move or resize — including a press on the resize edge — now routes throughset_focus, so the window comes to the front and takes the keyboard before the drag instead of being resized or moved while it stays behind. Input robustness around grabs and drags. A press under an active pointer grab (Wayland popup, drag-and-drop or explicit client grab) is routed to the grab instead of forge's own surface_at handling, so a click cannot leak to a window underneath a grabbing popup and a click outside the popup dismisses it cleanly. Only the press is blocked, never the release, so an in-flight move/resize drag or topbar swipe always reaches its end handler and can never stay glued to the cursor if a grab appears mid-gesture.on_touch_upnow ends any active move/resize drag before its other early-return paths (armed SSD button, topbar swipe, app switcher) and drops a stale armed button, fixing a race where pressing a second, overlapping window left the drag running forever after release. fix(input): keep the primary touch slot when migrating a drag across surfaces A touch drag started on an overlay that hides mid-gesture (e.g. the app launcher when dropping an icon onto the dock or the homescreen) froze: it neither moved nor dropped. Destroying the origin surface migrated the drag state (long_press_fired / long_press_origin) and touch_focus to the main surface, but not its primary_touch_id; subsequent touch motion/up events then fell through the auxiliary path and never reached the gesture machine, leaving on_drag_move and on_drop uncalled. reconcile_overlays and discard_overlay now adopt the destroyed overlay's primary_touch_id when a drag is in flight, so the rest of the touch sequence keeps driving the main surface. Touch-only; the mouse already migrated correctly via pointer_focus. ltk: add a chassis module for full-screen ambient surfaces Newsrc/chassis.rs, re-exported from the crate root, gathers the scaffolding that every full-screen ambient surface — greeter, lock screen, kiosk — otherwise repeats by hand over the existing theme andWallpaperBundleprimitives.set_default_theme(mode)finds, installs and activates thedefaulttheme document, returning the failure message instead of exiting so the caller decides how to abort.theme_logo_rgba(size)decodes the active theme's horizontal logo to RGBA, andtheme_icon_tinted(name, size, tint)loads a symbolic theme icon and tints it.branding_bundle_or_solid(name)resolves a theme branding image ("wallpaper","lockscreen", …) to aWallpaperBundle, falling back to a solid fill of the palette background when the theme ships none;wallpaper_bundle_or_solid()is the"wallpaper"convenience.backdrop(content, &wallpaper, w, h)stacks content over the wallpaper resolved for the surface size. No new capability — thin convenience over what the theme module andWallpaperBundlealready expose — but it removes the per-applicationload_theme_logo/build_wallpaper_bundle/ theme-bootstrap duplication. ltk: animatable, input-transparent child surfaces via App::subsurfaces() NewApp::subsurfaces() -> Vec<SubsurfaceSpec<Msg>>(default empty) describes input-transparent child surfaces composited over the main surface, withSubsurfaceSpec { id, view, x, y, content_version }and the stableSubsurfaceId. The motivating use is a slide/reveal that tracks a finger without the per-frame full-screen CPU re-raster a single-surface opacity or translate would cost: the content buffer is rasterised once and the compositor moves it.SubcompositorStateis bound inevent_loop/run.rsfrom the compositor'swl_compositor(absent →App::subsurfacessilently degrades to none);delegate_subcompositor!is added onAppData, which gains asubcompositorbinding and asubsurfacesmap. Newevent_loop/subsurface.rsreconciles the live subsurfaces against the specs: each spec becomes awl_subsurfacesized to the main surface with an empty input region, so all pointer/touch falls through to the parent and the host keeps a single gesture/input model. The content — its own SHM pool andCanvas, drawn through the existingDrawCtx/layout_and_drawpath — is rasterised only when the surface size or the spec'scontent_versionchanges; a position-only change emitswl_subsurface.set_positionand commits the child surface, then a bare parent commit for placement. Committing the child is deliberate: desync subsurface state (the position) is applied on the child surface's own commit, not the parent's, so without it the move is queued but never lands. Positions are given in layout (physical) pixels and divided by the surface scale for the logicalset_position. The reconcile pass runs on every run-loop iteration rather than being gated behind a main-surface redraw, so a finger-driven move repositions at input-event rate, decoupled from the frame-callback cadence that paces full redraws — without this the subsurface only moved when the main surface happened to redraw, so a drag over a static background froze the panel in place.admin
2026-05-29 23:28:48 +02:00 -
9ca3b60f3a
ltk: responsive padding/spacing and scrolling, expanded theme palette, and bundled Adwaita cursors A mixed pass over the default theme and the layout/input core, plus the toolkit's own cursor set. Grouped by area below. == Responsive sizing == Add
Length::dp( px )— a "design pixel". It interpretspxagainst a configurable reference vmin (default 412 px, the eydos mobile reference width) and returnsVmin( px / reference * 100 ).clamp( px * 0.7, px * 1.5 ), so a value authored against a mock-up scales with the surface without collapsing on tiny screens or ballooning on a 4K desktop. The reference is process-global, set viaset_design_reference()and read viadesign_reference()(stored as f32 bits in an AtomicU32); both are re-exported fromlib.rs. Make container and grid insets relative.Container's four padding fields becomeLengthinstead off32; every setter (padding,padding_h,padding_v,padding_top/right/bottom/left) now takesimpl Into<Length>, so existingf32call sites keep compiling via theFrom<f32>shim. The values are resolved against the viewport inContainer::preferred_sizeand in the container draw path (draw/layout.rs).WrapGrid'sspacing_x,spacing_yandpaddingget the same treatment, with aresolved( canvas )helper funnelling the per-frame resolution andgrid()seedingLength::pxdefaults. Container tests now compare againstLength::px( … ). == Scrolling ==Scroll::preferred_sizeis now axis-aware. A horizontal-only scroll reports its child's natural height rather than claiming all remaining vertical space, so it no longer steals Y from its siblings when it sits inside aColumn; vertical and both-axis scrolls keep the spacer-like( max_width, 0.0 ).Column's space-distribution correspondingly treats aScrollas a vertical space-claimer only when its axis allows Y. Disambiguate nested scroll viewports by direction. On press the gesture state now collects every scroll viewport under the point (scroll_candidates, innermost first) instead of committing to one; on the first 8 px of motion it locks onto the candidate whose axis matches the dominant direction (scroll_locked), so a horizontal scroller nested inside a vertical list no longer grabs the wrong axis. The pointer scroll hit test is aligned to the same innermost-first ordering. == Theme palette ==themes/default/theme.jsongains named colours (green / green-deep, yellow, orange / orange-deep, pink / pink-soft, sky-deep, error / error-soft, neutral-tertiary) and new semantic slots in both light and dark modes:danger,text-tertiary,accept,chip/chip-active/chip-active-fg, andavatar-1…avatar-9. == Cursors == Bundle GNOME's Adwaita cursor theme — the cursors GNOME Shell uses — intothemes/default/cursors/so a Wayland compositor can draw consistent, complete pointers for ltk applications without the toolkit rasterising cursors itself and without depending on adwaita-icon-theme being installed on the target. The cursors are copied verbatim in XCursor binary format: 35 image files, one per CSS/freedesktop cursor name (default, text, pointer, *-resize, …), plus 27 customary X11 alias symlinks (arrow → default, hand2 → pointer, …); a siblingcursor.thememakes the tree a valid XCursor theme. The existingltk-theme-default.installcopiesthemes/defaultrecursively, so the directory ships with no packaging change. Applications keep declaring aCursorShapeper widget overwp_cursor_shape_v1; the compositor resolves it against the active theme'scursors/directory by name, and the set covers all 34CursorShapevariants. Document the set inthemes/default/cursors/README.md(what it is, the XCursor layout, the full shape list, how the compositor consumes it, guidance for forks) andthemes/default/cursors/LICENSE.md(attribution and licence options, modelled on the icons catalogue LICENSE).lib.rslists the cursors in its third-party-assets section. Close out licensing indebian/copyright: aFiles: themes/default/cursors/*paragraph records the upstream dual offer (CC-BY-SA-3.0 or LGPL-3, and CC-BY-SA-4.0 for the newer assets) attributed to the GNOME Project, with standalone CC-BY-SA-3.0, CC-BY-SA-4.0 and LGPL-3 paragraphs (summary-plus-canonical-URL for the CC licences, matching the existing CC-BY-4.0 entry; LGPL-3 referencing /usr/share/common-licenses/LGPL-3). The files are unmodified from upstream, so there is nothing to declare under the ShareAlike "indicate if changes were made" clause. Addtests/cursor_assets.rs: everyCursorShapename resolves to a valid XCursor file (Xcur magic, following symlinks),cursor.themeis present, no entry is a dangling symlink, and the expected-name list stays in sync with the enum's 34 variants.admin
2026-05-28 23:11:14 +02:00 -
3d8523533c
text-input: re-sync on
enterand flag secure fields as Password LTK drives the on-screen keyboard through zwp_text_input_v3: focusing a text widget enables text-input so the compositor's input-method (squeekboard) brings the keyboard up. Two gaps are fixed here. Handle theenterevent by re-emitting enable + content type + commit. The compositor sendsenterwhen it (re)focuses the text input — notably when an input-method connects after the field has already enabled text-input (a startup race). LTK previously ignoredenter, so that activation was lost and the keyboard never appeared; it now re-declares its state and the OSK comes up. Declare the content type from the field'ssecureflag: secure fields are flaggedContentPurpose::PasswordwithContentHint::SensitiveData | HiddenText(so the IME/OSK skips prediction, autocorrect and storing the value), everything else staysNormal/None. The content type is also refreshed when focus moves between two text fields (e.g. username → password) without re-creating the text-input object, and a newAppData::text_input_securelets theenterre-emit path preserve the current field's type. Correct the docs:TextEdit::secure()and SECURITY.md claimed secure "skips text-input-v3 registration". It does not — the field still activates text-input so the OSK works on it; the protection is the Password / sensitive flagging, and the value still reaches a trusted compositor/IME.admin
2026-05-27 22:31:36 +02:00 -
1e2cb836f4
ltk: convert physical sizes to logical for overlays and input regions on HiDPI Overlay
set_sizeand input regions were handed physical (layout-space) pixels straight to layer-shell andwl_region, both of which expect logical coordinates. They only coincide at scale 1, so on a scale-2 output every overlay requested a surface twice its intended size and the input region covered the wrong area.apply_input_regionnow takes the surface scale and divides eachRectdown to logical before adding it to the region; the four draw paths (software and gles, full and partial) forward their scale.reconcile_overlaysconvertsOverlaySpec::sizeto logical for bothset_sizeand the initialOverlayConfig(0 = fill survives the divide), and seeds the new surface'sscale_factorfrom the parent so the first configure allocates a HiDPI buffer instead of rendering at scale 1 untilscale_factor_changedlands a frame or two later.admin
2026-05-26 22:22:18 +02:00 -
fc045a9c22
ltk: ext-session-lock-v1 client surface mode, plus a read-only mode for text fields Add a third Wayland surface type to the runtime so an ltk
Appcan be a screen locker, alongside the existing xdg-shell window and wlr-layer-shell surfaces. A newShellMode::SessionLockmakesrun()bindext_session_lock_manager_v1and request the lock at startup; the lock surface itself is created in the newSessionLockHandler::lockedcallback (one surface on the first advertised output) and replaces theSurfaceKind::PendingLockplaceholder the main surface holds until the compositor grants the lock. Theconfigureevent routes through the sameon_configurepath as layer and xdg surfaces, so sizing and rendering are unchanged, andfinished(the compositor denied or ended the lock) tears the loop down. The whole thing is additive and opt-in: theWindowandLayerpaths are untouched and nothing enters lock mode unless anAppreturnsShellMode::SessionLock, so existing apps are unaffected — the only non-additive edits are the two exhaustivematches onSurfaceKind(wl_surface/try_wl_surface), which gain arms for the two new variants. Doing the locker as a first-class surface rather than compositing a static texture into an offscreenUiSurfaceis the whole point: the compositor gives the lock surface keyboard focus, so ltk's existing text-input, editing, focus and IME machinery works inside the lock exactly as on any other surface — cursor, click-to-focus, Tab, character input. A locker built on top of this is just a normal interactive ltk app that happens to be presented on the lock layer, with no special input plumbing on the compositor or the app side.App::requested_exit()is the new way an app asks the runtime to tear the surface down and leave the loop; it is polled after every batch ofupdates. It exists because of the one hard invariant ofext-session-lock-v1: a locker that disconnects without sendingunlockleaves the compositor's outputs blanked forever — that is the protocol's deliberate anti-bypass guarantee. So whenrequested_exit()returns true and the surface is a session lock, the loop callssession_lock.unlock()and round-trips the connection before settingexit_requested, lifting the lock cleanly; for aWindoworLayersurface there is no lock and it simply exits. The consequence for lock apps is that they must stop callingprocess::exitfrom the lock path and instead flip a flag they return fromrequested_exit().text_editgains aread_only( bool )builder. A read-only field still renders its box and value in the normal field style but takes no keyboard focus and accepts no input:Element::is_focusableandElement::is_text_inputnow return false for a read-onlyTextEdit, which keeps it out of the Tab cycle, off the keyboard-edit path, and stops the cursor from ever being drawn on it. The flag is carried throughmap_msgso it survivesElement::map. This is for presenting a known, non-editable value in the same visual idiom as the editable fields beside it — for example the already-known user shown on a session lock, where letting that field take focus or blink a cursor would be wrong. Theshell_mode()doc comment and the README now list theSessionLocksurface type and point atrequested_exit()for the unlock. Two warnings are cleared along the way: the runtime no longer stores theSessionLockStateafter requesting the lock — it has noDrop, so the manager object outlives the dropped handle inside the connection and the lock lifecycle runs entirely off the returnedSessionLock, which removes a never-read field — and a pre-existing rustdocprivate_intra_doc_linkswarning inlist_item(a public doc comment linking to the privatetheme::ICON_SIZE) is downgraded to plain code formatting.admin
2026-05-26 00:11:33 +02:00 -
cff4b12a4a
event_loop: drop the duplicate on_resize that clobbered physical dimensions with logical ones Both
LayerShellHandler::configureandWindowHandler::configurecalledself.on_configure( w, h )— which already forwardsapp.on_resize( w * sf, h * sf )in physical pixels — and then immediately calledself.app.on_resize( w, h )again with the surface-local logical size. The second call won, so on any scale > 1 surface the app saw logical dimensions while the layout pass kept working in physical pixels. On a Librem 5 at scale 2 that meantscreen_width/screen_heightcame through as 360×720 against a 720×1440 layout: homescreen icons rendered at half their intended footprint and the mobile wallpaper, sized explicitly via.size( iw_at_h, sh ), filled only the top half of the screen (the launcher / lockscreen / greeter use.cover()so they were unaffected). Remove the redundantapp.on_resizefrom both handlers;on_configureis the single source of truth for the physical dimensions the app and the layout both expect.admin
2026-05-25 11:42:45 +02:00 -
f7ef932976
list_item: optional leading icon
yamabush1
2026-05-25 09:00:00 +02:00 -
24f4d2703a
ltk: introduce viewport-relative
Lengthso any size, padding, spacing or font height can scale with the surface instead of being frozen at a px constant, fixtext::preferred_sizeto honour the font-declared line gap, and add a responsive typographic scale The motivating bug was a lockscreen in a downstream app (eydos-loginmanager) where the clock at 87 px overlapped the date at 24 px on a Pinephone but not on a winit dev screen. The root cause split in two: the layout was wired with a singlef32spacing constant that worked at the dev resolution and broke at the smaller one, andtext::Text::preferred_sizewas returningascent - descentfor the line height — fontdue's terminology for "the minimum bounding box of an unaccented line", which deliberately drops theline_gapthat every typographic renderer (Pango, CoreText, DirectWrite) reserves between adjacent rows. At Sora's 200/em line gap, an 87 px row was visually 17 px taller than the rect the column allocated for it; stacked tight against the row above, the descenders bled into the row below. This commit fixes both halves at the toolkit level so every consumer benefits without bolting on a per-screenSizinghelper in their own view code.types::Length(with theLengthBaseenum behind it) is the new currency for any "how big" or "how far apart" parameter. Six variants —Px,Vw,Vh,Vmin,Vmax,Em— cover the cases a real UI hits: absolute pixels for fixed-chrome decisions, viewport-relative percentages for sizes that have to survive a portrait/landscape rotation, and root-font-size multiples for typographic hierarchy. Optionalmin_px/max_pxbounds attach to the sameLengthvalue via.clamp( lo, hi )(both ends),.at_least( lo )and.at_most( hi )(one-sided); the names are intentionally divergent fromf32::min/f32::maxto avoid being read with the opposite semantics (x.min(24)in std means "the smaller of x and 24", which is the inverse of what a min bound expresses). The bounds are stored as rawf32rather than nestedLengthvalues, which keepsLengthCopyand avoids aBoxallocation per widget per frame — the bounded-by-relative case (Vmin(20).clamp(Vmin(10), Vmin(40))) is rare enough that the trade is the right one.From<f32>,From<i32>andFrom<u32>are implemented so every legacy.size( 24.0 )/.padding( 8.0 )/.spacing( 4.0 )call keeps compiling unchanged; the migration is opt-in per call site. TheEM_BASE_DEFAULT = 16.0constant matchestheme::typography::BODYsoLength::em( 2.0 )resolves consistently with the body-text default; a future change can thread a theme-supplied em base through without breaking the resolver shape. The resolver —Length::resolve( viewport: ( f32, f32 ), em_base: f32 ) -> f32— runs at layout time against a viewport supplied by the renderer.Canvas::viewport_logical()is the new helper that exposes that viewport: it divides the canvas's physical size bydpi_scaleand falls back to physical size whendpi_scale <= 0.0, guarding the misconfigured-canvas path so a Vmin call doesn't poison every downstream measurement withNaNorinf. The viewport is in **logical** pixels — matching what every waylandxdg_toplevel.configureevent already hands the client — soLength::vmin( 18.0 )on a 360×720-logical Librem 5 portrait surface resolves to 64.8 px and the same expression on a 1600×900 dev screen resolves to 162 px, automatically. Every widget setter that took anf32size, padding, spacing, max-width, or fixed dimension now takesimpl Into<Length>and stores the value asLength: -widget::text::Text::size( impl Into<Length> ); thesizefield is nowLength.Text::resolved_size( &Canvas )is the internal accessor that every measurement / drawing path routes through, so the field can stayLengthwithout churning the call sites.preferred_sizeanddrawnow readnew_line_size = ascent - descent + line_gapfrom fontdue'sLineMetrics(the fix for the original bug) — the baseline placement is unchanged, only the row height grows by the font's declared leading, which is what every stacked layout was implicitly relying on. -layout::Spacer::height( impl Into<Length> )/.width( impl Into<Length> );fixed_height/fixed_widthare nowOption<Length>. Newresolved_height( &Canvas )/resolved_width( &Canvas )helpers replace the directs.fixed_height.unwrap_or( 0.0 )reads inlayout::column,layout::rowandlayout::stack.Spacer::preferred_sizegrows a&Canvasparameter for the same reason;Element::preferred_sizepasses the canvas through. -layout::Column::spacing/.padding/.max_width,layout::Row::spacing/.padding— all takeimpl Into<Length>and storeLength. Internalresolved_spacing( &Canvas ),resolved_padding( &Canvas ),resolved_max_width( &Canvas )helpers funnel every read, so the layout code paths stay readable. The column'sinner_wprivate helper picks up a&Canvasargument; the test that used it directly is updated.theme::typographykeeps its historicf32constants (H0…BODY_XS, plusLINE_HEIGHT) so the migration is gradual, and adds a parallel responsive scale exposed as functions returningLength:h0(),h1(),h2(),h3(),body(),body_s(),body_xs(). Each is aLength::vmin( pct ).clamp( min_px, max_px )whose percentage is calibrated against a 1000-px smaller side reproducing the legacy px constant exactly, and whose px clamps protect both ends of the spectrum — a 360-px Pinephone hits the lower clamp on the larger headings, a 4K desktop hits the upper one. The tests intheme::typographyexercise all three regimes (narrow phone, calibration point, large display) so future drift in the percentages or clamps is caught immediately.Canvas::viewport_logicalis the only render-surface API touched. None of the existing per-frame paths (draw_text,measure_text,font_line_metrics) change shape, so backends and external embedders aren't disturbed. Thedpi_scaleaccessor already existed; this commit only adds the convenience that ratios it against the surface size to return the unit layout actually wants. Test coverage rounds out the addition rather than just smoke-testing the happy path: 22 new tests, broken down astypes::length_tests(7 — every variant, clamp with relative value, clamp with swapped bounds,From<f32>),render::viewport_tests(3 — scale 1, scale 2, scale 0 fallback),theme::typography::tests(3 — phone-clamped, calibrated, 4K-clamped),layout::spacer::tests(4 — px height, vmin height, vw width, flex spacer reportsNone),layout::column::tests(3 new — vmin spacing accumulates, vmin padding, vmin max-width caps inner-w),layout::row::tests(2 new — vmin padding, vmin spacing produces correct visible gap between non-flex children regardless of the row's centering anchor), andwidget::text::tests(3 updated/new — defaults compare againstLength::px(16.0),.size( f32 )and.size( Length )both verified). The existing integration test intests/layout_stack_spacer.rsis updated to callSpacer::preferred_size( &canvas )and comparefixed_height/fixed_widthagainstSome( Length::px( n ) ). Documentation is updated end-to-end so the new API is discoverable fromcargo docwithout grepping the source:lib.rsgets a new entry forLengthunder the **Types** section and a new **Designing for multiple resolutions** section that lists the three patterns (relativeLengthfor sizing, responsive typography for hierarchy,view()-level branching on surface dimensions only when the structure itself must change).Canvas::viewport_logicalships with a runnableassert_eq!example covering the scale-2 case. The module-level docstrings forSpacer,ColumnandRownow show both anf32example (legacy, still valid) and aLength::vmin( ... ).clamp( ... )example for the responsive variant —cargo docrenders both side by side so the upgrade path is obvious. Out of scope for this commit, deliberate:WrapGrid::spacing_x/spacing_y/padding,widget::text_edit::TextEdit::font_size, andwidget::image::Image::sizestill takef32. None of them are on a critical responsive path right now, theFrom<f32>shim means migrating later is a one-line setter signature change per widget, and keeping this commit focused on the widgets the lockscreen actually uses keeps the diff reviewable. The line-gap fix intext::preferred_sizealready benefitsTextEditindirectly because its caret/row math reads from the same metrics helpers.admin
2026-05-24 00:12:50 +02:00 -
c553c4df4b
themes/default: drop the inner shadows from the
launcher.svgglyph In boththemes/default/branding/dark/launcher.svgandthemes/default/branding/light/launcher.svgthe launcher glyph is built from nine cells, each with its ownfilterN_diiii_4479_*. Those filters layered four successive inner-shadow passes (effect2..effect5_innerShadow_*) on top of the outer drop shadoweffect1_dropShadow_*: pairs offeColorMatrix in="SourceAlpha"+feOffset+feGaussianBlur+feComposite operator="arithmetic"+feBlend, with offsets(-3.6,-3.6),(1.8,1.8),(0.45,0.45)and(1.8,1.8)and blend modesplus-lighter,overlayandnormalto reproduce the light-above / dark-below bevel and inner halo of the original Figma export. That is twenty-four lines per cell and nine cells per file — 216 lines per SVG, 432 in total. This change removes those four inner-shadow passes in both files; the outer drop shadow (effect1_dropShadow_*), theclipPathwithbgblur, therectelements defining each cell and the rest of the document are kept verbatim. The launcher silhouette and its placement do not change: the icon still occupies the sameviewBoxand produces the same clip; what disappears is the specular highlight and dark inner contour of each cell, leaving flat rectangles on the background with their projected shadow. The diff is pure deletions, with no added lines, and the existing difference between thedarkandlightvariants is preserved (only the filter identifiers differ,_38862versus_38700).admin
2026-05-23 00:53:13 +02:00 -
88385e14b2
add Carousel widget and WrapGrid::centre_last_row Forge's app switcher needs two layouts the existing widget set didn't cover. The desktop grid wants a partial last row centred under the rows above (3 tiles → row 1: two, row 2: one centred) so a 7-of-9 leftover band reads balanced rather than left-aligned. The mobile variant wants a horizontal carousel where the focused tile sits centred in the viewport at a configurable fraction of its width and its neighbours peek out on the sides at a fixed gap. Extend
WrapGridwithcentre_last_row( bool ). When set, layout offsets a row that has fewer thancolumnschildren by(missing * (cell_w + spacing)) / 2so it stays centred inside the content rect. Defaults to false; every existing call site continues to land tiles flush-left. Covered by three layout tests (centred partial row, full row no-op, off-by-default). Add theCarouselwidget atsrc/widget/carousel/. It is a pure layout primitive:focused_width_frac(0.05–1.0, clamped),gapandoffsetare owned by the caller, leaving drag / inertia / snap policy to the host so the compositor can plug in its existing touch pipeline. Each child gets a rect atbase_x + idx * (child_w + gap)and the full viewport height;snap_offset( viewport_w, idx )translates index to centring offset andfocused_index( viewport_w )rounds the current offset back to the nearest tile. Plumbed intoElement::Carouselwith the matching arms inwidget/element.rsand walker indraw/layout.rs; re-exported asltk::{ Carousel, carousel }. Covered by nine unit tests (layout, offset shift, snap / focus round-trip, frac clamp, child height) plus acargo run --example carouseldemo with Prev / Next / arrow-key navigation against an external offset state. The example is wired into theexamplesMakefile target. Updates the widget catalogue and thewidget/mod.rslanding comment to list the carousel under "Clipping wrappers" and to mentioncentre_last_rowin the grid section.admin
2026-05-22 19:38:48 +02:00 -
0e52274053
app, event_loop: first-frame-committed hook and foreign_toplevel app_id only Two independent changes, both blockers for a working desktop session through the loginmanager-daemon handoff and the dock's running-app icons.
App::on_first_frame_committedis a new trait hook fired exactly once, immediately after the very firstwl_surface.commitof a rendered buffer on the main surface.AppDatagrows afirst_frame_committed: bool,draw_framenow returns whether this call performed that first commit, andtry_runinvokes the hook after the borrows held during the draw are released. Used by the loginmanager-daemon-aware crustace path to signal "ready to be presented" back to the daemon as soon as the GPU has the first frame — the actual present can still be deferred under VT switching (no DRM master yet), but the client-side commit is the right edge for handoff.toplevel_display_idin the foreign-toplevel-list handler no longer falls back toinfo.titleorinfo.identifierwheninfo.app_idis empty. Smithay creates eachext-foreign-toplevel-list-v1handle withapp_id = ""andinit_new_instanceflushes adoneimmediately, so subscribers used to see the protocol-level identifier (a 32-charAlphanumericrandom token) as the "app id" of every new toplevel — and remained stuck on it whenever the client's realset_app_idarrived between done events but the subscribing app's matcher couldn't resolve it to a.desktopentry. Returning the rawapp_id(empty or not) makes that first transientdoneignorable by the consumer's own empty-string guard; the seconddone, carrying the real app id, is processed normally.admin
2026-05-21 01:51:19 +02:00 -
78a7ae151c
layout/stack: opt-in
Stack::fit_content()so a wrapping container can adopt the stack's intrinsic sizeStack::preferred_sizeunconditionally reported(max_width, max_h)— every stack claimed the full width its parent offered, regardless of what its children actually needed. That is the right default for a FrameLayout-style overlay (the existing callers all rely on it) but it makes the natural "pin a stack to a fixed-size child (e.g. a spacer ofcard_w × card_h) and centre other children on top of it" pattern impossible: a container wrapping such a stack always inherited the full parent width and ignored the spacer's footprint. The newStack::fit_content()builder mirrors theColumn::fit_contentflag — when set,preferred_sizereturns the max of children's intrinsic widths and heights instead of claiming the parent'smax_width, with the same "skip filler widgets that themselves claimmax_width" exclusion list (Spacerwith nofixed_width,Separator,Scroll,ProgressBar,Slider,TextEditwithoutfixed_width) so the flag is not defeated by a flexible child slipping into the stack. Default behaviour is unchanged.admin
2026-05-20 15:26:41 +02:00 -
757063694e
layout/stack: opt-in
Stack::fit_content()so a wrapping container can adopt the stack's intrinsic sizeStack::preferred_sizeunconditionally reported(max_width, max_h)— every stack claimed the full width its parent offered, regardless of what its children actually needed. That is the right default for a FrameLayout-style overlay (the existing callers all rely on it) but it makes the natural "pin a stack to a fixed-size child (e.g. a spacer ofcard_w × card_h) and centre other children on top of it" pattern impossible: a container wrapping such a stack always inherited the full parent width and ignored the spacer's footprint. The newStack::fit_content()builder mirrors theColumn::fit_contentflag — when set,preferred_sizereturns the max of children's intrinsic widths and heights instead of claiming the parent'smax_width, with the same "skip filler widgets that themselves claimmax_width" exclusion list (Spacerwith nofixed_width,Separator,Scroll,ProgressBar,Slider,TextEditwithoutfixed_width) so the flag is not defeated by a flexible child slipping into the stack. Default behaviour is unchanged.admin
2026-05-20 15:24:34 +02:00 -
640db23de2
doc: silence rustdoc intra-doc warnings (private-item links and palette/surface ambiguities)
cargo doc --no-depswas emitting eight warnings about intra-doc links resolving to private items or ambiguous fn/module names. None reflect a behaviour bug; they were just noise that cluttered the docs build output. Five of the warnings came from doc comments that cross-referenced items not in the rendered public API.widget::scroll::Scroll(struct),Scroll::horizontal,Scroll::both,event_loop::text_editing(module) andtext_shaping(module) are allpubin their own modules, but thewidgetandevent_loopparents are private to the crate root, so rustdoc treats them as private when resolving links from items that ARE on the public surface (ScrollAxis, thescrollconstructor,font_bytes, thetext_editmodule docs). The fix is to drop the link syntax for those references: keep the identifier in backticks (so it still renders as code) but remove the surrounding[...]so rustdoc doesn't try to resolve it. Where the cross-reference had no semantic load beyond "see this module", the prose is rephrased to name the module without trying to link to it (e.g. "theevent_loop::text_editingprivate module", "see thetext_shapingprivate module"). The remaining three warnings werepalette/surfacelinking ambiguously between a module of that name and a function of the same name withincrate::theme. Adding()after the identifier inside the brackets (palette→palette(),surface→surface()) disambiguates to the function, which is the intent in all three sites — the surrounding text talks about "per-slot shorthand accessors", which is what the functions are. After thiscargo doc --no-depsruns clean with no warnings.admin
2026-05-19 22:41:42 +02:00 -
a8bbd1e35c
input/keyboard: fall through to App::on_key when the Enter dispatch target widget has no submit/press message
handle_key_returnpreviously routedReturnto either the focused or hovered widget's submit/press message and, only when neither index existed, fell through toapp.on_key_with_modifiers. If a stalehovered_idx(left over from a prior screen) pointed at a widget that exists in the newwidget_rectsbut exposes no submit or press handler,target.is_some()was true and the message-less dispatch silently swallowed the key — theelsebranch never ran andApp::on_keynever saw the keysym. This manifested in the eydos-loginmanager greeter asEnteron the Lock screen failing to fireMessage::Unlockafter a pause/resume cycle, until the user clicked something to refreshhovered_idx. Track whether the widget path actually pushed a message and, when it didn't, fall through to the app-level handler so theReturnkeysym still gets a chance to be interpreted by the application'son_key. No behaviour change when the focused/hovered widget does provide asubmit_msgorpress_msg.admin
2026-05-19 21:40:20 +02:00 -
9cc65e70ea
fix(event_loop): walk error source chain for BrokenPipe in OtherError
yamabush1
2026-05-18 20:48:35 +02:00 -
750eae7a93
fix(event_loop): exit cleanly on OtherError(BrokenPipe) from compositor
yamabush1
2026-05-17 18:28:50 +02:00 -
4a80165428
event_loop, a11y, text_shaping: AccessKit AT-SPI2 bridge, cross-app clipboard, xdg-activation, HarfBuzz shaping, multi-touch hooks Five orthogonal capabilities land together because they share the same
try_runplumbing: an optional global is bound at startup, a piece of state is added toAppData, the run-loop iteration drains an inbox / pushes a frame snapshot, and the public surface gains a small set of opt-inApphooks. Nothing here breaks an existing app — every new path degrades to a no-op when the compositor does not advertise the relevant global or when the platform adapter cannot start. AT-SPI2 accessibility via AccessKit. A newsrc/a11y/module owns the platform adapter and the inboundActionRequestchannel.A11yState::try_newconstructs anaccesskit_unix::Adapter; when the AT-SPI2 daemon is not on the session bus (headless CI, locked-down compositors) the constructor returnsNoneand the rest of the pipeline runs unchanged. After every successfuldraw_frame, the run loop builds a freshaccesskit::TreeUpdatefromwidget_rectsand pushes it through the adapter — main surface plus every visible overlay, each translated to global coordinates viasurface_offset_forso screen readers report positions in the same frame the user sees. Buttons / toggles / checkboxes / radios / list items / sliders / text edits map to the matchingRoles;ClickandFocusactions are advertised on every interactive node; inbound action requests are drained at the top of each iteration and translated into a synthetic press / focus on the matching widget. The integration is documented as best-effort indocs/architecture.mdunder "Known gaps and non-goals": hierarchical nesting, per-widget accessible names, live regions andAction::SetValueare listed as the natural follow-ups that the foundation now supports but does not yet wire. Cross-application clipboard viawl_data_device_manager. A newsrc/event_loop/data_device.rsbridges the existing process-localclipboard: Stringto the Wayland selection. Outbound (Ctrl+C / Cut): after the local clipboard is populated,publish_clipboard_selectioncreates aCopyPasteSourceofferingtext/plain;charset=utf-8and installs it as the seat's selection;DataSourceHandler::sendwrites the cached string into the fd the peer hands us. Inbound (Ctrl+V from another app):DataDeviceHandler::selectionasks for the offered text viaWlDataOffer::receive, spawns a tiny worker thread to drain the read pipe with a 16 MiB cap to prevent paste-bomb DoS, and posts the result back through anmpsc::Senderthat the run loop drains each iteration intodata.clipboard. Theclipboard:field's doc-comment is updated to reflect the new behaviour: process-local when the compositor does not advertise the global, synchronised with the seat selection otherwise. External drag-and-drop reception. The samedata_devicemodule handlesDragOfferenter / motion / leave / drop_performed:on_drop_motion( x, y )fires while the drag hovers over the surface,on_drop_leave()when it withdraws without dropping, andon_drop_received( x, y, mime, text )when an external payload (text/uri-list,text/plain, …) is released on top of an ltk window. The receive path reuses the same worker-thread / channel pattern as the clipboard so the run loop never blocks on the read fd. Three newApphooks expose the events with no-op defaults; apps that ignore them get the previous behaviour.xdg-activation-v1. The global is bound optionally; when it is present,try_runreads$XDG_ACTIVATION_TOKENfrom the environment, removes it immediately (single-use; preventing leaks into child processes) and stashes it onAppData::activation_token_pending. After the first successful configure of the main surface — the earliest point at whichxdg_activation_v1.activateis meaningful — the token is consumed once and the surface raised to focus. Compositors without the global leaveactivation_stateasNoneand the inbound path silently degrades. AnApp::request_activation_tokenoutbound path is reserved on the trait but not yet exercised here. HarfBuzz shaping. A newsrc/text_shaping.rs::shape_linedrives both renderers: the logical-order string is run throughunicode-bidi, split into per-font sub-runs, and shaped throughrustybuzz. EachPositionedGlyphcarries the per-fontglyph_id, the visual advance and the ink offsets — exactly whatfontdue::Font::rasterize_indexedneeds to render Arabic connected forms, Devanagari clusters and CJK shaped glyphs correctly. The GLES atlas is re-keyed on(glyph_id, size_bits, font_id)so glyphs from different fonts at the same size no longer collide, and the atlas format is selected per ES profile (GL_R8/GL_REDon ES3,GL_LUMINANCEon ES2) — the fragment shader samples.rfor both, sinceGL_LUMINANCEreplicates the coverage byte into.r=.g=.b. Software path follows the same key. NewCargo.tomldeps:unicode-bidi = "0.3",rustybuzz = "0.14". Multi-touch hooks.App::on_touch_down / on_touch_move / on_touch_up( id, x, y )expose the rawwl_touch.idof every secondary finger. The first finger to land remains the *primary slot* and is fed through the regular gesture machine (on_pointer_*, swipe, scroll, long-press, drag-and-drop). Every additional finger fires the new callbacks instead, leaving the existing single-slot behaviour untouched for apps that do not override them. This is the substrate for app-defined pinch-zoom / two-finger pan; the toolkit itself does not yet ship a built-in pinch gesture (called out in the same "Known gaps" doc section).event_loop::frameextracted fromdraw/mod.rs. Thedraw_frameorchestrator and its per-format SHM helper (pick_shm_format) move intosrc/event_loop/frame.rs, leavingdraw/strictly responsible for per-surface paint primitives. The import inevent_loop/run.rsis rewritten accordingly;draw/mod.rsshrinks from 192-line orchestrator to a thin module index. Overlay teardown safety.AppData::discard_overlay( id )synchronously removes a destroyed overlay from the map and rewrites every per-device focus that pointed at it (pointer, keyboard, every touch slot), migrating an in-flight long-press drag to the main surface the same wayreconcile_overlaysdoes. Used by the compositor-driven destruction paths (PopupHandler::done,LayerShellHandler::closed) where waiting for the next reconcile would leave a window in whichsurface()/surface_mut()panic. The non-panicking siblingstry_surface/try_surface_mutare added for callers on async dispatch paths (IMEDone, tooltip arm) that may race a teardown. Miscellaneous. CI:master→mainto match the actual default branch.Makefileaddscargo run --example dialogto the examples target.src/lib.rsre-exportswidget::scroll::ScrollAxisso apps can configure ascroll()axis without reaching into apub(crate)module.Cargo.tomladdsaccesskit = "0.17"andaccesskit_unix = "0.13".docs/architecture.mdgains the "Known gaps and non-goals" section that enumerates the new capabilities, what still ships flat, and what is deferred (per-widget a11y labels, primary selection, intra-process multi-touch gestures,wp_fractional_scale_v1).admin
2026-05-16 22:09:59 +02:00 -
4aa3480b64
refactor: split every monolithic module into focused submodules Each source file that had grown beyond a single concern is replaced by an identically-named directory containing focused submodules.
src/event_loop/mod.rs(878 lines) becomes a directory with clipboard, context_menu, cursor_shape, drag, focus, handlers, invalidation, overlays_reconcile, repeat, run, surface, text_editing, and tooltip. Every widget, input handler, and theme component follows the same split. Public interfaces are unchanged — only the internal file layout moves. image bumped from 0.25.2 to 0.25.9.admin
2026-05-15 23:46:56 +02:00 -
3d237039c6
input/keyboard: let the app intercept arrow / Tab keys before the default text-edit and focus-shift behaviours The keysym dispatcher used to give the focused widget first refusal on Left / Right / Up / Down / Tab. A
text_editswallowed the four arrows for cursor movement, and Tab walked the keyboard-focus ring throughnext_focusable_index. Apps only goton_key_with_modifiersfor those keys when no text input was focused (Tab never), so a search field with autocomplete couldn't drive a selection cursor through arrows or Tab without putting focus on something else first — which is exactly the wrong UX for a search-as-you-type list. Three arms inAppData::handle_key_pressnow queryself.app.on_key_with_modifiers( keysym, ctrl, shift )*first* and only fall back to the default behaviour if the app returnsNone: -Left | Right: previously branched onis_text_inputand routed the keypress tohandle_cursor_left/rightfor text fields, otherwise asked the app. Now: app first; if app saysNone, the existing text-cursor path runs for text inputs and non-text widgets get nothing (their previous fallback already produced no useful action without an app handler). -Up | Down: the previous logic was a four-way decision that tried text-cursor movement, thenmove_keyboard_hoverfor combo / list widgets, then the app handler. Now the app gets first refusal too. When it declines, the original cascade (text-cursor → hover navigation) still runs, so multilinetext_editcursor walking and combo / scrollable-list keyboard nav are unchanged for any app that doesn't intercept these keys. -Tab | ISO_Left_Tab: app first; onNonethe focus-shift path (next_focusable_index+set_focus) runs. Apps that want Tab as a navigation message between custom UI states (a search field cycling through results, a wizard advancing pages) finally have a hook; apps that don't get the standard tab-through-focusable behaviour by leavingon_key_with_modifiersreturningNonefor Tab, which is the trait's default. The interception is keysym-only — modifier state is forwarded so an app can distinguishTabfromShift+Tab,RightfromCtrl+Right. The text-input check is preserved as a local before the dispatch so the fallback doesn't lose the "is this even a text edit?" question; it just runs *after* the app instead of before. Net effect: a list / autocomplete that needs arrow / Tab navigation can now be implemented purely by overridingApp::on_key_with_modifiers, with no changes totext_edititself and no risk of breaking apps that don't need it. The Up / Down branch's old comment about the "fall through to list hover-navigation" rationale is dropped — the cascade still exists, but the app-first ordering is the new contract and the comment was about the previous one.admin
2026-05-15 00:40:01 +02:00 -
5ff4fa7e59
Fixed default dark theme
admin
2026-05-14 23:49:08 +02:00 -
bfe27b6fef
event_loop, widget, input: pointer-dwell tooltips, global drag coords, foreign-toplevel name cascade
Button::tooltip( text )registers a hint string that fires after a 600 ms pointer dwell.LaidOutWidgetgains atooltip: Option<String>field,Element::tooltip()exposes it to the input layer, and the existing pointer-hover path now callsarm_tooltipon hover-enter andcancel_tooltipon hover-leave / touch. The deadline is polled alongsidenext_long_press_wakeupintry_runso an idle pointer still gets a wake-up at the firing instant; on fire,tooltip_overlay()synthesises anOverlaySpec— a roundedtext_primary @ 95%pill drawn withbgtext — anchored above the hovered widget, flipping below or clamping inside the screen if it would clip, and pushed alongside the app's own overlays both in the redraw path and inreconcile_overlaysso the layer surface is created the same frame the tooltip becomes visible. Pointer-only by design: touch events explicitly cancel because a tap-and-release should never linger into a hint. Theshowcaseexample wires.tooltip(..)on the three button variants as a smoke test. The drag pipeline now reports positions in main-surface (global) coordinates instead of per-surface.surface_offset_for( focus )derives the top-left of an overlay surface fromSurfaceState::layer_anchor— newly stored atreconcile_overlaystime fromOverlaySpec::anchor— combined with the main surface's dimensions;on_drag_move,on_drop, the synthetic move emitted on drag-promotion inpointer.rs, and thepending_drag_initspush site ingesture::start_dragall translate before handing coordinates to the app. The motion and release paths additionallyrequest_redraw()every overlay so a dock-style drop target painted on anAnchor::Bottomlayer surface gets repainted as the drag moves — without that, the visible drop indicator only updates when the cursor re-enters the main surface. Drops still target whichever surface fired the release; only the coordinates are unified.ForeignToplevelListHandlerpreviously readapp_iddirectly viaForeignToplevelList::info(). Clients that never setapp_id(some winit-windowed compositors, simple test clients) were silently invisible to crustace-style docks because the empty string fell throughunwrap_or_default()and the dock then keyed entries off"".toplevel_display_id()cascades: preferapp_idfor desktop-entry matching, fall back totitlefor human-readable identification, and finally to the protocol-issuedidentifierwhich is always present and unique per handle. Applied to bothnew_toplevelandupdate_toplevel.theme::system_fontdb()lazily loads the system font database once viaOnceLockand reuses theArcfor everydecode_svg_bytescall. resvg's defaultOptions::fontdbis empty, so any SVG containing<text>rendered with the built-in fallback font or no font at all; with the system DB attached, icons and decorative SVGs with embedded labels now resolve glyphs correctly. Cached becauseload_system_fonts()walks every font path on the system and is comfortably tens of milliseconds on a cold cache — not something to repeat per icon decode.themes/default/theme.jsontweaks one variant's slot palette:surface-altfrom@indigo/D9to@white/D9andtext-primaryfrom@whiteto@navy, plus a cosmetic re-alignment of the"value"columns in the same slots block.admin
2026-05-14 22:36:17 +02:00 -
821037f509
container, text, date_picker: width-aware sizing pass
Container::max_width(px)mirrors the same flag onColumn/Row— the container reportsmin( offered, px )upwards and the draw pass capsrect.widthtopx, so a decorated child wrapped incontainer().max_width(260)no longer needs acolumn()-of-one shell just to access the cap. Propagated throughmap_msg, covered by a test, and documented under thecontainersection ofdocs/widgets.md.Text::no_truncate()opts out of the default ellipsis behaviour: whentruncate = falsethe draw pass paints the full string even ifmeasure(text) > rect.width. Useful for very short labels (calendar days "1"–"31", day-of-week stubs "Lu" / "Mi" / "Sá") inside grid slots whose width is dictated by the parent — a couple of pixels of overflow centred in the rect is invisible, while "..." in place of a single-digit number is loud.DatePicker::width(px)is a layout hint: when set,build()derivesheader_fs,dow_fsandday_fsfrom the slot width so the worst-case label in each row ("September 2026" in the header, "Mié" / "Sáb" in the DOW row, "30" in the day cell) fits at a sensible size; the design defaults stay as upper bounds. Day and DOW cells also flip onText::no_truncate()as a safety net — heuristic font sizing can't perfectly predict glyph widths across families, and overflowing one pixel beats truncating to "...".admin
2026-05-13 19:38:41 +02:00 -
96f437544a
event_loop: route
take_focus_requestto widgets on overlay surfaces Thetake_focus_requestblock intry_runonly searcheddata.main.widget_rectsfor the requestedWidgetId, so an app that wanted to focus a widget living on an overlay surface (a search field on a launcher overlay, a text edit on a dialog modal, a password field on a popup) silently no-op'd: the widget existed and had been laid out, but the lookup never found it because it was scanning the wrongwidget_rects. Crustace hit this trying to put cursor focus on the launcher search field when the launcher slides up — the field rendered with a visible caret but typing went to whoever the keyboard had been focused on before. The lookup now falls through todata.overlays.iter()if the main scan misses, returning the first(SurfaceFocus::Overlay(id), flat_idx)whose surface carries a widget with that id.data.set_focusalready accepts theSurfaceFocusdiscriminant, so the call site just forwards what the lookup found and requests a redraw on the matching surface instead of unconditionally on main. No effect on apps that target main-surface widgets — the main scan still runs first and short-circuits the overlay walk.admin
2026-05-13 13:47:01 +02:00 -
c3839060cc
theme, event_loop: window_controls.bar_bg slot + graceful exit on compositor disconnect
WindowControlsSpecgainsbar_bg, the background fill of the SSD title-bar strip the close / maximize / minimize controls sit on. Forge used to paint that strip withpalette.surfaceclamped to alpha 1.0 — a hack on a translucent panel token. With a dedicated slot the theme decides directly:@off-whitein light and a new@window-bar-darkshade in dark. Schema gets the matchingOption<String>field (defaulting topalette.surfaceto keep existing themes rendering the same) and the fallbackWindowControlsSpecseedsColor::WHITEso a missing theme still draws something readable.try_run's dispatch loop previously.expect("dispatch")-ed every calloop error. When the compositor closes the wayland socket —wl_display.error, forge crashing, the user logging out — the loop saw aBrokenPipe/ConnectionResetand panicked, which polluted the user's stderr with a backtrace for what is just "the session ended". The match now treats those twoIoErrorkinds as a clean shutdown: it printsConnection::protocol_error()(the typedwl_display.errorif the server sent one), setsexit_requested = true, and lets the loop drain out normally. Any other dispatch error keeps panicking — those are still genuine bugs.admin
2026-05-13 10:17:36 +02:00 -
dc781fb78d
event_loop: client binding for ext-foreign-toplevel-list-v1 Adds an
Appcallback that delivers the live list of open toplevels from the compositor — the data source a shell needs for dock running-app indicators, taskbar tiles, alt-tab and any other "what is currently running" UI. Hand-wiring the protocol binding from every shell that wants it is the kind of boilerplate ltk should absorb once: this is that move.Cargo.tomladds"staging"towayland-protocols' feature list. SCTK 0.20 already pulls staging in transitively (it carriesforeign_toplevel_list.rsand its own dispatch helper), so this is belt-and-braces against a future ltk tree that swaps SCTK for a different client toolkit — it keeps the protocol available crate-wide even then.src/app.rsintroducesToplevelEvent { Opened { id: u32, app_id: String }, Closed { id: u32 } }andApp::on_toplevel_event( &self, ToplevelEvent ) -> Option<Self::Message>with the default returningNoneso apps that do not care pay nothing (no allocation, no dispatch).idis the Wayland protocol id of the handle proxy — unique per session, stable for the handle's lifetime, the same value paired acrossOpenedandClosed.src/lib.rsre-exportsToplevelEventfrom the public prelude.src/event_loop/app_data.rsgrows apub foreign_toplevel_list: ForeignToplevelListfield.src/event_loop/mod.rsconstructs it viaForeignToplevelList::new( &globals, &qh )and stores it onAppData. If the compositor does not advertise the global the innerGlobalProxyjust resolves to "absent" and the list yields no toplevels — no error path needed at construction.src/event_loop/handlers.rsadds theProxyimport,delegate_foreign_toplevel_list!( @<A: App> AppData<A> )next to the rest, and implementsForeignToplevelListHandlerforAppData<A>. The three SCTK callbacks (new_toplevel,update_toplevel,toplevel_closed) each pull the handle's protocol id and its currently-cachedapp_idfrom the list's info cache, callself.app.on_toplevel_event( … ), and push the returned message ontopending_msgsso it flows through the normalupdatecycle with the regularinvalidate_afterscoping path.update_toplevelre-emitsOpenedwith the latest info — compositors fire this on title changes too, not justapp_idchanges, but apps whose state is keyed on(id, app_id)can absorb the repeat idempotently and apps that need title-change granularity can scope viainvalidate_after. The wire-up is generic: a shell that wants finer behaviour (focus follow, per-title indicators, multi-window grouping) can layer on top by translating the event into more specific app messages. The default app pays zero, the shell that opts in gets a real event stream without touchingsmithay-client-toolkitdirectly.admin
2026-05-11 22:30:29 +02:00 -
39fbafec24
container: generalise
backgroundfromColortoPaintContainer::backgroundnow storesOption<Paint>instead ofOption<Color>, and the builder acceptsimpl Into<Paint>so callers can pass a plainColor(auto-wrapped inPaint::Solidvia the trait impl) or an explicitLinearGradient/RadialGradient.layout_and_drawswitches fromcanvas.fill_rect( rect, bg, corners )tocanvas.fill_paint_rect( rect, &bg, corners )to consume the wider type. No behaviour change for existing call sites — solid-colour containers keep working unchanged thanks to theInto<Paint>forColor.admin
2026-05-11 12:20:28 +02:00 -
703a1ed228
event_loop: honour
App::window_size_hint()on the first configure Previously onlyset_min_size()was called with the requested size, on the assumption that the compositor would adopt it as the initial dimension. In practice that doesn't hold: xdg-shell has no "preferred initial size" primitive and compositors are free to pick any size within[min, max]on the first configure. The window ended up opening at the 800x600 fallback instead of the size the application had asked for. The fix pinsmin == maxbefore the first commit, which forces the compositor to honour the requested size in its first configure, and then releasesmax_sizefrom the configure handler via thepending_size_hint_unpinlatch so the surface remains user-resizable afterwards. The 800x600 fallback now only applies when the application does not provide a hint.admin
2026-05-10 23:16:17 +02:00 -
f3621b72c9
Added examples in README.md
admin
2026-05-10 15:23:22 +02:00 -
bbab5e238d
First commit. Version 0.1.0
admin
2026-05-10 09:58:23 +02:00 -
af105b7f7d
Initial commit
admin
2026-04-23 11:08:43 +02:00