Merge remote-tracking branch 'upstream/master' into chase/0.20

This commit is contained in:
Manuel Barrio Linares 2025-12-26 21:41:13 -03:00
commit c93c3ad6f4
49 changed files with 1552 additions and 1040 deletions

103
NEWS.md
View file

@ -9,7 +9,7 @@ The format is based on [Keep a Changelog]
| Date | All Changes | wlroots version | lines-of-code | | 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-10-10 | [0.9.2] | 0.19.1 | 28818 |
| 2025-08-02 | [0.9.1] | 0.19.0 | 28605 | | 2025-08-02 | [0.9.1] | 0.19.0 | 28605 |
| 2025-07-11 | [0.9.0] | 0.19.0 | 28586 | | 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 | | 2021-03-05 | [0.1.0] | 0.12.0 | 4627 |
[unreleased]: NEWS.md#unreleased [unreleased]: NEWS.md#unreleased
[0.9.3]: NEWS.md#093---2025-12-19
[0.9.2]: NEWS.md#092---2025-10-10 [0.9.2]: NEWS.md#092---2025-10-10
[0.9.1]: NEWS.md#091---2025-08-02 [0.9.1]: NEWS.md#091---2025-08-02
[0.9.0]: NEWS.md#090---2025-07-11 [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 with the environment variable `LABWC_FALLBACK_OUTPUT=NOOP-fallback` to
temporarily create a fallback-output when the last physical display temporarily create a fallback-output when the last physical display
disconnects. [#2914] [#2939] [wlroots-4878] [gtk-8792] 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 - 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 press-drag-release mouse action. For context: This is due to ambiguity in the
specifications and contrary implementations. For example, Gtk applications are 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 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 block the release due to this regression as it is an eco-system wide issue
that has existed for a long time. [#2787] that has existed for a long time. [#2787]
- VR headset support is disabled when compiled with wlroots `0.19.0` to work - It is strongly recommended to use at least wlroots 0.19.1 [#2943]
around a bug on the wlroots side which is expected to be fixed in wlroots [wlroots-5098] [#2887]
`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].
[wlroots-4878]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4878 [wlroots-4878]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4878
[wlroots-5098]:https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5098 [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 [gtk-8792]: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8792
## unreleased ## unreleased
[unreleased-commits] [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 `<theme><name></name></theme>`. There
are only a few areas left where empty string are ignored (like under
`<libinput>`) 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 ### Added
- Add `<windowSwitcher order="focus|age"/>` to optionally order windows by age
rather than most recent focus. @mbroemme [#3229]
- Replace `<snapping><range>` with `<snapping><range inner="" outer="">` to
provide more granular control when configuring the size of snapping areas
(including `<topMaximize>`) 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 - Allow the use of the `sendEventsMode` configuration option on keyboards in
order to disable keyboard input. @cillian64 [#3208] order to disable keyboard input. @cillian64 [#3208]
@ -131,8 +157,8 @@ differently [#3099]. There is a pending fix [wlroots-5159].
- Support the following new `<windowSwitcher>` configuration options: - Support the following new `<windowSwitcher>` configuration options:
- `<osd thumbnailLabelFormat="%T">` to specify the label text in each item in - `<osd thumbnailLabelFormat="%T">` to specify the label text in each item in
the thumbnail style window-switcher. @elviosak [#3187] the thumbnail style window-switcher. @elviosak [#3187]
- `<osd output="all|pointer|keyboard">` to specify which monitor(s) to show - `<osd output="all|focused|cursor">` to specify which monitor(s) to show
the OSD(s) on. @dntxi [#3201] the OSD(s) on. @dntxi [#3201] [#3248]
- Support window-switcher OSD item click to focus window @tokyo4j [#3186] - Support window-switcher OSD item click to focus window @tokyo4j [#3186]
- With the window-switcher custom field state specifiers 's' and 'S', show 's' - With the window-switcher custom field state specifiers 's' and 'S', show 's'
for shaded window @domo141 [#2895] for shaded window @domo141 [#2895]
@ -148,6 +174,28 @@ differently [#3099]. There is a pending fix [wlroots-5159].
### Fixed ### 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 - Update layer-shell client top layer visiblity on unmap instead of destroy
because it is possible for fullscreen xwayland windows to be unmapped without 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 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 ### Changed
- `<snapping><range>` is deprecated. Use `<snapping><range inner="" outer="">`
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` - Refactor window switcher configuration to put attributes `show` and `style`
under `<windowSwitcher><osd>` rather than directly under `<windowSwitcher>`. under `<windowSwitcher><osd>` rather than directly under `<windowSwitcher>`.
The old configuration syntax will remain supported for at least one release. 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 ShowMenu
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [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.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.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 [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 [#3145]: https://github.com/labwc/labwc/pull/3145
[#3146]: https://github.com/labwc/labwc/pull/3146 [#3146]: https://github.com/labwc/labwc/pull/3146
[#3148]: https://github.com/labwc/labwc/pull/3148 [#3148]: https://github.com/labwc/labwc/pull/3148
[#3152]: https://github.com/labwc/labwc/pull/3152
[#3153]: https://github.com/labwc/labwc/pull/3153 [#3153]: https://github.com/labwc/labwc/pull/3153
[#3157]: https://github.com/labwc/labwc/pull/3157 [#3157]: https://github.com/labwc/labwc/pull/3157
[#3158]: https://github.com/labwc/labwc/pull/3158 [#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 [#3199]: https://github.com/labwc/labwc/pull/3199
[#3201]: https://github.com/labwc/labwc/pull/3201 [#3201]: https://github.com/labwc/labwc/pull/3201
[#3208]: https://github.com/labwc/labwc/pull/3208 [#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

View file

@ -22,6 +22,13 @@
# XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle # XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle
# XKB_DEFAULT_OPTIONS=grp:shift_caps_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: ## Set cursor theme and size. Find system icons themes with:
## `find /usr/share/icons/ -type d -name "cursors"` ## `find /usr/share/icons/ -type d -name "cursors"`

View file

@ -140,4 +140,3 @@ labnag \\
--button-border-size 2\\ --button-border-size 2\\
-t 60 -t 60
``` ```

View file

@ -51,9 +51,13 @@ Actions are used in menus and keyboard/mouse bindings.
another window or screen edge. If set to "no", only move to another window or screen edge. If set to "no", only move to
the next screen edge. Default is yes. the next screen edge. Default is yes.
*<action name="Resize" />* *<action name="Resize" direction="value" />*
Begin interactive resize of window under cursor. Begin interactive resize of window under cursor.
*direction* [up|down|left|right|up-left|up-right|down-left|down-right]
Edge or corner from which to start resizing. If this is not provided,
the direction is inferred from the cursor position.
*<action name="ResizeRelative" left="" right="" top="" bottom="" />* *<action name="ResizeRelative" left="" right="" top="" bottom="" />*
Resize window relative to its current size. Values of left, right, Resize window relative to its current size. Values of left, right,
top or bottom tell how much to resize on that edge of window, top or bottom tell how much to resize on that edge of window,
@ -121,13 +125,25 @@ Actions are used in menus and keyboard/mouse bindings.
Resize and move the active window back to its untiled or unmaximized 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. position if it had been maximized or tiled to a direction or region.
*<action name="NextWindow" />*++ *<action name="NextWindow" workspace="current" output="all" identifier="all" />*++
*<action name="PreviousWindow" />* *<action name="PreviousWindow" workspace="current" output="all" identifier="all" />*
Cycle focus to next/previous window, respectively. 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".
*<action name="Reconfigure" />* *<action name="Reconfigure" />*
Re-load configuration and theme files. Re-load configuration and theme files.
@ -375,7 +391,7 @@ Actions are used in menus and keyboard/mouse bindings.
*x* [center|value] Specifies the horizontal warp position within the *x* [center|value] Specifies the horizontal warp position within the
target area. "center": Moves the cursor to the horizontal center of 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 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" the target area, respectively. Default is "center"
*y* [center|value] Equivalent for the vertical warp position within the *y* [center|value] Equivalent for the vertical warp position within the

View file

@ -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. 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 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 file encountered is processed in turn. Thus, user-specific files will augment
system-wide configurations, with conflicts favoring the user-specific system-wide configurations, with conflicts favoring the user-specific
alternative. alternative.
@ -339,7 +339,7 @@ this is for compatibility with Openbox.
## WINDOW SWITCHER ## WINDOW SWITCHER
``` ```
<windowSwitcher preview="yes" outlines="yes" allWorkspaces="no"> <windowSwitcher preview="yes" outlines="yes">
<osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" /> <osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" />
<fields> <fields>
<field content="icon" width="5%" /> <field content="icon" width="5%" />
@ -349,37 +349,39 @@ this is for compatibility with Openbox.
</windowSwitcher> </windowSwitcher>
``` ```
*<windowSwitcher preview="" outlines="" allWorkspaces="" unshade="">* *<windowSwitcher preview="" outlines="" unshade="" order="">*
*preview* [yes|no] Preview the contents of the selected window when *preview* [yes|no] Preview the contents of the selected window when
switching between windows. Default is yes. switching between windows. Default is yes.
*outlines* [yes|no] Draw an outline around the selected window when *outlines* [yes|no] Draw an outline around the selected window when
switching between windows. Default is yes. 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 *unshade* [yes|no] Temporarily unshade windows when switching between
them and permanently unshade on the final selection. Default is yes. them and permanently unshade on the final selection. Default is yes.
*<osd show="" style="" output="" thumbnailLabelFormat="" />* *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 doesnt change on focus. Default is *focus*.
*<windowSwitcher><osd show="" style="" output="" thumbnailLabelFormat="" />*
*show* [yes|no] Draw the OnScreenDisplay when switching between *show* [yes|no] Draw the OnScreenDisplay when switching between
windows. Default is yes. windows. Default is yes.
*style* [classic|thumbnail] Configures the style of the OSD. *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. "thumbnail" shows window thumbnail, icon and title in grids.
*output* [all|pointer|keyboard] Configures which monitor(s) show the OSD. *output* [all|focused|cursor] Configures which monitor(s) show the OSD.
"all" displays the OSD on all monitors. "all" displays the OSD on all monitors.
"pointer" displays the OSD on the monitor containing the mouse pointer. "focused" displays the OSD on the monitor with keyboard focus.
"keyboard" displays the OSD on the monitor with keyboard focus. "cursor" displays the OSD on the monitor containing the mouse pointer.
Default is "all". Default is "all".
*thumbnailLabelFormat* Format to be used for the thumbnail label according to *custom* *thumbnailLabelFormat* Format to be used for the thumbnail label
field below, only applied when using *<osd style="thumbnail" />*. according to *custom* field below, only applied when using
Default is "%T". *<osd style="thumbnail" />*. Default is "%T".
*<windowSwitcher><fields><field content="" width="%">* *<windowSwitcher><fields><field content="" width="%">*
Define window switcher fields when using *<osd style="classic" />*. Define window switcher fields when using *<osd style="classic" />*.
@ -498,13 +500,16 @@ activated with SnapToEdge actions or, optionally, by dragging windows to the
edges of an output. Edge snapping causes a window to occupy half of its output, edges of an output. Edge snapping causes a window to occupy half of its output,
extending outward from the snapped edge. extending outward from the snapped edge.
*<snapping><range>*++ *<snapping><range><inner>*++
*<snapping><range><outer>*++
*<snapping><cornerRange>* *<snapping><cornerRange>*
If an interactive move ends with the cursor within *<range>* pixels of an If an interactive move ends with the cursor within *inner* or *outer*
output edge, the window is snapped to the edge. If it's also within pixels of an output edge, the window is snapped to the edge. *inner*
*<cornerRange>* pixels of an output corner, the window is snapped to the edges are edges with an adjacent output and *outer* edges are edges
corner instead. A *<range>* of 0 disables snapping. without an adjacent output. If it's also within *<cornerRange>* pixels
Default is 10 for *<range>* and 50 for *<cornerRange>*. 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
*<range><inner>* and *<range><outer>*, and 50 for *<cornerRange>*.
*<snapping><overlay><enabled>* [yes|no] *<snapping><overlay><enabled>* [yes|no]
Show an overlay when snapping to a window to an edge. Default is yes. Show an overlay when snapping to a window to an edge. Default is yes.
@ -525,8 +530,8 @@ extending outward from the snapped edge.
*<snapping><notifyClient>* [always|region|edge|never] *<snapping><notifyClient>* [always|region|edge|never]
Snapping windows can trigger corresponding tiling events for native Snapping windows can trigger corresponding tiling events for native
Wayland clients. Clients may use these events to alter their rendering 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 based on knowledge that some edges of the window are confined to edges
a snapping region or output. For example, rounded corners may become 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 square when tiled, or media players may letter-box or pillar-box video
rather than imposing rigid aspect ratios on windows that will violate rather than imposing rigid aspect ratios on windows that will violate
the constraints of window snapping. the constraints of window snapping.
@ -566,6 +571,11 @@ extending outward from the snapped edge.
is 1. The number attribute is optional. If the number attribute is is 1. The number attribute is optional. If the number attribute is
specified, names.name is not required. specified, names.name is not required.
*<desktops><initial>*
Define the initial starting workspace. This must match one of the names
defined in <names> or must be an index equal to or lower than <number>.
If not set, the first workspace is used.
*<desktops><popupTime>* *<desktops><popupTime>*
Define the timeout after which to hide the workspace OSD. Define the timeout after which to hide the workspace OSD.
A setting of 0 disables the OSD. Default is 1000 ms. A setting of 0 disables the OSD. Default is 1000 ms.
@ -616,15 +626,16 @@ extending outward from the snapped edge.
*<theme><maximizedDecoration>* [titlebar|none] *<theme><maximizedDecoration>* [titlebar|none]
Specify how server side decorations are shown for maximized windows. Specify how server side decorations are shown for maximized windows.
*titlebar* shows titlebar above a maximized window. *none* shows no server *titlebar* shows titlebar above a maximized window. *none* shows no
side decorations around a maximized window. Default is titlebar. server side decorations around a maximized window. Default is titlebar.
*<theme><dropShadows>* [yes|no] *<theme><dropShadows>* [yes|no]
Should drop-shadows be rendered behind windows. Default is no. Should drop-shadows be rendered behind windows. Default is no.
*<theme><dropShadowsOnTiled>* [yes|no] *<theme><dropShadowsOnTiled>* [yes|no]
Should drop-shadows be rendered behind tiled windows. This won't take Should drop-shadows be rendered behind tiled windows. This won't take
effect if <core><gap> is smaller than window.active.shadow.size in theme. effect if <core><gap> is smaller than window.active.shadow.size in
theme.
Default is no. Default is no.
@ -700,6 +711,45 @@ extending outward from the snapped edge.
invisible zones just beyond the window that serve as click targets for invisible zones just beyond the window that serve as click targets for
mouse actions. Default is 8. mouse actions. Default is 8.
# INPUT CONFIGURATION
This section describes configuration of input devices including:
- Keyboards
- Mice
- Touchpads (sometimes referred to as laptop trackpads)
- Touchscreens
- Tablets
- Tablet tools (like stylus pens)
It aims to clarify related terminology and separation of concerns.
Keyboards are configured in the *<keyboard>* section below and are simple in
this regard.
Touchpads and mice are harder. They are both considered to be *pointer* devices
by the compositor, and can be configured in the *<mouse>* and *<libinput>*
sections. Any setting that is supported by libinput is configured in the
*<libinput>* section, and anything else is in *<mouse>*. Touchpad devices can
generate gesture events, like swipe and pinch. There are some related settings
(e.g. *threeFingerDrag* and *twoFingerScroll*) in the *<libinput>* section.
In the Wayland Compositor domain, events associated with touchscreens are
sometimes simply referred to as *touch* events. Touchscreens can be configured
in both the *<touch>* and *<libinput>* 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.
Tablets are considered special by libinput although in the eyes of the Wayland
protocol they are merely a *touch* capability. Tablets and associated tablet
tools are configured in the *<tablet>*, *<tablettool>* and *<libinput>*
sections. Note that the term *tablet* in libinput (and labwc) refers to graphics
tablets only (e.g. Wacom Intuos), not to tablet devices like the Apple iPad.
References:
- https://wayland.freedesktop.org/libinput/doc/latest/tablet-support.html
- https://wayland.freedesktop.org/libinput/doc/latest/gestures.html
## KEYBOARD ## KEYBOARD
*<keyboard><numlock>* [on|off] *<keyboard><numlock>* [on|off]
@ -808,6 +858,9 @@ extending outward from the snapped edge.
## MOUSE ## MOUSE
This section relates to mice and touchpads - which are both considered pointer
input-devices by the Wayland protocol.
*<mouse><doubleClickTime>* *<mouse><doubleClickTime>*
Set double click time in milliseconds. Default is 500. Set double click time in milliseconds. Default is 500.
@ -918,6 +971,11 @@ extending outward from the snapped edge.
## TOUCH ## TOUCH
This section relates to touchscreens and *not* touchpads.
Note: To rotate touch events with output rotation, use the libinput
*calibrationMatrix* setting.
``` ```
<touch deviceName="" mapToOutput="" mouseEmulation="no"/> <touch deviceName="" mapToOutput="" mouseEmulation="no"/>
``` ```
@ -993,7 +1051,7 @@ extending outward from the snapped edge.
*<tablet><map button="" to="" />* *<tablet><map button="" to="" />*
Pen and pad buttons behave like regular mouse buttons.With mouse 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, 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 the second pen button to the middle mouse button and a third pen
button is mapped to the side mouse button. button is mapped to the side mouse button.
@ -1012,10 +1070,10 @@ extending outward from the snapped edge.
When using mouse emulation, all pen buttons emulate regular mouse When using mouse emulation, all pen buttons emulate regular mouse
buttons. The tip, stylus and pad buttons can be mapped to all 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 mapped to left mouse click, the first pen button (Stylus) is mapped
to right mouse button click and the second pen button (Stylus2) 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. are by default mapped to their (regular) mouse counterparts.
Supported map *buttons* for mouse emulation are: Supported map *buttons* for mouse emulation are:
@ -1080,14 +1138,9 @@ extending outward from the snapped edge.
attribute is provided, a 'default' device profile will created that will attribute is provided, a 'default' device profile will created that will
act as the fallback for all libinput devices. Category can be set to any act as the fallback for all libinput devices. Category can be set to any
of the following types: of the following types:
- *touch* - Devices which have a defined width/height, but do not - *touch* - Includes touchscreens and drawing-tablets.
support multitouch (i.e. they cannot track multiple locations where - *touchpad* - Includes touchpads (also known as laptop trackpads)
the screen has been touched). Drawing tablets typically fall into this - *non-touch* - Includes traditional mice
type.
- *touchpad* - Same as 'touch' but support multitouch. This typically
includes laptop track pads with two-finger scroll and swipe gestures.
- *non-touch* - Anything not described above, for example traditional
mouse pointers.
- *default* - Defines a device-category applicable to all devices not - *default* - Defines a device-category applicable to all devices not
matched by anything else. This can be useful for a fallback, or if you matched by anything else. This can be useful for a fallback, or if you
want the same settings to be applied to all devices. want the same settings to be applied to all devices.
@ -1130,11 +1183,12 @@ extending outward from the snapped edge.
a tap immediately followed by a finger down as the start of a drag. a tap immediately followed by a finger down as the start of a drag.
*<libinput><device><dragLock>* [yes|no|timeout] *<libinput><device><dragLock>* [yes|no|timeout]
Enable or disable drag lock for this category. Drag lock ignores a temporary Enable or disable drag lock for this category. Drag lock ignores a
release of a finger during tap-and-dragging. temporary release of a finger during tap-and-dragging.
*timeout* also enables drag lock, but with a timeout: if your fingers are *timeout* also enables drag lock, but with a timeout: if your fingers
released for a certain amount of time, the drag gesture is cancelled. 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*. In libinput < 1.27, the behavior of *yes* is equivalent to *timeout*.
*<libinput><device><threeFingerDrag>* [yes|no|3|4] *<libinput><device><threeFingerDrag>* [yes|no|3|4]

View file

@ -308,8 +308,8 @@ all are supported.
See below for details. See below for details.
*osd.window-switcher.style-classic.width* *osd.window-switcher.style-classic.width*
Width of window switcher in pixels. Width can also be a percentage of the Width of window switcher in pixels. Width can also be a percentage of
monitor width by adding '%' as suffix (e.g. 70%). Default is 600. the monitor width by adding '%' as suffix (e.g. 70%). Default is 600.
*osd.window-switcher.style-classic.padding* *osd.window-switcher.style-classic.padding*
Padding of window switcher in pixels. This is the space between the 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* *osd.window-switcher.style-classic.item.icon.size*
Size of the icon in window switcher, in pixels. Size of the icon in window switcher, in pixels.
If not set, the font size derived from <theme><font place="OnScreenDisplay"> If not set, the font size derived from
is used. <theme><font place="OnScreenDisplay"> is used.
*osd.window-switcher.style-thumbnail* *osd.window-switcher.style-thumbnail*
Theme for window switcher when using <windowSwitcher style="thumbnail" />. Theme for window switcher when using
See below for details. <windowSwitcher style="thumbnail" />. See below for details.
*osd.window-switcher.style-thumbnail.width.max* *osd.window-switcher.style-thumbnail.width.max*
Maximum width of window switcher in pixels. Width can also be a percentage of Maximum width of window switcher in pixels. Width can also be a
the monitor width by adding '%' as suffix (e.g. 70%). Default is 80%. percentage of the monitor width by adding '%' as suffix (e.g. 70%).
Default is 80%.
*osd.window-switcher.style-thumbnail.padding* *osd.window-switcher.style-thumbnail.padding*
Padding of window switcher in pixels. This is the space between the 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. Height of window switcher items in pixels. Default is 250.
*osd.window-switcher.style-thumbnail.item.padding* *osd.window-switcher.style-thumbnail.item.padding*
Padding of window switcher items in pixels. This is the space between the Padding of window switcher items in pixels. This is the space between
border around selected items and window thumbnail. Default is 2. the border around selected items and window thumbnail. Default is 2.
*osd.window-switcher.style-thumbnail.item.active.border.width* *osd.window-switcher.style-thumbnail.item.active.border.width*
Border width of selected window switcher items in pixels. Default is 2. Border width of selected window switcher items in pixels. Default is 2.

View file

@ -6,7 +6,7 @@
<labwc_config> <labwc_config>
<theme> <theme>
<name></name> <name>Clearlooks-3.4</name>
<cornerRadius>8</cornerRadius> <cornerRadius>8</cornerRadius>
<font name="sans" size="10" /> <font name="sans" size="10" />
</theme> </theme>

View file

@ -33,8 +33,8 @@
<!-- <font><theme> can be defined without an attribute to set all places --> <!-- <font><theme> can be defined without an attribute to set all places -->
<theme> <theme>
<name></name> <!-- <name>Numix</name> -->
<icon></icon> <!-- <icon>breeze</icon> -->
<fallbackAppIcon>labwc</fallbackAppIcon> <fallbackAppIcon>labwc</fallbackAppIcon>
<titlebar> <titlebar>
<layout>icon:iconify,max,close</layout> <layout>icon:iconify,max,close</layout>
@ -77,7 +77,7 @@
</font> </font>
</theme> </theme>
<windowSwitcher preview="yes" outlines="yes" allWorkspaces="no" unshade="yes"> <windowSwitcher preview="yes" outlines="yes" unshade="yes">
<osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" /> <osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" />
<fields> <fields>
<field content="icon" width="5%" /> <field content="icon" width="5%" />
@ -98,7 +98,7 @@
Some contents are fixed-length and others are variable-length. Some contents are fixed-length and others are variable-length.
See "man 5 labwc-config" for details. See "man 5 labwc-config" for details.
<windowSwitcher preview="no" outlines="no" allWorkspaces="yes"> <windowSwitcher preview="no" outlines="no">
<osd show="yes" /> <osd show="yes" />
<fields> <fields>
<field content="workspace" width="5%" /> <field content="workspace" width="5%" />
@ -119,7 +119,7 @@
then workspace name, then identifier/app-id, then the window title. then workspace name, then identifier/app-id, then the window title.
It uses 100% of OSD window width. It uses 100% of OSD window width.
<windowSwitcher preview="no" outlines="no" allWorkspaces="yes"> <windowSwitcher preview="no" outlines="no">
<osd show="yes" /> <osd show="yes" />
<fields> <fields>
<field content="custom" format="foobar %b %3s %-10o %-20W %-10i %t" width="100%" /> <field content="custom" format="foobar %b %3s %-10o %-20W %-10i %t" width="100%" />
@ -161,8 +161,8 @@
</focus> </focus>
<snapping> <snapping>
<!-- Set range to 0 to disable window snapping completely --> <!-- Set inner and outer range to 0 to disable window snapping completely -->
<range>10</range> <range inner="10" outer="10" />
<cornerRange>50</cornerRange> <cornerRange>50</cornerRange>
<overlay enabled="yes"> <overlay enabled="yes">
<delay inner="500" outer="500" /> <delay inner="500" outer="500" />
@ -175,6 +175,7 @@
Workspaces can be configured like this: Workspaces can be configured like this:
<desktops> <desktops>
<popupTime>1000</popupTime> <popupTime>1000</popupTime>
<initial>Workspace 1</initial>
<names> <names>
<name>Workspace 1</name> <name>Workspace 1</name>
<name>Workspace 2</name> <name>Workspace 2</name>
@ -218,9 +219,9 @@
space automatically, so <margin> is only intended for other, specialist space automatically, so <margin> is only intended for other, specialist
cases. cases.
If output is left empty, the margin will be applied to all outputs. If 'output' is not provided, the margin will be applied to all outputs.
<margin top="" bottom="" left="" right="" output="" /> <margin top="10" bottom="10" left="10" right="10" output="HDMI-A-1" />
--> -->
<!-- Percent based regions based on output usable area, % char is required --> <!-- Percent based regions based on output usable area, % char is required -->
@ -525,7 +526,11 @@
If mouseEmulation is enabled, all touch up/down/motion events are If mouseEmulation is enabled, all touch up/down/motion events are
translated to mouse button and motion events. translated to mouse button and motion events.
--> -->
<touch deviceName="" mapToOutput="" mouseEmulation="no"/> <touch>
<!-- <deviceName>ELAN2514:00 04F3:2AF1<deviceName> -->
<!-- <mapToOutput>HDMI-A-1</mapToOutput> -->
<mouseEmulation>no</mouseEmulation>
</touch>
<!-- <!--
The tablet cursor movement can be restricted to a single output. The tablet cursor movement can be restricted to a single output.
@ -552,7 +557,8 @@
When using mouse emulation, the pen tip [tip] and the stylus buttons When using mouse emulation, the pen tip [tip] and the stylus buttons
can be set to any available mouse button [Left|Right|Middle|..|Task]. can be set to any available mouse button [Left|Right|Middle|..|Task].
--> -->
<tablet mapToOutput="" rotate="0" mouseEmulation="no"> <tablet rotate="0" mouseEmulation="no">
<!-- <mapToOutput>HDMI-A-1</mapToOutput> -->
<!-- Active area dimensions are in mm --> <!-- Active area dimensions are in mm -->
<area top="0.0" left="0.0" width="0.0" height="0.0" /> <area top="0.0" left="0.0" width="0.0" height="0.0" />
<map button="Tip" to="Left" /> <map button="Tip" to="Left" />
@ -583,10 +589,11 @@
- accelProfile [flat|adaptive] - accelProfile [flat|adaptive]
- tapButtonMap [lrm|lmr] - tapButtonMap [lrm|lmr]
- clickMethod [none|buttonAreas|clickfinger] - clickMethod [none|buttonAreas|clickfinger]
- scrollMethod [twoFinger|edge|none]
- sendEventsMode [yes|no|disabledOnExternalMouse] - sendEventsMode [yes|no|disabledOnExternalMouse]
- calibrationMatrix [six float values split by space] - calibrationMatrix [six float values split by space]
- scrollFactor [float] - scrollFactor [float]
The following <libinput>...</libinput> block may not be complete for The following <libinput>...</libinput> block may not be complete for
your requirements. Default values are device specific. Only set an option your requirements. Default values are device specific. Only set an option
if you require to override the default. Valid values must be inserted. if you require to override the default. Valid values must be inserted.
@ -595,21 +602,21 @@
<libinput> <libinput>
<device category="default"> <device category="default">
<naturalScroll></naturalScroll> <!-- <naturalScroll>no</naturalScroll> -->
<leftHanded></leftHanded> <!-- <leftHanded>no</leftHanded> -->
<pointerSpeed></pointerSpeed> <!-- <pointerSpeed>0.0</pointerSpeed> -->
<accelProfile></accelProfile> <!-- <accelProfile>adaptive</accelProfile> -->
<tap>yes</tap> <tap>yes</tap>
<tapButtonMap></tapButtonMap> <!-- <tapButtonMap>lrm</tapButtonMap> -->
<tapAndDrag></tapAndDrag> <!-- <tapAndDrag>yes</tapAndDrag> -->
<dragLock></dragLock> <!-- <dragLock>yes</dragLock> -->
<threeFingerDrag></threeFingerDrag> <!-- <threeFingerDrag>yes</threeFingerDrag> -->
<middleEmulation></middleEmulation> <!-- <middleEmulation>no</middleEmulation> -->
<disableWhileTyping></disableWhileTyping> <!-- <disableWhileTyping>yes</disableWhileTyping> -->
<clickMethod></clickMethod> <!-- <clickMethod>buttonAreas</clickMethod> -->
<scrollMethod></scrollMethod> <!-- <scrollMethod>twofinger</scrollMethod> -->
<sendEventsMode></sendEventsMode> <!-- <sendEventsMode>yes</sendEventsMode> -->
<calibrationMatrix></calibrationMatrix> <!-- <calibrationMatrix>1 0 0 0 1 0</calibrationMatrix> -->
<scrollFactor>1.0</scrollFactor> <scrollFactor>1.0</scrollFactor>
</device> </device>
</libinput> </libinput>
@ -624,8 +631,8 @@
# must only apply to the first instance of the window with that # must only apply to the first instance of the window with that
# particular 'identifier' or 'title'. # particular 'identifier' or 'title'.
# - Matching is case-insensitive and is performed using shell wildcard # - Matching is case-insensitive and is performed using shell wildcard
# patterns (see glob(7)) so '\*' (not between brackets) matches any string # patterns (see glob(7)) so '\*' (not between brackets) matches any
# and '?' matches any single character. # string and '?' matches any single character.
<windowRules> <windowRules>
<windowRule identifier="*"><action name="Maximize"/></windowRule> <windowRule identifier="*"><action name="Maximize"/></windowRule>
@ -678,7 +685,7 @@
<height>400</height> <height>400</height>
<initScale>2.0</initScale> <initScale>2.0</initScale>
<increment>0.2</increment> <increment>0.2</increment>
<useFilter>true</useFilter> <useFilter>yes</useFilter>
</magnifier> </magnifier>
</labwc_config> </labwc_config>

View file

@ -1,4 +1,5 @@
# Example shutdown file # 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). # For further details see labwc-config(5).

View file

@ -10,6 +10,17 @@ bool box_intersects(struct wlr_box *box_a, struct wlr_box *box_b);
void box_union(struct wlr_box *box_dest, struct wlr_box *box_a, void box_union(struct wlr_box *box_dest, struct wlr_box *box_a,
struct wlr_box *box_b); struct wlr_box *box_b);
/*
* Centers a content box (width & height) within a reference box,
* limiting it (if possible) to not extend outside a bounding box.
*
* The reference box and bounding box are often the same but could be
* different (e.g. when centering a view within its parent but limiting
* to usable output area).
*/
void box_center(int width, int height, const struct wlr_box *ref,
const struct wlr_box *bound, int *x, int *y);
/* /*
* Fits and centers a content box (width & height) within a bounding box. * Fits and centers a content box (width & height) within a bounding box.
* The content box is downscaled if necessary (preserving aspect ratio) but * The content box is downscaled if necessary (preserving aspect ratio) but

View file

@ -47,7 +47,7 @@ enum lab_node_type {
LAB_NODE_FRAME, LAB_NODE_FRAME,
LAB_NODE_ROOT, LAB_NODE_ROOT,
LAB_NODE_MENUITEM, LAB_NODE_MENUITEM,
LAB_NODE_OSD_ITEM, LAB_NODE_CYCLE_OSD_ITEM,
LAB_NODE_LAYER_SURFACE, LAB_NODE_LAYER_SURFACE,
LAB_NODE_UNMANAGED, LAB_NODE_UNMANAGED,
LAB_NODE_ALL, LAB_NODE_ALL,

View file

@ -151,7 +151,8 @@ struct rcxml {
int unmaximize_threshold; int unmaximize_threshold;
/* window snapping */ /* window snapping */
int snap_edge_range; int snap_edge_range_inner;
int snap_edge_range_outer;
int snap_edge_corner_range; int snap_edge_corner_range;
bool snap_overlay_enabled; bool snap_overlay_enabled;
int snap_overlay_delay_inner; int snap_overlay_delay_inner;
@ -167,6 +168,7 @@ struct rcxml {
struct { struct {
int popuptime; int popuptime;
int min_nr_workspaces; int min_nr_workspaces;
char *initial_workspace_name;
char *prefix; char *prefix;
struct wl_list workspaces; /* struct workspace.link */ struct wl_list workspaces; /* struct workspace.link */
} workspace_config; } workspace_config;
@ -176,15 +178,18 @@ struct rcxml {
/* Window Switcher */ /* Window Switcher */
struct { struct {
bool show;
bool preview; bool preview;
bool outlines; bool outlines;
bool unshade; bool unshade;
enum lab_view_criteria criteria; enum window_switcher_order order;
struct wl_list fields; /* struct window_switcher_field.link */ enum cycle_workspace_filter workspace_filter; /* deprecated */
enum window_switcher_style style; struct {
enum osd_output_criteria output_criteria; bool show;
char *thumbnail_label_format; 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; } window_switcher;
struct wl_list window_rules; /* struct window_rule.link */ struct wl_list window_rules; /* struct window_rule.link */

View file

@ -107,15 +107,30 @@ enum lab_window_type {
LAB_WINDOW_TYPE_LEN LAB_WINDOW_TYPE_LEN
}; };
enum window_switcher_style { enum window_switcher_order {
WINDOW_SWITCHER_CLASSIC, WINDOW_SWITCHER_ORDER_FOCUS,
WINDOW_SWITCHER_THUMBNAIL, WINDOW_SWITCHER_ORDER_AGE,
}; };
enum osd_output_criteria { enum cycle_osd_style {
OSD_OUTPUT_ALL, CYCLE_OSD_STYLE_CLASSIC,
OSD_OUTPUT_POINTER, CYCLE_OSD_STYLE_THUMBNAIL,
OSD_OUTPUT_KEYBOARD, };
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 */ #endif /* LABWC_CONFIG_TYPES_H */

109
include/cycle.h Normal file
View file

@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_CYCLE_H
#define LABWC_CYCLE_H
#include <stdbool.h>
#include <wayland-server-core.h>
#include "config/types.h"
struct output;
enum lab_cycle_dir {
LAB_CYCLE_DIR_NONE,
LAB_CYCLE_DIR_FORWARD,
LAB_CYCLE_DIR_BACKWARD,
};
/* TODO: add field with keyboard layout? */
enum cycle_osd_field_content {
LAB_FIELD_NONE = 0,
LAB_FIELD_TYPE,
LAB_FIELD_TYPE_SHORT,
LAB_FIELD_IDENTIFIER,
LAB_FIELD_TRIMMED_IDENTIFIER,
LAB_FIELD_ICON,
LAB_FIELD_DESKTOP_ENTRY_NAME,
LAB_FIELD_TITLE,
LAB_FIELD_TITLE_SHORT,
LAB_FIELD_WORKSPACE,
LAB_FIELD_WORKSPACE_SHORT,
LAB_FIELD_WIN_STATE,
LAB_FIELD_WIN_STATE_ALL,
LAB_FIELD_OUTPUT,
LAB_FIELD_OUTPUT_SHORT,
LAB_FIELD_CUSTOM,
LAB_FIELD_COUNT
};
struct cycle_osd_field {
enum cycle_osd_field_content content;
int width;
char *format;
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 buf;
struct view;
struct server;
struct wlr_scene_node;
/* Begin window switcher */
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);
/* Closes the OSD */
void cycle_finish(struct server *server, bool switch_focus);
/* Re-initialize the window switcher */
void cycle_reinitialize(struct server *server);
/* Focus the clicked window and close OSD */
void cycle_on_cursor_release(struct server *server, struct wlr_scene_node *node);
/* Used by osd.c internally to render window switcher fields */
void cycle_osd_field_get_content(struct cycle_osd_field *field,
struct buf *buf, struct view *view);
/* Sets view info to buf according to format */
void cycle_osd_field_set_custom(struct buf *buf, struct view *view,
const char *format);
/* Used by rcxml.c when parsing the config */
void cycle_osd_field_arg_from_xml_node(struct cycle_osd_field *field,
const char *nodename, const char *content);
bool cycle_osd_field_is_valid(struct cycle_osd_field *field);
void cycle_osd_field_free(struct cycle_osd_field *field);
/* Internal API */
struct cycle_osd_item {
struct view *view;
struct wlr_scene_tree *tree;
struct wl_list link;
};
struct cycle_osd_impl {
/*
* Create a scene-tree of OSD for an output.
* This sets output->cycle_osd.{items,tree}.
*/
void (*create)(struct output *output);
/*
* Update output->cycle_osd.tree to highlight
* server->cycle_state.selected_view.
*/
void (*update)(struct output *output);
};
extern struct cycle_osd_impl cycle_osd_classic_impl;
extern struct cycle_osd_impl cycle_osd_thumbnail_impl;
#endif // LABWC_CYCLE_H

View file

@ -38,6 +38,14 @@ struct cursor_context {
double sx, sy; double sx, sy;
}; };
/* Used to persistently store cursor context (e.g. in seat->pressed) */
struct cursor_context_saved {
struct cursor_context ctx;
struct wl_listener view_destroy;
struct wl_listener node_destroy;
struct wl_listener surface_destroy;
};
/** /**
* get_cursor_context - find view, surface and scene_node at cursor * get_cursor_context - find view, surface and scene_node at cursor
* *
@ -65,6 +73,13 @@ void cursor_set(struct seat *seat, enum lab_cursors cursor);
void cursor_set_visible(struct seat *seat, bool visible); void cursor_set_visible(struct seat *seat, bool visible);
/*
* Safely store a cursor context to saved_ctx. saved_ctx is cleared when either
* of its node, surface and view is destroyed.
*/
void cursor_context_save(struct cursor_context_saved *saved_ctx,
const struct cursor_context *ctx);
/** /**
* cursor_get_resize_edges - calculate resize edge based on cursor position * cursor_get_resize_edges - calculate resize edge based on cursor position
* @cursor - the current cursor (usually server->seat.cursor) * @cursor - the current cursor (usually server->seat.cursor)

View file

@ -5,6 +5,7 @@
#include <wlr/util/box.h> #include <wlr/util/box.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "common/set.h" #include "common/set.h"
#include "cycle.h"
#include "input/cursor.h" #include "input/cursor.h"
#include "overlay.h" #include "overlay.h"
@ -18,7 +19,7 @@ enum input_mode {
LAB_INPUT_STATE_MOVE, LAB_INPUT_STATE_MOVE,
LAB_INPUT_STATE_RESIZE, LAB_INPUT_STATE_RESIZE,
LAB_INPUT_STATE_MENU, LAB_INPUT_STATE_MENU,
LAB_INPUT_STATE_WINDOW_SWITCHER, LAB_INPUT_STATE_CYCLE, /* a.k.a. window switching */
}; };
struct seat { struct seat {
@ -65,8 +66,7 @@ struct seat {
struct input_method_relay *input_method_relay; struct input_method_relay *input_method_relay;
/** /**
* This is usually zeroed and is only set on button press while the * Cursor context saved when a mouse button is pressed on a view/surface.
* mouse is over a view or surface, and zeroed on button release.
* It is used to send cursor motion events to a surface even though * It is used to send cursor motion events to a surface even though
* the cursor has left the surface in the meantime. * the cursor has left the surface in the meantime.
* *
@ -76,10 +76,11 @@ struct seat {
* It is also used to: * It is also used to:
* - determine the target view for action in "Drag" mousebind * - determine the target view for action in "Drag" mousebind
* - validate view move/resize requests from CSD clients * - validate view move/resize requests from CSD clients
*
* Both (view && !surface) and (surface && !view) are possible.
*/ */
struct cursor_context pressed; struct cursor_context_saved pressed;
/* Cursor context of the last cursor motion */
struct cursor_context_saved last_cursor_ctx;
struct lab_set bound_buttons; struct lab_set bound_buttons;
@ -139,7 +140,6 @@ struct seat {
struct wl_list tablet_pads; struct wl_list tablet_pads;
struct wl_listener constraint_commit; struct wl_listener constraint_commit;
struct wl_listener pressed_surface_destroy;
struct wlr_virtual_pointer_manager_v1 *virtual_pointer; struct wlr_virtual_pointer_manager_v1 *virtual_pointer;
struct wl_listener new_virtual_pointer; struct wl_listener new_virtual_pointer;
@ -189,6 +189,7 @@ struct server {
struct wl_listener xdg_toplevel_icon_set_icon; struct wl_listener xdg_toplevel_icon_set_icon;
struct wl_list views; struct wl_list views;
uint64_t next_view_creation_id;
struct wl_list unmanaged_surfaces; struct wl_list unmanaged_surfaces;
struct seat seat; struct seat seat;
@ -302,15 +303,16 @@ struct server {
struct wlr_security_context_manager_v1 *security_context_manager_v1; struct wlr_security_context_manager_v1 *security_context_manager_v1;
/* Set when in cycle (alt-tab) mode */ /* Set when in cycle (alt-tab) mode */
struct osd_state { struct cycle_state {
struct view *cycle_view; struct view *selected_view;
struct wl_list views;
bool preview_was_shaded; bool preview_was_shaded;
bool preview_was_enabled; bool preview_was_enabled;
struct wlr_scene_node *preview_node; struct wlr_scene_node *preview_node;
struct wlr_scene_tree *preview_parent; struct wlr_scene_node *preview_dummy;
struct wlr_scene_node *preview_anchor;
struct lab_scene_rect *preview_outline; struct lab_scene_rect *preview_outline;
} osd_state; struct cycle_filter filter;
} cycle;
struct theme *theme; struct theme *theme;
@ -392,8 +394,6 @@ void seat_pointer_end_grab(struct seat *seat, struct wlr_surface *surface);
void seat_focus_lock_surface(struct seat *seat, struct wlr_surface *surface); void seat_focus_lock_surface(struct seat *seat, struct wlr_surface *surface);
void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer); void seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer);
void seat_set_pressed(struct seat *seat, struct cursor_context *ctx);
void seat_reset_pressed(struct seat *seat);
void seat_output_layout_changed(struct seat *seat); void seat_output_layout_changed(struct seat *seat);
/* /*

View file

@ -24,6 +24,7 @@ struct node_descriptor {
* @type: node descriptor type * @type: node descriptor type
* @view: associated view * @view: associated view
* @data: struct to point to as follows: * @data: struct to point to as follows:
* - LAB_NODE_CYCLE_OSD_ITEM struct cycle_osd_item
* - LAB_NODE_LAYER_SURFACE struct lab_layer_surface * - LAB_NODE_LAYER_SURFACE struct lab_layer_surface
* - LAB_NODE_LAYER_POPUP struct lab_layer_popup * - LAB_NODE_LAYER_POPUP struct lab_layer_popup
* - LAB_NODE_MENUITEM struct menuitem * - LAB_NODE_MENUITEM struct menuitem
@ -53,10 +54,10 @@ struct menuitem *node_menuitem_from_node(
struct wlr_scene_node *wlr_scene_node); struct wlr_scene_node *wlr_scene_node);
/** /**
* node_osd_item_from_node - return osd item struct from node * node_cycle_osd_item_from_node - return cycle OSD item struct from node
* @wlr_scene_node: wlr_scene_node from which to return data * @wlr_scene_node: wlr_scene_node from which to return data
*/ */
struct osd_item *node_osd_item_from_node( struct cycle_osd_item *node_cycle_osd_item_from_node(
struct wlr_scene_node *wlr_scene_node); struct wlr_scene_node *wlr_scene_node);
/** /**

View file

@ -1,101 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef LABWC_OSD_H
#define LABWC_OSD_H
#include <stdbool.h>
#include <wayland-server-core.h>
struct output;
enum lab_cycle_dir {
LAB_CYCLE_DIR_NONE,
LAB_CYCLE_DIR_FORWARD,
LAB_CYCLE_DIR_BACKWARD,
};
/* TODO: add field with keyboard layout? */
enum window_switcher_field_content {
LAB_FIELD_NONE = 0,
LAB_FIELD_TYPE,
LAB_FIELD_TYPE_SHORT,
LAB_FIELD_IDENTIFIER,
LAB_FIELD_TRIMMED_IDENTIFIER,
LAB_FIELD_ICON,
LAB_FIELD_DESKTOP_ENTRY_NAME,
LAB_FIELD_TITLE,
LAB_FIELD_TITLE_SHORT,
LAB_FIELD_WORKSPACE,
LAB_FIELD_WORKSPACE_SHORT,
LAB_FIELD_WIN_STATE,
LAB_FIELD_WIN_STATE_ALL,
LAB_FIELD_OUTPUT,
LAB_FIELD_OUTPUT_SHORT,
LAB_FIELD_CUSTOM,
LAB_FIELD_COUNT
};
struct window_switcher_field {
enum window_switcher_field_content content;
int width;
char *format;
struct wl_list link; /* struct rcxml.window_switcher.fields */
};
struct buf;
struct view;
struct server;
struct wlr_scene_node;
/* Begin window switcher */
void osd_begin(struct server *server, enum lab_cycle_dir direction);
/* Cycle the selected view in the window switcher */
void osd_cycle(struct server *server, enum lab_cycle_dir direction);
/* Closes the OSD */
void osd_finish(struct server *server, bool switch_focus);
/* Notify OSD about a destroying view */
void osd_on_view_destroy(struct view *view);
/* Focus the clicked window and close OSD */
void osd_on_cursor_release(struct server *server, struct wlr_scene_node *node);
/* Used by osd.c internally to render window switcher fields */
void osd_field_get_content(struct window_switcher_field *field,
struct buf *buf, struct view *view);
/* Sets view info to buf according to format */
void osd_field_set_custom(struct buf *buf, struct view *view,
const char *format);
/* Used by rcxml.c when parsing the config */
void osd_field_arg_from_xml_node(struct window_switcher_field *field,
const char *nodename, const char *content);
bool osd_field_is_valid(struct window_switcher_field *field);
void osd_field_free(struct window_switcher_field *field);
/* Internal API */
struct osd_item {
struct view *view;
struct wlr_scene_tree *tree;
struct wl_list link;
};
struct osd_impl {
/*
* Create a scene-tree of OSD for an output.
* This sets output->osd_scene.{items,tree}.
*/
void (*create)(struct output *output, struct wl_array *views);
/*
* Update output->osd_scene.tree to highlight
* server->osd_state.cycle_view.
*/
void (*update)(struct output *output);
};
extern struct osd_impl osd_classic_impl;
extern struct osd_impl osd_thumbnail_impl;
#endif // LABWC_OSD_H

View file

@ -15,14 +15,14 @@ struct output {
struct wlr_scene_output *scene_output; struct wlr_scene_output *scene_output;
struct wlr_scene_tree *layer_tree[LAB_NR_LAYERS]; struct wlr_scene_tree *layer_tree[LAB_NR_LAYERS];
struct wlr_scene_tree *layer_popup_tree; struct wlr_scene_tree *layer_popup_tree;
struct wlr_scene_tree *osd_tree; struct wlr_scene_tree *cycle_osd_tree;
struct wlr_scene_tree *session_lock_tree; struct wlr_scene_tree *session_lock_tree;
struct wlr_scene_buffer *workspace_osd; struct wlr_scene_buffer *workspace_osd;
struct osd_scene { struct cycle_osd_scene {
struct wl_list items; /* struct osd_item */ struct wl_list items; /* struct cycle_osd_item */
struct wlr_scene_tree *tree; struct wlr_scene_tree *tree;
} osd_scene; } cycle_osd;
/* In output-relative scene coordinates */ /* In output-relative scene coordinates */
struct wlr_box usable_area; struct wlr_box usable_area;

View file

@ -134,6 +134,9 @@ struct view {
const struct view_impl *impl; const struct view_impl *impl;
struct wl_list link; struct wl_list link;
/* This is cleared when the view is not in the cycle list */
struct wl_list cycle_link;
/* /*
* The primary output that the view is displayed on. Specifically: * The primary output that the view is displayed on. Specifically:
* *
@ -171,6 +174,7 @@ struct view {
bool mapped; bool mapped;
bool been_mapped; bool been_mapped;
uint64_t creation_id;
enum lab_ssd_mode ssd_mode; enum lab_ssd_mode ssd_mode;
enum ssd_preference ssd_preference; enum ssd_preference ssd_preference;
bool shaded; bool shaded;
@ -293,6 +297,9 @@ struct xdg_toplevel_view {
struct view base; struct view base;
struct wlr_xdg_surface *xdg_surface; struct wlr_xdg_surface *xdg_surface;
/* Optional black background fill behind fullscreen view */
struct wlr_scene_rect *fullscreen_bg;
/* Events unique to xdg-toplevel views */ /* Events unique to xdg-toplevel views */
struct wl_listener set_app_id; struct wl_listener set_app_id;
struct wl_listener request_show_window_menu; struct wl_listener request_show_window_menu;
@ -384,15 +391,6 @@ struct view *view_next(struct wl_list *head, struct view *view,
struct view *view_prev(struct wl_list *head, struct view *view, struct view *view_prev(struct wl_list *head, struct view *view,
enum lab_view_criteria criteria); enum lab_view_criteria criteria);
/*
* Same as `view_next()` except that they iterate one whole cycle rather than
* stopping at the list-head
*/
struct view *view_next_no_head_stop(struct wl_list *head, struct view *from,
enum lab_view_criteria criteria);
struct view *view_prev_no_head_stop(struct wl_list *head, struct view *from,
enum lab_view_criteria criteria);
/** /**
* view_array_append() - Append views that match criteria to array * view_array_append() - Append views that match criteria to array
* @server: server context * @server: server context

View file

@ -77,5 +77,7 @@ void xwayland_update_workarea(struct server *server);
void xwayland_reset_cursor(struct server *server); void xwayland_reset_cursor(struct server *server);
void xwayland_flush(struct server *server);
#endif /* HAVE_XWAYLAND */ #endif /* HAVE_XWAYLAND */
#endif /* LABWC_XWAYLAND_H */ #endif /* LABWC_XWAYLAND_H */

View file

@ -1,7 +1,7 @@
project( project(
'labwc', 'labwc',
'c', 'c',
version: '0.9.2', version: '0.9.3',
license: 'GPL-2.0-only', license: 'GPL-2.0-only',
meson_version: '>=0.59.0', meson_version: '>=0.59.0',
default_options: [ default_options: [

View file

@ -18,12 +18,12 @@
#include "common/spawn.h" #include "common/spawn.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "cycle.h"
#include "debug.h" #include "debug.h"
#include "input/keyboard.h" #include "input/keyboard.h"
#include "labwc.h" #include "labwc.h"
#include "magnifier.h" #include "magnifier.h"
#include "menu/menu.h" #include "menu/menu.h"
#include "osd.h"
#include "output.h" #include "output.h"
#include "output-virtual.h" #include "output-virtual.h"
#include "regions.h" #include "regions.h"
@ -366,6 +366,44 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup; goto cleanup;
} }
break; 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: case ACTION_TYPE_SHOW_MENU:
if (!strcmp(argument, "menu")) { if (!strcmp(argument, "menu")) {
action_arg_add_str(action, argument, content); action_arg_add_str(action, argument, content);
@ -414,6 +452,20 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup; goto cleanup;
} }
break; break;
case ACTION_TYPE_RESIZE:
if (!strcmp(argument, "direction")) {
enum lab_edge edge = lab_edge_parse(content,
/*tiled*/ true, /*any*/ false);
if (edge == LAB_EDGE_NONE || edge == LAB_EDGE_CENTER) {
wlr_log(WLR_ERROR,
"Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
} else {
action_arg_add_int(action, argument, edge);
}
goto cleanup;
}
break;
case ACTION_TYPE_RESIZE_RELATIVE: case ACTION_TYPE_RESIZE_RELATIVE:
if (!strcmp(argument, "left") || !strcmp(argument, "right") || if (!strcmp(argument, "left") || !strcmp(argument, "right") ||
!strcmp(argument, "top") || !strcmp(argument, "bottom")) { !strcmp(argument, "top") || !strcmp(argument, "bottom")) {
@ -1112,19 +1164,24 @@ run_action(struct view *view, struct server *server, struct action *action,
} }
break; break;
case ACTION_TYPE_NEXT_WINDOW: case ACTION_TYPE_NEXT_WINDOW:
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { case ACTION_TYPE_PREVIOUS_WINDOW: {
osd_cycle(server, LAB_CYCLE_DIR_FORWARD); 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, dir);
} else { } else {
osd_begin(server, LAB_CYCLE_DIR_FORWARD); cycle_begin(server, dir, filter);
}
break;
case ACTION_TYPE_PREVIOUS_WINDOW:
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) {
osd_cycle(server, LAB_CYCLE_DIR_BACKWARD);
} else {
osd_begin(server, LAB_CYCLE_DIR_BACKWARD);
} }
break; break;
}
case ACTION_TYPE_RECONFIGURE: case ACTION_TYPE_RECONFIGURE:
kill(getpid(), SIGHUP); kill(getpid(), SIGHUP);
break; break;
@ -1223,8 +1280,17 @@ run_action(struct view *view, struct server *server, struct action *action,
break; break;
case ACTION_TYPE_RESIZE: case ACTION_TYPE_RESIZE:
if (view) { if (view) {
enum lab_edge resize_edges = cursor_get_resize_edges( /*
server->seat.cursor, ctx); * If a direction was specified in the config, honour it.
* Otherwise, fall back to determining the resize edges from
* the current cursor position (existing behaviour).
*/
enum lab_edge resize_edges =
action_get_int(action, "direction", LAB_EDGE_NONE);
if (resize_edges == LAB_EDGE_NONE) {
resize_edges = cursor_get_resize_edges(
server->seat.cursor, ctx);
}
interactive_begin(view, LAB_INPUT_STATE_RESIZE, interactive_begin(view, LAB_INPUT_STATE_RESIZE,
resize_edges); resize_edges);
} }
@ -1569,7 +1635,7 @@ actions_run(struct view *activator, struct server *server,
struct action *action; struct action *action;
wl_list_for_each(action, actions, link) { wl_list_for_each(action, actions, link) {
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER if (server->input_mode == LAB_INPUT_STATE_CYCLE
&& action->type != ACTION_TYPE_NEXT_WINDOW && action->type != ACTION_TYPE_NEXT_WINDOW
&& action->type != ACTION_TYPE_PREVIOUS_WINDOW) { && action->type != ACTION_TYPE_PREVIOUS_WINDOW) {
wlr_log(WLR_INFO, "Only NextWindow or PreviousWindow " wlr_log(WLR_INFO, "Only NextWindow or PreviousWindow "

View file

@ -35,6 +35,25 @@ box_union(struct wlr_box *box_dest, struct wlr_box *box_a, struct wlr_box *box_b
box_dest->height = y2 - y1; box_dest->height = y2 - y1;
} }
void
box_center(int width, int height, const struct wlr_box *ref,
const struct wlr_box *bound, int *x, int *y)
{
*x = ref->x + (ref->width - width) / 2;
*y = ref->y + (ref->height - height) / 2;
if (*x < bound->x) {
*x = bound->x;
} else if (*x + width > bound->x + bound->width) {
*x = bound->x + bound->width - width;
}
if (*y < bound->y) {
*y = bound->y;
} else if (*y + height > bound->y + bound->height) {
*y = bound->y + bound->height - height;
}
}
struct wlr_box struct wlr_box
box_fit_within(int width, int height, struct wlr_box *bound) box_fit_within(int width, int height, struct wlr_box *bound)
{ {

View file

@ -29,8 +29,8 @@
#include "config/tablet.h" #include "config/tablet.h"
#include "config/tablet-tool.h" #include "config/tablet-tool.h"
#include "config/touch.h" #include "config/touch.h"
#include "cycle.h"
#include "labwc.h" #include "labwc.h"
#include "osd.h"
#include "regions.h" #include "regions.h"
#include "ssd.h" #include "ssd.h"
#include "translate.h" #include "translate.h"
@ -323,23 +323,23 @@ fill_window_rules(xmlNode *node)
static void static void
clear_window_switcher_fields(void) clear_window_switcher_fields(void)
{ {
struct window_switcher_field *field, *field_tmp; 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); wl_list_remove(&field->link);
osd_field_free(field); cycle_osd_field_free(field);
} }
} }
static void static void
fill_window_switcher_field(xmlNode *node) fill_window_switcher_field(xmlNode *node)
{ {
struct window_switcher_field *field = znew(*field); 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; xmlNode *child;
char *key, *content; char *key, *content;
LAB_XML_FOR_EACH(node, child, key, content) { LAB_XML_FOR_EACH(node, child, key, content) {
osd_field_arg_from_xml_node(field, key, content); cycle_osd_field_arg_from_xml_node(field, key, content);
} }
} }
@ -684,6 +684,10 @@ get_send_events_mode(const char *s)
goto err; goto err;
} }
if (!strcasecmp(s, "disabledOnExternalMouse")) {
return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
}
int ret = parse_bool(s, -1); int ret = parse_bool(s, -1);
if (ret >= 0) { if (ret >= 0) {
return ret return ret
@ -691,10 +695,6 @@ get_send_events_mode(const char *s)
: LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; : LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
} }
if (!strcasecmp(s, "disabledOnExternalMouse")) {
return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
}
err: err:
wlr_log(WLR_INFO, "Not a recognised send events mode"); wlr_log(WLR_INFO, "Not a recognised send events mode");
return -1; return -1;
@ -714,6 +714,8 @@ fill_libinput_category(xmlNode *node)
char *key, *content; char *key, *content;
LAB_XML_FOR_EACH(node, child, key, content) { LAB_XML_FOR_EACH(node, child, key, content) {
if (string_null_or_empty(content)) { if (string_null_or_empty(content)) {
wlr_log(WLR_ERROR, "Empty string is not allowed for "
"<libinput><device><%s>. Ignoring.", key);
continue; continue;
} }
if (!strcmp(key, "category")) { if (!strcmp(key, "category")) {
@ -1069,14 +1071,15 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(nodename, "prefix.desktops")) { } else if (!strcasecmp(nodename, "prefix.desktops")) {
xstrdup_replace(rc.workspace_config.prefix, content); xstrdup_replace(rc.workspace_config.prefix, content);
} else if (!strcasecmp(nodename, "thumbnailLabelFormat.osd.windowSwitcher")) { } 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)) { } else if (!lab_xml_node_is_leaf(node)) {
/* parse children of nested nodes other than above */ /* parse children of nested nodes other than above */
return true; return true;
} else if (str_space_only(content)) { } else if (str_space_only(content)) {
/* ignore empty leaf nodes other than above */ wlr_log(WLR_ERROR, "Empty string is not allowed for %s. "
"Ignoring.", nodename);
/* handle non-empty leaf nodes */ /* handle non-empty leaf nodes */
} else if (!strcmp(nodename, "decoration.core")) { } else if (!strcmp(nodename, "decoration.core")) {
@ -1177,7 +1180,14 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(nodename, "unMaximizeThreshold.resistance")) { } else if (!strcasecmp(nodename, "unMaximizeThreshold.resistance")) {
rc.unmaximize_threshold = atoi(content); rc.unmaximize_threshold = atoi(content);
} else if (!strcasecmp(nodename, "range.snapping")) { } else if (!strcasecmp(nodename, "range.snapping")) {
rc.snap_edge_range = atoi(content); rc.snap_edge_range_inner = atoi(content);
rc.snap_edge_range_outer = atoi(content);
wlr_log(WLR_ERROR, "<snapping><range> is deprecated. "
"Use <snapping><range inner=\"\" outer=\"\"> instead.");
} else if (!strcasecmp(nodename, "inner.range.snapping")) {
rc.snap_edge_range_inner = atoi(content);
} else if (!strcasecmp(nodename, "outer.range.snapping")) {
rc.snap_edge_range_outer = atoi(content);
} else if (!strcasecmp(nodename, "cornerRange.snapping")) { } else if (!strcasecmp(nodename, "cornerRange.snapping")) {
rc.snap_edge_corner_range = atoi(content); rc.snap_edge_corner_range = atoi(content);
} else if (!strcasecmp(nodename, "enabled.overlay.snapping")) { } else if (!strcasecmp(nodename, "enabled.overlay.snapping")) {
@ -1209,51 +1219,66 @@ entry(xmlNode *node, char *nodename, char *content)
* thumnailLabelFormat is handled above to allow for an empty value * thumnailLabelFormat is handled above to allow for an empty value
*/ */
} else if (!strcasecmp(nodename, "show.osd.windowSwitcher")) { } 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")) { } else if (!strcasecmp(nodename, "style.osd.windowSwitcher")) {
if (!strcasecmp(content, "classic")) { if (!strcasecmp(content, "classic")) {
rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC;
} else if (!strcasecmp(content, "thumbnail")) { } else if (!strcasecmp(content, "thumbnail")) {
rc.window_switcher.style = WINDOW_SWITCHER_THUMBNAIL; rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL;
} else { } else {
wlr_log(WLR_ERROR, "Invalid windowSwitcher style %s: " wlr_log(WLR_ERROR, "Invalid windowSwitcher style '%s': "
"should be one of classic|thumbnail", content); "should be one of classic|thumbnail", content);
} }
} else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) { } else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) {
if (!strcasecmp(content, "all")) { if (!strcasecmp(content, "all")) {
rc.window_switcher.output_criteria = OSD_OUTPUT_ALL; rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_ALL;
} else if (!strcasecmp(content, "pointer")) { } else if (!strcasecmp(content, "cursor")) {
rc.window_switcher.output_criteria = OSD_OUTPUT_POINTER; rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_CURSOR;
} else if (!strcasecmp(content, "keyboard")) { } else if (!strcasecmp(content, "focused")) {
rc.window_switcher.output_criteria = OSD_OUTPUT_KEYBOARD; rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_FOCUSED;
} else { } else {
wlr_log(WLR_ERROR, "Invalid windowSwitcher output %s: " wlr_log(WLR_ERROR, "Invalid windowSwitcher output '%s': "
"should be one of all|pointer|keyboard", content); "should be one of all|focused|cursor", content);
}
} else if (!strcasecmp(nodename, "order.windowSwitcher")) {
if (!strcasecmp(content, "focus")) {
rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS;
} else if (!strcasecmp(content, "age")) {
rc.window_switcher.order = WINDOW_SWITCHER_ORDER_AGE;
} else {
wlr_log(WLR_ERROR, "Invalid windowSwitcher order '%s': "
"should be one of focus|age", content);
} }
/* The following two are for backward compatibility only. */ /* The following two are for backward compatibility only. */
} else if (!strcasecmp(nodename, "show.windowSwitcher")) { } 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, "<windowSwitcher show=\"\" /> is deprecated." wlr_log(WLR_ERROR, "<windowSwitcher show=\"\" /> is deprecated."
" Use <osd show=\"\" />"); " Use <windowSwitcher><osd show=\"\" />");
} else if (!strcasecmp(nodename, "style.windowSwitcher")) { } else if (!strcasecmp(nodename, "style.windowSwitcher")) {
if (!strcasecmp(content, "classic")) { if (!strcasecmp(content, "classic")) {
rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC;
} else if (!strcasecmp(content, "thumbnail")) { } else if (!strcasecmp(content, "thumbnail")) {
rc.window_switcher.style = WINDOW_SWITCHER_THUMBNAIL; rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL;
} }
wlr_log(WLR_ERROR, "<windowSwitcher style=\"\" /> is deprecated." wlr_log(WLR_ERROR, "<windowSwitcher style=\"\" /> is deprecated."
" Use <osd style=\"\" />"); " Use <windowSwitcher><osd style=\"\" />");
} else if (!strcasecmp(nodename, "preview.windowSwitcher")) { } else if (!strcasecmp(nodename, "preview.windowSwitcher")) {
set_bool(content, &rc.window_switcher.preview); set_bool(content, &rc.window_switcher.preview);
} else if (!strcasecmp(nodename, "outlines.windowSwitcher")) { } else if (!strcasecmp(nodename, "outlines.windowSwitcher")) {
set_bool(content, &rc.window_switcher.outlines); set_bool(content, &rc.window_switcher.outlines);
} else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) { } else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) {
if (parse_bool(content, -1) == true) { int ret = parse_bool(content, -1);
rc.window_switcher.criteria &= if (ret < 0) {
~LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; wlr_log(WLR_ERROR, "Invalid value for <windowSwitcher"
" allWorkspaces=\"\">: '%s'", content);
} else {
rc.window_switcher.workspace_filter = ret ?
CYCLE_WORKSPACE_ALL : CYCLE_WORKSPACE_CURRENT;
} }
wlr_log(WLR_ERROR, "<windowSwitcher allWorkspaces=\"\" /> is deprecated."
" Use <action name=\"NextWindow\" workspace=\"\"> instead.");
} else if (!strcasecmp(nodename, "unshade.windowSwitcher")) { } else if (!strcasecmp(nodename, "unshade.windowSwitcher")) {
set_bool(content, &rc.window_switcher.unshade); set_bool(content, &rc.window_switcher.unshade);
@ -1263,7 +1288,7 @@ entry(xmlNode *node, char *nodename, char *content)
/* The following three are for backward compatibility only */ /* The following three are for backward compatibility only */
} else if (!strcasecmp(nodename, "show.windowSwitcher.core")) { } 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")) { } else if (!strcasecmp(nodename, "preview.windowSwitcher.core")) {
set_bool(content, &rc.window_switcher.preview); set_bool(content, &rc.window_switcher.preview);
} else if (!strcasecmp(nodename, "outlines.windowSwitcher.core")) { } else if (!strcasecmp(nodename, "outlines.windowSwitcher.core")) {
@ -1271,7 +1296,7 @@ entry(xmlNode *node, char *nodename, char *content)
/* The following three are for backward compatibility only */ /* The following three are for backward compatibility only */
} else if (!strcasecmp(nodename, "cycleViewOSD.core")) { } 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, "<cycleViewOSD> is deprecated." wlr_log(WLR_ERROR, "<cycleViewOSD> is deprecated."
" Use <windowSwitcher show=\"\" />"); " Use <windowSwitcher show=\"\" />");
} else if (!strcasecmp(nodename, "cycleViewPreview.core")) { } else if (!strcasecmp(nodename, "cycleViewPreview.core")) {
@ -1289,6 +1314,8 @@ entry(xmlNode *node, char *nodename, char *content)
wl_list_append(&rc.workspace_config.workspaces, &workspace->link); wl_list_append(&rc.workspace_config.workspaces, &workspace->link);
} else if (!strcasecmp(nodename, "popupTime.desktops")) { } else if (!strcasecmp(nodename, "popupTime.desktops")) {
rc.workspace_config.popuptime = atoi(content); 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")) { } else if (!strcasecmp(nodename, "number.desktops")) {
rc.workspace_config.min_nr_workspaces = MAX(1, atoi(content)); rc.workspace_config.min_nr_workspaces = MAX(1, atoi(content));
} else if (!strcasecmp(nodename, "popupShow.resize")) { } else if (!strcasecmp(nodename, "popupShow.resize")) {
@ -1400,7 +1427,7 @@ rcxml_init(void)
wl_list_init(&rc.libinput_categories); wl_list_init(&rc.libinput_categories);
wl_list_init(&rc.workspace_config.workspaces); wl_list_init(&rc.workspace_config.workspaces);
wl_list_init(&rc.regions); 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.window_rules);
wl_list_init(&rc.touch_configs); wl_list_init(&rc.touch_configs);
} }
@ -1456,7 +1483,8 @@ rcxml_init(void)
rc.unsnap_threshold = 20; rc.unsnap_threshold = 20;
rc.unmaximize_threshold = 150; rc.unmaximize_threshold = 150;
rc.snap_edge_range = 10; rc.snap_edge_range_inner = 10;
rc.snap_edge_range_outer = 10;
rc.snap_edge_corner_range = 50; rc.snap_edge_corner_range = 50;
rc.snap_overlay_enabled = true; rc.snap_overlay_enabled = true;
rc.snap_overlay_delay_inner = 500; rc.snap_overlay_delay_inner = 500;
@ -1464,16 +1492,15 @@ rcxml_init(void)
rc.snap_top_maximize = true; rc.snap_top_maximize = true;
rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS; rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS;
rc.window_switcher.show = true; rc.window_switcher.osd.show = true;
rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC;
rc.window_switcher.output_criteria = OSD_OUTPUT_ALL; rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_ALL;
rc.window_switcher.thumbnail_label_format = xstrdup("%T"); rc.window_switcher.osd.thumbnail_label_format = xstrdup("%T");
rc.window_switcher.preview = true; rc.window_switcher.preview = true;
rc.window_switcher.outlines = true; rc.window_switcher.outlines = true;
rc.window_switcher.unshade = true; rc.window_switcher.unshade = true;
rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE rc.window_switcher.workspace_filter = CYCLE_WORKSPACE_CURRENT;
| LAB_VIEW_CRITERIA_ROOT_TOPLEVEL rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS;
| LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER;
rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER; rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER;
rc.resize_draw_contents = true; rc.resize_draw_contents = true;
@ -1635,7 +1662,7 @@ static void
load_default_window_switcher_fields(void) load_default_window_switcher_fields(void)
{ {
static const struct { static const struct {
enum window_switcher_field_content content; enum cycle_osd_field_content content;
int width; int width;
} fields[] = { } fields[] = {
#if HAVE_LIBSFDO #if HAVE_LIBSFDO
@ -1648,12 +1675,11 @@ load_default_window_switcher_fields(void)
#endif #endif
}; };
struct window_switcher_field *field;
for (size_t i = 0; i < ARRAY_SIZE(fields); i++) { for (size_t i = 0; i < ARRAY_SIZE(fields); i++) {
field = znew(*field); struct cycle_osd_field *field = znew(*field);
field->content = fields[i].content; field->content = fields[i].content;
field->width = fields[i].width; field->width = fields[i].width;
wl_list_append(&rc.window_switcher.fields, &field->link); wl_list_append(&rc.window_switcher.osd.fields, &field->link);
} }
} }
@ -1774,7 +1800,7 @@ post_processing(void)
if (rc.workspace_config.popuptime == INT_MIN) { if (rc.workspace_config.popuptime == INT_MIN) {
rc.workspace_config.popuptime = 1000; 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"); wlr_log(WLR_INFO, "load default window switcher fields");
load_default_window_switcher_fields(); load_default_window_switcher_fields();
} }
@ -1867,13 +1893,13 @@ validate(void)
/* OSD fields */ /* OSD fields */
int field_width_sum = 0; int field_width_sum = 0;
struct window_switcher_field *field, *field_tmp; 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; field_width_sum += field->width;
if (!osd_field_is_valid(field) || field_width_sum > 100) { if (!cycle_osd_field_is_valid(field) || field_width_sum > 100) {
wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field); wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field);
wl_list_remove(&field->link); wl_list_remove(&field->link);
osd_field_free(field); cycle_osd_field_free(field);
} }
} }
} }
@ -1943,8 +1969,9 @@ rcxml_finish(void)
zfree(rc.icon_theme_name); zfree(rc.icon_theme_name);
zfree(rc.fallback_app_icon_name); zfree(rc.fallback_app_icon_name);
zfree(rc.workspace_config.prefix); zfree(rc.workspace_config.prefix);
zfree(rc.workspace_config.initial_workspace_name);
zfree(rc.tablet.output_name); zfree(rc.tablet.output_name);
zfree(rc.window_switcher.thumbnail_label_format); zfree(rc.window_switcher.osd.thumbnail_label_format);
clear_title_layout(); clear_title_layout();

435
src/cycle/cycle.c Normal file
View file

@ -0,0 +1,435 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "cycle.h"
#include <assert.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include "common/lab-scene-rect.h"
#include "common/list.h"
#include "common/scene-helpers.h"
#include "config/rcxml.h"
#include "labwc.h"
#include "node.h"
#include "output.h"
#include "scaled-buffer/scaled-font-buffer.h"
#include "scaled-buffer/scaled-icon-buffer.h"
#include "ssd.h"
#include "theme.h"
#include "view.h"
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);
static void
update_preview_outlines(struct view *view)
{
/* Create / Update preview outline tree */
struct server *server = view->server;
struct theme *theme = server->theme;
struct lab_scene_rect *rect = view->server->cycle.preview_outline;
if (!rect) {
struct lab_scene_rect_options opts = {
.border_colors = (float *[3]) {
theme->osd_window_switcher_preview_border_color[0],
theme->osd_window_switcher_preview_border_color[1],
theme->osd_window_switcher_preview_border_color[2],
},
.nr_borders = 3,
.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);
server->cycle.preview_outline = rect;
}
struct wlr_box geo = ssd_max_extents(view);
lab_scene_rect_set_size(rect, geo.width, geo.height);
wlr_scene_node_set_position(&rect->tree->node, geo.x, geo.y);
}
/* Returns the view to select next in the window switcher. */
static struct view *
get_next_selected_view(struct server *server, enum lab_cycle_dir dir)
{
struct cycle_state *cycle = &server->cycle;
assert(cycle->selected_view);
assert(!wl_list_empty(&server->cycle.views));
struct wl_list *link;
if (dir == LAB_CYCLE_DIR_FORWARD) {
link = cycle->selected_view->cycle_link.next;
if (link == &server->cycle.views) {
link = link->next;
}
} else {
link = cycle->selected_view->cycle_link.prev;
if (link == &server->cycle.views) {
link = link->prev;
}
}
struct view *view = wl_container_of(link, view, cycle_link);
return view;
}
static struct view *
get_first_view(struct wl_list *views)
{
assert(!wl_list_empty(views));
struct view *view = wl_container_of(views->next, view, cycle_link);
return view;
}
void
cycle_reinitialize(struct server *server)
{
struct cycle_state *cycle = &server->cycle;
if (server->input_mode != LAB_INPUT_STATE_CYCLE) {
/* OSD not active, no need for clean up */
return;
}
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, filter)) {
/*
* Preserve the selected view (or its previous view) if it's
* still in the cycle list
*/
if (selected_view->cycle_link.next) {
cycle->selected_view = selected_view;
} else if (selected_view_prev->cycle_link.next) {
cycle->selected_view = selected_view_prev;
} else {
/* should be unreachable */
wlr_log(WLR_ERROR, "could not find view to select");
cycle->selected_view = get_first_view(&server->cycle.views);
}
update_cycle(server);
} else {
/* Failed to re-init window switcher, exit */
cycle_finish(server, /*switch_focus*/ false);
}
}
void
cycle_on_cursor_release(struct server *server, struct wlr_scene_node *node)
{
assert(server->input_mode == LAB_INPUT_STATE_CYCLE);
struct cycle_osd_item *item = node_cycle_osd_item_from_node(node);
server->cycle.selected_view = item->view;
cycle_finish(server, /*switch_focus*/ true);
}
static void
restore_preview_node(struct server *server)
{
if (server->cycle.preview_node) {
wlr_scene_node_reparent(server->cycle.preview_node,
server->cycle.preview_dummy->parent);
wlr_scene_node_place_above(server->cycle.preview_node,
server->cycle.preview_dummy);
wlr_scene_node_destroy(server->cycle.preview_dummy);
/* Node was disabled / minimized before, disable again */
if (!server->cycle.preview_was_enabled) {
wlr_scene_node_set_enabled(server->cycle.preview_node, false);
}
if (server->cycle.preview_was_shaded) {
struct view *view = node_view_from_node(server->cycle.preview_node);
view_set_shade(view, true);
}
server->cycle.preview_node = NULL;
server->cycle.preview_dummy = NULL;
server->cycle.preview_was_enabled = false;
server->cycle.preview_was_shaded = false;
}
}
void
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, filter)) {
return;
}
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 */
server->cycle.selected_view = active_view;
} else {
/* Otherwise, select the first view in the cycle list */
server->cycle.selected_view = get_first_view(&server->cycle.views);
}
/* Pre-select the next view in the given direction */
server->cycle.selected_view = get_next_selected_view(server, direction);
seat_focus_override_begin(&server->seat,
LAB_INPUT_STATE_CYCLE, LAB_CURSOR_DEFAULT);
update_cycle(server);
/* Update cursor, in case it is within the area covered by OSD */
cursor_update_focus(server);
}
void
cycle_step(struct server *server, enum lab_cycle_dir direction)
{
assert(server->input_mode == LAB_INPUT_STATE_CYCLE);
server->cycle.selected_view = get_next_selected_view(server, direction);
update_cycle(server);
}
void
cycle_finish(struct server *server, bool switch_focus)
{
if (server->input_mode != LAB_INPUT_STATE_CYCLE) {
return;
}
struct view *selected_view = server->cycle.selected_view;
destroy_cycle(server);
/* FIXME: this sets focus to the old surface even with switch_focus=true */
seat_focus_override_end(&server->seat);
/* Hiding OSD may need a cursor change */
cursor_update_focus(server);
if (switch_focus && selected_view) {
if (rc.window_switcher.unshade) {
view_set_shade(selected_view, false);
}
desktop_focus_view(selected_view, /*raise*/ true);
}
}
static void
preview_selected_view(struct view *view)
{
assert(view);
assert(view->scene_tree);
struct server *server = view->server;
struct cycle_state *cycle = &server->cycle;
/* Move previous selected node back to its original place */
restore_preview_node(server);
cycle->preview_node = &view->scene_tree->node;
/* Create a dummy node at the original place of the previewed window */
struct wlr_scene_rect *dummy_rect = wlr_scene_rect_create(
cycle->preview_node->parent, 0, 0, (float [4]) {0});
wlr_scene_node_place_below(&dummy_rect->node, cycle->preview_node);
wlr_scene_node_set_enabled(&dummy_rect->node, false);
cycle->preview_dummy = &dummy_rect->node;
/* Store node enabled / minimized state and force-enable if disabled */
cycle->preview_was_enabled = cycle->preview_node->enabled;
wlr_scene_node_set_enabled(cycle->preview_node, true);
if (rc.window_switcher.unshade && view->shaded) {
view_set_shade(view, false);
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);
/* Finally raise selected node to the top */
wlr_scene_node_raise_to_top(cycle->preview_node);
}
static struct cycle_osd_impl *
get_osd_impl(void)
{
switch (rc.window_switcher.osd.style) {
case CYCLE_OSD_STYLE_CLASSIC:
return &cycle_osd_classic_impl;
case CYCLE_OSD_STYLE_THUMBNAIL:
return &cycle_osd_thumbnail_impl;
}
return NULL;
}
static uint64_t
get_outputs_by_filter(struct server *server,
enum cycle_output_filter output_filter)
{
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;
}
}
static void
insert_view_ordered_by_age(struct wl_list *views, struct view *new_view)
{
struct wl_list *link = views;
struct view *view;
wl_list_for_each(view, views, cycle_link) {
if (view->creation_id >= new_view->creation_id) {
break;
}
link = &view->cycle_link;
}
wl_list_insert(link, &new_view->cycle_link);
}
/* Return false on failure */
static bool
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, 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 {
wl_list_append(&server->cycle.views, &view->cycle_link);
}
}
if (wl_list_empty(&server->cycle.views)) {
wlr_log(WLR_DEBUG, "no views to switch between");
return false;
}
server->cycle.filter = filter;
if (rc.window_switcher.osd.show) {
/* Create OSD */
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;
}
if (!output_is_usable(output)) {
continue;
}
get_osd_impl()->create(output);
assert(output->cycle_osd.tree);
}
}
return true;
}
static void
update_cycle(struct server *server)
{
struct cycle_state *cycle = &server->cycle;
if (rc.window_switcher.osd.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.preview) {
preview_selected_view(cycle->selected_view);
}
/* Outline current window */
if (rc.window_switcher.outlines) {
if (view_is_focusable(server->cycle.selected_view)) {
update_preview_outlines(server->cycle.selected_view);
}
}
}
/* Resets all the states in server->cycle */
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;
}
}
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) {
wl_list_remove(&view->cycle_link);
view->cycle_link = (struct wl_list){0};
}
server->cycle.selected_view = NULL;
server->cycle.filter = (struct cycle_filter){0};
}

