diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 483e7bc9..caa54a33 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,6 +20,7 @@ on: - 'clients/**' - 't/**' - 'scripts/**' + - 'docs/**' - '.github/workflows/**' jobs: @@ -121,7 +122,7 @@ jobs: xbps-install -Syu xbps-install -y git meson gcc clang pkg-config scdoc \ cairo-devel glib-devel libpng-devel librsvg-devel libxml2-devel \ - pango-devel wlroots0.19-devel gdb bash xorg-server-xwayland \ + pango-devel wlroots0.20-devel gdb bash xorg-server-xwayland \ dejavu-fonts-ttf libsfdo-devel foot hwids # These builds are executed on all runners diff --git a/NEWS.md b/NEWS.md index d721ed3b..ad71a1df 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,8 @@ The format is based on [Keep a Changelog] | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| -| 2026-03-31 | [unreleased] | 0.20.0 | 27402 | +| 2026-05-25 | [0.20.0] | 0.20.1 | 28313 | +| 2026-04-17 | [0.9.7] | 0.19.2 | 29277 | | 2026-03-15 | [0.9.6] | 0.19.2 | 29271 | | 2026-03-04 | [0.9.5] | 0.19.2 | 29251 | | 2026-02-27 | [0.9.4] | 0.19.2 | 29225 | @@ -44,6 +45,8 @@ The format is based on [Keep a Changelog] | 2021-03-05 | [0.1.0] | 0.12.0 | 4627 | [unreleased]: NEWS.md#unreleased +[0.20.0]: NEWS.md#0200---2026-05-25 +[0.9.7]: NEWS.md#097---2026-04-17 [0.9.6]: NEWS.md#096---2026-03-15 [0.9.5]: NEWS.md#095---2026-03-04 [0.9.4]: NEWS.md#094---2026-02-27 @@ -77,6 +80,109 @@ The format is based on [Keep a Changelog] [0.2.0]: NEWS.md#020---2021-04-15 [0.1.0]: NEWS.md#010---2021-03-05 +## unreleased + +[unreleased-commits] + +## 0.20.0 - 2026-05-25 + +[0.20.0-commits] + +This is the first release using wlroots-0.20 and therefore has an increased risk +of teething issues. Many thanks to @Consolatis for leading the effort to port +across [#2956]. + +In terms of new features, the most noteworthy ones include: (i) the frequently +requested show-desktop action; (ii) initial toplevel capture support to +screenshot specific windows; (iii) menu accelerators/shortcuts; and (iv) HDR10 +support when running with the Vulkan renderer option. + +The eagle-eyed amongst you will have noticed the sudden jump from labwc `0.9.7` +to `0.20.0`. The reason for this is to align the minor number to that of the +wlroots version against which the compositor is linked. + +The 0.9.x series has turned into a maintenance branch named v0.9 with bug fixes +only, for anyone preferring to build with wlroots-0.19. + +Note to maintainers: + +- libinput >=1.26 is required in support of tablet tool pressure range + configuration. +- wlroots >=0.20.1 is required to avoid some bugs that we do not want labwc to + ship with. For details, see: + https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5325 + +### Added + +- Partially support toplevel-capture. Note that the following are not yet + implemented: (i) XWayland child windows; (ii) XWayland unmanaged windows + (e.g. popups); (iii) xdg child window positioning; (iv) xdg subsurfaces; + and (v) xdg popup positioning. [#2968] @Consolatis +- Add command line option -t|--title to set the labwc window title when running + nested [#3577] @mdsib +- Add support for HDR10 output @kode54 @Consolatis [#3424] +- Include wlroots version in --version string @Consolatis [#3567] [#3581] +- Implement menu accelerators (one-letter mnemonics to quickly select/exec + items from the current menu) @ch3rn1ka [#3505] +- Add Next/PreviousWindowImmediate actions @elviosak @johanmalm [#3547] +- Add labnag options `--details-border-color` and `--details-margin` + @st0rm-shad0w [#3527] +- Add config option `` to defer raise-on-focus by a + small amount when `raiseOnFocus` is enabled @joske [#3513] +- Install `labwc-session.target` systemd user unit when the systemd dependency + is available @joske [#3534] +- Add `onbutton` to config option ``. Also add + associated option ``. @diniamo [#3540] +- Add `overrideInhibition` option to `` [#3507] @drougas +- Add action `ToggleShowDesktop` to hide/unhide windows, and default keybind + `Super-d` to trigger this action [#3500] [#3595] @johanmalm +- Add `` config option so that privileged protocols can be + restricted [#3493] @xi +- Add action `DebugToggleKeyStateIndicator` to show a key-state on-screen + display (OSD) for debugging. [#3499] @johanmalm @tokyo4j +- Add support for `color-management-v1` and `color-representation-manager-v1` + protocols [#3469] @ManuLinares +- Add configuration option `` + to enable tablet tool pressure range libinput settings [#2916] @jp7677 +- Add `wl_fixes` interface [#2956] @kode54 + +### Fixed + +- Enable labnag long option --exclusive-zone [#3576] @st0rm-shad0w +- Position chromium popup correctly when a window is maximized on a multi- + output setup @elviosak [#3547] +- Run session activation environment update synchronously to avoid a race + condition with the autostart script [#3543] @joske +- Allow interactive resize on fully maximized windows so that a resize + initiated by modifier plus right-mouse-button-drag is not ignored [#3525] + @bjorn +- Gracefully handle missing XWayland packages, so that a labwc compositor which + has been built with XWayland support (which is optional) can be run even if + XWayland is not installed. [#3401] @quite +- Rework how XWayland window initial geometry is set to ensure that the natural + geometry does not exceed the usable output area when handling initial + maximize/fullscreen requests. [#3439] @jlindgren90. +- For XWayland windows, sync always-on-top state back to X.Org Server. This + makes `wmctrl -b toggle,above` work. [#3446] @jlindgren90 +- Fix missing title and icon with XWayland client override-redirect toggle. + There are no known issues with clients, so this is purely for preventative + purposes. [#3450] @jlindgren90 +- Update titlebar title when set to empty and fix an associated issue causing + the title to be misplaced outside of the titlebar when the window is resized. + [#3443] @tokyo4j +- When running nested, exit compositor when last output is destroyed because + in this situation, each output corresponds to a window in the parent + compositor and, unlike DRM outputs, these cannot be reconnected after being + destroyed. [#3440] @marler8997 +- Allow policy-based placement to apply when an initially-maximized/fullscreen + view is restored to floating geometry. [#3387] [#3502] @jlindgren90 + +### Changed + +- Change the default keybinds for XF86Audio{LowerVolume,RaiseVolume,Mute} to use + pactl instead of amixer [#3484] @danielrrrr +- Drop cosmic-workspace protocol [#3031] @tokyo4j + ## Notes on wlroots-0.19 There are some regression warnings worth noting for the switch to wlroots 0.19: @@ -111,52 +217,35 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: [wlroots-5098]:https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5098 [gtk-8792]: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8792 -## unreleased +## 0.9.7 - 2026-04-17 -[unreleased-commits] +[0.9.7-commits] -The codebase has been ported to wlroots 0.20 [#2956] @Consolatis +This is a small bug fix release. -### Added +``` + 0.9.6--------0.9.7 <--- v0.9 branch with bug-fixes only + / + / +0.9.4--------0.9.5-------- <-- master (built with wlroots-0.20) +``` -- Add configuration option `` - to enable tablet tool pressure range libinput settings [#2916] @jp7677 -- Add `wl_fixes` interface [#2956] @kode54 +## Fixed -### Fixed - -- Gracefully handle missing XWayland packages, so that a labwc compositor which - has been built with XWayland support (which is optional) can be run even if - XWayland is not installed. [#3401] @quite -- Rework how XWayland window initial geometry is set to ensure that the natural - geometry does not exceed the usable output area when handling initial - maximize/fullscreen requests. [#3439] @jlindgren90. -- For XWayland windows, sync always-on-top state back to X.Org Server. This - makes `wmctrl -b toggle,above` work. [#3446] @jlindgren90 -- Fix missing title and icon with XWayland client override-redirect toggle. - There are no known issues with clients, so this is purely for preventative - purposes. [#3450] @jlindgren90 -- Update titlebar title when set to empty and fix an associated issue causing - the title to be misplaced outside of the titlebar when the window is resized. - [#3443] @tokyo4j -- When running nested, exit compositor when last output is destroyed because - in this situation, each output corresponds to a window in the parent - compositor and, unlike DRM outputs, these cannot be reconnected after being - destroyed. [#3440] @marler8997 -- Allow policy-based placement to apply when an initially-maximized/fullscreen - view is restored to floating geometry. [#3387] @jlindgren90 - -### Changed - -- Drop cosmic-workspace protocol [#3031] @tokyo4j +- Fix intermittent failed-to-get-texture issue with some clients (e.g. foot) + when using the window-switcher in the thumbnail mode. [#3511] @yuiiio +- Fix tablet tool tilt motion. [#3494] @jp7677 +- Handle window-switcher buffer allocation failure when in 'thumbnail' mode. + This is believed to be very unlikely to happen, but has been reported by one + user and is believed to be GPU driver related. [#3490] @Consolatis ## 0.9.6 - 2026-03-15 [0.9.6-commits] This is an earlier-than-usual release containing bug fixes only. It has been -done on a separate branch (0.9.5-maintenance) to avoid the inclusion of -refactoring and new features. +done on a separate branch (v0.9) to avoid the inclusion of refactoring and new +features. ``` 0.9.6 <--- bug-fixes only @@ -213,9 +302,9 @@ Note to package maintainers: This release requires wayland version >=1.22.90 - Add theme option window.button.hover.bg.color [#3365] @johanmalm - Implement scrollable window-switcher OSD [#3291] @tokyo4j - Support the `NextWindow` options listed below [#3271] @tokyo4j - - `` - - `` - - `` + - `` + - `` + - `` - Add config option `*` for setting the active workspace on startup. [#3265] @5trixs0f @@ -263,7 +352,7 @@ Note to package maintainers: This release requires wayland version >=1.22.90 and get keyboard focus so that they can be operated with the keyboard. An example use-case is the xfce4-panel applications-menu being opened by the command xfce4-popup-applicationsmenu. [#3165] @johanmalm - - On popup destory, return focus to whoever had it before the popop [#3165] + - On popup destroy, return focus to whoever had it before the popop [#3165] @johanmalm @tokyo4j - Unshade window if selected from client-list-combined-menu [#3345] @Amodio - Show non-dialog child windows in window-switcher [#3339] @tokyo4j @@ -297,7 +386,7 @@ A big thank you to all involved in this release. ### Added -- Add `` to optionally order windows by age +- Add `` to optionally order windows by age rather than most recent focus. @mbroemme [#3229] - Replace `` with `` to provide more granular control when configuring the size of snapping areas @@ -328,7 +417,7 @@ A big thank you to all involved in this release. [#3134] - labnag: add --keyboard-focus option @tokyo4j [#3120] - Allow window switcher to temporarily unshade windows using config option - `` @Amodio @Consolatis [#3124] + `` @Amodio @Consolatis [#3124] - For the 'classic' style window-switcher, add the following theme options: - `osd.window-switcher.style-classic.item.active.border.color` - `osd.window-switcher.style-classic.item.active.bg.color` @@ -358,7 +447,7 @@ A big thank you to all involved in this release. client surface. Fixes a regression in 885919f. @tokyo4j [#3211] - Set all foreign-toplevel initial states correctly. This is not believed to fix any particular user-issue, but just feels safer. @jlindgren90 [#3217] -- Update layer-shell client top layer visiblity on unmap instead of destroy +- Update layer-shell client top layer visibility on unmap instead of destroy because it is possible for fullscreen xwayland windows to be unmapped without being destroyed, and in this case the top layer visibility needs to be updated to unhide other layer-shell clients like panels. @jlindgren90 [#3199] @@ -370,7 +459,7 @@ A big thank you to all involved in this release. @elviosak [#3146] [#3168] - Work around client-side rounding issues at right/bottom pixel. This fixes an issue with some clients (notably Qt ones) where cursor coordinates in the - rightmost or bottom fixel are incorrectly rounded up putting them outside the + rightmost or bottom pixel are incorrectly rounded up putting them outside the surface bounds. The issue has been particularly noticeable with layer-shell clients like lxqt-panel. @jlindgren90 [#3157] [#2379] [#3099] Note: This also avoids a similar server-side rounding issue with some @@ -418,7 +507,7 @@ A big thank you to all involved in this release. when a window is using fullscreen mode. @johanmalm [#3158] - Call labnag with on-demand keyboard interactivity by default @tokyo4j [#3120] - Temporarily unshade windows when switching windows. Restore old behaviour with - `` @Amodio @Consolatis [#3124] + `` @Amodio @Consolatis [#3124] - In the classic style window-switcher, the default color of the selected window item has been changed to inherit the border color but with 15% opacity @tokyo4j [#3118] @@ -437,7 +526,7 @@ A big thank you to all involved in this release. ``. @tokyo4j [#2981] - Add `toggle` option to `GoToDesktop` action. This has the effect of going back to the last desktop if already on the target. @RainerKuemmerle [#3024] -- Add `` to allow hiding titlebar +- Add `` to allow hiding titlebar when window is maximized. @CosmicFusion @tokyo4j [#3015] - Use client-send-to-menu as 'Workspace' submenu in built-in client-menu @johanmalm [#2995] @@ -488,7 +577,7 @@ A big thank you to all involved in this release. - Change default keybind `W-` to combine cardinal directions to support resizing of windows to fill a quarter of an output. This only affects users who do not use an `rc.xml` (thereby using default keybinds) or use the - `` option. Previous behavior can be restored by setting + `` option. Previous behavior can be restored by setting `combine="no"` as shown below. [#3081] @tokyo4j ``` @@ -587,7 +676,7 @@ window.*.title.bg.colorTo.splitTo: window rule to enable this. @Consolatis @tokyo4j [#2840] - Add config option ``. This enables autoscroll (middle-click to scroll up/down) in Chromium and electron based clients - without inadvertantly pasting the primary clipboard. @johanmalm [#2832] + without inadvertently pasting the primary clipboard. @johanmalm [#2832] - Bump `xdg_shell` version from 3 to 6 @tokyo4j [#2814] - Bump `wl_compositor` version from 5 to 6 @tokyo4j [#2812] - Support tablet tool mouse buttons @jp7677 [#2778] @@ -664,7 +753,7 @@ window.*.title.bg.colorTo.splitTo: agnostic on choice of launcher. - `A-` for `MoveToEdge` because `Alt-` keybinds should be for clients to use and this one results in frequent user complaints because it prevents - some common usage patterns like alt-left/right in web browers. + some common usage patterns like alt-left/right in web browsers. - Change default titlebar menu button from a dot to an arrow @johanmalm [#2844] - When `dragLock` is set to `yes`, the drag no longer expires after a short delay (known as `Sticky` mode) as recommended by libinput [#2803]. The timeout @@ -699,7 +788,7 @@ release. - Localize desktop-entry application names used by the window switcher via `desktop_entry_name` or the `%n` specifier @tokyo4j [#2653] - Add `HideCursor` action @jp7677 [#2633] -- Support application icons in window-switcher using `` +- Support application icons in window-switcher using `` and use this by default. @tokyo4j [#2621] - Support application icons in client-list-combined-menu @tokyo4j [#2617] - Support the use of the keypad-enter key when using menu. @zeusgoose [#2610] @@ -836,7 +925,7 @@ Notes to package maintainers: closing a popup did not move the pointer focus to the main toplevel until the cursor was moved. [#2443] - Improve algorithm for menu placement with xdg-positioner [#2408] -- Do not forward IME key-release without correspinding key-press to avoid stuck +- Do not forward IME key-release without corresponding key-press to avoid stuck keys [#2437] ### Changed @@ -898,7 +987,7 @@ Notes to package maintainers: ```xml - + ``` @@ -915,8 +1004,8 @@ menu.border.color: #aaaaaa ```xml - - + + ``` @@ -1046,7 +1135,7 @@ Notes to package maintainers: - Support the openbox style menus listed below. Written-by: @droc12345 1. `client-list-combined-menu` shows windows across all workspaces. This can be used with a mouse/key bind using: - `` [#2101] + `` [#2101] 2. `client-send-to` shows all workspaces that the current window can be sent to. This can additional be used within a client menu using: `` [#2152] @@ -1205,14 +1294,14 @@ have been attributed with a 'Written-by' against each relevant log entry. ```xml cascade - + ``` - Support relative tablet motion. Written-by: @jp7677 [#1962] ```xml - + ``` ### Fixed @@ -1296,7 +1385,7 @@ joint effort by @spl237 and @Consolatis. - Respect `menu.overlap.x` when using pipemenus. [#1940] - Do not try to restore windows to very small width/height on unmaximize. This fixes a bug with Thonny (Python IDE made with Tk). [#1938] -- Conditially set squared server-side decoration (SSD) corners when a view is +- Conditionally set squared server-side decoration (SSD) corners when a view is tiled. Written-by: @jp7677 [#1926] - Remember initial direction when starting window-cycling with `PreviousView`. Also make the toggling of direction when shift is pressed relative to the @@ -1320,7 +1409,7 @@ joint effort by @spl237 and @Consolatis. Chromium and Steam. [#1861] - Session-lock: fix flashing & update cursor shape. [#1858] - Remove tearing-controller listeners on destroy. [#1853] -- Handle invalid `ForEach` and `If` action cofigs. [#1838] +- Handle invalid `ForEach` and `If` action configs. [#1838] - Delay startup of applications until event loop is ready. This avoids race conditions when using autostart scripts that trigger a labwc SIGHUP. [#1588] - With `SendToDesktop` action follow=no option, ensure the topmost window is @@ -1337,7 +1426,7 @@ joint effort by @spl237 and @Consolatis. - Remove subprojects/seatd.wrap as no longer needed - Action `MoveToCursor` is deprecated in favour of: - ``. + ``. ## 0.7.2 - 2024-05-10 @@ -1354,7 +1443,7 @@ contributions from others as noted in the log. ### Added - Add `` to prevent clicks with small movements - from inadvertantly closing a menu or selecting a menu item. This is the + from inadvertently closing a menu or selecting a menu item. This is the equivalent of `` on Openbox. [#1760] - Support drop-shadows (disabled by default) for windows using server-side decorations. Written-by: @cillian64 @@ -1382,7 +1471,7 @@ window.inactive.shadow.color: #00000040 ```xml - + @@ -1424,7 +1513,7 @@ osd.window-switcher.width: 75% yes|no - + ``` @@ -1587,7 +1676,7 @@ osd.window-switcher.preview.border.color: #ffffff,#00a2ff,#ffffff ```xml - + ``` @@ -1595,7 +1684,7 @@ osd.window-switcher.preview.border.color: #ffffff,#00a2ff,#ffffff is already used by the action itself). [#1589] ```xml - + ``` - Do not deactivate window when giving keyboard focus to a non-view @@ -1660,8 +1749,8 @@ osd.window-switcher.preview.border.color: #ffffff,#00a2ff,#ffffff Written-by: @jp7677 ```xml - - + + ``` - Add tablet support including: @@ -1874,7 +1963,7 @@ relating to surface focus and keyboard issues, amongst others. - Allow referencing the current workspace in actions, for example: ```xml - + ``` ### Fixed @@ -2030,7 +2119,7 @@ relating to surface focus and keyboard issues, amongst others. ```xml - + ``` @@ -2097,9 +2186,9 @@ relating to surface focus and keyboard issues, amongst others. ```xml - + - + ``` @@ -2184,7 +2273,7 @@ Unless otherwise stated all contributions are by the core-devs ```xml - + @@ -2448,7 +2537,7 @@ reported, tested and fixed issues. Particular mentions go to @bi4k8, actions to be de-coupled from buttons. As a result, "Drag" and "DoubleClick" actions previously defined against "TitleBar" should now come under the "Title" context, for example: - `` + `` - Remove default alt-escape keybind for Exit because too many people have exited the compositor by mistake trying to get out of alt-tab cycling or similar. @@ -2681,7 +2770,9 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 ShowMenu [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ -[unreleased-commits]: https://github.com/labwc/labwc/compare/0.9.5...HEAD +[unreleased-commits]: https://github.com/labwc/labwc/compare/0.20.0...HEAD +[0.20.0-commits]: https://github.com/labwc/labwc/compare/0.9.5..0.20.0 +[0.9.7-commits]: https://github.com/labwc/labwc/compare/0.9.6...0.9.7 [0.9.6-commits]: https://github.com/labwc/labwc/compare/0.9.5...0.9.6 [0.9.5-commits]: https://github.com/labwc/labwc/compare/0.9.4...0.9.5 [0.9.4-commits]: https://github.com/labwc/labwc/compare/0.9.3...0.9.4 @@ -3220,6 +3311,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3410]: https://github.com/labwc/labwc/pull/3410 [#3411]: https://github.com/labwc/labwc/pull/3411 [#3412]: https://github.com/labwc/labwc/pull/3412 +[#3424]: https://github.com/labwc/labwc/pull/3424 [#3425]: https://github.com/labwc/labwc/pull/3425 [#3426]: https://github.com/labwc/labwc/pull/3426 [#3428]: https://github.com/labwc/labwc/pull/3428 @@ -3232,3 +3324,23 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3445]: https://github.com/labwc/labwc/pull/3445 [#3446]: https://github.com/labwc/labwc/pull/3446 [#3450]: https://github.com/labwc/labwc/pull/3450 +[#3469]: https://github.com/labwc/labwc/pull/3469 +[#3484]: https://github.com/labwc/labwc/pull/3484 +[#3490]: https://github.com/labwc/labwc/pull/3490 +[#3493]: https://github.com/labwc/labwc/pull/3493 +[#3494]: https://github.com/labwc/labwc/pull/3494 +[#3499]: https://github.com/labwc/labwc/pull/3499 +[#3500]: https://github.com/labwc/labwc/pull/3500 +[#3502]: https://github.com/labwc/labwc/pull/3502 +[#3505]: https://github.com/labwc/labwc/pull/3505 +[#3507]: https://github.com/labwc/labwc/pull/3507 +[#3511]: https://github.com/labwc/labwc/pull/3511 +[#3513]: https://github.com/labwc/labwc/pull/3513 +[#3525]: https://github.com/labwc/labwc/pull/3525 +[#3527]: https://github.com/labwc/labwc/pull/3527 +[#3534]: https://github.com/labwc/labwc/pull/3534 +[#3540]: https://github.com/labwc/labwc/pull/3540 +[#3543]: https://github.com/labwc/labwc/pull/3543 +[#3547]: https://github.com/labwc/labwc/pull/3547 +[#3567]: https://github.com/labwc/labwc/pull/3567 +[#3595]: https://github.com/labwc/labwc/pull/3595 diff --git a/README.md b/README.md index 379de77a..80796af7 100644 --- a/README.md +++ b/README.md @@ -213,15 +213,16 @@ If you have not created an rc.xml config file, default bindings will be: | `super`-`return` | lab-sensible-terminal | `alt`-`F4` | close window | `super`-`a` | toggle maximize +| `super`-`d` | toggle show-desktop | `super`-`mouse-left` | move window | `super`-`mouse-right` | resize window | `super`-`arrow` | resize window to fill half the output | `alt`-`space` | show the window menu -| `XF86_AudioLowerVolume` | amixer sset Master 5%- -| `XF86_AudioRaiseVolume` | amixer sset Master 5%+ -| `XF86_AudioMute` | amixer sset Master toggle -| `XF86_MonBrightnessUp` | brightnessctl set +10% -| `XF86_MonBrightnessDown` | brightnessctl set 10%- +| `XF86AudioLowerVolume` | pactl set-sink-volume @DEFAULT_SINK@ -5% +| `XF86AudioRaiseVolume` | pactl set-sink-volume @DEFAULT_SINK@ +5% +| `XF86AudioMute` | pactl set-sink-mute @DEFAULT_SINK@ toggle +| `XF86MonBrightnessUp` | brightnessctl set +10% +| `XF86MonBrightnessDown` | brightnessctl set 10%- A root-menu can be opened by clicking on the desktop. diff --git a/clients/labnag.c b/clients/labnag.c index 82b6e8cd..60a5abb3 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -46,6 +46,7 @@ struct conf { uint32_t button_text; uint32_t button_background; uint32_t details_background; + uint32_t details_border_color; uint32_t background; uint32_t text; uint32_t button_border; @@ -60,6 +61,7 @@ struct conf { ssize_t button_gap_close; ssize_t button_margin_right; ssize_t button_padding; + ssize_t details_margin; }; struct pointer { @@ -300,10 +302,10 @@ render_details_scroll_button(cairo_t *cairo, struct nag *nag, get_text_size(cairo, nag->conf->font_description, &text_width, &text_height, NULL, 1, true, "%s", button->text); - int border = nag->conf->button_border_thickness; - int padding = nag->conf->button_padding; + int border = nag->conf->details_border_thickness; + int padding = (nag->conf->button_padding / 3) + 2; - cairo_set_source_u32(cairo, nag->conf->details_background); + cairo_set_source_u32(cairo, nag->conf->details_border_color); cairo_rectangle(cairo, button->x, button->y, button->width, button->height); cairo_fill(cairo); @@ -316,7 +318,7 @@ render_details_scroll_button(cairo_t *cairo, struct nag *nag, cairo_set_source_u32(cairo, nag->conf->button_text); cairo_move_to(cairo, button->x + border + padding, - button->y + border + (button->height - text_height) / 2); + button->y + (button->height - text_height) / 2); render_text(cairo, nag->conf->font_description, 1, true, "%s", button->text); } @@ -331,8 +333,8 @@ get_detailed_scroll_button_width(cairo_t *cairo, struct nag *nag) NULL, 1, true, "%s", nag->details.button_down.text); int text_width = up_width > down_width ? up_width : down_width; - int border = nag->conf->button_border_thickness; - int padding = nag->conf->button_padding; + int border = nag->conf->details_border_thickness; + int padding = (nag->conf->button_padding / 3) + 2; return text_width + border * 2 + padding * 2; } @@ -343,8 +345,9 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) uint32_t width = nag->width; int border = nag->conf->details_border_thickness; + int margin = nag->conf->details_margin; int padding = nag->conf->message_padding; - int decor = padding + border; + int decor = margin + border; nag->details.x = decor; nag->details.y = y + decor; @@ -372,7 +375,7 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) bool show_buttons = nag->details.offset > 0; int button_width = get_detailed_scroll_button_width(cairo, nag); if (show_buttons) { - nag->details.width -= button_width; + nag->details.width += border - button_width; pango_layout_set_width(layout, (nag->details.width - padding * 2) * PANGO_SCALE); } @@ -385,7 +388,7 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) if (!show_buttons) { show_buttons = true; - nag->details.width -= button_width; + nag->details.width += border - button_width; pango_layout_set_width(layout, (nag->details.width - padding * 2) * PANGO_SCALE); } @@ -401,21 +404,29 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) nag->details.visible_lines = pango_layout_get_line_count(layout); + int border_rect_height = nag->details.height + 2 * border; + if (show_buttons) { nag->details.button_up.x = nag->details.x + nag->details.width; - nag->details.button_up.y = nag->details.y; + nag->details.button_up.y = nag->details.y - border; nag->details.button_up.width = button_width; - nag->details.button_up.height = nag->details.height / 2; + nag->details.button_up.height = (border_rect_height + border) / 2; render_details_scroll_button(cairo, nag, &nag->details.button_up); nag->details.button_down.x = nag->details.x + nag->details.width; nag->details.button_down.y = - nag->details.button_up.y + nag->details.button_up.height; + nag->details.button_up.y + nag->details.button_up.height - border; nag->details.button_down.width = button_width; - nag->details.button_down.height = nag->details.height / 2; + nag->details.button_down.height = + border_rect_height - nag->details.button_up.height + border; render_details_scroll_button(cairo, nag, &nag->details.button_down); } + cairo_set_source_u32(cairo, nag->conf->details_border_color); + cairo_rectangle(cairo, margin, nag->details.y - border, + nag->details.width + 2 * border, border_rect_height); + cairo_fill(cairo); + cairo_set_source_u32(cairo, nag->conf->details_background); cairo_rectangle(cairo, nag->details.x, nag->details.y, nag->details.width, nag->details.height); @@ -447,7 +458,7 @@ render_button(cairo_t *cairo, struct nag *nag, struct button *button, } button->x = *x - border - text_width - padding * 2 + 1; - button->y = (int)(ideal_height - text_height) / 2 - padding + 1; + button->y = (int)(ideal_height - text_height) / 2 - padding; button->width = text_width + padding * 2; button->height = text_height + padding * 2; @@ -1464,14 +1475,16 @@ conf_init(struct conf *conf) conf->keyboard_focus = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; conf->bar_border_thickness = 2; conf->message_padding = 8; - conf->details_border_thickness = 3; conf->button_border_thickness = 3; conf->button_gap = 20; conf->button_gap_close = 15; conf->button_margin_right = 2; conf->button_padding = 3; conf->button_background = 0x680A0AFF; + conf->details_margin = 11; + conf->details_border_thickness = 3; conf->details_background = 0x680A0AFF; + conf->details_border_color = 0x680A0AFF; conf->background = 0x900000FF; conf->text = 0xFFFFFFFF; conf->button_text = 0xFFFFFFFF; @@ -1551,16 +1564,18 @@ nag_parse_options(int argc, char **argv, struct nag *nag, TO_COLOR_BORDER_BOTTOM, TO_COLOR_BUTTON_BG, TO_COLOR_DETAILS, + TO_COLOR_DETAILS_BORDER, TO_COLOR_TEXT, TO_COLOR_BUTTON_TEXT, TO_THICK_BAR_BORDER, TO_PADDING_MESSAGE, - TO_THICK_DET_BORDER, + TO_THICK_DETAILS_BORDER, TO_THICK_BTN_BORDER, TO_GAP_BTN, TO_GAP_BTN_DISMISS, TO_MARGIN_BTN_RIGHT, TO_PADDING_BTN, + TO_MARGIN_DETAILS, }; static const struct option opts[] = { @@ -1577,6 +1592,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, {"message", required_argument, NULL, 'm'}, {"output", required_argument, NULL, 'o'}, {"timeout", required_argument, NULL, 't'}, + {"exclusive-zone", no_argument, NULL, 'x'}, {"version", no_argument, NULL, 'v'}, {"background-color", required_argument, NULL, TO_COLOR_BACKGROUND}, @@ -1587,8 +1603,10 @@ nag_parse_options(int argc, char **argv, struct nag *nag, {"button-text-color", required_argument, NULL, TO_COLOR_BUTTON_TEXT}, {"border-bottom-size", required_argument, NULL, TO_THICK_BAR_BORDER}, {"message-padding", required_argument, NULL, TO_PADDING_MESSAGE}, - {"details-border-size", required_argument, NULL, TO_THICK_DET_BORDER}, + {"details-border-size", required_argument, NULL, TO_THICK_DETAILS_BORDER}, {"details-background-color", required_argument, NULL, TO_COLOR_DETAILS}, + {"details-border-color", required_argument, NULL, TO_COLOR_DETAILS_BORDER}, + {"details-margin", required_argument, NULL, TO_MARGIN_DETAILS}, {"button-border-size", required_argument, NULL, TO_THICK_BTN_BORDER}, {"button-gap", required_argument, NULL, TO_GAP_BTN}, {"button-dismiss-gap", required_argument, NULL, TO_GAP_BTN_DISMISS}, @@ -1633,6 +1651,9 @@ nag_parse_options(int argc, char **argv, struct nag *nag, " --details-border-size size Thickness for the details border.\n" " --details-background-color RRGGBB[AA]\n" " Details background color.\n" + " --details-border-color RRGGBB[AA]\n" + " Details border color.\n" + " --details-margin margin Margin for the details.\n" " --button-border-size size Thickness for the button border.\n" " --button-gap gap Size of the gap between buttons\n" " --button-dismiss-gap gap Size of the gap for dismiss button.\n" @@ -1769,6 +1790,11 @@ nag_parse_options(int argc, char **argv, struct nag *nag, fprintf(stderr, "Invalid details background color: %s\n", optarg); } break; + case TO_COLOR_DETAILS_BORDER: + if (!parse_color(optarg, &conf->details_border_color)) { + fprintf(stderr, "Invalid details border color: %s\n", optarg); + } + break; case TO_COLOR_TEXT: /* Text color */ if (!parse_color(optarg, &conf->text)) { fprintf(stderr, "Invalid text color: %s\n", optarg); @@ -1785,7 +1811,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, case TO_PADDING_MESSAGE: /* Message padding */ conf->message_padding = strtol(optarg, NULL, 0); break; - case TO_THICK_DET_BORDER: /* Details border thickness */ + case TO_THICK_DETAILS_BORDER: /* Details border thickness */ conf->details_border_thickness = strtol(optarg, NULL, 0); break; case TO_THICK_BTN_BORDER: /* Button border thickness */ @@ -1803,6 +1829,9 @@ nag_parse_options(int argc, char **argv, struct nag *nag, case TO_PADDING_BTN: /* Padding for the button text */ conf->button_padding = strtol(optarg, NULL, 0); break; + case TO_MARGIN_DETAILS: + conf->details_margin = strtol(optarg, NULL, 0); + break; default: /* Help or unknown flag */ fprintf(c == 'h' ? stdout : stderr, "%s", usage); return LAB_EXIT_FAILURE; diff --git a/data/labwc-session.target b/data/labwc-session.target new file mode 100644 index 00000000..e71f20e5 --- /dev/null +++ b/data/labwc-session.target @@ -0,0 +1,6 @@ +[Unit] +Description=labwc session +Documentation=man:labwc(1) man:systemd.special(7) +BindsTo=graphical-session.target +Wants=graphical-session-pre.target +After=graphical-session-pre.target diff --git a/docs/autostart b/docs/autostart index b045ed82..18adb23b 100644 --- a/docs/autostart +++ b/docs/autostart @@ -1,10 +1,18 @@ # Example autostart file +# When running under systemd, uncomment the systemctl line below to pull in +# graphical-session.target via labwc-session.target. This lets systemd user +# services declaring WantedBy=graphical-session.target (panels, portals, +# notification daemons, etc.) start in sync with the labwc session. Enable +# individual services with: systemctl --user enable +# +# systemctl --user --no-block start labwc-session.target + # Set background color. swaybg -c '#113344' >/dev/null 2>&1 & # Configure output directives such as mode, position, scale and transform. -# Use wlr-randr to get your output names +# Use wlr-randr to get your output names. # Example ~/.config/kanshi/config below: # profile { # output HDMI-A-1 position 1366,0 diff --git a/docs/labnag.1.scd b/docs/labnag.1.scd index 6d4b959f..bf237d00 100644 --- a/docs/labnag.1.scd +++ b/docs/labnag.1.scd @@ -95,6 +95,12 @@ _labnag_ [options...] *--details-border-size* Set the thickness for the details border. +*--details-border-color* + Set the color of the details border. + +*--details-margin* + Set the margin for the details. + *--button-border-size* Set the thickness for the button border. diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index fe468cd1..2677e348 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -16,7 +16,7 @@ Actions are used in menus and keyboard/mouse bindings. SIGTERM signal. ** - Execute command. Note that in the interest of backward compatibility, + Execute command. Note that in the interest of backward compatibility, labwc supports as an alternative to even though openbox documentation states that it is deprecated. Note: Tilde (~) is expanded in the command before passing to execvp(). @@ -126,30 +126,34 @@ Actions are used in menus and keyboard/mouse bindings. position if it had been maximized or tiled to a direction or region. **++ -** +**++ +**++ +**++ Cycle focus to next/previous window, respectively. Default keybinds for NextWindow and PreviousWindow are Alt-Tab and Shift-Alt-Tab. While cycling through windows, the arrow keys move the selected window forwards/backwards and the escape key halts the cycling. + NextWindowImmediate and PreviousWindowImmediate skip the Window Switcher + and OSD, useful for binding to keys without modifiers. + *workspace* [all|current] - This determines whether to cycle through windows on all workspaces or the - current workspace. Default is "current". + This determines whether to cycle through windows on all workspaces or + the current workspace. Default is "current". *output* [all|focused|cursor] - This determines whether to cycle through windows on all outputs, the focused - output, or the output under the cursor. Default is "all". + This determines whether to cycle through windows on all outputs, the + focused output, or the output under the cursor. Default is "all". *identifier* [all|current] - This determines whether to cycle through all windows or only windows of the - same application as the currently focused window. Default is "all". + This determines whether to cycle through all windows or only windows of + the same application as the currently focused window. Default is "all". ** Re-load configuration and theme files. -** - +** Show a menu. ``` @@ -295,7 +299,7 @@ Actions are used in menus and keyboard/mouse bindings. (if one exists). *wrap* [yes|no] When using the direction attribute, wrap around from - right-to-left or top-to-bottom, and vice versa. Default no. + right-to-left or top-to-bottom, and vice versa. Default is no. ** Resizes active window size to width and height of the output when the @@ -309,10 +313,10 @@ Actions are used in menus and keyboard/mouse bindings. workspace or its index (starting at 1) as configured in rc.xml. *wrap* [yes|no] Wrap around from last desktop to first, and vice - versa. Default yes. + versa. Default is yes. *toggle* [yes|no] Toggle to “last” if already on the workspace that - would be the actual destination. Default no. + would be the actual destination. Default is no. ** Send active window to workspace. @@ -320,10 +324,11 @@ Actions are used in menus and keyboard/mouse bindings. *to* The workspace to send the window to. Supported values are the same as for GoToDesktop. - *follow* [yes|no] Also switch to the specified workspace. Default yes. + *follow* [yes|no] Also switch to the specified workspace. + Default is yes. *wrap* [yes|no] Wrap around from last desktop to first, and vice - versa. Default yes. + versa. Default is yes. ** Add virtual output (headless backend). @@ -341,11 +346,11 @@ Actions are used in menus and keyboard/mouse bindings. ``` - - + + - + ``` @@ -366,7 +371,7 @@ Actions are used in menus and keyboard/mouse bindings. *output_name* The name of virtual output. If not supplied, will remove the last virtual output added. -** +** Reposition the window according to the desired placement policy. *policy* [automatic|cursor|center|cascade] Use the specified policy, @@ -424,6 +429,13 @@ Actions are used in menus and keyboard/mouse bindings. Toggle the screen magnifier on or off at the last magnification level used. +** + Minimize all windows in the current workspace so that the desktop + becomes visible. On calling the action again the hidden windows are + unminimized, provided that - since the initial `ShowDesktop` - (a) no + windows have been unminimized; (b) workspaces have not been switched; + and (c) no new applications have been started. + **++ ** Increase or decrease the magnification level for the screen magnifier. @@ -436,6 +448,10 @@ Actions are used in menus and keyboard/mouse bindings. If used as the only action for a binding: clear an earlier defined binding. +** + Toggle visibility of key-state on-screen display (OSD). Note: This is + for debugging purposes only. + # CONDITIONAL ACTIONS Actions that execute other actions. Used in keyboard/mouse bindings. @@ -448,10 +464,10 @@ Actions that execute other actions. Used in keyboard/mouse bindings. ``` - - - - + + + + ``` @@ -503,7 +519,7 @@ Actions that execute other actions. Used in keyboard/mouse bindings. "right-occupied" directions will not wrap. *tiled* [up|right|down|left|up-left|up-right|down-left|down-right|center|any] - Whether the client is tiled (snapped) along the the + Whether the client is tiled (snapped) along the indicated screen edge. *tiled_region* @@ -533,9 +549,9 @@ Actions that execute other actions. Used in keyboard/mouse bindings. ``` - + - + diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 09f7b3eb..6a16b89c 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -25,7 +25,7 @@ The XDG Base Directory Specification does not specify whether or not programs should (a) allow the first-identified configuration file to supersede any others, or (b) define rules for merging the information from more than one file. -By default, labwc uses option (a), reading only the first file identified. With +By default, labwc uses option (a), reading only the first file identified. With the --merge-config option, the search order is reversed, but every configuration file encountered is processed in turn. Thus, user-specific files will augment system-wide configurations, with conflicts favoring the user-specific @@ -175,6 +175,7 @@ this is for compatibility with Openbox. no no yes + no no no yes @@ -240,6 +241,12 @@ this is for compatibility with Openbox. 'pkill kanshi ; wlopm --off \*' resume 'kanshi & wlopm --on \*' ``` +** [yes|no] + Automatically enable HDR support on outputs when configuring them, + where supported by the particular output device and display. Default + is no. Additionally requires the Vulkan backend. Can be set + via `WLR_RENDERER=vulkan` in `~/.config/labwc/environment`. + ** [yes|no] Try to re-use the existing output mode (resolution / refresh rate). This may prevent unnecessary screenblank delays when starting labwc @@ -493,6 +500,13 @@ this is for compatibility with Openbox. ** [yes|no] Raise window to top when focused. Default is no. +** [milliseconds] + When raiseOnFocus is enabled, delay the actual raise by this many + milliseconds. Default is 0 (raise immediately). A subsequent focus + change before the timer elapses restarts or cancels the pending raise. + Useful together with followMouse to avoid brief passes of the cursor + stacking up z-order changes. + ## WINDOW SNAPPING Windows may be "snapped" to an edge or user-defined region of an output when @@ -512,7 +526,8 @@ extending outward from the snapped edge. ** and **, and 50 for **. ** [yes|no] - Show an overlay when snapping a window to an output edge. Default is yes. + Show an overlay when snapping a window to an output edge. + Default is yes. **++ ** @@ -581,7 +596,7 @@ extending outward from the snapped edge. A setting of 0 disables the OSD. Default is 1000 ms. ** - Set the prefix to use when using "number" above. Default is "Workspace" + Set the prefix to use when using "number" above. Default is "Workspace". ## THEME @@ -735,7 +750,7 @@ generate gesture events, like swipe and pinch. There are some related settings (e.g. *threeFingerDrag* and *twoFingerScroll*) in the ** section. In the Wayland Compositor domain, events associated with touchscreens are -sometimes simply referred to as *touch* events. Touchscreens can be configured +sometimes simply referred to as *touch* events. Touchscreens can be configured in both the ** and ** sections. Note that touchscreen gestures are not interpreted by libinput, nor labwc. Any touch point is passed to the client (application) for any interpretation of gestures. @@ -760,7 +775,8 @@ References: Stores the keyboard layout either globally or per window and restores it when switching back to the window. Default is global. -** +** Define a *key* binding in the format *modifier-key*, where supported modifiers are: - S (shift) @@ -808,6 +824,12 @@ References: *allowWhenLocked* [yes|no] Make this keybind work even if the screen is locked. Default is no. + *overrideInhibition* [yes|no] + Make this keybind work even if the view inhibits keybinds. + This can be used to prevent W-Tab and similar keybinds from being + delivered to Virtual Machines, VNC clients or nested compositors. + Default is no. + *onRelease* [yes|no] When yes, fires the keybind action when the key or key combination is released, rather than first pressed. This is useful to @@ -820,7 +842,7 @@ References: ``` - + ``` @@ -841,11 +863,12 @@ References: W-Return - lab-sensible-terminal A-F4 - close window W-a - toggle maximize + W-d - toggle show-desktop W- - resize window to fill half or quarter of the output A-Space - show window menu ``` - Audio and MonBrightness keys are also bound to amixer and + Audio and MonBrightness keys are also bound to pactl and brightnessctl, respectively. ** @@ -880,7 +903,7 @@ input-devices by the Wayland protocol. - Shade: A button that, by default, toggles window shading. - AllDesktops: A button that, by default, toggles omnipresence of a window. - - Close: A button that, by default, closses a window. + - Close: A button that, by default, closes a window. - Border: The window's border including Top...BRCorner below. - Top: The top edge of the window's border. - Bottom: The bottom edge of the window's border. @@ -954,10 +977,10 @@ input-devices by the Wayland protocol. ``` - + - - + + ``` @@ -965,7 +988,7 @@ input-devices by the Wayland protocol. ** Load default mousebinds. This is an addition to the openbox specification and provides a way to keep config files simpler whilst - allowing user specific binds. Note that if no rc.xml is found, or if no + allowing user specific binds. Note that if no rc.xml is found, or if no entries exist, the same default mousebinds will be loaded even if the element is not provided. @@ -977,7 +1000,7 @@ Note: To rotate touch events with output rotation, use the libinput *calibrationMatrix* setting. ``` - + ``` ** @@ -1134,6 +1157,7 @@ Note: To rotate touch events with output rotation, use the libinput + 1.0 @@ -1238,19 +1262,26 @@ Note: To rotate touch events with output rotation, use the libinput The default method depends on the touchpad hardware. -** [none|twofinger|edge] - Configure the method by which physical movements on a touchpad are - mapped to scroll events. +** [none|twofinger|edge|onbutton] + Configure the method by which physical movements are mapped to scroll + events. The scroll methods available are: - *twofinger* - Scroll by two fingers being placed on the surface of the touchpad, then moving those fingers vertically or horizontally. - *edge* - Scroll by moving a single finger along the right edge (vertical scroll) or bottom edge (horizontal scroll). + - *onbutton* - Scroll by pressing a button. - *none* - No scroll events will be produced. The default method depends on the touchpad hardware. +** [button] + Set the button used for the *onbutton* scroll method. + + *button* is the decimal form of a value + from `linux/input-event-codes.h`. + ** [yes|no|disabledOnExternalMouse] Optionally enable or disable sending any device events. @@ -1298,7 +1329,7 @@ defined as shown below. - + @@ -1445,11 +1476,57 @@ situation. Whether to apply a bilinear filter to the magnified image, or just to use nearest-neighbour. Default is true - bilinear filtered. +## PRIVILEGED INTERFACES + +Labwc supports a small set of privileged wayland interfaces. All of these +interfaces are enabled by default for applications unless they are running +via a sandbox environment supporting the security-context-v1 protocol. + +Security conscious users are suggested to use a sandbox framework to run +potentially untrusted applications as it additionally limits access to the +filesystem (including labwc configuration) and other services like dbus. + +In addition to that, privileged protocols can be restricted for non-sandboxed +clients by defining a `` block: + +``` + + zwlr_layer_shell_v1 + zwlr_virtual_pointer_manager_v1 + +``` + +** + Name of the interface that should be allowed. + +This is the full list of interfaces that can be controlled with this mechanism: + +- `wp_drm_lease_device_v1` +- `zwlr_gamma_control_manager_v1` +- `zwlr_output_manager_v1` +- `zwlr_output_power_manager_v1` +- `zwp_input_method_manager_v2` +- `zwlr_virtual_pointer_manager_v1` +- `zwp_virtual_keyboard_manager_v1` +- `zwlr_export_dmabuf_manager_v1` +- `zwlr_screencopy_manager_v1` +- `ext_data_control_manager_v1` +- `zwlr_data_control_manager_v1` +- `wp_security_context_manager_v1` +- `ext_idle_notifier_v1` +- `zwlr_foreign_toplevel_manager_v1` +- `ext_foreign_toplevel_list_v1` +- `ext_session_lock_manager_v1` +- `zwlr_layer_shell_v1` +- `ext_workspace_manager_v1` +- `ext_image_copy_capture_manager_v1` +- `ext_output_image_capture_source_manager_v1` + ## ENVIRONMENT VARIABLES *XCURSOR_PATH* Specify a colon-separated list of paths to look for mouse cursors in. - Default + Default is ~/.local/share/icons: ~/.icons: /usr/share/icons: @@ -1460,7 +1537,7 @@ situation. *XCURSOR_SIZE* Specify an alternative mouse cursor size in pixels. Requires - XCURSOR_THEME to be set also. Default 24. + XCURSOR_THEME to be set also. Default is 24. *XCURSOR_THEME* Specify a mouse cursor theme within XCURSOR_PATH. diff --git a/docs/labwc-menu.5.scd b/docs/labwc-menu.5.scd index cf7ccbc7..e2c40d84 100644 --- a/docs/labwc-menu.5.scd +++ b/docs/labwc-menu.5.scd @@ -12,7 +12,7 @@ Static menus are built based on the menu.xml file located at # SYNTAX The menu file must be entirely enclosed within and - tags. Inside these tags, menus are specified as follows: + tags. Inside these tags, menus are specified as follows: ``` @@ -111,7 +111,7 @@ Pipe menus are menus generated dynamically based on output of scripts or binaries. They are so-called because the output of the executable is piped to the labwc menu. -For any ** entry in menu.xml, the +For any ** entry in menu.xml, the COMMAND will be executed the first time the item is selected (for example by cursor or keyboard input). The XML output of the command will be parsed and shown as a submenu. The content of pipemenus is cached until the whole menu @@ -124,7 +124,7 @@ menus, for example: ``` - + ``` @@ -144,10 +144,25 @@ obmenu-generator with the menu generator of your choice): ``` - + ``` +# ACCELERATORS / MNEMONICS + +Menu accelerators are one-letter mnemonics to quickly select/exec items from +the current menu. For each menu item, the accelerator is defined as the first +character of the item label, converted to lowercase. A different accelerator +can be explicitly defined in menu.xml with the special '\_' character before the +target letter. Accelerators can be any unicode character and are not limited to +ASCII. A usual underscore can be shown by duplicating it. + +If the menu only contains a single instance of the pressed accelerator the item +will be executed directly. Otherwise, all matching items are cycled through. + +Example: +The accelerator for an item with the label "e_Macs" is 'm'. + # LOCALISATION Available localisation for the default "client-menu" is only shown if no diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index 7e43c984..5ed1dd9c 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -246,7 +246,7 @@ osd.bg.bevel-width:2 *window.active.title.bg* Texture for the focused window's titlebar. See texture section above. - Default is *Solid* + Default is *Solid*. *window.active.title.bg.width* Used with beveled textures. @@ -265,7 +265,7 @@ osd.bg.bevel-width:2 *window.inactive.title.bg* Texture for non-focused windows' titlebars. See texture section above. - Default is *Solid* + Default is *Solid*. *window.inactive.title.bg.width* Used with beveled textures. @@ -685,7 +685,7 @@ all are supported. Width of magnifier window border in pixels. Default is 1. *magnifier.border.color* - Color of the magnfier window border. Default is #ff0000 (red). + Color of the magnifier window border. Default is #ff0000 (red). # BUTTONS diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index 2dab30a5..56dfe775 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -66,10 +66,18 @@ the `--exit` and `--reconfigure` options use. Manager, or the Window Manager can be launched independently first. On Wayland, the Compositor is both Display Server and Window Manager, so the described session management mechanisms do not work because the - Compositor needs to be running before the session can function. As some + Compositor needs to be running before the session can function. As some session clients support both X11 and Wayland, this command line option avoids re-writes and fragmentation. +*-t, --title* + Set the window title for labwc to use when it is running in a window + (i.e. nested in a compositor). is a format string to be used as + the window title, replacing `%o` with the name of the output + region. This is useful when simulating multiple screens, such as with + running labwc with the environment variable `WLR_WL_OUTPUTS=2`. In this + case, `%o` will be unique per simulated screen. + *-v, --version* Show the version number and quit @@ -118,6 +126,25 @@ this is accomplished by setting the session variables to empty strings. For systemd, the command `systemctl --user unset-environment` will be invoked to actually remove the variables from the activation environment. +A systemd user unit named `labwc-session.target` is also shipped alongside +the compositor for users who want to integrate labwc with systemd. It binds +to the standard `graphical-session.target`, so systemd user services can +start and stop in sync with the labwc session when they declare a WantedBy +or PartOf relationship to that target. Labwc does not activate the target +itself; users opt in by adding lines like the following to their +*autostart* and *shutdown* files: + +``` +systemctl --user --no-block start labwc-session.target +systemctl --user stop graphical-session.target +``` + +The example *autostart* and *shutdown* files shipped with labwc include +these commented out. To have a user service automatically started with +the session, enable it so the corresponding symlink under the +graphical-session.target.wants directory exists, for example by running +"systemctl --user enable dms.service". + # ENVIRONMENT VARIABLES Set the environment variables listed below to enable specific debug options. diff --git a/docs/menu.xml b/docs/menu.xml index 4b4d5dda..3344161a 100644 --- a/docs/menu.xml +++ b/docs/menu.xml @@ -23,7 +23,7 @@ Any menu with the id "workspaces" will be hidden if there is only a single workspace available. --> - + + 0 @@ -276,6 +279,9 @@ + + + @@ -291,19 +297,19 @@ - - + + - - + + - - + + - + - + @@ -592,7 +598,7 @@ - accelProfile [flat|adaptive] - tapButtonMap [lrm|lmr] - clickMethod [none|buttonAreas|clickfinger] - - scrollMethod [twoFinger|edge|none] + - scrollMethod [twoFinger|edge|onbutton|none] - sendEventsMode [yes|no|disabledOnExternalMouse] - calibrationMatrix [six float values split by space] - scrollFactor [float] @@ -618,6 +624,7 @@ + 1.0 @@ -638,10 +645,10 @@ # string and '?' matches any single character. - - - - + + + + # Example below for `lxqt-panel` and `pcmanfm-qt \-\-desktop` @@ -652,18 +659,18 @@ yes - + yes yes yes - + - + --> diff --git a/docs/shutdown b/docs/shutdown index feed6508..a036ff53 100644 --- a/docs/shutdown +++ b/docs/shutdown @@ -3,3 +3,11 @@ # This file is executed as a shell script when labwc is preparing to terminate # itself. # For further details see labwc-config(5). + +# When running under systemd, uncomment the systemctl line below to tear down +# graphical-session.target (which cascades to labwc-session.target via +# BindsTo, and to any service declaring PartOf=graphical-session.target). +# Running synchronously here ensures those services are stopped before the +# Wayland socket goes away, avoiding "Broken pipe" failures on teardown. +# +# systemctl --user stop graphical-session.target diff --git a/include/common/font.h b/include/common/font.h index a7a5ba55..22be2ae8 100644 --- a/include/common/font.h +++ b/include/common/font.h @@ -4,6 +4,7 @@ #include #include +#include struct lab_data_buffer; @@ -43,10 +44,11 @@ void font_get_buffer_size(int max_width, const char *text, struct font *font, * @font: font description * @color: foreground color in rgba format * @bg_pattern: background pattern + * @use_markup: flag to render pango markup */ void font_buffer_create(struct lab_data_buffer **buffer, int max_width, int height, const char *text, struct font *font, const float *color, - cairo_pattern_t *bg_pattern, double scale); + cairo_pattern_t *bg_pattern, double scale, bool use_markup); /** * font_finish - free some font related resources diff --git a/include/common/macros.h b/include/common/macros.h index f3240206..69d4261e 100644 --- a/include/common/macros.h +++ b/include/common/macros.h @@ -62,8 +62,12 @@ #define BOUNDED_INT(a) ((a) < INT_MAX && (a) > INT_MIN) #endif -#define LAB_WLR_VERSION_AT_LEAST(major, minor, micro) \ - (WLR_VERSION_NUM >= (((major) << 16) | ((minor) << 8) | (micro))) +#define _LAB_CALC_WLR_VERSION_NUM(major, minor, micro) (((major) << 16) | ((minor) << 8) | (micro)) + +#define LAB_WLR_VERSION_AT_LEAST(major, minor, micro) ( \ + server.wlr_version >= _LAB_CALC_WLR_VERSION_NUM(major, minor, micro)) + +#define LAB_WLR_VERSION_LOWER(major, minor, micro) (!LAB_WLR_VERSION_AT_LEAST(major, minor, micro)) /** * PIXEL () - calculate pixel offset in an array diff --git a/include/common/spawn.h b/include/common/spawn.h index 43123269..4df5ed0b 100644 --- a/include/common/spawn.h +++ b/include/common/spawn.h @@ -16,6 +16,12 @@ pid_t spawn_primary_client(const char *command); */ void spawn_async_no_shell(char const *command); +/** + * spawn_sync_no_shell - execute synchronously + * @command: command to be executed + */ +void spawn_sync_no_shell(char const *command); + /** * spawn_piped - execute asynchronously * @command: command to be executed diff --git a/include/config/default-bindings.h b/include/config/default-bindings.h index 9d42d237..d1a96562 100644 --- a/include/config/default-bindings.h +++ b/include/config/default-bindings.h @@ -28,6 +28,9 @@ static struct key_combos { }, { .binding = "W-a", .action = "ToggleMaximize", + }, { + .binding = "W-d", + .action = "ToggleShowDesktop", }, { .binding = "W-Left", .action = "SnapToEdge", @@ -84,35 +87,35 @@ static struct key_combos { .value = "no", }, }, { - .binding = "XF86_AudioLowerVolume", + .binding = "XF86AudioLowerVolume", .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master 5%-", + .value = "pactl set-sink-volume @DEFAULT_SINK@ -5%", }, }, { - .binding = "XF86_AudioRaiseVolume", + .binding = "XF86AudioRaiseVolume", .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master 5%+", + .value = "pactl set-sink-volume @DEFAULT_SINK@ +5%", }, }, { - .binding = "XF86_AudioMute", + .binding = "XF86AudioMute", .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master toggle", + .value = "pactl set-sink-mute @DEFAULT_SINK@ toggle", }, }, { - .binding = "XF86_MonBrightnessUp", + .binding = "XF86MonBrightnessUp", .action = "Execute", .attributes[0] = { .name = "command", .value = "brightnessctl set +10%", }, }, { - .binding = "XF86_MonBrightnessDown", + .binding = "XF86MonBrightnessDown", .action = "Execute", .attributes[0] = { .name = "command", @@ -141,14 +144,14 @@ static struct key_combos { * * * - * - * - * + * + * + * * * * * - * + * * * * diff --git a/include/config/keybind.h b/include/config/keybind.h index e3c9e21b..d02de387 100644 --- a/include/config/keybind.h +++ b/include/config/keybind.h @@ -23,6 +23,7 @@ struct keybind { struct wl_list actions; /* struct action.link */ struct wl_list link; /* struct rcxml.keybinds */ bool on_release; + bool override_inhibition; }; /** diff --git a/include/config/libinput.h b/include/config/libinput.h index 80b3fc10..077bc011 100644 --- a/include/config/libinput.h +++ b/include/config/libinput.h @@ -31,6 +31,7 @@ struct libinput_category { int dwt; /* -1 or libinput_config_dwt_state */ int click_method; /* -1 or libinput_config_click_method */ int scroll_method; /* -1 or libinput_config_scroll_method */ + int scroll_button; /* -1 or a button from linux/input_event_codes.h */ int send_events_mode; /* -1 or libinput_config_send_events_mode */ bool have_calibration_matrix; double scroll_factor; diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 517cd907..9c2183a8 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -36,6 +36,12 @@ enum tearing_mode { LAB_TEARING_FULLSCREEN_FORCED, }; +enum render_bit_depth { + LAB_RENDER_BIT_DEPTH_DEFAULT = 0, + LAB_RENDER_BIT_DEPTH_8, + LAB_RENDER_BIT_DEPTH_10, +}; + enum tiling_events_mode { LAB_TILING_EVENTS_NEVER = 0, LAB_TILING_EVENTS_REGION = 1 << 0, @@ -74,8 +80,10 @@ struct rcxml { int gap; enum adaptive_sync_mode adaptive_sync; enum tearing_mode allow_tearing; + enum render_bit_depth target_render_depth; bool auto_enable_outputs; bool reuse_output_mode; + uint32_t allowed_interfaces; bool xwayland_persistence; bool primary_selection; char *prompt_command; @@ -89,6 +97,7 @@ struct rcxml { bool focus_follow_mouse; bool focus_follow_mouse_requires_movement; bool raise_on_focus; + uint32_t raise_on_focus_delay_ms; /* theme */ char *theme_name; @@ -225,4 +234,6 @@ void rcxml_finish(void); */ void append_parsed_actions(xmlNode *node, struct wl_list *list); +uint32_t parse_privileged_interface(const char *name); + #endif /* LABWC_RCXML_H */ diff --git a/include/cycle.h b/include/cycle.h index 9bb4cb69..440b938f 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -103,6 +103,10 @@ void cycle_finish(bool switch_focus); /* Re-initialize the window switcher */ void cycle_reinitialize(void); +/* Immediately cycle to next/previous window */ +void cycle_immediate(enum lab_cycle_dir direction, + struct cycle_filter filter); + /* Focus the clicked window and close OSD */ void cycle_on_cursor_release(struct wlr_scene_node *node); diff --git a/include/input/key-state.h b/include/input/key-state.h index d9971b74..81374b6c 100644 --- a/include/input/key-state.h +++ b/include/input/key-state.h @@ -5,12 +5,17 @@ #include #include +struct seat; + /* * All keycodes in these functions are (Linux) libinput evdev scancodes which is * what 'wlr_keyboard' uses (e.g. 'seat->keyboard_group->keyboard->keycodes'). * Note: These keycodes are different to XKB scancodes by a value of 8. */ +void key_state_indicator_update(struct seat *seat); +void key_state_indicator_toggle(void); + /** * key_state_pressed_sent_keycodes - generate array of pressed+sent keys * Note: The array is generated by subtracting any bound keys from _all_ pressed diff --git a/include/labwc.h b/include/labwc.h index 511fdd65..87a42198 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -148,8 +148,15 @@ struct seat { }; struct server { + uint32_t wlr_version; + struct wl_display *wl_display; struct wl_event_loop *wl_event_loop; /* Can be used for timer events */ + + /* Pending auto-raise timer (used when rc.raise_on_focus_delay_ms > 0) */ + struct view *pending_auto_raise_view; + struct wl_event_source *pending_auto_raise_timer; + struct wlr_renderer *renderer; struct wlr_allocator *allocator; struct wlr_backend *backend; @@ -187,6 +194,13 @@ struct server { struct wlr_xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager; struct wl_listener xdg_toplevel_icon_set_icon; + struct { + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1 *manager; + struct { + struct wl_listener new_request; + } on; + } toplevel_capture; + /* front to back order */ struct wl_list views; uint64_t next_view_creation_id; @@ -308,6 +322,8 @@ struct server { struct sfdo *sfdo; pid_t primary_client_pid; + + char *title_fmt; }; /* defined in main.c */ @@ -343,6 +359,13 @@ void xdg_shell_finish(void); */ void desktop_focus_view(struct view *view, bool raise); +/** + * desktop_cancel_pending_auto_raise() - cancel any pending delayed auto-raise + * (from raiseOnFocusDelay). Called when a view is being destroyed, on config + * reload, or when a new focus change with raise=false supersedes the pending. + */ +void desktop_cancel_pending_auto_raise(void); + /** * desktop_focus_view_or_surface() - like desktop_focus_view() but can * also focus other (e.g. xwayland-unmanaged) surfaces diff --git a/include/menu/menu.h b/include/menu/menu.h index 46bbca12..3d546efa 100644 --- a/include/menu/menu.h +++ b/include/menu/menu.h @@ -23,9 +23,11 @@ struct menuitem { char *text; char *icon_name; const char *arrow; + uint32_t accelerator; struct menu *parent; struct menu *submenu; bool selectable; + bool use_markup; enum menuitem_type type; int native_width; struct wlr_scene_tree *tree; @@ -66,6 +68,19 @@ struct menu { /* For keyboard support */ void menu_item_select_next(void); void menu_item_select_previous(void); + +/** + * menu_item_select_by_accelerator - selects the next menu item with + * a matching accelerator, starting after the current selection + * + * @accelerator a shortcut to quickly select/open an item, defined in menu.xml + * with an underscore in the item label before the target letter. + * + * Return: a boolean value that represents whether the newly selected item + * needs to be executed. + */ +bool menu_item_select_by_accelerator(uint32_t accelerator); + void menu_submenu_enter(void); void menu_submenu_leave(void); bool menu_call_selected_actions(void); @@ -100,7 +115,7 @@ void menu_open_root(struct menu *menu, int x, int y); void menu_process_cursor_motion(struct wlr_scene_node *node); /** - * menu_close_root- close root menu + * menu_close_root - close root menu * * This function will close server.menu_current and set it to NULL. * Asserts that server.input_mode is set to LAB_INPUT_STATE_MENU. diff --git a/include/output.h b/include/output.h index 89d8be04..c17defa4 100644 --- a/include/output.h +++ b/include/output.h @@ -71,6 +71,9 @@ struct wlr_box output_usable_area_in_layout_coords(struct output *output); void handle_output_power_manager_set_mode(struct wl_listener *listener, void *data); void output_enable_adaptive_sync(struct output *output, bool enabled); +void output_enable_hdr(struct output *output, struct wlr_output_state *os, + bool enabled, bool silent); +void output_state_setup_hdr(struct output *output, bool silent); /** * Notifies whether a fullscreen view is displayed on the given output. diff --git a/include/scaled-buffer/scaled-buffer.h b/include/scaled-buffer/scaled-buffer.h index c2af6054..017e29fb 100644 --- a/include/scaled-buffer/scaled-buffer.h +++ b/include/scaled-buffer/scaled-buffer.h @@ -130,7 +130,7 @@ void scaled_buffer_request_update(struct scaled_buffer *self, /** * scaled_buffer_invalidate_sharing - clear the list of entire cached - * scaled_buffers used to share visually dupliated buffers. This should + * scaled_buffers used to share visually duplicated buffers. This should * be called on Reconfigure to force updates of newly created * scaled_buffers rather than reusing ones created before Reconfigure. */ diff --git a/include/scaled-buffer/scaled-font-buffer.h b/include/scaled-buffer/scaled-font-buffer.h index a5e95087..2f0fe422 100644 --- a/include/scaled-buffer/scaled-font-buffer.h +++ b/include/scaled-buffer/scaled-font-buffer.h @@ -15,6 +15,7 @@ struct scaled_font_buffer { /* Private */ char *text; + bool use_markup; int max_width; float color[4]; float bg_color[4]; @@ -69,8 +70,18 @@ scaled_font_buffer_create_for_titlebar(struct wlr_scene_tree *parent, * bg_color is ignored for font buffers created with * scaled_font_buffer_create_for_titlebar(). */ -void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, - int max_width, struct font *font, const float *color, +void scaled_font_buffer_update(struct scaled_font_buffer *self, + const char *text, int max_width, struct font *font, const float *color, const float *bg_color); +/** + * Update an existing auto scaling font buffer allowing the use of pango markup. + * + * Behaves identically to scaled_font_buffer_update(), but allows customization + * of the `use_markup` field of the @self struct via @use_markup. + */ +void scaled_font_buffer_update_markup(struct scaled_font_buffer *self, + const char *text, int max_width, struct font *font, const float *color, + const float *bg_color, bool use_markup); + #endif /* LABWC_SCALED_FONT_BUFFER_H */ diff --git a/include/show-desktop.h b/include/show-desktop.h new file mode 100644 index 00000000..cc86bcb4 --- /dev/null +++ b/include/show-desktop.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef LABWC_SHOW_DESKTOP_H +#define LABWC_SHOW_DESKTOP_H + +void show_desktop_toggle(void); +void show_desktop_reset(void); + +#endif /* LABWC_SHOW_DESKTOP_H */ diff --git a/include/view.h b/include/view.h index 6c6dc795..22af7d6e 100644 --- a/include/view.h +++ b/include/view.h @@ -177,6 +177,12 @@ struct view { char *title; char *app_id; /* WM_CLASS for xwayland windows */ + struct { + struct wlr_scene *scene; + struct wlr_ext_image_capture_source_v1 *source; + struct wl_listener on_capture_source_destroy; + } capture; + bool mapped; bool been_mapped; uint64_t creation_id; @@ -184,6 +190,7 @@ struct view { enum ssd_preference ssd_preference; bool shaded; bool minimized; + bool was_minimized_by_show_desktop_action; enum view_axis maximized; bool fullscreen; bool tearing_hint; @@ -319,6 +326,7 @@ struct xdg_toplevel_view { /* Events unique to xdg-toplevel views */ struct wl_listener set_app_id; struct wl_listener request_show_window_menu; + struct wl_listener set_parent; struct wl_listener new_popup; }; @@ -630,4 +638,6 @@ enum lab_placement_policy view_placement_parse(const char *policy); /* xdg.c */ struct wlr_xdg_surface *xdg_surface_from_view(struct view *view); +bool view_matches_criteria(struct view *view, enum lab_view_criteria criteria); + #endif /* LABWC_VIEW_H */ diff --git a/meson.build b/meson.build index 2c2d8e57..8684732f 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'labwc', 'c', - version: '0.9.5', + version: '0.20.0', license: 'GPL-2.0-only', meson_version: '>=0.59.0', default_options: [ @@ -53,7 +53,7 @@ add_project_arguments('-DLABWC_VERSION=@0@'.format(version), language: 'c') wlroots = dependency( 'wlroots-0.20', default_options: ['default_library=static', 'examples=false'], - version: ['>=0.20.0', '<0.21.0'], + version: ['>=0.20.1', '<0.21.0'], ) wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true' @@ -211,6 +211,17 @@ install_data('data/labwc.desktop', install_dir: get_option('datadir') / 'wayland install_data('data/labwc-portals.conf', install_dir: get_option('datadir') / 'xdg-desktop-portal') +# Install labwc-session.target so that systemd user services with +# WantedBy=graphical-session.target can be started and stopped in sync +# with a labwc session (see labwc(1) SESSION MANAGEMENT for the opt-in +# autostart/shutdown snippet). +systemd_feat = get_option('systemd-session') +systemd = dependency('systemd', required: systemd_feat) +if systemd.found() + install_data('data/labwc-session.target', + install_dir: systemd.get_variable('systemduserunitdir')) +endif + icons = ['labwc-symbolic.svg', 'labwc.svg'] foreach icon : icons icon_path = join_paths('data', icon) diff --git a/meson_options.txt b/meson_options.txt index a47efa86..e059220d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,6 +4,7 @@ option('svg', type: 'feature', value: 'enabled', description: 'Enable svg window option('icon', type: 'feature', value: 'enabled', description: 'Enable window icons') option('labnag', type: 'feature', value: 'auto', description: 'Build labnag notification daemon') option('nls', type: 'feature', value: 'auto', description: 'Enable native language support') +option('systemd-session', type: 'feature', value: 'auto', description: 'Install labwc-session.target systemd user unit') option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer') option('test', type: 'feature', value: 'disabled', description: 'Run tests') option('sections', type: 'feature', value: 'disabled', description: 'Show unused functions') diff --git a/po/LINGUAS b/po/LINGUAS index 33f036c9..50e5157a 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -1 +1 @@ -ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kk ko lt ms nl pa pl pt pt_BR ru sk sv tr uk vi zh_CN zh_TW +ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kab kk ko lt ms nl pa pl pt pt_BR ru sk sr_Latn sv tr uk vi zh_CN zh_TW diff --git a/po/de.po b/po/de.po index 5841f3f6..d83ef64a 100644 --- a/po/de.po +++ b/po/de.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-09-19 21:09+1000\n" -"PO-Revision-Date: 2025-08-15 20:01+0000\n" -"Last-Translator: Ettore Atalan \n" +"PO-Revision-Date: 2026-05-18 18:28+0000\n" +"Last-Translator: Demian \n" "Language-Team: German \n" "Language: de\n" @@ -21,7 +21,7 @@ msgstr "" #: src/menu/menu.c:1016 msgid "Go there..." -msgstr "Dorthin gehen..." +msgstr "Dorthin wechseln ..." #: src/menu/menu.c:1034 msgid "Terminal" @@ -61,11 +61,11 @@ msgstr "Immer im Vordergrund" #: src/menu/menu.c:1071 msgid "Move Left" -msgstr "nach links" +msgstr "Nach links" #: src/menu/menu.c:1078 msgid "Move Right" -msgstr "nach rechts" +msgstr "Nach rechts" #: src/menu/menu.c:1083 msgid "Always on Visible Workspace" diff --git a/po/kab.po b/po/kab.po new file mode 100644 index 00000000..44961f47 --- /dev/null +++ b/po/kab.po @@ -0,0 +1,80 @@ +# Labwc pot file +# Copyright (C) 2024 +# This file is distributed under the same license as the labwc package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: labwc\n" +"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" +"POT-Creation-Date: 2024-09-19 21:09+1000\n" +"PO-Revision-Date: 2026-03-31 08:01+0000\n" +"Last-Translator: ButterflyOfFire \n" +"Language-Team: Kabyle \n" +"Language: kab\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.2.1\n" + +#: src/menu/menu.c:1016 +msgid "Go there..." +msgstr "" + +#: src/menu/menu.c:1034 +msgid "Terminal" +msgstr "Ixf" + +#: src/menu/menu.c:1040 +msgid "Reconfigure" +msgstr "" + +#: src/menu/menu.c:1042 +msgid "Exit" +msgstr "Ffeɣ" + +#: src/menu/menu.c:1056 +msgid "Minimize" +msgstr "" + +#: src/menu/menu.c:1058 +msgid "Maximize" +msgstr "" + +#: src/menu/menu.c:1060 +msgid "Fullscreen" +msgstr "Agdil aččuran" + +#: src/menu/menu.c:1062 +msgid "Roll Up/Down" +msgstr "" + +#: src/menu/menu.c:1064 +msgid "Decorations" +msgstr "" + +#: src/menu/menu.c:1066 +msgid "Always on Top" +msgstr "" + +#: src/menu/menu.c:1071 +msgid "Move Left" +msgstr "" + +#: src/menu/menu.c:1078 +msgid "Move Right" +msgstr "" + +#: src/menu/menu.c:1083 +msgid "Always on Visible Workspace" +msgstr "" + +#: src/menu/menu.c:1086 +msgid "Workspace" +msgstr "" + +#: src/menu/menu.c:1089 +msgid "Close" +msgstr "Mdel" diff --git a/po/sr_Latn.po b/po/sr_Latn.po new file mode 100644 index 00000000..ef41f62e --- /dev/null +++ b/po/sr_Latn.po @@ -0,0 +1,81 @@ +# Labwc pot file +# Copyright (C) 2024 +# This file is distributed under the same license as the labwc package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: labwc\n" +"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" +"POT-Creation-Date: 2024-09-19 21:09+1000\n" +"PO-Revision-Date: 2026-05-05 08:28+0000\n" +"Last-Translator: Tobias Si \n" +"Language-Team: Serbian (latin) \n" +"Language: sr_Latn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.2.1\n" + +#: src/menu/menu.c:1016 +msgid "Go there..." +msgstr "Idi tamo..." + +#: src/menu/menu.c:1034 +msgid "Terminal" +msgstr "Terminal" + +#: src/menu/menu.c:1040 +msgid "Reconfigure" +msgstr "Podesi" + +#: src/menu/menu.c:1042 +msgid "Exit" +msgstr "Izlaz" + +#: src/menu/menu.c:1056 +msgid "Minimize" +msgstr "Minimiziraj" + +#: src/menu/menu.c:1058 +msgid "Maximize" +msgstr "Maksimiziraj" + +#: src/menu/menu.c:1060 +msgid "Fullscreen" +msgstr "Prikaz preko celog ekrana" + +#: src/menu/menu.c:1062 +msgid "Roll Up/Down" +msgstr "Prevuci Gore/Dole" + +#: src/menu/menu.c:1064 +msgid "Decorations" +msgstr "Dekoracije" + +#: src/menu/menu.c:1066 +msgid "Always on Top" +msgstr "Uvek na vrhu" + +#: src/menu/menu.c:1071 +msgid "Move Left" +msgstr "Pomeri levo" + +#: src/menu/menu.c:1078 +msgid "Move Right" +msgstr "Pomeri desno" + +#: src/menu/menu.c:1083 +msgid "Always on Visible Workspace" +msgstr "Uvek na vidljivom radnom prostoru" + +#: src/menu/menu.c:1086 +msgid "Workspace" +msgstr "Radni prostor" + +#: src/menu/menu.c:1089 +msgid "Close" +msgstr "Zatvori" diff --git a/src/action.c b/src/action.c index 66fa9b20..265ff6e7 100644 --- a/src/action.c +++ b/src/action.c @@ -21,12 +21,14 @@ #include "cycle.h" #include "debug.h" #include "input/keyboard.h" +#include "input/key-state.h" #include "labwc.h" #include "magnifier.h" #include "menu/menu.h" #include "output.h" #include "output-virtual.h" #include "regions.h" +#include "show-desktop.h" #include "ssd.h" #include "theme.h" #include "translate.h" @@ -82,6 +84,8 @@ struct action_arg_list { X(SHRINK_TO_EDGE, "ShrinkToEdge") \ X(NEXT_WINDOW, "NextWindow") \ X(PREVIOUS_WINDOW, "PreviousWindow") \ + X(NEXT_WINDOW_IMMEDIATE, "NextWindowImmediate") \ + X(PREVIOUS_WINDOW_IMMEDIATE, "PreviousWindowImmediate") \ X(RECONFIGURE, "Reconfigure") \ X(SHOW_MENU, "ShowMenu") \ X(TOGGLE_MAXIMIZE, "ToggleMaximize") \ @@ -132,14 +136,16 @@ struct action_arg_list { X(TOGGLE_MAGNIFY, "ToggleMagnify") \ X(ZOOM_IN, "ZoomIn") \ X(ZOOM_OUT, "ZoomOut") \ + X(TOGGLE_SHOW_DESKTOP, "ToggleShowDesktop") \ X(WARP_CURSOR, "WarpCursor") \ - X(HIDE_CURSOR, "HideCursor") + X(HIDE_CURSOR, "HideCursor") \ + X(DEBUG_TOGGLE_KEY_STATE_INDICATOR, "DebugToggleKeyStateIndicator") /* * Will expand to: * * enum action_type { - * ACTION_TYPE_INVALID, + * ACTION_TYPE_INVALID = 0, * ACTION_TYPE_NONE, * ACTION_TYPE_CLOSE, * ACTION_TYPE_KILL, @@ -333,6 +339,8 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char break; case ACTION_TYPE_NEXT_WINDOW: case ACTION_TYPE_PREVIOUS_WINDOW: + case ACTION_TYPE_NEXT_WINDOW_IMMEDIATE: + case ACTION_TYPE_PREVIOUS_WINDOW_IMMEDIATE: if (!strcasecmp(argument, "workspace")) { if (!strcasecmp(content, "all")) { action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL); @@ -1142,6 +1150,21 @@ run_action(struct view *view, struct action *action, } break; } + case ACTION_TYPE_NEXT_WINDOW_IMMEDIATE: + case ACTION_TYPE_PREVIOUS_WINDOW_IMMEDIATE: { + enum lab_cycle_dir dir = (action->type == ACTION_TYPE_NEXT_WINDOW_IMMEDIATE) ? + LAB_CYCLE_DIR_FORWARD : LAB_CYCLE_DIR_BACKWARD; + struct cycle_filter filter = { + .workspace = action_get_int(action, "workspace", + rc.window_switcher.workspace_filter), + .output = action_get_int(action, "output", + CYCLE_OUTPUT_ALL), + .app_id = action_get_int(action, "identifier", + CYCLE_APP_ID_ALL), + }; + cycle_immediate(dir, filter); + break; + } case ACTION_TYPE_RECONFIGURE: kill(getpid(), SIGHUP); break; @@ -1289,7 +1312,7 @@ run_action(struct view *view, struct action *action, /* * To support only setting one of width/height - * in + * in * we fall back to current dimension when unset. */ struct wlr_box box = { @@ -1560,6 +1583,9 @@ run_action(struct view *view, struct action *action, case ACTION_TYPE_ZOOM_OUT: magnifier_set_scale(MAGNIFY_DECREASE); break; + case ACTION_TYPE_TOGGLE_SHOW_DESKTOP: + show_desktop_toggle(); + break; case ACTION_TYPE_WARP_CURSOR: { const char *to = action_get_str(action, "to", "output"); const char *x = action_get_str(action, "x", "center"); @@ -1570,6 +1596,9 @@ run_action(struct view *view, struct action *action, case ACTION_TYPE_HIDE_CURSOR: cursor_set_visible(&server.seat, false); break; + case ACTION_TYPE_DEBUG_TOGGLE_KEY_STATE_INDICATOR: + key_state_indicator_toggle(); + break; case ACTION_TYPE_INVALID: wlr_log(WLR_ERROR, "Not executing unknown action"); break; diff --git a/src/common/buf.c b/src/common/buf.c index c141b62e..2a68b734 100644 --- a/src/common/buf.c +++ b/src/common/buf.c @@ -53,15 +53,14 @@ buf_expand_shell_variables(struct buf *s) if (s->data[i] == '$' && isvalid(s->data[i+1])) { /* expand environment variable */ buf_clear(&environment_variable); - buf_add(&environment_variable, s->data + i + 1); - char *p = environment_variable.data; - while (isvalid(*p)) { - ++p; + int len = 0; + while (isvalid(s->data[i + 1 + len])) { + buf_add_char(&environment_variable, s->data[i + 1 + len]); + ++len; } - *p = '\0'; - i += strlen(environment_variable.data); + i += len; strip_curly_braces(environment_variable.data); - p = getenv(environment_variable.data); + char *p = getenv(environment_variable.data); if (p) { buf_add(&tmp, p); } diff --git a/src/common/font.c b/src/common/font.c index b307729c..5162d284 100644 --- a/src/common/font.c +++ b/src/common/font.c @@ -79,7 +79,7 @@ font_get_buffer_size(int max_width, const char *text, struct font *font, void font_buffer_create(struct lab_data_buffer **buffer, int max_width, int height, const char *text, struct font *font, const float *color, - cairo_pattern_t *bg_pattern, double scale) + cairo_pattern_t *bg_pattern, double scale, bool use_markup) { if (string_null_or_empty(text)) { return; @@ -123,7 +123,13 @@ font_buffer_create(struct lab_data_buffer **buffer, int max_width, PangoLayout *layout = pango_cairo_create_layout(cairo); pango_context_set_round_glyph_positions(pango_layout_get_context(layout), false); pango_layout_set_width(layout, width * PANGO_SCALE); - pango_layout_set_text(layout, text, -1); + + if (use_markup) { + pango_layout_set_markup(layout, text, -1); + } else { + pango_layout_set_text(layout, text, -1); + } + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); if (!opaque_bg) { diff --git a/src/common/spawn.c b/src/common/spawn.c index 898c4800..6073d6ae 100644 --- a/src/common/spawn.c +++ b/src/common/spawn.c @@ -89,6 +89,37 @@ out: g_strfreev(argv); } +void +spawn_sync_no_shell(char const *command) +{ + GError *err = NULL; + gchar **argv = NULL; + + assert(command); + + g_shell_parse_argv((gchar *)command, NULL, &argv, &err); + if (err) { + g_message("%s", err->message); + g_error_free(err); + return; + } + + pid_t child = fork(); + switch (child) { + case -1: + wlr_log(WLR_ERROR, "unable to fork()"); + break; + case 0: + reset_signals_and_limits(); + execvp(argv[0], argv); + _exit(1); + default: + waitpid(child, NULL, 0); + break; + } + g_strfreev(argv); +} + pid_t spawn_primary_client(const char *command) { diff --git a/src/config/libinput.c b/src/config/libinput.c index 1209d267..59bf469e 100644 --- a/src/config/libinput.c +++ b/src/config/libinput.c @@ -25,6 +25,7 @@ libinput_category_init(struct libinput_category *l) l->dwt = -1; l->click_method = -1; l->scroll_method = -1; + l->scroll_button = -1; l->send_events_mode = -1; l->have_calibration_matrix = false; l->scroll_factor = 1.0; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index a3eeed63..ab0639d1 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -94,6 +94,43 @@ parse_window_type(const char *type) } } +uint32_t +parse_privileged_interface(const char *name) +{ + static const char * const ifaces[] = { + "wp_drm_lease_device_v1", + "zwlr_gamma_control_manager_v1", + "zwlr_output_manager_v1", + "zwlr_output_power_manager_v1", + "zwp_input_method_manager_v2", + "zwlr_virtual_pointer_manager_v1", + "zwp_virtual_keyboard_manager_v1", + "zwlr_export_dmabuf_manager_v1", + "zwlr_screencopy_manager_v1", + "ext_data_control_manager_v1", + "zwlr_data_control_manager_v1", + "wp_security_context_manager_v1", + "ext_idle_notifier_v1", + "zwlr_foreign_toplevel_manager_v1", + "ext_foreign_toplevel_list_v1", + "ext_session_lock_manager_v1", + "zwlr_layer_shell_v1", + "ext_workspace_manager_v1", + "ext_image_copy_capture_manager_v1", + "ext_output_image_capture_source_manager_v1", + }; + + static_assert(ARRAY_SIZE(ifaces) <= 32, + "return type too small for amount of privileged protocols"); + + for (size_t i = 0; i < ARRAY_SIZE(ifaces); i++) { + if (!strcmp(name, ifaces[i])) { + return 1 << i; + } + } + return 0; +} + /* * Openbox/labwc comparison * @@ -565,6 +602,7 @@ fill_keybind(xmlNode *node) lab_xml_get_bool(node, "onRelease", &keybind->on_release); lab_xml_get_bool(node, "layoutDependent", &keybind->use_syms_only); lab_xml_get_bool(node, "allowWhenLocked", &keybind->allow_when_locked); + lab_xml_get_bool(node, "overrideInhibition", &keybind->override_inhibition); append_parsed_actions(node, &keybind->actions); } @@ -575,9 +613,9 @@ fill_mousebind(xmlNode *node, const char *context) /* * Example of what we are parsing: * - * - * - * + * + * + * * */ @@ -853,9 +891,19 @@ fill_libinput_category(xmlNode *node) } else if (!strcasecmp(content, "twofinger")) { category->scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + } else if (!strcasecmp(content, "onbutton")) { + category->scroll_method = + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; } else { wlr_log(WLR_ERROR, "invalid scrollMethod"); } + } else if (!strcasecmp(key, "scrollButton")) { + int button = atoi(content); + if (button != 0) { + category->scroll_button = button; + } else { + wlr_log(WLR_ERROR, "invalid scrollButton"); + } } else if (!strcasecmp(key, "sendEventsMode")) { category->send_events_mode = get_send_events_mode(content); @@ -1032,6 +1080,16 @@ set_tearing_mode(const char *str, enum tearing_mode *variable) } } +static void +set_hdr_mode(const char *str, enum render_bit_depth *variable) +{ + if (parse_bool(str, -1) == 1) { + *variable = LAB_RENDER_BIT_DEPTH_10; + } else { + *variable = LAB_RENDER_BIT_DEPTH_8; + } +} + /* Returns true if the node's children should also be traversed */ static bool entry(xmlNode *node, char *nodename, char *content) @@ -1096,6 +1154,8 @@ entry(xmlNode *node, char *nodename, char *content) set_adaptive_sync_mode(content, &rc.adaptive_sync); } else if (!strcasecmp(nodename, "allowTearing.core")) { set_tearing_mode(content, &rc.allow_tearing); + } else if (!strcasecmp(nodename, "Hdr.core")) { + set_hdr_mode(content, &rc.target_render_depth); } else if (!strcasecmp(nodename, "autoEnableOutputs.core")) { set_bool(content, &rc.auto_enable_outputs); } else if (!strcasecmp(nodename, "reuseOutputMode.core")) { @@ -1147,6 +1207,9 @@ entry(xmlNode *node, char *nodename, char *content) set_bool(content, &rc.focus_follow_mouse_requires_movement); } else if (!strcasecmp(nodename, "raiseOnFocus.focus")) { set_bool(content, &rc.raise_on_focus); + } else if (!strcasecmp(nodename, "raiseOnFocusDelay.focus")) { + long val = strtol(content, NULL, 10); + rc.raise_on_focus_delay_ms = val > 0 ? (uint32_t)val : 0; } else if (!strcasecmp(nodename, "doubleClickTime.mouse")) { long doubleclick_time_parsed = strtol(content, NULL, 10); if (doubleclick_time_parsed > 0) { @@ -1377,6 +1440,16 @@ entry(xmlNode *node, char *nodename, char *content) rc.mag_increment = MAX(0, rc.mag_increment); } else if (!strcasecmp(nodename, "useFilter.magnifier")) { set_bool(content, &rc.mag_filter); + } else if (!strcasecmp(nodename, "privilegedInterfaces")) { + rc.allowed_interfaces = 0; + } else if (!strcasecmp(nodename, "allow.privilegedInterfaces")) { + uint32_t iface_id = parse_privileged_interface(content); + if (iface_id) { + rc.allowed_interfaces |= iface_id; + } else { + wlr_log(WLR_ERROR, "invalid value for " + ""); + } } return false; @@ -1457,8 +1530,10 @@ rcxml_init(void) rc.gap = 0; rc.adaptive_sync = LAB_ADAPTIVE_SYNC_DISABLED; rc.allow_tearing = LAB_TEARING_DISABLED; + rc.target_render_depth = LAB_RENDER_BIT_DEPTH_DEFAULT; rc.auto_enable_outputs = true; rc.reuse_output_mode = false; + rc.allowed_interfaces = UINT32_MAX; rc.xwayland_persistence = false; rc.primary_selection = true; @@ -1471,6 +1546,7 @@ rcxml_init(void) rc.focus_follow_mouse = false; rc.focus_follow_mouse_requires_movement = true; rc.raise_on_focus = false; + rc.raise_on_focus_delay_ms = 0; rc.doubleclick_time = 500; @@ -1658,6 +1734,8 @@ deduplicate_key_bindings(void) wl_list_remove(¤t->link); keybind_destroy(current); cleared++; + } else if (actions_contain_toggle_keybinds(¤t->actions)) { + current->override_inhibition = true; } } if (replaced) { diff --git a/src/config/session.c b/src/config/session.c index 7df6236c..61ce156f 100644 --- a/src/config/session.c +++ b/src/config/session.c @@ -219,12 +219,12 @@ execute_update(const char *env_keys, const char *env_unset_keys, bool initialize char *cmd = strdup_printf("dbus-update-activation-environment %s", initialize ? env_keys : env_unset_keys); - spawn_async_no_shell(cmd); + spawn_sync_no_shell(cmd); free(cmd); cmd = strdup_printf("systemctl --user %s %s", initialize ? "import-environment" : "unset-environment", env_keys); - spawn_async_no_shell(cmd); + spawn_sync_no_shell(cmd); free(cmd); } diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 56f1e212..c923a843 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -9,6 +9,7 @@ #include "common/mem.h" #include "common/scene-helpers.h" #include "config/rcxml.h" +#include "config/types.h" #include "labwc.h" #include "node.h" #include "output.h" @@ -171,7 +172,7 @@ cycle_begin(enum lab_cycle_dir direction, struct view *active_view = server.active_view; if (active_view && active_view->cycle_link.next) { - /* Select the active view it's in the cycle list */ + /* Select the active view if it's in the cycle list */ server.cycle.selected_view = active_view; } else { /* Otherwise, select the first view in the cycle list */ @@ -326,24 +327,82 @@ handle_osd_tree_destroy(struct wl_listener *listener, void *data) free(osd_output); } -/* Return false on failure */ -static bool -init_cycle(struct cycle_filter filter) +static enum lab_view_criteria +get_view_criteria(struct cycle_filter *filter) { enum lab_view_criteria criteria = LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER | LAB_VIEW_CRITERIA_NO_DIALOG; - if (filter.workspace == CYCLE_WORKSPACE_CURRENT) { + if (filter->workspace == CYCLE_WORKSPACE_CURRENT) { criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; } + return criteria; +} - uint64_t cycle_outputs = - get_outputs_by_filter(filter.output); - - const char *cycle_app_id = NULL; - if (filter.app_id == CYCLE_APP_ID_CURRENT && server.active_view) { - cycle_app_id = server.active_view->app_id; +static const char * +get_cycle_app_id(struct cycle_filter *filter) +{ + if (filter->app_id == CYCLE_APP_ID_CURRENT && server.active_view) { + return server.active_view->app_id; } + return NULL; +} + +static struct wl_list *prev(struct wl_list *elm) { return elm->prev; } +static struct wl_list *next(struct wl_list *elm) { return elm->next; } + +void +cycle_immediate(enum lab_cycle_dir direction, struct cycle_filter filter) +{ + if (wl_list_empty(&server.views)) { + return; + } + + enum lab_view_criteria criteria = get_view_criteria(&filter); + uint64_t cycle_outputs = get_outputs_by_filter(filter.output); + const char *cycle_app_id = get_cycle_app_id(&filter); + + struct wl_list *head = &server.views; + struct wl_list *(*iter)(struct wl_list *list); + iter = direction == LAB_CYCLE_DIR_FORWARD ? next : prev; + + struct wl_list *from = (direction == LAB_CYCLE_DIR_FORWARD) && server.active_view + ? &server.active_view->link : head; + + for (struct wl_list *elm = iter(from); elm != head; elm = iter(elm)) { + struct view *view = wl_container_of(elm, view, link); + if (!view_matches_criteria(view, criteria)) { + continue; + } + if (filter.output != CYCLE_OUTPUT_ALL) { + if (!view->output || !(cycle_outputs & view->output->id_bit)) { + continue; + } + } + if (cycle_app_id && strcmp(view->app_id, cycle_app_id) != 0) { + continue; + } + if (server.active_view && direction == LAB_CYCLE_DIR_FORWARD) { + /* + * When cycling forward, the current active view needs to be + * sent to back to keep the same sequence and avoid getting + * stuck in the 2 topmost views. + */ + view_move_to_back(server.active_view); + } + desktop_focus_view(view, true); + break; + } + cursor_update_focus(); +} + +/* Return false on failure */ +static bool +init_cycle(struct cycle_filter filter) +{ + enum lab_view_criteria criteria = get_view_criteria(&filter); + uint64_t cycle_outputs = get_outputs_by_filter(filter.output); + const char *cycle_app_id = get_cycle_app_id(&filter); struct view *view; for_each_view(view, &server.views, criteria) { diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index 948e4d35..ba688e56 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include "config/rcxml.h" @@ -46,8 +47,12 @@ render_node(struct wlr_render_pass *pass, if (!scene_buffer->buffer) { break; } - struct wlr_texture *texture = wlr_texture_from_buffer( - server.renderer, scene_buffer->buffer); + struct wlr_texture *texture = NULL; + struct wlr_client_buffer *client_buffer = + wlr_client_buffer_get(scene_buffer->buffer); + if (client_buffer) { + texture = client_buffer->texture; + } if (!texture) { break; } @@ -62,7 +67,6 @@ render_node(struct wlr_render_pass *pass, }, .transform = scene_buffer->transform, }); - wlr_texture_destroy(texture); break; } case WLR_SCENE_NODE_RECT: @@ -85,6 +89,10 @@ render_thumb(struct output *output, struct view *view) struct wlr_buffer *buffer = wlr_allocator_create_buffer(server.allocator, view->current.width, view->current.height, &output->wlr_output->swapchain->format); + if (!buffer) { + wlr_log(WLR_ERROR, "failed to allocate buffer for thumbnail"); + return NULL; + } struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass( server.renderer, buffer, NULL); render_node(pass, &view->content_tree->node, 0, 0); diff --git a/src/desktop.c b/src/desktop.c index 28bf4464..57ef9e3c 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -9,11 +9,13 @@ #include #include #include "common/scene-helpers.h" +#include "config/rcxml.h" #include "dnd.h" #include "labwc.h" #include "layers.h" #include "node.h" #include "output.h" +#include "show-desktop.h" #include "ssd.h" #include "view.h" #include "workspaces.h" @@ -64,8 +66,51 @@ set_or_offer_focus(struct view *view) } } +static int +handle_auto_raise_timer(void *data) +{ + (void)data; + struct view *view = server.pending_auto_raise_view; + server.pending_auto_raise_view = NULL; + + if (view && view->mapped) { + view_move_to_front(view); + } + return 0; /* ignored per wl_event_loop docs */ +} + void -desktop_focus_view(struct view *view, bool raise) +desktop_cancel_pending_auto_raise(void) +{ + server.pending_auto_raise_view = NULL; + if (server.pending_auto_raise_timer) { + /* Disarm by setting to 0 ms */ + wl_event_source_timer_update(server.pending_auto_raise_timer, 0); + } +} + +static void +schedule_delayed_auto_raise(struct view *view) +{ + server.pending_auto_raise_view = view; + if (!server.pending_auto_raise_timer) { + server.pending_auto_raise_timer = + wl_event_loop_add_timer(server.wl_event_loop, + handle_auto_raise_timer, NULL); + } + wl_event_source_timer_update(server.pending_auto_raise_timer, + rc.raise_on_focus_delay_ms); +} + +/* + * The raise_on_focus_delay is only meant to dampen z-order churn from + * focus-follows-mouse cursor passes. Explicit focus changes (alt-tab, + * Focus action, xdg/xwayland activation, etc.) should raise immediately. + * allow_delay is therefore only set when the caller is the sloppy-focus + * path in desktop_focus_view_or_surface(). + */ +static void +desktop_focus_view_internal(struct view *view, bool raise, bool allow_delay) { assert(view); /* @@ -102,8 +147,17 @@ desktop_focus_view(struct view *view, bool raise) workspaces_switch_to(view->workspace, /*update_focus*/ false); } + /* + * A new focus change supersedes any pending auto-raise from a + * previous focus event, regardless of whether we raise now. + */ + desktop_cancel_pending_auto_raise(); if (raise) { - view_move_to_front(view); + if (allow_delay && rc.raise_on_focus_delay_ms > 0) { + schedule_delayed_auto_raise(view); + } else { + view_move_to_front(view); + } } /* @@ -113,6 +167,14 @@ desktop_focus_view(struct view *view, bool raise) */ struct view *dialog = view_get_modal_dialog(view); set_or_offer_focus(dialog ? dialog : view); + + show_desktop_reset(); +} + +void +desktop_focus_view(struct view *view, bool raise) +{ + desktop_focus_view_internal(view, raise, /*allow_delay*/ false); } /* TODO: focus layer-shell surfaces also? */ @@ -122,7 +184,7 @@ desktop_focus_view_or_surface(struct seat *seat, struct view *view, { assert(view || surface); if (view) { - desktop_focus_view(view, raise); + desktop_focus_view_internal(view, raise, /*allow_delay*/ true); #if HAVE_XWAYLAND } else { struct wlr_xwayland_surface *xsurface = diff --git a/src/foreign-toplevel/ext-foreign.c b/src/foreign-toplevel/ext-foreign.c index 78774433..1dfa1d96 100644 --- a/src/foreign-toplevel/ext-foreign.c +++ b/src/foreign-toplevel/ext-foreign.c @@ -75,6 +75,9 @@ ext_foreign_toplevel_init(struct ext_foreign_toplevel *ext_toplevel, return; } + /* In support for ext-toplevel-capture */ + ext_toplevel->handle->data = view; + /* Client side requests */ ext_toplevel->on.handle_destroy.notify = handle_handle_destroy; wl_signal_add(&ext_toplevel->handle->events.destroy, &ext_toplevel->on.handle_destroy); diff --git a/src/img/img-xbm.c b/src/img/img-xbm.c index 91269f64..3771444a 100644 --- a/src/img/img-xbm.c +++ b/src/img/img-xbm.c @@ -226,7 +226,7 @@ out: /* * Openbox built-in icons are not bigger than 8x8, so have only written this - * function to cope wit that max size + * function to cope with that max size */ #define LABWC_BUILTIN_ICON_MAX_SIZE (8) diff --git a/src/img/img-xpm.c b/src/img/img-xpm.c index 0d251b49..3bc7a9bb 100644 --- a/src/img/img-xpm.c +++ b/src/img/img-xpm.c @@ -351,7 +351,7 @@ xpm_load_to_surface(struct file_handle *handle) goto out; } - for (int n = 0, xcnt = 0; n < wbytes; n += cpp, xcnt++) { + for (int n = 0; n < wbytes; n += cpp) { g_strlcpy(pixel_str, &buffer[n], cpp + 1); struct xpm_color *color = diff --git a/src/input/key-state.c b/src/input/key-state.c index 492f7b0f..6d089891 100644 --- a/src/input/key-state.c +++ b/src/input/key-state.c @@ -4,10 +4,171 @@ #include #include #include +#include +#include +#include +#include "config/rcxml.h" +#include "common/buf.h" #include "common/set.h" +#include "input/keyboard.h" +#include "labwc.h" +#include "scaled-buffer/scaled-font-buffer.h" static struct lab_set pressed, bound, pressed_sent; +static bool show_debug_indicator; +static struct indicator_state { + struct wlr_scene_tree *tree; + struct scaled_font_buffer *sfb_pressed; + struct scaled_font_buffer *sfb_bound; + struct scaled_font_buffer *sfb_pressed_sent; + struct scaled_font_buffer *sfb_modifiers; + struct xkb_keymap *keymap; +} indicator_state; + +static const char * +keycode_to_keyname(struct xkb_keymap *keymap, uint32_t keycode) +{ + const xkb_keysym_t *syms; + int syms_len = xkb_keymap_key_get_syms_by_level(keymap, keycode + 8, 0, 0, &syms); + if (!syms_len) { + return NULL; + } + + static char buf[256]; + if (!xkb_keysym_get_name(syms[0], buf, sizeof(buf))) { + return NULL; + } + + return buf; +} + +static const char * +modifier_to_name(uint32_t modifier) +{ + switch (modifier) { + case WLR_MODIFIER_SHIFT: + return "S"; + case WLR_MODIFIER_CAPS: + return "caps"; + case WLR_MODIFIER_CTRL: + return "C"; + case WLR_MODIFIER_ALT: + return "A"; + case WLR_MODIFIER_MOD2: + return "numlock"; + case WLR_MODIFIER_MOD3: + return "H"; + case WLR_MODIFIER_LOGO: + return "W"; + case WLR_MODIFIER_MOD5: + return "M"; + default: + return "?"; + } +} + +static void +init_indicator(struct indicator_state *state) +{ + state->tree = wlr_scene_tree_create(&server.scene->tree); + wlr_scene_node_set_enabled(&state->tree->node, false); + + state->sfb_pressed = scaled_font_buffer_create(state->tree); + wlr_scene_node_set_position(&state->sfb_pressed->scene_buffer->node, 0, 0); + state->sfb_bound = scaled_font_buffer_create(state->tree); + wlr_scene_node_set_position(&state->sfb_bound->scene_buffer->node, 0, 20); + state->sfb_pressed_sent = scaled_font_buffer_create(state->tree); + wlr_scene_node_set_position(&state->sfb_pressed_sent->scene_buffer->node, 0, 40); + state->sfb_modifiers = scaled_font_buffer_create(state->tree); + wlr_scene_node_set_position(&state->sfb_modifiers->scene_buffer->node, 0, 60); + + struct xkb_rule_names rules = { 0 }; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + state->keymap = xkb_map_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); +} + +static void +update_key_indicator_callback(void *data) +{ + struct seat *seat = data; + uint32_t all_modifiers = keyboard_get_all_modifiers(seat); + float black[4] = {0, 0, 0, 1}; + float white[4] = {1, 1, 1, 1}; + + if (!indicator_state.tree) { + init_indicator(&indicator_state); + } + + if (show_debug_indicator) { + wlr_scene_node_set_enabled(&indicator_state.tree->node, true); + } else { + wlr_scene_node_set_enabled(&indicator_state.tree->node, false); + return; + } + + struct buf buf = BUF_INIT; + + buf_add(&buf, "pressed="); + for (int i = 0; i < pressed.size; i++) { + const char *keyname = keycode_to_keyname(indicator_state.keymap, + pressed.values[i]); + buf_add_fmt(&buf, "%s (%d), ", keyname, pressed.values[i]); + } + scaled_font_buffer_update(indicator_state.sfb_pressed, buf.data, + -1, &rc.font_osd, black, white); + + buf_clear(&buf); + buf_add(&buf, "bound="); + for (int i = 0; i < bound.size; i++) { + const char *keyname = keycode_to_keyname(indicator_state.keymap, + bound.values[i]); + buf_add_fmt(&buf, "%s (%d), ", keyname, bound.values[i]); + } + scaled_font_buffer_update(indicator_state.sfb_bound, buf.data, + -1, &rc.font_osd, black, white); + + buf_clear(&buf); + buf_add(&buf, "pressed_sent="); + for (int i = 0; i < pressed_sent.size; i++) { + const char *keyname = keycode_to_keyname(indicator_state.keymap, + pressed_sent.values[i]); + buf_add_fmt(&buf, "%s (%d), ", keyname, pressed_sent.values[i]); + } + scaled_font_buffer_update(indicator_state.sfb_pressed_sent, buf.data, -1, + &rc.font_osd, black, white); + + buf_clear(&buf); + buf_add(&buf, "modifiers="); + for (int i = 0; i <= 7; i++) { + uint32_t mod = 1 << i; + if (all_modifiers & mod) { + buf_add_fmt(&buf, "%s, ", modifier_to_name(mod)); + } + } + buf_add_fmt(&buf, "(%d)", all_modifiers); + scaled_font_buffer_update(indicator_state.sfb_modifiers, buf.data, -1, + &rc.font_osd, black, white); + + buf_reset(&buf); +} + +void +key_state_indicator_update(struct seat *seat) +{ + if (!show_debug_indicator) { + return; + } + wl_event_loop_add_idle(server.wl_event_loop, + update_key_indicator_callback, seat); +} + +void +key_state_indicator_toggle(void) +{ + show_debug_indicator = !show_debug_indicator; +} + static void report(struct lab_set *key_set, const char *msg) { diff --git a/src/input/keyboard.c b/src/input/keyboard.c index 2d4bde6e..36884144 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -139,6 +139,8 @@ handle_modifiers(struct wl_listener *listener, void *data) struct seat *seat = keyboard->base.seat; struct wlr_keyboard *wlr_keyboard = keyboard->wlr_keyboard; + key_state_indicator_update(seat); + if (server.input_mode == LAB_INPUT_STATE_MOVE) { /* Any change to the modifier state re-enable region snap */ seat->region_prevent_snap = false; @@ -201,8 +203,10 @@ match_keybinding_for_sym(uint32_t modifiers, if (modifiers ^ keybind->modifiers) { continue; } - if (view_inhibits_actions(server.active_view, &keybind->actions)) { - continue; + if (!(keybind->override_inhibition)) { + if (view_inhibits_actions(server.active_view, &keybind->actions)) { + continue; + } } if (sym == XKB_KEY_NoSymbol) { /* Use keycodes */ @@ -440,15 +444,24 @@ handle_menu_keys(struct keysyms *syms) break; case XKB_KEY_Return: case XKB_KEY_KP_Enter: - menu_call_selected_actions(); + if (!menu_call_selected_actions()) { + menu_submenu_enter(); + }; break; case XKB_KEY_Escape: menu_close_root(); cursor_update_focus(); break; - default: - continue; - } + default: { + uint32_t accelerator = xkb_keysym_to_utf32(syms->syms[i]); + if (accelerator == 0) { + continue; + } + if (menu_item_select_by_accelerator(accelerator)) { + menu_call_selected_actions(); + } + break; + }} break; } } @@ -622,6 +635,9 @@ handle_key(struct wl_listener *listener, void *data) struct seat *seat = keyboard->base.seat; struct wlr_keyboard_key_event *event = data; struct wlr_seat *wlr_seat = seat->wlr_seat; + + key_state_indicator_update(seat); + idle_manager_notify_activity(seat->wlr_seat); /* any new press/release cancels current keybind repeat */ diff --git a/src/input/tablet.c b/src/input/tablet.c index 58587490..82d5e801 100644 --- a/src/input/tablet.c +++ b/src/input/tablet.c @@ -431,8 +431,6 @@ handle_tablet_tool_axis(struct wl_listener *listener, void *data) */ tool->dx = 0; tool->dy = 0; - tool->tilt_x = 0; - tool->tilt_y = 0; if (ev->updated_axes & WLR_TABLET_TOOL_AXIS_X) { tool->x = ev->x; diff --git a/src/interactive.c b/src/interactive.c index f4d8eea1..9214ba30 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -123,11 +123,12 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) cursor_shape = LAB_CURSOR_GRAB; break; case LAB_INPUT_STATE_RESIZE: { - if (view->shaded || view->fullscreen || - view->maximized == VIEW_AXIS_BOTH) { + if (view->shaded || view->fullscreen) { /* - * We don't allow resizing while shaded, - * fullscreen or maximized in both directions. + * We don't allow resizing while shaded or fullscreen. + * Maximized views are handled below by un-maximizing + * the axes being resized while keeping the current + * geometry as the starting point. */ return; } @@ -141,9 +142,9 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) } /* - * If tiled or maximized in only one direction, reset - * tiled state and un-maximize the relevant axes, but - * keep the same geometry as the starting point. + * If tiled or maximized, reset tiled state and un-maximize + * the axes that are being resized, but keep the same + * geometry as the starting point. */ enum view_axis maximized = view->maximized; if (server.resize_edges & LAB_EDGES_LEFT_RIGHT) { diff --git a/src/main.c b/src/main.c index 0534b542..1f8fc55e 100644 --- a/src/main.c +++ b/src/main.c @@ -4,9 +4,12 @@ #include #include #include +#include #include "common/fd-util.h" #include "common/font.h" +#include "common/macros.h" #include "common/spawn.h" +#include "common/string-helpers.h" #include "config/rcxml.h" #include "config/session.h" #include "labwc.h" @@ -36,6 +39,7 @@ static const struct option long_options[] = { {"reconfigure", no_argument, NULL, 'r'}, {"startup", required_argument, NULL, 's'}, {"session", required_argument, NULL, 'S'}, + {"title", required_argument, NULL, 't'}, {"version", no_argument, NULL, 'v'}, {"verbose", no_argument, NULL, 'V'}, {0, 0, 0, 0} @@ -54,6 +58,7 @@ static const char labwc_usage[] = " -r, --reconfigure Reload the compositor configuration\n" " -s, --startup Run command on startup\n" " -S, --session Run command on startup and terminate on exit\n" +" -t, --title Specify title to use when running in a window\n" " -v, --version Show version number and quit\n" " -V, --verbose Enable more verbose logging\n"; @@ -68,12 +73,15 @@ static void print_version(void) { #define FEATURE_ENABLED(feature) (HAVE_##feature ? "+" : "-") - printf("labwc %s (%sxwayland %snls %srsvg %slibsfdo)\n", + printf("labwc %s (%sxwayland %snls %srsvg %slibsfdo) wlroots-%d.%d.%d\n", LABWC_VERSION, FEATURE_ENABLED(XWAYLAND), FEATURE_ENABLED(NLS), FEATURE_ENABLED(RSVG), - FEATURE_ENABLED(LIBSFDO) + FEATURE_ENABLED(LIBSFDO), + wlr_version_get_major(), + wlr_version_get_minor(), + wlr_version_get_micro() ); #undef FEATURE_ENABLED } @@ -167,10 +175,16 @@ main(int argc, char *argv[]) char *primary_client = NULL; enum wlr_log_importance verbosity = WLR_ERROR; + server.wlr_version = _LAB_CALC_WLR_VERSION_NUM( + wlr_version_get_major(), + wlr_version_get_minor(), + wlr_version_get_micro() + ); + int c; while (1) { int index = 0; - c = getopt_long(argc, argv, "c:C:dehmrs:S:vV", long_options, &index); + c = getopt_long(argc, argv, "c:C:dehmrs:S:t:vV", long_options, &index); if (c == -1) { break; } @@ -199,6 +213,9 @@ main(int argc, char *argv[]) case 'S': primary_client = optarg; break; + case 't': + server.title_fmt = optarg; + break; case 'v': print_version(); exit(0); @@ -257,6 +274,10 @@ main(int argc, char *argv[]) increase_nofile_limit(); + if (string_null_or_empty(server.title_fmt)) { + server.title_fmt = "labwc - %o"; + } + server_init(); server_start(); diff --git a/src/menu/menu.c b/src/menu/menu.c index 396cb935..6b7d07b6 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -2,8 +2,10 @@ #define _POSIX_C_SOURCE 200809L #include "menu/menu.h" #include +#include #include #include +#include #include #include #include @@ -131,6 +133,92 @@ validate(void) } } +/* Read a single Unicode codepoint and convert it to lowercase */ +static uint32_t +read_unicode_char_lowercase(const char *first_byte, size_t *bytes_read) +{ + assert(bytes_read); + + if (string_null_or_empty(first_byte)) { + *bytes_read = 0; + return 0; + } + + gunichar codepoint = g_utf8_get_char_validated(first_byte, -1); + + bool partial_read = (codepoint == (gunichar)-2); + bool failed_read = (codepoint == (gunichar)-1); + if (partial_read || failed_read) { + /* Read only the first byte */ + *bytes_read = 1; + return (uint32_t)(unsigned char)first_byte[0]; + } + + *bytes_read = (size_t)(g_utf8_next_char(first_byte) - first_byte); + + return (uint32_t)g_unichar_tolower(codepoint); +} + +/* Retrieve the accelerator from an item label */ +static void +item_parse_accelerator(struct menuitem *item, const char *text) +{ + const char *accel_ptr = NULL; + char *underscore = strchr(text, '_'); + while (underscore) { + if (underscore[1] == '_') { + /* Ignore escaped underscores */ + underscore = strchr(underscore + 2, '_'); + } else if (underscore[1] != '\0') { + /* Found a valid accelerator */ + accel_ptr = underscore + 1; + break; + } else { + /* Ignore empty accelerator */ + break; + } + } + + size_t bytes_read = 0; + if (!accel_ptr) { + /* Default to the first char */ + item->text = xstrdup(text); + item->accelerator = read_unicode_char_lowercase(text, &bytes_read); + } else { + /* Set the accelerator and remove the preceding underscore */ + item->use_markup = true; + item->accelerator = read_unicode_char_lowercase(accel_ptr, &bytes_read); + item->text = strdup_printf("%.*s%.*s%s", + /* Prefix length + prefix */ + (int)(accel_ptr - 1 - text), text, + /* Accelerator (utf-8 byte) length + accelerator */ + (int)bytes_read, accel_ptr, + /* Remainder */ + accel_ptr + bytes_read); + } +} + +/* Remove underscores used for escaping other underscores from a string */ +static void +unescape_underscores(char *text) +{ + if (!text) { + return; + } + + char *src = text; + char *dst = text; + while (*src) { + if (*src == '_' && *(src + 1) == '_') { + *dst++ = '_'; + src += 2; + } else { + *dst++ = *src++; + } + } + *dst = '\0'; +} + static struct menuitem * item_create(struct menu *menu, const char *text, const char *icon_name, bool show_arrow) { @@ -142,8 +230,9 @@ item_create(struct menu *menu, const char *text, const char *icon_name, bool sho menuitem->parent = menu; menuitem->selectable = true; menuitem->type = LAB_MENU_ITEM; - menuitem->text = xstrdup(text); menuitem->arrow = show_arrow ? "›" : NULL; + item_parse_accelerator(menuitem, text); + unescape_underscores(menuitem->text); #if HAVE_LIBSFDO if (rc.menu_show_icons && !string_null_or_empty(icon_name)) { @@ -250,8 +339,8 @@ item_create_scene_for_state(struct menuitem *item, float *text_color, /* Create label */ struct scaled_font_buffer *label_buffer = scaled_font_buffer_create(tree); assert(label_buffer); - scaled_font_buffer_update(label_buffer, item->text, label_max_width, - &rc.font_menuitem, text_color, bg_color); + scaled_font_buffer_update_markup(label_buffer, item->text, label_max_width, + &rc.font_menuitem, text_color, bg_color, item->use_markup); /* Vertically center and left-align label */ int x = theme->menu_items_padding_x + icon_width; int y = (theme->menu_item_height - label_buffer->height) / 2; @@ -609,7 +698,7 @@ fill_menu(struct menu *parent, xmlNode *n) * * * - * + * * */ } else { @@ -1531,6 +1620,60 @@ menu_item_select_previous(void) menu_item_select(/* forward */ false); } +bool +menu_item_select_by_accelerator(uint32_t accelerator) +{ + struct menu *menu = get_selection_leaf(); + if (!menu || wl_list_empty(&menu->menuitems)) { + return false; + } + + bool needs_exec = false; + bool matched = false; + + struct menuitem *selection = menu->selection.item; + struct wl_list *start = selection ? &selection->link : &menu->menuitems; + struct wl_list *current = start; + struct menuitem *item = NULL; + struct menuitem *next_selection = NULL; + do { + current = current->next; + if (current == &menu->menuitems) { + /* Allow wrap around */ + continue; + } + item = wl_container_of(current, item, link); + if (item->accelerator == accelerator) { + if (!matched) { + /* Found first match */ + next_selection = item; + needs_exec = true; + matched = true; + } else { + /* + * Found another match, + * cycle selection instead of executing + */ + needs_exec = false; + break; + } + } + } while (current != start); + + if (!next_selection) { + return false; + } + + menu_process_item_selection(next_selection); + if (needs_exec && next_selection->submenu) { + /* Since we can't execute a submenu, enter it */ + needs_exec = false; + menu_submenu_enter(); + } + + return needs_exec; +} + bool menu_call_selected_actions(void) { diff --git a/src/meson.build b/src/meson.build index 40ec3170..05163cfa 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,7 @@ labwc_sources = files( 'seat.c', 'server.c', 'session-lock.c', + 'show-desktop.c', 'snap-constraints.c', 'snap.c', 'tearing.c', diff --git a/src/output-state.c b/src/output-state.c index 7da1108b..37ebde8a 100644 --- a/src/output-state.c +++ b/src/output-state.c @@ -24,6 +24,7 @@ output_state_init(struct output *output) backup_config, output->wlr_output); wlr_output_head_v1_state_apply(&backup_head->state, &output->pending); + wlr_output_configuration_v1_destroy(backup_config); } diff --git a/src/output.c b/src/output.c index aea35062..2eab8ec3 100644 --- a/src/output.c +++ b/src/output.c @@ -9,6 +9,8 @@ #define _POSIX_C_SOURCE 200809L #include "output.h" #include +#include +#include #include #include #include @@ -25,6 +27,7 @@ #include "common/macros.h" #include "common/mem.h" #include "common/scene-helpers.h" +#include "common/string-helpers.h" #include "config/rcxml.h" #include "labwc.h" #include "layers.h" @@ -51,6 +54,150 @@ #include #endif +static uint32_t output_formats_8bit[] = { + /* 32 bpp RGB with 8 bit component width */ + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + + /* 24 bpp RGB */ + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, +}; + +uint32_t output_formats_10bit[] = { + /* 32 bpp RGB with 10 bit component width */ + DRM_FORMAT_XRGB2101010, + DRM_FORMAT_XBGR2101010, + DRM_FORMAT_RGBX1010102, + DRM_FORMAT_BGRX1010102, + + DRM_FORMAT_ARGB2101010, + DRM_FORMAT_ABGR2101010, + DRM_FORMAT_RGBA1010102, + DRM_FORMAT_BGRA1010102, +}; + +static bool +output_set_render_format(struct output *output, uint32_t candidates[], size_t count) +{ + for (size_t i = 0; i < count; i++) { + wlr_output_state_set_render_format(&output->pending, candidates[i]); + if (wlr_output_test_state(output->wlr_output, &output->pending)) { + return true; + } + } + return false; +} + +static bool +output_format_in_candidates(uint32_t render_format, uint32_t candidates[], size_t count) +{ + for (size_t i = 0; i < count; i++) { + if (candidates[i] == render_format) { + return true; + } + } + return false; +} + +static enum render_bit_depth +bit_depth_from_format(uint32_t render_format) +{ + if (output_format_in_candidates(render_format, output_formats_10bit, + ARRAY_SIZE(output_formats_10bit))) { + return LAB_RENDER_BIT_DEPTH_10; + } else if (output_format_in_candidates(render_format, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) { + return LAB_RENDER_BIT_DEPTH_8; + } + return LAB_RENDER_BIT_DEPTH_DEFAULT; +} + +static enum render_bit_depth +get_config_render_bit_depth(void) +{ + return rc.target_render_depth; +} + +static bool +output_supports_hdr(const struct wlr_output *output, const char **unsupported_reason_ptr) +{ + const char *unsupported_reason = NULL; + if (!(output->supported_primaries & WLR_COLOR_NAMED_PRIMARIES_BT2020)) { + unsupported_reason = "BT2020 primaries not supported by output"; + } else if (!(output->supported_transfer_functions & + WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ)) { + unsupported_reason = "PQ transfer function not supported by output"; + } else if (!server.renderer->features.output_color_transform) { + unsupported_reason = "renderer doesn't support output color transforms"; + } + if (unsupported_reason_ptr) { + *unsupported_reason_ptr = unsupported_reason; + } + return !unsupported_reason; +} + +void +output_state_setup_hdr(struct output *output, bool silent) +{ + uint32_t render_format = output->wlr_output->render_format; + const char *unsupported_reason = NULL; + bool hdr_supported = output_supports_hdr(output->wlr_output, + &unsupported_reason); + bool hdr_succeeded = false; + + enum render_bit_depth render_bit_depth = get_config_render_bit_depth(); + if (render_bit_depth == LAB_RENDER_BIT_DEPTH_DEFAULT) { + render_bit_depth = bit_depth_from_format(render_format); + } + + if (!hdr_supported && render_bit_depth == LAB_RENDER_BIT_DEPTH_10) { + if (!silent) { + wlr_log(WLR_INFO, "Cannot enable HDR on output %s: %s", + output->wlr_output->name, unsupported_reason); + } + render_bit_depth = LAB_RENDER_BIT_DEPTH_8; + } + + if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10 && + bit_depth_from_format(render_format) == render_bit_depth) { + /* 10-bit was set successfully before, try to save some + * tests by reusing the format + */ + hdr_succeeded = true; + } else if (render_bit_depth == LAB_RENDER_BIT_DEPTH_10) { + hdr_succeeded = output_set_render_format(output, output_formats_10bit, + ARRAY_SIZE(output_formats_10bit)); + if (!hdr_succeeded) { + if (!silent) { + wlr_log(WLR_INFO, "No 10 bit color formats" + " supported, HDR disabled."); + } + if (!output_set_render_format(output, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit))) { + if (!silent) { + wlr_log(WLR_ERROR, "No 8 bit color formats" + " supported either!"); + } + } + } + } else { + if (!output_set_render_format(output, output_formats_8bit, + ARRAY_SIZE(output_formats_8bit)) && !silent) { + wlr_log(WLR_ERROR, "No 8 bit color formats supported!"); + } + } + + output_enable_hdr(output, &output->pending, hdr_succeeded, silent); +} + bool output_get_tearing_allowance(struct output *output) { @@ -371,10 +518,7 @@ configure_new_output(struct output *output) output_enable_adaptive_sync(output, true); } - output_state_commit(output); - - wlr_output_effective_resolution(wlr_output, - &output->usable_area.width, &output->usable_area.height); + output_state_setup_hdr(output, false); /* * Wait until wlr_output_layout_add_auto() returns before @@ -384,6 +528,19 @@ configure_new_output(struct output *output) server.pending_output_layout_change++; add_output_to_layout(output); server.pending_output_layout_change--; + + /* + * Commit the output this way instead, HDR needs a buffer, and + * this commit must be called after the output is added to the + * layout above. + */ + lab_wlr_scene_output_commit(output->scene_output, &output->pending); + + /* + * Collect the effective resolution after the final commit. + */ + wlr_output_effective_resolution(wlr_output, + &output->usable_area.width, &output->usable_area.height); } static uint64_t @@ -450,9 +607,10 @@ handle_new_output(struct wl_listener *listener, void *data) } if (wlr_output_is_wl(wlr_output)) { - char title[64]; - snprintf(title, sizeof(title), "%s - %s", "labwc", wlr_output->name); - wlr_wl_output_set_title(wlr_output, title); + GString *title = g_string_new(server.title_fmt); + g_string_replace(title, "%o", wlr_output->name, 0); + wlr_wl_output_set_title(wlr_output, title->str); + g_string_free(title, TRUE); wlr_wl_output_set_app_id(wlr_output, "labwc"); } @@ -653,6 +811,7 @@ output_config_apply(struct wlr_output_configuration_v1 *config) wlr_output_state_set_transform(os, head->state.transform); output_enable_adaptive_sync(output, head->state.adaptive_sync_enabled); + output_state_setup_hdr(output, false); } if (!output_state_commit(output)) { /* @@ -666,6 +825,19 @@ output_config_apply(struct wlr_output_configuration_v1 *config) break; } + if (output_enabled) { + /* + * The above commit was likely made without an image + * buffer attached. This will break applying HDR color + * transformation, since image descriptions must be + * committed with a buffer attached. Queue the HDR mode + * again if output is enabled, but make it silent, + * since it would have emitted messages already when + * called above. + */ + output_state_setup_hdr(output, true); + } + /* * Add or remove output from layout only if the commit went * through. Note that at startup, the output may have already @@ -1136,3 +1308,34 @@ output_set_has_fullscreen_view(struct output *output, bool has_fullscreen_view) output_enable_adaptive_sync(output, has_fullscreen_view); output_state_commit(output); } + +void +output_enable_hdr(struct output *output, struct wlr_output_state *os, + bool enabled, bool silent) +{ + if (enabled && !output_supports_hdr(output->wlr_output, NULL)) { + enabled = false; + } + + if (!enabled) { + if (output->wlr_output->supported_primaries != 0 || + output->wlr_output->supported_transfer_functions != 0) { + if (!silent) { + wlr_log(WLR_DEBUG, "Disabling HDR on output %s", + output->wlr_output->name); + } + wlr_output_state_set_image_description(os, NULL); + } + return; + } + + if (!silent) { + wlr_log(WLR_DEBUG, "Enabling HDR on output %s", + output->wlr_output->name); + } + const struct wlr_output_image_description image_desc = { + .primaries = WLR_COLOR_NAMED_PRIMARIES_BT2020, + .transfer_function = WLR_COLOR_TRANSFER_FUNCTION_ST2084_PQ, + }; + wlr_output_state_set_image_description(os, &image_desc); +} diff --git a/src/scaled-buffer/scaled-font-buffer.c b/src/scaled-buffer/scaled-font-buffer.c index bb93fc67..c748c00c 100644 --- a/src/scaled-buffer/scaled-font-buffer.c +++ b/src/scaled-buffer/scaled-font-buffer.c @@ -26,7 +26,7 @@ _create_buffer(struct scaled_buffer *scaled_buffer, double scale) /* Buffer gets free'd automatically along the backing wlr_buffer */ font_buffer_create(&buffer, self->max_width, self->height, self->text, - &self->font, self->color, bg_pattern, scale); + &self->font, self->color, bg_pattern, scale, self->use_markup); if (!buffer) { wlr_log(WLR_ERROR, "font_buffer_create() failed"); @@ -56,6 +56,7 @@ _equal(struct scaled_buffer *scaled_buffer_a, struct scaled_font_buffer *b = scaled_buffer_b->data; return str_equal(a->text, b->text) + && a->use_markup == b->use_markup && a->max_width == b->max_width && str_equal(a->font.name, b->font.name) && a->font.size == b->font.size @@ -104,10 +105,10 @@ scaled_font_buffer_create_for_titlebar(struct wlr_scene_tree *parent, return self; } -void -scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, +static void +_scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, int max_width, struct font *font, const float *color, - const float *bg_color) + const float *bg_color, bool use_markup) { assert(self); assert(text); @@ -120,6 +121,7 @@ scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, /* Update internal state */ self->text = xstrdup(text); + self->use_markup = use_markup; self->max_width = max_width; if (font->name) { self->font.name = xstrdup(font->name); @@ -139,3 +141,19 @@ scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, scaled_buffer_request_update(self->scaled_buffer, self->width, self->height); } + +void +scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, + int max_width, struct font *font, const float *color, + const float *bg_color) +{ + _scaled_font_buffer_update(self, text, max_width, font, color, bg_color, false); +} + +void +scaled_font_buffer_update_markup(struct scaled_font_buffer *self, const char *text, + int max_width, struct font *font, const float *color, + const float *bg_color, bool use_markup) +{ + _scaled_font_buffer_update(self, text, max_width, font, color, bg_color, use_markup); +} diff --git a/src/seat.c b/src/seat.c index 93d108be..fe85dc26 100644 --- a/src/seat.c +++ b/src/seat.c @@ -328,6 +328,16 @@ configure_libinput(struct wlr_input_device *wlr_input_device) libinput_device_config_scroll_set_method(libinput_dev, dc->scroll_method); } + libinput_device_config_scroll_set_button(libinput_dev, + libinput_device_config_scroll_get_default_button(libinput_dev)); + if (dc->scroll_button < 0) { + wlr_log(WLR_INFO, "scroll button not configured"); + } else { + wlr_log(WLR_INFO, "scroll button configured (%d)", + dc->scroll_button); + libinput_device_config_scroll_set_button(libinput_dev, dc->scroll_button); + } + libinput_device_config_send_events_set_mode(libinput_dev, libinput_device_config_send_events_get_default_mode(libinput_dev)); if ((dc->send_events_mode != LIBINPUT_CONFIG_SEND_EVENTS_ENABLED diff --git a/src/server.c b/src/server.c index a25ed300..31c08d37 100644 --- a/src/server.c +++ b/src/server.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #define _POSIX_C_SOURCE 200809L #include "config.h" +#include #include #include #include @@ -9,6 +10,8 @@ #include #include #include +#include +#include #include #include #include @@ -88,6 +91,12 @@ reload_config_and_theme(void) /* Avoid UAF when dialog client is used during reconfigure */ action_prompts_destroy(); + /* + * Cancel any pending auto-raise before reloading config in case the + * raiseOnFocusDelay option was disabled or changed. + */ + desktop_cancel_pending_auto_raise(); + scaled_buffer_invalidate_sharing(); rcxml_finish(); clearborder_cache(border_cache); @@ -214,39 +223,6 @@ handle_drm_lease_request(struct wl_listener *listener, void *data) } #endif -static bool -protocol_is_privileged(const struct wl_interface *iface) -{ - static const char * const rejected[] = { - "wp_drm_lease_device_v1", - "zwlr_gamma_control_manager_v1", - "zwlr_output_manager_v1", - "zwlr_output_power_manager_v1", - "zwp_input_method_manager_v2", - "zwlr_virtual_pointer_manager_v1", - "zwp_virtual_keyboard_manager_v1", - "zwlr_export_dmabuf_manager_v1", - "zwlr_screencopy_manager_v1", - "ext_data_control_manager_v1", - "zwlr_data_control_manager_v1", - "wp_security_context_manager_v1", - "ext_idle_notifier_v1", - "zwlr_foreign_toplevel_manager_v1", - "ext_foreign_toplevel_list_v1", - "ext_session_lock_manager_v1", - "zwlr_layer_shell_v1", - "ext_workspace_manager_v1", - "ext_image_copy_capture_manager_v1", - "ext_output_image_capture_source_manager_v1", - }; - for (size_t i = 0; i < ARRAY_SIZE(rejected); i++) { - if (!strcmp(iface->name, rejected[i])) { - return true; - } - } - return false; -} - static bool allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, const struct wl_interface *iface) @@ -289,6 +265,8 @@ allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, "xdg_wm_dialog_v1", /* plus */ "wp_alpha_modifier_v1", + "wp_color_manager_v1", + "wp_color_representation_manager_v1", "wp_linux_drm_syncobj_manager_v1", "zxdg_exporter_v1", "zxdg_exporter_v2", @@ -327,6 +305,11 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo } #endif + uint32_t iface_id = parse_privileged_interface(iface->name); + if (iface_id && !(iface_id & rc.allowed_interfaces)) { + return false; + } + /* Do not allow security_context_manager_v1 to clients with a security context attached */ const struct wlr_security_context_v1_state *security_context = wlr_security_context_manager_v1_lookup_client( @@ -342,11 +325,11 @@ server_global_filter(const struct wl_client *client, const struct wl_global *glo /* * TODO: The following call is basically useless right now * and should be replaced with - * assert(allow || protocol_is_privileged(iface)); + * assert(allow || iface_id); * This ensures that our lists are in sync with what * protocols labwc supports. */ - if (!allow && !protocol_is_privileged(iface)) { + if (!allow && !iface_id) { wlr_log(WLR_ERROR, "Blocking unknown protocol %s", iface->name); } else if (!allow) { wlr_log(WLR_DEBUG, "Blocking %s for security context %s->%s->%s", @@ -432,6 +415,39 @@ handle_renderer_lost(struct wl_listener *listener, void *data) wlr_renderer_destroy(old_renderer); } +static void +handle_toplevel_capture_source_destroy(struct wl_listener *listener, void *data) +{ + struct view *view = wl_container_of(listener, view, capture.on_capture_source_destroy); + assert(view->capture.source); + view->capture.source = NULL; + wl_list_remove(&listener->link); + wl_list_init(&listener->link); +} + +static void +handle_toplevel_capture_request(struct wl_listener *listener, void *data) +{ + struct wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request *request = data; + struct view *view = request->toplevel_handle->data; + assert(view); + wlr_log(WLR_INFO, "Capturing toplevel %s", view->app_id); + + if (!view->capture.source) { + view->capture.source = wlr_ext_image_capture_source_v1_create_with_scene_node( + &view->capture.scene->tree.node, server.wl_event_loop, + server.allocator, server.renderer); + assert(view->capture.source); + + view->capture.on_capture_source_destroy.notify = + handle_toplevel_capture_source_destroy; + wl_signal_add(&view->capture.source->events.destroy, + &view->capture.on_capture_source_destroy); + } + wlr_ext_foreign_toplevel_image_capture_source_manager_v1_request_accept( + request, view->capture.source); +} + void server_init(void) { @@ -596,6 +612,47 @@ server_init(void) * | output->layer_tree[0] | background layer surfaces (e.g. swaybg) */ + if (server.renderer->features.input_color_transform) { + const enum wp_color_manager_v1_render_intent render_intents[] = { + WP_COLOR_MANAGER_V1_RENDER_INTENT_PERCEPTUAL, + }; + size_t transfer_functions_len = 0; + enum wp_color_manager_v1_transfer_function *transfer_functions = + wlr_color_manager_v1_transfer_function_list_from_renderer( + server.renderer, &transfer_functions_len); + + size_t primaries_len = 0; + enum wp_color_manager_v1_primaries *primaries = + wlr_color_manager_v1_primaries_list_from_renderer( + server.renderer, &primaries_len); + + struct wlr_color_manager_v1 *cm = wlr_color_manager_v1_create( + server.wl_display, 2, &(struct wlr_color_manager_v1_options){ + .features = { + .parametric = true, + .set_mastering_display_primaries = true, + }, + .render_intents = render_intents, + .render_intents_len = ARRAY_SIZE(render_intents), + .transfer_functions = transfer_functions, + .transfer_functions_len = transfer_functions_len, + .primaries = primaries, + .primaries_len = primaries_len, + }); + + free(transfer_functions); + free(primaries); + + if (cm) { + wlr_scene_set_color_manager_v1(server.scene, cm); + } else { + wlr_log(WLR_ERROR, "unable to create color manager"); + } + } + + wlr_color_representation_manager_v1_create_with_renderer( + server.wl_display, 1, server.renderer); + server.workspace_tree = lab_wlr_scene_tree_create(&server.scene->tree); server.xdg_popup_tree = lab_wlr_scene_tree_create(&server.scene->tree); #if HAVE_XWAYLAND @@ -670,6 +727,19 @@ server_init(void) wlr_screencopy_manager_v1_create(server.wl_display); wlr_ext_image_copy_capture_manager_v1_create(server.wl_display, 1); wlr_ext_output_image_capture_source_manager_v1_create(server.wl_display, 1); + + server.toplevel_capture.manager = + wlr_ext_foreign_toplevel_image_capture_source_manager_v1_create( + server.wl_display, 1); + if (server.toplevel_capture.manager) { + server.toplevel_capture.on.new_request.notify = handle_toplevel_capture_request; + wl_signal_add(&server.toplevel_capture.manager->events.new_request, + &server.toplevel_capture.on.new_request); + } else { + /* Allow safe removal on shutdown */ + wl_list_init(&server.toplevel_capture.on.new_request.link); + } + wlr_data_control_manager_v1_create(server.wl_display); wlr_ext_data_control_manager_v1_create(server.wl_display, LAB_EXT_DATA_CONTROL_VERSION); @@ -804,6 +874,8 @@ server_finish(void) server.drm_lease_request.notify = NULL; } + wl_list_remove(&server.toplevel_capture.on.new_request.link); + wlr_backend_destroy(server.backend); wlr_allocator_destroy(server.allocator); diff --git a/src/show-desktop.c b/src/show-desktop.c new file mode 100644 index 00000000..efddf2de --- /dev/null +++ b/src/show-desktop.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "show-desktop.h" +#include +#include "common/array.h" +#include "config/types.h" +#include "labwc.h" +#include "view.h" + +static bool is_showing_desktop; + +static void +minimize_views(struct wl_array *views, bool minimize) +{ + struct view **view; + wl_array_for_each_reverse(view, views) { + view_minimize(*view, minimize); + } +} + +static void +show(void) +{ + static struct wl_array views; + wl_array_init(&views); + + /* Build array first as minimize changes server.views */ + struct view *view; + for_each_view(view, &server.views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { + if (view->minimized) { + continue; + } + view->was_minimized_by_show_desktop_action = true; + array_add(&views, view); + } + minimize_views(&views, true); + is_showing_desktop = true; + + wl_array_release(&views); +} + +static void +restore(void) +{ + static struct wl_array views; + wl_array_init(&views); + + struct view *view; + for_each_view(view, &server.views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) { + if (view->was_minimized_by_show_desktop_action) { + array_add(&views, view); + } + } + minimize_views(&views, false); + show_desktop_reset(); + + wl_array_release(&views); +} + +void +show_desktop_toggle(void) +{ + if (is_showing_desktop) { + restore(); + } else { + show(); + } +} + +void +show_desktop_reset(void) +{ + is_showing_desktop = false; + + struct view *view; + for_each_view(view, &server.views, LAB_VIEW_CRITERIA_NONE) { + view->was_minimized_by_show_desktop_action = false; + } +} diff --git a/src/theme.c b/src/theme.c index 3ae44177..2777fa49 100644 --- a/src/theme.c +++ b/src/theme.c @@ -1103,7 +1103,7 @@ entry(struct theme *theme, const char *key, const char *value) parse_color(value, theme->window[SSD_ACTIVE].button_hover_border_color); } - /* botton hover overlay */ + /* button hover overlay */ if (match_glob(key, "window.button.hover.bg.color")) { parse_color(value, theme->window_button_hover_bg_color); } diff --git a/src/view.c b/src/view.c index 1d43fcfa..21005f20 100644 --- a/src/view.c +++ b/src/view.c @@ -80,7 +80,7 @@ struct view_query * view_query_create(void) { struct view_query *query = znew(*query); - /* Must be synced with view_matches_criteria() in window-rules.c */ + /* Must be synced with view_matches_rule() in window-rules.c */ query->window_type = LAB_WINDOW_TYPE_INVALID; query->maximized = VIEW_AXIS_INVALID; query->decoration = LAB_SSD_MODE_INVALID; @@ -263,8 +263,8 @@ view_get_root(struct view *view) return view; } -static bool -matches_criteria(struct view *view, enum lab_view_criteria criteria) +bool +view_matches_criteria(struct view *view, enum lab_view_criteria criteria) { if (!view_is_focusable(view)) { return false; @@ -316,7 +316,7 @@ view_next(struct wl_list *head, struct view *view, enum lab_view_criteria criter for (elm = elm->next; elm != head; elm = elm->next) { view = wl_container_of(elm, view, link); - if (matches_criteria(view, criteria)) { + if (view_matches_criteria(view, criteria)) { return view; } } @@ -332,7 +332,7 @@ view_prev(struct wl_list *head, struct view *view, enum lab_view_criteria criter for (elm = elm->prev; elm != head; elm = elm->prev) { view = wl_container_of(elm, view, link); - if (matches_criteria(view, criteria)) { + if (view_matches_criteria(view, criteria)) { return view; } } @@ -2482,6 +2482,10 @@ view_init(struct view *view) view->title = xstrdup(""); view->app_id = xstrdup(""); + + view->capture.scene = wlr_scene_create(); + view->capture.scene->restack_xwayland_surfaces = false; + wl_list_init(&view->capture.on_capture_source_destroy.link); } void @@ -2503,6 +2507,9 @@ view_destroy(struct view *view) wl_list_remove(&view->request_fullscreen.link); wl_list_remove(&view->set_title.link); wl_list_remove(&view->destroy.link); + wl_list_remove(&view->capture.on_capture_source_destroy.link); + + wlr_scene_node_destroy(&view->capture.scene->tree.node); if (view->foreign_toplevel) { foreign_toplevel_destroy(view->foreign_toplevel); @@ -2521,6 +2528,10 @@ view_destroy(struct view *view) server.active_view = NULL; } + if (server.pending_auto_raise_view == view) { + desktop_cancel_pending_auto_raise(); + } + if (server.session_lock_manager->last_active_view == view) { server.session_lock_manager->last_active_view = NULL; } diff --git a/src/window-rules.c b/src/window-rules.c index 3911be7d..4118ae32 100644 --- a/src/window-rules.c +++ b/src/window-rules.c @@ -24,7 +24,7 @@ other_instances_exist(struct view *self, struct view_query *query) } static bool -view_matches_criteria(struct window_rule *rule, struct view *view) +view_matches_rule(struct window_rule *rule, struct view *view) { struct view_query query = { .identifier = rule->identifier, @@ -52,7 +52,7 @@ window_rules_apply(struct view *view, enum window_rule_event event) if (rule->event != event) { continue; } - if (view_matches_criteria(rule, view)) { + if (view_matches_rule(rule, view)) { actions_run(view, &rule->actions, NULL); } } @@ -69,8 +69,8 @@ window_rules_get_property(struct view *view, const char *property) * for foot's "serverDecoration" property to be "default". * * - * - * + * + * * */ struct window_rule *rule; @@ -81,7 +81,7 @@ window_rules_get_property(struct view *view, const char *property) * attribute would still return here if that property was asked * for. */ - if (view_matches_criteria(rule, view)) { + if (view_matches_rule(rule, view)) { if (rule->server_decoration && !strcasecmp(property, "serverDecoration")) { return rule->server_decoration; diff --git a/src/workspaces.c b/src/workspaces.c index a87a1d3b..dc2d2a78 100644 --- a/src/workspaces.c +++ b/src/workspaces.c @@ -22,6 +22,7 @@ #include "common/borderset.h" #include "labwc.h" #include "output.h" +#include "show-desktop.h" #include "theme.h" #include "view.h" @@ -552,6 +553,8 @@ workspaces_switch_to(struct workspace *target, bool update_focus) desktop_update_top_layer_visibility(); wlr_ext_workspace_handle_v1_set_active(target->ext_workspace, true); + + show_desktop_reset(); } void diff --git a/src/xdg-popup.c b/src/xdg-popup.c index 04f82f9e..7ca9ab01 100644 --- a/src/xdg-popup.c +++ b/src/xdg-popup.c @@ -43,10 +43,13 @@ popup_unconstrain(struct xdg_popup *popup) * than zero, typically with Qt apps. We therefore clamp it to avoid for * example the 'File' menu of a maximized window to end up on an another * output. + * Also some apps open the menu exactly at the right border when maximized, + * causing popup_box->x (or y?) to be in the next output. We subtract one + * inside MAX to avoid the problem mentioned above. */ struct wlr_box *popup_box = &popup->wlr_popup->scheduled.geometry; - struct output *output = output_nearest_to(parent_lx + MAX(popup_box->x, 0), - parent_ly + MAX(popup_box->y, 0)); + struct output *output = output_nearest_to(parent_lx + MAX(popup_box->x - 1, 0), + parent_ly + MAX(popup_box->y - 1, 0)); struct wlr_box usable = output_usable_area_in_layout_coords(output); /* Get offset of toplevel window from its surface */ @@ -166,4 +169,6 @@ xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup) node_descriptor_create(wlr_popup->base->surface->data, LAB_NODE_XDG_POPUP, view, /*data*/ NULL); + + wlr_scene_xdg_surface_create(&view->capture.scene->tree, wlr_popup->base); } diff --git a/src/xdg.c b/src/xdg.c index 5092ab7a..03035d63 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -30,6 +30,8 @@ #define LAB_XDG_SHELL_VERSION 6 #define CONFIGURE_TIMEOUT_MS 100 +static struct view *xdg_toplevel_view_get_root(struct view *view); + static struct xdg_toplevel_view * xdg_toplevel_view_from_view(struct view *view) { @@ -121,7 +123,7 @@ set_fullscreen_from_request(struct view *view, view_set_fullscreen(view, requested->fullscreen); } -/* Called from commit handler and updates view->pending.x/y directly */ +/* Called from map/commit handler and updates view->pending.x/y directly */ static void set_initial_position(struct view *view) { @@ -263,11 +265,10 @@ handle_commit(struct wl_listener *listener, void *data) bool update_required = false; /* - * The pending size will be empty in two cases: - * (1) when the view is first mapped - * (2) when leaving fullscreen or un-maximizing, if the view - * was initially fullscreen/maximized and the natural - * geometry isn't known yet + * If we didn't know the natural size when leaving fullscreen or + * unmaximizing, then the pending size will be 0x0. In this case, + * the pending x/y is also unset and we still need to position + * the window. */ if (wlr_box_empty(&view->pending) && !wlr_box_empty(&size)) { view->pending.width = size.width; @@ -463,6 +464,7 @@ handle_destroy(struct wl_listener *listener, void *data) /* Remove xdg-shell view specific listeners */ wl_list_remove(&xdg_toplevel_view->set_app_id.link); wl_list_remove(&xdg_toplevel_view->request_show_window_menu.link); + wl_list_remove(&xdg_toplevel_view->set_parent.link); wl_list_remove(&xdg_toplevel_view->new_popup.link); wl_list_remove(&view->commit.link); @@ -566,6 +568,23 @@ handle_request_show_window_menu(struct wl_listener *listener, void *data) menu_open_root(menu, cursor->x, cursor->y); } +static void +handle_set_parent(struct wl_listener *listener, void *data) +{ + struct xdg_toplevel_view *xdg_toplevel_view = wl_container_of( + listener, xdg_toplevel_view, set_parent); + struct view *view = &xdg_toplevel_view->base; + struct view *view_root = xdg_toplevel_view_get_root(view); + if (view_root == view) { + return; + } + struct wlr_scene_node *node, *tmp; + wl_list_for_each_safe(node, tmp, &view->capture.scene->tree.children, link) { + wlr_log(WLR_DEBUG, "moving capture scene node to view_root"); + wlr_scene_node_reparent(node, &view_root->capture.scene->tree); + } +} + static void handle_set_title(struct wl_listener *listener, void *data) { @@ -821,6 +840,29 @@ handle_map(struct wl_listener *listener, void *data) } else { view_set_ssd_mode(view, LAB_SSD_MODE_NONE); } + + /* + * Set initial "pending" dimensions. "Current" + * dimensions remain zero until handle_commit(). + * Note: this must be done before view_impl_map() + * for window rules to work correctly. + */ + if (wlr_box_empty(&view->pending)) { + struct wlr_xdg_surface *xdg_surface = + xdg_surface_from_view(view); + view->pending.width = xdg_surface->geometry.width; + view->pending.height = xdg_surface->geometry.height; + set_initial_position(view); + } + + /* + * Set initial "current" position directly before + * calling view_moved() to reduce flicker + */ + view->current.x = view->pending.x; + view->current.y = view->pending.y; + + view_moved(view); } view_impl_map(view); @@ -1024,6 +1066,9 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data) mappable_connect(&view->mappable, xdg_surface->surface, handle_map, handle_unmap); + struct view *root_view = xdg_toplevel_view_get_root(view); + wlr_scene_xdg_surface_create(&root_view->capture.scene->tree, xdg_surface); + struct wlr_xdg_toplevel *toplevel = xdg_surface->toplevel; CONNECT_SIGNAL(toplevel, view, destroy); CONNECT_SIGNAL(toplevel, view, request_move); @@ -1037,6 +1082,7 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data) /* Events specific to XDG toplevel views */ CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_app_id); CONNECT_SIGNAL(toplevel, xdg_toplevel_view, request_show_window_menu); + CONNECT_SIGNAL(toplevel, xdg_toplevel_view, set_parent); CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup); wl_list_insert(&server.views, &view->link); diff --git a/src/xwayland.c b/src/xwayland.c index 967364c5..5838d412 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -783,6 +783,7 @@ handle_map(struct wl_listener *listener, void *data) view->content_tree = wlr_scene_subsurface_tree_create( view->scene_tree, view->surface); die_if_null(view->content_tree); + wlr_scene_subsurface_tree_create(&view->capture.scene->tree, view->surface); } wlr_scene_node_set_enabled(&view->content_tree->node, !view->shaded);