Compare commits

..

43 commits

Author SHA1 Message Date
John Lindgren
4f8b80700e tree-wide: do not try to use outputs with no scene_output
- check for valid scene_output in output_is_usable()
- change many "output != NULL" checks to use output_is_usable()
- remove one now-redundant separate check for valid scene_output

Fixes a crash at startup (with autoEnableOutputs=no) due to
dereferencing null scene_output in create_output_config() since:

7d7ece21d9
("output: suppress error when output position is unavailable")

Fixes: #3357
2026-02-04 21:05:16 +00:00
John Lindgren
81778a16bb xwayland: flush X11 connection after focus/activate
This mitigates a race where the XWayland server may generate an unwanted
FocusOut event for the newly activated window, if it receives mouse/
pointer events over the parallel wayland connection first.

In particular, this fixes an issue with certain fullscreen applications
(such as Minecraft) that self-minimize when receiving FocusOut.

Also limit a previous similar workaround to apply only to XWayland views.

Fixes: #3344

See also: https://gitlab.freedesktop.org/wlroots/wlroots/-/issues/4044
2026-02-04 18:53:45 +01:00
tokyo4j
12b6d05481 view: fix error messages on region or usable area changes
f58b532 implemented output-relative position saving/restoring on output
un-plugging/re-plugging. It worked as follows:

1. Store the output-relative view geometry in `view->last_placement`
  (if not set) before adding/removing an output from the layout.
2. After adding/removing an output, call `view_adjust_for_layout_change()`
  after the layout change to restore the output-relative view geometry
  based on `view->last_placement`.

However, it didn't consider `view_adjust_for_layout_change()` being
called from other places such as `regions_reconfigure()` and
`output_update_all_usable_areas()`, causing an error message "view has
no last placement info". This can happen when a panel is mapped or
unmapped, or on Reconfigure.

This commit fixes it by changing the life cycle of
`view->last_placement`. It used to be set only before output layout
changes and cleared on user-initiated moves/resizes, but now it is set
and updated on user-initiated moves/resizes. I think this is more
intuitive, too.
2026-02-04 19:14:51 +09:00
tokyo4j
7fabc6afe3 string-helpers: update comments for str_equal() 2026-02-04 19:14:51 +09:00
tokyo4j
55a256f2fa desktop: use for_each_view() in desktop_topmost_focusable_view()
Fixes a regression in 83b619c2 that the bottom-most view is focused
when when an exclusive layer surface (e.g. fuzzel) is unfocused.

Also, added some comments to clarify the order.
2026-01-31 17:16:13 +01:00
John Lindgren
67e3b36e58 view: cancel interactive move/resize when minimized/unmapped
Currently, with enough dexterity, you can minimize a window via hotkey
with one hand while continuing to move it (now invisible) with the
mouse in your other hand. The same can probably happen with XWayland
windows that self-unmap at an inconvenient time.

Probably not a common occurrence, but it's trivial to handle.
2026-01-31 08:15:49 +01:00
tokyo4j
37618a1456 Avoid double use of struct workspace
`struct workspace` was used both for representing an actual workspace
and for an entry of workspace configuration. Avoid it for clarity.
2026-01-30 18:41:37 +01:00
tokyo4j
4819f47f98 cycle: fix spurious focus changes on finishing window switcher
As described in the `FIXME` comment in `cycle.c`, we had spurious focus
changes where the keyboard focus is momentarily given to the previously
focused window when finishing the window switcher, an then it is given
to the selected window.

This commit fixes this by adding a parameter in
`seat_focus_override_end()` to avoid restoring the focus to the
previously focused window.

I also removed the check for `!seat->seat->keyboard_state.focused_surface`
in `seat_focus_override_end()`. I thought it was necessary to avoid
updating the keyboard focus if the focus was given to a session-lock
surface before e.g. finishing window switching, but `seat_focus()` is
no-op in that case anyway.
2026-01-30 17:06:30 +00:00
tokyo4j
83b619c285 desktop: use for_each_view() instead of scene graph traversal
...for desktop_focus_output() and desktop_topmost_focusable_view().
2026-01-30 16:53:06 +00:00
Jacques Boscq
edf3624dac Unshade window if selected from client-list-combined-menu 2026-01-28 22:34:50 +01:00
tokyo4j
90812103b6 view: don't update top layer visibility in view_discover_output()
This commit does not change any behaviors.

We don't need to call `desktop_update_top_layer_visibility()` in
`view_discover_output()` since it's called in `view_update_outputs()`.
2026-01-28 19:58:06 +00:00
tokyo4j
a62441ff77 cycle: show non-dialog child windows in window switcher
Before this commit, the window switcher skipped all the child windows.

