diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77b8bf01..f926354d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -281,14 +281,18 @@ jobs: LABWC_RUNS=2 scripts/ci/smoke-test.sh build-gcc-gdb ' | $TARGET - - name: Build with gcc - catch no font installed case - if: matrix.name == 'Void-musl' - run: | - echo ' - cd "$GITHUB_WORKSPACE" - xbps-remove -y dejavu-fonts-ttf - export CC=gcc - meson setup build-gcc-nofont -Dxwayland=enabled --werror - meson compile -C build-gcc-nofont - LABWC_EXPECT_RETURNCODE=1 scripts/ci/smoke-test.sh build-gcc-nofont - ' | $TARGET + # Void made the foot package depend on the font. + # As this test uses both and the feature itself + # seems to work well lets just skip it for now + # + #- name: Build with gcc - catch no font installed case + # if: matrix.name == 'Void-musl' + # run: | + # echo ' + # cd "$GITHUB_WORKSPACE" + # xbps-remove -y dejavu-fonts-ttf + # export CC=gcc + # meson setup build-gcc-nofont -Dxwayland=enabled --werror + # meson compile -C build-gcc-nofont + # LABWC_EXPECT_RETURNCODE=1 scripts/ci/smoke-test.sh build-gcc-nofont + # ' | $TARGET diff --git a/.github/workflows/irc.yml b/.github/workflows/irc.yml.disabled similarity index 100% rename from .github/workflows/irc.yml rename to .github/workflows/irc.yml.disabled diff --git a/NEWS.md b/NEWS.md index 4a3bbc8f..f64845b4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog] | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| -| 2025-11-15 | [unreleased] | 0.19.2 | 28825 | +| 2025-12-19 | [0.9.3] | 0.19.2 | 28968 | | 2025-10-10 | [0.9.2] | 0.19.1 | 28818 | | 2025-08-02 | [0.9.1] | 0.19.0 | 28605 | | 2025-07-11 | [0.9.0] | 0.19.0 | 28586 | @@ -40,6 +40,7 @@ The format is based on [Keep a Changelog] | 2021-03-05 | [0.1.0] | 0.12.0 | 4627 | [unreleased]: NEWS.md#unreleased +[0.9.3]: NEWS.md#093---2025-12-19 [0.9.2]: NEWS.md#092---2025-10-10 [0.9.1]: NEWS.md#091---2025-08-02 [0.9.0]: NEWS.md#090---2025-07-11 @@ -89,8 +90,6 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: with the environment variable `LABWC_FALLBACK_OUTPUT=NOOP-fallback` to temporarily create a fallback-output when the last physical display disconnects. [#2914] [#2939] [wlroots-4878] [gtk-8792] -- Due to a single-pixel protocol issue, `waylock` and `chayang` do not work. - This will be fixed in `wlroots-0.19.1`. [#2943] [wlroots-5098] - Menu item can no longer be activated in any Gtk applications with a single press-drag-release mouse action. For context: This is due to ambiguity in the specifications and contrary implementations. For example, Gtk applications are @@ -98,26 +97,53 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: other compositors like Weston, Mutter and labwc. It has been decided not to block the release due to this regression as it is an eco-system wide issue that has existed for a long time. [#2787] -- VR headset support is disabled when compiled with wlroots `0.19.0` to work - around a bug on the wlroots side which is expected to be fixed in wlroots - `0.19.1` [#2887] - -With wlroots compiled with libwayland (>= 1.24.0), there is an invisible margin -preventing pointer focus on some layer-shell surfaces including those created by -Gtk. In simple words, this is because libwayland now rounds floats a bit -differently [#3099]. There is a pending fix [wlroots-5159]. +- It is strongly recommended to use at least wlroots 0.19.1 [#2943] + [wlroots-5098] [#2887] [wlroots-4878]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4878 [wlroots-5098]:https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5098 -[wlroots-5159]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5159 [gtk-8792]: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8792 ## unreleased [unreleased-commits] +## 0.9.3 - 2025-12-19 + +[0.9.3-commits] + +This release contains a good amount of bug-fixes, code simplification and +small usability improvements. + +With the stability that comes with having tracked `wlroots 0.19` for a decent +length of time, this feels like the best version of labwc so far. + +In terms of new features, it is worth drawing attention to the click support in +the window-switcher on-screen-display by @tokyo4j [#3186] which has frequently +been requested by users. + +As a general note to users, we discourage the use of empty strings in the +`rc.xml` configuration file, for example ``. There +are only a few areas left where empty string are ignored (like under +``) but the intent for future releases is to consistently read empty +strings as empty strings. As a preparation, this release has added some warnings +for empty strings that are currently ignored, so that users can take action. +Also, the example `docs/rc.xml.all` has been updated to remove poor examples in +this regard. + +A big thank you to all involved in this release. + ### Added +- 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 + (including ``) on output edges with and without adjacent outputs. + @elviosak [#3241] +- Add `direction` option to `Resize` action supporting the values `up-left`, + `up`, `up-right`, `left`, `right`, `down-left`, `down`, `down-right`. This + mirrors Fluxbox's `StartResizing [corner]` behavior. @mbroemme [#3239] - Allow the use of the `sendEventsMode` configuration option on keyboards in order to disable keyboard input. @cillian64 [#3208] @@ -131,8 +157,8 @@ differently [#3099]. There is a pending fix [wlroots-5159]. - Support the following new `` configuration options: - `` to specify the label text in each item in the thumbnail style window-switcher. @elviosak [#3187] - - `` to specify which monitor(s) to show - the OSD(s) on. @dntxi [#3201] + - `` to specify which monitor(s) to show + the OSD(s) on. @dntxi [#3201] [#3248] - Support window-switcher OSD item click to focus window @tokyo4j [#3186] - With the window-switcher custom field state specifiers 's' and 'S', show 's' for shaded window @domo141 [#2895] @@ -148,6 +174,28 @@ differently [#3099]. There is a pending fix [wlroots-5159]. ### Fixed +- Handle desktop files with dots in their names better @Consolatis [#3267] +- Do not synthesize cursor relative motion events from absolute events to fix a + couple of problems: Firstly to avoid unexpectedly large relative motion deltas + with multiple input devices or in nested/VM scenarios, and secondly to fix + erratic mouse behavior in applications that use relative events whilst locking + with pointer constraints. @jlindgren90 [#3251] +- Allow cursor movement until entering constraint surface, to fix an issue where + the cursor would get stuck (immovable) outside the window of a Wine/Wayland + game, if it was already outside when the game started (which is common with + 4:3 games on a 16:9 screen). @jlindgren90 [#3252] +- Flush XCB connection to mitigate race between Raise and input. @jlindgren90 + [#3249] +- Fix disappearing XWayland popups with some (less commonly used) clients like + Imagemagick's `display` command, `xshogi`, `xedit` and `xfig` caused by + too many surface-pings. @jlindgren90 [#3152] [#3246] +- Center small fullscreen xdg-shell windows and add black background fill. This + increases spec compliance and improves the user experience with games like + SWAT4, Quake III and Splinter Cell 3. @jlindgren90 [#3233] +- When followMouse=yes, update focus on cursor entering SSD rather than just the + 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 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 @@ -181,6 +229,16 @@ differently [#3099]. There is a pending fix [wlroots-5159]. ### Changed +- `` is deprecated. Use `` + instead. @elviosak [#3241] +- When cycling through windows (typically with Alt-Tab) there are two minor + user-visible changes. For most users these will not be noticeable, but are + mentioned here for completeness. + - The initially selected window will now be the one that previously had + keyboard focus when cycling commenced rather than the second topmost one. + @tokyo4j [#3236] + - Windows that are spawned whilst cycling can no longer be cycled through. The + intent is to fix this in future releases. @tokyo4j [#3236] - Refactor window switcher configuration to put attributes `show` and `style` under `` rather than directly under ``. The old configuration syntax will remain supported for at least one release. @@ -2461,7 +2519,8 @@ 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.2...HEAD +[unreleased-commits]: https://github.com/labwc/labwc/compare/0.9.3...HEAD +[0.9.3-commits]: https://github.com/labwc/labwc/compare/0.9.2...0.9.3 [0.9.2-commits]: https://github.com/labwc/labwc/compare/0.9.1...0.9.2 [0.9.1-commits]: https://github.com/labwc/labwc/compare/0.9.0...0.9.1 [0.9.0-commits]: https://github.com/labwc/labwc/compare/0.8.4...0.9.0 @@ -2938,6 +2997,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3145]: https://github.com/labwc/labwc/pull/3145 [#3146]: https://github.com/labwc/labwc/pull/3146 [#3148]: https://github.com/labwc/labwc/pull/3148 +[#3152]: https://github.com/labwc/labwc/pull/3152 [#3153]: https://github.com/labwc/labwc/pull/3153 [#3157]: https://github.com/labwc/labwc/pull/3157 [#3158]: https://github.com/labwc/labwc/pull/3158 @@ -2951,3 +3011,16 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3199]: https://github.com/labwc/labwc/pull/3199 [#3201]: https://github.com/labwc/labwc/pull/3201 [#3208]: https://github.com/labwc/labwc/pull/3208 +[#3211]: https://github.com/labwc/labwc/pull/3211 +[#3217]: https://github.com/labwc/labwc/pull/3217 +[#3229]: https://github.com/labwc/labwc/pull/3229 +[#3233]: https://github.com/labwc/labwc/pull/3233 +[#3236]: https://github.com/labwc/labwc/pull/3236 +[#3239]: https://github.com/labwc/labwc/pull/3239 +[#3241]: https://github.com/labwc/labwc/pull/3241 +[#3246]: https://github.com/labwc/labwc/pull/3246 +[#3248]: https://github.com/labwc/labwc/pull/3248 +[#3249]: https://github.com/labwc/labwc/pull/3249 +[#3251]: https://github.com/labwc/labwc/pull/3251 +[#3252]: https://github.com/labwc/labwc/pull/3252 +[#3267]: https://github.com/labwc/labwc/pull/3267 diff --git a/docs/environment b/docs/environment index f9c59b17..abc9c8b4 100644 --- a/docs/environment +++ b/docs/environment @@ -22,6 +22,13 @@ # XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle # XKB_DEFAULT_OPTIONS=grp:shift_caps_toggle +## GTK4 started to require input methods like fcitx5 or ibus to handle +## simple compose sequences. If you do not use input methods, uncomment +## the following line to force GTK4 internal composing. For further +## information see https://labwc.github.io/integration.html#gtk +## +# GTK_IM_MODULE=simple + ## ## Set cursor theme and size. Find system icons themes with: ## `find /usr/share/icons/ -type d -name "cursors"` diff --git a/docs/labnag.1.scd b/docs/labnag.1.scd index a2a36c10..6d4b959f 100644 --- a/docs/labnag.1.scd +++ b/docs/labnag.1.scd @@ -140,4 +140,3 @@ labnag \\ --button-border-size 2\\ -t 60 ``` - diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index 96e44cb6..fe468cd1 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -125,13 +125,25 @@ Actions are used in menus and keyboard/mouse bindings. Resize and move the active window back to its untiled or unmaximized position if it had been maximized or tiled to a direction or region. -**++ -** +**++ +** Cycle focus to next/previous window, respectively. - Default keybind for NextWindow is Alt-Tab. + 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. - The arrow keys are used to move forwards/backwards while cycling. + *workspace* [all|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". + + *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". ** Re-load configuration and theme files. @@ -379,7 +391,7 @@ Actions are used in menus and keyboard/mouse bindings. *x* [center|value] Specifies the horizontal warp position within the target area. "center": Moves the cursor to the horizontal center of the target area. Positive or negative integers warp the cursor to a position - offset by the specified number of pixels from the left or right edge of + offset by the specified number of pixels from the left or right edge of the target area, respectively. Default is "center" *y* [center|value] Equivalent for the vertical warp position within the diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index 4cb89cd5..a1cedb20 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -26,7 +26,7 @@ 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 -the --merge-config option, the search order is reserved, but every configuration +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 alternative. @@ -339,7 +339,7 @@ this is for compatibility with Openbox. ## WINDOW SWITCHER ``` - + @@ -349,31 +349,28 @@ this is for compatibility with Openbox. ``` -** +** *preview* [yes|no] Preview the contents of the selected window when switching between windows. Default is yes. *outlines* [yes|no] Draw an outline around the selected window when switching between windows. Default is yes. - *allWorkspaces* [yes|no] Show windows regardless of what workspace - they are on. Default no (that is only windows on the current workspace - are shown). - *unshade* [yes|no] Temporarily unshade windows when switching between them and permanently unshade on the final selection. Default is yes. - *order* [focus|age] The order in which windows are cycled. *focus* cycles by - recent focus history, starting with the previously focused window. *age* cycles - by creation/open order, a stable taskbar-style ordering that doesn’t change on - focus. Default is *focus*. + *order* [focus|age] The order in which windows are cycled. *focus* + cycles by recent focus history, starting with the previously focused + window. *age* cycles by creation/open order, a stable taskbar-style + ordering that doesn’t change on focus. Default is *focus*. ** *show* [yes|no] Draw the OnScreenDisplay when switching between windows. Default is yes. *style* [classic|thumbnail] Configures the style of the OSD. - "classic" displays window information like icons and titles in a vertical list. + "classic" displays window information like icons and titles in a + vertical list. "thumbnail" shows window thumbnail, icon and title in grids. *output* [all|focused|cursor] Configures which monitor(s) show the OSD. @@ -382,9 +379,9 @@ this is for compatibility with Openbox. "cursor" displays the OSD on the monitor containing the mouse pointer. Default is "all". - *thumbnailLabelFormat* Format to be used for the thumbnail label according to *custom* - field below, only applied when using **. - Default is "%T". + *thumbnailLabelFormat* Format to be used for the thumbnail label + according to *custom* field below, only applied when using + **. Default is "%T". ** Define window switcher fields when using **. @@ -506,13 +503,13 @@ extending outward from the snapped edge. **++ **++ ** - If an interactive move ends with the cursor within *inner* or *outer* pixels - of an output edge, the window is snapped to the edge. *inner* edges are edges - with an adjacent output and *outer* edges are edges without an adjacent output. - If it's also within ** pixels of an output corner, the window is - snapped to the corner instead. - If *inner* and *outer* is 0, snapping is disabled. - Default is 10 for ** and **, and 50 for **. + If an interactive move ends with the cursor within *inner* or *outer* + pixels of an output edge, the window is snapped to the edge. *inner* + edges are edges with an adjacent output and *outer* edges are edges + without an adjacent output. If it's also within ** pixels + of an output corner, the window is snapped to the corner instead. + If *inner* and *outer* is 0, snapping is disabled. Default is 10 for + ** and **, and 50 for **. ** [yes|no] Show an overlay when snapping to a window to an edge. Default is yes. @@ -533,8 +530,8 @@ extending outward from the snapped edge. ** [always|region|edge|never] Snapping windows can trigger corresponding tiling events for native Wayland clients. Clients may use these events to alter their rendering - based on knowledge that some edges of the window are confined to edges of - a snapping region or output. For example, rounded corners may become + based on knowledge that some edges of the window are confined to edges + of a snapping region or output. For example, rounded corners may become square when tiled, or media players may letter-box or pillar-box video rather than imposing rigid aspect ratios on windows that will violate the constraints of window snapping. @@ -574,6 +571,11 @@ extending outward from the snapped edge. is 1. The number attribute is optional. If the number attribute is specified, names.name is not required. +** + Define the initial starting workspace. This must match one of the names + defined in or must be an index equal to or lower than . + If not set, the first workspace is used. + ** Define the timeout after which to hide the workspace OSD. A setting of 0 disables the OSD. Default is 1000 ms. @@ -624,15 +626,16 @@ extending outward from the snapped edge. ** [titlebar|none] Specify how server side decorations are shown for maximized windows. - *titlebar* shows titlebar above a maximized window. *none* shows no server - side decorations around a maximized window. Default is titlebar. + *titlebar* shows titlebar above a maximized window. *none* shows no + server side decorations around a maximized window. Default is titlebar. ** [yes|no] Should drop-shadows be rendered behind windows. Default is no. ** [yes|no] Should drop-shadows be rendered behind tiled windows. This won't take - effect if is smaller than window.active.shadow.size in theme. + effect if is smaller than window.active.shadow.size in + theme. Default is no. @@ -1048,7 +1051,7 @@ Note: To rotate touch events with output rotation, use the libinput ** Pen and pad buttons behave like regular mouse buttons.With mouse - emulation set to "no", which is the default, and if not specified + emulation set to "no", which is the default, and if not specified otherwise, the first pen button is mapped to the right mouse button, the second pen button to the middle mouse button and a third pen button is mapped to the side mouse button. @@ -1067,10 +1070,10 @@ Note: To rotate touch events with output rotation, use the libinput When using mouse emulation, all pen buttons emulate regular mouse buttons. The tip, stylus and pad buttons can be mapped to all - available mouse buttons. If not specified otherwise, the tip is + available mouse buttons. If not specified otherwise, the tip is mapped to left mouse click, the first pen button (Stylus) is mapped to right mouse button click and the second pen button (Stylus2) - emulates a middle mouse button click. Buttons of a tablet tool mouse + emulates a middle mouse button click. Buttons of a tablet tool mouse are by default mapped to their (regular) mouse counterparts. Supported map *buttons* for mouse emulation are: @@ -1180,11 +1183,12 @@ Note: To rotate touch events with output rotation, use the libinput a tap immediately followed by a finger down as the start of a drag. ** [yes|no|timeout] - Enable or disable drag lock for this category. Drag lock ignores a temporary - release of a finger during tap-and-dragging. + Enable or disable drag lock for this category. Drag lock ignores a + temporary release of a finger during tap-and-dragging. - *timeout* also enables drag lock, but with a timeout: if your fingers are - released for a certain amount of time, the drag gesture is cancelled. + *timeout* also enables drag lock, but with a timeout: if your fingers + are released for a certain amount of time, the drag gesture is + cancelled. In libinput < 1.27, the behavior of *yes* is equivalent to *timeout*. ** [yes|no|3|4] diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index 8097615b..653f3b1e 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -308,8 +308,8 @@ all are supported. See below for details. *osd.window-switcher.style-classic.width* - Width of window switcher in pixels. Width can also be a percentage of the - monitor width by adding '%' as suffix (e.g. 70%). Default is 600. + Width of window switcher in pixels. Width can also be a percentage of + the monitor width by adding '%' as suffix (e.g. 70%). Default is 600. *osd.window-switcher.style-classic.padding* Padding of window switcher in pixels. This is the space between the @@ -337,16 +337,17 @@ all are supported. *osd.window-switcher.style-classic.item.icon.size* Size of the icon in window switcher, in pixels. - If not set, the font size derived from - is used. + If not set, the font size derived from + is used. *osd.window-switcher.style-thumbnail* - Theme for window switcher when using . - See below for details. + Theme for window switcher when using + . See below for details. *osd.window-switcher.style-thumbnail.width.max* - Maximum width of window switcher in pixels. Width can also be a percentage of - the monitor width by adding '%' as suffix (e.g. 70%). Default is 80%. + Maximum width of window switcher in pixels. Width can also be a + percentage of the monitor width by adding '%' as suffix (e.g. 70%). + Default is 80%. *osd.window-switcher.style-thumbnail.padding* Padding of window switcher in pixels. This is the space between the @@ -359,8 +360,8 @@ all are supported. Height of window switcher items in pixels. Default is 250. *osd.window-switcher.style-thumbnail.item.padding* - Padding of window switcher items in pixels. This is the space between the - border around selected items and window thumbnail. Default is 2. + Padding of window switcher items in pixels. This is the space between + the border around selected items and window thumbnail. Default is 2. *osd.window-switcher.style-thumbnail.item.active.border.width* Border width of selected window switcher items in pixels. Default is 2. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index f3d046d4..bc9566fe 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -77,7 +77,7 @@ - + @@ -98,7 +98,7 @@ Some contents are fixed-length and others are variable-length. See "man 5 labwc-config" for details. - + @@ -119,7 +119,7 @@ then workspace name, then identifier/app-id, then the window title. It uses 100% of OSD window width. - + @@ -175,6 +175,7 @@ Workspaces can be configured like this: 1000 + Workspace 1 Workspace 1 Workspace 2 @@ -630,8 +631,8 @@ # must only apply to the first instance of the window with that # particular 'identifier' or 'title'. # - Matching is case-insensitive and is performed using shell wildcard - # patterns (see glob(7)) so '\*' (not between brackets) matches any string - # and '?' matches any single character. + # patterns (see glob(7)) so '\*' (not between brackets) matches any + # string and '?' matches any single character. diff --git a/docs/shutdown b/docs/shutdown index 970eaa26..feed6508 100644 --- a/docs/shutdown +++ b/docs/shutdown @@ -1,4 +1,5 @@ # Example shutdown file -# This file is executed as a shell script when labwc is preparing to terminate itself. +# This file is executed as a shell script when labwc is preparing to terminate +# itself. # For further details see labwc-config(5). diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 94eb6ebe..6036be9c 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -168,6 +168,7 @@ struct rcxml { struct { int popuptime; int min_nr_workspaces; + char *initial_workspace_name; char *prefix; struct wl_list workspaces; /* struct workspace.link */ } workspace_config; @@ -177,16 +178,18 @@ struct rcxml { /* Window Switcher */ struct { - bool show; bool preview; bool outlines; bool unshade; - enum lab_view_criteria criteria; - struct wl_list fields; /* struct window_switcher_field.link */ - enum cycle_osd_style style; - enum cycle_osd_output_criteria output_criteria; - char *thumbnail_label_format; enum window_switcher_order order; + enum cycle_workspace_filter workspace_filter; /* deprecated */ + struct { + bool show; + enum cycle_osd_style style; + enum cycle_output_filter output_filter; + char *thumbnail_label_format; + struct wl_list fields; /* struct cycle_osd_field.link */ + } osd; } window_switcher; struct wl_list window_rules; /* struct window_rule.link */ diff --git a/include/config/types.h b/include/config/types.h index 99c5929e..fc293cd8 100644 --- a/include/config/types.h +++ b/include/config/types.h @@ -117,10 +117,20 @@ enum cycle_osd_style { CYCLE_OSD_STYLE_THUMBNAIL, }; -enum cycle_osd_output_criteria { - CYCLE_OSD_OUTPUT_ALL, - CYCLE_OSD_OUTPUT_CURSOR, - CYCLE_OSD_OUTPUT_FOCUSED, +enum cycle_workspace_filter { + CYCLE_WORKSPACE_ALL, + CYCLE_WORKSPACE_CURRENT, +}; + +enum cycle_output_filter { + CYCLE_OUTPUT_ALL, + CYCLE_OUTPUT_CURSOR, + CYCLE_OUTPUT_FOCUSED, +}; + +enum cycle_app_id_filter { + CYCLE_APP_ID_ALL, + CYCLE_APP_ID_CURRENT, }; #endif /* LABWC_CONFIG_TYPES_H */ diff --git a/include/cycle.h b/include/cycle.h index aaecff50..c6e42810 100644 --- a/include/cycle.h +++ b/include/cycle.h @@ -4,8 +4,11 @@ #include #include +#include +#include "config/types.h" struct output; +struct wlr_box; enum lab_cycle_dir { LAB_CYCLE_DIR_NONE, @@ -39,7 +42,47 @@ struct cycle_osd_field { enum cycle_osd_field_content content; int width; char *format; - struct wl_list link; /* struct rcxml.window_switcher.fields */ + struct wl_list link; /* struct rcxml.window_switcher.osd.fields */ +}; + +struct cycle_filter { + enum cycle_workspace_filter workspace; + enum cycle_output_filter output; + enum cycle_app_id_filter app_id; +}; + +struct cycle_state { + struct view *selected_view; + struct wl_list views; + struct wl_list osd_outputs; /* struct cycle_osd_output.link */ + bool preview_was_shaded; + bool preview_was_enabled; + struct wlr_scene_node *preview_node; + struct wlr_scene_node *preview_dummy; + struct lab_scene_rect *preview_outline; + struct cycle_filter filter; +}; + +struct cycle_osd_output { + struct wl_list link; /* struct cycle_state.osd_outputs */ + struct output *output; + struct wl_listener tree_destroy; + + /* set by cycle_osd_impl->init() */ + struct wl_list items; /* struct cycle_osd_item.link */ + struct wlr_scene_tree *tree; + /* set by cycle_osd_impl->init() and moved by cycle_osd_scroll_update() */ + struct wlr_scene_tree *items_tree; + + /* used in osd-scroll.c */ + struct cycle_osd_scroll_context { + int top_row_idx; + int nr_rows, nr_cols, nr_visible_rows; + int delta_y; + struct wlr_box bar_area; + struct wlr_scene_tree *bar_tree; + struct lab_scene_rect *bar; + } scroll; }; struct buf; @@ -48,7 +91,8 @@ struct server; struct wlr_scene_node; /* Begin window switcher */ -void cycle_begin(struct server *server, enum lab_cycle_dir direction); +void cycle_begin(struct server *server, enum lab_cycle_dir direction, + struct cycle_filter filter); /* Cycle the selected view in the window switcher */ void cycle_step(struct server *server, enum lab_cycle_dir direction); @@ -84,17 +128,38 @@ struct cycle_osd_item { struct cycle_osd_impl { /* - * Create a scene-tree of OSD for an output. - * This sets output->cycle_osd.{items,tree}. + * Create a scene-tree of OSD for an output and fill + * osd_output->items. */ - void (*create)(struct output *output); + void (*init)(struct cycle_osd_output *osd_output); /* - * Update output->cycle_osd.tree to highlight - * server->cycle_state.selected_view. + * Update the OSD to highlight server->cycle.selected_view. */ - void (*update)(struct output *output); + void (*update)(struct cycle_osd_output *osd_output); }; +#define SCROLLBAR_W 10 + +/** + * Initialize the context and scene for scrolling OSD items. + * + * @output: Output of the OSD + * @bar_area: Area where the scrollbar is drawn + * @delta_y: The vertical delta by which items are scrolled (usually item height) + * @nr_cols: Number of columns in the OSD + * @nr_rows: Number of rows in the OSD + * @nr_visible_rows: Number of visible rows in the OSD + * @border_color: Border color of the scrollbar + * @bg_color: Background color of the scrollbar + */ +void cycle_osd_scroll_init(struct cycle_osd_output *osd_output, + struct wlr_box bar_area, int delta_y, + int nr_cols, int nr_rows, int nr_visible_rows, + float *border_color, float *bg_color); + +/* Scroll the OSD to show server->cycle.selected_view if needed */ +void cycle_osd_scroll_update(struct cycle_osd_output *osd_output); + extern struct cycle_osd_impl cycle_osd_classic_impl; extern struct cycle_osd_impl cycle_osd_thumbnail_impl; diff --git a/include/labwc.h b/include/labwc.h index 3d3ca2a3..c4848a11 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -5,6 +5,7 @@ #include #include #include "common/set.h" +#include "cycle.h" #include "input/cursor.h" #include "overlay.h" @@ -237,6 +238,7 @@ struct server { /* Tree for unmanaged xsurfaces without initialized view (usually popups) */ struct wlr_scene_tree *unmanaged_tree; #endif + struct wlr_scene_tree *cycle_preview_tree; /* Tree for built in menu */ struct wlr_scene_tree *menu_tree; @@ -302,15 +304,7 @@ struct server { struct wlr_security_context_manager_v1 *security_context_manager_v1; /* Set when in cycle (alt-tab) mode */ - struct cycle_state { - struct view *selected_view; - struct wl_list views; - bool preview_was_shaded; - bool preview_was_enabled; - struct wlr_scene_node *preview_node; - struct wlr_scene_node *preview_dummy; - struct lab_scene_rect *preview_outline; - } cycle; + struct cycle_state cycle; struct theme *theme; diff --git a/include/output.h b/include/output.h index 25001247..2b1e4bcf 100644 --- a/include/output.h +++ b/include/output.h @@ -19,11 +19,6 @@ struct output { struct wlr_scene_tree *session_lock_tree; struct wlr_scene_buffer *workspace_osd; - struct cycle_osd_scene { - struct wl_list items; /* struct cycle_osd_item */ - struct wlr_scene_tree *tree; - } cycle_osd; - /* In output-relative scene coordinates */ struct wlr_box usable_area; diff --git a/include/view.h b/include/view.h index fae462db..d2ff5ddf 100644 --- a/include/view.h +++ b/include/view.h @@ -512,8 +512,7 @@ void view_constrain_size_to_that_of_usable_area(struct view *view); void view_set_maximized(struct view *view, enum view_axis maximized); void view_set_untiled(struct view *view); -void view_maximize(struct view *view, enum view_axis axis, - bool store_natural_geometry); +void view_maximize(struct view *view, enum view_axis axis); void view_set_fullscreen(struct view *view, bool fullscreen); void view_toggle_maximize(struct view *view, enum view_axis axis); bool view_wants_decorations(struct view *view); @@ -540,8 +539,8 @@ void view_move_to_edge(struct view *view, enum lab_edge direction, bool snap_to_ void view_grow_to_edge(struct view *view, enum lab_edge direction); void view_shrink_to_edge(struct view *view, enum lab_edge direction); void view_snap_to_edge(struct view *view, enum lab_edge direction, - bool across_outputs, bool combine, bool store_natural_geometry); -void view_snap_to_region(struct view *view, struct region *region, bool store_natural_geometry); + bool across_outputs, bool combine); +void view_snap_to_region(struct view *view, struct region *region); void view_move_to_output(struct view *view, struct output *output); void view_move_to_front(struct view *view); diff --git a/include/xwayland.h b/include/xwayland.h index bbb9fa1c..cee15302 100644 --- a/include/xwayland.h +++ b/include/xwayland.h @@ -75,8 +75,6 @@ void xwayland_adjust_usable_area(struct view *view, void xwayland_update_workarea(struct server *server); -void xwayland_reset_cursor(struct server *server); - void xwayland_flush(struct server *server); #endif /* HAVE_XWAYLAND */ diff --git a/meson.build b/meson.build index ba3a2148..5a18d5a3 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'labwc', 'c', - version: '0.9.2', + version: '0.9.3', license: 'GPL-2.0-only', meson_version: '>=0.59.0', default_options: [ diff --git a/protocols/meson.build b/protocols/meson.build index 8f368deb..39a2a5b7 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -28,7 +28,6 @@ server_protocols = [ wl_protocol_dir / 'staging/color-management/color-management-v1.xml', 'cosmic-workspace-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml', - 'wlr-input-inhibitor-unstable-v1.xml', 'wlr-output-power-management-unstable-v1.xml', ] diff --git a/protocols/wlr-input-inhibitor-unstable-v1.xml b/protocols/wlr-input-inhibitor-unstable-v1.xml deleted file mode 100644 index b62d1bb4..00000000 --- a/protocols/wlr-input-inhibitor-unstable-v1.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - Copyright © 2018 Drew DeVault - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Clients can use this interface to prevent input events from being sent to - any surfaces but its own, which is useful for example in lock screen - software. It is assumed that access to this interface will be locked down - to whitelisted clients by the compositor. - - - - - Activates the input inhibitor. As long as the inhibitor is active, the - compositor will not send input events to other clients. - - - - - - - - - - - - While this resource exists, input to clients other than the owner of the - inhibitor resource will not receive input events. The client that owns - this resource will receive all input events normally. The compositor will - also disable all of its own input processing (such as keyboard shortcuts) - while the inhibitor is active. - - The compositor may continue to send input events to selected clients, - such as an on-screen keyboard (via the input-method protocol). - - - - - Destroy the inhibitor and allow other clients to receive input. - - - - diff --git a/src/action.c b/src/action.c index 7ddba0be..7f974917 100644 --- a/src/action.c +++ b/src/action.c @@ -366,6 +366,44 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char goto cleanup; } break; + case ACTION_TYPE_NEXT_WINDOW: + case ACTION_TYPE_PREVIOUS_WINDOW: + if (!strcasecmp(argument, "workspace")) { + if (!strcasecmp(content, "all")) { + action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL); + } else if (!strcasecmp(content, "current")) { + action_arg_add_int(action, argument, CYCLE_WORKSPACE_CURRENT); + } else { + wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", + action_names[action->type], argument, content); + } + goto cleanup; + } + if (!strcasecmp(argument, "output")) { + if (!strcasecmp(content, "all")) { + action_arg_add_int(action, argument, CYCLE_OUTPUT_ALL); + } else if (!strcasecmp(content, "cursor")) { + action_arg_add_int(action, argument, CYCLE_OUTPUT_CURSOR); + } else if (!strcasecmp(content, "focused")) { + action_arg_add_int(action, argument, CYCLE_OUTPUT_FOCUSED); + } else { + wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", + action_names[action->type], argument, content); + } + goto cleanup; + } + if (!strcasecmp(argument, "identifier")) { + if (!strcasecmp(content, "all")) { + action_arg_add_int(action, argument, CYCLE_APP_ID_ALL); + } else if (!strcasecmp(content, "current")) { + action_arg_add_int(action, argument, CYCLE_APP_ID_CURRENT); + } else { + wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)", + action_names[action->type], argument, content); + } + goto cleanup; + } + break; case ACTION_TYPE_SHOW_MENU: if (!strcmp(argument, "menu")) { action_arg_add_str(action, argument, content); @@ -1108,7 +1146,7 @@ run_action(struct view *view, struct server *server, struct action *action, } bool combine = action_get_bool(action, "combine", false); view_snap_to_edge(view, edge, /*across_outputs*/ true, - combine, /*store_natural_geometry*/ true); + combine); } break; case ACTION_TYPE_GROW_TO_EDGE: @@ -1126,19 +1164,24 @@ run_action(struct view *view, struct server *server, struct action *action, } break; case ACTION_TYPE_NEXT_WINDOW: + case ACTION_TYPE_PREVIOUS_WINDOW: { + enum lab_cycle_dir dir = (action->type == ACTION_TYPE_NEXT_WINDOW) ? + 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), + }; if (server->input_mode == LAB_INPUT_STATE_CYCLE) { - cycle_step(server, LAB_CYCLE_DIR_FORWARD); + cycle_step(server, dir); } else { - cycle_begin(server, LAB_CYCLE_DIR_FORWARD); - } - break; - case ACTION_TYPE_PREVIOUS_WINDOW: - if (server->input_mode == LAB_INPUT_STATE_CYCLE) { - cycle_step(server, LAB_CYCLE_DIR_BACKWARD); - } else { - cycle_begin(server, LAB_CYCLE_DIR_BACKWARD); + cycle_begin(server, dir, filter); } break; + } case ACTION_TYPE_RECONFIGURE: kill(getpid(), SIGHUP); break; @@ -1160,16 +1203,14 @@ run_action(struct view *view, struct server *server, struct action *action, if (view) { enum view_axis axis = action_get_int(action, "direction", VIEW_AXIS_BOTH); - view_maximize(view, axis, - /*store_natural_geometry*/ true); + view_maximize(view, axis); } break; case ACTION_TYPE_UNMAXIMIZE: if (view) { enum view_axis axis = action_get_int(action, "direction", VIEW_AXIS_BOTH); - view_maximize(view, view->maximized & ~axis, - /*store_natural_geometry*/ true); + view_maximize(view, view->maximized & ~axis); } break; case ACTION_TYPE_TOGGLE_FULLSCREEN: @@ -1383,8 +1424,7 @@ run_action(struct view *view, struct server *server, struct action *action, view_apply_natural_geometry(view); break; } - view_snap_to_region(view, region, - /*store_natural_geometry*/ true); + view_snap_to_region(view, region); } else { wlr_log(WLR_ERROR, "Invalid SnapToRegion id: '%s'", region_name); } @@ -1392,8 +1432,7 @@ run_action(struct view *view, struct server *server, struct action *action, } case ACTION_TYPE_UNSNAP: if (view && !view->fullscreen && !view_is_floating(view)) { - view_maximize(view, VIEW_AXIS_NONE, - /* store_natural_geometry */ false); + view_maximize(view, VIEW_AXIS_NONE); view_set_untiled(view); view_apply_natural_geometry(view); } diff --git a/src/config/rcxml.c b/src/config/rcxml.c index eacd3d64..c1b73c35 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -324,7 +324,7 @@ static void clear_window_switcher_fields(void) { struct cycle_osd_field *field, *field_tmp; - wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) { + wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.osd.fields, link) { wl_list_remove(&field->link); cycle_osd_field_free(field); } @@ -334,7 +334,7 @@ static void fill_window_switcher_field(xmlNode *node) { struct cycle_osd_field *field = znew(*field); - wl_list_append(&rc.window_switcher.fields, &field->link); + wl_list_append(&rc.window_switcher.osd.fields, &field->link); xmlNode *child; char *key, *content; @@ -684,6 +684,10 @@ get_send_events_mode(const char *s) goto err; } + if (!strcasecmp(s, "disabledOnExternalMouse")) { + return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; + } + int ret = parse_bool(s, -1); if (ret >= 0) { return ret @@ -691,10 +695,6 @@ get_send_events_mode(const char *s) : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; } - if (!strcasecmp(s, "disabledOnExternalMouse")) { - return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; - } - err: wlr_log(WLR_INFO, "Not a recognised send events mode"); return -1; @@ -1071,7 +1071,7 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(nodename, "prefix.desktops")) { xstrdup_replace(rc.workspace_config.prefix, content); } else if (!strcasecmp(nodename, "thumbnailLabelFormat.osd.windowSwitcher")) { - xstrdup_replace(rc.window_switcher.thumbnail_label_format, content); + xstrdup_replace(rc.window_switcher.osd.thumbnail_label_format, content); } else if (!lab_xml_node_is_leaf(node)) { /* parse children of nested nodes other than above */ @@ -1219,25 +1219,25 @@ entry(xmlNode *node, char *nodename, char *content) * thumnailLabelFormat is handled above to allow for an empty value */ } else if (!strcasecmp(nodename, "show.osd.windowSwitcher")) { - set_bool(content, &rc.window_switcher.show); + set_bool(content, &rc.window_switcher.osd.show); } else if (!strcasecmp(nodename, "style.osd.windowSwitcher")) { if (!strcasecmp(content, "classic")) { - rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC; } else if (!strcasecmp(content, "thumbnail")) { - rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL; } else { - wlr_log(WLR_ERROR, "Invalid windowSwitcher style %s: " + wlr_log(WLR_ERROR, "Invalid windowSwitcher style '%s': " "should be one of classic|thumbnail", content); } } else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) { if (!strcasecmp(content, "all")) { - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL; + rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_ALL; } else if (!strcasecmp(content, "cursor")) { - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_CURSOR; + rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_CURSOR; } else if (!strcasecmp(content, "focused")) { - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_FOCUSED; + rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_FOCUSED; } else { - wlr_log(WLR_ERROR, "Invalid windowSwitcher output %s: " + wlr_log(WLR_ERROR, "Invalid windowSwitcher output '%s': " "should be one of all|focused|cursor", content); } } else if (!strcasecmp(nodename, "order.windowSwitcher")) { @@ -1246,20 +1246,20 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(content, "age")) { rc.window_switcher.order = WINDOW_SWITCHER_ORDER_AGE; } else { - wlr_log(WLR_ERROR, "Invalid windowSwitcher order %s: " + wlr_log(WLR_ERROR, "Invalid windowSwitcher order '%s': " "should be one of focus|age", content); } /* The following two are for backward compatibility only. */ } else if (!strcasecmp(nodename, "show.windowSwitcher")) { - set_bool(content, &rc.window_switcher.show); + set_bool(content, &rc.window_switcher.osd.show); wlr_log(WLR_ERROR, " is deprecated." " Use "); } else if (!strcasecmp(nodename, "style.windowSwitcher")) { if (!strcasecmp(content, "classic")) { - rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC; } else if (!strcasecmp(content, "thumbnail")) { - rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL; } wlr_log(WLR_ERROR, " is deprecated." " Use "); @@ -1269,10 +1269,16 @@ entry(xmlNode *node, char *nodename, char *content) } else if (!strcasecmp(nodename, "outlines.windowSwitcher")) { set_bool(content, &rc.window_switcher.outlines); } else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) { - if (parse_bool(content, -1) == true) { - rc.window_switcher.criteria &= - ~LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; + int ret = parse_bool(content, -1); + if (ret < 0) { + wlr_log(WLR_ERROR, "Invalid value for : '%s'", content); + } else { + rc.window_switcher.workspace_filter = ret ? + CYCLE_WORKSPACE_ALL : CYCLE_WORKSPACE_CURRENT; } + wlr_log(WLR_ERROR, " is deprecated." + " Use instead."); } else if (!strcasecmp(nodename, "unshade.windowSwitcher")) { set_bool(content, &rc.window_switcher.unshade); @@ -1282,7 +1288,7 @@ entry(xmlNode *node, char *nodename, char *content) /* The following three are for backward compatibility only */ } else if (!strcasecmp(nodename, "show.windowSwitcher.core")) { - set_bool(content, &rc.window_switcher.show); + set_bool(content, &rc.window_switcher.osd.show); } else if (!strcasecmp(nodename, "preview.windowSwitcher.core")) { set_bool(content, &rc.window_switcher.preview); } else if (!strcasecmp(nodename, "outlines.windowSwitcher.core")) { @@ -1290,7 +1296,7 @@ entry(xmlNode *node, char *nodename, char *content) /* The following three are for backward compatibility only */ } else if (!strcasecmp(nodename, "cycleViewOSD.core")) { - set_bool(content, &rc.window_switcher.show); + set_bool(content, &rc.window_switcher.osd.show); wlr_log(WLR_ERROR, " is deprecated." " Use "); } else if (!strcasecmp(nodename, "cycleViewPreview.core")) { @@ -1308,6 +1314,8 @@ entry(xmlNode *node, char *nodename, char *content) wl_list_append(&rc.workspace_config.workspaces, &workspace->link); } else if (!strcasecmp(nodename, "popupTime.desktops")) { rc.workspace_config.popuptime = atoi(content); + } else if (!strcasecmp(nodename, "initial.desktops")) { + xstrdup_replace(rc.workspace_config.initial_workspace_name, content); } else if (!strcasecmp(nodename, "number.desktops")) { rc.workspace_config.min_nr_workspaces = MAX(1, atoi(content)); } else if (!strcasecmp(nodename, "popupShow.resize")) { @@ -1419,7 +1427,7 @@ rcxml_init(void) wl_list_init(&rc.libinput_categories); wl_list_init(&rc.workspace_config.workspaces); wl_list_init(&rc.regions); - wl_list_init(&rc.window_switcher.fields); + wl_list_init(&rc.window_switcher.osd.fields); wl_list_init(&rc.window_rules); wl_list_init(&rc.touch_configs); } @@ -1484,16 +1492,14 @@ rcxml_init(void) rc.snap_top_maximize = true; rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS; - rc.window_switcher.show = true; - rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC; - rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL; - rc.window_switcher.thumbnail_label_format = xstrdup("%T"); + rc.window_switcher.osd.show = true; + rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC; + rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_ALL; + rc.window_switcher.osd.thumbnail_label_format = xstrdup("%T"); rc.window_switcher.preview = true; rc.window_switcher.outlines = true; rc.window_switcher.unshade = true; - rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE - | LAB_VIEW_CRITERIA_ROOT_TOPLEVEL - | LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER; + rc.window_switcher.workspace_filter = CYCLE_WORKSPACE_CURRENT; rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS; rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER; @@ -1673,7 +1679,7 @@ load_default_window_switcher_fields(void) struct cycle_osd_field *field = znew(*field); field->content = fields[i].content; field->width = fields[i].width; - wl_list_append(&rc.window_switcher.fields, &field->link); + wl_list_append(&rc.window_switcher.osd.fields, &field->link); } } @@ -1794,7 +1800,7 @@ post_processing(void) if (rc.workspace_config.popuptime == INT_MIN) { rc.workspace_config.popuptime = 1000; } - if (!wl_list_length(&rc.window_switcher.fields)) { + if (!wl_list_length(&rc.window_switcher.osd.fields)) { wlr_log(WLR_INFO, "load default window switcher fields"); load_default_window_switcher_fields(); } @@ -1888,7 +1894,7 @@ validate(void) /* OSD fields */ int field_width_sum = 0; struct cycle_osd_field *field, *field_tmp; - wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) { + wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.osd.fields, link) { field_width_sum += field->width; if (!cycle_osd_field_is_valid(field) || field_width_sum > 100) { wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field); @@ -1963,8 +1969,9 @@ rcxml_finish(void) zfree(rc.icon_theme_name); zfree(rc.fallback_app_icon_name); zfree(rc.workspace_config.prefix); + zfree(rc.workspace_config.initial_workspace_name); zfree(rc.tablet.output_name); - zfree(rc.window_switcher.thumbnail_label_format); + zfree(rc.window_switcher.osd.thumbnail_label_format); clear_title_layout(); diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index adf9b05e..32a7438f 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -6,6 +6,7 @@ #include #include "common/lab-scene-rect.h" #include "common/list.h" +#include "common/mem.h" #include "common/scene-helpers.h" #include "config/rcxml.h" #include "labwc.h" @@ -17,7 +18,7 @@ #include "theme.h" #include "view.h" -static bool init_cycle(struct server *server); +static bool init_cycle(struct server *server, struct cycle_filter filter); static void update_cycle(struct server *server); static void destroy_cycle(struct server *server); @@ -39,7 +40,8 @@ update_preview_outlines(struct view *view) .border_width = theme->osd_window_switcher_preview_border_width, }; rect = lab_scene_rect_create(&server->scene->tree, &opts); - wlr_scene_node_place_above(&rect->tree->node, &server->menu_tree->node); + wlr_scene_node_place_above(&rect->tree->node, + &server->cycle_preview_tree->node); server->cycle.preview_outline = rect; } @@ -93,9 +95,10 @@ cycle_reinitialize(struct server *server) struct view *selected_view = cycle->selected_view; struct view *selected_view_prev = get_next_selected_view(server, LAB_CYCLE_DIR_BACKWARD); + struct cycle_filter filter = cycle->filter; destroy_cycle(server); - if (init_cycle(server)) { + if (init_cycle(server, filter)) { /* * Preserve the selected view (or its previous view) if it's * still in the cycle list @@ -152,13 +155,14 @@ restore_preview_node(struct server *server) } void -cycle_begin(struct server *server, enum lab_cycle_dir direction) +cycle_begin(struct server *server, enum lab_cycle_dir direction, + struct cycle_filter filter) { if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) { return; } - if (!init_cycle(server)) { + if (!init_cycle(server, filter)) { return; } @@ -242,13 +246,8 @@ preview_selected_view(struct view *view) cycle->preview_was_shaded = true; } - /* - * FIXME: This abuses an implementation detail of the always-on-top tree. - * Create a permanent server->osd_preview_tree instead that can - * also be used as parent for the preview outlines. - */ wlr_scene_node_reparent(cycle->preview_node, - view->server->view_tree_always_on_top); + view->server->cycle_preview_tree); /* Finally raise selected node to the top */ wlr_scene_node_raise_to_top(cycle->preview_node); @@ -257,7 +256,7 @@ preview_selected_view(struct view *view) static struct cycle_osd_impl * get_osd_impl(void) { - switch (rc.window_switcher.style) { + switch (rc.window_switcher.osd.style) { case CYCLE_OSD_STYLE_CLASSIC: return &cycle_osd_classic_impl; case CYCLE_OSD_STYLE_THUMBNAIL: @@ -266,14 +265,36 @@ get_osd_impl(void) return NULL; } -static void -create_osd_on_output(struct output *output) +static uint64_t +get_outputs_by_filter(struct server *server, + enum cycle_output_filter output_filter) { - if (!output_is_usable(output)) { - return; + struct output *output = NULL; + + switch (output_filter) { + case CYCLE_OUTPUT_ALL: + break; + case CYCLE_OUTPUT_CURSOR: + output = output_nearest_to_cursor(server); + break; + case CYCLE_OUTPUT_FOCUSED: { + struct view *view = server->active_view; + if (view && output_is_usable(view->output)) { + output = view->output; + } else { + /* Fallback to pointer */ + output = output_nearest_to_cursor(server); + } + break; + } + } + + if (output) { + return output->id_bit; + } else { + /* bitmask for all outputs */ + return UINT64_MAX; } - get_osd_impl()->create(output); - assert(output->cycle_osd.tree); } static void @@ -290,12 +311,49 @@ insert_view_ordered_by_age(struct wl_list *views, struct view *new_view) wl_list_insert(link, &new_view->cycle_link); } +static void +handle_osd_tree_destroy(struct wl_listener *listener, void *data) +{ + struct cycle_osd_output *osd_output = + wl_container_of(listener, osd_output, tree_destroy); + struct cycle_osd_item *item, *tmp; + wl_list_for_each_safe(item, tmp, &osd_output->items, link) { + wl_list_remove(&item->link); + free(item); + } + wl_list_remove(&osd_output->tree_destroy.link); + wl_list_remove(&osd_output->link); + free(osd_output); +} + /* Return false on failure */ static bool -init_cycle(struct server *server) +init_cycle(struct server *server, struct cycle_filter filter) { + enum lab_view_criteria criteria = + LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER + | LAB_VIEW_CRITERIA_ROOT_TOPLEVEL; + if (filter.workspace == CYCLE_WORKSPACE_CURRENT) { + criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; + } + + uint64_t cycle_outputs = + get_outputs_by_filter(server, 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; + } + struct view *view; - for_each_view(view, &server->views, rc.window_switcher.criteria) { + for_each_view(view, &server->views, criteria) { + if (!(cycle_outputs & view->output->id_bit)) { + continue; + } + if (cycle_app_id && strcmp(view->app_id, cycle_app_id) != 0) { + continue; + } + if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) { insert_view_ordered_by_age(&server->cycle.views, view); } else { @@ -306,31 +364,31 @@ init_cycle(struct server *server) wlr_log(WLR_DEBUG, "no views to switch between"); return false; } + server->cycle.filter = filter; - if (rc.window_switcher.show) { + if (rc.window_switcher.osd.show) { /* Create OSD */ - switch (rc.window_switcher.output_criteria) { - case CYCLE_OSD_OUTPUT_ALL: { - struct output *output; - wl_list_for_each(output, &server->outputs, link) { - create_osd_on_output(output); + uint64_t osd_outputs = get_outputs_by_filter(server, + rc.window_switcher.osd.output_filter); + struct output *output; + wl_list_for_each(output, &server->outputs, link) { + if (!(osd_outputs & output->id_bit)) { + continue; } - break; - } - case CYCLE_OSD_OUTPUT_CURSOR: - create_osd_on_output(output_nearest_to_cursor(server)); - break; - case CYCLE_OSD_OUTPUT_FOCUSED: { - struct output *output; - if (server->active_view) { - output = server->active_view->output; - } else { - /* Fallback to pointer, if there is no active_view */ - output = output_nearest_to_cursor(server); + if (!output_is_usable(output)) { + continue; } - create_osd_on_output(output); - break; - } + + struct cycle_osd_output *osd_output = znew(*osd_output); + wl_list_append(&server->cycle.osd_outputs, &osd_output->link); + osd_output->output = output; + wl_list_init(&osd_output->items); + + get_osd_impl()->init(osd_output); + + osd_output->tree_destroy.notify = handle_osd_tree_destroy; + wl_signal_add(&osd_output->tree->node.events.destroy, + &osd_output->tree_destroy); } } @@ -342,12 +400,10 @@ update_cycle(struct server *server) { struct cycle_state *cycle = &server->cycle; - if (rc.window_switcher.show) { - struct output *output; - wl_list_for_each(output, &server->outputs, link) { - if (output->cycle_osd.tree) { - get_osd_impl()->update(output); - } + if (rc.window_switcher.osd.show) { + struct cycle_osd_output *osd_output; + wl_list_for_each(osd_output, &cycle->osd_outputs, link) { + get_osd_impl()->update(osd_output); } } @@ -357,8 +413,8 @@ update_cycle(struct server *server) /* Outline current window */ if (rc.window_switcher.outlines) { - if (view_is_focusable(server->cycle.selected_view)) { - update_preview_outlines(server->cycle.selected_view); + if (view_is_focusable(cycle->selected_view)) { + update_preview_outlines(cycle->selected_view); } } } @@ -367,31 +423,25 @@ update_cycle(struct server *server) static void destroy_cycle(struct server *server) { - struct output *output; - wl_list_for_each(output, &server->outputs, link) { - struct cycle_osd_item *item, *tmp; - wl_list_for_each_safe(item, tmp, &output->cycle_osd.items, link) { - wl_list_remove(&item->link); - free(item); - } - if (output->cycle_osd.tree) { - wlr_scene_node_destroy(&output->cycle_osd.tree->node); - output->cycle_osd.tree = NULL; - } + struct cycle_osd_output *osd_output, *tmp; + wl_list_for_each_safe(osd_output, tmp, &server->cycle.osd_outputs, link) { + /* calls handle_osd_tree_destroy() */ + wlr_scene_node_destroy(&osd_output->tree->node); } restore_preview_node(server); if (server->cycle.preview_outline) { wlr_scene_node_destroy(&server->cycle.preview_outline->tree->node); - server->cycle.preview_outline = NULL; } - struct view *view, *tmp; - wl_list_for_each_safe(view, tmp, &server->cycle.views, cycle_link) { + struct view *view, *tmp2; + wl_list_for_each_safe(view, tmp2, &server->cycle.views, cycle_link) { wl_list_remove(&view->cycle_link); view->cycle_link = (struct wl_list){0}; } - server->cycle.selected_view = NULL; + server->cycle = (struct cycle_state){0}; + wl_list_init(&server->cycle.views); + wl_list_init(&server->cycle.osd_outputs); } diff --git a/src/cycle/meson.build b/src/cycle/meson.build index 07e9f7aa..db244520 100644 --- a/src/cycle/meson.build +++ b/src/cycle/meson.build @@ -2,5 +2,6 @@ labwc_sources += files( 'cycle.c', 'osd-classic.c', 'osd-field.c', + 'osd-scroll.c', 'osd-thumbnail.c', ) diff --git a/src/cycle/osd-classic.c b/src/cycle/osd-classic.c index 944b9063..c9b5497d 100644 --- a/src/cycle/osd-classic.c +++ b/src/cycle/osd-classic.c @@ -36,7 +36,7 @@ create_fields_scene(struct server *server, struct view *view, &theme->osd_window_switcher_classic; struct cycle_osd_field *field; - wl_list_for_each(field, &rc.window_switcher.fields, link) { + wl_list_for_each(field, &rc.window_switcher.osd.fields, link) { int field_width = field_widths_sum * field->width / 100.0; struct wlr_scene_node *node = NULL; int height = -1; @@ -77,10 +77,9 @@ create_fields_scene(struct server *server, struct view *view, } static void -cycle_osd_classic_create(struct output *output) +cycle_osd_classic_init(struct cycle_osd_output *osd_output) { - assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); - + struct output *output = osd_output->output; struct server *server = output->server; struct theme *theme = server->theme; struct window_switcher_classic_theme *switcher_theme = @@ -98,13 +97,18 @@ cycle_osd_classic_create(struct output *output) if (switcher_theme->width_is_percent) { w = output_box.width * switcher_theme->width / 100; } - int h = nr_views * switcher_theme->item_height + 2 * padding; + int workspace_name_h = 0; if (show_workspace) { /* workspace indicator */ - h += switcher_theme->item_height; + workspace_name_h = switcher_theme->item_height; } + int nr_visible_views = (output_box.height - workspace_name_h - 2 * padding) + / switcher_theme->item_height; + nr_visible_views = MIN(nr_visible_views, nr_views); + int h = workspace_name_h + nr_visible_views * switcher_theme->item_height + + 2 * padding; - output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree); + osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree); float *text_color = theme->osd_label_text_color; float *bg_color = theme->osd_bg_color; @@ -118,7 +122,7 @@ cycle_osd_classic_create(struct output *output) .width = w, .height = h, }; - lab_scene_rect_create(output->cycle_osd.tree, &bg_opts); + lab_scene_rect_create(osd_output->tree, &bg_opts); int y = padding; @@ -136,7 +140,7 @@ cycle_osd_classic_create(struct output *output) } struct scaled_font_buffer *font_buffer = - scaled_font_buffer_create(output->cycle_osd.tree); + scaled_font_buffer_create(osd_output->tree); wlr_scene_node_set_position(&font_buffer->scene_buffer->node, x, y + (switcher_theme->item_height - font_height(&font)) / 2); scaled_font_buffer_update(font_buffer, workspace_name, 0, @@ -144,7 +148,7 @@ cycle_osd_classic_create(struct output *output) y += switcher_theme->item_height; } - int nr_fields = wl_list_length(&rc.window_switcher.fields); + int nr_fields = wl_list_length(&rc.window_switcher.osd.fields); /* This is the width of the area available for text fields */ int field_widths_sum = w - 2 * padding @@ -155,13 +159,17 @@ cycle_osd_classic_create(struct output *output) goto error; } + float *active_bg_color = switcher_theme->item_active_bg_color; + float *active_border_color = switcher_theme->item_active_border_color; + osd_output->items_tree = wlr_scene_tree_create(osd_output->tree); + /* Draw text for each node */ struct view *view; wl_list_for_each(view, &server->cycle.views, cycle_link) { struct cycle_osd_classic_item *item = znew(*item); - wl_list_append(&output->cycle_osd.items, &item->base.link); + wl_list_append(&osd_output->items, &item->base.link); item->base.view = view; - item->base.tree = wlr_scene_tree_create(output->cycle_osd.tree); + item->base.tree = wlr_scene_tree_create(osd_output->items_tree); node_descriptor_create(&item->base.tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item); /* @@ -187,9 +195,6 @@ cycle_osd_classic_create(struct output *output) item->active_tree = wlr_scene_tree_create(item->base.tree); wlr_scene_node_set_enabled(&item->active_tree->node, false); - float *active_bg_color = switcher_theme->item_active_bg_color; - float *active_border_color = switcher_theme->item_active_border_color; - /* Highlight around selected window's item */ struct lab_scene_rect_options highlight_opts = { .border_colors = (float *[1]) {active_border_color}, @@ -216,25 +221,39 @@ cycle_osd_classic_create(struct output *output) y += switcher_theme->item_height; } + struct wlr_box scrollbar_area = { + .x = w - padding - SCROLLBAR_W, + .y = padding, + .width = SCROLLBAR_W, + .height = h - 2 * padding, + }; + cycle_osd_scroll_init(osd_output, scrollbar_area, + switcher_theme->item_height, + /*nr_cols*/ 1, /*nr_rows*/ nr_views, nr_visible_views, + active_border_color, active_bg_color); + error:; /* Center OSD */ - wlr_scene_node_set_position(&output->cycle_osd.tree->node, + wlr_scene_node_set_position(&osd_output->tree->node, output_box.x + (output_box.width - w) / 2, output_box.y + (output_box.height - h) / 2); } static void -cycle_osd_classic_update(struct output *output) +cycle_osd_classic_update(struct cycle_osd_output *osd_output) { + struct server *server = osd_output->output->server; + cycle_osd_scroll_update(osd_output); + struct cycle_osd_classic_item *item; - wl_list_for_each(item, &output->cycle_osd.items, base.link) { - bool active = item->base.view == output->server->cycle.selected_view; + wl_list_for_each(item, &osd_output->items, base.link) { + bool active = item->base.view == server->cycle.selected_view; wlr_scene_node_set_enabled(&item->normal_tree->node, !active); wlr_scene_node_set_enabled(&item->active_tree->node, active); } } struct cycle_osd_impl cycle_osd_classic_impl = { - .create = cycle_osd_classic_create, + .init = cycle_osd_classic_init, .update = cycle_osd_classic_update, }; diff --git a/src/cycle/osd-scroll.c b/src/cycle/osd-scroll.c new file mode 100644 index 00000000..2bca8021 --- /dev/null +++ b/src/cycle/osd-scroll.c @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include "common/lab-scene-rect.h" +#include "labwc.h" +#include "cycle.h" +#include "output.h" + +void +cycle_osd_scroll_init(struct cycle_osd_output *osd_output, struct wlr_box bar_area, + int delta_y, int nr_cols, int nr_rows, int nr_visible_rows, + float *border_color, float *bg_color) +{ + if (nr_visible_rows >= nr_rows) { + /* OSD doesn't have so many windows to scroll through */ + return; + } + + struct cycle_osd_scroll_context *scroll = &osd_output->scroll; + scroll->nr_cols = nr_cols; + scroll->nr_rows = nr_rows; + scroll->nr_visible_rows = nr_visible_rows; + scroll->top_row_idx = 0; + scroll->bar_area = bar_area; + scroll->delta_y = delta_y; + scroll->bar_tree = wlr_scene_tree_create(osd_output->tree); + wlr_scene_node_set_position(&scroll->bar_tree->node, + bar_area.x, bar_area.y); + + struct lab_scene_rect_options scrollbar_opts = { + .border_colors = (float *[1]) { border_color }, + .nr_borders = 1, + .border_width = 1, + .bg_color = bg_color, + .width = bar_area.width, + .height = bar_area.height * nr_visible_rows / nr_rows, + }; + scroll->bar = lab_scene_rect_create(scroll->bar_tree, &scrollbar_opts); +} + +static int +get_cycle_idx(struct cycle_osd_output *osd_output) +{ + struct server *server = osd_output->output->server; + + int idx = 0; + struct cycle_osd_item *item; + wl_list_for_each(item, &osd_output->items, link) { + if (item->view == server->cycle.selected_view) { + return idx; + } + idx++; + } + assert(false && "selected view not found in items"); + return -1; +} + +void +cycle_osd_scroll_update(struct cycle_osd_output *osd_output) +{ + struct cycle_osd_scroll_context *scroll = &osd_output->scroll; + if (!scroll->bar) { + return; + } + + int cycle_idx = get_cycle_idx(osd_output); + + /* Update the range of visible rows */ + int bottom_row_idx = scroll->top_row_idx + scroll->nr_visible_rows; + while (cycle_idx < scroll->top_row_idx * scroll->nr_cols) { + scroll->top_row_idx--; + bottom_row_idx--; + } + while (cycle_idx >= bottom_row_idx * scroll->nr_cols) { + scroll->top_row_idx++; + bottom_row_idx++; + } + + /* Vertically move scrollbar by (bar height) / (# of total rows) */ + wlr_scene_node_set_position(&scroll->bar->tree->node, 0, + scroll->bar_area.height * scroll->top_row_idx / scroll->nr_rows); + /* Vertically move items */ + wlr_scene_node_set_position(&osd_output->items_tree->node, 0, + -scroll->delta_y * scroll->top_row_idx); + + /* Hide items outside of visible area */ + int idx = 0; + struct cycle_osd_item *item; + wl_list_for_each(item, &osd_output->items, link) { + bool visible = idx >= scroll->top_row_idx * scroll->nr_cols + && idx < bottom_row_idx * scroll->nr_cols; + wlr_scene_node_set_enabled(&item->tree->node, visible); + idx++; + } +} diff --git a/src/cycle/osd-thumbnail.c b/src/cycle/osd-thumbnail.c index 2245d1ad..d5a0bf3e 100644 --- a/src/cycle/osd-thumbnail.c +++ b/src/cycle/osd-thumbnail.c @@ -103,7 +103,7 @@ create_label(struct wlr_scene_tree *parent, struct view *view, { struct buf buf = BUF_INIT; cycle_osd_field_set_custom(&buf, view, - rc.window_switcher.thumbnail_label_format); + rc.window_switcher.osd.thumbnail_label_format); struct scaled_font_buffer *buffer = scaled_font_buffer_create(parent); scaled_font_buffer_update(buffer, buf.data, @@ -117,9 +117,9 @@ create_label(struct wlr_scene_tree *parent, struct view *view, static struct cycle_osd_thumbnail_item * create_item_scene(struct wlr_scene_tree *parent, struct view *view, - struct output *output) + struct cycle_osd_output *osd_output) { - struct server *server = output->server; + struct server *server = osd_output->output->server; struct theme *theme = server->theme; struct window_switcher_thumbnail_theme *switcher_theme = &theme->osd_window_switcher_thumbnail; @@ -137,7 +137,7 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, } struct cycle_osd_thumbnail_item *item = znew(*item); - wl_list_append(&output->cycle_osd.items, &item->base.link); + wl_list_append(&osd_output->items, &item->base.link); struct wlr_scene_tree *tree = wlr_scene_tree_create(parent); node_descriptor_create(&tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item); item->base.tree = tree; @@ -159,7 +159,7 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, switcher_theme->item_height, (float[4]) {0}); /* thumbnail */ - struct wlr_buffer *thumb_buffer = render_thumb(output, view); + struct wlr_buffer *thumb_buffer = render_thumb(osd_output->output, view); if (thumb_buffer) { struct wlr_scene_buffer *thumb_scene_buffer = wlr_scene_buffer_create(tree, thumb_buffer); @@ -194,9 +194,10 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, } static void -get_items_geometry(struct output *output, struct theme *theme, - int nr_thumbs, int *nr_rows, int *nr_cols) +get_items_geometry(struct output *output, int nr_thumbs, + int *nr_cols, int *nr_rows, int *nr_visible_rows) { + struct theme *theme = output->server->theme; struct window_switcher_thumbnail_theme *switcher_theme = &theme->osd_window_switcher_thumbnail; int output_width, output_height; @@ -223,32 +224,35 @@ get_items_geometry(struct output *output, struct theme *theme, (*nr_rows)++; *nr_cols = ceilf((float)nr_thumbs / *nr_rows); } + + *nr_visible_rows = MIN(*nr_rows, + (output_height - 2 * padding) / switcher_theme->item_height); } static void -cycle_osd_thumbnail_create(struct output *output) +cycle_osd_thumbnail_init(struct cycle_osd_output *osd_output) { - assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items)); - + struct output *output = osd_output->output; struct server *server = output->server; struct theme *theme = server->theme; struct window_switcher_thumbnail_theme *switcher_theme = &theme->osd_window_switcher_thumbnail; int padding = theme->osd_border_width + switcher_theme->padding; - output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree); + osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree); + osd_output->items_tree = wlr_scene_tree_create(osd_output->tree); int nr_views = wl_list_length(&server->cycle.views); assert(nr_views > 0); - int nr_rows, nr_cols; - get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols); + int nr_cols, nr_rows, nr_visible_rows; + get_items_geometry(output, nr_views, &nr_cols, &nr_rows, &nr_visible_rows); /* items */ struct view *view; int index = 0; wl_list_for_each(view, &server->cycle.views, cycle_link) { struct cycle_osd_thumbnail_item *item = create_item_scene( - output->cycle_osd.tree, view, output); + osd_output->items_tree, view, osd_output); if (!item) { break; } @@ -258,17 +262,31 @@ cycle_osd_thumbnail_create(struct output *output) index++; } + int items_width = switcher_theme->item_width * nr_cols; + int items_height = switcher_theme->item_height * nr_visible_rows; + + struct wlr_box scrollbar_area = { + .x = padding + items_width - SCROLLBAR_W, + .y = padding, + .width = SCROLLBAR_W, + .height = items_height, + }; + cycle_osd_scroll_init(osd_output, scrollbar_area, + switcher_theme->item_height, nr_cols, nr_rows, nr_visible_rows, + switcher_theme->item_active_border_color, + switcher_theme->item_active_bg_color); + /* background */ struct lab_scene_rect_options bg_opts = { .border_colors = (float *[1]) { theme->osd_border_color }, .nr_borders = 1, .border_width = theme->osd_border_width, .bg_color = theme->osd_bg_color, - .width = nr_cols * switcher_theme->item_width + 2 * padding, - .height = nr_rows * switcher_theme->item_height + 2 * padding, + .width = items_width + 2 * padding, + .height = items_height + 2 * padding, }; struct lab_scene_rect *bg = - lab_scene_rect_create(output->cycle_osd.tree, &bg_opts); + lab_scene_rect_create(osd_output->tree, &bg_opts); wlr_scene_node_lower_to_bottom(&bg->tree->node); /* center */ @@ -277,15 +295,18 @@ cycle_osd_thumbnail_create(struct output *output) &output_box); int lx = output_box.x + (output_box.width - bg_opts.width) / 2; int ly = output_box.y + (output_box.height - bg_opts.height) / 2; - wlr_scene_node_set_position(&output->cycle_osd.tree->node, lx, ly); + wlr_scene_node_set_position(&osd_output->tree->node, lx, ly); } static void -cycle_osd_thumbnail_update(struct output *output) +cycle_osd_thumbnail_update(struct cycle_osd_output *osd_output) { + struct server *server = osd_output->output->server; + cycle_osd_scroll_update(osd_output); + struct cycle_osd_thumbnail_item *item; - wl_list_for_each(item, &output->cycle_osd.items, base.link) { - bool active = (item->base.view == output->server->cycle.selected_view); + wl_list_for_each(item, &osd_output->items, base.link) { + bool active = (item->base.view == server->cycle.selected_view); wlr_scene_node_set_enabled(&item->active_bg->tree->node, active); wlr_scene_node_set_enabled( &item->active_label->scene_buffer->node, active); @@ -295,6 +316,6 @@ cycle_osd_thumbnail_update(struct output *output) } struct cycle_osd_impl cycle_osd_thumbnail_impl = { - .create = cycle_osd_thumbnail_create, + .init = cycle_osd_thumbnail_init, .update = cycle_osd_thumbnail_update, }; diff --git a/src/foreign-toplevel/wlr-foreign.c b/src/foreign-toplevel/wlr-foreign.c index f5b58110..70ce0890 100644 --- a/src/foreign-toplevel/wlr-foreign.c +++ b/src/foreign-toplevel/wlr-foreign.c @@ -26,8 +26,7 @@ handle_request_maximize(struct wl_listener *listener, void *data) struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; view_maximize(wlr_toplevel->view, - event->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE, - /*store_natural_geometry*/ true); + event->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE); } static void diff --git a/src/input/cursor.c b/src/input/cursor.c index 29409bea..c74c72f6 100644 --- a/src/input/cursor.c +++ b/src/input/cursor.c @@ -1610,9 +1610,6 @@ void cursor_reload(struct seat *seat) { cursor_load(seat); -#if HAVE_XWAYLAND - xwayland_reset_cursor(seat->server); -#endif cursor_update_image(seat); } diff --git a/src/interactive.c b/src/interactive.c index 03c4ad53..4bbecb9a 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -273,17 +273,12 @@ snap_to_edge(struct view *view) enum lab_edge edge = edge1 | edge2; view_set_output(view, output); - /* - * Don't store natural geometry here (it was - * stored already in interactive_begin()) - */ if (edge == LAB_EDGE_TOP && rc.snap_top_maximize) { /* */ - view_maximize(view, VIEW_AXIS_BOTH, - /*store_natural_geometry*/ false); + view_maximize(view, VIEW_AXIS_BOTH); } else { view_snap_to_edge(view, edge, /*across_outputs*/ false, - /*combine*/ false, /*store_natural_geometry*/ false); + /*combine*/ false); } return true; @@ -298,8 +293,7 @@ snap_to_region(struct view *view) struct region *region = regions_from_cursor(view->server); if (region) { - view_snap_to_region(view, region, - /*store_natural_geometry*/ false); + view_snap_to_region(view, region); return true; } return false; diff --git a/src/output.c b/src/output.c index f8c19c56..d25d2736 100644 --- a/src/output.c +++ b/src/output.c @@ -542,7 +542,6 @@ handle_new_output(struct wl_listener *listener, void *data) wl_signal_add(&wlr_output->events.request_state, &output->request_state); wl_list_init(&output->regions); - wl_list_init(&output->cycle_osd.items); /* * Create layer-trees (background, bottom, top and overlay) and diff --git a/src/server.c b/src/server.c index d7ec97c8..0bd059dc 100644 --- a/src/server.c +++ b/src/server.c @@ -100,6 +100,7 @@ reload_config_and_theme(struct server *server) view_reload_ssd(view); } + cycle_finish(server, /*switch_focus*/ false); menu_reconfigure(server); seat_reconfigure(server); regions_reconfigure(server); @@ -556,6 +557,7 @@ server_init(struct server *server) wl_list_init(&server->views); wl_list_init(&server->unmanaged_surfaces); wl_list_init(&server->cycle.views); + wl_list_init(&server->cycle.osd_outputs); server->scene = wlr_scene_create(); if (!server->scene) { @@ -569,21 +571,22 @@ server_init(struct server *server) * z-order for nodes which cover the whole work-area. For per-output * scene-trees, see handle_new_output() in src/output.c * - * | Type | Scene Tree | Per Output | Example - * | ------------------- | ---------------- | ---------- | ------- - * | ext-session | lock-screen | Yes | swaylock - * | window switcher OSD | cycle_osd_tree | Yes | - * | compositor-menu | menu_tree | No | root-menu - * | layer-shell | layer-popups | Yes | - * | layer-shell | overlay-layer | Yes | - * | layer-shell | top-layer | Yes | waybar - * | xwayland-OR | unmanaged | No | dmenu - * | xdg-popups | xdg-popups | No | - * | toplevels windows | always-on-top | No | - * | toplevels windows | normal | No | firefox - * | toplevels windows | always-on-bottom | No | pcmanfm-qt --desktop - * | layer-shell | bottom-layer | Yes | waybar - * | layer-shell | background-layer | Yes | swaybg + * | Scene Tree | Description + * | ---------------------------------- | ------------------------------------- + * | output->session_lock_tree | session lock surfaces (e.g. swaylock) + * | output->cycle_osd_tree | window switcher's on-screen display + * | server->cycle_preview_tree | window switcher's previewed window + * | server->menu_tree | labwc's server-side menus + * | output->layer_popup_tree | xdg popups on layer surfaces + * | output->layer_tree[3] | overlay layer surfaces (e.g. rofi) + * | output->layer_tree[2] | top layer surfaces (e.g. waybar) + * | server->unmanaged_tree | unmanaged X11 surfaces (e.g. dmenu) + * | server->xdg_popup_tree | xdg popups on xdg windows + * | server->view_tree_always_on_top | always-on-top xdg/X11 windows + * | server->view_tree | normal xdg/X11 windows (e.g. firefox) + * | server->view_tree_always_on_bottom | always-on-bottom xdg/X11 windows + * | output->layer_tree[1] | bottom layer surfaces + * | output->layer_tree[0] | background layer surfaces (e.g. swaybg) */ if (server->renderer->features.input_color_transform) { @@ -633,6 +636,7 @@ server_init(struct server *server) server->unmanaged_tree = wlr_scene_tree_create(&server->scene->tree); #endif server->menu_tree = wlr_scene_tree_create(&server->scene->tree); + server->cycle_preview_tree = wlr_scene_tree_create(&server->scene->tree); workspaces_init(server); diff --git a/src/view.c b/src/view.c index c439b443..20f3bbdc 100644 --- a/src/view.c +++ b/src/view.c @@ -614,7 +614,7 @@ view_move_relative(struct view *view, int x, int y) if (view->fullscreen) { return; } - view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false); + view_maximize(view, VIEW_AXIS_NONE); if (view_is_tiled(view)) { view_set_untiled(view); view_move_resize(view, view->natural_geometry); @@ -632,7 +632,7 @@ view_move_to_cursor(struct view *view) return; } view_set_fullscreen(view, false); - view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false); + view_maximize(view, VIEW_AXIS_NONE); if (view_is_tiled(view)) { view_set_untiled(view); view_move_resize(view, view->natural_geometry); @@ -1375,9 +1375,15 @@ view_set_untiled(struct view *view) view_notify_tiled(view); } +static bool +in_interactive_move(struct view *view) +{ + return (view->server->input_mode == LAB_INPUT_STATE_MOVE + && view->server->grabbed_view == view); +} + void -view_maximize(struct view *view, enum view_axis axis, - bool store_natural_geometry) +view_maximize(struct view *view, enum view_axis axis) { assert(view); @@ -1389,6 +1395,7 @@ view_maximize(struct view *view, enum view_axis axis, return; } + bool store_natural_geometry = !in_interactive_move(view); view_set_shade(view, false); if (axis != VIEW_AXIS_NONE) { @@ -1440,8 +1447,7 @@ view_toggle_maximize(struct view *view, enum view_axis axis) case VIEW_AXIS_HORIZONTAL: case VIEW_AXIS_VERTICAL: /* Toggle one axis (XOR) */ - view_maximize(view, view->maximized ^ axis, - /*store_natural_geometry*/ true); + view_maximize(view, view->maximized ^ axis); break; case VIEW_AXIS_BOTH: /* @@ -1449,8 +1455,7 @@ view_toggle_maximize(struct view *view, enum view_axis axis) * maximized, otherwise unmaximize. */ view_maximize(view, (view->maximized == VIEW_AXIS_BOTH) ? - VIEW_AXIS_NONE : VIEW_AXIS_BOTH, - /*store_natural_geometry*/ true); + VIEW_AXIS_NONE : VIEW_AXIS_BOTH); break; default: break; @@ -2064,7 +2069,7 @@ view_placement_parse(const char *policy) void view_snap_to_edge(struct view *view, enum lab_edge edge, - bool across_outputs, bool combine, bool store_natural_geometry) + bool across_outputs, bool combine) { assert(view); @@ -2078,6 +2083,7 @@ view_snap_to_edge(struct view *view, enum lab_edge edge, return; } + bool store_natural_geometry = !in_interactive_move(view); view_set_shade(view, false); if (lab_edge_is_cardinal(edge) && view->maximized == VIEW_AXIS_NONE @@ -2124,8 +2130,7 @@ view_snap_to_edge(struct view *view, enum lab_edge edge, if (view->maximized != VIEW_AXIS_NONE) { /* Unmaximize + keep using existing natural_geometry */ - view_maximize(view, VIEW_AXIS_NONE, - /*store_natural_geometry*/ false); + view_maximize(view, VIEW_AXIS_NONE); } else if (store_natural_geometry) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); @@ -2139,8 +2144,7 @@ view_snap_to_edge(struct view *view, enum lab_edge edge, } void -view_snap_to_region(struct view *view, struct region *region, - bool store_natural_geometry) +view_snap_to_region(struct view *view, struct region *region) { assert(view); assert(region); @@ -2155,12 +2159,12 @@ view_snap_to_region(struct view *view, struct region *region, return; } + bool store_natural_geometry = !in_interactive_move(view); view_set_shade(view, false); if (view->maximized != VIEW_AXIS_NONE) { /* Unmaximize + keep using existing natural_geometry */ - view_maximize(view, VIEW_AXIS_NONE, - /*store_natural_geometry*/ false); + view_maximize(view, VIEW_AXIS_NONE); } else if (store_natural_geometry) { /* store current geometry as new natural_geometry */ view_store_natural_geometry(view); @@ -2193,7 +2197,7 @@ view_move_to_output(struct view *view, struct output *output) view_apply_tiled_geometry(view); } else if (view->tiled_region) { struct region *region = regions_from_name(view->tiled_region->name, output); - view_snap_to_region(view, region, /*store_natural_geometry*/ false); + view_snap_to_region(view, region); } } diff --git a/src/workspaces.c b/src/workspaces.c index f56d170a..7b0b8e96 100644 --- a/src/workspaces.c +++ b/src/workspaces.c @@ -182,6 +182,33 @@ _osd_update(struct server *server) } } +static struct workspace * +workspace_find_by_name(struct server *server, const char *name) +{ + struct workspace *workspace; + + /* by index */ + size_t parsed_index = parse_workspace_index(name); + if (parsed_index) { + size_t index = 0; + wl_list_for_each(workspace, &server->workspaces.all, link) { + if (parsed_index == ++index) { + return workspace; + } + } + } + + /* by name */ + wl_list_for_each(workspace, &server->workspaces.all, link) { + if (!strcmp(workspace->name, name)) { + return workspace; + } + } + + wlr_log(WLR_ERROR, "Workspace '%s' not found", name); + return NULL; +} + /* cosmic workspace handlers */ static void handle_cosmic_workspace_activate(struct wl_listener *listener, void *data) @@ -209,18 +236,11 @@ add_workspace(struct server *server, const char *name) workspace->name = xstrdup(name); workspace->tree = wlr_scene_tree_create(server->view_tree); wl_list_append(&server->workspaces.all, &workspace->link); - if (!server->workspaces.current) { - server->workspaces.current = workspace; - } else { - wlr_scene_node_set_enabled(&workspace->tree->node, false); - } - - bool active = server->workspaces.current == workspace; + wlr_scene_node_set_enabled(&workspace->tree->node, false); /* cosmic */ workspace->cosmic_workspace = lab_cosmic_workspace_create(server->workspaces.cosmic_group); lab_cosmic_workspace_set_name(workspace->cosmic_workspace, name); - lab_cosmic_workspace_set_active(workspace->cosmic_workspace, active); workspace->on_cosmic.activate.notify = handle_cosmic_workspace_activate; wl_signal_add(&workspace->cosmic_workspace->events.activate, @@ -231,7 +251,6 @@ add_workspace(struct server *server, const char *name) server->workspaces.ext_manager, /*id*/ NULL); lab_ext_workspace_assign_to_group(workspace->ext_workspace, server->workspaces.ext_group); lab_ext_workspace_set_name(workspace->ext_workspace, name); - lab_ext_workspace_set_active(workspace->ext_workspace, active); workspace->on_ext.activate.notify = handle_ext_workspace_activate; wl_signal_add(&workspace->ext_workspace->events.activate, @@ -398,6 +417,27 @@ workspaces_init(struct server *server) wl_list_for_each(conf, &rc.workspace_config.workspaces, link) { add_workspace(server, conf->name); } + + /* + * After adding workspaces, check if there is an initial workspace + * selected and set that as the initial workspace. + */ + char *initial_name = rc.workspace_config.initial_workspace_name; + struct workspace *initial = NULL; + struct workspace *first = wl_container_of( + server->workspaces.all.next, first, link); + + if (initial_name) { + initial = workspace_find_by_name(server, initial_name); + } + if (!initial) { + initial = first; + } + + server->workspaces.current = initial; + wlr_scene_node_set_enabled(&initial->tree->node, true); + lab_cosmic_workspace_set_active(initial->cosmic_workspace, true); + lab_ext_workspace_set_active(initial->ext_workspace, true); } /* @@ -507,21 +547,13 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap) if (!name) { return NULL; } - size_t index = 0; - struct workspace *target; - size_t wants_index = parse_workspace_index(name); - struct wl_list *workspaces = &anchor->server->workspaces.all; + struct server *server = anchor->server; + struct wl_list *workspaces = &server->workspaces.all; - if (wants_index) { - wl_list_for_each(target, workspaces, link) { - if (wants_index == ++index) { - return target; - } - } - } else if (!strcasecmp(name, "current")) { + if (!strcasecmp(name, "current")) { return anchor; } else if (!strcasecmp(name, "last")) { - return anchor->server->workspaces.last; + return server->workspaces.last; } else if (!strcasecmp(name, "left")) { return get_prev(anchor, workspaces, wrap); } else if (!strcasecmp(name, "right")) { @@ -530,15 +562,8 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap) return get_prev_occupied(anchor, workspaces, wrap); } else if (!strcasecmp(name, "right-occupied")) { return get_next_occupied(anchor, workspaces, wrap); - } else { - wl_list_for_each(target, workspaces, link) { - if (!strcasecmp(target->name, name)) { - return target; - } - } } - wlr_log(WLR_ERROR, "Workspace '%s' not found", name); - return NULL; + return workspace_find_by_name(server, name); } static void diff --git a/src/xdg.c b/src/xdg.c index 9ff24541..b239e8da 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -225,8 +225,7 @@ handle_commit(struct wl_listener *listener, void *data) set_fullscreen_from_request(view, &toplevel->requested); } if (toplevel->requested.maximized) { - view_maximize(view, VIEW_AXIS_BOTH, - /*store_natural_geometry*/ true); + view_maximize(view, VIEW_AXIS_BOTH); } return; } @@ -505,8 +504,7 @@ handle_request_maximize(struct wl_listener *listener, void *data) view_set_output(view, output_nearest_to_cursor(view->server)); } bool maximized = toplevel->requested.maximized; - view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE, - /*store_natural_geometry*/ true); + view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE); } static void diff --git a/src/xwayland.c b/src/xwayland.c index 17eb995e..9b30c2c8 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -470,7 +470,7 @@ handle_request_maximize(struct wl_listener *listener, void *data) if (surf->maximized_horz) { maximize |= VIEW_AXIS_HORIZONTAL; } - view_maximize(view, maximize, /*store_natural_geometry*/ true); + view_maximize(view, maximize); } static void @@ -704,7 +704,7 @@ handle_map_request(struct wl_listener *listener, void *data) if (xsurface->maximized_vert) { axis |= VIEW_AXIS_VERTICAL; } - view_maximize(view, axis, /*store_natural_geometry*/ true); + view_maximize(view, axis); /* * We could also call set_initial_position() here, but it's not * really necessary until the view is actually mapped (and at @@ -1200,60 +1200,11 @@ xwayland_server_init(struct server *server, struct wlr_compositor *compositor) server->seat.xcursor_manager, XCURSOR_DEFAULT, 1); if (xcursor) { struct wlr_xcursor_image *image = xcursor->images[0]; - wlr_xwayland_set_cursor(server->xwayland, image->buffer, - image->width * 4, image->width, - image->height, image->hotspot_x, - image->hotspot_y); - } -} - -void -xwayland_reset_cursor(struct server *server) -{ - /* - * As xwayland caches the pixel data when not yet started up - * due to the delayed lazy startup approach, we do have to - * re-set the xwayland cursor image. Otherwise the first X11 - * client connected will cause the xwayland server to use - * the cached (and potentially destroyed) pixel data. - * - * Calling this function after reloading the cursor theme - * ensures that the cached pixel data keeps being valid. - * - * To reproduce: - * - Compile with b_sanitize=address,undefined - * - Start labwc (nothing in autostart that could create - * a X11 connection, e.g. no GTK or X11 application) - * - Reconfigure - * - Start some X11 client - */ - - if (!server->xwayland) { - return; - } - - struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor( - server->seat.xcursor_manager, XCURSOR_DEFAULT, 1); - - if (xcursor && !server->xwayland->xwm) { - /* Prevents setting the cursor on an active xwayland server */ - struct wlr_xcursor_image *image = xcursor->images[0]; - wlr_xwayland_set_cursor(server->xwayland, image->buffer, - image->width * 4, image->width, - image->height, image->hotspot_x, - image->hotspot_y); - return; - } - - if (server->xwayland->cursor) { - /* - * The previous configured theme has set the - * default cursor or the xwayland server is - * currently running but still has a cached - * xcursor set that will be used on the next - * xwayland destroy -> lazy startup cycle. - */ - zfree(server->xwayland->cursor); + struct wlr_buffer *cursor_buffer = wlr_xcursor_image_get_buffer(image); + if (cursor_buffer) { + wlr_xwayland_set_cursor(server->xwayland, cursor_buffer, + image->hotspot_x, image->hotspot_y); + } } }