diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2876ac9..82139d61 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ on: - 'src/**' - 'include/**' - 'protocols/**' + - 'clients/**' - 'scripts/**' - '.github/workflows/**' @@ -93,6 +94,7 @@ jobs: apt-get install -y git gcc clang gdb xwayland apt-get build-dep -y labwc apt-get build-dep -y libwlroots-0.19-dev + apt-get build-dep -y libxkbcommon-dev - name: Install FreeBSD dependencies if: matrix.name == 'FreeBSD' @@ -119,7 +121,7 @@ jobs: xbps-install -y git meson gcc clang pkg-config scdoc \ cairo-devel glib-devel libpng-devel librsvg-devel libxml2-devel \ pango-devel wlroots0.19-devel gdb bash xorg-server-xwayland \ - dejavu-fonts-ttf libsfdo-devel foot + dejavu-fonts-ttf libsfdo-devel foot hwids # These builds are executed on all runners - name: Build with gcc diff --git a/NEWS.md b/NEWS.md index 4216dad6..1ae97c51 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog] | Date | All Changes | wlroots version | lines-of-code | |------------|---------------|-----------------|---------------| -| 2025-09-15 | [unreleased] | 0.19.0 | 28686 | +| 2025-10-10 | [0.9.2] | 0.19.1 | 28818 | | 2025-08-02 | [0.9.1] | 0.19.0 | 28605 | | 2025-07-11 | [0.9.0] | 0.19.0 | 28586 | | 2025-05-02 | [0.8.4] | 0.18.2 | 27679 | @@ -39,6 +39,7 @@ The format is based on [Keep a Changelog] | 2021-03-05 | [0.1.0] | 0.12.0 | 4627 | [unreleased]: NEWS.md#unreleased +[0.9.2]: NEWS.md#092---2025-10-10 [0.9.1]: NEWS.md#091---2025-08-02 [0.9.0]: NEWS.md#090---2025-07-11 [0.8.4]: NEWS.md#084---2025-05-02 @@ -100,23 +101,35 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: around a bug on the wlroots side which is expected to be fixed in wlroots `0.19.1` [#2887] +With wlroots compiled with libwayland (>= 1.24.0), there is an invisible margin +preventing pointer focus on some layer-shell surfaces including those created by +Gtk. In simple words, this is because libwayland now rounds floats a bit +differently [#3099]. There is a pending fix [wlroots-5159]. + [wlroots-4878]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4878 [wlroots-5098]:https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5098 +[wlroots-5159]: https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5159 [gtk-8792]: https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8792 ## unreleased [unreleased-commits] +## 0.9.2 - 2025-10-10 + +[0.9.2-commits] + ### Added +- Allow `SnapToEdge` and `ToggleSnapToEdge` to combine two cardinal directions + with the config option `combine="yes|no"`. [#3081] @tokyo4j - Support `Border` context for mousebinds as an alias for `Top`...`BRCorner` to make configuration easier. @tokyo4j [#3047] - Add window-switcher mode with thumbnails. This can be enabled with: ``. @tokyo4j [#2981] - Add `toggle` option to `GoToDesktop` action. This has the effect of going back to the last desktop if already on the target. @RainerKuemmerle [#3024] -- Add `` to allow hiding titlebar +- Add `` to allow hiding titlebar when window is maximized. @CosmicFusion @tokyo4j [#3015] - Use client-send-to-menu as 'Workspace' submenu in built-in client-menu @johanmalm [#2995] @@ -126,6 +139,7 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: [#2994] - Add `labnag` (a dialog client with message and buttons) and associated `` option in 'If' actions. @johanmalm @Consolatis @tokyo4j [#2699] +- Support config option `` @johanmalm [#3097] - Allow snapping to corner edges during interactive move with associated config options ``. @tokyo4j [#2885] - Support new values "up-left", "up-right", "down-left" and "down-right" with @@ -147,6 +161,7 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: ### Fixed +- On detecting broken icon theme, fall back on 'hicolor' @Consolatis [#3126] - Restore initially-maximized window position after unplug/plug @tokyo4j [#3042] - Fix large client-side icon not being loaded when the rendered icon size is larger than icon sizes from the client. @tokyo4j [#3033] @@ -162,6 +177,27 @@ There are some regression warnings worth noting for the switch to wlroots 0.19: ### Changed +- Change default keybind `W-` to combine cardinal directions to support + resizing of windows to fill a quarter of an output. This only affects users + who do not use an `rc.xml` (thereby using default keybinds) or use the + `` option. Previous behavior can be restored by setting + `combine="no"` as shown below. [#3081] @tokyo4j + +``` + + + + + + + + + + + + +``` + - `Focus` and `Raise` on window border press because it is probably what most people expect and it makes the behavior consistent with that of Openbox. @johanmalm [#3039] [#3049] @@ -2337,7 +2373,8 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 ShowMenu [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ -[unreleased-commits]: https://github.com/labwc/labwc/compare/0.9.0...HEAD +[unreleased-commits]: https://github.com/labwc/labwc/compare/0.9.2...HEAD +[0.9.2-commits]: https://github.com/labwc/labwc/compare/0.9.1...0.9.2 [0.9.1-commits]: https://github.com/labwc/labwc/compare/0.9.0...0.9.1 [0.9.0-commits]: https://github.com/labwc/labwc/compare/0.8.4...0.9.0 [0.8.4-commits]: https://github.com/labwc/labwc/compare/0.8.3...0.8.4 @@ -2800,3 +2837,7 @@ Compile with wlroots 0.12.0 and wayland-server >=1.16 [#3043]: https://github.com/labwc/labwc/pull/3043 [#3047]: https://github.com/labwc/labwc/pull/3047 [#3049]: https://github.com/labwc/labwc/pull/3049 +[#3081]: https://github.com/labwc/labwc/pull/3081 +[#3097]: https://github.com/labwc/labwc/pull/3097 +[#3099]: https://github.com/labwc/labwc/pull/3099 +[#3126]: https://github.com/labwc/labwc/pull/3126 diff --git a/clients/labnag.c b/clients/labnag.c index 23e0be1f..31ac4e9d 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -19,6 +19,7 @@ #ifdef __FreeBSD__ #include /* For signalfd() */ #endif +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include "action-prompt-codes.h" #include "pool-buffer.h" #include "cursor-shape-v1-client-protocol.h" @@ -38,6 +40,7 @@ struct conf { char *output; uint32_t anchors; int32_t layer; /* enum zwlr_layer_shell_v1_layer or -1 if unset */ + enum zwlr_layer_surface_v1_keyboard_interactivity keyboard_focus; /* Colors */ uint32_t button_text; @@ -69,11 +72,18 @@ struct pointer { int y; }; +struct keyboard { + struct wl_keyboard *keyboard; + struct xkb_keymap *keymap; + struct xkb_state *state; +}; + struct seat { struct wl_seat *wl_seat; uint32_t wl_name; struct nag *nag; struct pointer pointer; + struct keyboard keyboard; struct wl_list link; /* nag.seats */ }; @@ -130,6 +140,7 @@ struct nag { struct conf *conf; char *message; struct wl_list buttons; + int selected_button; struct pollfd pollfds[NR_FDS]; struct { @@ -409,7 +420,8 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) } static uint32_t -render_button(cairo_t *cairo, struct nag *nag, struct button *button, int *x) +render_button(cairo_t *cairo, struct nag *nag, struct button *button, + bool selected, int *x) { int text_width, text_height; get_text_size(cairo, nag->conf->font_description, &text_width, @@ -439,6 +451,14 @@ render_button(cairo_t *cairo, struct nag *nag, struct button *button, int *x) button->width, button->height); cairo_fill(cairo); + if (selected) { + cairo_set_source_u32(cairo, nag->conf->button_border); + cairo_set_line_width(cairo, 1); + cairo_rectangle(cairo, button->x + 1.5, button->y + 1.5, + button->width - 3, button->height - 3); + cairo_stroke(cairo); + } + cairo_set_source_u32(cairo, nag->conf->button_text); cairo_move_to(cairo, button->x + padding, button->y + padding); render_text(cairo, nag->conf->font_description, 1, true, @@ -464,11 +484,13 @@ render_to_cairo(cairo_t *cairo, struct nag *nag) int x = nag->width - nag->conf->button_margin_right; x -= nag->conf->button_gap_close; + int idx = 0; struct button *button; wl_list_for_each(button, &nag->buttons, link) { - h = render_button(cairo, nag, button, &x); + h = render_button(cairo, nag, button, idx == nag->selected_button, &x); max_height = h > max_height ? h : max_height; x -= nag->conf->button_gap; + idx++; } if (nag->details.visible) { @@ -555,6 +577,15 @@ seat_destroy(struct seat *seat) if (seat->pointer.pointer) { wl_pointer_destroy(seat->pointer.pointer); } + if (seat->keyboard.keyboard) { + wl_keyboard_destroy(seat->keyboard.keyboard); + } + if (seat->keyboard.keymap) { + xkb_keymap_unref(seat->keyboard.keymap); + } + if (seat->keyboard.state) { + xkb_state_unref(seat->keyboard.state); + } wl_seat_destroy(seat->wl_seat); wl_list_remove(&seat->link); free(seat); @@ -939,12 +970,170 @@ static const struct wl_pointer_listener pointer_listener = { .axis_discrete = wl_pointer_axis_discrete, }; +static void +wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) +{ + struct seat *seat = data; + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + wlr_log(WLR_ERROR, "unreconizable keymap format: %d", format); + close(fd); + return; + } + + char *map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map_shm == MAP_FAILED) { + wlr_log_errno(WLR_ERROR, "mmap()"); + close(fd); + return; + } + + if (seat->keyboard.keymap) { + xkb_keymap_unref(seat->keyboard.keymap); + seat->keyboard.keymap = NULL; + } + if (seat->keyboard.state) { + xkb_state_unref(seat->keyboard.state); + seat->keyboard.state = NULL; + } + struct xkb_context *xkb = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + seat->keyboard.keymap = xkb_keymap_new_from_string(xkb, map_shm, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (seat->keyboard.keymap) { + seat->keyboard.state = xkb_state_new(seat->keyboard.keymap); + } else { + wlr_log(WLR_ERROR, "failed to compile keymap"); + } + xkb_context_unref(xkb); + + munmap(map_shm, size); + close(fd); +} + +static void +wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + struct wl_surface *surface, struct wl_array *keys) +{ +} + +static void +wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + struct wl_surface *surface) +{ +} + +static void +wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, + uint32_t time, uint32_t key, uint32_t state) +{ + struct seat *seat = data; + struct nag *nag = seat->nag; + + if (!seat->keyboard.keymap || !seat->keyboard.state) { + wlr_log(WLR_ERROR, "keymap/state unavailable"); + return; + } + + if (state != WL_KEYBOARD_KEY_STATE_PRESSED) { + return; + } + + key += 8; + const xkb_keysym_t *syms; + if (!xkb_keymap_key_get_syms_by_level(seat->keyboard.keymap, + key, 0, 0, &syms)) { + wlr_log(WLR_ERROR, "failed to translate key: %d", key); + return; + } + xkb_mod_mask_t mods = xkb_state_serialize_mods(seat->keyboard.state, + XKB_STATE_MODS_EFFECTIVE); + xkb_mod_index_t shift_idx = xkb_keymap_mod_get_index( + seat->keyboard.keymap, XKB_MOD_NAME_SHIFT); + bool shift = shift_idx != XKB_MOD_INVALID && (mods & (1 << shift_idx)); + + int nr_buttons = wl_list_length(&nag->buttons); + + switch (syms[0]) { + case XKB_KEY_Left: + case XKB_KEY_Right: + case XKB_KEY_Tab: { + if (nr_buttons <= 0) { + break; + } + int direction; + if (syms[0] == XKB_KEY_Left || (syms[0] == XKB_KEY_Tab && shift)) { + direction = 1; + } else { + direction = -1; + } + nag->selected_button += nr_buttons + direction; + nag->selected_button %= nr_buttons; + render_frame(nag); + close_pollfd(&nag->pollfds[FD_TIMER]); + break; + } + case XKB_KEY_Escape: + exit_status = LAB_EXIT_CANCELLED; + nag->run_display = false; + break; + case XKB_KEY_Return: + case XKB_KEY_KP_Enter: { + int idx = 0; + struct button *button; + wl_list_for_each(button, &nag->buttons, link) { + if (idx == nag->selected_button) { + button_execute(nag, button); + close_pollfd(&nag->pollfds[FD_TIMER]); + exit_status = idx; + break; + } + idx++; + } + break; + } + } +} + +static void +wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, + uint32_t mods_locked, uint32_t group) +{ + struct seat *seat = data; + + if (!seat->keyboard.state) { + wlr_log(WLR_ERROR, "xkb state unavailable"); + return; + } + + xkb_state_update_mask(seat->keyboard.state, mods_depressed, + mods_latched, mods_locked, 0, 0, group); +} + +static void +wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, + int32_t rate, int32_t delay) +{ +} + +static const struct wl_keyboard_listener keyboard_listener = { + .keymap = wl_keyboard_keymap, + .enter = wl_keyboard_enter, + .leave = wl_keyboard_leave, + .key = wl_keyboard_key, + .modifiers = wl_keyboard_modifiers, + .repeat_info = wl_keyboard_repeat_info, +}; + static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, enum wl_seat_capability caps) { struct seat *seat = data; bool cap_pointer = caps & WL_SEAT_CAPABILITY_POINTER; + bool cap_keyboard = caps & WL_SEAT_CAPABILITY_KEYBOARD; + if (cap_pointer && !seat->pointer.pointer) { seat->pointer.pointer = wl_seat_get_pointer(wl_seat); wl_pointer_add_listener(seat->pointer.pointer, @@ -953,6 +1142,15 @@ seat_handle_capabilities(void *data, struct wl_seat *wl_seat, wl_pointer_destroy(seat->pointer.pointer); seat->pointer.pointer = NULL; } + + if (cap_keyboard && !seat->keyboard.keyboard) { + seat->keyboard.keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(seat->keyboard.keyboard, + &keyboard_listener, seat); + } else if (!cap_keyboard && seat->keyboard.keyboard) { + wl_keyboard_destroy(seat->keyboard.keyboard); + seat->keyboard.keyboard = NULL; + } } static void @@ -1075,7 +1273,7 @@ handle_global(void *data, struct wl_registry *registry, uint32_t name, } } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { nag->layer_shell = wl_registry_bind( - registry, name, &zwlr_layer_shell_v1_interface, 1); + registry, name, &zwlr_layer_shell_v1_interface, 4); } else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { nag->cursor_shape_manager = wl_registry_bind( registry, name, &wp_cursor_shape_manager_v1_interface, 1); @@ -1170,6 +1368,8 @@ nag_setup(struct nag *nag) &layer_surface_listener, nag); zwlr_layer_surface_v1_set_anchor(nag->layer_surface, nag->conf->anchors); + zwlr_layer_surface_v1_set_keyboard_interactivity(nag->layer_surface, + nag->conf->keyboard_focus); wl_registry_destroy(registry); @@ -1233,7 +1433,7 @@ nag_run(struct nag *nag) wl_display_cancel_read(nag->display); } if (nag->pollfds[FD_TIMER].revents & POLLIN) { - exit_status = LAB_EXIT_TIMEOUT; + exit_status = LAB_EXIT_CANCELLED; break; } if (nag->pollfds[FD_SIGNAL].revents & POLLIN) { @@ -1250,13 +1450,7 @@ conf_init(struct conf *conf) | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; conf->layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; - conf->button_background = 0x333333FF; - conf->details_background = 0x333333FF; - conf->background = 0x323232FF; - conf->text = 0xFFFFFFFF; - conf->button_text = 0xFFFFFFFF; - conf->button_border = 0x222222FF; - conf->border_bottom = 0x444444FF; + conf->keyboard_focus = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; conf->bar_border_thickness = 2; conf->message_padding = 8; conf->details_border_thickness = 3; @@ -1364,6 +1558,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, {"debug", no_argument, NULL, 'd'}, {"edge", required_argument, NULL, 'e'}, {"layer", required_argument, NULL, 'y'}, + {"keyboard-focus", required_argument, NULL, 'k'}, {"font", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"detailed-message", no_argument, NULL, 'l'}, @@ -1402,6 +1597,8 @@ nag_parse_options(int argc, char **argv, struct nag *nag, " -e, --edge top|bottom Set the edge to use.\n" " -y, --layer overlay|top|bottom|background\n" " Set the layer to use.\n" + " -k, --keyboard-focus none|exclusive|on-demand|\n" + " Set the policy for keyboard focus.\n" " -f, --font Set the font to use.\n" " -h, --help Show help message and quit.\n" " -l, --detailed-message Read a detailed message from stdin.\n" @@ -1433,7 +1630,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, optind = 1; while (1) { - int c = getopt_long(argc, argv, "B:Z:c:de:y:f:hlL:m:o:s:t:vx", opts, NULL); + int c = getopt_long(argc, argv, "B:Z:c:de:y:k:f:hlL:m:o:s:t:vx", opts, NULL); if (c == -1) { break; } @@ -1487,6 +1684,23 @@ nag_parse_options(int argc, char **argv, struct nag *nag, return LAB_EXIT_FAILURE; } break; + case 'k': + if (strcmp(optarg, "none") == 0) { + conf->keyboard_focus = + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; + } else if (strcmp(optarg, "exclusive") == 0) { + conf->keyboard_focus = + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE; + } else if (strcmp(optarg, "on-demand") == 0) { + conf->keyboard_focus = + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND; + } else { + fprintf(stderr, "Invalid keyboard focus: %s\n" + "Usage: --keyboard-focus none|exclusive|on-demand\n", + optarg); + return LAB_EXIT_FAILURE; + } + break; case 'f': /* Font */ pango_font_description_free(conf->font_description); conf->font_description = pango_font_description_from_string(optarg); @@ -1629,6 +1843,14 @@ main(int argc, char **argv) wl_list_insert(nag.buttons.prev, &nag.details.button_details->link); } + int nr_buttons = wl_list_length(&nag.buttons); + if (conf.keyboard_focus && nr_buttons > 0) { + /* select the leftmost button */ + nag.selected_button = nr_buttons - 1; + } else { + nag.selected_button = -1; + } + wlr_log(WLR_DEBUG, "Output: %s", nag.conf->output); wlr_log(WLR_DEBUG, "Anchors: %lu", (unsigned long)nag.conf->anchors); wlr_log(WLR_DEBUG, "Message: %s", nag.message); diff --git a/clients/meson.build b/clients/meson.build index c131915d..467bc035 100644 --- a/clients/meson.build +++ b/clients/meson.build @@ -49,9 +49,10 @@ executable( wlroots, server_protos, epoll_dep, + xkbcommon, ], include_directories: [labwc_inc], - install: true + install: true, ) clients = files('lab-sensible-terminal') diff --git a/docs/labnag.1.scd b/docs/labnag.1.scd index c8aa4d09..a2a36c10 100644 --- a/docs/labnag.1.scd +++ b/docs/labnag.1.scd @@ -31,6 +31,9 @@ _labnag_ [options...] *-y, --layer* overlay|top|bottom|background Set the layer to use. +*-k, --keyboard-focus none|exclusive|on-demand* + Set the policy for keyboard focus. + *-f, --font* Set the font to use. diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index d389c4cf..ca167b62 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -171,7 +171,6 @@ this is for compatibility with Openbox. ``` server - titlebar 0 no no @@ -188,11 +187,6 @@ this is for compatibility with Openbox. that it is not always possible to turn off client side decorations. Default is server. -** [titlebar|none] - Specify how server side decorations are shown for maximized windows. - *titlebar* shows titlebar above a maximized window. *none* shows no server - side decorations around a maximized window. Default is titlebar. - ** The distance in pixels between windows and output edges when using movement actions, for example MoveToEdge. Default is 0. @@ -291,6 +285,7 @@ this is for compatibility with Openbox. --button-text-color '%t' \\ --border-bottom-size 1 \\ --button-border-size 3 \\ + --keyboard-focus on-demand \\ --timeout 0 ``` @@ -352,7 +347,7 @@ this is for compatibility with Openbox. ``` -** +** *show* [yes|no] Draw the OnScreenDisplay when switching between windows. Default is yes. @@ -370,6 +365,9 @@ this is for compatibility with Openbox. 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. + ** Define window switcher fields when using **. @@ -403,9 +401,9 @@ this is for compatibility with Openbox. fields are: - 'B' - shell type, values [xwayland|xdg-shell] - 'b' - shell type (short form), values [X|W] - - 'S' - state of window, values [M|m|F] (3 spaces allocated) - (maximized, minimized, fullscreen) - - 's' - state of window (short form), values [M|m|F] (1 space) + - 'S' - state of window, values [m|s|M|F] (4 spaces allocated) + (minimized, shaded, maximized, fullscreen) + - 's' - state of window (short form), values [m|s|M|F] (1 space) - 'I' - wm-class/app-id - 'i' - wm-class/app-id trimmed, remove "org." if available - 'n' - desktop entry/file application name, falls back to @@ -603,6 +601,11 @@ extending outward from the snapped edge. Even when disabling server side decorations via ToggleDecorations, keep a small border (and resize area) around the window. Default is yes. +** [titlebar|none] + Specify how server side decorations are shown for maximized windows. + *titlebar* shows titlebar above a maximized window. *none* shows no server + side decorations around a maximized window. Default is titlebar. + ** [yes|no] Should drop-shadows be rendered behind windows. Default is no. diff --git a/docs/labwc-theme.5.scd b/docs/labwc-theme.5.scd index efc3a1a5..8097615b 100644 --- a/docs/labwc-theme.5.scd +++ b/docs/labwc-theme.5.scd @@ -327,6 +327,14 @@ all are supported. Border width of the selection box in the window switcher in pixels. Default is 2. +*osd.window-switcher.style-classic.item.active.border.color* + Border color around the selected window switcher item. + Default is *osd.label.text.color* with 50% opacity. + +*osd.window-switcher.style-classic.item.active.bg.color* + Background color of the selected window switcher item. + Default is *osd.label.text.color* with 15% opacity. + *osd.window-switcher.style-classic.item.icon.size* Size of the icon in window switcher, in pixels. If not set, the font size derived from @@ -358,10 +366,12 @@ all are supported. Border width of selected window switcher items in pixels. Default is 2. *osd.window-switcher.style-thumbnail.item.active.border.color* - Color of border around selected window switcher items. Default is #589bda. + Color of border around selected window switcher items. + Default is *osd.label.text.color* with 50% opacity. *osd.window-switcher.style-thumbnail.item.active.bg.color* - Color of selected window switcher items. Default is #c7e2fc. + Color of selected window switcher items. + Default is *osd.label.text.color* with 15% opacity. *osd.window-switcher.style-thumbnail.item.icon.size* Size of window icons in window switcher items in pixels. Default is 60. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index ed80fdf0..1dd08c4d 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -11,7 +11,6 @@ server - titlebar 0 no no @@ -45,6 +44,7 @@ 8 yes + titlebar no no @@ -79,7 +79,8 @@ - + diff --git a/docs/themerc b/docs/themerc index 7b251a3c..9139170c 100644 --- a/docs/themerc +++ b/docs/themerc @@ -97,6 +97,8 @@ osd.window-switcher.style-classic.padding: 4 osd.window-switcher.style-classic.item.padding.x: 10 osd.window-switcher.style-classic.item.padding.y: 1 osd.window-switcher.style-classic.item.active.border.width: 2 +osd.window-switcher.style-classic.item.active.border.color: #706f6d +osd.window-switcher.style-classic.item.active.bg.color: #bfbcba # The icon size the same as the font size by default # osd.window-switcher.style-classic.item.icon.size: 50 @@ -106,8 +108,8 @@ osd.window-switcher.style-thumbnail.item.width: 300 osd.window-switcher.style-thumbnail.item.height: 250 osd.window-switcher.style-thumbnail.item.padding: 10 osd.window-switcher.style-thumbnail.item.active.border.width: 2 -osd.window-switcher.style-thumbnail.item.active.border.color: #589bda -osd.window-switcher.style-thumbnail.item.active.bg.color: #c7e2fc +osd.window-switcher.style-thumbnail.item.active.border.color: #706f6d +osd.window-switcher.style-thumbnail.item.active.bg.color: #bfbcba osd.window-switcher.style-thumbnail.item.icon.size: 60 osd.window-switcher.preview.border.width: 1 diff --git a/include/action-prompt-codes.h b/include/action-prompt-codes.h index d1ca0f05..68d1b066 100644 --- a/include/action-prompt-codes.h +++ b/include/action-prompt-codes.h @@ -3,7 +3,7 @@ #define LABWC_ACTION_PROMPT_CODES_H #define LAB_EXIT_FAILURE 255 -#define LAB_EXIT_TIMEOUT 254 +#define LAB_EXIT_CANCELLED 254 #define LAB_EXIT_SUCCESS 0 #endif /* LABWC_ACTION_PROMPT_CODES_H */ diff --git a/include/action-prompt-command.h b/include/action-prompt-command.h deleted file mode 100644 index 219896b9..00000000 --- a/include/action-prompt-command.h +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -#ifndef LABWC_ACTION_PROMPT_COMMAND_H -#define LABWC_ACTION_PROMPT_COMMAND_H - -struct buf; -struct action; -struct theme; - -void action_prompt_command(struct buf *buf, const char *format, - struct action *action, struct theme *theme); - -#endif /* LABWC_ACTION_PROMPT_COMMAND_H */ diff --git a/include/common/buf.h b/include/common/buf.h index 24158630..1298cac2 100644 --- a/include/common/buf.h +++ b/include/common/buf.h @@ -109,4 +109,11 @@ void buf_reset(struct buf *s); */ void buf_move(struct buf *dst, struct buf *src); +/** + * buf_from_file - read file into memory buffer + * @filename: file to read + * Free returned buffer with buf_reset(). + */ +struct buf buf_from_file(const char *filename); + #endif /* LABWC_BUF_H */ diff --git a/include/common/grab-file.h b/include/common/grab-file.h deleted file mode 100644 index 6a6de56d..00000000 --- a/include/common/grab-file.h +++ /dev/null @@ -1,20 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Read file into memory - * - * Copyright Johan Malm 2020 - */ - -#ifndef LABWC_GRAB_FILE_H -#define LABWC_GRAB_FILE_H - -#include "common/buf.h" - -/** - * grab_file - read file into memory buffer - * @filename: file to read - * Free returned buffer with buf_reset(). - */ -struct buf grab_file(const char *filename); - -#endif /* LABWC_GRAB_FILE_H */ diff --git a/include/config/rcxml.h b/include/config/rcxml.h index e4fd184b..50874571 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -179,6 +179,7 @@ struct rcxml { bool show; bool preview; bool outlines; + bool unshade; enum lab_view_criteria criteria; struct wl_list fields; /* struct window_switcher_field.link */ enum window_switcher_style style; diff --git a/include/labwc.h b/include/labwc.h index 5a3bbefa..735f57b8 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -303,6 +303,7 @@ struct server { /* Set when in cycle (alt-tab) mode */ struct osd_state { struct view *cycle_view; + bool preview_was_shaded; bool preview_was_enabled; struct wlr_scene_node *preview_node; struct wlr_scene_tree *preview_parent; diff --git a/include/theme.h b/include/theme.h index 797b5072..58ea3e13 100644 --- a/include/theme.h +++ b/include/theme.h @@ -83,7 +83,7 @@ struct theme { /* * Themes/textures for each active/inactive window. Indexed by - * THEME_INACTIVE and THEME_ACTIVE. + * ssd_active_state. */ struct { /* title background pattern (solid or gradient) */ @@ -170,6 +170,8 @@ struct theme { int item_padding_x; int item_padding_y; int item_active_border_width; + float item_active_border_color[4]; + float item_active_bg_color[4]; int item_icon_size; bool width_is_percent; @@ -210,10 +212,6 @@ struct theme { int mag_border_width; }; -/* TODO: replace with enum ssd_active_state */ -#define THEME_INACTIVE 0 -#define THEME_ACTIVE 1 - struct server; /** diff --git a/include/view.h b/include/view.h index ba662550..a8456de2 100644 --- a/include/view.h +++ b/include/view.h @@ -106,7 +106,6 @@ struct view_size_hints { struct view_impl { void (*configure)(struct view *view, struct wlr_box geo); void (*close)(struct view *view); - const char *(*get_string_prop)(struct view *view, const char *prop); void (*map)(struct view *view); void (*set_activated)(struct view *view, bool activated); void (*set_fullscreen)(struct view *view, bool fullscreen); @@ -173,6 +172,10 @@ struct view { struct wlr_scene_tree *scene_tree; struct wlr_scene_tree *content_tree; + /* These are never NULL and an empty string is set instead. */ + char *title; + char *app_id; /* WM_CLASS for xwayland windows */ + bool mapped; bool been_mapped; enum lab_ssd_mode ssd_mode; @@ -571,9 +574,8 @@ bool view_on_output(struct view *view, struct output *output); */ bool view_has_strut_partial(struct view *view); -const char *view_get_string_prop(struct view *view, const char *prop); -void view_update_title(struct view *view); -void view_update_app_id(struct view *view); +void view_set_title(struct view *view, const char *title); +void view_set_app_id(struct view *view, const char *app_id); void view_reload_ssd(struct view *view); void view_set_shade(struct view *view, bool shaded); diff --git a/meson.build b/meson.build index 0ac5cea1..ba3a2148 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project( 'labwc', 'c', - version: '0.9.0', + version: '0.9.2', license: 'GPL-2.0-only', meson_version: '>=0.59.0', default_options: [ @@ -51,9 +51,9 @@ endif add_project_arguments('-DLABWC_VERSION=@0@'.format(version), language: 'c') wlroots = dependency( - 'wlroots-0.19', + 'wlroots-0.20', default_options: ['default_library=static', 'examples=false'], - version: ['>=0.19.0', '<0.20.0'], + version: ['>=0.20.0', '<0.21.0'], ) wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true' @@ -127,7 +127,9 @@ conf_data.set10('HAVE_RSVG', have_rsvg) conf_data.set10('HAVE_LIBSFDO', have_libsfdo) foreach sym : ['LIBINPUT_CONFIG_DRAG_LOCK_ENABLED_STICKY', 'LIBINPUT_CONFIG_3FG_DRAG_ENABLED_3FG'] - conf_data.set10('HAVE_' + sym, cc.has_header_symbol('libinput.h', sym, dependencies: input)) + has_sym = input.type_name() != 'internal' \ + and cc.has_header_symbol('libinput.h', sym, dependencies: input) + conf_data.set10('HAVE_' + sym, has_sym) endforeach if get_option('static_analyzer').enabled() diff --git a/po/ca.po b/po/ca.po index f9c6975e..7bc442bc 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,9 +8,10 @@ msgstr "" "Project-Id-Version: labwc\n" "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" "POT-Creation-Date: 2024-09-19 21:09+1000\n" -"PO-Revision-Date: 2025-03-29 11:25+0000\n" -"Last-Translator: Davidmp \n" -"Language-Team: Catalan \n" +"PO-Revision-Date: 2025-10-11 20:01+0000\n" +"Last-Translator: alvaroelpob \n" +"Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -24,7 +25,7 @@ msgstr "Ves-hi..." #: src/menu/menu.c:1034 msgid "Terminal" -msgstr "" +msgstr "Terminal" #: src/menu/menu.c:1040 msgid "Reconfigure" diff --git a/po/cs.po b/po/cs.po index 27fe6a68..11ff25ce 100644 --- a/po/cs.po +++ b/po/cs.po @@ -8,29 +8,32 @@ 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: 2024-03-02 02:00+0100\n" -"Last-Translator: zenobit \n" -"Language-Team: Czech \n" +"PO-Revision-Date: 2025-10-11 20:01+0000\n" +"Last-Translator: p-bo \n" +"Language-Team: Czech \n" "Language: cs\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==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Weblate 4.2.1\n" #: src/menu/menu.c:1016 msgid "Go there..." -msgstr "" +msgstr "Přejít tam..." #: src/menu/menu.c:1034 msgid "Terminal" -msgstr "" +msgstr "Terminál" #: src/menu/menu.c:1040 msgid "Reconfigure" -msgstr "Překonfigurovat" +msgstr "Přenastavit" #: src/menu/menu.c:1042 msgid "Exit" -msgstr "Odejít" +msgstr "Ukončit" #: src/menu/menu.c:1056 msgid "Minimize" @@ -46,7 +49,7 @@ msgstr "Na celou obrazovku" #: src/menu/menu.c:1062 msgid "Roll Up/Down" -msgstr "Rolovat nahoru/dolů" +msgstr "Posouvat nahoru/dolů" #: src/menu/menu.c:1064 msgid "Decorations" @@ -66,11 +69,11 @@ msgstr "Posunout doprava" #: src/menu/menu.c:1083 msgid "Always on Visible Workspace" -msgstr "Vždy na viditelné Pracovní Ploše" +msgstr "Vždy na viditelné Pracovní ploše" #: src/menu/menu.c:1086 msgid "Workspace" -msgstr "Pracovní Plocha" +msgstr "Pracovní plocha" #: src/menu/menu.c:1089 msgid "Close" diff --git a/src/action-prompt-command.c b/src/action-prompt-command.c deleted file mode 100644 index faad41c9..00000000 --- a/src/action-prompt-command.c +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -#define _POSIX_C_SOURCE 200809L -#include "action-prompt-command.h" -#include -#include -#include "action.h" -#include "common/buf.h" -#include "theme.h" -#include "translate.h" - -enum { - LAB_PROMPT_NONE = 0, - LAB_PROMPT_MESSAGE, - LAB_PROMPT_NO, - LAB_PROMPT_YES, - LAB_PROMPT_BG_COL, - LAB_PROMPT_TEXT_COL, - - LAB_PROMPT_COUNT -}; - -typedef void field_conversion_type(struct buf *buf, struct action *action, - struct theme *theme); - -struct field_converter { - const char fmt_char; - field_conversion_type *fn; -}; - -/* %m */ -static void -set_message(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add(buf, action_get_str(action, "message.prompt", "Choose wisely")); -} - -/* %n */ -static void -set_no(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add(buf, _("No")); -} - -/* %y */ -static void -set_yes(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add(buf, _("Yes")); -} - -/* %b */ -static void -set_bg_col(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add_hex_color(buf, theme->osd_bg_color); -} - -/* %t */ -static void -set_text_col(struct buf *buf, struct action *action, struct theme *theme) -{ - buf_add_hex_color(buf, theme->osd_label_text_color); -} - -static const struct field_converter field_converter[LAB_PROMPT_COUNT] = { - [LAB_PROMPT_MESSAGE] = { 'm', set_message }, - [LAB_PROMPT_NO] = { 'n', set_no }, - [LAB_PROMPT_YES] = { 'y', set_yes }, - [LAB_PROMPT_BG_COL] = { 'b', set_bg_col }, - [LAB_PROMPT_TEXT_COL] = { 't', set_text_col }, -}; - -void -action_prompt_command(struct buf *buf, const char *format, - struct action *action, struct theme *theme) -{ - if (!format) { - wlr_log(WLR_ERROR, "missing format"); - return; - } - - for (const char *p = format; *p; p++) { - /* - * If we're not on a conversion specifier (like %m) then just - * keep adding it to the buffer - */ - if (*p != '%') { - buf_add_char(buf, *p); - continue; - } - - /* Process the %* conversion specifier */ - ++p; - - bool found = false; - for (unsigned char i = 0; i < LAB_PROMPT_COUNT; i++) { - if (*p == field_converter[i].fmt_char) { - field_converter[i].fn(buf, action, theme); - found = true; - break; - } - } - if (!found) { - wlr_log(WLR_ERROR, - "invalid prompt command conversion specifier '%c'", *p); - } - } -} diff --git a/src/action.c b/src/action.c index 9a5623ff..a351160d 100644 --- a/src/action.c +++ b/src/action.c @@ -10,7 +10,6 @@ #include #include #include "action-prompt-codes.h" -#include "action-prompt-command.h" #include "common/buf.h" #include "common/macros.h" #include "common/list.h" @@ -30,6 +29,7 @@ #include "regions.h" #include "ssd.h" #include "theme.h" +#include "translate.h" #include "view.h" #include "workspaces.h" @@ -831,11 +831,55 @@ handle_view_destroy(struct wl_listener *listener, void *data) prompt->view = NULL; } +static void +print_prompt_command(struct buf *buf, const char *format, + struct action *action, struct theme *theme) +{ + assert(format); + + for (const char *p = format; *p; p++) { + /* + * If we're not on a conversion specifier (like %m) then just + * keep adding it to the buffer + */ + if (*p != '%') { + buf_add_char(buf, *p); + continue; + } + + /* Process the %* conversion specifier */ + ++p; + + switch (*p) { + case 'm': + buf_add(buf, action_get_str(action, + "message.prompt", "Choose wisely")); + break; + case 'n': + buf_add(buf, _("No")); + break; + case 'y': + buf_add(buf, _("Yes")); + break; + case 'b': + buf_add_hex_color(buf, theme->osd_bg_color); + break; + case 't': + buf_add_hex_color(buf, theme->osd_label_text_color); + break; + default: + wlr_log(WLR_ERROR, + "invalid prompt command conversion specifier '%c'", *p); + break; + } + } +} + static void action_prompt_create(struct view *view, struct server *server, struct action *action) { struct buf command = BUF_INIT; - action_prompt_command(&command, rc.prompt_command, action, rc.theme); + print_prompt_command(&command, rc.prompt_command, action, rc.theme); wlr_log(WLR_INFO, "prompt command: '%s'", command.data); @@ -890,7 +934,7 @@ action_check_prompt_result(pid_t pid, int exit_code) if (exit_code == LAB_EXIT_SUCCESS) { wlr_log(WLR_INFO, "Selected the 'then' branch"); actions = action_get_actionlist(prompt->action, "then"); - } else if (exit_code == LAB_EXIT_TIMEOUT) { + } else if (exit_code == LAB_EXIT_CANCELLED) { /* no-op */ } else { wlr_log(WLR_INFO, "Selected the 'else' branch"); diff --git a/src/common/buf.c b/src/common/buf.c index bd8e82d0..c141b62e 100644 --- a/src/common/buf.c +++ b/src/common/buf.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "common/macros.h" #include "common/mem.h" #include "common/string-helpers.h" @@ -203,3 +204,37 @@ buf_move(struct buf *dst, struct buf *src) *dst = *src; *src = BUF_INIT; } + +struct buf +buf_from_file(const char *filename) +{ + struct buf buf = BUF_INIT; + FILE *stream = fopen(filename, "r"); + if (!stream) { + return buf; + } + + if (fseek(stream, 0, SEEK_END) == -1) { + wlr_log_errno(WLR_ERROR, "fseek(%s)", filename); + fclose(stream); + return buf; + } + long size = ftell(stream); + if (size == -1) { + wlr_log_errno(WLR_ERROR, "ftell(%s)", filename); + fclose(stream); + return buf; + } + rewind(stream); + + buf_expand(&buf, size + 1); + if (fread(buf.data, 1, size, stream) == (size_t)size) { + buf.len = size; + buf.data[size] = '\0'; + } else { + wlr_log_errno(WLR_ERROR, "fread(%s)", filename); + buf_reset(&buf); + } + fclose(stream); + return buf; +} diff --git a/src/common/grab-file.c b/src/common/grab-file.c deleted file mode 100644 index 9c90045c..00000000 --- a/src/common/grab-file.c +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Read file into memory - * - * Copyright Johan Malm 2020 - */ - -#define _POSIX_C_SOURCE 200809L -#include "common/grab-file.h" -#include "common/buf.h" - -#include -#include -#include - -struct buf -grab_file(const char *filename) -{ - char *line = NULL; - size_t len = 0; - FILE *stream = fopen(filename, "r"); - if (!stream) { - return BUF_INIT; - } - struct buf buffer = BUF_INIT; - while ((getline(&line, &len, stream) != -1)) { - char *p = strrchr(line, '\n'); - if (p) { - *p = '\0'; - } - buf_add(&buffer, line); - } - free(line); - fclose(stream); - return buffer; -} diff --git a/src/common/meson.build b/src/common/meson.build index 39b4d4b4..4cf52023 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -6,7 +6,6 @@ labwc_sources += files( 'fd-util.c', 'file-helpers.c', 'font.c', - 'grab-file.c', 'graphic-helpers.c', 'lab-scene-rect.c', 'match.c', diff --git a/src/common/xml.c b/src/common/xml.c index 7e089267..0185d9a3 100644 --- a/src/common/xml.c +++ b/src/common/xml.c @@ -48,7 +48,7 @@ create_attribute_tree(const xmlAttr *attr) } /* - * Consider . + * Consider . * These three attributes are represented by following trees. * action(dst)---name * action(src)---position---x @@ -79,7 +79,8 @@ merge_two_trees(xmlNode *dst, xmlNode *src) && !strcasecmp((char *)dst->name, (char *)src->name)) { xmlNode *next_dst = dst->last; xmlNode *next_src = src->children; - xmlAddChild(dst, src->children); + xmlUnlinkNode(next_src); + xmlAddChild(dst, next_src); xmlUnlinkNode(src); xmlFreeNode(src); src = next_src; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 1353b3c2..a966577c 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -1083,12 +1083,6 @@ entry(xmlNode *node, char *nodename, char *content) } else { rc.xdg_shell_server_side_deco = true; } - } else if (!strcasecmp(nodename, "maximizedDecoration.core")) { - if (!strcasecmp(content, "titlebar")) { - rc.hide_maximized_window_titlebar = false; - } else if (!strcasecmp(content, "none")) { - rc.hide_maximized_window_titlebar = true; - } } else if (!strcmp(nodename, "gap.core")) { rc.gap = atoi(content); } else if (!strcasecmp(nodename, "adaptiveSync.core")) { @@ -1130,6 +1124,12 @@ entry(xmlNode *node, char *nodename, char *content) rc.corner_radius = atoi(content); } else if (!strcasecmp(nodename, "keepBorder.theme")) { set_bool(content, &rc.ssd_keep_border); + } else if (!strcasecmp(nodename, "maximizedDecoration.theme")) { + if (!strcasecmp(content, "titlebar")) { + rc.hide_maximized_window_titlebar = false; + } else if (!strcasecmp(content, "none")) { + rc.hide_maximized_window_titlebar = true; + } } else if (!strcasecmp(nodename, "dropShadows.theme")) { set_bool(content, &rc.shadows_enabled); } else if (!strcasecmp(nodename, "dropShadowsOnTiled.theme")) { @@ -1217,6 +1217,8 @@ entry(xmlNode *node, char *nodename, char *content) rc.window_switcher.criteria &= ~LAB_VIEW_CRITERIA_CURRENT_WORKSPACE; } + } else if (!strcasecmp(nodename, "unshade.windowSwitcher")) { + set_bool(content, &rc.window_switcher.unshade); /* Remove this long term - just a friendly warning for now */ } else if (strstr(nodename, "windowswitcher.core")) { @@ -1429,6 +1431,7 @@ rcxml_init(void) rc.window_switcher.style = WINDOW_SWITCHER_CLASSIC; rc.window_switcher.preview = true; rc.window_switcher.outlines = true; + rc.window_switcher.unshade = true; rc.window_switcher.criteria = LAB_VIEW_CRITERIA_CURRENT_WORKSPACE | LAB_VIEW_CRITERIA_ROOT_TOPLEVEL | LAB_VIEW_CRITERIA_NO_SKIP_WINDOW_SWITCHER; @@ -1642,6 +1645,7 @@ post_processing(void) "--button-text-color '%t' " "--border-bottom-size 1 " "--button-border-size 3 " + "--keyboard-focus on-demand " "--timeout 0"); } if (!rc.fallback_app_icon_name) { @@ -1868,25 +1872,13 @@ rcxml_read(const char *filename) */ for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); - FILE *stream = fopen(path->string, "r"); - if (!stream) { + struct buf b = buf_from_file(path->string); + if (!b.len) { continue; } wlr_log(WLR_INFO, "read config file %s", path->string); - struct buf b = BUF_INIT; - char *line = NULL; - size_t len = 0; - while (getline(&line, &len, stream) != -1) { - char *p = strrchr(line, '\n'); - if (p) { - *p = '\0'; - } - buf_add(&b, line); - } - zfree(line); - fclose(stream); rcxml_parse_xml(&b); buf_reset(&b); if (!should_merge_config) { diff --git a/src/debug.c b/src/debug.c index 97c011f2..84b844d8 100644 --- a/src/debug.c +++ b/src/debug.c @@ -71,11 +71,10 @@ get_view_part(struct view *view, struct wlr_scene_node *node) return NULL; } if (node == &view->scene_tree->node) { - const char *app_id = view_get_string_prop(view, "app_id"); - if (string_null_or_empty(app_id)) { + if (string_null_or_empty(view->app_id)) { return "view"; } - snprintf(view_name, sizeof(view_name), "view (%s)", app_id); + snprintf(view_name, sizeof(view_name), "view (%s)", view->app_id); return view_name; } if (node == &view->content_tree->node) { diff --git a/src/desktop-entry.c b/src/desktop-entry.c index 7aefa77e..7617fa26 100644 --- a/src/desktop-entry.c +++ b/src/desktop-entry.c @@ -37,9 +37,10 @@ log_handler(enum sfdo_log_level level, const char *fmt, va_list args, void *tag) /* * To avoid logging issues with .desktop files as errors, all libsfdo - * error-logging is demoted to info level. + * error-logging is demoted to info level unless running with + * LABWC_DEBUG_LIBSFDO. */ - if (level == SFDO_LOG_LEVEL_ERROR) { + if (!debug_libsfdo && level == SFDO_LOG_LEVEL_ERROR) { level = SFDO_LOG_LEVEL_INFO; } @@ -95,19 +96,37 @@ desktop_entry_init(struct server *server) * We set some relaxed load options to accommodate delinquent themes in * the wild, namely: * - * - SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING to "impose less - * restrictions on the format of icon theme files" + * - SFDO_ICON_THEME_LOAD_OPTION_RELAXED to "impose less restrictions + * on the format of icon theme files" * - * - SFDO_ICON_THEME_LOAD_OPTION_RELAXED to "continue loading even if it - * fails to find a theme or one of its dependencies." + * - SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING to "continue loading + * even if it fails to find a theme or one of its dependencies." */ int load_options = SFDO_ICON_THEME_LOAD_OPTIONS_DEFAULT - | SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING - | SFDO_ICON_THEME_LOAD_OPTION_RELAXED; + | SFDO_ICON_THEME_LOAD_OPTION_RELAXED + | SFDO_ICON_THEME_LOAD_OPTION_ALLOW_MISSING; sfdo->icon_theme = sfdo_icon_theme_load( sfdo->icon_ctx, rc.icon_theme_name, load_options); + if (!sfdo->icon_theme) { + /* + * sfdo_icon_theme_load() falls back to hicolor theme with + * _ALLOW_MISSING flag when the theme is missing, but just + * fails when the theme is invalid. + * So manually call sfdo_icon_theme_load() again here. + */ + wlr_log(WLR_ERROR, "Failed to load icon theme %s, falling back to 'hicolor'", + rc.icon_theme_name); + + if (!debug_libsfdo) { + wlr_log(WLR_ERROR, "Further information is available by setting " + "the LABWC_DEBUG_LIBSFDO=1 env var before starting labwc"); + } + + sfdo->icon_theme = sfdo_icon_theme_load( + sfdo->icon_ctx, "hicolor", load_options); + } if (!sfdo->icon_theme) { goto err_icon_theme; } @@ -129,6 +148,10 @@ err_desktop_ctx: err_basedir_ctx: free(sfdo); wlr_log(WLR_ERROR, "Failed to initialize icon loader"); + if (!debug_libsfdo) { + wlr_log(WLR_ERROR, "Further information is available by setting " + "the LABWC_DEBUG_LIBSFDO=1 env var before starting labwc"); + } } void diff --git a/src/foreign-toplevel/ext-foreign.c b/src/foreign-toplevel/ext-foreign.c index 587193a6..748050fa 100644 --- a/src/foreign-toplevel/ext-foreign.c +++ b/src/foreign-toplevel/ext-foreign.c @@ -32,8 +32,8 @@ handle_new_app_id(struct wl_listener *listener, void *data) assert(ext_toplevel->handle); struct wlr_ext_foreign_toplevel_handle_v1_state state = { - .title = view_get_string_prop(ext_toplevel->view, "title"), - .app_id = view_get_string_prop(ext_toplevel->view, "app_id") + .title = ext_toplevel->view->title, + .app_id = ext_toplevel->view->app_id, }; wlr_ext_foreign_toplevel_handle_v1_update_state(ext_toplevel->handle, &state); @@ -47,8 +47,8 @@ handle_new_title(struct wl_listener *listener, void *data) assert(ext_toplevel->handle); struct wlr_ext_foreign_toplevel_handle_v1_state state = { - .title = view_get_string_prop(ext_toplevel->view, "title"), - .app_id = view_get_string_prop(ext_toplevel->view, "app_id") + .title = ext_toplevel->view->title, + .app_id = ext_toplevel->view->app_id, }; wlr_ext_foreign_toplevel_handle_v1_update_state(ext_toplevel->handle, &state); @@ -63,15 +63,15 @@ ext_foreign_toplevel_init(struct ext_foreign_toplevel *ext_toplevel, ext_toplevel->view = view; struct wlr_ext_foreign_toplevel_handle_v1_state state = { - .title = view_get_string_prop(view, "title"), - .app_id = view_get_string_prop(view, "app_id") + .title = view->title, + .app_id = view->app_id, }; ext_toplevel->handle = wlr_ext_foreign_toplevel_handle_v1_create( view->server->foreign_toplevel_list, &state); if (!ext_toplevel->handle) { wlr_log(WLR_ERROR, "cannot create ext toplevel handle for (%s)", - view_get_string_prop(view, "title")); + view->title); return; } diff --git a/src/foreign-toplevel/wlr-foreign.c b/src/foreign-toplevel/wlr-foreign.c index 9f0ed8e6..a84775f5 100644 --- a/src/foreign-toplevel/wlr-foreign.c +++ b/src/foreign-toplevel/wlr-foreign.c @@ -94,13 +94,8 @@ handle_new_app_id(struct wl_listener *listener, void *data) wl_container_of(listener, wlr_toplevel, on_view.new_app_id); assert(wlr_toplevel->handle); - const char *app_id = view_get_string_prop(wlr_toplevel->view, "app_id"); - const char *wlr_app_id = wlr_toplevel->handle->app_id; - if (app_id && wlr_app_id && !strcmp(app_id, wlr_app_id)) { - /* Don't send app_id if they are the same */ - return; - } - wlr_foreign_toplevel_handle_v1_set_app_id(wlr_toplevel->handle, app_id); + wlr_foreign_toplevel_handle_v1_set_app_id(wlr_toplevel->handle, + wlr_toplevel->view->app_id); } static void @@ -110,13 +105,8 @@ handle_new_title(struct wl_listener *listener, void *data) wl_container_of(listener, wlr_toplevel, on_view.new_title); assert(wlr_toplevel->handle); - const char *title = view_get_string_prop(wlr_toplevel->view, "title"); - const char *wlr_title = wlr_toplevel->handle->title; - if (title && wlr_title && !strcmp(title, wlr_title)) { - /* Don't send title if they are the same */ - return; - } - wlr_foreign_toplevel_handle_v1_set_title(wlr_toplevel->handle, title); + wlr_foreign_toplevel_handle_v1_set_title(wlr_toplevel->handle, + wlr_toplevel->view->title); } static void @@ -202,7 +192,7 @@ wlr_foreign_toplevel_init(struct wlr_foreign_toplevel *wlr_toplevel, view->server->foreign_toplevel_manager); if (!wlr_toplevel->handle) { wlr_log(WLR_ERROR, "cannot create wlr foreign toplevel handle for (%s)", - view_get_string_prop(view, "title")); + view->title); return; } diff --git a/src/img/img-xbm.c b/src/img/img-xbm.c index 6ecbbc9a..91269f64 100644 --- a/src/img/img-xbm.c +++ b/src/img/img-xbm.c @@ -12,7 +12,7 @@ #include #include #include -#include "common/grab-file.h" +#include "common/buf.h" #include "common/mem.h" #include "common/string-helpers.h" #include "buffer.h" @@ -273,7 +273,7 @@ img_xbm_load(const char *filename, float *rgba) uint32_t color = argb32(rgba); /* Read file into memory as it's easier to tokenize that way */ - struct buf token_buf = grab_file(filename); + struct buf token_buf = buf_from_file(filename); if (token_buf.len) { struct token *tokens = tokenize_xbm(token_buf.data); pixmap = parse_xbm_tokens(tokens, color); diff --git a/src/input/ime.c b/src/input/ime.c index 92d88ffe..efbe54d6 100644 --- a/src/input/ime.c +++ b/src/input/ime.c @@ -309,7 +309,8 @@ handle_keyboard_grab_destroy(struct wl_listener *listener, void *data) { struct input_method_relay *relay = wl_container_of(listener, relay, keyboard_grab_destroy); - struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = data; + struct wlr_input_method_keyboard_grab_v2 *keyboard_grab = + relay->input_method->keyboard_grab; assert(keyboard_grab); wl_list_remove(&relay->keyboard_grab_destroy.link); @@ -583,11 +584,11 @@ input_method_relay_create(struct seat *seat) relay->popup_tree = wlr_scene_tree_create(&seat->server->scene->tree); relay->new_text_input.notify = handle_new_text_input; - wl_signal_add(&seat->server->text_input_manager->events.text_input, + wl_signal_add(&seat->server->text_input_manager->events.new_text_input, &relay->new_text_input); relay->new_input_method.notify = handle_new_input_method; - wl_signal_add(&seat->server->input_method_manager->events.input_method, + wl_signal_add(&seat->server->input_method_manager->events.new_input_method, &relay->new_input_method); relay->focused_surface_destroy.notify = handle_focused_surface_destroy; diff --git a/src/input/keyboard.c b/src/input/keyboard.c index ae92e73f..8f7a3e81 100644 --- a/src/input/keyboard.c +++ b/src/input/keyboard.c @@ -96,6 +96,9 @@ end_cycling(struct server *server) /* FIXME: osd_finish() transiently sets focus to the old surface */ osd_finish(server); /* Note that server->osd_state.cycle_view is cleared at this point */ + if (rc.window_switcher.unshade) { + view_set_shade(cycle_view, false); + } desktop_focus_view(cycle_view, /*raise*/ true); } diff --git a/src/menu/menu.c b/src/menu/menu.c index 0e23e850..bc0fdcad 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -482,13 +482,13 @@ fill_item(struct menu *menu, xmlNode *node) goto out; } - struct menuitem *item = item_create(menu, (char *)label, icon_name, false); + struct menuitem *item = item_create(menu, label, icon_name, false); lab_xml_expand_dotted_attributes(node); append_parsed_actions(node, &item->actions); out: - free(label); - free(icon_name); + xmlFree(label); + xmlFree(icon_name); } static void @@ -619,10 +619,10 @@ fill_menu(struct server *server, struct menu *parent, xmlNode *n) item->submenu = menu; } error: - free(label); - free(icon_name); - free(execute); - free(id); + xmlFree(label); + xmlFree(icon_name); + xmlFree(execute); + xmlFree(id); } /* This can be one of and */ @@ -631,7 +631,7 @@ fill_separator(struct menu *menu, xmlNode *n) { char *label = (char *)xmlGetProp(n, (const xmlChar *)"label"); separator_create(menu, label); - free(label); + xmlFree(label); } /* parent==NULL when processing toplevel menus in menu.xml */ @@ -679,30 +679,6 @@ parse_buf(struct server *server, struct menu *parent, struct buf *buf) return true; } -/* - * @stream can come from either of the following: - * - fopen() in the case of reading a file such as menu.xml - * - popen() when processing pipemenus - */ -static void -parse_stream(struct server *server, FILE *stream) -{ - char *line = NULL; - size_t len = 0; - struct buf b = BUF_INIT; - - while (getline(&line, &len, stream) != -1) { - char *p = strrchr(line, '\n'); - if (p) { - *p = '\0'; - } - buf_add(&b, line); - } - free(line); - parse_buf(server, NULL, &b); - buf_reset(&b); -} - static void parse_xml(const char *filename, struct server *server) { @@ -715,13 +691,13 @@ parse_xml(const char *filename, struct server *server) for (struct wl_list *elm = iter(&paths); elm != &paths; elm = iter(elm)) { struct path *path = wl_container_of(elm, path, link); - FILE *stream = fopen(path->string, "r"); - if (!stream) { + struct buf buf = buf_from_file(path->string); + if (!buf.len) { continue; } wlr_log(WLR_INFO, "read menu file %s", path->string); - parse_stream(server, stream); - fclose(stream); + parse_buf(server, /*parent*/ NULL, &buf); + buf_reset(&buf); if (!should_merge_config) { break; } @@ -900,8 +876,8 @@ update_client_list_combined_menu(struct server *server) wl_list_for_each(view, &server->views, link) { if (view->workspace == workspace) { - const char *title = view_get_string_prop(view, "title"); - if (!view->foreign_toplevel || string_null_or_empty(title)) { + if (!view->foreign_toplevel + || string_null_or_empty(view->title)) { continue; } @@ -909,9 +885,9 @@ update_client_list_combined_menu(struct server *server) buf_add(&buffer, "*"); } if (view->minimized) { - buf_add_fmt(&buffer, "(%s)", title); + buf_add_fmt(&buffer, "(%s)", view->title); } else { - buf_add(&buffer, title); + buf_add(&buffer, view->title); } item = item_create(menu, buffer.data, NULL, /*show arrow*/ false); diff --git a/src/meson.build b/src/meson.build index dc760f0c..330b5daf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,5 @@ labwc_sources = files( 'action.c', - 'action-prompt-command.c', 'buffer.c', 'debug.c', 'desktop.c', diff --git a/src/osd/osd-classic.c b/src/osd/osd-classic.c index be740f2f..4cb92867 100644 --- a/src/osd/osd-classic.c +++ b/src/osd/osd-classic.c @@ -19,9 +19,59 @@ struct osd_classic_scene_item { struct view *view; - struct wlr_scene_node *highlight_outline; + struct wlr_scene_tree *normal_tree, *active_tree; }; +static void +create_fields_scene(struct server *server, struct view *view, + struct wlr_scene_tree *parent, const float *text_color, + const float *bg_color, int field_widths_sum, int x, int y) +{ + struct theme *theme = server->theme; + struct window_switcher_classic_theme *switcher_theme = + &theme->osd_window_switcher_classic; + + struct window_switcher_field *field; + 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; + + if (field->content == LAB_FIELD_ICON) { + int icon_size = MIN(field_width, + switcher_theme->item_icon_size); + struct scaled_icon_buffer *icon_buffer = + scaled_icon_buffer_create(parent, + server, icon_size, icon_size); + scaled_icon_buffer_set_view(icon_buffer, view); + node = &icon_buffer->scene_buffer->node; + height = icon_size; + } else { + struct buf buf = BUF_INIT; + osd_field_get_content(field, &buf, view); + + if (!string_null_or_empty(buf.data)) { + struct scaled_font_buffer *font_buffer = + scaled_font_buffer_create(parent); + scaled_font_buffer_update(font_buffer, + buf.data, field_width, + &rc.font_osd, text_color, bg_color); + node = &font_buffer->scene_buffer->node; + height = font_height(&rc.font_osd); + } + + buf_reset(&buf); + } + + if (node) { + int item_height = switcher_theme->item_height; + wlr_scene_node_set_position(node, + x, y + (item_height - height) / 2); + } + x += field_width + switcher_theme->item_padding_x; + } +} + static void osd_classic_create(struct output *output, struct wl_array *views) { @@ -126,62 +176,33 @@ osd_classic_create(struct output *output, struct wl_array *views) + switcher_theme->item_padding_x; struct wlr_scene_tree *item_root = wlr_scene_tree_create(output->osd_scene.tree); + item->normal_tree = wlr_scene_tree_create(item_root); + item->active_tree = wlr_scene_tree_create(item_root); + wlr_scene_node_set_enabled(&item->active_tree->node, false); - struct window_switcher_field *field; - 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; - - if (field->content == LAB_FIELD_ICON) { - int icon_size = MIN(field_width, - switcher_theme->item_icon_size); - struct scaled_icon_buffer *icon_buffer = - scaled_icon_buffer_create(item_root, - server, icon_size, icon_size); - scaled_icon_buffer_set_view(icon_buffer, *view); - node = &icon_buffer->scene_buffer->node; - height = icon_size; - } else { - buf_clear(&buf); - osd_field_get_content(field, &buf, *view); - - if (!string_null_or_empty(buf.data)) { - struct scaled_font_buffer *font_buffer = - scaled_font_buffer_create(item_root); - scaled_font_buffer_update(font_buffer, - buf.data, field_width, - &rc.font_osd, text_color, bg_color); - node = &font_buffer->scene_buffer->node; - height = font_height(&rc.font_osd); - } - } - - if (node) { - int item_height = switcher_theme->item_height; - wlr_scene_node_set_position(node, - x, y + (item_height - height) / 2); - } - x += field_width + switcher_theme->item_padding_x; - } + 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 */ int highlight_x = theme->osd_border_width + switcher_theme->padding; struct lab_scene_rect_options highlight_opts = { - .border_colors = (float *[1]) {text_color}, + .border_colors = (float *[1]) {active_border_color}, + .bg_color = active_bg_color, .nr_borders = 1, .border_width = switcher_theme->item_active_border_width, .width = w - 2 * theme->osd_border_width - 2 * switcher_theme->padding, .height = switcher_theme->item_height, }; - struct lab_scene_rect *highlight_rect = lab_scene_rect_create( - output->osd_scene.tree, &highlight_opts); - item->highlight_outline = &highlight_rect->tree->node; - wlr_scene_node_set_position(item->highlight_outline, highlight_x, y); - wlr_scene_node_set_enabled(item->highlight_outline, false); + item->active_tree, &highlight_opts); + wlr_scene_node_set_position(&highlight_rect->tree->node, highlight_x, y); + + create_fields_scene(server, *view, item->normal_tree, + text_color, bg_color, field_widths_sum, x, y); + create_fields_scene(server, *view, item->active_tree, + text_color, active_bg_color, field_widths_sum, x, y); y += switcher_theme->item_height; } @@ -200,8 +221,9 @@ osd_classic_update(struct output *output) { struct osd_classic_scene_item *item; wl_array_for_each(item, &output->osd_scene.items) { - wlr_scene_node_set_enabled(item->highlight_outline, - item->view == output->server->osd_state.cycle_view); + bool active = item->view == output->server->osd_state.cycle_view; + wlr_scene_node_set_enabled(&item->normal_tree->node, !active); + wlr_scene_node_set_enabled(&item->active_tree->node, active); } } diff --git a/src/osd/osd-field.c b/src/osd/osd-field.c index c8f796d1..01c4e48b 100644 --- a/src/osd/osd-field.c +++ b/src/osd/osd-field.c @@ -26,17 +26,13 @@ struct field_converter { /* Internal helpers */ static const char * -get_app_id_or_class(struct view *view, bool trim) +get_identifier(struct view *view, bool trim) { - /* - * XWayland clients return WM_CLASS for 'app_id' so we don't need a - * special case for that here. - */ - const char *identifier = view_get_string_prop(view, "app_id"); + const char *identifier = view->app_id; /* remove the first two nodes of 'org.' strings */ if (trim && !strncmp(identifier, "org.", 4)) { - char *p = (char *)identifier + 4; + const char *p = identifier + 4; p = strchr(p, '.'); if (p) { return ++p; @@ -49,14 +45,13 @@ static const char * get_desktop_name(struct view *view) { #if HAVE_LIBSFDO - const char *app_id = view_get_string_prop(view, "app_id"); - const char *name = desktop_entry_name_lookup(view->server, app_id); + const char *name = desktop_entry_name_lookup(view->server, view->app_id); if (name) { return name; } #endif - return get_app_id_or_class(view, /* trim */ true); + return get_identifier(view, /* trim */ true); } static const char * @@ -73,21 +68,12 @@ get_type(struct view *view, bool short_form) return "???"; } -static const char * -get_title(struct view *view) -{ - return view_get_string_prop(view, "title"); -} - static const char * get_title_if_different(struct view *view) { - const char *identifier = get_app_id_or_class(view, /*trim*/ false); - const char *title = get_title(view); - if (!identifier) { - return title; - } - return (!title || !strcmp(identifier, title)) ? NULL : title; + const char *identifier = get_identifier(view, /*trim*/ false); + const char *title = view->title; + return !strcmp(identifier, title) ? NULL : title; } /* Field handlers */ @@ -125,10 +111,12 @@ static void field_set_win_state(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: s */ - if (view->maximized) { - buf_add(buf, "M"); - } else if (view->minimized) { + if (view->minimized) { buf_add(buf, "m"); + } else if (view->shaded) { + buf_add(buf, "s"); + } else if (view->maximized) { + buf_add(buf, "M"); } else if (view->fullscreen) { buf_add(buf, "F"); } else { @@ -141,6 +129,7 @@ field_set_win_state_all(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: S */ buf_add(buf, view->minimized ? "m" : " "); + buf_add(buf, view->shaded ? "s" : " "); buf_add(buf, view->maximized ? "M" : " "); buf_add(buf, view->fullscreen ? "F" : " "); /* TODO: add always-on-top and omnipresent ? */ @@ -169,14 +158,14 @@ static void field_set_identifier(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: I */ - buf_add(buf, get_app_id_or_class(view, /*trim*/ false)); + buf_add(buf, get_identifier(view, /*trim*/ false)); } static void field_set_identifier_trimmed(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: i */ - buf_add(buf, get_app_id_or_class(view, /*trim*/ true)); + buf_add(buf, get_identifier(view, /*trim*/ true)); } static void @@ -190,7 +179,7 @@ static void field_set_title(struct buf *buf, struct view *view, const char *format) { /* custom type conversion-specifier: T */ - buf_add(buf, get_title(view)); + buf_add(buf, view->title); } static void diff --git a/src/osd/osd-thumbnail.c b/src/osd/osd-thumbnail.c index 24505a31..c9e6b283 100644 --- a/src/osd/osd-thumbnail.c +++ b/src/osd/osd-thumbnail.c @@ -155,15 +155,12 @@ create_item_scene(struct wlr_scene_tree *parent, struct view *view, } /* title */ - const char *title = view_get_string_prop(view, "title"); - if (title) { - item->normal_title = create_title(item->tree, switcher_theme, - title, theme->osd_label_text_color, - theme->osd_bg_color, title_y); - item->active_title = create_title(item->tree, switcher_theme, - title, theme->osd_label_text_color, - switcher_theme->item_active_bg_color, title_y); - } + item->normal_title = create_title(item->tree, switcher_theme, + view->title, theme->osd_label_text_color, + theme->osd_bg_color, title_y); + item->active_title = create_title(item->tree, switcher_theme, + view->title, theme->osd_label_text_color, + switcher_theme->item_active_bg_color, title_y); /* icon */ int icon_size = switcher_theme->item_icon_size; diff --git a/src/osd/osd.c b/src/osd/osd.c index df1b7ad9..e436c19e 100644 --- a/src/osd/osd.c +++ b/src/osd/osd.c @@ -9,6 +9,7 @@ #include "common/scene-helpers.h" #include "config/rcxml.h" #include "labwc.h" +#include "node.h" #include "output.h" #include "scaled-buffer/scaled-font-buffer.h" #include "scaled-buffer/scaled-icon-buffer.h" @@ -160,9 +161,14 @@ restore_preview_node(struct server *server) if (!osd_state->preview_was_enabled) { wlr_scene_node_set_enabled(osd_state->preview_node, false); } + if (osd_state->preview_was_shaded) { + struct view *view = node_view_from_node(osd_state->preview_node); + view_set_shade(view, true); + } osd_state->preview_node = NULL; osd_state->preview_parent = NULL; osd_state->preview_anchor = NULL; + osd_state->preview_was_shaded = false; } } @@ -203,6 +209,7 @@ osd_finish(struct server *server) server->osd_state.preview_node = NULL; server->osd_state.preview_anchor = NULL; server->osd_state.cycle_view = NULL; + server->osd_state.preview_was_shaded = false; destroy_osd_scenes(server); @@ -244,6 +251,10 @@ preview_cycled_view(struct view *view) if (!osd_state->preview_was_enabled) { wlr_scene_node_set_enabled(osd_state->preview_node, true); } + if (rc.window_switcher.unshade && view->shaded) { + view_set_shade(view, false); + osd_state->preview_was_shaded = true; + } /* * FIXME: This abuses an implementation detail of the always-on-top tree. @@ -294,6 +305,10 @@ update_osd(struct server *server) } } + if (rc.window_switcher.preview) { + preview_cycled_view(server->osd_state.cycle_view); + } + /* Outline current window */ if (rc.window_switcher.outlines) { if (view_is_focusable(server->osd_state.cycle_view)) { @@ -301,9 +316,6 @@ update_osd(struct server *server) } } - if (rc.window_switcher.preview) { - preview_cycled_view(server->osd_state.cycle_view); - } out: wl_array_release(&views); } diff --git a/src/scaled-buffer/scaled-icon-buffer.c b/src/scaled-buffer/scaled-icon-buffer.c index 0cd09b22..ceab0509 100644 --- a/src/scaled-buffer/scaled-icon-buffer.c +++ b/src/scaled-buffer/scaled-icon-buffer.c @@ -281,7 +281,7 @@ handle_view_new_app_id(struct wl_listener *listener, void *data) struct scaled_icon_buffer *self = wl_container_of(listener, self, on_view.new_app_id); - const char *app_id = view_get_string_prop(self->view, "app_id"); + const char *app_id = self->view->app_id; if (str_equal(app_id, self->view_app_id)) { return; } diff --git a/src/server.c b/src/server.c index c39649b8..bf424f9c 100644 --- a/src/server.c +++ b/src/server.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -255,6 +256,7 @@ allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, "wl_data_device_manager", /* would be great if we could drop this one */ "wl_seat", "xdg_wm_base", + "wl_fixes", /* enhanced */ "wl_output", "wl_drm", @@ -276,6 +278,7 @@ allow_for_sandbox(const struct wlr_security_context_v1_state *security_state, "zxdg_importer_v1", "zxdg_importer_v2", "xdg_toplevel_icon_manager_v1", + "xdg_dialog_v1", /* plus */ "wp_alpha_modifier_v1", "wp_linux_drm_syncobj_manager_v1", @@ -434,6 +437,8 @@ server_init(struct server *server) server->wl_event_loop = wl_display_get_event_loop(server->wl_display); + wlr_fixes_create(server->wl_display, 1); + /* Catch signals */ server->sighup_source = wl_event_loop_add_signal( server->wl_event_loop, SIGHUP, handle_sighup, server); diff --git a/src/ssd/ssd-shadow.c b/src/ssd/ssd-shadow.c index 53c6631d..2ead17ce 100644 --- a/src/ssd/ssd-shadow.c +++ b/src/ssd/ssd-shadow.c @@ -259,8 +259,8 @@ ssd_shadow_update(struct ssd *ssd) bool maximized = view->maximized == VIEW_AXIS_BOTH; bool tiled_shadows = false; if (rc.shadows_on_tiled) { - if (rc.gap >= theme->window[THEME_ACTIVE].shadow_size - && rc.gap >= theme->window[THEME_INACTIVE].shadow_size) { + if (rc.gap >= theme->window[SSD_ACTIVE].shadow_size + && rc.gap >= theme->window[SSD_INACTIVE].shadow_size) { tiled_shadows = true; } else { wlr_log(WLR_INFO, "gap size < shadow_size, ignore rc.shadows_ontiled"); diff --git a/src/ssd/ssd-titlebar.c b/src/ssd/ssd-titlebar.c index 13f5e7df..09f5362a 100644 --- a/src/ssd/ssd-titlebar.c +++ b/src/ssd/ssd-titlebar.c @@ -440,14 +440,13 @@ ssd_update_title(struct ssd *ssd) } struct view *view = ssd->view; - char *title = (char *)view_get_string_prop(view, "title"); - if (string_null_or_empty(title)) { + if (string_null_or_empty(view->title)) { return; } struct theme *theme = view->server->theme; struct ssd_state_title *state = &ssd->state.title; - bool title_unchanged = state->text && !strcmp(title, state->text); + bool title_unchanged = state->text && !strcmp(view->title, state->text); int offset_left, offset_right; get_title_offsets(ssd, &offset_left, &offset_right); @@ -473,7 +472,7 @@ ssd_update_title(struct ssd *ssd) } const float bg_color[4] = {0, 0, 0, 0}; /* ignored */ - scaled_font_buffer_update(subtree->title, title, + scaled_font_buffer_update(subtree->title, view->title, title_bg_width, font, text_color, bg_color); @@ -483,10 +482,7 @@ ssd_update_title(struct ssd *ssd) } if (!title_unchanged) { - if (state->text) { - free(state->text); - } - state->text = xstrdup(title); + xstrdup_replace(state->text, view->title); } ssd_update_title_positions(ssd, offset_left, offset_right); } diff --git a/src/ssd/ssd.c b/src/ssd/ssd.c index 9ec7fa3b..d1381c1a 100644 --- a/src/ssd/ssd.c +++ b/src/ssd/ssd.c @@ -348,7 +348,7 @@ ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable) float *color = enable ? rc.theme->window_toggled_keybinds_color - : rc.theme->window[THEME_ACTIVE].border_color; + : rc.theme->window[SSD_ACTIVE].border_color; wlr_scene_rect_set_color(ssd->border.subtrees[SSD_ACTIVE].top, color); } diff --git a/src/theme.c b/src/theme.c index d5279367..f6684ee9 100644 --- a/src/theme.c +++ b/src/theme.c @@ -162,7 +162,7 @@ get_button_filename(char *buf, size_t len, const char *name, const char *postfix } static void -load_button(struct theme *theme, struct button *b, int active) +load_button(struct theme *theme, struct button *b, enum ssd_active_state active) { struct lab_img *(*button_imgs)[LAB_BS_ALL + 1] = theme->window[active].button_imgs; @@ -374,8 +374,8 @@ load_buttons(struct theme *theme) for (size_t i = 0; i < ARRAY_SIZE(buttons); ++i) { struct button *b = &buttons[i]; - load_button(theme, b, THEME_INACTIVE); - load_button(theme, b, THEME_ACTIVE); + load_button(theme, b, SSD_INACTIVE); + load_button(theme, b, SSD_ACTIVE); } } @@ -537,24 +537,24 @@ theme_builtin(struct theme *theme, struct server *server) theme->window_titlebar_padding_height = 0; theme->window_titlebar_padding_width = 0; - parse_hexstr("#aaaaaa", theme->window[THEME_ACTIVE].border_color); - parse_hexstr("#aaaaaa", theme->window[THEME_INACTIVE].border_color); + parse_hexstr("#aaaaaa", theme->window[SSD_ACTIVE].border_color); + parse_hexstr("#aaaaaa", theme->window[SSD_INACTIVE].border_color); parse_hexstr("#ff0000", theme->window_toggled_keybinds_color); - theme->window[THEME_ACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; - theme->window[THEME_INACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; - parse_hexstr("#e1dedb", theme->window[THEME_ACTIVE].title_bg.color); - parse_hexstr("#f6f5f4", theme->window[THEME_INACTIVE].title_bg.color); - theme->window[THEME_ACTIVE].title_bg.color_split_to[0] = FLT_MIN; - theme->window[THEME_INACTIVE].title_bg.color_split_to[0] = FLT_MIN; - theme->window[THEME_ACTIVE].title_bg.color_to[0] = FLT_MIN; - theme->window[THEME_INACTIVE].title_bg.color_to[0] = FLT_MIN; - theme->window[THEME_ACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; - theme->window[THEME_INACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; + theme->window[SSD_ACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; + theme->window[SSD_INACTIVE].title_bg.gradient = LAB_GRADIENT_NONE; + parse_hexstr("#e1dedb", theme->window[SSD_ACTIVE].title_bg.color); + parse_hexstr("#f6f5f4", theme->window[SSD_INACTIVE].title_bg.color); + theme->window[SSD_ACTIVE].title_bg.color_split_to[0] = FLT_MIN; + theme->window[SSD_INACTIVE].title_bg.color_split_to[0] = FLT_MIN; + theme->window[SSD_ACTIVE].title_bg.color_to[0] = FLT_MIN; + theme->window[SSD_INACTIVE].title_bg.color_to[0] = FLT_MIN; + theme->window[SSD_ACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; + theme->window[SSD_INACTIVE].title_bg.color_to_split_to[0] = FLT_MIN; - parse_hexstr("#000000", theme->window[THEME_ACTIVE].label_text_color); - parse_hexstr("#000000", theme->window[THEME_INACTIVE].label_text_color); + parse_hexstr("#000000", theme->window[SSD_ACTIVE].label_text_color); + parse_hexstr("#000000", theme->window[SSD_INACTIVE].label_text_color); theme->window_label_text_justify = parse_justification("Center"); theme->window_button_width = 26; @@ -565,15 +565,15 @@ theme_builtin(struct theme *theme, struct server *server) for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST; type <= LAB_NODE_BUTTON_LAST; type++) { parse_hexstr("#000000", - theme->window[THEME_INACTIVE].button_colors[type]); + theme->window[SSD_INACTIVE].button_colors[type]); parse_hexstr("#000000", - theme->window[THEME_ACTIVE].button_colors[type]); + theme->window[SSD_ACTIVE].button_colors[type]); } - theme->window[THEME_ACTIVE].shadow_size = 60; - theme->window[THEME_INACTIVE].shadow_size = 40; - parse_hexstr("#00000060", theme->window[THEME_ACTIVE].shadow_color); - parse_hexstr("#00000040", theme->window[THEME_INACTIVE].shadow_color); + theme->window[SSD_ACTIVE].shadow_size = 60; + theme->window[SSD_INACTIVE].shadow_size = 40; + parse_hexstr("#00000060", theme->window[SSD_ACTIVE].shadow_color); + parse_hexstr("#00000040", theme->window[SSD_INACTIVE].shadow_color); theme->menu_overlap_x = 0; theme->menu_overlap_y = 0; @@ -604,6 +604,8 @@ theme_builtin(struct theme *theme, struct server *server) theme->osd_window_switcher_classic.item_padding_x = 10; theme->osd_window_switcher_classic.item_padding_y = 1; theme->osd_window_switcher_classic.item_active_border_width = 2; + theme->osd_window_switcher_classic.item_active_border_color[0] = FLT_MIN; + theme->osd_window_switcher_classic.item_active_bg_color[0] = FLT_MIN; theme->osd_window_switcher_classic.item_icon_size = -1; theme->osd_window_switcher_thumbnail.max_width = 80; @@ -613,8 +615,8 @@ theme_builtin(struct theme *theme, struct server *server) theme->osd_window_switcher_thumbnail.item_height = 250; theme->osd_window_switcher_thumbnail.item_padding = 10; theme->osd_window_switcher_thumbnail.item_active_border_width = 2; - parse_color("#589bda", theme->osd_window_switcher_thumbnail.item_active_border_color); - parse_color("#c7e2fc", theme->osd_window_switcher_thumbnail.item_active_bg_color); + theme->osd_window_switcher_thumbnail.item_active_border_color[0] = FLT_MIN; + theme->osd_window_switcher_thumbnail.item_active_bg_color[0] = FLT_MIN; theme->osd_window_switcher_thumbnail.item_icon_size = 60; /* inherit settings in post_processing() if not set elsewhere */ @@ -709,15 +711,15 @@ entry(struct theme *theme, const char *key, const char *value) } if (match_glob(key, "window.active.border.color")) { - parse_color(value, theme->window[THEME_ACTIVE].border_color); + parse_color(value, theme->window[SSD_ACTIVE].border_color); } if (match_glob(key, "window.inactive.border.color")) { - parse_color(value, theme->window[THEME_INACTIVE].border_color); + parse_color(value, theme->window[SSD_INACTIVE].border_color); } /* border.color is obsolete, but handled for backward compatibility */ if (match_glob(key, "border.color")) { - parse_color(value, theme->window[THEME_ACTIVE].border_color); - parse_color(value, theme->window[THEME_INACTIVE].border_color); + parse_color(value, theme->window[SSD_ACTIVE].border_color); + parse_color(value, theme->window[SSD_INACTIVE].border_color); } if (match_glob(key, "window.active.indicator.toggled-keybind.color")) { @@ -725,41 +727,41 @@ entry(struct theme *theme, const char *key, const char *value) } if (match_glob(key, "window.active.title.bg")) { - theme->window[THEME_ACTIVE].title_bg.gradient = parse_gradient(value); + theme->window[SSD_ACTIVE].title_bg.gradient = parse_gradient(value); } if (match_glob(key, "window.inactive.title.bg")) { - theme->window[THEME_INACTIVE].title_bg.gradient = parse_gradient(value); + theme->window[SSD_INACTIVE].title_bg.gradient = parse_gradient(value); } if (match_glob(key, "window.active.title.bg.color")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg.color); + parse_color(value, theme->window[SSD_ACTIVE].title_bg.color); } if (match_glob(key, "window.inactive.title.bg.color")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg.color); + parse_color(value, theme->window[SSD_INACTIVE].title_bg.color); } if (match_glob(key, "window.active.title.bg.color.splitTo")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_split_to); + parse_color(value, theme->window[SSD_ACTIVE].title_bg.color_split_to); } if (match_glob(key, "window.inactive.title.bg.color.splitTo")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_split_to); + parse_color(value, theme->window[SSD_INACTIVE].title_bg.color_split_to); } if (match_glob(key, "window.active.title.bg.colorTo")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_to); + parse_color(value, theme->window[SSD_ACTIVE].title_bg.color_to); } if (match_glob(key, "window.inactive.title.bg.colorTo")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_to); + parse_color(value, theme->window[SSD_INACTIVE].title_bg.color_to); } if (match_glob(key, "window.active.title.bg.colorTo.splitTo")) { - parse_color(value, theme->window[THEME_ACTIVE].title_bg.color_to_split_to); + parse_color(value, theme->window[SSD_ACTIVE].title_bg.color_to_split_to); } if (match_glob(key, "window.inactive.title.bg.colorTo.splitTo")) { - parse_color(value, theme->window[THEME_INACTIVE].title_bg.color_to_split_to); + parse_color(value, theme->window[SSD_INACTIVE].title_bg.color_to_split_to); } if (match_glob(key, "window.active.label.text.color")) { - parse_color(value, theme->window[THEME_ACTIVE].label_text_color); + parse_color(value, theme->window[SSD_ACTIVE].label_text_color); } if (match_glob(key, "window.inactive.label.text.color")) { - parse_color(value, theme->window[THEME_INACTIVE].label_text_color); + parse_color(value, theme->window[SSD_INACTIVE].label_text_color); } if (match_glob(key, "window.label.text.justify")) { theme->window_label_text_justify = parse_justification(value); @@ -795,85 +797,85 @@ entry(struct theme *theme, const char *key, const char *value) for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST; type <= LAB_NODE_BUTTON_LAST; type++) { parse_color(value, - theme->window[THEME_ACTIVE].button_colors[type]); + theme->window[SSD_ACTIVE].button_colors[type]); } } if (match_glob(key, "window.inactive.button.unpressed.image.color")) { for (enum lab_node_type type = LAB_NODE_BUTTON_FIRST; type <= LAB_NODE_BUTTON_LAST; type++) { parse_color(value, - theme->window[THEME_INACTIVE].button_colors[type]); + theme->window[SSD_INACTIVE].button_colors[type]); } } /* individual buttons */ if (match_glob(key, "window.active.button.menu.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_WINDOW_MENU]); - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_WINDOW_ICON]); } if (match_glob(key, "window.active.button.iconify.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_ICONIFY]); } if (match_glob(key, "window.active.button.max.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_MAXIMIZE]); } if (match_glob(key, "window.active.button.shade.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_SHADE]); } if (match_glob(key, "window.active.button.desk.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_OMNIPRESENT]); } if (match_glob(key, "window.active.button.close.unpressed.image.color")) { - parse_color(value, theme->window[THEME_ACTIVE] + parse_color(value, theme->window[SSD_ACTIVE] .button_colors[LAB_NODE_BUTTON_CLOSE]); } if (match_glob(key, "window.inactive.button.menu.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_WINDOW_MENU]); - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_WINDOW_ICON]); } if (match_glob(key, "window.inactive.button.iconify.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_ICONIFY]); } if (match_glob(key, "window.inactive.button.max.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_MAXIMIZE]); } if (match_glob(key, "window.inactive.button.shade.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_SHADE]); } if (match_glob(key, "window.inactive.button.desk.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_OMNIPRESENT]); } if (match_glob(key, "window.inactive.button.close.unpressed.image.color")) { - parse_color(value, theme->window[THEME_INACTIVE] + parse_color(value, theme->window[SSD_INACTIVE] .button_colors[LAB_NODE_BUTTON_CLOSE]); } /* window drop-shadows */ if (match_glob(key, "window.active.shadow.size")) { - theme->window[THEME_ACTIVE].shadow_size = get_int_if_positive( + theme->window[SSD_ACTIVE].shadow_size = get_int_if_positive( value, "window.active.shadow.size"); } if (match_glob(key, "window.inactive.shadow.size")) { - theme->window[THEME_INACTIVE].shadow_size = get_int_if_positive( + theme->window[SSD_INACTIVE].shadow_size = get_int_if_positive( value, "window.inactive.shadow.size"); } if (match_glob(key, "window.active.shadow.color")) { - parse_color(value, theme->window[THEME_ACTIVE].shadow_color); + parse_color(value, theme->window[SSD_ACTIVE].shadow_color); } if (match_glob(key, "window.inactive.shadow.color")) { - parse_color(value, theme->window[THEME_INACTIVE].shadow_color); + parse_color(value, theme->window[SSD_INACTIVE].shadow_color); } if (match_glob(key, "menu.overlap.x")) { @@ -989,6 +991,12 @@ entry(struct theme *theme, const char *key, const char *value) get_int_if_positive(value, "osd.window-switcher.style-classic.item.active.border.width"); } + if (match_glob(key, "osd.window-switcher.style-classic.item.active.border.color")) { + parse_color(value, switcher_classic_theme->item_active_border_color); + } + if (match_glob(key, "osd.window-switcher.style-classic.item.active.bg.color")) { + parse_color(value, switcher_classic_theme->item_active_bg_color); + } if (match_glob(key, "osd.window-switcher.style-classic.item.icon.size") || match_glob(key, "osd.window-switcher.item.icon.size")) { switcher_classic_theme->item_icon_size = @@ -1388,7 +1396,8 @@ create_titlebar_fill(cairo_pattern_t *pattern, int height) static void create_backgrounds(struct theme *theme) { - for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) { + enum ssd_active_state active; + FOR_EACH_ACTIVE_STATE(active) { theme->window[active].titlebar_pattern = create_titlebar_pattern( &theme->window[active].title_bg, theme->titlebar_height); @@ -1410,7 +1419,8 @@ create_corners(struct theme *theme) .height = theme->titlebar_height + theme->border_width, }; - for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) { + enum ssd_active_state active; + FOR_EACH_ACTIVE_STATE(active) { struct rounded_corner_ctx ctx = { .box = &box, .radius = rc.corner_radius, @@ -1540,7 +1550,7 @@ shadow_corner_gradient(struct lab_data_buffer *buffer, int visible_size, } static void -create_shadow(struct theme *theme, int active) +create_shadow(struct theme *theme, enum ssd_active_state active) { /* Size of shadow visible extending beyond the window */ int visible_size = theme->window[active].shadow_size; @@ -1582,8 +1592,8 @@ create_shadow(struct theme *theme, int active) static void create_shadows(struct theme *theme) { - create_shadow(theme, THEME_INACTIVE); - create_shadow(theme, THEME_ACTIVE); + create_shadow(theme, SSD_INACTIVE); + create_shadow(theme, SSD_ACTIVE); } static void @@ -1633,6 +1643,31 @@ get_titlebar_height(struct theme *theme) return h; } +/* Blend foreground color (with new alpha) with background color */ +static void +blend_color_with_bg(float *dst, float *fg, float fg_a, float *bg) +{ + /* Guard against zero division */ + if (fg[3] <= 0.0f) { + memset(dst, 0, sizeof(float) * 4); + return; + } + + /* Redo premultiplication to update foreground alpha */ + float new_fg[4] = { + fg[0] / fg[3] * fg_a, + fg[1] / fg[3] * fg_a, + fg[2] / fg[3] * fg_a, + fg_a, + }; + + /* Blend colors */ + dst[0] = new_fg[0] + bg[0] * (1.0f - new_fg[3]); + dst[1] = new_fg[1] + bg[1] * (1.0f - new_fg[3]); + dst[2] = new_fg[2] + bg[2] * (1.0f - new_fg[3]); + dst[3] = new_fg[3] + bg[3] * (1.0f - new_fg[3]); +} + static void post_processing(struct theme *theme) { @@ -1643,8 +1678,8 @@ post_processing(struct theme *theme) theme->titlebar_height = get_titlebar_height(theme); - fill_background_colors(&theme->window[THEME_INACTIVE].title_bg); - fill_background_colors(&theme->window[THEME_ACTIVE].title_bg); + fill_background_colors(&theme->window[SSD_INACTIVE].title_bg); + fill_background_colors(&theme->window[SSD_ACTIVE].title_bg); theme->menu_item_height = font_height(&rc.font_menuitem) + 2 * theme->menu_items_padding_y; @@ -1689,14 +1724,14 @@ post_processing(struct theme *theme) } if (theme->menu_border_color[0] == FLT_MIN) { memcpy(theme->menu_border_color, - theme->window[THEME_ACTIVE].border_color, + theme->window[SSD_ACTIVE].border_color, sizeof(theme->menu_border_color)); } /* Inherit OSD settings if not set */ if (theme->osd_bg_color[0] == FLT_MIN) { memcpy(theme->osd_bg_color, - theme->window[THEME_ACTIVE].title_bg.color, + theme->window[SSD_ACTIVE].title_bg.color, sizeof(theme->osd_bg_color)); } if (theme->osd_border_width == INT_MIN) { @@ -1704,7 +1739,7 @@ post_processing(struct theme *theme) } if (theme->osd_label_text_color[0] == FLT_MIN) { memcpy(theme->osd_label_text_color, - theme->window[THEME_ACTIVE].label_text_color, + theme->window[SSD_ACTIVE].label_text_color, sizeof(theme->osd_label_text_color)); } if (theme->osd_border_color[0] == FLT_MIN) { @@ -1721,6 +1756,22 @@ post_processing(struct theme *theme) memcpy(theme->osd_border_color, theme->osd_label_text_color, sizeof(theme->osd_border_color)); } + if (switcher_classic_theme->item_active_border_color[0] == FLT_MIN) { + blend_color_with_bg(switcher_classic_theme->item_active_border_color, + theme->osd_label_text_color, 0.50, theme->osd_bg_color); + } + if (switcher_classic_theme->item_active_bg_color[0] == FLT_MIN) { + blend_color_with_bg(switcher_classic_theme->item_active_bg_color, + theme->osd_label_text_color, 0.15, theme->osd_bg_color); + } + if (switcher_thumb_theme->item_active_border_color[0] == FLT_MIN) { + blend_color_with_bg(switcher_thumb_theme->item_active_border_color, + theme->osd_label_text_color, 0.50, theme->osd_bg_color); + } + if (switcher_thumb_theme->item_active_bg_color[0] == FLT_MIN) { + blend_color_with_bg(switcher_thumb_theme->item_active_bg_color, + theme->osd_label_text_color, 0.15, theme->osd_bg_color); + } if (theme->osd_workspace_switcher_boxes_width == 0) { theme->osd_workspace_switcher_boxes_height = 0; } @@ -1805,14 +1856,15 @@ theme_finish(struct theme *theme) type <= LAB_NODE_BUTTON_LAST; type++) { for (uint8_t state_set = LAB_BS_DEFAULT; state_set <= LAB_BS_ALL; state_set++) { - destroy_img(&theme->window[THEME_INACTIVE] + destroy_img(&theme->window[SSD_INACTIVE] .button_imgs[type][state_set]); - destroy_img(&theme->window[THEME_ACTIVE] + destroy_img(&theme->window[SSD_ACTIVE] .button_imgs[type][state_set]); } } - for (int active = THEME_INACTIVE; active <= THEME_ACTIVE; active++) { + enum ssd_active_state active; + FOR_EACH_ACTIVE_STATE(active) { zfree_pattern(theme->window[active].titlebar_pattern); zdrop(&theme->window[active].titlebar_fill); zdrop(&theme->window[active].corner_top_left_normal); diff --git a/src/view-impl-common.c b/src/view-impl-common.c index d529941a..df72fe62 100644 --- a/src/view-impl-common.c +++ b/src/view-impl-common.c @@ -10,8 +10,6 @@ void view_impl_map(struct view *view) { desktop_focus_view(view, /*raise*/ true); - view_update_title(view); - view_update_app_id(view); if (!view->been_mapped) { window_rules_apply(view, LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP); } @@ -36,8 +34,7 @@ view_impl_map(struct view *view) desktop_update_top_layer_visibility(view->server); wlr_log(WLR_DEBUG, "[map] identifier=%s, title=%s", - view_get_string_prop(view, "app_id"), - view_get_string_prop(view, "title")); + view->app_id, view->title); } void diff --git a/src/view.c b/src/view.c index 792401cf..87ef8566 100644 --- a/src/view.c +++ b/src/view.c @@ -133,11 +133,11 @@ view_contains_window_type(struct view *view, enum lab_window_type window_type) bool view_matches_query(struct view *view, struct view_query *query) { - if (!query_str_match(query->identifier, view_get_string_prop(view, "app_id"))) { + if (!query_str_match(query->identifier, view->app_id)) { return false; } - if (!query_str_match(query->title, view_get_string_prop(view, "title"))) { + if (!query_str_match(query->title, view->title)) { return false; } @@ -2161,7 +2161,8 @@ view_snap_to_edge(struct view *view, enum lab_edge edge, view_set_shade(view, false); - if (lab_edge_is_cardinal(edge) && view->maximized == VIEW_AXIS_NONE) { + if (lab_edge_is_cardinal(edge) && view->maximized == VIEW_AXIS_NONE + && view->tiled != LAB_EDGE_CENTER) { enum lab_edge invert_edge = lab_edge_invert(edge); /* Represents axis of snapping direction */ enum lab_edge parallel_mask = edge | invert_edge; @@ -2384,31 +2385,36 @@ view_has_strut_partial(struct view *view) view->impl->has_strut_partial(view); } -/* Note: It is safe to assume that this function never returns NULL */ -const char * -view_get_string_prop(struct view *view, const char *prop) -{ - assert(view); - assert(prop); - if (view->impl->get_string_prop) { - const char *ret = view->impl->get_string_prop(view, prop); - return ret ? ret : ""; - } - return ""; -} - void -view_update_title(struct view *view) +view_set_title(struct view *view, const char *title) { assert(view); + if (!title) { + title = ""; + } + + if (!strcmp(view->title, title)) { + return; + } + xstrdup_replace(view->title, title); + ssd_update_title(view->ssd); wl_signal_emit_mutable(&view->events.new_title, NULL); } void -view_update_app_id(struct view *view) +view_set_app_id(struct view *view, const char *app_id) { assert(view); + if (!app_id) { + app_id = ""; + } + + if (!strcmp(view->app_id, app_id)) { + return; + } + xstrdup_replace(view->app_id, app_id); + wl_signal_emit_mutable(&view->events.new_app_id, NULL); } @@ -2561,6 +2567,9 @@ view_init(struct view *view) wl_signal_init(&view->events.activated); wl_signal_init(&view->events.set_icon); wl_signal_init(&view->events.destroy); + + view->title = xstrdup(""); + view->app_id = xstrdup(""); } void @@ -2584,6 +2593,9 @@ view_destroy(struct view *view) wl_list_remove(&view->set_title.link); wl_list_remove(&view->destroy.link); + zfree(view->title); + zfree(view->app_id); + if (view->foreign_toplevel) { foreign_toplevel_destroy(view->foreign_toplevel); view->foreign_toplevel = NULL; diff --git a/src/xdg.c b/src/xdg.c index 1e09dd99..83ab9bb9 100644 --- a/src/xdg.c +++ b/src/xdg.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "buffer.h" @@ -216,7 +217,7 @@ handle_commit(struct wl_listener *listener, void *data) && extent.height == view->pending.height) { wlr_log(WLR_DEBUG, "window geometry for client (%s) " "appears to be incorrect - ignoring", - view_get_string_prop(view, "app_id")); + view->app_id); size = extent; /* Use surface extent instead */ } } @@ -283,9 +284,8 @@ handle_configure_timeout(void *data) assert(view->pending_configure_serial > 0); assert(view->pending_configure_timeout); - const char *app_id = view_get_string_prop(view, "app_id"); wlr_log(WLR_INFO, "client (%s) did not respond to configure request " - "in %d ms", app_id, CONFIGURE_TIMEOUT_MS); + "in %d ms", view->app_id, CONFIGURE_TIMEOUT_MS); wl_event_source_remove(view->pending_configure_timeout); view->pending_configure_serial = 0; @@ -492,7 +492,9 @@ static void handle_set_title(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_title); - view_update_title(view); + struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); + + view_set_title(view, toplevel->title); } static void @@ -501,7 +503,9 @@ handle_set_app_id(struct wl_listener *listener, void *data) struct xdg_toplevel_view *xdg_toplevel_view = wl_container_of(listener, xdg_toplevel_view, set_app_id); struct view *view = &xdg_toplevel_view->base; - view_update_app_id(view); + struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); + + view_set_app_id(view, toplevel->app_id); } static void @@ -614,6 +618,18 @@ xdg_toplevel_view_append_children(struct view *self, struct wl_array *children) } } +static bool +xdg_toplevel_view_is_modal_dialog(struct view *view) +{ + struct wlr_xdg_toplevel *toplevel = xdg_toplevel_from_view(view); + struct wlr_xdg_dialog_v1 *dialog = + wlr_xdg_dialog_v1_try_from_wlr_xdg_toplevel(toplevel); + if (!dialog) { + return false; + } + return dialog->modal; +} + static void xdg_toplevel_view_set_activated(struct view *view, bool activated) { @@ -740,31 +756,6 @@ set_initial_position(struct view *view) view_place_by_policy(view, /* allow_cursor */ true, rc.placement_policy); } -static const char * -xdg_toplevel_view_get_string_prop(struct view *view, const char *prop) -{ - struct xdg_toplevel_view *xdg_view = xdg_toplevel_view_from_view(view); - struct wlr_xdg_toplevel *xdg_toplevel = xdg_view->xdg_surface - ? xdg_view->xdg_surface->toplevel - : NULL; - if (!xdg_toplevel) { - /* - * This may happen due to a matchOnce rule when - * a view is destroyed while A-Tab is open. See - * https://github.com/labwc/labwc/issues/1082#issuecomment-1716137180 - */ - return ""; - } - - if (!strcmp(prop, "title")) { - return xdg_toplevel->title ? xdg_toplevel->title : ""; - } - if (!strcmp(prop, "app_id")) { - return xdg_toplevel->app_id ? xdg_toplevel->app_id : ""; - } - return ""; -} - static void init_foreign_toplevel(struct view *view) { @@ -884,7 +875,6 @@ xdg_view_get_pid(struct view *view) static const struct view_impl xdg_toplevel_view_impl = { .configure = xdg_toplevel_view_configure, .close = xdg_toplevel_view_close, - .get_string_prop = xdg_toplevel_view_get_string_prop, .map = xdg_toplevel_view_map, .set_activated = xdg_toplevel_view_set_activated, .set_fullscreen = xdg_toplevel_view_set_fullscreen, @@ -894,6 +884,7 @@ static const struct view_impl xdg_toplevel_view_impl = { .minimize = xdg_toplevel_view_minimize, .get_root = xdg_toplevel_view_get_root, .append_children = xdg_toplevel_view_append_children, + .is_modal_dialog = xdg_toplevel_view_is_modal_dialog, .get_size_hints = xdg_toplevel_view_get_size_hints, .contains_window_type = xdg_toplevel_view_contains_window_type, .get_pid = xdg_view_get_pid, @@ -1139,6 +1130,8 @@ xdg_shell_init(struct server *server) server->xdg_toplevel_icon_set_icon.notify = handle_xdg_toplevel_icon_set_icon; wl_signal_add(&server->xdg_toplevel_icon_manager->events.set_icon, &server->xdg_toplevel_icon_set_icon); + + wlr_xdg_wm_dialog_v1_create(server->wl_display, 1); } void diff --git a/src/xwayland.c b/src/xwayland.c index 0ab92f0d..6b408a78 100644 --- a/src/xwayland.c +++ b/src/xwayland.c @@ -498,7 +498,8 @@ static void handle_set_title(struct wl_listener *listener, void *data) { struct view *view = wl_container_of(listener, view, set_title); - view_update_title(view); + struct xwayland_view *xwayland_view = xwayland_view_from_view(view); + view_set_title(view, xwayland_view->xwayland_surface->title); } static void @@ -507,35 +508,7 @@ handle_set_class(struct wl_listener *listener, void *data) struct xwayland_view *xwayland_view = wl_container_of(listener, xwayland_view, set_class); struct view *view = &xwayland_view->base; - view_update_app_id(view); -} -static void -xwayland_view_close(struct view *view) -{ - wlr_xwayland_surface_close(xwayland_surface_from_view(view)); -} - -static const char * -xwayland_view_get_string_prop(struct view *view, const char *prop) -{ - struct xwayland_view *xwayland_view = xwayland_view_from_view(view); - struct wlr_xwayland_surface *xwayland_surface = xwayland_view->xwayland_surface; - if (!xwayland_surface) { - /* - * This may happen due to a matchOnce rule when - * a view is destroyed while A-Tab is open. See - * https://github.com/labwc/labwc/issues/1082#issuecomment-1716137180 - */ - return ""; - } - - if (!strcmp(prop, "title")) { - return xwayland_surface->title ? xwayland_surface->title : ""; - } - if (!strcmp(prop, "class")) { - return xwayland_surface->class ? xwayland_surface->class : ""; - } /* * Use the WM_CLASS 'instance' (1st string) for the app_id. Per * ICCCM, this is usually "the trailing part of the name used to @@ -545,10 +518,13 @@ xwayland_view_get_string_prop(struct view *view, const char *prop) * 'instance' except for being capitalized. We want lowercase * here since we use the app_id for icon lookups. */ - if (!strcmp(prop, "app_id")) { - return xwayland_surface->instance ? xwayland_surface->instance : ""; - } - return ""; + view_set_app_id(view, xwayland_view->xwayland_surface->instance); +} + +static void +xwayland_view_close(struct view *view) +{ + wlr_xwayland_surface_close(xwayland_surface_from_view(view)); } static void @@ -1050,7 +1026,6 @@ xwayland_view_get_pid(struct view *view) static const struct view_impl xwayland_view_impl = { .configure = xwayland_view_configure, .close = xwayland_view_close, - .get_string_prop = xwayland_view_get_string_prop, .map = xwayland_view_map, .set_activated = xwayland_view_set_activated, .set_fullscreen = xwayland_view_set_fullscreen, diff --git a/subprojects/wlroots.wrap b/subprojects/wlroots.wrap index 25a947ed..4352289b 100644 --- a/subprojects/wlroots.wrap +++ b/subprojects/wlroots.wrap @@ -1,7 +1,7 @@ [wrap-git] url = https://gitlab.freedesktop.org/wlroots/wlroots.git -revision = 0.19 +revision = master [provide] -dependency_names = wlroots-0.19 -wlroots-0.19=wlroots +dependency_names = wlroots-0.20 +wlroots-0.20=wlroots