However, as child windows not marked as modal dialogs can lose focus
(ref. `desktop_focus_view()`), it will make sense to include them in the
window switcher so that users can refocus them with keyboard.
This behavior follows KWin.
2026-01-27 21:10:32 +00:00
tokyo4j
87586104cd view: add view_is_modal_dialog() 2026-01-27 21:10:32 +00:00
Johan Malm
ac94e1d44c NEWS.md: interim update 2026-01-26 20:59:49 +00:00
Consolatis
645a7b56ce docs: correct description for 'overlay enabled' config
Some checks failed
labwc.github.io / notify (push) Has been cancelled
2026-01-21 20:30:35 +00:00
John Lindgren
aa672215b1 view: avoid restacking when view (or a sub-view) is already in front
Currently, every click within a sub-view results in first restacking
the parent view in front, and then the sub-view. This is unnecessary
and has caused issues in the past, such as with Xaw popups (which
we've worked around in d748dc78bc by adding an xcb_flush()).

It would be better not to do the unnecessary restacking at all.
2026-01-19 18:35:17 +00:00
Weblate
ee7376421c Translation updates from weblate
Co-authored-by: Baurzhan Muftakhidinov <baurthefirst@gmail.com>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: milotype <mail@milotype.de>
Translate-URL: https://translate.lxqt-project.org/projects/labwc/labwc/hr/
Translate-URL: https://translate.lxqt-project.org/projects/labwc/labwc/kk/
Translation: Labwc/labwc
2026-01-18 21:02:25 +00:00
John Lindgren
cd291fe051 view: avoid repeated focus changes unminimizing parent/child views
When unminimizing a group of N parent/child views, we currently end up
triggering N focus changes (as well as O(N^2) surface restackings) due
to calling desktop_focus_view() for each view in turn.

Since desktop_focus_view() already raises all sibling views together
via view_move_to_front(), let's make view_minimize() call it only once
at the very end, once all views are visible.

Test cases:
- Audacious with floating plugin views (XWayland)
- xfce4-terminal with About dialog (xdg-shell)

v2: also avoid repeated focus changes when minimizing
2026-01-18 20:38:53 +00:00
tokyo4j
7d7ece21d9 output: suppress error when output position is unavailable
This commit removes the "failed to get output layout box" error printed
when disabling an output with `wlr-randr`.

Also, use `output->scene_output->{x,y}` instead of calling
`wlr_output_layout_get_box()` for simplicity. The positions of
scene-outputs are synced with the output-layout-outputs.
2026-01-14 18:14:10 +00:00
tokyo4j
37c7de32c8 view: keep and restore the output-relative position on layout changes
Before this patch, we always tried to preserve the global positions of
floating windows across layout changes, which meant that windows could
jump to different outputs if the output coordinates changed.

Instead, this patch adds the output name and output-relative position in
`view->last_placement` to keep them across layout changes, like KDE and
GNOME do.

This also allows us to remove `view->lost_output_due_to_layout_change`,
which was required to keep the output of fullscreen/maximized windows,
since we now always try to keep `view->output` whether or not the window
is floating.
2026-01-15 02:10:31 +09:00
tokyo4j
f58b532214 view: save last placement info before layout change
I will add output name and relative view position in
`view->last_placement` later, which needs to be saved before layout
changes unlike the global position.
2026-01-15 02:10:31 +09:00
tokyo4j
a964d41dd1 view: document adjust_floating_geometry() 2026-01-15 02:10:31 +09:00
tokyo4j
ee52853e69 view: rename last_layout_geometry to last_placement.layout_geo 2026-01-15 02:10:31 +09:00
John Lindgren
20929c0484 view: rework saving/restoring geometry across layout changes
After several iterations, this is basically a complete re-work. The old
implementation was difficult to follow and sometimes failed to restore
fullscreen/maximized/tiled geometry correctly, since it was based
entirely on natural (floating) geometry.

The new implementation:
 - always saves the actual (pending) geometry at first layout change
 - explicitly tracks whether a view has moved between outputs
 - consolidates invalidating the saved geometry into one place, rather
   than having lots of invalidate() calls sprinkled everywhere
2026-01-12 17:41:02 +09:00
Vasiliy Stelmachenok
c1c156ef39 xwayland: Do not try to focus a window that was already in focus
Signed-off-by: Vasiliy Stelmachenok <ventureo@cachyos.org>
2026-01-12 00:06:10 +01:00
Tomi Ollila
f09a0c2be3 scripts/check: run checkpatch.pl processes with max 16 args each
Reduce the overhead of fork/execve/perl startup time by not
doing those for every files that are checked.

This also makes the check execution complete faster.

On similar systems and similar background load, the execution time
varies based on how find(1) outputs the (*.[ch]) files it sees on
filesystem -- the filenames are not sorted but are written from
directories src/ include/ clients/ t/ in that order -- more than
80% of the time goes checking files in src/, and how the 16-file
batches from that dir (108 files in src/, 208 total, as of 2026-01)
are distributed the checkpatch.pl processes affect mostly to the
total run time.
2026-01-09 23:17:14 +01:00
Consolatis
02327e19b0 CI: disable no-font check
Void made the foot package depend on the font.

Running this test now always fails with
> `dejavu-fonts-ttf-2.37_3` in transaction breaks installed pkg `foot-1.25.0_2'

As this test uses both and the feature itself
seems to work well lets just skip it for now.
2026-01-05 17:40:28 +00:00
John Lindgren
9600c73ea4 protocols: remove wlr-input-inhibitor-unstable-v1
It appears we don't support this protocol any more. It was originally
used for swaylock, which now uses ext-session-lock-v1 instead.
2026-01-04 04:20:33 +01:00
John Lindgren
f5909ac54d view: eliminate store_natural_geometry arguments
These were added to fix handling of natural geometry for snap-to-edge
behavior back in 9021020f6e and seemed like a good idea at the time.
Since then, the number of call sites has exploded, so it seems more
maintainable to put explicit checks for interactive move within the
three functions affected.
2026-01-01 12:54:16 +00:00
tokyo4j
742c2b53fd cycle: implement scrollable OSD
Before this commit, the OSD could overflow the screen when displaying
many window items. In this commit, we hide the overflowed items and
show a scrollbar to fit the OSD within the screen.
2025-12-31 22:46:53 +00:00
tokyo4j
97b31429a0 cycle: cycle: ensure server->cycle is cleared in destroy_cycle()
...as I'm adding more state variables there for scrolling mechanism.
2025-12-31 22:46:53 +00:00
tokyo4j
9f50971e0b Halt window switcher on Reconfigure
There was an invalid memory access (since introduction of thumbnail
style in 2e9292b) with following steps:

1. Press Alt-Tab
2. Update `<windowSwitcher><osd><style>` from `classic` to `thumbnail`
3. Run `Reconfigure`
4. Press Alt-Tab again

...because `cycle_osd_thumbnail_update()` is called even though
`cycle_osd_output->items` holds `cycle_osd_classic_item`.

This commit halts window switcher on `Reconfigure` to clear
`cycle_osd_output->items` and avoid that invalid memory access.
2025-12-31 14:18:02 +01:00
tokyo4j
8ad96c0410 cycle: add server->cycle_preview_tree
This doesn't change any behaviors.
2025-12-28 21:03:03 +00:00
tokyo4j
dfe428ae14 cycle: refactor to aggregate type definitions into cycle.h
We declared `cycle_state` struct in `labwc.h` and `cycle_osd_scene`
struct in `output.h`, which was unclean in terms of separation of
concerns.

So this commit firstly moves `cycle_state` to `cycle.h`, then replaces
`cycle_osd_scene` in `output.h` with `cycle_osd_output` in `cycle.h`
which is dynamically allocated in a similar manner to
`session_lock_output`. This ensures that all states about alt-tabbing
are stored in `server->cycle`.

Also, this commit fixes a rare memory leak in `output->cycle_osd.items`
when an output is destroyed while alt-tabbing, by freeing it when the
osd tree is destroyed.
2025-12-28 20:57:37 +00:00
Consolatis
276d4e61f9 config/rcxml.c: prevent wrong parse_bool() err message
by only parsing the boolean when it is not `disabledOnExternalMouse`.

Fixes: #3293
2025-12-26 20:39:36 +00:00
Consolatis
ec9579fdc1 CI: disable IRC notifications for now
Libera started requiring an account for the IP ranges of GH CI.
This currently results in the IRC notification job running for
more than one hour because the IRC client it uses doesn't
terminate properly after giving up and thus eats our CI minutes.

The patch disables the notifications for now until we figure out
a better way, either by registering a libera account for the bot
and supplying the credentials via secret or by changing to GH
webhooks to a publicly available service like pipe.pico.sh which
then can be listened to by our existing IRC bot.
2025-12-26 16:22:58 +01:00
tokyo4j
610d869561 cycle: add <action name="NextWindow" output="" and identifier="">
Some checks failed
labwc.github.io / notify (push) Has been cancelled
output="all|focused|cursor" filters windows by the output they are on.
identifier="all|current" filters windows by their app-id.
2025-12-26 05:25:54 +09:00
tokyo4j
a5c6ff499c cycle: support <action name="NextWindow" workspace="current|all">
This commit deprecates <windowSwitcher allWorkspaces="yes|no"> and adds
per-action argument <action name="NextWindow" workspace="current|all">.
2025-12-26 05:25:54 +09:00
Consolatis
caa9b90e80 docs: add example for GTK4 composing 2025-12-23 20:11:33 +00:00
Cameron Scott McCreery
64aec6ff5d
workspaces: add config option for initial workspace selection
Some checks failed
labwc.github.io / notify (push) Has been cancelled
2025-12-22 22:17:43 +01:00
tokyo4j
c9b088e343 cycle: add and use get_outputs_by_filter()
This function can be reused for filtering windows to cycle through.
2025-12-22 18:57:51 +00:00
tokyo4j
64af206114 Rename cycle_osd_output_criteria to cycle_output_filter 2025-12-22 18:57:51 +00:00
tokyo4j
e2d83ff7f5 rcxml: sync rcxml.window_switcher with XML format 2025-12-22 18:57:51 +00:00
43 changed files with 1128 additions and 670 deletions

View file

@ -280,14 +280,18 @@ jobs:
LABWC_RUNS=2 scripts/ci/smoke-test.sh build-gcc-gdb
' | $TARGET
- name: Build with gcc - catch no font installed case
if: matrix.name == 'Void-musl'
run: |
echo '
cd "$GITHUB_WORKSPACE"
xbps-remove -y dejavu-fonts-ttf
export CC=gcc
meson setup build-gcc-nofont -Dxwayland=enabled --werror
meson compile -C build-gcc-nofont
LABWC_EXPECT_RETURNCODE=1 scripts/ci/smoke-test.sh build-gcc-nofont
' | $TARGET
# Void made the foot package depend on the font.
# As this test uses both and the feature itself
# seems to work well lets just skip it for now
#
#- name: Build with gcc - catch no font installed case
# if: matrix.name == 'Void-musl'
# run: |
# echo '
# cd "$GITHUB_WORKSPACE"
# xbps-remove -y dejavu-fonts-ttf
# export CC=gcc
# meson setup build-gcc-nofont -Dxwayland=enabled --werror
# meson compile -C build-gcc-nofont
# LABWC_EXPECT_RETURNCODE=1 scripts/ci/smoke-test.sh build-gcc-nofont
# ' | $TARGET

38
NEWS.md
View file

@ -9,6 +9,7 @@ The format is based on [Keep a Changelog]
| Date | All Changes | wlroots version | lines-of-code |
|------------|---------------|-----------------|---------------|
| 2026-01-24 | [unreleased] | 0.19.2 | |
| 2025-12-19 | [0.9.3] | 0.19.2 | 28968 |
| 2025-10-10 | [0.9.2] | 0.19.1 | 28818 |
| 2025-08-02 | [0.9.1] | 0.19.0 | 28605 |
@ -108,6 +109,34 @@ There are some regression warnings worth noting for the switch to wlroots 0.19:
[unreleased-commits]
### Added
- Implement scrollable window-switcher OSD [#3291] @tokyo4j
- Support the `NextWindow` options listed below [#3271] @tokyo4j
- `<action name="NextWindow" workspace="current|all"/>`
- `<action name="NextWindow" output="all|focused|cursor"/>`
- `<action name="NextWindow" identifier="all|current"/>`
- Add config option `*<desktops><initial>` for setting the active workspace on
startup. [#3265] @5trixs0f
### Fixed
- Improve logic for restoring view positions after output disconnect and
reconnect [#3309] [#3310] @jlindgren90 @tokyo4j
- Avoid restacking when a window is already in front; and avoid repeated focus
changes when unminimizing a window with child windows. These changes are not
believed to be visible to the user, but are mentioned here for completeness.
[#3323] [#3325] @jlindgren90
- Do not try to focus an XWayland window that is already in focus. This fixes an
issue with old versions of Minecraft (<=1.5.2). [#3316] @ventureoo
- Halt window-switcher on reconfigure to avoid invalid memory access in some
circumstances. [#3297] @tokyo4j
### Changed
- `<windowSwitcher allWorkspaces="yes|no">` is deprecated. Instead, use:
`<action name="NextWindow" workspace="current|all">`. [#3271] @tokyo4j
## 0.9.3 - 2025-12-19
[0.9.3-commits]
@ -3023,4 +3052,13 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16
[#3249]: https://github.com/labwc/labwc/pull/3249
[#3251]: https://github.com/labwc/labwc/pull/3251
[#3252]: https://github.com/labwc/labwc/pull/3252
[#3265]: https://github.com/labwc/labwc/pull/3265
[#3267]: https://github.com/labwc/labwc/pull/3267
[#3271]: https://github.com/labwc/labwc/pull/3271
[#3291]: https://github.com/labwc/labwc/pull/3291
[#3297]: https://github.com/labwc/labwc/pull/3297
[#3309]: https://github.com/labwc/labwc/pull/3309
[#3310]: https://github.com/labwc/labwc/pull/3310
[#3316]: https://github.com/labwc/labwc/pull/3316
[#3323]: https://github.com/labwc/labwc/pull/3323
[#3325]: https://github.com/labwc/labwc/pull/3325

View file

@ -22,6 +22,13 @@
# XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle
# XKB_DEFAULT_OPTIONS=grp:shift_caps_toggle
## GTK4 started to require input methods like fcitx5 or ibus to handle
## simple compose sequences. If you do not use input methods, uncomment
## the following line to force GTK4 internal composing. For further
## information see https://labwc.github.io/integration.html#gtk
##
# GTK_IM_MODULE=simple
##
## Set cursor theme and size. Find system icons themes with:
## `find /usr/share/icons/ -type d -name "cursors"`

View file

@ -125,13 +125,25 @@ Actions are used in menus and keyboard/mouse bindings.
Resize and move the active window back to its untiled or unmaximized
position if it had been maximized or tiled to a direction or region.
*<action name="NextWindow" />*++
*<action name="PreviousWindow" />*
*<action name="NextWindow" workspace="current" output="all" identifier="all" />*++
*<action name="PreviousWindow" workspace="current" output="all" identifier="all" />*
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" />*
Re-load configuration and theme files.

View file

@ -339,7 +339,7 @@ this is for compatibility with Openbox.
## WINDOW SWITCHER
```
<windowSwitcher preview="yes" outlines="yes" allWorkspaces="no">
<windowSwitcher preview="yes" outlines="yes">
<osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" />
<fields>
<field content="icon" width="5%" />
@ -349,17 +349,13 @@ this is for compatibility with Openbox.
</windowSwitcher>
```
*<windowSwitcher preview="" outlines="" allWorkspaces="" unshade="" order="">*
*<windowSwitcher preview="" outlines="" unshade="" order="">*
*preview* [yes|no] Preview the contents of the selected window when
switching between windows. Default is yes.
*outlines* [yes|no] Draw an outline around the selected window when
switching between windows. Default is yes.
*allWorkspaces* [yes|no] Show windows regardless of what workspace
they are on. Default no (that is only windows on the current workspace
are shown).
*unshade* [yes|no] Temporarily unshade windows when switching between
them and permanently unshade on the final selection. Default is yes.
@ -516,7 +512,7 @@ extending outward from the snapped edge.
*<range><inner>* and *<range><outer>*, and 50 for *<cornerRange>*.
*<snapping><overlay><enabled>* [yes|no]
Show an overlay when snapping to a window to an edge. Default is yes.
Show an overlay when snapping a window to an output edge. Default is yes.
*<snapping><overlay><delay><inner>*++
*<snapping><overlay><delay><outer>*
@ -575,6 +571,11 @@ extending outward from the snapped edge.
is 1. The number attribute is optional. If the number attribute is
specified, names.name is not required.
*<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>*
Define the timeout after which to hide the workspace OSD.
A setting of 0 disables the OSD. Default is 1000 ms.

View file

@ -77,7 +77,7 @@
</font>
</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" />
<fields>
<field content="icon" width="5%" />
@ -98,7 +98,7 @@
Some contents are fixed-length and others are variable-length.
See "man 5 labwc-config" for details.
<windowSwitcher preview="no" outlines="no" allWorkspaces="yes">
<windowSwitcher preview="no" outlines="no">
<osd show="yes" />
<fields>
<field content="workspace" width="5%" />
@ -119,7 +119,7 @@
then workspace name, then identifier/app-id, then the window title.
It uses 100% of OSD window width.
<windowSwitcher preview="no" outlines="no" allWorkspaces="yes">
<windowSwitcher preview="no" outlines="no">
<osd show="yes" />
<fields>
<field content="custom" format="foobar %b %3s %-10o %-20W %-10i %t" width="100%" />
@ -175,6 +175,7 @@
Workspaces can be configured like this:
<desktops>
<popupTime>1000</popupTime>
<initial>Workspace 1</initial>
<names>
<name>Workspace 1</name>
<name>Workspace 2</name>

View file

@ -90,8 +90,8 @@ bool str_starts_with(const char *s, char needle, const char *ignore_chars);
/**
* str_equal - indicate whether two strings are identical
* @a: first string to compare
* @b: second string to compare
* @a: first string to compare (can be NULL)
* @b: second string to compare (can be NULL)
*
* If both strings are NULL, returns true.
*/

View file

@ -57,6 +57,11 @@ struct usable_area_override {
struct wl_list link; /* struct rcxml.usable_area_overrides */
};
struct workspace_config {
struct wl_list link; /* struct rcxml.workspace_config.workspaces */
char *name;
};
struct rcxml {
/* from command line */
char *config_dir;
@ -168,8 +173,9 @@ struct rcxml {
struct {
int popuptime;
int min_nr_workspaces;
char *initial_workspace_name;
char *prefix;
struct wl_list workspaces; /* struct workspace.link */
struct wl_list workspaces; /* struct workspace_config.link */
} workspace_config;
/* Regions */
@ -177,16 +183,18 @@ struct rcxml {
/* Window Switcher */
struct {
bool show;
bool preview;
bool outlines;
bool unshade;
enum lab_view_criteria criteria;
struct wl_list fields; /* struct window_switcher_field.link */
enum cycle_osd_style style;
enum cycle_osd_output_criteria output_criteria;
char *thumbnail_label_format;
enum window_switcher_order order;
enum cycle_workspace_filter workspace_filter; /* deprecated */
struct {
bool show;
enum cycle_osd_style style;
enum cycle_output_filter output_filter;
char *thumbnail_label_format;
struct wl_list fields; /* struct cycle_osd_field.link */
} osd;
} window_switcher;
struct wl_list window_rules; /* struct window_rule.link */

View file

@ -71,9 +71,9 @@ enum lab_view_criteria {
/* Positive criteria */
LAB_VIEW_CRITERIA_FULLSCREEN = 1 << 1,
LAB_VIEW_CRITERIA_ALWAYS_ON_TOP = 1 << 2,
LAB_VIEW_CRITERIA_ROOT_TOPLEVEL = 1 << 3,
/* Negative criteria */
LAB_VIEW_CRITERIA_NO_DIALOG = 1 << 5,
LAB_VIEW_CRITERIA_NO_ALWAYS_ON_TOP = 1 << 6,
LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER = 1 << 7,
LAB_VIEW_CRITERIA_NO_OMNIPRESENT = 1 << 8,
@ -117,10 +117,20 @@ enum cycle_osd_style {
CYCLE_OSD_STYLE_THUMBNAIL,
};
enum cycle_osd_output_criteria {
CYCLE_OSD_OUTPUT_ALL,
CYCLE_OSD_OUTPUT_CURSOR,
CYCLE_OSD_OUTPUT_FOCUSED,
enum cycle_workspace_filter {
CYCLE_WORKSPACE_ALL,
CYCLE_WORKSPACE_CURRENT,
};
enum cycle_output_filter {
CYCLE_OUTPUT_ALL,
CYCLE_OUTPUT_CURSOR,
CYCLE_OUTPUT_FOCUSED,
};
enum cycle_app_id_filter {
CYCLE_APP_ID_ALL,
CYCLE_APP_ID_CURRENT,
};
#endif /* LABWC_CONFIG_TYPES_H */

View file

@ -4,8 +4,11 @@
#include <stdbool.h>
#include <wayland-server-core.h>
#include <wlr/util/box.h>
#include "config/types.h"
struct output;
struct wlr_box;
enum lab_cycle_dir {
LAB_CYCLE_DIR_NONE,
@ -39,7 +42,47 @@ struct cycle_osd_field {
enum cycle_osd_field_content content;
int width;
char *format;
struct wl_list link; /* struct rcxml.window_switcher.fields */
struct wl_list link; /* struct rcxml.window_switcher.osd.fields */
};
struct cycle_filter {
enum cycle_workspace_filter workspace;
enum cycle_output_filter output;
enum cycle_app_id_filter app_id;
};
struct cycle_state {
struct view *selected_view;
struct wl_list views;
struct wl_list osd_outputs; /* struct cycle_osd_output.link */
bool preview_was_shaded;
bool preview_was_enabled;
struct wlr_scene_node *preview_node;
struct wlr_scene_node *preview_dummy;
struct lab_scene_rect *preview_outline;
struct cycle_filter filter;
};
struct cycle_osd_output {
struct wl_list link; /* struct cycle_state.osd_outputs */
struct output *output;
struct wl_listener tree_destroy;
/* set by cycle_osd_impl->init() */
struct wl_list items; /* struct cycle_osd_item.link */
struct wlr_scene_tree *tree;
/* set by cycle_osd_impl->init() and moved by cycle_osd_scroll_update() */
struct wlr_scene_tree *items_tree;
/* used in osd-scroll.c */
struct cycle_osd_scroll_context {
int top_row_idx;
int nr_rows, nr_cols, nr_visible_rows;
int delta_y;
struct wlr_box bar_area;
struct wlr_scene_tree *bar_tree;
struct lab_scene_rect *bar;
} scroll;
};
struct buf;
@ -48,7 +91,8 @@ struct server;
struct wlr_scene_node;
/* Begin window switcher */
void cycle_begin(struct server *server, enum lab_cycle_dir direction);
void cycle_begin(struct server *server, enum lab_cycle_dir direction,
struct cycle_filter filter);
/* Cycle the selected view in the window switcher */
void cycle_step(struct server *server, enum lab_cycle_dir direction);
@ -84,17 +128,38 @@ struct cycle_osd_item {
struct cycle_osd_impl {
/*
* Create a scene-tree of OSD for an output.
* This sets output->cycle_osd.{items,tree}.
* Create a scene-tree of OSD for an output and fill
* osd_output->items.
*/
void (*create)(struct output *output);
void (*init)(struct cycle_osd_output *osd_output);
/*
* Update output->cycle_osd.tree to highlight
* server->cycle_state.selected_view.
* Update the OSD to highlight server->cycle.selected_view.
*/
void (*update)(struct output *output);
void (*update)(struct cycle_osd_output *osd_output);
};
#define SCROLLBAR_W 10
/**
* Initialize the context and scene for scrolling OSD items.
*
* @output: Output of the OSD
* @bar_area: Area where the scrollbar is drawn
* @delta_y: The vertical delta by which items are scrolled (usually item height)
* @nr_cols: Number of columns in the OSD
* @nr_rows: Number of rows in the OSD
* @nr_visible_rows: Number of visible rows in the OSD
* @border_color: Border color of the scrollbar
* @bg_color: Background color of the scrollbar
*/
void cycle_osd_scroll_init(struct cycle_osd_output *osd_output,
struct wlr_box bar_area, int delta_y,
int nr_cols, int nr_rows, int nr_visible_rows,
float *border_color, float *bg_color);
/* Scroll the OSD to show server->cycle.selected_view if needed */
void cycle_osd_scroll_update(struct cycle_osd_output *osd_output);
extern struct cycle_osd_impl cycle_osd_classic_impl;
extern struct cycle_osd_impl cycle_osd_thumbnail_impl;

View file

@ -5,6 +5,7 @@
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include "common/set.h"
#include "cycle.h"
#include "input/cursor.h"
#include "overlay.h"
@ -187,6 +188,7 @@ struct server {
struct wlr_xdg_toplevel_icon_manager_v1 *xdg_toplevel_icon_manager;
struct wl_listener xdg_toplevel_icon_set_icon;
/* front to back order */
struct wl_list views;
uint64_t next_view_creation_id;
struct wl_list unmanaged_surfaces;
@ -237,6 +239,7 @@ struct server {
/* Tree for unmanaged xsurfaces without initialized view (usually popups) */
struct wlr_scene_tree *unmanaged_tree;
#endif
struct wlr_scene_tree *cycle_preview_tree;
/* Tree for built in menu */
struct wlr_scene_tree *menu_tree;
@ -302,15 +305,7 @@ struct server {
struct wlr_security_context_manager_v1 *security_context_manager_v1;
/* Set when in cycle (alt-tab) mode */
struct cycle_state {
struct view *selected_view;
struct wl_list views;
bool preview_was_shaded;
bool preview_was_enabled;
struct wlr_scene_node *preview_node;
struct wlr_scene_node *preview_dummy;
struct lab_scene_rect *preview_outline;
} cycle;
struct cycle_state cycle;
struct theme *theme;
@ -403,10 +398,10 @@ void seat_output_layout_changed(struct seat *seat);
void seat_focus_override_begin(struct seat *seat, enum input_mode input_mode,
enum lab_cursors cursor_shape);
/*
* Restore the pointer/keyboard focus which was cleared in
* seat_focus_override_begin().
* If restore_focus=true, restore the pointer/keyboard focus which was cleared
* in seat_focus_override_begin().
*/
void seat_focus_override_end(struct seat *seat);
void seat_focus_override_end(struct seat *seat, bool restore_focus);
/**
* interactive_anchor_to_cursor() - repositions the geometry to remain

View file

@ -19,11 +19,6 @@ struct output {
struct wlr_scene_tree *session_lock_tree;
struct wlr_scene_buffer *workspace_osd;
struct cycle_osd_scene {
struct wl_list items; /* struct cycle_osd_item */
struct wlr_scene_tree *tree;
} cycle_osd;
/* In output-relative scene coordinates */
struct wlr_box usable_area;

View file

@ -214,12 +214,20 @@ struct view {
*/
struct wlr_box natural_geometry;
/*
* Whenever an output layout change triggers a view relocation, the
* last pending position (or natural geometry) will be saved so the
* view may be restored to its original location on a subsequent layout
* change.
* last_placement represents the last view position set by the user.
* output_name and relative_geo are used to keep or restore the view
* position relative to the output and layout_geo is used to keep the
* global position when the output is lost.
*/
struct wlr_box last_layout_geometry;
struct {
char *output_name;
/* view geometry in output-relative coordinates */
struct wlr_box relative_geo;
/* view geometry in layout coordinates */
struct wlr_box layout_geo;
} last_placement;
/* Set temporarily when moving view due to layout change */
bool adjusting_for_layout_change;
/* used by xdg-shell views */
uint32_t pending_configure_serial;
@ -336,7 +344,7 @@ void view_query_free(struct view_query *view);
bool view_matches_query(struct view *view, struct view_query *query);
/**
* for_each_view() - iterate over all views which match criteria
* for_each_view() - iterate over all views which match criteria (front to back)
* @view: Iterator.
* @head: Head of list to iterate over.
* @criteria: Criteria to match against.
@ -352,7 +360,7 @@ bool view_matches_query(struct view *view, struct view_query *query);
view = view_next(head, view, criteria))
/**
* for_each_view_reverse() - iterate over all views which match criteria
* for_each_view_reverse() - iterate over all views which match criteria (back to front)
* @view: Iterator.
* @head: Head of list to iterate over.
* @criteria: Criteria to match against.
@ -512,8 +520,7 @@ void view_constrain_size_to_that_of_usable_area(struct view *view);
void view_set_maximized(struct view *view, enum view_axis maximized);
void view_set_untiled(struct view *view);
void view_maximize(struct view *view, enum view_axis axis,
bool store_natural_geometry);
void view_maximize(struct view *view, enum view_axis axis);
void view_set_fullscreen(struct view *view, bool fullscreen);
void view_toggle_maximize(struct view *view, enum view_axis axis);
bool view_wants_decorations(struct view *view);
@ -534,19 +541,20 @@ bool view_titlebar_visible(struct view *view);
void view_set_ssd_mode(struct view *view, enum lab_ssd_mode mode);
void view_set_decorations(struct view *view, enum lab_ssd_mode mode, bool force_ssd);
void view_toggle_fullscreen(struct view *view);
void view_invalidate_last_layout_geometry(struct view *view);
void view_adjust_for_layout_change(struct view *view);
void view_move_to_edge(struct view *view, enum lab_edge direction, bool snap_to_windows);
void view_grow_to_edge(struct view *view, enum lab_edge direction);
void view_shrink_to_edge(struct view *view, enum lab_edge direction);
void view_snap_to_edge(struct view *view, enum lab_edge direction,
bool across_outputs, bool combine, bool store_natural_geometry);
void view_snap_to_region(struct view *view, struct region *region, bool store_natural_geometry);
bool across_outputs, bool combine);
void view_snap_to_region(struct view *view, struct region *region);
void view_move_to_output(struct view *view, struct output *output);
void view_move_to_front(struct view *view);
void view_move_to_back(struct view *view);
bool view_is_modal_dialog(struct view *view);
/**
* view_get_modal_dialog() - returns any modal dialog found among this
* view's children or siblings (or possibly this view itself). Applies

View file

@ -10,12 +10,8 @@ struct seat;
struct server;
struct wlr_scene_tree;
/* Double use: as config in config/rcxml.c and as instance in workspaces.c */
struct workspace {
struct wl_list link; /*
* struct server.workspaces
* struct rcxml.workspace_config.workspaces
*/
struct wl_list link; /* struct server.workspaces */
struct server *server;
char *name;

View file

@ -1 +1 @@
ar ca cs da de el es et eu fa fi fr gl he hu id it ja ka ko lt ms nl pa pl pt pt_BR ru sk sv tr uk vi zh_CN zh_TW
ar ca cs da de el es et eu fa fi fr gl he hr hu id it ja ka kk ko lt ms nl pa pl pt pt_BR ru sk sv tr uk vi zh_CN zh_TW

81
po/hr.po Normal file
View file

@ -0,0 +1,81 @@
# Labwc pot file
# Copyright (C) 2024
# This file is distributed under the same license as the labwc package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: labwc\n"
"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n"
"POT-Creation-Date: 2024-09-19 21:09+1000\n"
"PO-Revision-Date: 2026-01-18 21:01+0000\n"
"Last-Translator: milotype <mail@milotype.de>\n"
"Language-Team: Croatian <https://translate.lxqt-project.org/projects/labwc/"
"labwc/hr/>\n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.2.1\n"
#: src/menu/menu.c:1016
msgid "Go there..."
msgstr "Idi tamo …"
#: src/menu/menu.c:1034
msgid "Terminal"
msgstr "Terminal"
#: src/menu/menu.c:1040
msgid "Reconfigure"
msgstr "Konfiguriraj ponovo"
#: src/menu/menu.c:1042
msgid "Exit"
msgstr "Zatvori"
#: src/menu/menu.c:1056
msgid "Minimize"
msgstr "Smanji prozor"
#: src/menu/menu.c:1058
msgid "Maximize"
msgstr "Maks. raširi prozor"
#: src/menu/menu.c:1060
msgid "Fullscreen"
msgstr "Cjeloekranski prikaz"
#: src/menu/menu.c:1062
msgid "Roll Up/Down"
msgstr "Pomiči se prema gore/dolje"
#: src/menu/menu.c:1064
msgid "Decorations"
msgstr "Grafički elementi"
#: src/menu/menu.c:1066
msgid "Always on Top"
msgstr "Uvijek gore"
#: src/menu/menu.c:1071
msgid "Move Left"
msgstr "Pomakni ulijevo"
#: src/menu/menu.c:1078
msgid "Move Right"
msgstr "Pomakni udesno"
#: src/menu/menu.c:1083
msgid "Always on Visible Workspace"
msgstr "Uvijek na vidljivom radnom prostoru"
#: src/menu/menu.c:1086
msgid "Workspace"
msgstr "Radni prostor"
#: src/menu/menu.c:1089
msgid "Close"
msgstr "Zatvori"

80
po/kk.po Normal file
View file

@ -0,0 +1,80 @@
# Labwc pot file
# Copyright (C) 2024
# This file is distributed under the same license as the labwc package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: labwc\n"
"Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n"
"POT-Creation-Date: 2024-09-19 21:09+1000\n"
"PO-Revision-Date: 2026-01-18 21:01+0000\n"
"Last-Translator: Baurzhan Muftakhidinov <baurthefirst@gmail.com>\n"
"Language-Team: Kazakh <https://translate.lxqt-project.org/projects/labwc/"
"labwc/kk/>\n"
"Language: kk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.2.1\n"
#: src/menu/menu.c:1016
msgid "Go there..."
msgstr "Онда бару..."
#: src/menu/menu.c:1034
msgid "Terminal"
msgstr "Терминал"
#: src/menu/menu.c:1040
msgid "Reconfigure"
msgstr "Қайта баптау"
#: src/menu/menu.c:1042
msgid "Exit"
msgstr "Шығу"
#: src/menu/menu.c:1056
msgid "Minimize"
msgstr "Қайыру"
#: src/menu/menu.c:1058
msgid "Maximize"
msgstr "Жазық қылу"
#: src/menu/menu.c:1060
msgid "Fullscreen"
msgstr "Толық экран"
#: src/menu/menu.c:1062
msgid "Roll Up/Down"
msgstr "Жоғары/Төмен жинау"
#: src/menu/menu.c:1064
msgid "Decorations"
msgstr "Безендірулер"
#: src/menu/menu.c:1066
msgid "Always on Top"
msgstr "Әрқашан жоғарыда"
#: src/menu/menu.c:1071
msgid "Move Left"
msgstr "Солға жылжыту"
#: src/menu/menu.c:1078
msgid "Move Right"
msgstr "Оңға жылжыту"
#: src/menu/menu.c:1083
msgid "Always on Visible Workspace"
msgstr "Әрқашан көрінетін жұмыс орнында"
#: src/menu/menu.c:1086
msgid "Workspace"
msgstr "Жұмыс орны"
#: src/menu/menu.c:1089
msgid "Close"
msgstr "Жабу"

View file

@ -27,7 +27,6 @@ server_protocols = [
wl_protocol_dir / 'staging/ext-image-copy-capture/ext-image-copy-capture-v1.xml',
'cosmic-workspace-unstable-v1.xml',
'wlr-layer-shell-unstable-v1.xml',
'wlr-input-inhibitor-unstable-v1.xml',
'wlr-output-power-management-unstable-v1.xml',
]

View file

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_input_inhibit_unstable_v1">
<copyright>
Copyright © 2018 Drew DeVault
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_input_inhibit_manager_v1" version="1">
<description summary="inhibits input events to other clients">
Clients can use this interface to prevent input events from being sent to
any surfaces but its own, which is useful for example in lock screen
software. It is assumed that access to this interface will be locked down
to whitelisted clients by the compositor.
</description>
<request name="get_inhibitor">
<description summary="inhibit input to other clients">
Activates the input inhibitor. As long as the inhibitor is active, the
compositor will not send input events to other clients.
</description>
<arg name="id" type="new_id" interface="zwlr_input_inhibitor_v1"/>
</request>
<enum name="error">
<entry name="already_inhibited" value="0" summary="an input inhibitor is already in use on the compositor"/>
</enum>
</interface>
<interface name="zwlr_input_inhibitor_v1" version="1">
<description summary="inhibits input to other clients">
While this resource exists, input to clients other than the owner of the
inhibitor resource will not receive input events. The client that owns
this resource will receive all input events normally. The compositor will
also disable all of its own input processing (such as keyboard shortcuts)
while the inhibitor is active.
The compositor may continue to send input events to selected clients,
such as an on-screen keyboard (via the input-method protocol).
</description>
<request name="destroy" type="destructor">
<description summary="destroy the input inhibitor object">
Destroy the inhibitor and allow other clients to receive input.
</description>
</request>
</interface>
</protocol>

View file

@ -20,7 +20,7 @@ run_checks () {
fi
find src/ include/ clients/ t/ \( -name "*.c" -o -name "*.h" \) -type f -print0 |
nice xargs -0 --max-args 1 --max-procs $(nproc) \
nice xargs -0 --max-args 16 --max-procs $(nproc) \
scripts/checkpatch.pl --terse --no-tree --strict --file
return $?
}

View file

@ -366,6 +366,44 @@ action_arg_from_xml_node(struct action *action, const char *nodename, const char
goto cleanup;
}
break;
case ACTION_TYPE_NEXT_WINDOW:
case ACTION_TYPE_PREVIOUS_WINDOW:
if (!strcasecmp(argument, "workspace")) {
if (!strcasecmp(content, "all")) {
action_arg_add_int(action, argument, CYCLE_WORKSPACE_ALL);
} else if (!strcasecmp(content, "current")) {
action_arg_add_int(action, argument, CYCLE_WORKSPACE_CURRENT);
} else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
}
goto cleanup;
}
if (!strcasecmp(argument, "output")) {
if (!strcasecmp(content, "all")) {
action_arg_add_int(action, argument, CYCLE_OUTPUT_ALL);
} else if (!strcasecmp(content, "cursor")) {
action_arg_add_int(action, argument, CYCLE_OUTPUT_CURSOR);
} else if (!strcasecmp(content, "focused")) {
action_arg_add_int(action, argument, CYCLE_OUTPUT_FOCUSED);
} else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
}
goto cleanup;
}
if (!strcasecmp(argument, "identifier")) {
if (!strcasecmp(content, "all")) {
action_arg_add_int(action, argument, CYCLE_APP_ID_ALL);
} else if (!strcasecmp(content, "current")) {
action_arg_add_int(action, argument, CYCLE_APP_ID_CURRENT);
} else {
wlr_log(WLR_ERROR, "Invalid argument for action %s: '%s' (%s)",
action_names[action->type], argument, content);
}
goto cleanup;
}
break;
case ACTION_TYPE_SHOW_MENU:
if (!strcmp(argument, "menu")) {
action_arg_add_str(action, argument, content);
@ -1108,7 +1146,7 @@ run_action(struct view *view, struct server *server, struct action *action,
}
bool combine = action_get_bool(action, "combine", false);
view_snap_to_edge(view, edge, /*across_outputs*/ true,
combine, /*store_natural_geometry*/ true);
combine);
}
break;
case ACTION_TYPE_GROW_TO_EDGE:
@ -1126,19 +1164,24 @@ run_action(struct view *view, struct server *server, struct action *action,
}
break;
case ACTION_TYPE_NEXT_WINDOW:
case ACTION_TYPE_PREVIOUS_WINDOW: {
enum lab_cycle_dir dir = (action->type == ACTION_TYPE_NEXT_WINDOW) ?
LAB_CYCLE_DIR_FORWARD : LAB_CYCLE_DIR_BACKWARD;
struct cycle_filter filter = {
.workspace = action_get_int(action, "workspace",
rc.window_switcher.workspace_filter),
.output = action_get_int(action, "output",
CYCLE_OUTPUT_ALL),
.app_id = action_get_int(action, "identifier",
CYCLE_APP_ID_ALL),
};
if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
cycle_step(server, LAB_CYCLE_DIR_FORWARD);
cycle_step(server, dir);
} else {
cycle_begin(server, LAB_CYCLE_DIR_FORWARD);
}
break;
case ACTION_TYPE_PREVIOUS_WINDOW:
if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
cycle_step(server, LAB_CYCLE_DIR_BACKWARD);
} else {
cycle_begin(server, LAB_CYCLE_DIR_BACKWARD);
cycle_begin(server, dir, filter);
}
break;
}
case ACTION_TYPE_RECONFIGURE:
kill(getpid(), SIGHUP);
break;
@ -1160,16 +1203,14 @@ run_action(struct view *view, struct server *server, struct action *action,
if (view) {
enum view_axis axis = action_get_int(action,
"direction", VIEW_AXIS_BOTH);
view_maximize(view, axis,
/*store_natural_geometry*/ true);
view_maximize(view, axis);
}
break;
case ACTION_TYPE_UNMAXIMIZE:
if (view) {
enum view_axis axis = action_get_int(action,
"direction", VIEW_AXIS_BOTH);
view_maximize(view, view->maximized & ~axis,
/*store_natural_geometry*/ true);
view_maximize(view, view->maximized & ~axis);
}
break;
case ACTION_TYPE_TOGGLE_FULLSCREEN:
@ -1368,7 +1409,7 @@ run_action(struct view *view, struct server *server, struct action *action,
break;
}
struct output *output = view->output;
if (!output) {
if (!output_is_usable(output)) {
break;
}
const char *region_name = action_get_str(action, "region", NULL);
@ -1383,8 +1424,7 @@ run_action(struct view *view, struct server *server, struct action *action,
view_apply_natural_geometry(view);
break;
}
view_snap_to_region(view, region,
/*store_natural_geometry*/ true);
view_snap_to_region(view, region);
} else {
wlr_log(WLR_ERROR, "Invalid SnapToRegion id: '%s'", region_name);
}
@ -1392,8 +1432,7 @@ run_action(struct view *view, struct server *server, struct action *action,
}
case ACTION_TYPE_UNSNAP:
if (view && !view->fullscreen && !view_is_floating(view)) {
view_maximize(view, VIEW_AXIS_NONE,
/* store_natural_geometry */ false);
view_maximize(view, VIEW_AXIS_NONE);
view_set_untiled(view);
view_apply_natural_geometry(view);
}

View file

@ -324,7 +324,7 @@ static void
clear_window_switcher_fields(void)
{
struct cycle_osd_field *field, *field_tmp;
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) {
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.osd.fields, link) {
wl_list_remove(&field->link);
cycle_osd_field_free(field);
}
@ -334,7 +334,7 @@ static void
fill_window_switcher_field(xmlNode *node)
{
struct cycle_osd_field *field = znew(*field);
wl_list_append(&rc.window_switcher.fields, &field->link);
wl_list_append(&rc.window_switcher.osd.fields, &field->link);
xmlNode *child;
char *key, *content;
@ -684,6 +684,10 @@ get_send_events_mode(const char *s)
goto err;
}
if (!strcasecmp(s, "disabledOnExternalMouse")) {
return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
}
int ret = parse_bool(s, -1);
if (ret >= 0) {
return ret
@ -691,10 +695,6 @@ get_send_events_mode(const char *s)
: LIBINPUT_CONFIG_SEND_EVENTS_DISABLED;
}
if (!strcasecmp(s, "disabledOnExternalMouse")) {
return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE;
}
err:
wlr_log(WLR_INFO, "Not a recognised send events mode");
return -1;
@ -1071,7 +1071,7 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(nodename, "prefix.desktops")) {
xstrdup_replace(rc.workspace_config.prefix, content);
} else if (!strcasecmp(nodename, "thumbnailLabelFormat.osd.windowSwitcher")) {
xstrdup_replace(rc.window_switcher.thumbnail_label_format, content);
xstrdup_replace(rc.window_switcher.osd.thumbnail_label_format, content);
} else if (!lab_xml_node_is_leaf(node)) {
/* parse children of nested nodes other than above */
@ -1219,23 +1219,23 @@ entry(xmlNode *node, char *nodename, char *content)
* thumnailLabelFormat is handled above to allow for an empty value
*/
} else if (!strcasecmp(nodename, "show.osd.windowSwitcher")) {
set_bool(content, &rc.window_switcher.show);
set_bool(content, &rc.window_switcher.osd.show);
} else if (!strcasecmp(nodename, "style.osd.windowSwitcher")) {
if (!strcasecmp(content, "classic")) {
rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC;
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC;
} else if (!strcasecmp(content, "thumbnail")) {
rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL;
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL;
} else {
wlr_log(WLR_ERROR, "Invalid windowSwitcher style '%s': "
"should be one of classic|thumbnail", content);
}
} else if (!strcasecmp(nodename, "output.osd.windowSwitcher")) {
if (!strcasecmp(content, "all")) {
rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL;
rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_ALL;
} else if (!strcasecmp(content, "cursor")) {
rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_CURSOR;
rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_CURSOR;
} else if (!strcasecmp(content, "focused")) {
rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_FOCUSED;
rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_FOCUSED;
} else {
wlr_log(WLR_ERROR, "Invalid windowSwitcher output '%s': "
"should be one of all|focused|cursor", content);
@ -1252,14 +1252,14 @@ entry(xmlNode *node, char *nodename, char *content)
/* The following two are for backward compatibility only. */
} else if (!strcasecmp(nodename, "show.windowSwitcher")) {
set_bool(content, &rc.window_switcher.show);
set_bool(content, &rc.window_switcher.osd.show);
wlr_log(WLR_ERROR, "<windowSwitcher show=\"\" /> is deprecated."
" Use <windowSwitcher><osd show=\"\" />");
} else if (!strcasecmp(nodename, "style.windowSwitcher")) {
if (!strcasecmp(content, "classic")) {
rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC;
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC;
} else if (!strcasecmp(content, "thumbnail")) {
rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL;
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL;
}
wlr_log(WLR_ERROR, "<windowSwitcher style=\"\" /> is deprecated."
" Use <windowSwitcher><osd style=\"\" />");
@ -1269,10 +1269,16 @@ entry(xmlNode *node, char *nodename, char *content)
} else if (!strcasecmp(nodename, "outlines.windowSwitcher")) {
set_bool(content, &rc.window_switcher.outlines);
} else if (!strcasecmp(nodename, "allWorkspaces.windowSwitcher")) {
if (parse_bool(content, -1) == true) {
rc.window_switcher.criteria &=
~LAB_VIEW_CRITERIA_CURRENT_WORKSPACE;
int ret = parse_bool(content, -1);
if (ret < 0) {
wlr_log(WLR_ERROR, "Invalid value for <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")) {
set_bool(content, &rc.window_switcher.unshade);
@ -1282,7 +1288,7 @@ entry(xmlNode *node, char *nodename, char *content)
/* The following three are for backward compatibility only */
} else if (!strcasecmp(nodename, "show.windowSwitcher.core")) {
set_bool(content, &rc.window_switcher.show);
set_bool(content, &rc.window_switcher.osd.show);
} else if (!strcasecmp(nodename, "preview.windowSwitcher.core")) {
set_bool(content, &rc.window_switcher.preview);
} else if (!strcasecmp(nodename, "outlines.windowSwitcher.core")) {
@ -1290,7 +1296,7 @@ entry(xmlNode *node, char *nodename, char *content)
/* The following three are for backward compatibility only */
} else if (!strcasecmp(nodename, "cycleViewOSD.core")) {
set_bool(content, &rc.window_switcher.show);
set_bool(content, &rc.window_switcher.osd.show);
wlr_log(WLR_ERROR, "<cycleViewOSD> is deprecated."
" Use <windowSwitcher show=\"\" />");
} else if (!strcasecmp(nodename, "cycleViewPreview.core")) {
@ -1303,11 +1309,13 @@ entry(xmlNode *node, char *nodename, char *content)
" Use <windowSwitcher outlines=\"\" />");
} else if (!strcasecmp(nodename, "name.names.desktops")) {
struct workspace *workspace = znew(*workspace);
workspace->name = xstrdup(content);
wl_list_append(&rc.workspace_config.workspaces, &workspace->link);
struct workspace_config *conf = znew(*conf);
conf->name = xstrdup(content);
wl_list_append(&rc.workspace_config.workspaces, &conf->link);
} else if (!strcasecmp(nodename, "popupTime.desktops")) {
rc.workspace_config.popuptime = atoi(content);
} else if (!strcasecmp(nodename, "initial.desktops")) {
xstrdup_replace(rc.workspace_config.initial_workspace_name, content);
} else if (!strcasecmp(nodename, "number.desktops")) {
rc.workspace_config.min_nr_workspaces = MAX(1, atoi(content));
} else if (!strcasecmp(nodename, "popupShow.resize")) {
@ -1419,7 +1427,7 @@ rcxml_init(void)
wl_list_init(&rc.libinput_categories);
wl_list_init(&rc.workspace_config.workspaces);
wl_list_init(&rc.regions);
wl_list_init(&rc.window_switcher.fields);
wl_list_init(&rc.window_switcher.osd.fields);
wl_list_init(&rc.window_rules);
wl_list_init(&rc.touch_configs);
}
@ -1484,16 +1492,14 @@ rcxml_init(void)
rc.snap_top_maximize = true;
rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS;
rc.window_switcher.show = true;
rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC;
rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL;
rc.window_switcher.thumbnail_label_format = xstrdup("%T");
rc.window_switcher.osd.show = true;
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC;
rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_ALL;
rc.window_switcher.osd.thumbnail_label_format = xstrdup("%T");
rc.window_switcher.preview = true;
rc.window_switcher.outlines = true;
rc.window_switcher.unshade = true;
rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE
| LAB_VIEW_CRITERIA_ROOT_TOPLEVEL
| LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER;
rc.window_switcher.workspace_filter = CYCLE_WORKSPACE_CURRENT;
rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS;
rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER;
@ -1673,7 +1679,7 @@ load_default_window_switcher_fields(void)
struct cycle_osd_field *field = znew(*field);
field->content = fields[i].content;
field->width = fields[i].width;
wl_list_append(&rc.window_switcher.fields, &field->link);
wl_list_append(&rc.window_switcher.osd.fields, &field->link);
}
}
@ -1778,15 +1784,14 @@ post_processing(void)
}
struct buf b = BUF_INIT;
struct workspace *workspace;
for (int i = nr_workspaces; i < rc.workspace_config.min_nr_workspaces; i++) {
workspace = znew(*workspace);
struct workspace_config *conf = znew(*conf);
if (!string_null_or_empty(rc.workspace_config.prefix)) {
buf_add_fmt(&b, "%s ", rc.workspace_config.prefix);
}
buf_add_fmt(&b, "%d", i + 1);
workspace->name = xstrdup(b.data);
wl_list_append(&rc.workspace_config.workspaces, &workspace->link);
conf->name = xstrdup(b.data);
wl_list_append(&rc.workspace_config.workspaces, &conf->link);
buf_clear(&b);
}
buf_reset(&b);
@ -1794,7 +1799,7 @@ post_processing(void)
if (rc.workspace_config.popuptime == INT_MIN) {
rc.workspace_config.popuptime = 1000;
}
if (!wl_list_length(&rc.window_switcher.fields)) {
if (!wl_list_length(&rc.window_switcher.osd.fields)) {
wlr_log(WLR_INFO, "load default window switcher fields");
load_default_window_switcher_fields();
}
@ -1888,7 +1893,7 @@ validate(void)
/* OSD fields */
int field_width_sum = 0;
struct cycle_osd_field *field, *field_tmp;
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.fields, link) {
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.osd.fields, link) {
field_width_sum += field->width;
if (!cycle_osd_field_is_valid(field) || field_width_sum > 100) {
wlr_log(WLR_ERROR, "Deleting invalid window switcher field %p", field);
@ -1963,8 +1968,9 @@ rcxml_finish(void)
zfree(rc.icon_theme_name);
zfree(rc.fallback_app_icon_name);
zfree(rc.workspace_config.prefix);
zfree(rc.workspace_config.initial_workspace_name);
zfree(rc.tablet.output_name);
zfree(rc.window_switcher.thumbnail_label_format);
zfree(rc.window_switcher.osd.thumbnail_label_format);
clear_title_layout();
@ -2004,7 +2010,7 @@ rcxml_finish(void)
zfree(l);
}
struct workspace *w, *w_tmp;
struct workspace_config *w, *w_tmp;
wl_list_for_each_safe(w, w_tmp, &rc.workspace_config.workspaces, link) {
wl_list_remove(&w->link);
zfree(w->name);

View file

@ -6,6 +6,7 @@
#include <wlr/util/log.h>
#include "common/lab-scene-rect.h"
#include "common/list.h"
#include "common/mem.h"
#include "common/scene-helpers.h"
#include "config/rcxml.h"
#include "labwc.h"
@ -17,7 +18,7 @@
#include "theme.h"
#include "view.h"
static bool init_cycle(struct server *server);
static bool init_cycle(struct server *server, struct cycle_filter filter);
static void update_cycle(struct server *server);
static void destroy_cycle(struct server *server);
@ -39,7 +40,8 @@ update_preview_outlines(struct view *view)
.border_width = theme->osd_window_switcher_preview_border_width,
};
rect = lab_scene_rect_create(&server->scene->tree, &opts);
wlr_scene_node_place_above(&rect->tree->node, &server->menu_tree->node);
wlr_scene_node_place_above(&rect->tree->node,
&server->cycle_preview_tree->node);
server->cycle.preview_outline = rect;
}
@ -93,9 +95,10 @@ cycle_reinitialize(struct server *server)
struct view *selected_view = cycle->selected_view;
struct view *selected_view_prev =
get_next_selected_view(server, LAB_CYCLE_DIR_BACKWARD);
struct cycle_filter filter = cycle->filter;
destroy_cycle(server);
if (init_cycle(server)) {
if (init_cycle(server, filter)) {
/*
* Preserve the selected view (or its previous view) if it's
* still in the cycle list
@ -152,13 +155,14 @@ restore_preview_node(struct server *server)
}
void
cycle_begin(struct server *server, enum lab_cycle_dir direction)
cycle_begin(struct server *server, enum lab_cycle_dir direction,
struct cycle_filter filter)
{
if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
return;
}
if (!init_cycle(server)) {
if (!init_cycle(server, filter)) {
return;
}
@ -200,8 +204,7 @@ cycle_finish(struct server *server, bool switch_focus)
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);
seat_focus_override_end(&server->seat, /*restore_focus*/ false);
/* Hiding OSD may need a cursor change */
cursor_update_focus(server);
@ -242,13 +245,8 @@ preview_selected_view(struct view *view)
cycle->preview_was_shaded = true;
}
/*
* FIXME: This abuses an implementation detail of the always-on-top tree.
* Create a permanent server->osd_preview_tree instead that can
* also be used as parent for the preview outlines.
*/
wlr_scene_node_reparent(cycle->preview_node,
view->server->view_tree_always_on_top);
view->server->cycle_preview_tree);
/* Finally raise selected node to the top */
wlr_scene_node_raise_to_top(cycle->preview_node);
@ -257,7 +255,7 @@ preview_selected_view(struct view *view)
static struct cycle_osd_impl *
get_osd_impl(void)
{
switch (rc.window_switcher.style) {
switch (rc.window_switcher.osd.style) {
case CYCLE_OSD_STYLE_CLASSIC:
return &cycle_osd_classic_impl;
case CYCLE_OSD_STYLE_THUMBNAIL:
@ -266,14 +264,36 @@ get_osd_impl(void)
return NULL;
}
static void
create_osd_on_output(struct output *output)
static uint64_t
get_outputs_by_filter(struct server *server,
enum cycle_output_filter output_filter)
{
if (!output_is_usable(output)) {
return;
struct output *output = NULL;
switch (output_filter) {
case CYCLE_OUTPUT_ALL:
break;
case CYCLE_OUTPUT_CURSOR:
output = output_nearest_to_cursor(server);
break;
case CYCLE_OUTPUT_FOCUSED: {
struct view *view = server->active_view;
if (view && output_is_usable(view->output)) {
output = view->output;
} else {
/* Fallback to pointer */
output = output_nearest_to_cursor(server);
}
break;
}
}
if (output) {
return output->id_bit;
} else {
/* bitmask for all outputs */
return UINT64_MAX;
}
get_osd_impl()->create(output);
assert(output->cycle_osd.tree);
}
static void
@ -290,12 +310,49 @@ insert_view_ordered_by_age(struct wl_list *views, struct view *new_view)
wl_list_insert(link, &new_view->cycle_link);
}
static void
handle_osd_tree_destroy(struct wl_listener *listener, void *data)
{
struct cycle_osd_output *osd_output =
wl_container_of(listener, osd_output, tree_destroy);
struct cycle_osd_item *item, *tmp;
wl_list_for_each_safe(item, tmp, &osd_output->items, link) {
wl_list_remove(&item->link);
free(item);
}
wl_list_remove(&osd_output->tree_destroy.link);
wl_list_remove(&osd_output->link);
free(osd_output);
}
/* Return false on failure */
static bool
init_cycle(struct server *server)
init_cycle(struct server *server, struct cycle_filter filter)
{
enum lab_view_criteria criteria =
LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER
| LAB_VIEW_CRITERIA_NO_DIALOG;
if (filter.workspace == CYCLE_WORKSPACE_CURRENT) {
criteria |= LAB_VIEW_CRITERIA_CURRENT_WORKSPACE;
}
uint64_t cycle_outputs =
get_outputs_by_filter(server, filter.output);
const char *cycle_app_id = NULL;
if (filter.app_id == CYCLE_APP_ID_CURRENT && server->active_view) {
cycle_app_id = server->active_view->app_id;
}
struct view *view;
for_each_view(view, &server->views, rc.window_switcher.criteria) {
for_each_view(view, &server->views, criteria) {
if (!(cycle_outputs & view->output->id_bit)) {
continue;
}
if (cycle_app_id && strcmp(view->app_id, cycle_app_id) != 0) {
continue;
}
if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) {
insert_view_ordered_by_age(&server->cycle.views, view);
} else {
@ -306,31 +363,31 @@ init_cycle(struct server *server)
wlr_log(WLR_DEBUG, "no views to switch between");
return false;
}
server->cycle.filter = filter;
if (rc.window_switcher.show) {
if (rc.window_switcher.osd.show) {
/* Create OSD */
switch (rc.window_switcher.output_criteria) {
case CYCLE_OSD_OUTPUT_ALL: {
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
create_osd_on_output(output);
uint64_t osd_outputs = get_outputs_by_filter(server,
rc.window_switcher.osd.output_filter);
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (!(osd_outputs & output->id_bit)) {
continue;
}
break;
}
case CYCLE_OSD_OUTPUT_CURSOR:
create_osd_on_output(output_nearest_to_cursor(server));
break;
case CYCLE_OSD_OUTPUT_FOCUSED: {
struct output *output;
if (server->active_view) {
output = server->active_view->output;
} else {
/* Fallback to pointer, if there is no active_view */
output = output_nearest_to_cursor(server);
if (!output_is_usable(output)) {
continue;
}
create_osd_on_output(output);
break;
}
struct cycle_osd_output *osd_output = znew(*osd_output);
wl_list_append(&server->cycle.osd_outputs, &osd_output->link);
osd_output->output = output;
wl_list_init(&osd_output->items);
get_osd_impl()->init(osd_output);
osd_output->tree_destroy.notify = handle_osd_tree_destroy;
wl_signal_add(&osd_output->tree->node.events.destroy,
&osd_output->tree_destroy);
}
}
@ -342,12 +399,10 @@ update_cycle(struct server *server)
{
struct cycle_state *cycle = &server->cycle;
if (rc.window_switcher.show) {
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (output->cycle_osd.tree) {
get_osd_impl()->update(output);
}
if (rc.window_switcher.osd.show) {
struct cycle_osd_output *osd_output;
wl_list_for_each(osd_output, &cycle->osd_outputs, link) {
get_osd_impl()->update(osd_output);
}
}
@ -357,8 +412,8 @@ update_cycle(struct server *server)
/* Outline current window */
if (rc.window_switcher.outlines) {
if (view_is_focusable(server->cycle.selected_view)) {
update_preview_outlines(server->cycle.selected_view);
if (view_is_focusable(cycle->selected_view)) {
update_preview_outlines(cycle->selected_view);
}
}
}
@ -367,31 +422,25 @@ update_cycle(struct server *server)
static void
destroy_cycle(struct server *server)
{
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
struct cycle_osd_item *item, *tmp;
wl_list_for_each_safe(item, tmp, &output->cycle_osd.items, link) {
wl_list_remove(&item->link);
free(item);
}
if (output->cycle_osd.tree) {
wlr_scene_node_destroy(&output->cycle_osd.tree->node);
output->cycle_osd.tree = NULL;
}
struct cycle_osd_output *osd_output, *tmp;
wl_list_for_each_safe(osd_output, tmp, &server->cycle.osd_outputs, link) {
/* calls handle_osd_tree_destroy() */
wlr_scene_node_destroy(&osd_output->tree->node);
}
restore_preview_node(server);
if (server->cycle.preview_outline) {
wlr_scene_node_destroy(&server->cycle.preview_outline->tree->node);
server->cycle.preview_outline = NULL;
}
struct view *view, *tmp;
wl_list_for_each_safe(view, tmp, &server->cycle.views, cycle_link) {
struct view *view, *tmp2;
wl_list_for_each_safe(view, tmp2, &server->cycle.views, cycle_link) {
wl_list_remove(&view->cycle_link);
view->cycle_link = (struct wl_list){0};
}
server->cycle.selected_view = NULL;
server->cycle = (struct cycle_state){0};
wl_list_init(&server->cycle.views);
wl_list_init(&server->cycle.osd_outputs);
}

View file

@ -2,5 +2,6 @@ labwc_sources += files(
'cycle.c',
'osd-classic.c',
'osd-field.c',
'osd-scroll.c',
'osd-thumbnail.c',
)

View file

@ -36,7 +36,7 @@ create_fields_scene(struct server *server, struct view *view,
&theme->osd_window_switcher_classic;
struct cycle_osd_field *field;
wl_list_for_each(field, &rc.window_switcher.fields, link) {
wl_list_for_each(field, &rc.window_switcher.osd.fields, link) {
int field_width = field_widths_sum * field->width / 100.0;
struct wlr_scene_node *node = NULL;
int height = -1;
@ -77,10 +77,9 @@ create_fields_scene(struct server *server, struct view *view,
}
static void
cycle_osd_classic_create(struct output *output)
cycle_osd_classic_init(struct cycle_osd_output *osd_output)
{
assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items));
struct output *output = osd_output->output;
struct server *server = output->server;
struct theme *theme = server->theme;
struct window_switcher_classic_theme *switcher_theme =
@ -98,13 +97,18 @@ cycle_osd_classic_create(struct output *output)
if (switcher_theme->width_is_percent) {
w = output_box.width * switcher_theme->width / 100;
}
int h = nr_views * switcher_theme->item_height + 2 * padding;
int workspace_name_h = 0;
if (show_workspace) {
/* workspace indicator */
h += switcher_theme->item_height;
workspace_name_h = switcher_theme->item_height;
}
int nr_visible_views = (output_box.height - workspace_name_h - 2 * padding)
/ switcher_theme->item_height;
nr_visible_views = MIN(nr_visible_views, nr_views);
int h = workspace_name_h + nr_visible_views * switcher_theme->item_height
+ 2 * padding;
output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree);
osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree);
float *text_color = theme->osd_label_text_color;
float *bg_color = theme->osd_bg_color;
@ -118,7 +122,7 @@ cycle_osd_classic_create(struct output *output)
.width = w,
.height = h,
};
lab_scene_rect_create(output->cycle_osd.tree, &bg_opts);
lab_scene_rect_create(osd_output->tree, &bg_opts);
int y = padding;
@ -136,7 +140,7 @@ cycle_osd_classic_create(struct output *output)
}
struct scaled_font_buffer *font_buffer =
scaled_font_buffer_create(output->cycle_osd.tree);
scaled_font_buffer_create(osd_output->tree);
wlr_scene_node_set_position(&font_buffer->scene_buffer->node,
x, y + (switcher_theme->item_height - font_height(&font)) / 2);
scaled_font_buffer_update(font_buffer, workspace_name, 0,
@ -144,7 +148,7 @@ cycle_osd_classic_create(struct output *output)
y += switcher_theme->item_height;
}
int nr_fields = wl_list_length(&rc.window_switcher.fields);
int nr_fields = wl_list_length(&rc.window_switcher.osd.fields);
/* This is the width of the area available for text fields */
int field_widths_sum = w - 2 * padding
@ -155,13 +159,17 @@ cycle_osd_classic_create(struct output *output)
goto error;
}
float *active_bg_color = switcher_theme->item_active_bg_color;
float *active_border_color = switcher_theme->item_active_border_color;
osd_output->items_tree = wlr_scene_tree_create(osd_output->tree);
/* Draw text for each node */
struct view *view;
wl_list_for_each(view, &server->cycle.views, cycle_link) {
struct cycle_osd_classic_item *item = znew(*item);
wl_list_append(&output->cycle_osd.items, &item->base.link);
wl_list_append(&osd_output->items, &item->base.link);
item->base.view = view;
item->base.tree = wlr_scene_tree_create(output->cycle_osd.tree);
item->base.tree = wlr_scene_tree_create(osd_output->items_tree);
node_descriptor_create(&item->base.tree->node,
LAB_NODE_CYCLE_OSD_ITEM, NULL, item);
/*
@ -187,9 +195,6 @@ cycle_osd_classic_create(struct output *output)
item->active_tree = wlr_scene_tree_create(item->base.tree);
wlr_scene_node_set_enabled(&item->active_tree->node, false);
float *active_bg_color = switcher_theme->item_active_bg_color;
float *active_border_color = switcher_theme->item_active_border_color;
/* Highlight around selected window's item */
struct lab_scene_rect_options highlight_opts = {
.border_colors = (float *[1]) {active_border_color},
@ -216,25 +221,39 @@ cycle_osd_classic_create(struct output *output)
y += switcher_theme->item_height;
}
struct wlr_box scrollbar_area = {
.x = w - padding - SCROLLBAR_W,
.y = padding,
.width = SCROLLBAR_W,
.height = h - 2 * padding,
};
cycle_osd_scroll_init(osd_output, scrollbar_area,
switcher_theme->item_height,
/*nr_cols*/ 1, /*nr_rows*/ nr_views, nr_visible_views,
active_border_color, active_bg_color);
error:;
/* Center OSD */
wlr_scene_node_set_position(&output->cycle_osd.tree->node,
wlr_scene_node_set_position(&osd_output->tree->node,
output_box.x + (output_box.width - w) / 2,
output_box.y + (output_box.height - h) / 2);
}
static void
cycle_osd_classic_update(struct output *output)
cycle_osd_classic_update(struct cycle_osd_output *osd_output)
{
struct server *server = osd_output->output->server;
cycle_osd_scroll_update(osd_output);
struct cycle_osd_classic_item *item;
wl_list_for_each(item, &output->cycle_osd.items, base.link) {
bool active = item->base.view == output->server->cycle.selected_view;
wl_list_for_each(item, &osd_output->items, base.link) {
bool active = item->base.view == server->cycle.selected_view;
wlr_scene_node_set_enabled(&item->normal_tree->node, !active);
wlr_scene_node_set_enabled(&item->active_tree->node, active);
}
}
struct cycle_osd_impl cycle_osd_classic_impl = {
.create = cycle_osd_classic_create,
.init = cycle_osd_classic_init,
.update = cycle_osd_classic_update,
};

95
src/cycle/osd-scroll.c Normal file
View file

@ -0,0 +1,95 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <assert.h>
#include <wlr/types/wlr_scene.h>
#include "common/lab-scene-rect.h"
#include "labwc.h"
#include "cycle.h"
#include "output.h"
void
cycle_osd_scroll_init(struct cycle_osd_output *osd_output, struct wlr_box bar_area,
int delta_y, int nr_cols, int nr_rows, int nr_visible_rows,
float *border_color, float *bg_color)
{
if (nr_visible_rows >= nr_rows) {
/* OSD doesn't have so many windows to scroll through */
return;
}
struct cycle_osd_scroll_context *scroll = &osd_output->scroll;
scroll->nr_cols = nr_cols;
scroll->nr_rows = nr_rows;
scroll->nr_visible_rows = nr_visible_rows;
scroll->top_row_idx = 0;
scroll->bar_area = bar_area;
scroll->delta_y = delta_y;
scroll->bar_tree = wlr_scene_tree_create(osd_output->tree);
wlr_scene_node_set_position(&scroll->bar_tree->node,
bar_area.x, bar_area.y);
struct lab_scene_rect_options scrollbar_opts = {
.border_colors = (float *[1]) { border_color },
.nr_borders = 1,
.border_width = 1,
.bg_color = bg_color,
.width = bar_area.width,
.height = bar_area.height * nr_visible_rows / nr_rows,
};
scroll->bar = lab_scene_rect_create(scroll->bar_tree, &scrollbar_opts);
}
static int
get_cycle_idx(struct cycle_osd_output *osd_output)
{
struct server *server = osd_output->output->server;
int idx = 0;
struct cycle_osd_item *item;
wl_list_for_each(item, &osd_output->items, link) {
if (item->view == server->cycle.selected_view) {
return idx;
}
idx++;
}
assert(false && "selected view not found in items");
return -1;
}
void
cycle_osd_scroll_update(struct cycle_osd_output *osd_output)
{
struct cycle_osd_scroll_context *scroll = &osd_output->scroll;
if (!scroll->bar) {
return;
}
int cycle_idx = get_cycle_idx(osd_output);
/* Update the range of visible rows */
int bottom_row_idx = scroll->top_row_idx + scroll->nr_visible_rows;
while (cycle_idx < scroll->top_row_idx * scroll->nr_cols) {
scroll->top_row_idx--;
bottom_row_idx--;
}
while (cycle_idx >= bottom_row_idx * scroll->nr_cols) {
scroll->top_row_idx++;
bottom_row_idx++;
}
/* Vertically move scrollbar by (bar height) / (# of total rows) */
wlr_scene_node_set_position(&scroll->bar->tree->node, 0,
scroll->bar_area.height * scroll->top_row_idx / scroll->nr_rows);
/* Vertically move items */
wlr_scene_node_set_position(&osd_output->items_tree->node, 0,
-scroll->delta_y * scroll->top_row_idx);
/* Hide items outside of visible area */
int idx = 0;
struct cycle_osd_item *item;
wl_list_for_each(item, &osd_output->items, link) {
bool visible = idx >= scroll->top_row_idx * scroll->nr_cols
&& idx < bottom_row_idx * scroll->nr_cols;
wlr_scene_node_set_enabled(&item->tree->node, visible);
idx++;
}
}

View file

@ -103,7 +103,7 @@ create_label(struct wlr_scene_tree *parent, struct view *view,
{
struct buf buf = BUF_INIT;
cycle_osd_field_set_custom(&buf, view,
rc.window_switcher.thumbnail_label_format);
rc.window_switcher.osd.thumbnail_label_format);
struct scaled_font_buffer *buffer =
scaled_font_buffer_create(parent);
scaled_font_buffer_update(buffer, buf.data,
@ -117,9 +117,9 @@ create_label(struct wlr_scene_tree *parent, struct view *view,
static struct cycle_osd_thumbnail_item *
create_item_scene(struct wlr_scene_tree *parent, struct view *view,
struct output *output)
struct cycle_osd_output *osd_output)
{
struct server *server = output->server;
struct server *server = osd_output->output->server;
struct theme *theme = server->theme;
struct window_switcher_thumbnail_theme *switcher_theme =
&theme->osd_window_switcher_thumbnail;
@ -137,7 +137,7 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view,
}
struct cycle_osd_thumbnail_item *item = znew(*item);
wl_list_append(&output->cycle_osd.items, &item->base.link);
wl_list_append(&osd_output->items, &item->base.link);
struct wlr_scene_tree *tree = wlr_scene_tree_create(parent);
node_descriptor_create(&tree->node, LAB_NODE_CYCLE_OSD_ITEM, NULL, item);
item->base.tree = tree;
@ -159,7 +159,7 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view,
switcher_theme->item_height, (float[4]) {0});
/* thumbnail */
struct wlr_buffer *thumb_buffer = render_thumb(output, view);
struct wlr_buffer *thumb_buffer = render_thumb(osd_output->output, view);
if (thumb_buffer) {
struct wlr_scene_buffer *thumb_scene_buffer =
wlr_scene_buffer_create(tree, thumb_buffer);
@ -194,9 +194,10 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view,
}
static void
get_items_geometry(struct output *output, struct theme *theme,
int nr_thumbs, int *nr_rows, int *nr_cols)
get_items_geometry(struct output *output, int nr_thumbs,
int *nr_cols, int *nr_rows, int *nr_visible_rows)
{
struct theme *theme = output->server->theme;
struct window_switcher_thumbnail_theme *switcher_theme =
&theme->osd_window_switcher_thumbnail;
int output_width, output_height;
@ -223,32 +224,35 @@ get_items_geometry(struct output *output, struct theme *theme,
(*nr_rows)++;
*nr_cols = ceilf((float)nr_thumbs / *nr_rows);
}
*nr_visible_rows = MIN(*nr_rows,
(output_height - 2 * padding) / switcher_theme->item_height);
}
static void
cycle_osd_thumbnail_create(struct output *output)
cycle_osd_thumbnail_init(struct cycle_osd_output *osd_output)
{
assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items));
struct output *output = osd_output->output;
struct server *server = output->server;
struct theme *theme = server->theme;
struct window_switcher_thumbnail_theme *switcher_theme =
&theme->osd_window_switcher_thumbnail;
int padding = theme->osd_border_width + switcher_theme->padding;
output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree);
osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree);
osd_output->items_tree = wlr_scene_tree_create(osd_output->tree);
int nr_views = wl_list_length(&server->cycle.views);
assert(nr_views > 0);
int nr_rows, nr_cols;
get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols);
int nr_cols, nr_rows, nr_visible_rows;
get_items_geometry(output, nr_views, &nr_cols, &nr_rows, &nr_visible_rows);
/* items */
struct view *view;
int index = 0;
wl_list_for_each(view, &server->cycle.views, cycle_link) {
struct cycle_osd_thumbnail_item *item = create_item_scene(
output->cycle_osd.tree, view, output);
osd_output->items_tree, view, osd_output);
if (!item) {
break;
}
@ -258,17 +262,31 @@ cycle_osd_thumbnail_create(struct output *output)
index++;
}
int items_width = switcher_theme->item_width * nr_cols;
int items_height = switcher_theme->item_height * nr_visible_rows;
struct wlr_box scrollbar_area = {
.x = padding + items_width - SCROLLBAR_W,
.y = padding,
.width = SCROLLBAR_W,
.height = items_height,
};
cycle_osd_scroll_init(osd_output, scrollbar_area,
switcher_theme->item_height, nr_cols, nr_rows, nr_visible_rows,
switcher_theme->item_active_border_color,
switcher_theme->item_active_bg_color);
/* background */
struct lab_scene_rect_options bg_opts = {
.border_colors = (float *[1]) { theme->osd_border_color },
.nr_borders = 1,
.border_width = theme->osd_border_width,
.bg_color = theme->osd_bg_color,
.width = nr_cols * switcher_theme->item_width + 2 * padding,
.height = nr_rows * switcher_theme->item_height + 2 * padding,
.width = items_width + 2 * padding,
.height = items_height + 2 * padding,
};
struct lab_scene_rect *bg =
lab_scene_rect_create(output->cycle_osd.tree, &bg_opts);
lab_scene_rect_create(osd_output->tree, &bg_opts);
wlr_scene_node_lower_to_bottom(&bg->tree->node);
/* center */
@ -277,15 +295,18 @@ cycle_osd_thumbnail_create(struct output *output)
&output_box);
int lx = output_box.x + (output_box.width - bg_opts.width) / 2;
int ly = output_box.y + (output_box.height - bg_opts.height) / 2;
wlr_scene_node_set_position(&output->cycle_osd.tree->node, lx, ly);
wlr_scene_node_set_position(&osd_output->tree->node, lx, ly);
}
static void
cycle_osd_thumbnail_update(struct output *output)
cycle_osd_thumbnail_update(struct cycle_osd_output *osd_output)
{
struct server *server = osd_output->output->server;
cycle_osd_scroll_update(osd_output);
struct cycle_osd_thumbnail_item *item;
wl_list_for_each(item, &output->cycle_osd.items, base.link) {
bool active = (item->base.view == output->server->cycle.selected_view);
wl_list_for_each(item, &osd_output->items, base.link) {
bool active = (item->base.view == server->cycle.selected_view);
wlr_scene_node_set_enabled(&item->active_bg->tree->node, active);
wlr_scene_node_set_enabled(
&item->active_label->scene_buffer->node, active);
@ -295,6 +316,6 @@ cycle_osd_thumbnail_update(struct output *output)
}
struct cycle_osd_impl cycle_osd_thumbnail_impl = {
.create = cycle_osd_thumbnail_create,
.init = cycle_osd_thumbnail_init,
.update = cycle_osd_thumbnail_update,
};

View file

@ -55,7 +55,9 @@ set_or_offer_focus(struct view *view)
break;
case VIEW_WANTS_FOCUS_LIKELY:
case VIEW_WANTS_FOCUS_UNLIKELY:
view_offer_focus(view);
if (view->surface != seat->seat->keyboard_state.focused_surface) {
view_offer_focus(view);
}
break;
case VIEW_WANTS_FOCUS_NEVER:
break;
@ -136,16 +138,9 @@ static struct view *
desktop_topmost_focusable_view(struct server *server)
{
struct view *view;
struct wl_list *node_list;
struct wlr_scene_node *node;
node_list = &server->workspaces.current->tree->children;
wl_list_for_each_reverse(node, node_list, link) {
if (!node->data) {
/* We found some non-view, most likely the region overlay */
continue;
}
view = node_view_from_node(node);
if (view_is_focusable(view) && !view->minimized) {
for_each_view(view, &server->views,
LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
if (!view->minimized) {
return view;
}
}
@ -170,41 +165,31 @@ desktop_focus_topmost_view(struct server *server)
void
desktop_focus_output(struct output *output)
{
if (!output_is_usable(output) || output->server->input_mode
struct server *server = output->server;
if (!output_is_usable(output) || server->input_mode
!= LAB_INPUT_STATE_PASSTHROUGH) {
return;
}
struct view *view;
struct wlr_scene_node *node;
struct wlr_output_layout *layout = output->server->output_layout;
struct wl_list *list_head =
&output->server->workspaces.current->tree->children;
wl_list_for_each_reverse(node, list_head, link) {
if (!node->data) {
continue;
}
view = node_view_from_node(node);
if (!view_is_focusable(view)) {
continue;
}
if (wlr_output_layout_intersects(layout,
output->wlr_output, &view->current)) {
for_each_view(view, &server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
if (view->outputs & output->id_bit) {
desktop_focus_view(view, /*raise*/ false);
wlr_cursor_warp(view->server->seat.cursor, NULL,
wlr_cursor_warp(server->seat.cursor, NULL,
view->current.x + view->current.width / 2,
view->current.y + view->current.height / 2);
cursor_update_focus(view->server);
cursor_update_focus(server);
return;
}
}
/* No view found on desired output */
struct wlr_box layout_box;
wlr_output_layout_get_box(output->server->output_layout,
wlr_output_layout_get_box(server->output_layout,
output->wlr_output, &layout_box);
wlr_cursor_warp(output->server->seat.cursor, NULL,
wlr_cursor_warp(server->seat.cursor, NULL,
layout_box.x + output->usable_area.x + output->usable_area.width / 2,
layout_box.y + output->usable_area.y + output->usable_area.height / 2);
cursor_update_focus(output->server);
cursor_update_focus(server);
}
void

View file

@ -26,8 +26,7 @@ handle_request_maximize(struct wl_listener *listener, void *data)
struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data;
view_maximize(wlr_toplevel->view,
event->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
/*store_natural_geometry*/ true);
event->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE);
}
static void

View file

@ -92,9 +92,6 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges)
/* Store natural geometry at start of move */
view_store_natural_geometry(view);
if (view_is_floating(view)) {
view_invalidate_last_layout_geometry(view);
}
/* Prevent region snapping when just moving via A-Left mousebind */
seat->region_prevent_snap = keyboard_get_all_modifiers(seat);
@ -111,12 +108,6 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges)
return;
}
/*
* Resizing overrides any attempt to restore window
* geometries altered by layout changes.
*/
view_invalidate_last_layout_geometry(view);
/*
* If tiled or maximized in only one direction, reset
* tiled state and un-maximize the relevant axes, but
@ -273,17 +264,12 @@ snap_to_edge(struct view *view)
enum lab_edge edge = edge1 | edge2;
view_set_output(view, output);
/*
* Don't store natural geometry here (it was
* stored already in interactive_begin())
*/
if (edge == LAB_EDGE_TOP && rc.snap_top_maximize) {
/* <topMaximize> */
view_maximize(view, VIEW_AXIS_BOTH,
/*store_natural_geometry*/ false);
view_maximize(view, VIEW_AXIS_BOTH);
} else {
view_snap_to_edge(view, edge, /*across_outputs*/ false,
/*combine*/ false, /*store_natural_geometry*/ false);
/*combine*/ false);
}
return true;
@ -298,8 +284,7 @@ snap_to_region(struct view *view)
struct region *region = regions_from_cursor(view->server);
if (region) {
view_snap_to_region(view, region,
/*store_natural_geometry*/ false);
view_snap_to_region(view, region);
return true;
}
return false;
@ -340,5 +325,5 @@ interactive_cancel(struct view *view)
view->server->grabbed_view = NULL;
/* Restore keyboard/pointer focus */
seat_focus_override_end(&view->server->seat);
seat_focus_override_end(&view->server->seat, /*restore_focus*/ true);
}

View file

@ -145,7 +145,7 @@ try_to_focus_next_layer_or_toplevel(struct server *server)
{
struct seat *seat = &server->seat;
struct output *output = output_nearest_to_cursor(server);
if (!output) {
if (!output_is_usable(output)) {
goto no_output;
}
@ -589,16 +589,14 @@ handle_new_layer_surface(struct wl_listener *listener, void *data)
struct wlr_layer_surface_v1 *layer_surface = data;
if (!layer_surface->output) {
struct wlr_output *output = wlr_output_layout_output_at(
server->output_layout, server->seat.cursor->x,
server->seat.cursor->y);
if (!output) {
struct output *output = output_nearest_to_cursor(server);
if (!output_is_usable(output)) {
wlr_log(WLR_INFO,
"No output available to assign layer surface");
wlr_layer_surface_v1_destroy(layer_surface);
return;
}
layer_surface->output = output;
layer_surface->output = output->wlr_output;
}
struct lab_layer_surface *surface = znew(*surface);

View file

@ -731,7 +731,7 @@ menu_reposition(struct menu *menu, struct wlr_box anchor_rect)
/* Get output usable area to place the menu within */
struct output *output = output_nearest_to(menu->server,
anchor_rect.x, anchor_rect.y);
if (!output) {
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "no output found around (%d,%d)",
anchor_rect.x, anchor_rect.y);
return;
@ -1430,7 +1430,7 @@ menu_execute_item(struct menuitem *item)
struct server *server = item->parent->server;
menu_close(server->menu_current);
server->menu_current = NULL;
seat_focus_override_end(&server->seat);
seat_focus_override_end(&server->seat, /*restore_focus*/ true);
/*
* We call the actions after closing the menu so that virtual keyboard
@ -1443,6 +1443,9 @@ menu_execute_item(struct menuitem *item)
*/
if (!strcmp(item->parent->id, "client-list-combined-menu")
&& item->client_list_view) {
if (item->client_list_view->shaded) {
view_set_shade(item->client_list_view, false);
}
actions_run(item->client_list_view, server, &item->actions, NULL);
} else {
actions_run(item->parent->triggered_by_view, server,
@ -1530,7 +1533,7 @@ menu_close_root(struct server *server)
menu_close(server->menu_current);
server->menu_current = NULL;
reset_pipemenus(server);
seat_focus_override_end(&server->seat);
seat_focus_override_end(&server->seat, /*restore_focus*/ true);
}
void

View file

@ -128,17 +128,6 @@ handle_output_frame(struct wl_listener *listener, void *data)
return;
}
if (!output->scene_output) {
/*
* TODO: This is a short term fix for issue #1667,
* a proper fix would require restructuring
* the life cycle of scene outputs, e.g.
* creating them on handle_new_output() only.
*/
wlr_log(WLR_INFO, "Failed to render new frame: no scene-output");
return;
}
if (output->gamma_lut_changed) {
/*
* We are not mixing the gamma state with
@ -166,7 +155,8 @@ static void
handle_output_destroy(struct wl_listener *listener, void *data)
{
struct output *output = wl_container_of(listener, output, destroy);
struct seat *seat = &output->server->seat;
struct server *server = output->server;
struct seat *seat = &server->seat;
regions_evacuate_output(output);
regions_destroy(seat, &output->regions);
if (seat->overlay.active.output == output) {
@ -190,7 +180,6 @@ handle_output_destroy(struct wl_listener *listener, void *data)
}
struct view *view;
struct server *server = output->server;
wl_list_for_each(view, &server->views, link) {
if (view->output == output) {
view_on_output_destroy(view);
@ -542,7 +531,6 @@ handle_new_output(struct wl_listener *listener, void *data)
wl_signal_add(&wlr_output->events.request_state, &output->request_state);
wl_list_init(&output->regions);
wl_list_init(&output->cycle_osd.items);
/*
* Create layer-trees (background, bottom, top and overlay) and
@ -877,14 +865,9 @@ wlr_output_configuration_v1 *create_output_config(struct server *server)
wlr_output_configuration_v1_destroy(config);
return NULL;
}
struct wlr_box box;
wlr_output_layout_get_box(server->output_layout,
output->wlr_output, &box);
if (!wlr_box_empty(&box)) {
head->state.x = box.x;
head->state.y = box.y;
} else {
wlr_log(WLR_ERROR, "failed to get output layout box");
if (output_is_usable(output)) {
head->state.x = output->scene_output->x;
head->state.y = output->scene_output->y;
}
}
return config;
@ -1068,8 +1051,14 @@ output_get_adjacent(struct output *output, enum lab_edge edge, bool wrap)
bool
output_is_usable(struct output *output)
{
/* output_is_usable(NULL) is safe and returns false */
return output && output->wlr_output->enabled;
/*
* output_is_usable(NULL) is safe and returns false.
*
* Checking output->scene_output != NULL is necessary in case the
* wlr_output was initially enabled but hasn't been configured yet
* (occurs with autoEnableOutputs=no).
*/
return output && output->wlr_output->enabled && output->scene_output;
}
/* returns true if usable area changed */
@ -1129,7 +1118,7 @@ output_update_all_usable_areas(struct server *server, bool layout_changed)
struct wlr_box
output_usable_area_in_layout_coords(struct output *output)
{
if (!output) {
if (!output_is_usable(output)) {
return (struct wlr_box){0};
}
struct wlr_box box = output->usable_area;

View file

@ -53,7 +53,7 @@ regions_from_cursor(struct server *server)
struct wlr_output *wlr_output = wlr_output_layout_output_at(
server->output_layout, lx, ly);
struct output *output = output_from_wlr_output(server, wlr_output);
if (!output) {
if (!output_is_usable(output)) {
return NULL;
}

View file

@ -902,12 +902,12 @@ seat_focus_override_begin(struct seat *seat, enum input_mode input_mode,
}
void
seat_focus_override_end(struct seat *seat)
seat_focus_override_end(struct seat *seat, bool restore_focus)
{
seat->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
if (seat->focus_override.surface) {
if (!seat->seat->keyboard_state.focused_surface) {
if (restore_focus) {
seat_focus(seat, seat->focus_override.surface,
/*replace_exclusive_layer*/ false,
/*is_lock_surface*/ false);
@ -916,5 +916,7 @@ seat_focus_override_end(struct seat *seat)
seat->focus_override.surface = NULL;
}
cursor_update_focus(seat->server);
if (restore_focus) {
cursor_update_focus(seat->server);
}
}

View file

@ -97,6 +97,7 @@ reload_config_and_theme(struct server *server)
view_reload_ssd(view);
}
cycle_finish(server, /*switch_focus*/ false);
menu_reconfigure(server);
seat_reconfigure(server);
regions_reconfigure(server);
@ -550,6 +551,7 @@ server_init(struct server *server)
wl_list_init(&server->views);
wl_list_init(&server->unmanaged_surfaces);
wl_list_init(&server->cycle.views);
wl_list_init(&server->cycle.osd_outputs);
server->scene = wlr_scene_create();
if (!server->scene) {
@ -563,21 +565,22 @@ server_init(struct server *server)
* z-order for nodes which cover the whole work-area. For per-output
* scene-trees, see handle_new_output() in src/output.c
*
* | Type | Scene Tree | Per Output | Example
* | ------------------- | ---------------- | ---------- | -------
* | ext-session | lock-screen | Yes | swaylock
* | window switcher OSD | cycle_osd_tree | Yes |
* | compositor-menu | menu_tree | No | root-menu
* | layer-shell | layer-popups | Yes |
* | layer-shell | overlay-layer | Yes |
* | layer-shell | top-layer | Yes | waybar
* | xwayland-OR | unmanaged | No | dmenu
* | xdg-popups | xdg-popups | No |
* | toplevels windows | always-on-top | No |
* | toplevels windows | normal | No | firefox
* | toplevels windows | always-on-bottom | No | pcmanfm-qt --desktop
* | layer-shell | bottom-layer | Yes | waybar
* | layer-shell | background-layer | Yes | swaybg
* | Scene Tree | Description
* | ---------------------------------- | -------------------------------------
* | output->session_lock_tree | session lock surfaces (e.g. swaylock)
* | output->cycle_osd_tree | window switcher's on-screen display
* | server->cycle_preview_tree | window switcher's previewed window
* | server->menu_tree | labwc's server-side menus
* | output->layer_popup_tree | xdg popups on layer surfaces
* | output->layer_tree[3] | overlay layer surfaces (e.g. rofi)
* | output->layer_tree[2] | top layer surfaces (e.g. waybar)
* | server->unmanaged_tree | unmanaged X11 surfaces (e.g. dmenu)
* | server->xdg_popup_tree | xdg popups on xdg windows
* | server->view_tree_always_on_top | always-on-top xdg/X11 windows
* | server->view_tree | normal xdg/X11 windows (e.g. firefox)
* | server->view_tree_always_on_bottom | always-on-bottom xdg/X11 windows
* | output->layer_tree[1] | bottom layer surfaces
* | output->layer_tree[0] | background layer surfaces (e.g. swaybg)
*/
server->view_tree_always_on_bottom = wlr_scene_tree_create(&server->scene->tree);
@ -588,6 +591,7 @@ server_init(struct server *server)
server->unmanaged_tree = wlr_scene_tree_create(&server->scene->tree);
#endif
server->menu_tree = wlr_scene_tree_create(&server->scene->tree);
server->cycle_preview_tree = wlr_scene_tree_create(&server->scene->tree);
workspaces_init(server);

View file

@ -95,7 +95,7 @@ ssd_extents_update(struct ssd *ssd)
wlr_scene_node_set_enabled(&ssd->extents.tree->node, true);
}
if (!view->output) {
if (!output_is_usable(view->output)) {
return;
}

View file

@ -11,6 +11,11 @@ view_impl_map(struct view *view)
{
view_update_visibility(view);
/* Leave minimized, if minimized before map */
if (!view->minimized) {
desktop_focus_view(view, /* raise */ true);
}
if (!view->been_mapped) {
window_rules_apply(view, LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP);
}
@ -41,6 +46,19 @@ view_impl_unmap(struct view *view)
{
view_update_visibility(view);
/*
* When exiting an xwayland application with multiple views
* mapped, a race condition can occur: after the topmost view
* is unmapped, the next view under it is offered focus, but is
* also unmapped before accepting focus (so server->active_view
* remains NULL). To avoid being left with no active view at
* all, check for that case also.
*/
struct server *server = view->server;
if (view == server->active_view || !server->active_view) {
desktop_focus_topmost_view(server);
}
/*
* Destroy the foreign toplevel handle so the unmapped view
* doesn't show up in panels and the like.

View file

@ -14,6 +14,7 @@
#include "common/list.h"
#include "common/match.h"
#include "common/mem.h"
#include "common/string-helpers.h"
#include "config/rcxml.h"
#include "cycle.h"
#include "foreign-toplevel/foreign.h"
@ -287,8 +288,8 @@ matches_criteria(struct view *view, enum lab_view_criteria criteria)
return false;
}
}
if (criteria & LAB_VIEW_CRITERIA_ROOT_TOPLEVEL) {
if (view != view_get_root(view)) {
if (criteria & LAB_VIEW_CRITERIA_NO_DIALOG) {
if (view_is_modal_dialog(view)) {
return false;
}
}
@ -461,10 +462,6 @@ view_discover_output(struct view *view, struct wlr_box *geometry)
if (output && output != view->output) {
view->output = output;
/* Show fullscreen views above top-layer */
if (view->fullscreen) {
desktop_update_top_layer_visibility(view->server);
}
return true;
}
@ -581,6 +578,8 @@ view_moved(struct view *view)
}
}
static void save_last_placement(struct view *view);
void
view_move_resize(struct view *view, struct wlr_box geo)
{
@ -588,6 +587,20 @@ view_move_resize(struct view *view, struct wlr_box geo)
if (view->impl->configure) {
view->impl->configure(view, geo);
}
/*
* If the move/resize was user-initiated (rather than due to
* output layout change), then update the last placement info.
*
* TODO: consider also updating view->output here for floating
* views (based on view->pending) rather than waiting until
* view_moved(). This might eliminate some race conditions with
* view_adjust_for_layout_change(), which uses view->pending.
* Not sure if it might have other side-effects though.
*/
if (!view->adjusting_for_layout_change) {
save_last_placement(view);
}
}
void
@ -614,7 +627,7 @@ view_move_relative(struct view *view, int x, int y)
if (view->fullscreen) {
return;
}
view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false);
view_maximize(view, VIEW_AXIS_NONE);
if (view_is_tiled(view)) {
view_set_untiled(view);
view_move_resize(view, view->natural_geometry);
@ -632,7 +645,7 @@ view_move_to_cursor(struct view *view)
return;
}
view_set_fullscreen(view, false);
view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false);
view_maximize(view, VIEW_AXIS_NONE);
if (view_is_tiled(view)) {
view_set_untiled(view);
view_move_resize(view, view->natural_geometry);
@ -730,7 +743,7 @@ view_adjust_size(struct view *view, int *w, int *h)
}
static void
_minimize(struct view *view, bool minimized)
_minimize(struct view *view, bool minimized, bool *need_refocus)
{
assert(view);
if (view->minimized == minimized) {
@ -743,8 +756,15 @@ _minimize(struct view *view, bool minimized)
view->minimized = minimized;
wl_signal_emit_mutable(&view->events.minimized, NULL);
view_update_visibility(view);
/*
* Need to focus a different view when:
* - minimizing the active view
* - unminimizing any mapped view
*/
*need_refocus |= (minimized ?
(view == view->server->active_view) : view->mapped);
}
static void
@ -757,7 +777,7 @@ view_append_children(struct view *view, struct wl_array *children)
}
static void
minimize_sub_views(struct view *view, bool minimized)
minimize_sub_views(struct view *view, bool minimized, bool *need_refocus)
{
struct view **child;
struct wl_array children;
@ -765,8 +785,8 @@ minimize_sub_views(struct view *view, bool minimized)
wl_array_init(&children);
view_append_children(view, &children);
wl_array_for_each(child, &children) {
_minimize(*child, minimized);
minimize_sub_views(*child, minimized);
_minimize(*child, minimized, need_refocus);
minimize_sub_views(*child, minimized, need_refocus);
}
wl_array_release(&children);
}
@ -781,8 +801,10 @@ void
view_minimize(struct view *view, bool minimized)
{
assert(view);
struct server *server = view->server;
bool need_refocus = false;
if (view->server->input_mode == LAB_INPUT_STATE_CYCLE) {
if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
wlr_log(WLR_ERROR, "not minimizing window while window switching");
return;
}
@ -793,8 +815,20 @@ view_minimize(struct view *view, bool minimized)
* 'open file' dialog), so it saves trying to unmap them twice
*/
struct view *root = view_get_root(view);
_minimize(root, minimized);
minimize_sub_views(root, minimized);
_minimize(root, minimized, &need_refocus);
minimize_sub_views(root, minimized, &need_refocus);
/*
* Update focus only at the end to avoid repeated focus changes.
* desktop_focus_view() will raise all sibling views together.
*/
if (need_refocus) {
if (minimized) {
desktop_focus_topmost_view(server);
} else {
desktop_focus_view(view, /* raise */ true);
}
}
}
bool
@ -825,6 +859,7 @@ view_compute_centered_position(struct view *view, const struct wlr_box *ref,
return true;
}
/* Make sure the passed-in view geometry is visible in view->output */
static bool
adjust_floating_geometry(struct view *view, struct wlr_box *geometry,
bool midpoint_visibility)
@ -1070,7 +1105,7 @@ view_place_by_policy(struct view *view, bool allow_cursor,
void
view_constrain_size_to_that_of_usable_area(struct view *view)
{
if (!view || !view->output || view->fullscreen) {
if (!view || !output_is_usable(view->output) || view->fullscreen) {
return;
}
@ -1375,9 +1410,15 @@ view_set_untiled(struct view *view)
view_notify_tiled(view);
}
static bool
in_interactive_move(struct view *view)
{
return (view->server->input_mode == LAB_INPUT_STATE_MOVE
&& view->server->grabbed_view == view);
}
void
view_maximize(struct view *view, enum view_axis axis,
bool store_natural_geometry)
view_maximize(struct view *view, enum view_axis axis)
{
assert(view);
@ -1389,6 +1430,7 @@ view_maximize(struct view *view, enum view_axis axis,
return;
}
bool store_natural_geometry = !in_interactive_move(view);
view_set_shade(view, false);
if (axis != VIEW_AXIS_NONE) {
@ -1398,9 +1440,6 @@ view_maximize(struct view *view, enum view_axis axis,
* a maximized view.
*/
interactive_cancel(view);
if (store_natural_geometry && view_is_floating(view)) {
view_invalidate_last_layout_geometry(view);
}
}
/*
@ -1440,8 +1479,7 @@ view_toggle_maximize(struct view *view, enum view_axis axis)
case VIEW_AXIS_HORIZONTAL:
case VIEW_AXIS_VERTICAL:
/* Toggle one axis (XOR) */
view_maximize(view, view->maximized ^ axis,
/*store_natural_geometry*/ true);
view_maximize(view, view->maximized ^ axis);
break;
case VIEW_AXIS_BOTH:
/*
@ -1449,8 +1487,7 @@ view_toggle_maximize(struct view *view, enum view_axis axis)
* maximized, otherwise unmaximize.
*/
view_maximize(view, (view->maximized == VIEW_AXIS_BOTH) ?
VIEW_AXIS_NONE : VIEW_AXIS_BOTH,
/*store_natural_geometry*/ true);
VIEW_AXIS_NONE : VIEW_AXIS_BOTH);
break;
default:
break;
@ -1690,7 +1727,6 @@ view_set_fullscreen(struct view *view, bool fullscreen)
*/
interactive_cancel(view);
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
}
set_fullscreen(view, fullscreen);
@ -1708,139 +1744,80 @@ view_set_fullscreen(struct view *view, bool fullscreen)
cursor_update_focus(view->server);
}
static bool
last_layout_geometry_is_valid(struct view *view)
static void
save_last_placement(struct view *view)
{
return view->last_layout_geometry.width > 0
&& view->last_layout_geometry.height > 0;
assert(view);
struct output *output = view->output;
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "cannot save last placement in unusable output");
return;
}
if (!str_equal(view->last_placement.output_name, output->wlr_output->name)) {
xstrdup_replace(view->last_placement.output_name,
output->wlr_output->name);
}
view->last_placement.layout_geo = view->pending;
view->last_placement.relative_geo = view->pending;
view->last_placement.relative_geo.x -= output->scene_output->x;
view->last_placement.relative_geo.y -= output->scene_output->y;
}
static void
update_last_layout_geometry(struct view *view)
{
/*
* Only update an invalid last-layout geometry to prevent a series of
* successive layout changes from continually replacing the "preferred"
* location with whatever location the view currently holds. The
* "preferred" location should be whatever state was set by user
* interaction, not automatic responses to layout changes.
*/
if (last_layout_geometry_is_valid(view)) {
return;
}
if (view_is_floating(view)) {
view->last_layout_geometry = view->pending;
} else if (!wlr_box_empty(&view->natural_geometry)) {
view->last_layout_geometry = view->natural_geometry;
} else {
/* e.g. initially-maximized window */
view->last_layout_geometry =
view_get_fallback_natural_geometry(view);
}
}
static bool
apply_last_layout_geometry(struct view *view, bool force_update)
{
/* Only apply a valid last-layout geometry */
if (!last_layout_geometry_is_valid(view)) {
return false;
}
/*
* Unless forced, the last-layout geometry is only applied
* when the relevant view geometry is distinct.
*/
if (!force_update) {
struct wlr_box *relevant = view_is_floating(view) ?
&view->pending : &view->natural_geometry;
if (wlr_box_equal(relevant, &view->last_layout_geometry)) {
return false;
}
}
view->natural_geometry = view->last_layout_geometry;
adjust_floating_geometry(view, &view->natural_geometry,
/* midpoint_visibility */ true);
return true;
}
void
view_invalidate_last_layout_geometry(struct view *view)
clear_last_placement(struct view *view)
{
assert(view);
view->last_layout_geometry.width = 0;
view->last_layout_geometry.height = 0;
zfree(view->last_placement.output_name);
view->last_placement.relative_geo = (struct wlr_box){0};
view->last_placement.layout_geo = (struct wlr_box){0};
}
void
view_adjust_for_layout_change(struct view *view)
{
assert(view);
bool is_floating = view_is_floating(view);
bool use_natural = false;
if (!output_is_usable(view->output)) {
/* A view losing an output should have a last-layout geometry */
update_last_layout_geometry(view);
if (wlr_box_empty(&view->last_placement.layout_geo)) {
/* Not using assert() just in case */
wlr_log(WLR_ERROR, "view has no last placement info");
return;
}
/* Capture a pointer to the last-layout geometry (only if valid) */
struct wlr_box *last_geometry = NULL;
if (last_layout_geometry_is_valid(view)) {
last_geometry = &view->last_layout_geometry;
}
/*
* Check if an output change is required:
* - Floating views are always mapped to the nearest output
* - Any view without a usable output needs to be repositioned
* - Any view with a valid last-layout geometry might be better
* positioned on another output
*/
if (is_floating || last_geometry || !output_is_usable(view->output)) {
/* Move the view to an appropriate output, if needed */
bool output_changed = view_discover_output(view, last_geometry);
view->adjusting_for_layout_change = true;
struct wlr_box new_geo;
struct output *output = output_from_name(view->server,
view->last_placement.output_name);
if (output_is_usable(output)) {
/*
* Try to apply the last-layout to the natural geometry
* (adjusting to ensure that it fits on the screen). This is
* forced if the output has changed, but will be done
* opportunistically even on the same output if the last-layout
* geometry is different from the view's governing geometry.
* When the previous output (which might have been reconnected
* or relocated) is available, keep the relative position on it.
*/
if (apply_last_layout_geometry(view, output_changed)) {
use_natural = true;
}
/*
* Whether or not the view has moved, the layout has changed.
* Ensure that the view now has a valid last-layout geometry.
*/
update_last_layout_geometry(view);
}
if (!is_floating) {
view_apply_special_geometry(view);
} else if (use_natural) {
/*
* Move the window to its natural location, because
* we are trying to restore a prior layout.
*/
view_apply_natural_geometry(view);
new_geo = view->last_placement.relative_geo;
new_geo.x += output->scene_output->x;
new_geo.y += output->scene_output->y;
view->output = output;
} else {
/* Otherwise, just ensure the view is on screen. */
struct wlr_box geometry = view->pending;
if (adjust_floating_geometry(view, &geometry,
/* midpoint_visibility */ true)) {
view_move_resize(view, geometry);
}
/*
* Otherwise, evacuate the view to another output. Use the last
* layout geometry so that the view position is kept when the
* user reconnects the previous output in a different connector
* or the reconnected output somehow gets a different name.
*/
view_discover_output(view, &view->last_placement.layout_geo);
new_geo = view->last_placement.layout_geo;
}
if (!view_is_floating(view)) {
view_apply_special_geometry(view);
} else {
/* Ensure view is on-screen */
adjust_floating_geometry(view, &new_geo,
/* midpoint_visibility */ true);
view_move_resize(view, new_geo);
}
view_update_outputs(view);
view->adjusting_for_layout_change = false;
}
void
@ -1923,7 +1900,7 @@ view_move_to_edge(struct view *view, enum lab_edge direction, bool snap_to_windo
/* Otherwise, move to edge of next adjacent display, if possible */
struct output *output =
output_get_adjacent(view->output, direction, /* wrap */ false);
if (!output) {
if (!output_is_usable(output)) {
return;
}
@ -2064,7 +2041,7 @@ view_placement_parse(const char *policy)
void
view_snap_to_edge(struct view *view, enum lab_edge edge,
bool across_outputs, bool combine, bool store_natural_geometry)
bool across_outputs, bool combine)
{
assert(view);
@ -2078,6 +2055,7 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
return;
}
bool store_natural_geometry = !in_interactive_move(view);
view_set_shade(view, false);
if (lab_edge_is_cardinal(edge) && view->maximized == VIEW_AXIS_NONE
@ -2102,7 +2080,7 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
*/
output = output_get_adjacent(view->output, edge,
/* wrap */ false);
if (!output) {
if (!output_is_usable(output)) {
return;
}
edge = invert_edge;
@ -2124,12 +2102,10 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
if (view->maximized != VIEW_AXIS_NONE) {
/* Unmaximize + keep using existing natural_geometry */
view_maximize(view, VIEW_AXIS_NONE,
/*store_natural_geometry*/ false);
view_maximize(view, VIEW_AXIS_NONE);
} else if (store_natural_geometry) {
/* store current geometry as new natural_geometry */
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
}
view_set_untiled(view);
view_set_output(view, output);
@ -2139,8 +2115,7 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
}
void
view_snap_to_region(struct view *view, struct region *region,
bool store_natural_geometry)
view_snap_to_region(struct view *view, struct region *region)
{
assert(view);
assert(region);
@ -2155,16 +2130,15 @@ view_snap_to_region(struct view *view, struct region *region,
return;
}
bool store_natural_geometry = !in_interactive_move(view);
view_set_shade(view, false);
if (view->maximized != VIEW_AXIS_NONE) {
/* Unmaximize + keep using existing natural_geometry */
view_maximize(view, VIEW_AXIS_NONE,
/*store_natural_geometry*/ false);
view_maximize(view, VIEW_AXIS_NONE);
} else if (store_natural_geometry) {
/* store current geometry as new natural_geometry */
view_store_natural_geometry(view);
view_invalidate_last_layout_geometry(view);
}
view_set_untiled(view);
view->tiled_region = region;
@ -2177,7 +2151,6 @@ view_move_to_output(struct view *view, struct output *output)
{
assert(view);
view_invalidate_last_layout_geometry(view);
view_set_output(view, output);
if (view_is_floating(view)) {
struct wlr_box output_area = output_usable_area_in_layout_coords(output);
@ -2193,7 +2166,7 @@ view_move_to_output(struct view *view, struct output *output)
view_apply_tiled_geometry(view);
} else if (view->tiled_region) {
struct region *region = regions_from_name(view->tiled_region->name, output);
view_snap_to_region(view, region, /*store_natural_geometry*/ false);
view_snap_to_region(view, region);
}
}
@ -2237,6 +2210,18 @@ void
view_move_to_front(struct view *view)
{
assert(view);
struct server *server = view->server;
assert(!wl_list_empty(&server->views));
/*
* Check whether the view is already in front, or is the root
* parent of the view in front (in which case we don't want to
* raise it in front of its sub-view).
*/
struct view *front = wl_container_of(server->views.next, front, link);
if (view == front || view == view_get_root(front)) {
return;
}
struct view *root = view_get_root(view);
assert(root);
@ -2257,7 +2242,9 @@ view_move_to_front(struct view *view)
* to an incorrect X window depending on timing. To mitigate the
* race, perform an explicit flush after restacking.
*/
xwayland_flush(view->server);
if (view->type == LAB_XWAYLAND_VIEW) {
xwayland_flush(view->server);
}
#endif
cursor_update_focus(view->server);
desktop_update_top_layer_visibility(view->server);
@ -2277,15 +2264,20 @@ view_move_to_back(struct view *view)
desktop_update_top_layer_visibility(view->server);
}
bool
view_is_modal_dialog(struct view *view)
{
assert(view);
assert(view->impl->is_modal_dialog);
return view->impl->is_modal_dialog(view);
}
struct view *
view_get_modal_dialog(struct view *view)
{
assert(view);
if (!view->impl->is_modal_dialog) {
return NULL;
}
/* check view itself first */
if (view->impl->is_modal_dialog(view)) {
if (view_is_modal_dialog(view)) {
return view;
}
@ -2298,7 +2290,7 @@ view_get_modal_dialog(struct view *view)
wl_array_init(&children);
view_append_children(root, &children);
wl_array_for_each(child, &children) {
if (view->impl->is_modal_dialog(*child)) {
if (view_is_modal_dialog(*child)) {
dialog = *child;
break;
}
@ -2409,30 +2401,12 @@ view_update_visibility(struct view *view)
}
wlr_scene_node_set_enabled(&view->scene_tree->node, visible);
struct server *server = view->server;
if (visible) {
desktop_focus_view(view, /*raise*/ true);
} else {
/*
* When exiting an xwayland application with multiple
* views mapped, a race condition can occur: after the
* topmost view is unmapped, the next view under it is
* offered focus, but is also unmapped before accepting
* focus (so server->active_view remains NULL). To avoid
* being left with no active view at all, check for that
* case also.
*/
if (view == server->active_view || !server->active_view) {
desktop_focus_topmost_view(server);
}
}
/*
* Show top layer when a fullscreen view is hidden.
* Hide it if a fullscreen view is shown (or uncovered).
*/
desktop_update_top_layer_visibility(server);
desktop_update_top_layer_visibility(view->server);
/*
* We may need to disable adaptive sync if view was fullscreen.
@ -2447,7 +2421,12 @@ view_update_visibility(struct view *view)
/* Update usable area to account for XWayland "struts" (panels) */
if (view_has_strut_partial(view)) {
output_update_all_usable_areas(server, false);
output_update_all_usable_areas(view->server, false);
}
/* View might have been unmapped/minimized during move/resize */
if (!visible) {
interactive_cancel(view);
}
}
@ -2554,8 +2533,11 @@ view_destroy(struct view *view)
view->foreign_toplevel = NULL;
}
/*
* This check is (in theory) redundant since interactive_cancel()
* is called at unmap. Leaving it here just to be sure.
*/
if (server->grabbed_view == view) {
/* Application got killed while moving around */
interactive_cancel(view);
}
@ -2576,6 +2558,7 @@ view_destroy(struct view *view)
undecorate(view);
clear_last_placement(view);
view_set_icon(view, NULL, NULL);
menu_on_view_destroy(view);

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 */
static void
handle_cosmic_workspace_activate(struct wl_listener *listener, void *data)
@ -209,18 +236,11 @@ add_workspace(struct server *server, const char *name)
workspace->name = xstrdup(name);
workspace->tree = wlr_scene_tree_create(server->view_tree);
wl_list_append(&server->workspaces.all, &workspace->link);
if (!server->workspaces.current) {
server->workspaces.current = workspace;
} else {
wlr_scene_node_set_enabled(&workspace->tree->node, false);
}
bool active = server->workspaces.current == workspace;
wlr_scene_node_set_enabled(&workspace->tree->node, false);
/* cosmic */
workspace->cosmic_workspace = lab_cosmic_workspace_create(server->workspaces.cosmic_group);
lab_cosmic_workspace_set_name(workspace->cosmic_workspace, name);
lab_cosmic_workspace_set_active(workspace->cosmic_workspace, active);
workspace->on_cosmic.activate.notify = handle_cosmic_workspace_activate;
wl_signal_add(&workspace->cosmic_workspace->events.activate,
@ -231,7 +251,6 @@ add_workspace(struct server *server, const char *name)
server->workspaces.ext_manager, /*id*/ NULL);
lab_ext_workspace_assign_to_group(workspace->ext_workspace, server->workspaces.ext_group);
lab_ext_workspace_set_name(workspace->ext_workspace, name);
lab_ext_workspace_set_active(workspace->ext_workspace, active);
workspace->on_ext.activate.notify = handle_ext_workspace_activate;
wl_signal_add(&workspace->ext_workspace->events.activate,
@ -394,10 +413,31 @@ workspaces_init(struct server *server)
wl_list_init(&server->workspaces.all);
struct workspace *conf;
struct workspace_config *conf;
wl_list_for_each(conf, &rc.workspace_config.workspaces, link) {
add_workspace(server, conf->name);
}
/*
* After adding workspaces, check if there is an initial workspace
* selected and set that as the initial workspace.
*/
char *initial_name = rc.workspace_config.initial_workspace_name;
struct workspace *initial = NULL;
struct workspace *first = wl_container_of(
server->workspaces.all.next, first, link);
if (initial_name) {
initial = workspace_find_by_name(server, initial_name);
}
if (!initial) {
initial = first;
}
server->workspaces.current = initial;
wlr_scene_node_set_enabled(&initial->tree->node, true);
lab_cosmic_workspace_set_active(initial->cosmic_workspace, true);
lab_ext_workspace_set_active(initial->ext_workspace, true);
}
/*
@ -507,21 +547,13 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap)
if (!name) {
return NULL;
}
size_t index = 0;
struct workspace *target;
size_t wants_index = parse_workspace_index(name);
struct wl_list *workspaces = &anchor->server->workspaces.all;
struct server *server = anchor->server;
struct wl_list *workspaces = &server->workspaces.all;
if (wants_index) {
wl_list_for_each(target, workspaces, link) {
if (wants_index == ++index) {
return target;
}
}
} else if (!strcasecmp(name, "current")) {
if (!strcasecmp(name, "current")) {
return anchor;
} else if (!strcasecmp(name, "last")) {
return anchor->server->workspaces.last;
return server->workspaces.last;
} else if (!strcasecmp(name, "left")) {
return get_prev(anchor, workspaces, wrap);
} else if (!strcasecmp(name, "right")) {
@ -530,15 +562,8 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap)
return get_prev_occupied(anchor, workspaces, wrap);
} else if (!strcasecmp(name, "right-occupied")) {
return get_next_occupied(anchor, workspaces, wrap);
} else {
wl_list_for_each(target, workspaces, link) {
if (!strcasecmp(target->name, name)) {
return target;
}
}
}
wlr_log(WLR_ERROR, "Workspace '%s' not found", name);
return NULL;
return workspace_find_by_name(server, name);
}
static void
@ -565,36 +590,34 @@ workspaces_reconfigure(struct server *server)
* - Destroy workspaces if fewer workspace are desired
*/
struct wl_list *actual_workspace_link = server->workspaces.all.next;
struct wl_list *workspace_link = server->workspaces.all.next;
struct workspace *configured_workspace;
wl_list_for_each(configured_workspace,
&rc.workspace_config.workspaces, link) {
struct workspace *actual_workspace = wl_container_of(
actual_workspace_link, actual_workspace, link);
struct workspace_config *conf;
wl_list_for_each(conf, &rc.workspace_config.workspaces, link) {
struct workspace *workspace = wl_container_of(
workspace_link, workspace, link);
if (actual_workspace_link == &server->workspaces.all) {
if (workspace_link == &server->workspaces.all) {
/* # of configured workspaces increased */
wlr_log(WLR_DEBUG, "Adding workspace \"%s\"",
configured_workspace->name);
add_workspace(server, configured_workspace->name);
conf->name);
add_workspace(server, conf->name);
continue;
}
if (strcmp(actual_workspace->name, configured_workspace->name)) {
if (strcmp(workspace->name, conf->name)) {
/* Workspace is renamed */
wlr_log(WLR_DEBUG, "Renaming workspace \"%s\" to \"%s\"",
actual_workspace->name, configured_workspace->name);
free(actual_workspace->name);
actual_workspace->name = xstrdup(configured_workspace->name);
workspace->name, conf->name);
xstrdup_replace(workspace->name, conf->name);
lab_cosmic_workspace_set_name(
actual_workspace->cosmic_workspace, actual_workspace->name);
workspace->cosmic_workspace, workspace->name);
lab_ext_workspace_set_name(
actual_workspace->ext_workspace, actual_workspace->name);
workspace->ext_workspace, workspace->name);
}
actual_workspace_link = actual_workspace_link->next;
workspace_link = workspace_link->next;
}
if (actual_workspace_link == &server->workspaces.all) {
if (workspace_link == &server->workspaces.all) {
return;
}
@ -603,30 +626,30 @@ workspaces_reconfigure(struct server *server)
struct workspace *first_workspace =
wl_container_of(server->workspaces.all.next, first_workspace, link);
while (actual_workspace_link != &server->workspaces.all) {
struct workspace *actual_workspace = wl_container_of(
actual_workspace_link, actual_workspace, link);
while (workspace_link != &server->workspaces.all) {
struct workspace *workspace = wl_container_of(
workspace_link, workspace, link);
wlr_log(WLR_DEBUG, "Destroying workspace \"%s\"",
actual_workspace->name);
workspace->name);
struct view *view;
wl_list_for_each(view, &server->views, link) {
if (view->workspace == actual_workspace) {
if (view->workspace == workspace) {
view_move_to_workspace(view, first_workspace);
}
}
if (server->workspaces.current == actual_workspace) {
if (server->workspaces.current == workspace) {
workspaces_switch_to(first_workspace,
/* update_focus */ true);
}
if (server->workspaces.last == actual_workspace) {
if (server->workspaces.last == workspace) {
server->workspaces.last = first_workspace;
}
actual_workspace_link = actual_workspace_link->next;
destroy_workspace(actual_workspace);
workspace_link = workspace_link->next;
destroy_workspace(workspace);
}
}

View file

@ -225,8 +225,7 @@ handle_commit(struct wl_listener *listener, void *data)
set_fullscreen_from_request(view, &toplevel->requested);
}
if (toplevel->requested.maximized) {
view_maximize(view, VIEW_AXIS_BOTH,
/*store_natural_geometry*/ true);
view_maximize(view, VIEW_AXIS_BOTH);
}
return;
}
@ -501,12 +500,11 @@ handle_request_maximize(struct wl_listener *listener, void *data)
return;
}
if (!view->mapped && !view->output) {
if (!view->mapped && !output_is_usable(view->output)) {
view_set_output(view, output_nearest_to_cursor(view->server));
}
bool maximized = toplevel->requested.maximized;
view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
/*store_natural_geometry*/ true);
view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE);
}
static void
@ -523,7 +521,7 @@ handle_request_fullscreen(struct wl_listener *listener, void *data)
return;
}
if (!view->mapped && !view->output) {
if (!view->mapped && !output_is_usable(view->output)) {
view_set_output(view, output_nearest_to_cursor(view->server));
}
set_fullscreen_from_request(view,
@ -821,7 +819,7 @@ handle_map(struct wl_listener *listener, void *data)
* An output should have been chosen when the surface was first
* created, but take one more opportunity to assign an output if not.
*/
if (!view->output) {
if (!output_is_usable(view->output)) {
view_set_output(view, output_nearest_to_cursor(view->server));
}

View file

@ -228,7 +228,7 @@ ensure_initial_geometry_and_output(struct view *view)
view->pending = view->current;
}
}
if (!view->output) {
if (!output_is_usable(view->output)) {
/*
* Just use the cursor output since we don't know yet
* whether the surface position is meaningful.
@ -470,7 +470,7 @@ handle_request_maximize(struct wl_listener *listener, void *data)
if (surf->maximized_horz) {
maximize |= VIEW_AXIS_HORIZONTAL;
}
view_maximize(view, maximize, /*store_natural_geometry*/ true);
view_maximize(view, maximize);
}
static void
@ -704,7 +704,7 @@ handle_map_request(struct wl_listener *listener, void *data)
if (xsurface->maximized_vert) {
axis |= VIEW_AXIS_VERTICAL;
}
view_maximize(view, axis, /*store_natural_geometry*/ true);
view_maximize(view, axis);
/*
* We could also call set_initial_position() here, but it's not
* really necessary until the view is actually mapped (and at
@ -946,6 +946,14 @@ xwayland_view_set_activated(struct view *view, bool activated)
}
wlr_xwayland_surface_activate(xwayland_surface, activated);
/*
* Make sure that the X11-protocol messages (SetInputFocus etc.)
* are sent immediately. This mitigates a race where the XWayland
* server may generate an unwanted FocusOut event for the newly
* activated window, if it receives mouse/pointer events over the
* parallel wayland connection first.
*/
xwayland_flush(view->server);
}
static void