View file

@ -1,5 +1,5 @@
labwc_sources += files( labwc_sources += files(
'osd.c', 'cycle.c',
'osd-classic.c', 'osd-classic.c',
'osd-field.c', 'osd-field.c',
'osd-thumbnail.c', 'osd-thumbnail.c',

View file

@ -4,24 +4,25 @@
#include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_scene.h>
#include <wlr/util/box.h> #include <wlr/util/box.h>
#include <wlr/util/log.h> #include <wlr/util/log.h>
#include "common/array.h"
#include "common/buf.h" #include "common/buf.h"
#include "common/font.h" #include "common/font.h"
#include "common/lab-scene-rect.h" #include "common/lab-scene-rect.h"
#include "common/list.h" #include "common/list.h"
#include "common/mem.h"
#include "common/string-helpers.h" #include "common/string-helpers.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "cycle.h"
#include "labwc.h" #include "labwc.h"
#include "node.h" #include "node.h"
#include "osd.h"
#include "output.h" #include "output.h"
#include "scaled-buffer/scaled-font-buffer.h" #include "scaled-buffer/scaled-font-buffer.h"
#include "scaled-buffer/scaled-icon-buffer.h" #include "scaled-buffer/scaled-icon-buffer.h"
#include "theme.h" #include "theme.h"
#include "view.h"
#include "workspaces.h" #include "workspaces.h"
struct osd_classic_item { struct cycle_osd_classic_item {
struct osd_item base; struct cycle_osd_item base;
struct wlr_scene_tree *normal_tree, *active_tree; struct wlr_scene_tree *normal_tree, *active_tree;
}; };
@ -34,8 +35,8 @@ create_fields_scene(struct server *server, struct view *view,
struct window_switcher_classic_theme *switcher_theme = struct window_switcher_classic_theme *switcher_theme =
&theme->osd_window_switcher_classic; &theme->osd_window_switcher_classic;
struct window_switcher_field *field; 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; int field_width = field_widths_sum * field->width / 100.0;
struct wlr_scene_node *node = NULL; struct wlr_scene_node *node = NULL;
int height = -1; int height = -1;
@ -51,7 +52,7 @@ create_fields_scene(struct server *server, struct view *view,
height = icon_size; height = icon_size;
} else { } else {
struct buf buf = BUF_INIT; struct buf buf = BUF_INIT;
osd_field_get_content(field, &buf, view); cycle_osd_field_get_content(field, &buf, view);
if (!string_null_or_empty(buf.data)) { if (!string_null_or_empty(buf.data)) {
struct scaled_font_buffer *font_buffer = struct scaled_font_buffer *font_buffer =
@ -76,9 +77,9 @@ create_fields_scene(struct server *server, struct view *view,
} }
static void static void
osd_classic_create(struct output *output, struct wl_array *views) cycle_osd_classic_create(struct output *output)
{ {
assert(!output->osd_scene.tree && wl_list_empty(&output->osd_scene.items)); assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items));
struct server *server = output->server; struct server *server = output->server;
struct theme *theme = server->theme; struct theme *theme = server->theme;
@ -87,6 +88,7 @@ osd_classic_create(struct output *output, struct wl_array *views)
int padding = theme->osd_border_width + switcher_theme->padding; int padding = theme->osd_border_width + switcher_theme->padding;
bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1; bool show_workspace = wl_list_length(&rc.workspace_config.workspaces) > 1;
const char *workspace_name = server->workspaces.current->name; const char *workspace_name = server->workspaces.current->name;
int nr_views = wl_list_length(&server->cycle.views);
struct wlr_box output_box; struct wlr_box output_box;
wlr_output_layout_get_box(server->output_layout, output->wlr_output, wlr_output_layout_get_box(server->output_layout, output->wlr_output,
@ -96,13 +98,13 @@ osd_classic_create(struct output *output, struct wl_array *views)
if (switcher_theme->width_is_percent) { if (switcher_theme->width_is_percent) {
w = output_box.width * switcher_theme->width / 100; w = output_box.width * switcher_theme->width / 100;
} }
int h = wl_array_len(views) * switcher_theme->item_height + 2 * padding; int h = nr_views * switcher_theme->item_height + 2 * padding;
if (show_workspace) { if (show_workspace) {
/* workspace indicator */ /* workspace indicator */
h += switcher_theme->item_height; h += switcher_theme->item_height;
} }
output->osd_scene.tree = wlr_scene_tree_create(output->osd_tree); output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree);
float *text_color = theme->osd_label_text_color; float *text_color = theme->osd_label_text_color;
float *bg_color = theme->osd_bg_color; float *bg_color = theme->osd_bg_color;
@ -116,7 +118,7 @@ osd_classic_create(struct output *output, struct wl_array *views)
.width = w, .width = w,
.height = h, .height = h,
}; };
lab_scene_rect_create(output->osd_scene.tree, &bg_opts); lab_scene_rect_create(output->cycle_osd.tree, &bg_opts);
int y = padding; int y = padding;
@ -134,7 +136,7 @@ osd_classic_create(struct output *output, struct wl_array *views)
} }
struct scaled_font_buffer *font_buffer = struct scaled_font_buffer *font_buffer =
scaled_font_buffer_create(output->osd_scene.tree); scaled_font_buffer_create(output->cycle_osd.tree);
wlr_scene_node_set_position(&font_buffer->scene_buffer->node, wlr_scene_node_set_position(&font_buffer->scene_buffer->node,
x, y + (switcher_theme->item_height - font_height(&font)) / 2); x, y + (switcher_theme->item_height - font_height(&font)) / 2);
scaled_font_buffer_update(font_buffer, workspace_name, 0, scaled_font_buffer_update(font_buffer, workspace_name, 0,
@ -142,8 +144,7 @@ osd_classic_create(struct output *output, struct wl_array *views)
y += switcher_theme->item_height; y += switcher_theme->item_height;
} }
struct buf buf = BUF_INIT; int nr_fields = wl_list_length(&rc.window_switcher.osd.fields);
int nr_fields = wl_list_length(&rc.window_switcher.fields);
/* This is the width of the area available for text fields */ /* This is the width of the area available for text fields */
int field_widths_sum = w - 2 * padding int field_widths_sum = w - 2 * padding
@ -155,14 +156,14 @@ osd_classic_create(struct output *output, struct wl_array *views)
} }
/* Draw text for each node */ /* Draw text for each node */
struct view **view; struct view *view;
wl_array_for_each(view, views) { wl_list_for_each(view, &server->cycle.views, cycle_link) {
struct osd_classic_item *item = znew(*item); struct cycle_osd_classic_item *item = znew(*item);
wl_list_append(&output->osd_scene.items, &item->base.link); wl_list_append(&output->cycle_osd.items, &item->base.link);
item->base.view = *view; item->base.view = view;
item->base.tree = wlr_scene_tree_create(output->osd_scene.tree); item->base.tree = wlr_scene_tree_create(output->cycle_osd.tree);
node_descriptor_create(&item->base.tree->node, node_descriptor_create(&item->base.tree->node,
LAB_NODE_OSD_ITEM, NULL, item); LAB_NODE_CYCLE_OSD_ITEM, NULL, item);
/* /*
* OSD border * OSD border
* +---------------------------------+ * +---------------------------------+
@ -192,9 +193,9 @@ osd_classic_create(struct output *output, struct wl_array *views)
/* Highlight around selected window's item */ /* Highlight around selected window's item */
struct lab_scene_rect_options highlight_opts = { struct lab_scene_rect_options highlight_opts = {
.border_colors = (float *[1]) {active_border_color}, .border_colors = (float *[1]) {active_border_color},
.bg_color = active_bg_color,
.nr_borders = 1, .nr_borders = 1,
.border_width = switcher_theme->item_active_border_width, .border_width = switcher_theme->item_active_border_width,
.bg_color = active_bg_color,
.width = w - 2 * padding, .width = w - 2 * padding,
.height = switcher_theme->item_height, .height = switcher_theme->item_height,
}; };
@ -207,34 +208,33 @@ osd_classic_create(struct output *output, struct wl_array *views)
w - 2 * padding, switcher_theme->item_height, (float[4]) {0}); w - 2 * padding, switcher_theme->item_height, (float[4]) {0});
wlr_scene_node_set_position(&hitbox->node, padding, y); wlr_scene_node_set_position(&hitbox->node, padding, y);
create_fields_scene(server, *view, item->normal_tree, create_fields_scene(server, view, item->normal_tree,
text_color, bg_color, field_widths_sum, x, y); text_color, bg_color, field_widths_sum, x, y);
create_fields_scene(server, *view, item->active_tree, create_fields_scene(server, view, item->active_tree,
text_color, active_bg_color, field_widths_sum, x, y); text_color, active_bg_color, field_widths_sum, x, y);
y += switcher_theme->item_height; y += switcher_theme->item_height;
} }
buf_reset(&buf);
error:; error:;
/* Center OSD */ /* Center OSD */
wlr_scene_node_set_position(&output->osd_scene.tree->node, wlr_scene_node_set_position(&output->cycle_osd.tree->node,
output_box.x + (output_box.width - w) / 2, output_box.x + (output_box.width - w) / 2,
output_box.y + (output_box.height - h) / 2); output_box.y + (output_box.height - h) / 2);
} }
static void static void
osd_classic_update(struct output *output) cycle_osd_classic_update(struct output *output)
{ {
struct osd_classic_item *item; struct cycle_osd_classic_item *item;
wl_list_for_each(item, &output->osd_scene.items, base.link) { wl_list_for_each(item, &output->cycle_osd.items, base.link) {
bool active = item->base.view == output->server->osd_state.cycle_view; bool active = item->base.view == output->server->cycle.selected_view;
wlr_scene_node_set_enabled(&item->normal_tree->node, !active); wlr_scene_node_set_enabled(&item->normal_tree->node, !active);
wlr_scene_node_set_enabled(&item->active_tree->node, active); wlr_scene_node_set_enabled(&item->active_tree->node, active);
} }
} }
struct osd_impl osd_classic_impl = { struct cycle_osd_impl cycle_osd_classic_impl = {
.create = osd_classic_create, .create = cycle_osd_classic_create,
.update = osd_classic_update, .update = cycle_osd_classic_update,
}; };

