Compare commits

..

No commits in common. "master" and "0.9.3" have entirely different histories.

43 changed files with 670 additions and 1128 deletions

View file

@ -280,18 +280,14 @@ jobs:
LABWC_RUNS=2 scripts/ci/smoke-test.sh build-gcc-gdb
' | $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
- 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,7 +9,6 @@ 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 |
@ -109,34 +108,6 @@ 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]
@ -3052,13 +3023,4 @@ 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,13 +22,6 @@
# 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,25 +125,13 @@ 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" workspace="current" output="all" identifier="all" />*++
*<action name="PreviousWindow" workspace="current" output="all" identifier="all" />*
*<action name="NextWindow" />*++
*<action name="PreviousWindow" />*
Cycle focus to next/previous window, respectively.
Default keybinds for NextWindow and PreviousWindow are Alt-Tab and
Shift-Alt-Tab. While cycling through windows, the arrow keys move the
selected window forwards/backwards and the escape key halts the cycling.
Default keybind for NextWindow is Alt-Tab.
*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".
The arrow keys are used to move forwards/backwards while cycling.
*<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">
<windowSwitcher preview="yes" outlines="yes" allWorkspaces="no">
<osd show="yes" style="classic" output="all" thumbnailLabelFormat="%T" />
<fields>
<field content="icon" width="5%" />
@ -349,13 +349,17 @@ this is for compatibility with Openbox.
</windowSwitcher>
```
*<windowSwitcher preview="" outlines="" unshade="" order="">*
*<windowSwitcher preview="" outlines="" allWorkspaces="" 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.
@ -512,7 +516,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 a window to an output edge. Default is yes.
Show an overlay when snapping to a window to an edge. Default is yes.
*<snapping><overlay><delay><inner>*++
*<snapping><overlay><delay><outer>*
@ -571,11 +575,6 @@ 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" unshade="yes">
<windowSwitcher preview="yes" outlines="yes" allWorkspaces="no" 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">
<windowSwitcher preview="no" outlines="no" allWorkspaces="yes">
<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">
<windowSwitcher preview="no" outlines="no" allWorkspaces="yes">
<osd show="yes" />
<fields>
<field content="custom" format="foobar %b %3s %-10o %-20W %-10i %t" width="100%" />
@ -175,7 +175,6 @@
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 (can be NULL)
* @b: second string to compare (can be NULL)
* @a: first string to compare
* @b: second string to compare
*
* If both strings are NULL, returns true.
*/

View file

@ -57,11 +57,6 @@ 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;
@ -173,9 +168,8 @@ struct rcxml {
struct {
int popuptime;
int min_nr_workspaces;
char *initial_workspace_name;
char *prefix;
struct wl_list workspaces; /* struct workspace_config.link */
struct wl_list workspaces; /* struct workspace.link */
} workspace_config;
/* Regions */
@ -183,18 +177,16 @@ 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,20 +117,10 @@ enum cycle_osd_style {
CYCLE_OSD_STYLE_THUMBNAIL,
};
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,
enum cycle_osd_output_criteria {
CYCLE_OSD_OUTPUT_ALL,
CYCLE_OSD_OUTPUT_CURSOR,
CYCLE_OSD_OUTPUT_FOCUSED,
};
#endif /* LABWC_CONFIG_TYPES_H */

View file

@ -4,11 +4,8 @@
#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,
@ -42,47 +39,7 @@ struct cycle_osd_field {
enum cycle_osd_field_content content;
int width;
char *format;
struct wl_list link; /* struct rcxml.window_switcher.osd.fields */
};
struct cycle_filter {
enum cycle_workspace_filter workspace;
enum cycle_output_filter output;
enum cycle_app_id_filter app_id;
};
struct 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 wl_list link; /* struct rcxml.window_switcher.fields */
};
struct buf;
@ -91,8 +48,7 @@ struct server;
struct wlr_scene_node;
/* Begin window switcher */
void cycle_begin(struct server *server, enum lab_cycle_dir direction,
struct cycle_filter filter);
void cycle_begin(struct server *server, enum lab_cycle_dir direction);
/* Cycle the selected view in the window switcher */
void cycle_step(struct server *server, enum lab_cycle_dir direction);
@ -128,38 +84,17 @@ struct cycle_osd_item {
struct cycle_osd_impl {
/*
* Create a scene-tree of OSD for an output and fill
* osd_output->items.
* Create a scene-tree of OSD for an output.
* This sets output->cycle_osd.{items,tree}.
*/
void (*init)(struct cycle_osd_output *osd_output);
void (*create)(struct output *output);
/*
* Update the OSD to highlight server->cycle.selected_view.
* Update output->cycle_osd.tree to highlight
* server->cycle_state.selected_view.
*/
void (*update)(struct cycle_osd_output *osd_output);
void (*update)(struct output *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,7 +5,6 @@
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include "common/set.h"
#include "cycle.h"
#include "input/cursor.h"
#include "overlay.h"
@ -188,7 +187,6 @@ 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;
@ -239,7 +237,6 @@ 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;
@ -305,7 +302,15 @@ struct server {
struct wlr_security_context_manager_v1 *security_context_manager_v1;
/* Set when in cycle (alt-tab) mode */
struct cycle_state cycle;
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 theme *theme;
@ -398,10 +403,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);
/*
* If restore_focus=true, restore the pointer/keyboard focus which was cleared
* in seat_focus_override_begin().
* Restore the pointer/keyboard focus which was cleared in
* seat_focus_override_begin().
*/
void seat_focus_override_end(struct seat *seat, bool restore_focus);
void seat_focus_override_end(struct seat *seat);
/**
* interactive_anchor_to_cursor() - repositions the geometry to remain

View file

@ -19,6 +19,11 @@ 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,20 +214,12 @@ struct view {
*/
struct wlr_box natural_geometry;
/*
* 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.
* 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.
*/
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;
struct wlr_box last_layout_geometry;
/* used by xdg-shell views */
uint32_t pending_configure_serial;
@ -344,7 +336,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 (front to back)
* for_each_view() - iterate over all views which match criteria
* @view: Iterator.
* @head: Head of list to iterate over.
* @criteria: Criteria to match against.
@ -360,7 +352,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 (back to front)
* for_each_view_reverse() - iterate over all views which match criteria
* @view: Iterator.
* @head: Head of list to iterate over.
* @criteria: Criteria to match against.
@ -520,7 +512,8 @@ 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);
void view_maximize(struct view *view, enum view_axis axis,
bool store_natural_geometry);
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);
@ -541,20 +534,19 @@ 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);
void view_snap_to_region(struct view *view, struct region *region);
bool across_outputs, bool combine, bool store_natural_geometry);
void view_snap_to_region(struct view *view, struct region *region, bool store_natural_geometry);
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,8 +10,12 @@ 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 wl_list link; /*
* struct server.workspaces
* struct rcxml.workspace_config.workspaces
*/
struct server *server;
char *name;

View file

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

View file

@ -1,81 +0,0 @@
# 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"

View file

@ -1,80 +0,0 @@
# 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,6 +27,7 @@ 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

@ -0,0 +1,67 @@
<?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 16 --max-procs $(nproc) \
nice xargs -0 --max-args 1 --max-procs $(nproc) \
scripts/checkpatch.pl --terse --no-tree --strict --file
return $?
}

View file

@ -366,44 +366,6 @@ 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);
@ -1146,7 +1108,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);
combine, /*store_natural_geometry*/ true);
}
break;
case ACTION_TYPE_GROW_TO_EDGE:
@ -1164,24 +1126,19 @@ 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, dir);
cycle_step(server, LAB_CYCLE_DIR_FORWARD);
} else {
cycle_begin(server, dir, filter);
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);
}
break;
}
case ACTION_TYPE_RECONFIGURE:
kill(getpid(), SIGHUP);
break;
@ -1203,14 +1160,16 @@ 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);
view_maximize(view, axis,
/*store_natural_geometry*/ true);
}
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);
view_maximize(view, view->maximized & ~axis,
/*store_natural_geometry*/ true);
}
break;
case ACTION_TYPE_TOGGLE_FULLSCREEN:
@ -1409,7 +1368,7 @@ run_action(struct view *view, struct server *server, struct action *action,
break;
}
struct output *output = view->output;
if (!output_is_usable(output)) {
if (!output) {
break;
}
const char *region_name = action_get_str(action, "region", NULL);
@ -1424,7 +1383,8 @@ run_action(struct view *view, struct server *server, struct action *action,
view_apply_natural_geometry(view);
break;
}
view_snap_to_region(view, region);
view_snap_to_region(view, region,
/*store_natural_geometry*/ true);
} else {
wlr_log(WLR_ERROR, "Invalid SnapToRegion id: '%s'", region_name);
}
@ -1432,7 +1392,8 @@ 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);
view_maximize(view, VIEW_AXIS_NONE,
/* store_natural_geometry */ false);
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.osd.fields, link) {
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.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.osd.fields, &field->link);
wl_list_append(&rc.window_switcher.fields, &field->link);
xmlNode *child;
char *key, *content;
@ -684,10 +684,6 @@ 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
@ -695,6 +691,10 @@ 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.osd.thumbnail_label_format, content);
xstrdup_replace(rc.window_switcher.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.osd.show);
set_bool(content, &rc.window_switcher.show);
} else if (!strcasecmp(nodename, "style.osd.windowSwitcher")) {
if (!strcasecmp(content, "classic")) {
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_CLASSIC;
rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC;
} else if (!strcasecmp(content, "thumbnail")) {
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL;
rc.window_switcher.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.osd.output_filter = CYCLE_OUTPUT_ALL;
rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_ALL;
} else if (!strcasecmp(content, "cursor")) {
rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_CURSOR;
rc.window_switcher.output_criteria = CYCLE_OSD_OUTPUT_CURSOR;
} else if (!strcasecmp(content, "focused")) {
rc.window_switcher.osd.output_filter = CYCLE_OUTPUT_FOCUSED;
rc.window_switcher.output_criteria = CYCLE_OSD_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.osd.show);
set_bool(content, &rc.window_switcher.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.osd.style = CYCLE_OSD_STYLE_CLASSIC;
rc.window_switcher.style = CYCLE_OSD_STYLE_CLASSIC;
} else if (!strcasecmp(content, "thumbnail")) {
rc.window_switcher.osd.style = CYCLE_OSD_STYLE_THUMBNAIL;
rc.window_switcher.style = CYCLE_OSD_STYLE_THUMBNAIL;
}
wlr_log(WLR_ERROR, "<windowSwitcher style=\"\" /> is deprecated."
" Use <windowSwitcher><osd style=\"\" />");
@ -1269,16 +1269,10 @@ 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")) {
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;
if (parse_bool(content, -1) == true) {
rc.window_switcher.criteria &=
~LAB_VIEW_CRITERIA_CURRENT_WORKSPACE;
}
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);
@ -1288,7 +1282,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.osd.show);
set_bool(content, &rc.window_switcher.show);
} else if (!strcasecmp(nodename, "preview.windowSwitcher.core")) {
set_bool(content, &rc.window_switcher.preview);
} else if (!strcasecmp(nodename, "outlines.windowSwitcher.core")) {
@ -1296,7 +1290,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.osd.show);
set_bool(content, &rc.window_switcher.show);
wlr_log(WLR_ERROR, "<cycleViewOSD> is deprecated."
" Use <windowSwitcher show=\"\" />");
} else if (!strcasecmp(nodename, "cycleViewPreview.core")) {
@ -1309,13 +1303,11 @@ entry(xmlNode *node, char *nodename, char *content)
" Use <windowSwitcher outlines=\"\" />");
} else if (!strcasecmp(nodename, "name.names.desktops")) {
struct workspace_config *conf = znew(*conf);
conf->name = xstrdup(content);
wl_list_append(&rc.workspace_config.workspaces, &conf->link);
struct workspace *workspace = znew(*workspace);
workspace->name = xstrdup(content);
wl_list_append(&rc.workspace_config.workspaces, &workspace->link);
} else if (!strcasecmp(nodename, "popupTime.desktops")) {
rc.workspace_config.popuptime = atoi(content);
} else if (!strcasecmp(nodename, "initial.desktops")) {
xstrdup_replace(rc.workspace_config.initial_workspace_name, content);
} else if (!strcasecmp(nodename, "number.desktops")) {
rc.workspace_config.min_nr_workspaces = MAX(1, atoi(content));
} else if (!strcasecmp(nodename, "popupShow.resize")) {
@ -1427,7 +1419,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.osd.fields);
wl_list_init(&rc.window_switcher.fields);
wl_list_init(&rc.window_rules);
wl_list_init(&rc.touch_configs);
}
@ -1492,14 +1484,16 @@ rcxml_init(void)
rc.snap_top_maximize = true;
rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS;
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.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.preview = true;
rc.window_switcher.outlines = true;
rc.window_switcher.unshade = true;
rc.window_switcher.workspace_filter = CYCLE_WORKSPACE_CURRENT;
rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE
| LAB_VIEW_CRITERIA_ROOT_TOPLEVEL
| LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER;
rc.window_switcher.order = WINDOW_SWITCHER_ORDER_FOCUS;
rc.resize_indicator = LAB_RESIZE_INDICATOR_NEVER;
@ -1679,7 +1673,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.osd.fields, &field->link);
wl_list_append(&rc.window_switcher.fields, &field->link);
}
}
@ -1784,14 +1778,15 @@ post_processing(void)
}
struct buf b = BUF_INIT;
struct workspace *workspace;
for (int i = nr_workspaces; i < rc.workspace_config.min_nr_workspaces; i++) {
struct workspace_config *conf = znew(*conf);
workspace = znew(*workspace);
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);
conf->name = xstrdup(b.data);
wl_list_append(&rc.workspace_config.workspaces, &conf->link);
workspace->name = xstrdup(b.data);
wl_list_append(&rc.workspace_config.workspaces, &workspace->link);
buf_clear(&b);
}
buf_reset(&b);
@ -1799,7 +1794,7 @@ post_processing(void)
if (rc.workspace_config.popuptime == INT_MIN) {
rc.workspace_config.popuptime = 1000;
}
if (!wl_list_length(&rc.window_switcher.osd.fields)) {
if (!wl_list_length(&rc.window_switcher.fields)) {
wlr_log(WLR_INFO, "load default window switcher fields");
load_default_window_switcher_fields();
}
@ -1893,7 +1888,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.osd.fields, link) {
wl_list_for_each_safe(field, field_tmp, &rc.window_switcher.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);
@ -1968,9 +1963,8 @@ 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.osd.thumbnail_label_format);
zfree(rc.window_switcher.thumbnail_label_format);
clear_title_layout();
@ -2010,7 +2004,7 @@ rcxml_finish(void)
zfree(l);
}
struct workspace_config *w, *w_tmp;
struct workspace *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,7 +6,6 @@
#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"
@ -18,7 +17,7 @@
#include "theme.h"
#include "view.h"
static bool init_cycle(struct server *server, struct cycle_filter filter);
static bool init_cycle(struct server *server);
static void update_cycle(struct server *server);
static void destroy_cycle(struct server *server);
@ -40,8 +39,7 @@ 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->cycle_preview_tree->node);
wlr_scene_node_place_above(&rect->tree->node, &server->menu_tree->node);
server->cycle.preview_outline = rect;
}
@ -95,10 +93,9 @@ 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, filter)) {
if (init_cycle(server)) {
/*
* Preserve the selected view (or its previous view) if it's
* still in the cycle list
@ -155,14 +152,13 @@ restore_preview_node(struct server *server)
}
void
cycle_begin(struct server *server, enum lab_cycle_dir direction,
struct cycle_filter filter)
cycle_begin(struct server *server, enum lab_cycle_dir direction)
{
if (server->input_mode != LAB_INPUT_STATE_PASSTHROUGH) {
return;
}
if (!init_cycle(server, filter)) {
if (!init_cycle(server)) {
return;
}
@ -204,7 +200,8 @@ cycle_finish(struct server *server, bool switch_focus)
struct view *selected_view = server->cycle.selected_view;
destroy_cycle(server);
seat_focus_override_end(&server->seat, /*restore_focus*/ false);
/* FIXME: this sets focus to the old surface even with switch_focus=true */
seat_focus_override_end(&server->seat);
/* Hiding OSD may need a cursor change */
cursor_update_focus(server);
@ -245,8 +242,13 @@ 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->cycle_preview_tree);
view->server->view_tree_always_on_top);
/* Finally raise selected node to the top */
wlr_scene_node_raise_to_top(cycle->preview_node);
@ -255,7 +257,7 @@ preview_selected_view(struct view *view)
static struct cycle_osd_impl *
get_osd_impl(void)
{
switch (rc.window_switcher.osd.style) {
switch (rc.window_switcher.style) {
case CYCLE_OSD_STYLE_CLASSIC:
return &cycle_osd_classic_impl;
case CYCLE_OSD_STYLE_THUMBNAIL:
@ -264,36 +266,14 @@ get_osd_impl(void)
return NULL;
}
static uint64_t
get_outputs_by_filter(struct server *server,
enum cycle_output_filter output_filter)
static void
create_osd_on_output(struct output *output)
{
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;
if (!output_is_usable(output)) {
return;
}
get_osd_impl()->create(output);
assert(output->cycle_osd.tree);
}
static void
@ -310,49 +290,12 @@ 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, struct cycle_filter filter)
init_cycle(struct server *server)
{
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, criteria) {
if (!(cycle_outputs & view->output->id_bit)) {
continue;
}
if (cycle_app_id && strcmp(view->app_id, cycle_app_id) != 0) {
continue;
}
for_each_view(view, &server->views, rc.window_switcher.criteria) {
if (rc.window_switcher.order == WINDOW_SWITCHER_ORDER_AGE) {
insert_view_ordered_by_age(&server->cycle.views, view);
} else {
@ -363,31 +306,31 @@ init_cycle(struct server *server, struct cycle_filter filter)
wlr_log(WLR_DEBUG, "no views to switch between");
return false;
}
server->cycle.filter = filter;
if (rc.window_switcher.osd.show) {
if (rc.window_switcher.show) {
/* Create OSD */
uint64_t osd_outputs = get_outputs_by_filter(server,
rc.window_switcher.osd.output_filter);
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
if (!(osd_outputs & output->id_bit)) {
continue;
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);
}
if (!output_is_usable(output)) {
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);
}
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);
create_osd_on_output(output);
break;
}
}
}
@ -399,10 +342,12 @@ update_cycle(struct server *server)
{
struct cycle_state *cycle = &server->cycle;
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);
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);
}
}
}
@ -412,8 +357,8 @@ update_cycle(struct server *server)
/* Outline current window */
if (rc.window_switcher.outlines) {
if (view_is_focusable(cycle->selected_view)) {
update_preview_outlines(cycle->selected_view);
if (view_is_focusable(server->cycle.selected_view)) {
update_preview_outlines(server->cycle.selected_view);
}
}
}
@ -422,25 +367,31 @@ update_cycle(struct server *server)
static void
destroy_cycle(struct server *server)
{
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);
struct output *output;
wl_list_for_each(output, &server->outputs, link) {
struct cycle_osd_item *item, *tmp;
wl_list_for_each_safe(item, tmp, &output->cycle_osd.items, link) {
wl_list_remove(&item->link);
free(item);
}
if (output->cycle_osd.tree) {
wlr_scene_node_destroy(&output->cycle_osd.tree->node);
output->cycle_osd.tree = NULL;
}
}
restore_preview_node(server);
if (server->cycle.preview_outline) {
wlr_scene_node_destroy(&server->cycle.preview_outline->tree->node);
server->cycle.preview_outline = NULL;
}
struct view *view, *tmp2;
wl_list_for_each_safe(view, tmp2, &server->cycle.views, cycle_link) {
struct view *view, *tmp;
wl_list_for_each_safe(view, tmp, &server->cycle.views, cycle_link) {
wl_list_remove(&view->cycle_link);
view->cycle_link = (struct wl_list){0};
}
server->cycle = (struct cycle_state){0};
wl_list_init(&server->cycle.views);
wl_list_init(&server->cycle.osd_outputs);
server->cycle.selected_view = NULL;
}

View file

@ -2,6 +2,5 @@ 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.osd.fields, link) {
wl_list_for_each(field, &rc.window_switcher.fields, link) {
int field_width = field_widths_sum * field->width / 100.0;
struct wlr_scene_node *node = NULL;
int height = -1;
@ -77,9 +77,10 @@ create_fields_scene(struct server *server, struct view *view,
}
static void
cycle_osd_classic_init(struct cycle_osd_output *osd_output)
cycle_osd_classic_create(struct output *output)
{
struct output *output = osd_output->output;
assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items));
struct server *server = output->server;
struct theme *theme = server->theme;
struct window_switcher_classic_theme *switcher_theme =
@ -97,18 +98,13 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output)
if (switcher_theme->width_is_percent) {
w = output_box.width * switcher_theme->width / 100;
}
int workspace_name_h = 0;
int h = nr_views * switcher_theme->item_height + 2 * padding;
if (show_workspace) {
/* workspace indicator */
workspace_name_h = switcher_theme->item_height;
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;
osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree);
output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree);
float *text_color = theme->osd_label_text_color;
float *bg_color = theme->osd_bg_color;
@ -122,7 +118,7 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output)
.width = w,
.height = h,
};
lab_scene_rect_create(osd_output->tree, &bg_opts);
lab_scene_rect_create(output->cycle_osd.tree, &bg_opts);
int y = padding;
@ -140,7 +136,7 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output)
}
struct scaled_font_buffer *font_buffer =
scaled_font_buffer_create(osd_output->tree);
scaled_font_buffer_create(output->cycle_osd.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,
@ -148,7 +144,7 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_output)
y += switcher_theme->item_height;
}
int nr_fields = wl_list_length(&rc.window_switcher.osd.fields);
int nr_fields = wl_list_length(&rc.window_switcher.fields);
/* This is the width of the area available for text fields */
int field_widths_sum = w - 2 * padding
@ -159,17 +155,13 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_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(&osd_output->items, &item->base.link);
wl_list_append(&output->cycle_osd.items, &item->base.link);
item->base.view = view;
item->base.tree = wlr_scene_tree_create(osd_output->items_tree);
item->base.tree = wlr_scene_tree_create(output->cycle_osd.tree);
node_descriptor_create(&item->base.tree->node,
LAB_NODE_CYCLE_OSD_ITEM, NULL, item);
/*
@ -195,6 +187,9 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_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},
@ -221,39 +216,25 @@ cycle_osd_classic_init(struct cycle_osd_output *osd_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(&osd_output->tree->node,
wlr_scene_node_set_position(&output->cycle_osd.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 cycle_osd_output *osd_output)
cycle_osd_classic_update(struct output *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, &osd_output->items, base.link) {
bool active = item->base.view == server->cycle.selected_view;
wl_list_for_each(item, &output->cycle_osd.items, base.link) {
bool active = item->base.view == output->server->cycle.selected_view;
wlr_scene_node_set_enabled(&item->normal_tree->node, !active);
wlr_scene_node_set_enabled(&item->active_tree->node, active);
}
}
struct cycle_osd_impl cycle_osd_classic_impl = {
.init = cycle_osd_classic_init,
.create = cycle_osd_classic_create,
.update = cycle_osd_classic_update,
};

View file

@ -1,95 +0,0 @@
// 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.osd.thumbnail_label_format);
rc.window_switcher.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 cycle_osd_output *osd_output)
struct output *output)
{
struct server *server = osd_output->output->server;
struct server *server = 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(&osd_output->items, &item->base.link);
wl_list_append(&output->cycle_osd.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(osd_output->output, view);
struct wlr_buffer *thumb_buffer = render_thumb(output, view);
if (thumb_buffer) {
struct wlr_scene_buffer *thumb_scene_buffer =
wlr_scene_buffer_create(tree, thumb_buffer);
@ -194,10 +194,9 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view,
}
static void
get_items_geometry(struct output *output, int nr_thumbs,
int *nr_cols, int *nr_rows, int *nr_visible_rows)
get_items_geometry(struct output *output, struct theme *theme,
int nr_thumbs, int *nr_rows, int *nr_cols)
{
struct theme *theme = output->server->theme;
struct window_switcher_thumbnail_theme *switcher_theme =
&theme->osd_window_switcher_thumbnail;
int output_width, output_height;
@ -224,35 +223,32 @@ get_items_geometry(struct output *output, int nr_thumbs,
(*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_init(struct cycle_osd_output *osd_output)
cycle_osd_thumbnail_create(struct output *output)
{
struct output *output = osd_output->output;
assert(!output->cycle_osd.tree && wl_list_empty(&output->cycle_osd.items));
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;
osd_output->tree = wlr_scene_tree_create(output->cycle_osd_tree);
osd_output->items_tree = wlr_scene_tree_create(osd_output->tree);
output->cycle_osd.tree = wlr_scene_tree_create(output->cycle_osd_tree);
int nr_views = wl_list_length(&server->cycle.views);
assert(nr_views > 0);
int nr_cols, nr_rows, nr_visible_rows;
get_items_geometry(output, nr_views, &nr_cols, &nr_rows, &nr_visible_rows);
int nr_rows, nr_cols;
get_items_geometry(output, theme, nr_views, &nr_rows, &nr_cols);
/* 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(
osd_output->items_tree, view, osd_output);
output->cycle_osd.tree, view, output);
if (!item) {
break;
}
@ -262,31 +258,17 @@ cycle_osd_thumbnail_init(struct cycle_osd_output *osd_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 = items_width + 2 * padding,
.height = items_height + 2 * padding,
.width = nr_cols * switcher_theme->item_width + 2 * padding,
.height = nr_rows * switcher_theme->item_height + 2 * padding,
};
struct lab_scene_rect *bg =
lab_scene_rect_create(osd_output->tree, &bg_opts);
lab_scene_rect_create(output->cycle_osd.tree, &bg_opts);
wlr_scene_node_lower_to_bottom(&bg->tree->node);
/* center */
@ -295,18 +277,15 @@ cycle_osd_thumbnail_init(struct cycle_osd_output *osd_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(&osd_output->tree->node, lx, ly);
wlr_scene_node_set_position(&output->cycle_osd.tree->node, lx, ly);
}
static void
cycle_osd_thumbnail_update(struct cycle_osd_output *osd_output)
cycle_osd_thumbnail_update(struct output *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, &osd_output->items, base.link) {
bool active = (item->base.view == server->cycle.selected_view);
wl_list_for_each(item, &output->cycle_osd.items, base.link) {
bool active = (item->base.view == output->server->cycle.selected_view);
wlr_scene_node_set_enabled(&item->active_bg->tree->node, active);
wlr_scene_node_set_enabled(
&item->active_label->scene_buffer->node, active);
@ -316,6 +295,6 @@ cycle_osd_thumbnail_update(struct cycle_osd_output *osd_output)
}
struct cycle_osd_impl cycle_osd_thumbnail_impl = {
.init = cycle_osd_thumbnail_init,
.create = cycle_osd_thumbnail_create,
.update = cycle_osd_thumbnail_update,
};

View file

@ -55,9 +55,7 @@ set_or_offer_focus(struct view *view)
break;
case VIEW_WANTS_FOCUS_LIKELY:
case VIEW_WANTS_FOCUS_UNLIKELY:
if (view->surface != seat->seat->keyboard_state.focused_surface) {
view_offer_focus(view);
}
view_offer_focus(view);
break;
case VIEW_WANTS_FOCUS_NEVER:
break;
@ -138,9 +136,16 @@ static struct view *
desktop_topmost_focusable_view(struct server *server)
{
struct view *view;
for_each_view(view, &server->views,
LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
if (!view->minimized) {
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) {
return view;
}
}
@ -165,31 +170,41 @@ desktop_focus_topmost_view(struct server *server)
void
desktop_focus_output(struct output *output)
{
struct server *server = output->server;
if (!output_is_usable(output) || server->input_mode
if (!output_is_usable(output) || output->server->input_mode
!= LAB_INPUT_STATE_PASSTHROUGH) {
return;
}
struct view *view;
for_each_view(view, &server->views, LAB_VIEW_CRITERIA_CURRENT_WORKSPACE) {
if (view->outputs & output->id_bit) {
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)) {
desktop_focus_view(view, /*raise*/ false);
wlr_cursor_warp(server->seat.cursor, NULL,
wlr_cursor_warp(view->server->seat.cursor, NULL,
view->current.x + view->current.width / 2,
view->current.y + view->current.height / 2);
cursor_update_focus(server);
cursor_update_focus(view->server);
return;
}
}
/* No view found on desired output */
struct wlr_box layout_box;
wlr_output_layout_get_box(server->output_layout,
wlr_output_layout_get_box(output->server->output_layout,
output->wlr_output, &layout_box);
wlr_cursor_warp(server->seat.cursor, NULL,
wlr_cursor_warp(output->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(server);
cursor_update_focus(output->server);
}
void

View file

@ -26,7 +26,8 @@ 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);
event->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
/*store_natural_geometry*/ true);
}
static void

View file

@ -92,6 +92,9 @@ 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);
@ -108,6 +111,12 @@ 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
@ -264,12 +273,17 @@ 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);
view_maximize(view, VIEW_AXIS_BOTH,
/*store_natural_geometry*/ false);
} else {
view_snap_to_edge(view, edge, /*across_outputs*/ false,
/*combine*/ false);
/*combine*/ false, /*store_natural_geometry*/ false);
}
return true;
@ -284,7 +298,8 @@ snap_to_region(struct view *view)
struct region *region = regions_from_cursor(view->server);
if (region) {
view_snap_to_region(view, region);
view_snap_to_region(view, region,
/*store_natural_geometry*/ false);
return true;
}
return false;
@ -325,5 +340,5 @@ interactive_cancel(struct view *view)
view->server->grabbed_view = NULL;
/* Restore keyboard/pointer focus */
seat_focus_override_end(&view->server->seat, /*restore_focus*/ true);
seat_focus_override_end(&view->server->seat);
}

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_is_usable(output)) {
if (!output) {
goto no_output;
}
@ -589,14 +589,16 @@ handle_new_layer_surface(struct wl_listener *listener, void *data)
struct wlr_layer_surface_v1 *layer_surface = data;
if (!layer_surface->output) {
struct output *output = output_nearest_to_cursor(server);
if (!output_is_usable(output)) {
struct wlr_output *output = wlr_output_layout_output_at(
server->output_layout, server->seat.cursor->x,
server->seat.cursor->y);
if (!output) {
wlr_log(WLR_INFO,
"No output available to assign layer surface");
wlr_layer_surface_v1_destroy(layer_surface);
return;
}
layer_surface->output = output->wlr_output;
layer_surface->output = 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_is_usable(output)) {
if (!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, /*restore_focus*/ true);
seat_focus_override_end(&server->seat);
/*
* We call the actions after closing the menu so that virtual keyboard
@ -1443,9 +1443,6 @@ 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,
@ -1533,7 +1530,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, /*restore_focus*/ true);
seat_focus_override_end(&server->seat);
}
void

View file

@ -128,6 +128,17 @@ 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
@ -155,8 +166,7 @@ static void
handle_output_destroy(struct wl_listener *listener, void *data)
{
struct output *output = wl_container_of(listener, output, destroy);
struct server *server = output->server;
struct seat *seat = &server->seat;
struct seat *seat = &output->server->seat;
regions_evacuate_output(output);
regions_destroy(seat, &output->regions);
if (seat->overlay.active.output == output) {
@ -180,6 +190,7 @@ 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);
@ -531,6 +542,7 @@ 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
@ -865,9 +877,14 @@ wlr_output_configuration_v1 *create_output_config(struct server *server)
wlr_output_configuration_v1_destroy(config);
return NULL;
}
if (output_is_usable(output)) {
head->state.x = output->scene_output->x;
head->state.y = output->scene_output->y;
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");
}
}
return config;
@ -1051,14 +1068,8 @@ 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.
*
* 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;
/* output_is_usable(NULL) is safe and returns false */
return output && output->wlr_output->enabled;
}
/* returns true if usable area changed */
@ -1118,7 +1129,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_is_usable(output)) {
if (!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_is_usable(output)) {
if (!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, bool restore_focus)
seat_focus_override_end(struct seat *seat)
{
seat->server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
if (seat->focus_override.surface) {
if (restore_focus) {
if (!seat->seat->keyboard_state.focused_surface) {
seat_focus(seat, seat->focus_override.surface,
/*replace_exclusive_layer*/ false,
/*is_lock_surface*/ false);
@ -916,7 +916,5 @@ seat_focus_override_end(struct seat *seat, bool restore_focus)
seat->focus_override.surface = NULL;
}
if (restore_focus) {
cursor_update_focus(seat->server);
}
cursor_update_focus(seat->server);
}

View file

@ -97,7 +97,6 @@ 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);
@ -551,7 +550,6 @@ 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) {
@ -565,22 +563,21 @@ 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
*
* | 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)
* | 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
*/
server->view_tree_always_on_bottom = wlr_scene_tree_create(&server->scene->tree);
@ -591,7 +588,6 @@ 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 (!output_is_usable(view->output)) {
if (!view->output) {
return;
}

View file

@ -11,11 +11,6 @@ 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);
}
@ -46,19 +41,6 @@ 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,7 +14,6 @@
#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"
@ -288,8 +287,8 @@ matches_criteria(struct view *view, enum lab_view_criteria criteria)
return false;
}
}
if (criteria & LAB_VIEW_CRITERIA_NO_DIALOG) {
if (view_is_modal_dialog(view)) {
if (criteria & LAB_VIEW_CRITERIA_ROOT_TOPLEVEL) {
if (view != view_get_root(view)) {
return false;
}
}
@ -462,6 +461,10 @@ 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;
}
@ -578,8 +581,6 @@ view_moved(struct view *view)
}
}
static void save_last_placement(struct view *view);
void
view_move_resize(struct view *view, struct wlr_box geo)
{
@ -587,20 +588,6 @@ 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
@ -627,7 +614,7 @@ view_move_relative(struct view *view, int x, int y)
if (view->fullscreen) {
return;
}
view_maximize(view, VIEW_AXIS_NONE);
view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false);
if (view_is_tiled(view)) {
view_set_untiled(view);
view_move_resize(view, view->natural_geometry);
@ -645,7 +632,7 @@ view_move_to_cursor(struct view *view)
return;
}
view_set_fullscreen(view, false);
view_maximize(view, VIEW_AXIS_NONE);
view_maximize(view, VIEW_AXIS_NONE, /*store_natural_geometry*/ false);
if (view_is_tiled(view)) {
view_set_untiled(view);
view_move_resize(view, view->natural_geometry);
@ -743,7 +730,7 @@ view_adjust_size(struct view *view, int *w, int *h)
}
static void
_minimize(struct view *view, bool minimized, bool *need_refocus)
_minimize(struct view *view, bool minimized)
{
assert(view);
if (view->minimized == minimized) {
@ -756,15 +743,8 @@ _minimize(struct view *view, bool minimized, bool *need_refocus)
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);
view_update_visibility(view);
}
static void
@ -777,7 +757,7 @@ view_append_children(struct view *view, struct wl_array *children)
}
static void
minimize_sub_views(struct view *view, bool minimized, bool *need_refocus)
minimize_sub_views(struct view *view, bool minimized)
{
struct view **child;
struct wl_array children;
@ -785,8 +765,8 @@ minimize_sub_views(struct view *view, bool minimized, bool *need_refocus)
wl_array_init(&children);
view_append_children(view, &children);
wl_array_for_each(child, &children) {
_minimize(*child, minimized, need_refocus);
minimize_sub_views(*child, minimized, need_refocus);
_minimize(*child, minimized);
minimize_sub_views(*child, minimized);
}
wl_array_release(&children);
}
@ -801,10 +781,8 @@ void
view_minimize(struct view *view, bool minimized)
{
assert(view);
struct server *server = view->server;
bool need_refocus = false;
if (server->input_mode == LAB_INPUT_STATE_CYCLE) {
if (view->server->input_mode == LAB_INPUT_STATE_CYCLE) {
wlr_log(WLR_ERROR, "not minimizing window while window switching");
return;
}
@ -815,20 +793,8 @@ 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, &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);
}
}
_minimize(root, minimized);
minimize_sub_views(root, minimized);
}
bool
@ -859,7 +825,6 @@ 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)
@ -1105,7 +1070,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 || !output_is_usable(view->output) || view->fullscreen) {
if (!view || !view->output || view->fullscreen) {
return;
}
@ -1410,15 +1375,9 @@ 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)
view_maximize(struct view *view, enum view_axis axis,
bool store_natural_geometry)
{
assert(view);
@ -1430,7 +1389,6 @@ view_maximize(struct view *view, enum view_axis axis)
return;
}
bool store_natural_geometry = !in_interactive_move(view);
view_set_shade(view, false);
if (axis != VIEW_AXIS_NONE) {
@ -1440,6 +1398,9 @@ 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);
}
}
/*
@ -1479,7 +1440,8 @@ 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);
view_maximize(view, view->maximized ^ axis,
/*store_natural_geometry*/ true);
break;
case VIEW_AXIS_BOTH:
/*
@ -1487,7 +1449,8 @@ 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);
VIEW_AXIS_NONE : VIEW_AXIS_BOTH,
/*store_natural_geometry*/ true);
break;
default:
break;
@ -1727,6 +1690,7 @@ 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);
@ -1744,80 +1708,139 @@ view_set_fullscreen(struct view *view, bool fullscreen)
cursor_update_focus(view->server);
}
static void
save_last_placement(struct view *view)
static bool
last_layout_geometry_is_valid(struct view *view)
{
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;
return view->last_layout_geometry.width > 0
&& view->last_layout_geometry.height > 0;
}
static void
clear_last_placement(struct view *view)
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)
{
assert(view);
zfree(view->last_placement.output_name);
view->last_placement.relative_geo = (struct wlr_box){0};
view->last_placement.layout_geo = (struct wlr_box){0};
view->last_layout_geometry.width = 0;
view->last_layout_geometry.height = 0;
}
void
view_adjust_for_layout_change(struct view *view)
{
assert(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;
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);
}
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)) {
/*
* When the previous output (which might have been reconnected
* or relocated) is available, keep the relative position on it.
*/
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, 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;
/* 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;
}
if (!view_is_floating(view)) {
/*
* 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);
/*
* 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.
*/
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);
} else {
/* Ensure view is on-screen */
adjust_floating_geometry(view, &new_geo,
/* midpoint_visibility */ true);
view_move_resize(view, new_geo);
/* 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);
}
}
view_update_outputs(view);
view->adjusting_for_layout_change = false;
}
void
@ -1900,7 +1923,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_is_usable(output)) {
if (!output) {
return;
}
@ -2041,7 +2064,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 across_outputs, bool combine, bool store_natural_geometry)
{
assert(view);
@ -2055,7 +2078,6 @@ 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
@ -2080,7 +2102,7 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
*/
output = output_get_adjacent(view->output, edge,
/* wrap */ false);
if (!output_is_usable(output)) {
if (!output) {
return;
}
edge = invert_edge;
@ -2102,10 +2124,12 @@ 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);
view_maximize(view, VIEW_AXIS_NONE,
/*store_natural_geometry*/ false);
} 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);
@ -2115,7 +2139,8 @@ view_snap_to_edge(struct view *view, enum lab_edge edge,
}
void
view_snap_to_region(struct view *view, struct region *region)
view_snap_to_region(struct view *view, struct region *region,
bool store_natural_geometry)
{
assert(view);
assert(region);
@ -2130,15 +2155,16 @@ 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);
view_maximize(view, VIEW_AXIS_NONE,
/*store_natural_geometry*/ false);
} 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;
@ -2151,6 +2177,7 @@ 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);
@ -2166,7 +2193,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);
view_snap_to_region(view, region, /*store_natural_geometry*/ false);
}
}
@ -2210,18 +2237,6 @@ 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);
@ -2242,9 +2257,7 @@ 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.
*/
if (view->type == LAB_XWAYLAND_VIEW) {
xwayland_flush(view->server);
}
xwayland_flush(view->server);
#endif
cursor_update_focus(view->server);
desktop_update_top_layer_visibility(view->server);
@ -2264,20 +2277,15 @@ 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_is_modal_dialog(view)) {
if (view->impl->is_modal_dialog(view)) {
return view;
}
@ -2290,7 +2298,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_is_modal_dialog(*child)) {
if (view->impl->is_modal_dialog(*child)) {
dialog = *child;
break;
}
@ -2401,12 +2409,30 @@ 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(view->server);
desktop_update_top_layer_visibility(server);
/*
* We may need to disable adaptive sync if view was fullscreen.
@ -2421,12 +2447,7 @@ 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(view->server, false);
}
/* View might have been unmapped/minimized during move/resize */
if (!visible) {
interactive_cancel(view);
output_update_all_usable_areas(server, false);
}
}
@ -2533,11 +2554,8 @@ 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);
}
@ -2558,7 +2576,6 @@ 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,33 +182,6 @@ _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)
@ -236,11 +209,18 @@ 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);
wlr_scene_node_set_enabled(&workspace->tree->node, false);
if (!server->workspaces.current) {
server->workspaces.current = workspace;
} else {
wlr_scene_node_set_enabled(&workspace->tree->node, false);
}
bool active = server->workspaces.current == workspace;
/* 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,
@ -251,6 +231,7 @@ 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,
@ -413,31 +394,10 @@ workspaces_init(struct server *server)
wl_list_init(&server->workspaces.all);
struct workspace_config *conf;
struct workspace *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);
}
/*
@ -547,13 +507,21 @@ workspaces_find(struct workspace *anchor, const char *name, bool wrap)
if (!name) {
return NULL;
}
struct server *server = anchor->server;
struct wl_list *workspaces = &server->workspaces.all;
size_t index = 0;
struct workspace *target;
size_t wants_index = parse_workspace_index(name);
struct wl_list *workspaces = &anchor->server->workspaces.all;
if (!strcasecmp(name, "current")) {
if (wants_index) {
wl_list_for_each(target, workspaces, link) {
if (wants_index == ++index) {
return target;
}
}
} else if (!strcasecmp(name, "current")) {
return anchor;
} else if (!strcasecmp(name, "last")) {
return server->workspaces.last;
return anchor->server->workspaces.last;
} else if (!strcasecmp(name, "left")) {
return get_prev(anchor, workspaces, wrap);
} else if (!strcasecmp(name, "right")) {
@ -562,8 +530,15 @@ 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;
}
}
}
return workspace_find_by_name(server, name);
wlr_log(WLR_ERROR, "Workspace '%s' not found", name);
return NULL;
}
static void
@ -590,34 +565,36 @@ workspaces_reconfigure(struct server *server)
* - Destroy workspaces if fewer workspace are desired
*/
struct wl_list *workspace_link = server->workspaces.all.next;
struct wl_list *actual_workspace_link = server->workspaces.all.next;
struct workspace_config *conf;
wl_list_for_each(conf, &rc.workspace_config.workspaces, link) {
struct workspace *workspace = wl_container_of(
workspace_link, workspace, link);
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);
if (workspace_link == &server->workspaces.all) {
if (actual_workspace_link == &server->workspaces.all) {
/* # of configured workspaces increased */
wlr_log(WLR_DEBUG, "Adding workspace \"%s\"",
conf->name);
add_workspace(server, conf->name);
configured_workspace->name);
add_workspace(server, configured_workspace->name);
continue;
}
if (strcmp(workspace->name, conf->name)) {
if (strcmp(actual_workspace->name, configured_workspace->name)) {
/* Workspace is renamed */
wlr_log(WLR_DEBUG, "Renaming workspace \"%s\" to \"%s\"",
workspace->name, conf->name);
xstrdup_replace(workspace->name, conf->name);
actual_workspace->name, configured_workspace->name);
free(actual_workspace->name);
actual_workspace->name = xstrdup(configured_workspace->name);
lab_cosmic_workspace_set_name(
workspace->cosmic_workspace, workspace->name);
actual_workspace->cosmic_workspace, actual_workspace->name);
lab_ext_workspace_set_name(
workspace->ext_workspace, workspace->name);
actual_workspace->ext_workspace, actual_workspace->name);
}
workspace_link = workspace_link->next;
actual_workspace_link = actual_workspace_link->next;
}
if (workspace_link == &server->workspaces.all) {
if (actual_workspace_link == &server->workspaces.all) {
return;
}
@ -626,30 +603,30 @@ workspaces_reconfigure(struct server *server)
struct workspace *first_workspace =
wl_container_of(server->workspaces.all.next, first_workspace, link);
while (workspace_link != &server->workspaces.all) {
struct workspace *workspace = wl_container_of(
workspace_link, workspace, link);
while (actual_workspace_link != &server->workspaces.all) {
struct workspace *actual_workspace = wl_container_of(
actual_workspace_link, actual_workspace, link);
wlr_log(WLR_DEBUG, "Destroying workspace \"%s\"",
workspace->name);
actual_workspace->name);
struct view *view;
wl_list_for_each(view, &server->views, link) {
if (view->workspace == workspace) {
if (view->workspace == actual_workspace) {
view_move_to_workspace(view, first_workspace);
}
}
if (server->workspaces.current == workspace) {
if (server->workspaces.current == actual_workspace) {
workspaces_switch_to(first_workspace,
/* update_focus */ true);
}
if (server->workspaces.last == workspace) {
if (server->workspaces.last == actual_workspace) {
server->workspaces.last = first_workspace;
}
workspace_link = workspace_link->next;
destroy_workspace(workspace);
actual_workspace_link = actual_workspace_link->next;
destroy_workspace(actual_workspace);
}
}

View file

@ -225,7 +225,8 @@ 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);
view_maximize(view, VIEW_AXIS_BOTH,
/*store_natural_geometry*/ true);
}
return;
}
@ -500,11 +501,12 @@ handle_request_maximize(struct wl_listener *listener, void *data)
return;
}
if (!view->mapped && !output_is_usable(view->output)) {
if (!view->mapped && !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);
view_maximize(view, maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE,
/*store_natural_geometry*/ true);
}
static void
@ -521,7 +523,7 @@ handle_request_fullscreen(struct wl_listener *listener, void *data)
return;
}
if (!view->mapped && !output_is_usable(view->output)) {
if (!view->mapped && !view->output) {
view_set_output(view, output_nearest_to_cursor(view->server));
}
set_fullscreen_from_request(view,
@ -819,7 +821,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 (!output_is_usable(view->output)) {
if (!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 (!output_is_usable(view->output)) {
if (!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);
view_maximize(view, maximize, /*store_natural_geometry*/ true);
}
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);
view_maximize(view, axis, /*store_natural_geometry*/ true);
/*
* We could also call set_initial_position() here, but it's not
* really necessary until the view is actually mapped (and at
@ -946,14 +946,6 @@ 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