View file

@ -5,11 +5,11 @@
#include "common/buf.h" #include "common/buf.h"
#include "common/mem.h" #include "common/mem.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "cycle.h"
#include "view.h" #include "view.h"
#include "workspaces.h" #include "workspaces.h"
#include "labwc.h" #include "labwc.h"
#include "desktop-entry.h" #include "desktop-entry.h"
#include "osd.h"
#include "output.h" #include "output.h"
/* includes '%', terminating 's' and NULL byte, 8 is enough for %-9999s */ /* includes '%', terminating 's' and NULL byte, 8 is enough for %-9999s */
@ -204,11 +204,11 @@ static const struct field_converter field_converter[LAB_FIELD_COUNT] = {
[LAB_FIELD_TITLE] = { 'T', field_set_title }, [LAB_FIELD_TITLE] = { 'T', field_set_title },
[LAB_FIELD_TITLE_SHORT] = { 't', field_set_title_short }, [LAB_FIELD_TITLE_SHORT] = { 't', field_set_title_short },
/* fmt_char can never be matched so prevents LAB_FIELD_CUSTOM recursion */ /* fmt_char can never be matched so prevents LAB_FIELD_CUSTOM recursion */
[LAB_FIELD_CUSTOM] = { '\0', osd_field_set_custom }, [LAB_FIELD_CUSTOM] = { '\0', cycle_osd_field_set_custom },
}; };
void void
osd_field_set_custom(struct buf *buf, struct view *view, const char *format) cycle_osd_field_set_custom(struct buf *buf, struct view *view, const char *format)
{ {
if (!format) { if (!format) {
wlr_log(WLR_ERROR, "Missing format for custom window switcher field"); wlr_log(WLR_ERROR, "Missing format for custom window switcher field");
@ -286,7 +286,7 @@ reset_format:
} }
void void
osd_field_arg_from_xml_node(struct window_switcher_field *field, cycle_osd_field_arg_from_xml_node(struct cycle_osd_field *field,
const char *nodename, const char *content) const char *nodename, const char *content)
{ {
if (!strcmp(nodename, "content")) { if (!strcmp(nodename, "content")) {
@ -332,7 +332,7 @@ osd_field_arg_from_xml_node(struct window_switcher_field *field,
} }
bool bool
osd_field_is_valid(struct window_switcher_field *field) cycle_osd_field_is_valid(struct cycle_osd_field *field)
{ {
if (field->content == LAB_FIELD_NONE) { if (field->content == LAB_FIELD_NONE) {
wlr_log(WLR_ERROR, "Invalid OSD field: no content set"); wlr_log(WLR_ERROR, "Invalid OSD field: no content set");
@ -350,7 +350,7 @@ osd_field_is_valid(struct window_switcher_field *field)
} }
void void
osd_field_get_content(struct window_switcher_field *field, cycle_osd_field_get_content(struct cycle_osd_field *field,
struct buf *buf, struct view *view) struct buf *buf, struct view *view)
{ {
if (field->content == LAB_FIELD_NONE) { if (field->content == LAB_FIELD_NONE) {
@ -363,7 +363,7 @@ osd_field_get_content(struct window_switcher_field *field,
} }
void void
osd_field_free(struct window_switcher_field *field) cycle_osd_field_free(struct cycle_osd_field *field)
{ {
zfree(field->format); zfree(field->format);
zfree(field); zfree(field);

View file

@ -5,22 +5,22 @@
#include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_scene.h> #include <wlr/types/wlr_scene.h>
#include "config/rcxml.h" #include "config/rcxml.h"
#include "common/array.h"
#include "common/box.h" #include "common/box.h"
#include "common/buf.h" #include "common/buf.h"
#include "common/lab-scene-rect.h" #include "common/lab-scene-rect.h"
#include "common/list.h" #include "common/list.h"
#include "common/mem.h"
#include "cycle.h"
#include "labwc.h" #include "labwc.h"
#include "node.h" #include "node.h"
#include "osd.h"
#include "output.h" #include "output.h"
#include "scaled-buffer/scaled-font-buffer.h" #include "scaled-buffer/scaled-font-buffer.h"
#include "scaled-buffer/scaled-icon-buffer.h" #include "scaled-buffer/scaled-icon-buffer.h"
#include "theme.h" #include "theme.h"
#include "view.h" #include "view.h"
struct osd_thumbnail_item { struct cycle_osd_thumbnail_item {
struct osd_item base; struct cycle_osd_item base;
struct scaled_font_buffer *normal_label; struct scaled_font_buffer *normal_label;
struct scaled_font_buffer *active_label; struct scaled_font_buffer *active_label;
struct lab_scene_rect *active_bg; struct lab_scene_rect *active_bg;
@ -102,8 +102,8 @@ create_label(struct wlr_scene_tree *parent, struct view *view,
const float *text_color, const float *bg_color, int y) const float *text_color, const float *bg_color, int y)
{ {
struct buf buf = BUF_INIT; struct buf buf = BUF_INIT;
osd_field_set_custom(&buf, view, 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 = struct scaled_font_buffer *buffer =
scaled_font_buffer_create(parent); scaled_font_buffer_create(parent);
scaled_font_buffer_update(buffer, buf.data, scaled_font_buffer_update(buffer, buf.data,
@ -115,7 +115,7 @@ create_label(struct wlr_scene_tree *parent, struct view *view,
return buffer; return buffer;
} }
static struct osd_thumbnail_item * static struct cycle_osd_thumbnail_item *
create_item_scene(struct wlr_scene_tree *parent, struct view *view, create_item_scene(struct wlr_scene_tree *parent, struct view *view,
struct output *output) struct output *output)
{ {
@ -136,10 +136,10 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view,
return NULL; return NULL;
} }
struct osd_thumbnail_item *item = znew(*item); struct cycle_osd_thumbnail_item *item = znew(*item);
wl_list_append(&output->osd_scene.items, &item->base.link); wl_list_append(&output->cycle_osd.items, &item->base.link);
struct wlr_scene_tree *tree = wlr_scene_tree_create(parent); struct wlr_scene_tree *tree = wlr_scene_tree_create(parent);
node_descriptor_create(&tree->node, LAB_NODE_OSD_ITEM, NULL, item); node_descriptor_create(&tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item);
item->base.tree = tree; item->base.tree = tree;
item->base.view = view; item->base.view = view;
@ -226,9 +226,9 @@ get_items_geometry(struct output *output, struct theme *theme,
} }
static void static void
osd_thumbnail_create(struct output *output, struct wl_array *views) cycle_osd_thumbnail_create(struct output *output)
{ {
assert(!output->osd_scene.tree && wl_list_empty(&output->osd_scene.items)); assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items));
struct server *server = output->server; struct server *server = output->server;
struct theme *theme = server->theme; struct theme *theme = server->theme;
@ -236,19 +236,19 @@ osd_thumbnail_create(struct output *output, struct wl_array *views)
&theme->osd_window_switcher_thumbnail; &theme->osd_window_switcher_thumbnail;
int padding = theme->osd_border_width + switcher_theme->padding; int padding = theme->osd_border_width + switcher_theme->padding;
output->osd_scene.tree = wlr_scene_tree_create(output->osd_tree); output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree);
int nr_views = wl_array_len(views); int nr_views = wl_list_length(&server->cycle.views);
assert(nr_views > 0); assert(nr_views > 0);
int nr_rows, nr_cols; int nr_rows, nr_cols;
get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols); get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols);
/* items */ /* items */
struct view **view; struct view *view;
int index = 0; int index = 0;
wl_array_for_each(view, views) { wl_list_for_each(view, &server->cycle.views, cycle_link) {
struct osd_thumbnail_item *item = create_item_scene( struct cycle_osd_thumbnail_item *item = create_item_scene(
output->osd_scene.tree, *view, output); output->cycle_osd.tree, view, output);
if (!item) { if (!item) {
break; break;
} }
@ -268,7 +268,7 @@ osd_thumbnail_create(struct output *output, struct wl_array *views)
.height = nr_rows * switcher_theme->item_height + 2 * padding, .height = nr_rows * switcher_theme->item_height + 2 * padding,
}; };
struct lab_scene_rect *bg = struct lab_scene_rect *bg =
lab_scene_rect_create(output->osd_scene.tree, &bg_opts); lab_scene_rect_create(output->cycle_osd.tree, &bg_opts);
wlr_scene_node_lower_to_bottom(&bg->tree->node); wlr_scene_node_lower_to_bottom(&bg->tree->node);
/* center */ /* center */
@ -277,15 +277,15 @@ osd_thumbnail_create(struct output *output, struct wl_array *views)
&output_box); &output_box);
int lx = output_box.x + (output_box.width - bg_opts.width) / 2; int lx = output_box.x + (output_box.width - bg_opts.width) / 2;
int ly = output_box.y + (output_box.height - bg_opts.height) / 2; int ly = output_box.y + (output_box.height - bg_opts.height) / 2;
wlr_scene_node_set_position(&output->osd_scene.tree->node, lx, ly); wlr_scene_node_set_position(&output->cycle_osd.tree->node, lx, ly);
} }
static void static void
osd_thumbnail_update(struct output *output) cycle_osd_thumbnail_update(struct output *output)
{ {
struct osd_thumbnail_item *item; struct cycle_osd_thumbnail_item *item;
wl_list_for_each(item, &output->osd_scene.items, base.link) { wl_list_for_each(item, &output->cycle_osd.items, base.link) {
bool active = (item->base.view == output->server->osd_state.cycle_view); bool active = (item->base.view == output->server->cycle.selected_view);
wlr_scene_node_set_enabled(&item->active_bg->tree->node, active); wlr_scene_node_set_enabled(&item->active_bg->tree->node, active);
wlr_scene_node_set_enabled( wlr_scene_node_set_enabled(
&item->active_label->scene_buffer->node, active); &item->active_label->scene_buffer->node, active);
@ -294,7 +294,7 @@ osd_thumbnail_update(struct output *output)
} }
} }
struct osd_impl osd_thumbnail_impl = { struct cycle_osd_impl cycle_osd_thumbnail_impl = {
.create = osd_thumbnail_create, .create = cycle_osd_thumbnail_create,
.update = osd_thumbnail_update, .update = cycle_osd_thumbnail_update,
}; };

View file

@ -21,7 +21,7 @@
#define IGNORE_SSD true #define IGNORE_SSD true
#define IGNORE_MENU true #define IGNORE_MENU true
#define IGNORE_OSD_PREVIEW_OUTLINE true #define IGNORE_CYCLE_PREVIEW_OUTLINE true
#define IGNORE_SNAPPING_OVERLAY true #define IGNORE_SNAPPING_OVERLAY true
static struct view *last_view; static struct view *last_view;
@ -118,7 +118,7 @@ get_special(struct server *server, struct wlr_scene_node *node)
if (node->parent == &server->scene->tree) { if (node->parent == &server->scene->tree) {
struct output *output; struct output *output;
wl_list_for_each(output, &server->outputs, link) { wl_list_for_each(output, &server->outputs, link) {
if (node == &output->osd_tree->node) { if (node == &output->cycle_osd_tree->node) {
return "output->osd_tree"; return "output->osd_tree";
} }
if (node == &output->layer_popup_tree->node) { if (node == &output->layer_popup_tree->node) {
@ -150,10 +150,10 @@ get_special(struct server *server, struct wlr_scene_node *node)
/* Created on-demand */ /* Created on-demand */
return "seat->im_relay->popup_tree"; return "seat->im_relay->popup_tree";
} }
if (server->osd_state.preview_outline if (server->cycle.preview_outline
&& node == &server->osd_state.preview_outline->tree->node) { && node == &server->cycle.preview_outline->tree->node) {
/* Created on-demand */ /* Created on-demand */
return "osd_state->preview_outline"; return "cycle_state->preview_outline";
} }
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
if (node == &server->unmanaged_tree->node) { if (node == &server->unmanaged_tree->node) {
@ -216,13 +216,11 @@ dump_tree(struct server *server, struct wlr_scene_node *node,
} }
printf("%.*s %*c %4d %4d [%p]\n", max_width - 1, type, padding, ' ', x, y, node); printf("%.*s %*c %4d %4d [%p]\n", max_width - 1, type, padding, ' ', x, y, node);
struct lab_scene_rect *osd_preview_outline =
server->osd_state.preview_outline;
if ((IGNORE_MENU && node == &server->menu_tree->node) if ((IGNORE_MENU && node == &server->menu_tree->node)
|| (IGNORE_SSD && last_view || (IGNORE_SSD && last_view
&& ssd_debug_is_root_node(last_view->ssd, node)) && ssd_debug_is_root_node(last_view->ssd, node))
|| (IGNORE_OSD_PREVIEW_OUTLINE && osd_preview_outline || (IGNORE_CYCLE_PREVIEW_OUTLINE && server->cycle.preview_outline
&& node == &osd_preview_outline->tree->node) && node == &server->cycle.preview_outline->tree->node)
|| (IGNORE_SNAPPING_OVERLAY && server->seat.overlay.rect || (IGNORE_SNAPPING_OVERLAY && server->seat.overlay.rect
&& node == &server->seat.overlay.rect->tree->node)) { && node == &server->seat.overlay.rect->tree->node)) {
printf("%*c%s\n", pos + 4 + INDENT_SIZE, ' ', "<skipping children>"); printf("%*c%s\n", pos + 4 + INDENT_SIZE, ' ', "<skipping children>");

View file

@ -251,6 +251,9 @@ err:
* (e.g. "thunderbird" matches "org.mozilla.Thunderbird.desktop" * (e.g. "thunderbird" matches "org.mozilla.Thunderbird.desktop"
* and "XTerm" matches "xterm.desktop"). This is not per any spec * and "XTerm" matches "xterm.desktop"). This is not per any spec
* but is needed to find icons for existing applications. * but is needed to find icons for existing applications.
*
* The second loop tries to match more partial strings, for
* example "gimp-2.0" would match "org.something.gimp.desktop".
*/ */
static struct sfdo_desktop_entry * static struct sfdo_desktop_entry *
get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id) get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id)
@ -258,6 +261,7 @@ get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id)
size_t n_entries; size_t n_entries;
struct sfdo_desktop_entry **entries = sfdo_desktop_db_get_entries(db, &n_entries); struct sfdo_desktop_entry **entries = sfdo_desktop_db_get_entries(db, &n_entries);
/* Would match "org.foobar.xterm" when given app-id "XTerm" */
for (size_t i = 0; i < n_entries; i++) { for (size_t i = 0; i < n_entries; i++) {
struct sfdo_desktop_entry *entry = entries[i]; struct sfdo_desktop_entry *entry = entries[i];
const char *desktop_id = sfdo_desktop_entry_get_id(entry, NULL); const char *desktop_id = sfdo_desktop_entry_get_id(entry, NULL);
@ -266,6 +270,8 @@ get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id)
const char *desktop_id_base = dot ? (dot + 1) : desktop_id; const char *desktop_id_base = dot ? (dot + 1) : desktop_id;
if (!strcasecmp(app_id, desktop_id_base)) { if (!strcasecmp(app_id, desktop_id_base)) {
wlr_log(WLR_DEBUG, "'%s' to '%s.desktop' via case-insensitive match",
app_id, desktop_id);
return entry; return entry;
} }
@ -278,20 +284,31 @@ get_db_entry_by_id_fuzzy(struct sfdo_desktop_db *db, const char *app_id)
const char *wm_class = const char *wm_class =
sfdo_desktop_entry_get_startup_wm_class(entry, NULL); sfdo_desktop_entry_get_startup_wm_class(entry, NULL);
if (wm_class && !strcasecmp(app_id, wm_class)) { if (wm_class && !strcasecmp(app_id, wm_class)) {
wlr_log(WLR_DEBUG, "'%s' to '%s.desktop' via StartupWMClass",
app_id, desktop_id);
return entry; return entry;
} }
} }
/* Try matching partial strings - catches GIMP, among others */ /* Would match "org.foobar.xterm-unicode" when given app-id "XTerm" */
const int app_id_len = strlen(app_id);
for (size_t i = 0; i < n_entries; i++) { for (size_t i = 0; i < n_entries; i++) {
struct sfdo_desktop_entry *entry = entries[i]; struct sfdo_desktop_entry *entry = entries[i];
const char *desktop_id = sfdo_desktop_entry_get_id(entry, NULL); const char *desktop_id = sfdo_desktop_entry_get_id(entry, NULL);
const char *dot = strrchr(desktop_id, '.'); const char *dot = strrchr(desktop_id, '.');
const char *desktop_id_base = dot ? (dot + 1) : desktop_id; const char *desktop_id_base = dot ? (dot + 1) : desktop_id;
int alen = strlen(app_id); const int dlen = strlen(desktop_id_base);
int dlen = strlen(desktop_id_base); const int cmp_len = MIN(app_id_len, dlen);
if (cmp_len < 3) {
if (!strncasecmp(app_id, desktop_id_base, alen > dlen ? dlen : alen)) { /*
* Without this check, app-id "foot" would match
* "something.f" and any app-id would match "R.E.P.O."
*/
continue;
}
if (!strncasecmp(app_id, desktop_id_base, cmp_len)) {
wlr_log(WLR_DEBUG, "'%s' to '%s.desktop' via partial match",
app_id, desktop_id);
return entry; return entry;
} }
} }
@ -304,10 +321,14 @@ get_desktop_entry(struct sfdo *sfdo, const char *app_id)
{ {
struct sfdo_desktop_entry *entry = sfdo_desktop_db_get_entry_by_id( struct sfdo_desktop_entry *entry = sfdo_desktop_db_get_entry_by_id(
sfdo->desktop_db, app_id, SFDO_NT); sfdo->desktop_db, app_id, SFDO_NT);
if (!entry) { if (entry) {
entry = get_db_entry_by_id_fuzzy(sfdo->desktop_db, app_id); wlr_log(WLR_DEBUG, "matched '%s.desktop' via exact match", app_id);
return entry;
}
entry = get_db_entry_by_id_fuzzy(sfdo->desktop_db, app_id);
if (!entry) {
wlr_log(WLR_DEBUG, "failed to find .desktop file for '%s'", app_id);
} }
return entry; return entry;
} }

View file

@ -74,7 +74,7 @@ desktop_focus_view(struct view *view, bool raise)
return; return;
} }
if (view->server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { if (view->server->input_mode == LAB_INPUT_STATE_CYCLE) {
wlr_log(WLR_DEBUG, "not focusing window while window switching"); wlr_log(WLR_DEBUG, "not focusing window while window switching");
return; return;
} }
@ -340,10 +340,10 @@ get_cursor_context(struct server *server)
ret.node = node; ret.node = node;
ret.type = LAB_NODE_MENUITEM; ret.type = LAB_NODE_MENUITEM;
return ret; return ret;
case LAB_NODE_OSD_ITEM: case LAB_NODE_CYCLE_OSD_ITEM:
/* Always return the top scene node for osd items */ /* Always return the top scene node for osd items */
ret.node = node; ret.node = node;
ret.type = LAB_NODE_OSD_ITEM; ret.type = LAB_NODE_CYCLE_OSD_ITEM;
return ret; return ret;
case LAB_NODE_BUTTON_FIRST...LAB_NODE_BUTTON_LAST: case LAB_NODE_BUTTON_FIRST...LAB_NODE_BUTTON_LAST:
case LAB_NODE_SSD_ROOT: case LAB_NODE_SSD_ROOT:

View file

@ -34,7 +34,7 @@ handle_drag_start(struct wl_listener *listener, void *data)
struct wlr_drag *drag = data; struct wlr_drag *drag = data;
seat->drag.active = true; seat->drag.active = true;
seat_reset_pressed(seat); cursor_context_save(&seat->pressed, NULL);
if (drag->icon) { if (drag->icon) {
/* Cleans up automatically on drag->icon->events.destroy */ /* Cleans up automatically on drag->icon->events.destroy */
wlr_scene_drag_icon_create(seat->drag.icons, drag->icon); wlr_scene_drag_icon_create(seat->drag.icons, drag->icon);

View file

@ -21,6 +21,7 @@
#include "common/mem.h" #include "common/mem.h"
#include "config/mousebind.h" #include "config/mousebind.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "cycle.h"
#include "dnd.h" #include "dnd.h"
#include "idle.h" #include "idle.h"
#include "input/gestures.h" #include "input/gestures.h"
@ -30,7 +31,6 @@
#include "labwc.h" #include "labwc.h"
#include "layers.h" #include "layers.h"
#include "menu/menu.h" #include "menu/menu.h"
#include "osd.h"
#include "output.h" #include "output.h"
#include "resistance.h" #include "resistance.h"
#include "resize-outlines.h" #include "resize-outlines.h"
@ -437,8 +437,72 @@ cursor_update_image(struct seat *seat)
cursor_names[cursor]); cursor_names[cursor]);
} }
static void
clear_cursor_context(struct cursor_context_saved *saved_ctx)
{
if (saved_ctx->node_destroy.notify) {
wl_list_remove(&saved_ctx->node_destroy.link);
}
if (saved_ctx->surface_destroy.notify) {
wl_list_remove(&saved_ctx->surface_destroy.link);
}
if (saved_ctx->view_destroy.notify) {
wl_list_remove(&saved_ctx->view_destroy.link);
}
*saved_ctx = (struct cursor_context_saved) {0};
}
static void
handle_ctx_node_destroy(struct wl_listener *listener, void *data)
{
struct cursor_context_saved *saved_ctx =
wl_container_of(listener, saved_ctx, node_destroy);
clear_cursor_context(saved_ctx);
}
static void
handle_ctx_surface_destroy(struct wl_listener *listener, void *data)
{
struct cursor_context_saved *saved_ctx =
wl_container_of(listener, saved_ctx, surface_destroy);
clear_cursor_context(saved_ctx);
}
static void
handle_ctx_view_destroy(struct wl_listener *listener, void *data)
{
struct cursor_context_saved *saved_ctx =
wl_container_of(listener, saved_ctx, view_destroy);
clear_cursor_context(saved_ctx);
}
void
cursor_context_save(struct cursor_context_saved *saved_ctx,
const struct cursor_context *ctx)
{
assert(saved_ctx);
clear_cursor_context(saved_ctx);
if (!ctx) {
return;
}
saved_ctx->ctx = *ctx;
if (ctx->node) {
saved_ctx->node_destroy.notify = handle_ctx_node_destroy;
wl_signal_add(&ctx->node->events.destroy, &saved_ctx->node_destroy);
}
if (ctx->surface) {
saved_ctx->surface_destroy.notify = handle_ctx_surface_destroy;
wl_signal_add(&ctx->surface->events.destroy, &saved_ctx->surface_destroy);
}
if (ctx->view) {
saved_ctx->view_destroy.notify = handle_ctx_view_destroy;
wl_signal_add(&ctx->view->events.destroy, &saved_ctx->view_destroy);
}
}
static bool static bool
update_pressed_surface(struct seat *seat, struct cursor_context *ctx) update_pressed_surface(struct seat *seat, const struct cursor_context *ctx)
{ {
/* /*
* In most cases, we don't want to leave one surface and enter * In most cases, we don't want to leave one surface and enter
@ -454,10 +518,10 @@ update_pressed_surface(struct seat *seat, struct cursor_context *ctx)
if (!wlr_seat_pointer_has_grab(seat->seat)) { if (!wlr_seat_pointer_has_grab(seat->seat)) {
return false; return false;
} }
if (seat->pressed.surface && ctx->surface != seat->pressed.surface) { if (seat->pressed.ctx.surface && ctx->surface != seat->pressed.ctx.surface) {
struct wlr_surface *toplevel = get_toplevel(ctx->surface); struct wlr_surface *toplevel = get_toplevel(ctx->surface);
if (toplevel && toplevel == get_toplevel(seat->pressed.surface)) { if (toplevel && toplevel == get_toplevel(seat->pressed.ctx.surface)) {
seat_set_pressed(seat, ctx); cursor_context_save(&seat->pressed, ctx);
return true; return true;
} }
} }
@ -466,11 +530,11 @@ update_pressed_surface(struct seat *seat, struct cursor_context *ctx)
/* /*
* Common logic shared by cursor_update_focus(), process_cursor_motion() * Common logic shared by cursor_update_focus(), process_cursor_motion()
* and cursor_axis() * and process_cursor_axis()
*/ */
static bool static void
cursor_update_common(struct server *server, struct cursor_context *ctx, cursor_update_common(struct server *server, const struct cursor_context *ctx,
bool cursor_has_moved, double *sx, double *sy) struct cursor_context *notified_ctx)
{ {
struct seat *seat = &server->seat; struct seat *seat = &server->seat;
struct wlr_seat *wlr_seat = seat->seat; struct wlr_seat *wlr_seat = seat->seat;
@ -483,14 +547,14 @@ cursor_update_common(struct server *server, struct cursor_context *ctx,
* interactive move/resize, window switcher and * interactive move/resize, window switcher and
* menu interaction. * menu interaction.
*/ */
return false; return;
} }
/* TODO: verify drag_icon logic */ /* TODO: verify drag_icon logic */
if (seat->pressed.surface && ctx->surface != seat->pressed.surface if (seat->pressed.ctx.surface && ctx->surface != seat->pressed.ctx.surface
&& !update_pressed_surface(seat, ctx) && !update_pressed_surface(seat, ctx)
&& !seat->drag.active) { && !seat->drag.active) {
if (cursor_has_moved) { if (notified_ctx) {
/* /*
* Button has been pressed while over another * Button has been pressed while over another
* surface and is still held down. Just send * surface and is still held down. Just send
@ -499,12 +563,16 @@ cursor_update_common(struct server *server, struct cursor_context *ctx,
* if the cursor moves outside of the surface. * if the cursor moves outside of the surface.
*/ */
int lx, ly; int lx, ly;
wlr_scene_node_coords(seat->pressed.node, &lx, &ly); wlr_scene_node_coords(seat->pressed.ctx.node, &lx, &ly);
*sx = server->seat.cursor->x - lx; *notified_ctx = seat->pressed.ctx;
*sy = server->seat.cursor->y - ly; notified_ctx->sx = server->seat.cursor->x - lx;
return true; notified_ctx->sy = server->seat.cursor->y - ly;
} }
return false; return;
}
if (notified_ctx) {
*notified_ctx = *ctx;
} }
if (ctx->surface) { if (ctx->surface) {
@ -516,11 +584,6 @@ cursor_update_common(struct server *server, struct cursor_context *ctx,
wlr_seat_pointer_notify_enter(wlr_seat, ctx->surface, wlr_seat_pointer_notify_enter(wlr_seat, ctx->surface,
ctx->sx, ctx->sy); ctx->sx, ctx->sy);
seat->server_cursor = LAB_CURSOR_CLIENT; seat->server_cursor = LAB_CURSOR_CLIENT;
if (cursor_has_moved) {
*sx = ctx->sx;
*sy = ctx->sy;
return true;
}
} else { } else {
/* /*
* Cursor is over a server (labwc) surface. Clear focus * Cursor is over a server (labwc) surface. Clear focus
@ -538,7 +601,6 @@ cursor_update_common(struct server *server, struct cursor_context *ctx,
cursor_set(seat, cursor); cursor_set(seat, cursor);
} }
} }
return false;
} }
enum lab_edge enum lab_edge
@ -597,32 +659,45 @@ cursor_process_motion(struct server *server, uint32_t time, double *sx, double *
* moving/resizing the wrong view * moving/resizing the wrong view
*/ */
mousebind->pressed_in_context = false; mousebind->pressed_in_context = false;
actions_run(seat->pressed.view, server, actions_run(seat->pressed.ctx.view, server,
&mousebind->actions, &seat->pressed); &mousebind->actions, &seat->pressed.ctx);
} }
} }
struct wlr_surface *old_focused_surface = /*
seat->seat->pointer_state.focused_surface; * Cursor context that is actually interacting with cursor and should
* be notified to the client. E.g. it is cleared when menu is open,
* and the pressed view is set while out-of-surface dragging.
*/
struct cursor_context notified_ctx = {0};
cursor_update_common(server, &ctx, &notified_ctx);
bool notify = cursor_update_common(server, &ctx, if (rc.focus_follow_mouse) {
/* cursor_has_moved */ true, sx, sy);
struct wlr_surface *new_focused_surface =
seat->seat->pointer_state.focused_surface;
if (rc.focus_follow_mouse && new_focused_surface
&& old_focused_surface != new_focused_surface) {
/* /*
* If followMouse=yes, update the keyboard focus when the * If followMouse=yes, entering a surface or view updates
* cursor enters a surface * keyboard focus. Note that moving the cursor between a
* surface and a SSD within the same view doesn't update
* keyboard focus, and that entering a surface/view doesn't
* update keyboard focus if implicit grab is active.
*/ */
desktop_focus_view_or_surface(seat, bool entering = false;
view_from_wlr_surface(new_focused_surface), if (notified_ctx.view) {
new_focused_surface, rc.raise_on_focus); entering = notified_ctx.view
!= seat->last_cursor_ctx.ctx.view;
} else if (notified_ctx.surface) {
entering = notified_ctx.surface
!= seat->last_cursor_ctx.ctx.surface;
}
if (entering) {
desktop_focus_view_or_surface(seat, notified_ctx.view,
notified_ctx.surface, rc.raise_on_focus);
}
} }
cursor_context_save(&seat->last_cursor_ctx, &notified_ctx);
return notify; *sx = notified_ctx.sx;
*sy = notified_ctx.sy;
return notified_ctx.surface;
} }
static void static void
@ -641,8 +716,7 @@ _cursor_update_focus(struct server *server)
ctx.surface, rc.raise_on_focus); ctx.surface, rc.raise_on_focus);
} }
double sx, sy; cursor_update_common(server, &ctx, NULL);
cursor_update_common(server, &ctx, /*cursor_has_moved*/ false, &sx, &sy);
} }
void void
@ -766,10 +840,12 @@ apply_constraint(struct seat *seat, struct wlr_pointer *pointer, double *x, doub
if (!seat->server->active_view) { if (!seat->server->active_view) {
return; return;
} }
if (!seat->current_constraint || pointer->base.type != WLR_INPUT_DEVICE_POINTER) { if (!seat->current_constraint
|| pointer->base.type != WLR_INPUT_DEVICE_POINTER
|| seat->current_constraint->type
!= WLR_POINTER_CONSTRAINT_V1_CONFINED) {
return; return;
} }
assert(seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_CONFINED);
double sx = seat->cursor->x; double sx = seat->cursor->x;
double sy = seat->cursor->y; double sy = seat->cursor->y;
@ -792,7 +868,9 @@ cursor_locked(struct seat *seat, struct wlr_pointer *pointer)
{ {
return seat->current_constraint return seat->current_constraint
&& pointer->base.type == WLR_INPUT_DEVICE_POINTER && pointer->base.type == WLR_INPUT_DEVICE_POINTER
&& seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED; && seat->current_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED
&& seat->current_constraint->surface
== seat->seat->pointer_state.focused_surface;
} }
static void static void
@ -907,11 +985,6 @@ handle_motion_absolute(struct wl_listener *listener, void *data)
double dx = lx - seat->cursor->x; double dx = lx - seat->cursor->x;
double dy = ly - seat->cursor->y; double dy = ly - seat->cursor->y;
wlr_relative_pointer_manager_v1_send_relative_motion(
seat->server->relative_pointer_manager,
seat->seat, (uint64_t)event->time_msec * 1000,
dx, dy, dx, dy);
preprocess_cursor_motion(seat, event->pointer, preprocess_cursor_motion(seat, event->pointer,
event->time_msec, dx, dy); event->time_msec, dx, dy);
} }
@ -920,7 +993,7 @@ static void
process_release_mousebinding(struct server *server, process_release_mousebinding(struct server *server,
struct cursor_context *ctx, uint32_t button) struct cursor_context *ctx, uint32_t button)
{ {
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
return; return;
} }
@ -989,7 +1062,7 @@ static bool
process_press_mousebinding(struct server *server, struct cursor_context *ctx, process_press_mousebinding(struct server *server, struct cursor_context *ctx,
uint32_t button) uint32_t button)
{ {
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
return false; return false;
} }
@ -1073,7 +1146,7 @@ cursor_process_button_press(struct seat *seat, uint32_t button, uint32_t time_ms
if (ctx.view || ctx.surface) { if (ctx.view || ctx.surface) {
/* Store cursor context for later action processing */ /* Store cursor context for later action processing */
seat_set_pressed(seat, &ctx); cursor_context_save(&seat->pressed, &ctx);
} }
if (server->input_mode == LAB_INPUT_STATE_MENU) { if (server->input_mode == LAB_INPUT_STATE_MENU) {
@ -1138,12 +1211,12 @@ cursor_process_button_release(struct seat *seat, uint32_t button,
{ {
struct server *server = seat->server; struct server *server = seat->server;
struct cursor_context ctx = get_cursor_context(server); struct cursor_context ctx = get_cursor_context(server);
struct wlr_surface *pressed_surface = seat->pressed.surface; struct wlr_surface *pressed_surface = seat->pressed.ctx.surface;
/* Always notify button release event when it's not bound */ /* Always notify button release event when it's not bound */
const bool notify = !lab_set_contains(&seat->bound_buttons, button); const bool notify = !lab_set_contains(&seat->bound_buttons, button);
seat_reset_pressed(seat); cursor_context_save(&seat->pressed, NULL);
if (server->input_mode == LAB_INPUT_STATE_MENU) { if (server->input_mode == LAB_INPUT_STATE_MENU) {
/* TODO: take into account overflow of time_msec */ /* TODO: take into account overflow of time_msec */
@ -1157,9 +1230,9 @@ cursor_process_button_release(struct seat *seat, uint32_t button,
} }
return notify; return notify;
} }
if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
if (ctx.type == LAB_NODE_OSD_ITEM) { if (ctx.type == LAB_NODE_CYCLE_OSD_ITEM) {
osd_on_cursor_release(server, ctx.node); cycle_on_cursor_release(server, ctx.node);
} }
return notify; return notify;
} }
@ -1347,8 +1420,7 @@ process_cursor_axis(struct server *server, enum wl_pointer_axis orientation,
/* Bindings swallow mouse events if activated */ /* Bindings swallow mouse events if activated */
if (ctx.surface && !consumed) { if (ctx.surface && !consumed) {
/* Make sure we are sending the events to the surface under the cursor */ /* Make sure we are sending the events to the surface under the cursor */
double sx, sy; cursor_update_common(server, &ctx, NULL);
cursor_update_common(server, &ctx, /*cursor_has_moved*/ false, &sx, &sy);
return true; return true;
} }

View file

@ -11,12 +11,12 @@
#include "common/macros.h" #include "common/macros.h"
#include "config/keybind.h" #include "config/keybind.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "cycle.h"
#include "idle.h" #include "idle.h"
#include "input/ime.h" #include "input/ime.h"
#include "input/key-state.h" #include "input/key-state.h"
#include "labwc.h" #include "labwc.h"
#include "menu/menu.h" #include "menu/menu.h"
#include "osd.h"
#include "session-lock.h" #include "session-lock.h"
#include "view.h" #include "view.h"
#include "workspaces.h" #include "workspaces.h"
@ -141,17 +141,16 @@ handle_modifiers(struct wl_listener *listener, void *data)
overlay_update(seat); overlay_update(seat);
} }
bool window_switcher_active = server->input_mode bool cycling = server->input_mode == LAB_INPUT_STATE_CYCLE;
== LAB_INPUT_STATE_WINDOW_SWITCHER;
if ((window_switcher_active || seat->workspace_osd_shown_by_modifier) if ((cycling || seat->workspace_osd_shown_by_modifier)
&& !keyboard_get_all_modifiers(seat)) { && !keyboard_get_all_modifiers(seat)) {
if (window_switcher_active) { if (cycling) {
if (key_state_nr_bound_keys()) { if (key_state_nr_bound_keys()) {
should_cancel_cycling_on_next_key_release = true; should_cancel_cycling_on_next_key_release = true;
} else { } else {
should_cancel_cycling_on_next_key_release = false; should_cancel_cycling_on_next_key_release = false;
osd_finish(server, /*switch_focus*/ true); cycle_finish(server, /*switch_focus*/ true);
} }
} }
if (seat->workspace_osd_shown_by_modifier) { if (seat->workspace_osd_shown_by_modifier) {
@ -388,7 +387,7 @@ handle_key_release(struct server *server, uint32_t evdev_keycode)
*/ */
if (should_cancel_cycling_on_next_key_release) { if (should_cancel_cycling_on_next_key_release) {
should_cancel_cycling_on_next_key_release = false; should_cancel_cycling_on_next_key_release = false;
osd_finish(server, /*switch_focus*/ true); cycle_finish(server, /*switch_focus*/ true);
} }
/* /*
@ -461,19 +460,19 @@ handle_cycle_view_key(struct server *server, struct keyinfo *keyinfo)
for (int i = 0; i < keyinfo->translated.nr_syms; i++) { for (int i = 0; i < keyinfo->translated.nr_syms; i++) {
if (keyinfo->translated.syms[i] == XKB_KEY_Escape) { if (keyinfo->translated.syms[i] == XKB_KEY_Escape) {
/* Esc deactivates window switcher */ /* Esc deactivates window switcher */
osd_finish(server, /*switch_focus*/ false); cycle_finish(server, /*switch_focus*/ false);
return true; return true;
} }
if (keyinfo->translated.syms[i] == XKB_KEY_Up if (keyinfo->translated.syms[i] == XKB_KEY_Up
|| keyinfo->translated.syms[i] == XKB_KEY_Left) { || keyinfo->translated.syms[i] == XKB_KEY_Left) {
/* Up/Left cycles the window backward */ /* Up/Left cycles the window backward */
osd_cycle(server, LAB_CYCLE_DIR_BACKWARD); cycle_step(server, LAB_CYCLE_DIR_BACKWARD);
return true; return true;
} }
if (keyinfo->translated.syms[i] == XKB_KEY_Down if (keyinfo->translated.syms[i] == XKB_KEY_Down
|| keyinfo->translated.syms[i] == XKB_KEY_Right) { || keyinfo->translated.syms[i] == XKB_KEY_Right) {
/* Down/Right cycles the window forward */ /* Down/Right cycles the window forward */
osd_cycle(server, LAB_CYCLE_DIR_FORWARD); cycle_step(server, LAB_CYCLE_DIR_FORWARD);
return true; return true;
} }
} }
@ -523,7 +522,7 @@ handle_compositor_keybindings(struct keyboard *keyboard,
key_state_store_pressed_key_as_bound(event->keycode); key_state_store_pressed_key_as_bound(event->keycode);
handle_menu_keys(server, &keyinfo.translated); handle_menu_keys(server, &keyinfo.translated);
return LAB_KEY_HANDLED_TRUE; return LAB_KEY_HANDLED_TRUE;
} else if (server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { } else if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
if (handle_cycle_view_key(server, &keyinfo)) { if (handle_cycle_view_key(server, &keyinfo)) {
key_state_store_pressed_key_as_bound(event->keycode); key_state_store_pressed_key_as_bound(event->keycode);
return LAB_KEY_HANDLED_TRUE; return LAB_KEY_HANDLED_TRUE;

View file

@ -42,7 +42,7 @@ interactive_anchor_to_cursor(struct server *server, struct wlr_box *geo)
if (wlr_box_empty(geo)) { if (wlr_box_empty(geo)) {
return; return;
} }
/* Resize grab_box while anchoring it to grab_box.{x,y} */ /* Resize grab_box while anchoring it to grab_{x,y} */
server->grab_box.x = max_move_scale(server->grab_x, server->grab_box.x, server->grab_box.x = max_move_scale(server->grab_x, server->grab_box.x,
server->grab_box.width, geo->width); server->grab_box.width, geo->width);
server->grab_box.y = max_move_scale(server->grab_y, server->grab_box.y, server->grab_box.y = max_move_scale(server->grab_y, server->grab_box.y,
@ -186,7 +186,7 @@ edge_from_cursor(struct seat *seat, struct output **dest_output,
return false; return false;
} }
if (rc.snap_edge_range == 0) { if (rc.snap_edge_range_inner == 0 && rc.snap_edge_range_outer == 0) {
return false; return false;
} }
@ -197,9 +197,31 @@ edge_from_cursor(struct seat *seat, struct output **dest_output,
} }
*dest_output = output; *dest_output = output;
/* Translate into output local coordinates */
double cursor_x = seat->cursor->x; double cursor_x = seat->cursor->x;
double cursor_y = seat->cursor->y; double cursor_y = seat->cursor->y;
int top_range = rc.snap_edge_range_outer;
int bottom_range = rc.snap_edge_range_outer;
int left_range = rc.snap_edge_range_outer;
int right_range = rc.snap_edge_range_outer;
if (wlr_output_layout_adjacent_output(seat->server->output_layout, WLR_DIRECTION_UP,
output->wlr_output, cursor_x, cursor_y)) {
top_range = rc.snap_edge_range_inner;
}
if (wlr_output_layout_adjacent_output(seat->server->output_layout, WLR_DIRECTION_DOWN,
output->wlr_output, cursor_x, cursor_y)) {
bottom_range = rc.snap_edge_range_inner;
}
if (wlr_output_layout_adjacent_output(seat->server->output_layout, WLR_DIRECTION_LEFT,
output->wlr_output, cursor_x, cursor_y)) {
left_range = rc.snap_edge_range_inner;
}
if (wlr_output_layout_adjacent_output(seat->server->output_layout, WLR_DIRECTION_RIGHT,
output->wlr_output, cursor_x, cursor_y)) {
right_range = rc.snap_edge_range_inner;
}
/* Translate into output local coordinates */
wlr_output_layout_output_coords(seat->server->output_layout, wlr_output_layout_output_coords(seat->server->output_layout,
output->wlr_output, &cursor_x, &cursor_y); output->wlr_output, &cursor_x, &cursor_y);
@ -210,13 +232,13 @@ edge_from_cursor(struct seat *seat, struct output **dest_output,
int left = cursor_x - area->x; int left = cursor_x - area->x;
int right = area->x + area->width - cursor_x; int right = area->x + area->width - cursor_x;
if (top < rc.snap_edge_range) { if (top < top_range) {
*edge1 = LAB_EDGE_TOP; *edge1 = LAB_EDGE_TOP;
} else if (bottom < rc.snap_edge_range) { } else if (bottom < bottom_range) {
*edge1 = LAB_EDGE_BOTTOM; *edge1 = LAB_EDGE_BOTTOM;
} else if (left < rc.snap_edge_range) { } else if (left < left_range) {
*edge1 = LAB_EDGE_LEFT; *edge1 = LAB_EDGE_LEFT;
} else if (right < rc.snap_edge_range) { } else if (right < right_range) {
*edge1 = LAB_EDGE_RIGHT; *edge1 = LAB_EDGE_RIGHT;
} else { } else {
return false; return false;

View file

@ -49,12 +49,12 @@ endif
subdir('common') subdir('common')
subdir('config') subdir('config')
subdir('cycle')
subdir('decorations') subdir('decorations')
subdir('foreign-toplevel') subdir('foreign-toplevel')
subdir('img') subdir('img')
subdir('input') subdir('input')
subdir('menu') subdir('menu')
subdir('osd')
subdir('protocols') subdir('protocols')
subdir('scaled-buffer') subdir('scaled-buffer')
subdir('ssd') subdir('ssd')

View file

@ -59,13 +59,13 @@ node_menuitem_from_node(struct wlr_scene_node *wlr_scene_node)
return (struct menuitem *)node_descriptor->data; return (struct menuitem *)node_descriptor->data;
} }
struct osd_item * struct cycle_osd_item *
node_osd_item_from_node(struct wlr_scene_node *wlr_scene_node) node_cycle_osd_item_from_node(struct wlr_scene_node *wlr_scene_node)
{ {
assert(wlr_scene_node->data); assert(wlr_scene_node->data);
struct node_descriptor *node_descriptor = wlr_scene_node->data; struct node_descriptor *node_descriptor = wlr_scene_node->data;
assert(node_descriptor->type == LAB_NODE_OSD_ITEM); assert(node_descriptor->type == LAB_NODE_CYCLE_OSD_ITEM);
return (struct osd_item *)node_descriptor->data; return (struct cycle_osd_item *)node_descriptor->data;
} }
struct ssd_button * struct ssd_button *

View file

@ -1,374 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "osd.h"
#include <assert.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include "common/array.h"
#include "common/lab-scene-rect.h"
#include "common/scene-helpers.h"
#include "config/rcxml.h"
#include "labwc.h"
#include "node.h"
#include "output.h"
#include "scaled-buffer/scaled-font-buffer.h"
#include "scaled-buffer/scaled-icon-buffer.h"
#include "ssd.h"
#include "theme.h"
#include "view.h"
static void update_osd(struct server *server);
static void
destroy_osd_scenes(struct server *server)
{
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
struct osd_item *item, *tmp;
wl_list_for_each_safe(item, tmp, &output->osd_scene.items, link) {
wl_list_remove(&item->link);
free(item);
}
if (output->osd_scene.tree) {
wlr_scene_node_destroy(&output->osd_scene.tree->node);
output->osd_scene.tree = NULL;
}
}
}
static void
osd_update_preview_outlines(struct view *view)
{
/* Create / Update preview outline tree */
struct server *server = view->server;
struct theme *theme = server->theme;
struct lab_scene_rect *rect = view->server->osd_state.preview_outline;
if (!rect) {
struct lab_scene_rect_options opts = {
.border_colors = (float *[3]) {
theme->osd_window_switcher_preview_border_color[0],
theme->osd_window_switcher_preview_border_color[1],
theme->osd_window_switcher_preview_border_color[2],
},
.nr_borders = 3,
.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);
server->osd_state.preview_outline = rect;
}
struct wlr_box geo = ssd_max_extents(view);
lab_scene_rect_set_size(rect, geo.width, geo.height);
wlr_scene_node_set_position(&rect->tree->node, geo.x, geo.y);
}
/*
* Returns the view to select next in the window switcher.
* If !start_view, the second focusable view is returned.
*/
static struct view *
get_next_cycle_view(struct server *server, struct view *start_view,
enum lab_cycle_dir dir)
{
struct view *(*iter)(struct wl_list *head, struct view *view,
enum lab_view_criteria criteria);
bool forwards = dir == LAB_CYCLE_DIR_FORWARD;
iter = forwards ? view_next_no_head_stop : view_prev_no_head_stop;
enum lab_view_criteria criteria = rc.window_switcher.criteria;
/*
* Views are listed in stacking order, topmost first. Usually the
* topmost view is already focused, so when iterating in the forward
* direction we pre-select the view second from the top:
*
* View #1 (on top, currently focused)
* View #2 (pre-selected)
* View #3
* ...
*/
if (!start_view && forwards) {
start_view = iter(&server->views, NULL, criteria);
}
return iter(&server->views, start_view, criteria);
}
void
osd_on_view_destroy(struct view *view)
{
assert(view);
struct osd_state *osd_state = &view->server->osd_state;
if (view->server->input_mode != LAB_INPUT_STATE_WINDOW_SWITCHER) {
/* OSD not active, no need for clean up */
return;
}
if (osd_state->cycle_view == view) {
/*
* If we are the current OSD selected view, cycle
* to the next because we are dying.
*/
/* Also resets preview node */
osd_state->cycle_view = get_next_cycle_view(view->server,
osd_state->cycle_view, LAB_CYCLE_DIR_BACKWARD);
/*
* If we cycled back to ourselves, then we have no more windows.
* Just close the OSD for good.
*/
if (osd_state->cycle_view == view || !osd_state->cycle_view) {
/* osd_finish() additionally resets cycle_view to NULL */
osd_finish(view->server, /*switch_focus*/ false);
}
}
if (osd_state->cycle_view) {
/* Recreate the OSD to reflect the view has now gone. */
destroy_osd_scenes(view->server);
update_osd(view->server);
}
if (view->scene_tree) {
struct wlr_scene_node *node = &view->scene_tree->node;
if (osd_state->preview_anchor == node) {
/*
* If we are the anchor for the current OSD selected view,
* replace the anchor with the node before us.
*/
osd_state->preview_anchor = lab_wlr_scene_get_prev_node(node);
}
}
}
void
osd_on_cursor_release(struct server *server, struct wlr_scene_node *node)
{
assert(server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER);
struct osd_item *item = node_osd_item_from_node(node);
server->osd_state.cycle_view = item->view;
osd_finish(server, /*switch_focus*/ true);
}
static void
restore_preview_node(struct server *server)
{
struct osd_state *osd_state = &server->osd_state;
if (osd_state->preview_node) {
wlr_scene_node_reparent(osd_state->preview_node,
osd_state->preview_parent);
if (osd_state->preview_anchor) {
wlr_scene_node_place_above(osd_state->preview_node,
osd_state->preview_anchor);
} else {
/* Selected view was the first node */
wlr_scene_node_lower_to_bottom(osd_state->preview_node);
}
/* Node was disabled / minimized before, disable again */
if (!osd_state->preview_was_enabled) {
wlr_scene_node_set_enabled(osd_state->preview_node, false);
}
if (osd_state->preview_was_shaded) {
struct view *view = node_view_from_node(osd_state->preview_node);
view_set_shade(view, true);
}
osd_state->preview_node = NULL;
osd_state->preview_parent = NULL;
osd_state->preview_anchor = NULL;
osd_state->preview_was_shaded = false;
}
}
void
osd_begin(struct server *server, enum lab_cycle_dir direction)
{
if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
return;
}
server->osd_state.cycle_view = get_next_cycle_view(server,
server->osd_state.cycle_view, direction);
seat_focus_override_begin(&server->seat,
LAB_INPUT_STATE_WINDOW_SWITCHER, LAB_CURSOR_DEFAULT);
update_osd(server);
/* Update cursor, in case it is within the area covered by OSD */
cursor_update_focus(server);
}
void
osd_cycle(struct server *server, enum lab_cycle_dir direction)
{
assert(server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER);
server->osd_state.cycle_view = get_next_cycle_view(server,
server->osd_state.cycle_view, direction);
update_osd(server);
}
void
osd_finish(struct server *server, bool switch_focus)
{
if (server->input_mode != LAB_INPUT_STATE_WINDOW_SWITCHER) {
return;
}
restore_preview_node(server);
/* FIXME: this sets focus to the old surface even with switch_focus=true */
seat_focus_override_end(&server->seat);
struct view *cycle_view = server->osd_state.cycle_view;
server->osd_state.preview_node = NULL;
server->osd_state.preview_anchor = NULL;
server->osd_state.cycle_view = NULL;
server->osd_state.preview_was_shaded = false;
destroy_osd_scenes(server);
if (server->osd_state.preview_outline) {
/* Destroy the whole multi_rect so we can easily react to new themes */
wlr_scene_node_destroy(&server->osd_state.preview_outline->tree->node);
server->osd_state.preview_outline = NULL;
}
/* Hiding OSD may need a cursor change */
cursor_update_focus(server);
if (switch_focus && cycle_view) {
if (rc.window_switcher.unshade) {
view_set_shade(cycle_view, false);
}
desktop_focus_view(cycle_view, /*raise*/ true);
}
}
static void
preview_cycled_view(struct view *view)
{
assert(view);
assert(view->scene_tree);
struct osd_state *osd_state = &view->server->osd_state;
/* Move previous selected node back to its original place */
restore_preview_node(view->server);
/* Store some pointers so we can reset the preview later on */
osd_state->preview_node = &view->scene_tree->node;
osd_state->preview_parent = view->scene_tree->node.parent;
/* Remember the sibling right before the selected node */
osd_state->preview_anchor = lab_wlr_scene_get_prev_node(
osd_state->preview_node);
while (osd_state->preview_anchor && !osd_state->preview_anchor->data) {
/* Ignore non-view nodes */
osd_state->preview_anchor = lab_wlr_scene_get_prev_node(
osd_state->preview_anchor);
}
/* Store node enabled / minimized state and force-enable if disabled */
osd_state->preview_was_enabled = osd_state->preview_node->enabled;
if (!osd_state->preview_was_enabled) {
wlr_scene_node_set_enabled(osd_state->preview_node, true);
}
if (rc.window_switcher.unshade && view->shaded) {
view_set_shade(view, false);
osd_state->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(osd_state->preview_node,
view->server->view_tree_always_on_top);
/* Finally raise selected node to the top */
wlr_scene_node_raise_to_top(osd_state->preview_node);
}
static void
update_osd_on_output(struct server *server, struct output *output,
struct osd_impl *osd_impl, struct wl_array *views)
{
if (!output_is_usable(output)) {
return;
}
if (!output->osd_scene.tree) {
osd_impl->create(output, views);
assert(output->osd_scene.tree);
}
osd_impl->update(output);
}
static void
update_osd(struct server *server)
{
struct wl_array views;
wl_array_init(&views);
view_array_append(server, &views, rc.window_switcher.criteria);
struct osd_impl *osd_impl = NULL;
switch (rc.window_switcher.style) {
case WINDOW_SWITCHER_CLASSIC:
osd_impl = &osd_classic_impl;
break;
case WINDOW_SWITCHER_THUMBNAIL:
osd_impl = &osd_thumbnail_impl;
break;
}
if (!wl_array_len(&views) || !server->osd_state.cycle_view) {
osd_finish(server, /*switch_focus*/ false);
goto out;
}
if (rc.window_switcher.show) {
/* Display the actual OSD */
switch (rc.window_switcher.output_criteria) {
case OSD_OUTPUT_ALL: {
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
update_osd_on_output(server, output, osd_impl, &views);
}
break;
}
case OSD_OUTPUT_POINTER:
update_osd_on_output(server,
output_nearest_to_cursor(server), osd_impl, &views);
break;
case OSD_OUTPUT_KEYBOARD: {
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);
}
update_osd_on_output(server, output, osd_impl, &views);
break;
}
}
}
if (rc.window_switcher.preview) {
preview_cycled_view(server->osd_state.cycle_view);
}
/* Outline current window */
if (rc.window_switcher.outlines) {
if (view_is_focusable(server->osd_state.cycle_view)) {
osd_update_preview_outlines(server->osd_state.cycle_view);
}
}
out:
wl_array_release(&views);
}

View file

@ -182,7 +182,7 @@ handle_output_destroy(struct wl_listener *listener, void *data)
wlr_scene_node_destroy(&output->layer_tree[i]->node); wlr_scene_node_destroy(&output->layer_tree[i]->node);
} }
wlr_scene_node_destroy(&output->layer_popup_tree->node); wlr_scene_node_destroy(&output->layer_popup_tree->node);
wlr_scene_node_destroy(&output->osd_tree->node); wlr_scene_node_destroy(&output->cycle_osd_tree->node);
wlr_scene_node_destroy(&output->session_lock_tree->node); wlr_scene_node_destroy(&output->session_lock_tree->node);
if (output->workspace_osd) { if (output->workspace_osd) {
wlr_scene_node_destroy(&output->workspace_osd->node); wlr_scene_node_destroy(&output->workspace_osd->node);
@ -542,7 +542,7 @@ handle_new_output(struct wl_listener *listener, void *data)
wl_signal_add(&wlr_output->events.request_state, &output->request_state); wl_signal_add(&wlr_output->events.request_state, &output->request_state);
wl_list_init(&output->regions); wl_list_init(&output->regions);
wl_list_init(&output->osd_scene.items); wl_list_init(&output->cycle_osd.items);
/* /*
* Create layer-trees (background, bottom, top and overlay) and * Create layer-trees (background, bottom, top and overlay) and
@ -553,7 +553,7 @@ handle_new_output(struct wl_listener *listener, void *data)
wlr_scene_tree_create(&server->scene->tree); wlr_scene_tree_create(&server->scene->tree);
} }
output->layer_popup_tree = wlr_scene_tree_create(&server->scene->tree); output->layer_popup_tree = wlr_scene_tree_create(&server->scene->tree);
output->osd_tree = wlr_scene_tree_create(&server->scene->tree); output->cycle_osd_tree = wlr_scene_tree_create(&server->scene->tree);
output->session_lock_tree = wlr_scene_tree_create(&server->scene->tree); output->session_lock_tree = wlr_scene_tree_create(&server->scene->tree);
/* /*
@ -577,7 +577,7 @@ handle_new_output(struct wl_listener *listener, void *data)
wlr_scene_node_place_below(&output->layer_tree[3]->node, menu_node); wlr_scene_node_place_below(&output->layer_tree[3]->node, menu_node);
wlr_scene_node_place_below(&output->layer_popup_tree->node, menu_node); wlr_scene_node_place_below(&output->layer_popup_tree->node, menu_node);
wlr_scene_node_raise_to_top(&output->osd_tree->node); wlr_scene_node_raise_to_top(&output->cycle_osd_tree->node);
wlr_scene_node_raise_to_top(&output->session_lock_tree->node); wlr_scene_node_raise_to_top(&output->session_lock_tree->node);
/* /*

View file

@ -848,46 +848,6 @@ seat_set_focus_layer(struct seat *seat, struct wlr_layer_surface_v1 *layer)
seat->focused_layer = layer; seat->focused_layer = layer;
} }
static void
pressed_surface_destroy(struct wl_listener *listener, void *data)
{
struct seat *seat = wl_container_of(listener, seat,
pressed_surface_destroy);
/*
* Using data directly prevents 'unused variable'
* warning when compiling without asserts
*/
assert(data == seat->pressed.surface);
seat_reset_pressed(seat);
}
void
seat_set_pressed(struct seat *seat, struct cursor_context *ctx)
{
assert(ctx);
assert(ctx->view || ctx->surface);
seat_reset_pressed(seat);
seat->pressed = *ctx;
if (ctx->surface) {
seat->pressed_surface_destroy.notify = pressed_surface_destroy;
wl_signal_add(&ctx->surface->events.destroy,
&seat->pressed_surface_destroy);
}
}
void
seat_reset_pressed(struct seat *seat)
{
if (seat->pressed.surface) {
wl_list_remove(&seat->pressed_surface_destroy.link);
}
seat->pressed = (struct cursor_context){0};
}
void void
seat_output_layout_changed(struct seat *seat) seat_output_layout_changed(struct seat *seat)
{ {

View file

@ -555,6 +555,7 @@ server_init(struct server *server)
wl_list_init(&server->views); wl_list_init(&server->views);
wl_list_init(&server->unmanaged_surfaces); wl_list_init(&server->unmanaged_surfaces);
wl_list_init(&server->cycle.views);
server->scene = wlr_scene_create(); server->scene = wlr_scene_create();
if (!server->scene) { if (!server->scene) {
@ -568,21 +569,21 @@ server_init(struct server *server)
* z-order for nodes which cover the whole work-area. For per-output * z-order for nodes which cover the whole work-area. For per-output
* scene-trees, see handle_new_output() in src/output.c * scene-trees, see handle_new_output() in src/output.c
* *
* | Type | Scene Tree | Per Output | Example * | Type | Scene Tree | Per Output | Example
* | ----------------- | ---------------- | ---------- | ------- * | ------------------- | ---------------- | ---------- | -------
* | ext-session | lock-screen | Yes | swaylock * | ext-session | lock-screen | Yes | swaylock
* | osd | osd_tree | Yes | * | window switcher OSD | cycle_osd_tree | Yes |
* | compositor-menu | menu_tree | No | root-menu * | compositor-menu | menu_tree | No | root-menu
* | layer-shell | layer-popups | Yes | * | layer-shell | layer-popups | Yes |
* | layer-shell | overlay-layer | Yes | * | layer-shell | overlay-layer | Yes |
* | layer-shell | top-layer | Yes | waybar * | layer-shell | top-layer | Yes | waybar
* | xwayland-OR | unmanaged | No | dmenu * | xwayland-OR | unmanaged | No | dmenu
* | xdg-popups | xdg-popups | No | * | xdg-popups | xdg-popups | No |
* | toplevels windows | always-on-top | No | * | toplevels windows | always-on-top | No |
* | toplevels windows | normal | No | firefox * | toplevels windows | normal | No | firefox
* | toplevels windows | always-on-bottom | No | pcmanfm-qt --desktop * | toplevels windows | always-on-bottom | No | pcmanfm-qt --desktop
* | layer-shell | bottom-layer | Yes | waybar * | layer-shell | bottom-layer | Yes | waybar
* | layer-shell | background-layer | Yes | swaybg * | layer-shell | background-layer | Yes | swaybg
*/ */
if (server->renderer->features.input_color_transform) { if (server->renderer->features.input_color_transform) {

View file

@ -65,7 +65,6 @@ view_impl_apply_geometry(struct view *view, int w, int h)
{ {
struct wlr_box *current = &view->current; struct wlr_box *current = &view->current;
struct wlr_box *pending = &view->pending; struct wlr_box *pending = &view->pending;
struct wlr_box old = *current;
/* /*
* Anchor right edge if resizing via left edge. * Anchor right edge if resizing via left edge.
@ -100,8 +99,4 @@ view_impl_apply_geometry(struct view *view, int w, int h)
current->width = w; current->width = w;
current->height = h; current->height = h;
if (!wlr_box_equal(current, &old)) {
view_moved(view);
}
} }

View file

@ -15,11 +15,11 @@
#include "common/match.h" #include "common/match.h"
#include "common/mem.h" #include "common/mem.h"
#include "config/rcxml.h" #include "config/rcxml.h"
#include "cycle.h"
#include "foreign-toplevel/foreign.h" #include "foreign-toplevel/foreign.h"
#include "input/keyboard.h" #include "input/keyboard.h"
#include "labwc.h" #include "labwc.h"
#include "menu/menu.h" #include "menu/menu.h"
#include "osd.h"
#include "output.h" #include "output.h"
#include "placement.h" #include "placement.h"
#include "regions.h" #include "regions.h"
@ -35,6 +35,7 @@
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
#include <wlr/xwayland.h> #include <wlr/xwayland.h>
#include "xwayland.h"
#endif #endif
struct view * struct view *
@ -345,48 +346,6 @@ view_prev(struct wl_list *head, struct view *view, enum lab_view_criteria criter
return NULL; return NULL;
} }
struct view *
view_next_no_head_stop(struct wl_list *head, struct view *from,
enum lab_view_criteria criteria)
{
assert(head);
struct wl_list *elm = from ? &from->link : head;
struct wl_list *end = elm;
for (elm = elm->next; elm != end; elm = elm->next) {
if (elm == head) {
continue;
}
struct view *view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) {
return view;
}
}
return from;
}
struct view *
view_prev_no_head_stop(struct wl_list *head, struct view *from,
enum lab_view_criteria criteria)
{
assert(head);
struct wl_list *elm = from ? &from->link : head;
struct wl_list *end = elm;
for (elm = elm->prev; elm != end; elm = elm->prev) {
if (elm == head) {
continue;
}
struct view *view = wl_container_of(elm, view, link);
if (matches_criteria(view, criteria)) {
return view;
}
}
return from;
}
void void
view_array_append(struct server *server, struct wl_array *views, view_array_append(struct server *server, struct wl_array *views,
enum lab_view_criteria criteria) enum lab_view_criteria criteria)
@ -823,7 +782,7 @@ view_minimize(struct view *view, bool minimized)
{ {
assert(view); assert(view);
if (view->server->input_mode == LAB_INPUT_STATE_WINDOW_SWITCHER) { if (view->server->input_mode == LAB_INPUT_STATE_CYCLE) {
wlr_log(WLR_ERROR, "not minimizing window while window switching"); wlr_log(WLR_ERROR, "not minimizing window while window switching");
return; return;
} }
@ -858,23 +817,7 @@ view_compute_centered_position(struct view *view, const struct wlr_box *ref,
int height = h + margin.top + margin.bottom; int height = h + margin.top + margin.bottom;
/* If reference box is NULL then center to usable area */ /* If reference box is NULL then center to usable area */
if (!ref) { box_center(width, height, ref ? ref : &usable, &usable, x, y);
ref = &usable;
}
*x = ref->x + (ref->width - width) / 2;
*y = ref->y + (ref->height - height) / 2;
/* Fit the view within the usable area */
if (*x < usable.x) {
*x = usable.x;
} else if (*x + width > usable.x + usable.width) {
*x = usable.x + usable.width - width;
}
if (*y < usable.y) {
*y = usable.y;
} else if (*y + height > usable.y + usable.height) {
*y = usable.y + usable.height - height;
}
*x += margin.left; *x += margin.left;
*y += margin.top; *y += margin.top;
@ -1281,13 +1224,8 @@ view_apply_fullscreen_geometry(struct view *view)
assert(output_is_usable(view->output)); assert(output_is_usable(view->output));
struct wlr_box box = { 0 }; struct wlr_box box = { 0 };
wlr_output_effective_resolution(view->output->wlr_output, wlr_output_layout_get_box(view->server->output_layout,
&box.width, &box.height); view->output->wlr_output, &box);
double ox = 0, oy = 0;
wlr_output_layout_output_coords(view->server->output_layout,
view->output->wlr_output, &ox, &oy);
box.x -= ox;
box.y -= oy;
view_move_resize(view, box); view_move_resize(view, box);
} }
@ -1762,6 +1700,12 @@ view_set_fullscreen(struct view *view, bool fullscreen)
view_apply_special_geometry(view); view_apply_special_geometry(view);
} }
output_set_has_fullscreen_view(view->output, view->fullscreen); output_set_has_fullscreen_view(view->output, view->fullscreen);
/*
* Entering/leaving fullscreen might result in a different
* scene node ending up under the cursor even if view_moved()
* isn't called. Update cursor focus explicitly for that case.
*/
cursor_update_focus(view->server);
} }
static bool static bool
@ -2304,6 +2248,17 @@ view_move_to_front(struct view *view)
move_to_front(view); move_to_front(view);
} }
#if HAVE_XWAYLAND
/*
* view_move_to_front() is typically called on each mouse press
* via Raise action. This means we are restacking windows just
* about at the same time we send the mouse press input to the
* X server, and creates a race where the mouse press could go
* to an incorrect X window depending on timing. To mitigate the
* race, perform an explicit flush after restacking.
*/
xwayland_flush(view->server);
#endif
cursor_update_focus(view->server); cursor_update_focus(view->server);
desktop_update_top_layer_visibility(view->server); desktop_update_top_layer_visibility(view->server);
} }
@ -2612,15 +2567,13 @@ view_destroy(struct view *view)
server->session_lock_manager->last_active_view = NULL; server->session_lock_manager->last_active_view = NULL;
} }
if (server->seat.pressed.view == view) {
seat_reset_pressed(&server->seat);
}
if (view->tiled_region_evacuate) { if (view->tiled_region_evacuate) {
zfree(view->tiled_region_evacuate); zfree(view->tiled_region_evacuate);
} }
osd_on_view_destroy(view); /* TODO: call this on map/unmap instead */
cycle_reinitialize(server);
undecorate(view); undecorate(view);
view_set_icon(view, NULL, NULL); view_set_icon(view, NULL, NULL);

View file

@ -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 */ /* cosmic workspace handlers */
static void static void
handle_cosmic_workspace_activate(struct wl_listener *listener, void *data) 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->name = xstrdup(name);
workspace->tree = wlr_scene_tree_create(server->view_tree); workspace->tree = wlr_scene_tree_create(server->view_tree);
wl_list_append(&server->workspaces.all, &workspace->link); wl_list_append(&server->workspaces.all, &workspace->link);
if (!server->workspaces.current) { wlr_scene_node_set_enabled(&workspace->tree->node, false);
server->workspaces.current = workspace;
} else {
wlr_scene_node_set_enabled(&workspace->tree->node, false);
}
bool active = server->workspaces.current == workspace;
/* cosmic */ /* cosmic */
workspace->cosmic_workspace = lab_cosmic_workspace_create(server->workspaces.cosmic_group); workspace->cosmic_workspace = lab_cosmic_workspace_create(server->workspaces.cosmic_group);
lab_cosmic_workspace_set_name(workspace->cosmic_workspace, name); 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; workspace->on_cosmic.activate.notify = handle_cosmic_workspace_activate;
wl_signal_add(&workspace->cosmic_workspace->events.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); server->workspaces.ext_manager, /*id*/ NULL);
lab_ext_workspace_assign_to_group(workspace->ext_workspace, server->workspaces.ext_group); 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_name(workspace->ext_workspace, name);
lab_ext_workspace_set_active(workspace->ext_workspace, active);
workspace->on_ext.activate.notify = handle_ext_workspace_activate; workspace->on_ext.activate.notify = handle_ext_workspace_activate;
wl_signal_add(&workspace->ext_workspace->events.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) { wl_list_for_each(conf, &rc.workspace_config.workspaces, link) {
add_workspace(server, conf->name); 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) { if (!name) {
return NULL; return NULL;
} }
size_t index = 0; struct server *server = anchor->server;
struct workspace *target; struct wl_list *workspaces = &server->workspaces.all;
size_t wants_index = parse_workspace_index(name);
struct wl_list *workspaces = &anchor->server->workspaces.all;
if (wants_index) { if (!strcasecmp(name, "current")) {
wl_list_for_each(target, workspaces, link) {
if (wants_index == ++index) {
return target;
}
}
} else if (!strcasecmp(name, "current")) {
return anchor; return anchor;
} else if (!strcasecmp(name, "last")) { } else if (!strcasecmp(name, "last")) {
return anchor->server->workspaces.last; return server->workspaces.last;
} else if (!strcasecmp(name, "left")) { } else if (!strcasecmp(name, "left")) {
return get_prev(anchor, workspaces, wrap); return get_prev(anchor, workspaces, wrap);
} else if (!strcasecmp(name, "right")) { } 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); return get_prev_occupied(anchor, workspaces, wrap);
} else if (!strcasecmp(name, "right-occupied")) { } else if (!strcasecmp(name, "right-occupied")) {
return get_next_occupied(anchor, workspaces, wrap); 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 workspace_find_by_name(server, name);
return NULL;
} }
static void static void

View file

@ -10,6 +10,7 @@
#include <wlr/types/wlr_xdg_toplevel_icon_v1.h> #include <wlr/types/wlr_xdg_toplevel_icon_v1.h>
#include "buffer.h" #include "buffer.h"
#include "common/array.h" #include "common/array.h"
#include "common/box.h"
#include "common/macros.h" #include "common/macros.h"
#include "common/mem.h" #include "common/mem.h"
#include "config/rcxml.h" #include "config/rcxml.h"
@ -129,6 +130,58 @@ do_late_positioning(struct view *view)
} }
} }
static void
disable_fullscreen_bg(struct view *view)
{
struct xdg_toplevel_view *xdg_view = xdg_toplevel_view_from_view(view);
if (xdg_view->fullscreen_bg) {
wlr_scene_node_set_enabled(&xdg_view->fullscreen_bg->node, false);
}
}
/*
* Centers any fullscreen view smaller than the full output size.
* This should be called immediately before view_moved().
*/
static void
center_fullscreen_if_needed(struct view *view)
{
if (!view->fullscreen || !output_is_usable(view->output)) {
disable_fullscreen_bg(view);
return;
}
struct wlr_box output_box = {0};
wlr_output_layout_get_box(view->server->output_layout,
view->output->wlr_output, &output_box);
box_center(view->current.width, view->current.height, &output_box,
&output_box, &view->current.x, &view->current.y);
/* Reset pending x/y to computed position also */
view->pending.x = view->current.x;
view->pending.y = view->current.y;
if (view->current.width >= output_box.width
&& view->current.width >= output_box.height) {
disable_fullscreen_bg(view);
return;
}
struct xdg_toplevel_view *xdg_view = xdg_toplevel_view_from_view(view);
if (!xdg_view->fullscreen_bg) {
const float black[4] = {0, 0, 0, 1};
xdg_view->fullscreen_bg =
wlr_scene_rect_create(view->scene_tree, 0, 0, black);
wlr_scene_node_lower_to_bottom(&xdg_view->fullscreen_bg->node);
}
wlr_scene_node_set_position(&xdg_view->fullscreen_bg->node,
output_box.x - view->current.x, output_box.y - view->current.y);
wlr_scene_rect_set_size(xdg_view->fullscreen_bg,
output_box.width, output_box.height);
wlr_scene_node_set_enabled(&xdg_view->fullscreen_bg->node, true);
}
/* TODO: reorder so this forward declaration isn't needed */ /* TODO: reorder so this forward declaration isn't needed */
static void set_pending_configure_serial(struct view *view, uint32_t serial); static void set_pending_configure_serial(struct view *view, uint32_t serial);
@ -238,6 +291,8 @@ handle_commit(struct wl_listener *listener, void *data)
if (update_required) { if (update_required) {
view_impl_apply_geometry(view, size.width, size.height); view_impl_apply_geometry(view, size.width, size.height);
center_fullscreen_if_needed(view);
view_moved(view);
/* /*
* Some views (e.g., terminals that scale as multiples of rows * Some views (e.g., terminals that scale as multiples of rows
@ -335,9 +390,11 @@ handle_configure_timeout(void *data)
} }
view->current.x = view->pending.x; view->current.x = view->pending.x;
view->current.y = view->pending.y; view->current.y = view->pending.y;
view_moved(view);
} }
center_fullscreen_if_needed(view);
view_moved(view);
/* Re-sync pending view with current state */ /* Re-sync pending view with current state */
snap_constraints_update(view); snap_constraints_update(view);
view->pending = view->current; view->pending = view->current;
@ -400,7 +457,7 @@ handle_request_move(struct wl_listener *listener, void *data)
* want. * want.
*/ */
struct view *view = wl_container_of(listener, view, request_move); struct view *view = wl_container_of(listener, view, request_move);
if (view == view->server->seat.pressed.view) { if (view == view->server->seat.pressed.ctx.view) {
interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE);
} }
} }
@ -418,7 +475,7 @@ handle_request_resize(struct wl_listener *listener, void *data)
*/ */
struct wlr_xdg_toplevel_resize_event *event = data; struct wlr_xdg_toplevel_resize_event *event = data;
struct view *view = wl_container_of(listener, view, request_resize); struct view *view = wl_container_of(listener, view, request_resize);
if (view == view->server->seat.pressed.view) { if (view == view->server->seat.pressed.ctx.view) {
interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges);
} }
} }
@ -545,6 +602,12 @@ xdg_toplevel_view_configure(struct view *view, struct wlr_box geo)
} else if (view->pending_configure_serial == 0) { } else if (view->pending_configure_serial == 0) {
view->current.x = geo.x; view->current.x = geo.x;
view->current.y = geo.y; view->current.y = geo.y;
/*
* It's a bit difficult to think of a corner case where
* center_fullscreen_if_needed() would actually be needed
* here, but including it anyway for completeness.
*/
center_fullscreen_if_needed(view);
view_moved(view); view_moved(view);
} }
} }
@ -663,6 +726,10 @@ xdg_toplevel_view_set_fullscreen(struct view *view, bool fullscreen)
if (serial > 0) { if (serial > 0) {
set_pending_configure_serial(view, serial); set_pending_configure_serial(view, serial);
} }
/* Disable background fill immediately on leaving fullscreen */
if (!fullscreen) {
disable_fullscreen_bg(view);
}
} }
static void static void
@ -785,6 +852,9 @@ handle_map(struct wl_listener *listener, void *data)
set_initial_position(view); set_initial_position(view);
} }
/* Disable background fill at map (paranoid?) */
disable_fullscreen_bg(view);
/* /*
* Set initial "current" position directly before * Set initial "current" position directly before
* calling view_moved() to reduce flicker * calling view_moved() to reduce flicker
@ -929,8 +999,6 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data)
assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL);
wlr_xdg_surface_ping(xdg_surface);
struct xdg_toplevel_view *xdg_toplevel_view = znew(*xdg_toplevel_view); struct xdg_toplevel_view *xdg_toplevel_view = znew(*xdg_toplevel_view);
struct view *view = &xdg_toplevel_view->base; struct view *view = &xdg_toplevel_view->base;
@ -1017,6 +1085,7 @@ handle_new_xdg_toplevel(struct wl_listener *listener, void *data)
CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup); CONNECT_SIGNAL(xdg_surface, xdg_toplevel_view, new_popup);
wl_list_insert(&server->views, &view->link); wl_list_insert(&server->views, &view->link);
view->creation_id = server->next_view_creation_id++;
} }
static void static void

View file

@ -274,6 +274,7 @@ handle_commit(struct wl_listener *listener, void *data)
*/ */
if (current->width != state->width || current->height != state->height) { if (current->width != state->width || current->height != state->height) {
view_impl_apply_geometry(view, state->width, state->height); view_impl_apply_geometry(view, state->width, state->height);
view_moved(view);
} }
} }
@ -289,7 +290,7 @@ handle_request_move(struct wl_listener *listener, void *data)
* want. * want.
*/ */
struct view *view = wl_container_of(listener, view, request_move); struct view *view = wl_container_of(listener, view, request_move);
if (view == view->server->seat.pressed.view) { if (view == view->server->seat.pressed.ctx.view) {
interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE); interactive_begin(view, LAB_INPUT_STATE_MOVE, LAB_EDGE_NONE);
} }
} }
@ -307,7 +308,7 @@ handle_request_resize(struct wl_listener *listener, void *data)
*/ */
struct wlr_xwayland_resize_event *event = data; struct wlr_xwayland_resize_event *event = data;
struct view *view = wl_container_of(listener, view, request_resize); struct view *view = wl_container_of(listener, view, request_resize);
if (view == view->server->seat.pressed.view) { if (view == view->server->seat.pressed.ctx.view) {
interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges); interactive_begin(view, LAB_INPUT_STATE_RESIZE, event->edges);
} }
} }
@ -1035,6 +1036,7 @@ xwayland_view_create(struct server *server,
CONNECT_SIGNAL(xsurface, xwayland_view, map_request); CONNECT_SIGNAL(xsurface, xwayland_view, map_request);
wl_list_insert(&view->server->views, &view->link); wl_list_insert(&view->server->views, &view->link);
view->creation_id = view->server->next_view_creation_id++;
if (xsurface->surface) { if (xsurface->surface) {
handle_associate(&xwayland_view->associate, NULL); handle_associate(&xwayland_view->associate, NULL);
@ -1050,7 +1052,6 @@ handle_new_surface(struct wl_listener *listener, void *data)
struct server *server = struct server *server =
wl_container_of(listener, server, xwayland_new_surface); wl_container_of(listener, server, xwayland_new_surface);
struct wlr_xwayland_surface *xsurface = data; struct wlr_xwayland_surface *xsurface = data;
wlr_xwayland_surface_ping(xsurface);
/* /*
* We do not create 'views' for xwayland override_redirect surfaces, * We do not create 'views' for xwayland override_redirect surfaces,
@ -1438,3 +1439,13 @@ xwayland_update_workarea(struct server *server)
}; };
wlr_xwayland_set_workareas(server->xwayland, &workarea, 1); wlr_xwayland_set_workareas(server->xwayland, &workarea, 1);
} }
void
xwayland_flush(struct server *server)
{
if (!server->xwayland || !server->xwayland->xwm) {
return;
}
xcb_flush(wlr_xwayland_get_xwm_connection(server->xwayland));
}