diff --git a/README.md b/README.md index ec00083d..9cc81e88 100644 --- a/README.md +++ b/README.md @@ -217,9 +217,9 @@ If you have not created an rc.xml config file, default bindings will be: | `super`-`mouse-right` | resize window | `super`-`arrow` | resize window to fill half the output | `alt`-`space` | show the window menu -| `XF86AudioLowerVolume` | amixer sset Master 5%- -| `XF86AudioRaiseVolume` | amixer sset Master 5%+ -| `XF86AudioMute` | amixer sset Master toggle +| `XF86AudioLowerVolume` | pactl set-sink-volume @DEFAULT_SINK@ -5% +| `XF86AudioRaiseVolume` | pactl set-sink-volume @DEFAULT_SINK@ +5% +| `XF86AudioMute` | pactl set-sink-mute @DEFAULT_SINK@ toggle | `XF86MonBrightnessUp` | brightnessctl set +10% | `XF86MonBrightnessDown` | brightnessctl set 10%- diff --git a/clients/labnag.c b/clients/labnag.c index 82b6e8cd..8d47ee29 100644 --- a/clients/labnag.c +++ b/clients/labnag.c @@ -46,6 +46,7 @@ struct conf { uint32_t button_text; uint32_t button_background; uint32_t details_background; + uint32_t details_border_color; uint32_t background; uint32_t text; uint32_t button_border; @@ -60,6 +61,7 @@ struct conf { ssize_t button_gap_close; ssize_t button_margin_right; ssize_t button_padding; + ssize_t details_margin; }; struct pointer { @@ -300,10 +302,10 @@ render_details_scroll_button(cairo_t *cairo, struct nag *nag, get_text_size(cairo, nag->conf->font_description, &text_width, &text_height, NULL, 1, true, "%s", button->text); - int border = nag->conf->button_border_thickness; - int padding = nag->conf->button_padding; + int border = nag->conf->details_border_thickness; + int padding = (nag->conf->button_padding / 3) + 2; - cairo_set_source_u32(cairo, nag->conf->details_background); + cairo_set_source_u32(cairo, nag->conf->details_border_color); cairo_rectangle(cairo, button->x, button->y, button->width, button->height); cairo_fill(cairo); @@ -316,7 +318,7 @@ render_details_scroll_button(cairo_t *cairo, struct nag *nag, cairo_set_source_u32(cairo, nag->conf->button_text); cairo_move_to(cairo, button->x + border + padding, - button->y + border + (button->height - text_height) / 2); + button->y + (button->height - text_height) / 2); render_text(cairo, nag->conf->font_description, 1, true, "%s", button->text); } @@ -331,8 +333,8 @@ get_detailed_scroll_button_width(cairo_t *cairo, struct nag *nag) NULL, 1, true, "%s", nag->details.button_down.text); int text_width = up_width > down_width ? up_width : down_width; - int border = nag->conf->button_border_thickness; - int padding = nag->conf->button_padding; + int border = nag->conf->details_border_thickness; + int padding = (nag->conf->button_padding / 3) + 2; return text_width + border * 2 + padding * 2; } @@ -343,8 +345,9 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) uint32_t width = nag->width; int border = nag->conf->details_border_thickness; + int margin = nag->conf->details_margin; int padding = nag->conf->message_padding; - int decor = padding + border; + int decor = margin + border; nag->details.x = decor; nag->details.y = y + decor; @@ -372,7 +375,7 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) bool show_buttons = nag->details.offset > 0; int button_width = get_detailed_scroll_button_width(cairo, nag); if (show_buttons) { - nag->details.width -= button_width; + nag->details.width += border - button_width; pango_layout_set_width(layout, (nag->details.width - padding * 2) * PANGO_SCALE); } @@ -385,7 +388,7 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) if (!show_buttons) { show_buttons = true; - nag->details.width -= button_width; + nag->details.width += border - button_width; pango_layout_set_width(layout, (nag->details.width - padding * 2) * PANGO_SCALE); } @@ -401,21 +404,29 @@ render_detailed(cairo_t *cairo, struct nag *nag, uint32_t y) nag->details.visible_lines = pango_layout_get_line_count(layout); + int border_rect_height = nag->details.height + 2 * border; + if (show_buttons) { nag->details.button_up.x = nag->details.x + nag->details.width; - nag->details.button_up.y = nag->details.y; + nag->details.button_up.y = nag->details.y - border; nag->details.button_up.width = button_width; - nag->details.button_up.height = nag->details.height / 2; + nag->details.button_up.height = (border_rect_height + border) / 2; render_details_scroll_button(cairo, nag, &nag->details.button_up); nag->details.button_down.x = nag->details.x + nag->details.width; nag->details.button_down.y = - nag->details.button_up.y + nag->details.button_up.height; + nag->details.button_up.y + nag->details.button_up.height - border; nag->details.button_down.width = button_width; - nag->details.button_down.height = nag->details.height / 2; + nag->details.button_down.height = + border_rect_height - nag->details.button_up.height + border; render_details_scroll_button(cairo, nag, &nag->details.button_down); } + cairo_set_source_u32(cairo, nag->conf->details_border_color); + cairo_rectangle(cairo, margin, nag->details.y - border, + nag->details.width + 2 * border, border_rect_height); + cairo_fill(cairo); + cairo_set_source_u32(cairo, nag->conf->details_background); cairo_rectangle(cairo, nag->details.x, nag->details.y, nag->details.width, nag->details.height); @@ -447,7 +458,7 @@ render_button(cairo_t *cairo, struct nag *nag, struct button *button, } button->x = *x - border - text_width - padding * 2 + 1; - button->y = (int)(ideal_height - text_height) / 2 - padding + 1; + button->y = (int)(ideal_height - text_height) / 2 - padding; button->width = text_width + padding * 2; button->height = text_height + padding * 2; @@ -1464,14 +1475,16 @@ conf_init(struct conf *conf) conf->keyboard_focus = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; conf->bar_border_thickness = 2; conf->message_padding = 8; - conf->details_border_thickness = 3; conf->button_border_thickness = 3; conf->button_gap = 20; conf->button_gap_close = 15; conf->button_margin_right = 2; conf->button_padding = 3; conf->button_background = 0x680A0AFF; + conf->details_margin = 11; + conf->details_border_thickness = 3; conf->details_background = 0x680A0AFF; + conf->details_border_color = 0x680A0AFF; conf->background = 0x900000FF; conf->text = 0xFFFFFFFF; conf->button_text = 0xFFFFFFFF; @@ -1551,16 +1564,18 @@ nag_parse_options(int argc, char **argv, struct nag *nag, TO_COLOR_BORDER_BOTTOM, TO_COLOR_BUTTON_BG, TO_COLOR_DETAILS, + TO_COLOR_DETAILS_BORDER, TO_COLOR_TEXT, TO_COLOR_BUTTON_TEXT, TO_THICK_BAR_BORDER, TO_PADDING_MESSAGE, - TO_THICK_DET_BORDER, + TO_THICK_DETAILS_BORDER, TO_THICK_BTN_BORDER, TO_GAP_BTN, TO_GAP_BTN_DISMISS, TO_MARGIN_BTN_RIGHT, TO_PADDING_BTN, + TO_MARGIN_DETAILS, }; static const struct option opts[] = { @@ -1587,8 +1602,10 @@ nag_parse_options(int argc, char **argv, struct nag *nag, {"button-text-color", required_argument, NULL, TO_COLOR_BUTTON_TEXT}, {"border-bottom-size", required_argument, NULL, TO_THICK_BAR_BORDER}, {"message-padding", required_argument, NULL, TO_PADDING_MESSAGE}, - {"details-border-size", required_argument, NULL, TO_THICK_DET_BORDER}, + {"details-border-size", required_argument, NULL, TO_THICK_DETAILS_BORDER}, {"details-background-color", required_argument, NULL, TO_COLOR_DETAILS}, + {"details-border-color", required_argument, NULL, TO_COLOR_DETAILS_BORDER}, + {"details-margin", required_argument, NULL, TO_MARGIN_DETAILS}, {"button-border-size", required_argument, NULL, TO_THICK_BTN_BORDER}, {"button-gap", required_argument, NULL, TO_GAP_BTN}, {"button-dismiss-gap", required_argument, NULL, TO_GAP_BTN_DISMISS}, @@ -1633,6 +1650,9 @@ nag_parse_options(int argc, char **argv, struct nag *nag, " --details-border-size size Thickness for the details border.\n" " --details-background-color RRGGBB[AA]\n" " Details background color.\n" + " --details-border-color RRGGBB[AA]\n" + " Details border color.\n" + " --details-margin margin Margin for the details.\n" " --button-border-size size Thickness for the button border.\n" " --button-gap gap Size of the gap between buttons\n" " --button-dismiss-gap gap Size of the gap for dismiss button.\n" @@ -1769,6 +1789,11 @@ nag_parse_options(int argc, char **argv, struct nag *nag, fprintf(stderr, "Invalid details background color: %s\n", optarg); } break; + case TO_COLOR_DETAILS_BORDER: + if (!parse_color(optarg, &conf->details_border_color)) { + fprintf(stderr, "Invalid details border color: %s\n", optarg); + } + break; case TO_COLOR_TEXT: /* Text color */ if (!parse_color(optarg, &conf->text)) { fprintf(stderr, "Invalid text color: %s\n", optarg); @@ -1785,7 +1810,7 @@ nag_parse_options(int argc, char **argv, struct nag *nag, case TO_PADDING_MESSAGE: /* Message padding */ conf->message_padding = strtol(optarg, NULL, 0); break; - case TO_THICK_DET_BORDER: /* Details border thickness */ + case TO_THICK_DETAILS_BORDER: /* Details border thickness */ conf->details_border_thickness = strtol(optarg, NULL, 0); break; case TO_THICK_BTN_BORDER: /* Button border thickness */ @@ -1803,6 +1828,9 @@ nag_parse_options(int argc, char **argv, struct nag *nag, case TO_PADDING_BTN: /* Padding for the button text */ conf->button_padding = strtol(optarg, NULL, 0); break; + case TO_MARGIN_DETAILS: + conf->details_margin = strtol(optarg, NULL, 0); + break; default: /* Help or unknown flag */ fprintf(c == 'h' ? stdout : stderr, "%s", usage); return LAB_EXIT_FAILURE; diff --git a/data/labwc-session.target b/data/labwc-session.target new file mode 100644 index 00000000..e71f20e5 --- /dev/null +++ b/data/labwc-session.target @@ -0,0 +1,6 @@ +[Unit] +Description=labwc session +Documentation=man:labwc(1) man:systemd.special(7) +BindsTo=graphical-session.target +Wants=graphical-session-pre.target +After=graphical-session-pre.target diff --git a/docs/autostart b/docs/autostart index b045ed82..17fcc270 100644 --- a/docs/autostart +++ b/docs/autostart @@ -1,5 +1,13 @@ # Example autostart file +# When running under systemd, uncomment the systemctl line below to pull in +# graphical-session.target via labwc-session.target. This lets systemd user +# services declaring WantedBy=graphical-session.target (panels, portals, +# notification daemons, etc.) start in sync with the labwc session. Enable +# individual services with: systemctl --user enable +# +# systemctl --user --no-block start labwc-session.target + # Set background color. swaybg -c '#113344' >/dev/null 2>&1 & diff --git a/docs/labnag.1.scd b/docs/labnag.1.scd index 6d4b959f..bf237d00 100644 --- a/docs/labnag.1.scd +++ b/docs/labnag.1.scd @@ -95,6 +95,12 @@ _labnag_ [options...] *--details-border-size* Set the thickness for the details border. +*--details-border-color* + Set the color of the details border. + +*--details-margin* + Set the margin for the details. + *--button-border-size* Set the thickness for the button border. diff --git a/docs/labwc-actions.5.scd b/docs/labwc-actions.5.scd index de1dc00d..fb616340 100644 --- a/docs/labwc-actions.5.scd +++ b/docs/labwc-actions.5.scd @@ -514,7 +514,7 @@ Actions that execute other actions. Used in keyboard/mouse bindings. "right-occupied" directions will not wrap. *tiled* [up|right|down|left|up-left|up-right|down-left|down-right|center|any] - Whether the client is tiled (snapped) along the the + Whether the client is tiled (snapped) along the indicated screen edge. *tiled_region* diff --git a/docs/labwc-config.5.scd b/docs/labwc-config.5.scd index ac94d54c..8ea88d8f 100644 --- a/docs/labwc-config.5.scd +++ b/docs/labwc-config.5.scd @@ -493,6 +493,13 @@ this is for compatibility with Openbox. ** [yes|no] Raise window to top when focused. Default is no. +** [milliseconds] + When raiseOnFocus is enabled, delay the actual raise by this many + milliseconds. Default is 0 (raise immediately). A subsequent focus + change before the timer elapses restarts or cancels the pending raise. + Useful together with followMouse to avoid brief passes of the cursor + stacking up z-order changes. + ## WINDOW SNAPPING Windows may be "snapped" to an edge or user-defined region of an output when @@ -851,7 +858,7 @@ overrideInhibition="">* A-Space - show window menu ``` - Audio and MonBrightness keys are also bound to amixer and + Audio and MonBrightness keys are also bound to pactl and brightnessctl, respectively. ** @@ -1140,6 +1147,7 @@ Note: To rotate touch events with output rotation, use the libinput + 1.0 @@ -1244,19 +1252,24 @@ Note: To rotate touch events with output rotation, use the libinput The default method depends on the touchpad hardware. -** [none|twofinger|edge] - Configure the method by which physical movements on a touchpad are - mapped to scroll events. +** [none|twofinger|edge|onbutton] + Configure the method by which physical movements are mapped to scroll events. The scroll methods available are: - *twofinger* - Scroll by two fingers being placed on the surface of the touchpad, then moving those fingers vertically or horizontally. - *edge* - Scroll by moving a single finger along the right edge (vertical scroll) or bottom edge (horizontal scroll). + - *onbutton* - Scroll by pressing a button. - *none* - No scroll events will be produced. The default method depends on the touchpad hardware. +** [button] + Set the button used for the *onbutton* scroll method. + + *button* is the decimal form of a value from `linux/input-event-codes.h`. + ** [yes|no|disabledOnExternalMouse] Optionally enable or disable sending any device events. diff --git a/docs/labwc.1.scd b/docs/labwc.1.scd index 2dab30a5..31f28c19 100644 --- a/docs/labwc.1.scd +++ b/docs/labwc.1.scd @@ -118,6 +118,25 @@ this is accomplished by setting the session variables to empty strings. For systemd, the command `systemctl --user unset-environment` will be invoked to actually remove the variables from the activation environment. +A systemd user unit named `labwc-session.target` is also shipped alongside +the compositor for users who want to integrate labwc with systemd. It binds +to the standard `graphical-session.target`, so systemd user services can +start and stop in sync with the labwc session when they declare a WantedBy +or PartOf relationship to that target. Labwc does not activate the target +itself; users opt in by adding lines like the following to their +*autostart* and *shutdown* files: + +``` +systemctl --user --no-block start labwc-session.target +systemctl --user stop graphical-session.target +``` + +The example *autostart* and *shutdown* files shipped with labwc include +these commented out. To have a user service automatically started with +the session, enable it so the corresponding symlink under the +graphical-session.target.wants directory exists, for example by running +"systemctl --user enable dms.service". + # ENVIRONMENT VARIABLES Set the environment variables listed below to enable specific debug options. diff --git a/docs/rc.xml.all b/docs/rc.xml.all index 6d543b46..9755530b 100644 --- a/docs/rc.xml.all +++ b/docs/rc.xml.all @@ -158,6 +158,8 @@ no yes no + + 0 @@ -292,13 +294,13 @@ - + - + - + @@ -592,7 +594,7 @@ - accelProfile [flat|adaptive] - tapButtonMap [lrm|lmr] - clickMethod [none|buttonAreas|clickfinger] - - scrollMethod [twoFinger|edge|none] + - scrollMethod [twoFinger|edge|onbutton|none] - sendEventsMode [yes|no|disabledOnExternalMouse] - calibrationMatrix [six float values split by space] - scrollFactor [float] @@ -618,6 +620,7 @@ + 1.0 diff --git a/docs/shutdown b/docs/shutdown index feed6508..a036ff53 100644 --- a/docs/shutdown +++ b/docs/shutdown @@ -3,3 +3,11 @@ # This file is executed as a shell script when labwc is preparing to terminate # itself. # For further details see labwc-config(5). + +# When running under systemd, uncomment the systemctl line below to tear down +# graphical-session.target (which cascades to labwc-session.target via +# BindsTo, and to any service declaring PartOf=graphical-session.target). +# Running synchronously here ensures those services are stopped before the +# Wayland socket goes away, avoiding "Broken pipe" failures on teardown. +# +# systemctl --user stop graphical-session.target diff --git a/include/config/default-bindings.h b/include/config/default-bindings.h index f2042c28..a49f60f4 100644 --- a/include/config/default-bindings.h +++ b/include/config/default-bindings.h @@ -88,21 +88,21 @@ static struct key_combos { .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master 5%-", + .value = "pactl set-sink-volume @DEFAULT_SINK@ -5%", }, }, { .binding = "XF86AudioRaiseVolume", .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master 5%+", + .value = "pactl set-sink-volume @DEFAULT_SINK@ +5%", }, }, { .binding = "XF86AudioMute", .action = "Execute", .attributes[0] = { .name = "command", - .value = "amixer sset Master toggle", + .value = "pactl set-sink-mute @DEFAULT_SINK@ toggle", }, }, { .binding = "XF86MonBrightnessUp", diff --git a/include/config/libinput.h b/include/config/libinput.h index 80b3fc10..077bc011 100644 --- a/include/config/libinput.h +++ b/include/config/libinput.h @@ -31,6 +31,7 @@ struct libinput_category { int dwt; /* -1 or libinput_config_dwt_state */ int click_method; /* -1 or libinput_config_click_method */ int scroll_method; /* -1 or libinput_config_scroll_method */ + int scroll_button; /* -1 or a button from linux/input_event_codes.h */ int send_events_mode; /* -1 or libinput_config_send_events_mode */ bool have_calibration_matrix; double scroll_factor; diff --git a/include/config/rcxml.h b/include/config/rcxml.h index 8a2c606c..3ef7bd67 100644 --- a/include/config/rcxml.h +++ b/include/config/rcxml.h @@ -90,6 +90,7 @@ struct rcxml { bool focus_follow_mouse; bool focus_follow_mouse_requires_movement; bool raise_on_focus; + uint32_t raise_on_focus_delay_ms; /* theme */ char *theme_name; diff --git a/include/labwc.h b/include/labwc.h index 511fdd65..dc4c1311 100644 --- a/include/labwc.h +++ b/include/labwc.h @@ -150,6 +150,11 @@ struct seat { struct server { struct wl_display *wl_display; struct wl_event_loop *wl_event_loop; /* Can be used for timer events */ + + /* Pending auto-raise timer (used when rc.raise_on_focus_delay_ms > 0) */ + struct view *pending_auto_raise_view; + struct wl_event_source *pending_auto_raise_timer; + struct wlr_renderer *renderer; struct wlr_allocator *allocator; struct wlr_backend *backend; @@ -343,6 +348,13 @@ void xdg_shell_finish(void); */ void desktop_focus_view(struct view *view, bool raise); +/** + * desktop_cancel_pending_auto_raise() - cancel any pending delayed auto-raise + * (from raiseOnFocusDelay). Called when a view is being destroyed, on config + * reload, or when a new focus change with raise=false supersedes the pending. + */ +void desktop_cancel_pending_auto_raise(void); + /** * desktop_focus_view_or_surface() - like desktop_focus_view() but can * also focus other (e.g. xwayland-unmanaged) surfaces diff --git a/meson.build b/meson.build index 2c2d8e57..7e345ba6 100644 --- a/meson.build +++ b/meson.build @@ -211,6 +211,17 @@ install_data('data/labwc.desktop', install_dir: get_option('datadir') / 'wayland install_data('data/labwc-portals.conf', install_dir: get_option('datadir') / 'xdg-desktop-portal') +# Install labwc-session.target so that systemd user services with +# WantedBy=graphical-session.target can be started and stopped in sync +# with a labwc session (see labwc(1) SESSION MANAGEMENT for the opt-in +# autostart/shutdown snippet). +systemd_feat = get_option('systemd-session') +systemd = dependency('systemd', required: systemd_feat) +if systemd.found() + install_data('data/labwc-session.target', + install_dir: systemd.get_variable('systemduserunitdir')) +endif + icons = ['labwc-symbolic.svg', 'labwc.svg'] foreach icon : icons icon_path = join_paths('data', icon) diff --git a/meson_options.txt b/meson_options.txt index a47efa86..e059220d 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,6 +4,7 @@ option('svg', type: 'feature', value: 'enabled', description: 'Enable svg window option('icon', type: 'feature', value: 'enabled', description: 'Enable window icons') option('labnag', type: 'feature', value: 'auto', description: 'Build labnag notification daemon') option('nls', type: 'feature', value: 'auto', description: 'Enable native language support') +option('systemd-session', type: 'feature', value: 'auto', description: 'Install labwc-session.target systemd user unit') option('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer') option('test', type: 'feature', value: 'disabled', description: 'Run tests') option('sections', type: 'feature', value: 'disabled', description: 'Show unused functions') diff --git a/src/config/libinput.c b/src/config/libinput.c index 1209d267..59bf469e 100644 --- a/src/config/libinput.c +++ b/src/config/libinput.c @@ -25,6 +25,7 @@ libinput_category_init(struct libinput_category *l) l->dwt = -1; l->click_method = -1; l->scroll_method = -1; + l->scroll_button = -1; l->send_events_mode = -1; l->have_calibration_matrix = false; l->scroll_factor = 1.0; diff --git a/src/config/rcxml.c b/src/config/rcxml.c index 6f56f7ae..aea27d8d 100644 --- a/src/config/rcxml.c +++ b/src/config/rcxml.c @@ -893,9 +893,19 @@ fill_libinput_category(xmlNode *node) } else if (!strcasecmp(content, "twofinger")) { category->scroll_method = LIBINPUT_CONFIG_SCROLL_2FG; + } else if (!strcasecmp(content, "onbutton")) { + category->scroll_method = + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; } else { wlr_log(WLR_ERROR, "invalid scrollMethod"); } + } else if (!strcasecmp(key, "scrollButton")) { + int button = atoi(content); + if (button != 0) { + category->scroll_button = button; + } else { + wlr_log(WLR_ERROR, "invalid scrollButton"); + } } else if (!strcasecmp(key, "sendEventsMode")) { category->send_events_mode = get_send_events_mode(content); @@ -1187,6 +1197,9 @@ entry(xmlNode *node, char *nodename, char *content) set_bool(content, &rc.focus_follow_mouse_requires_movement); } else if (!strcasecmp(nodename, "raiseOnFocus.focus")) { set_bool(content, &rc.raise_on_focus); + } else if (!strcasecmp(nodename, "raiseOnFocusDelay.focus")) { + long val = strtol(content, NULL, 10); + rc.raise_on_focus_delay_ms = val > 0 ? (uint32_t)val : 0; } else if (!strcasecmp(nodename, "doubleClickTime.mouse")) { long doubleclick_time_parsed = strtol(content, NULL, 10); if (doubleclick_time_parsed > 0) { @@ -1522,6 +1535,7 @@ rcxml_init(void) rc.focus_follow_mouse = false; rc.focus_follow_mouse_requires_movement = true; rc.raise_on_focus = false; + rc.raise_on_focus_delay_ms = 0; rc.doubleclick_time = 500; diff --git a/src/cycle/cycle.c b/src/cycle/cycle.c index 77195176..2649f1e5 100644 --- a/src/cycle/cycle.c +++ b/src/cycle/cycle.c @@ -167,7 +167,7 @@ cycle_begin(enum lab_cycle_dir direction, struct view *active_view = server.active_view; if (active_view && active_view->cycle_link.next) { - /* Select the active view it's in the cycle list */ + /* Select the active view if it's in the cycle list */ server.cycle.selected_view = active_view; } else { /* Otherwise, select the first view in the cycle list */ diff --git a/src/desktop.c b/src/desktop.c index 4c7870f1..57ef9e3c 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -9,6 +9,7 @@ #include #include #include "common/scene-helpers.h" +#include "config/rcxml.h" #include "dnd.h" #include "labwc.h" #include "layers.h" @@ -65,8 +66,51 @@ set_or_offer_focus(struct view *view) } } +static int +handle_auto_raise_timer(void *data) +{ + (void)data; + struct view *view = server.pending_auto_raise_view; + server.pending_auto_raise_view = NULL; + + if (view && view->mapped) { + view_move_to_front(view); + } + return 0; /* ignored per wl_event_loop docs */ +} + void -desktop_focus_view(struct view *view, bool raise) +desktop_cancel_pending_auto_raise(void) +{ + server.pending_auto_raise_view = NULL; + if (server.pending_auto_raise_timer) { + /* Disarm by setting to 0 ms */ + wl_event_source_timer_update(server.pending_auto_raise_timer, 0); + } +} + +static void +schedule_delayed_auto_raise(struct view *view) +{ + server.pending_auto_raise_view = view; + if (!server.pending_auto_raise_timer) { + server.pending_auto_raise_timer = + wl_event_loop_add_timer(server.wl_event_loop, + handle_auto_raise_timer, NULL); + } + wl_event_source_timer_update(server.pending_auto_raise_timer, + rc.raise_on_focus_delay_ms); +} + +/* + * The raise_on_focus_delay is only meant to dampen z-order churn from + * focus-follows-mouse cursor passes. Explicit focus changes (alt-tab, + * Focus action, xdg/xwayland activation, etc.) should raise immediately. + * allow_delay is therefore only set when the caller is the sloppy-focus + * path in desktop_focus_view_or_surface(). + */ +static void +desktop_focus_view_internal(struct view *view, bool raise, bool allow_delay) { assert(view); /* @@ -103,8 +147,17 @@ desktop_focus_view(struct view *view, bool raise) workspaces_switch_to(view->workspace, /*update_focus*/ false); } + /* + * A new focus change supersedes any pending auto-raise from a + * previous focus event, regardless of whether we raise now. + */ + desktop_cancel_pending_auto_raise(); if (raise) { - view_move_to_front(view); + if (allow_delay && rc.raise_on_focus_delay_ms > 0) { + schedule_delayed_auto_raise(view); + } else { + view_move_to_front(view); + } } /* @@ -118,6 +171,12 @@ desktop_focus_view(struct view *view, bool raise) show_desktop_reset(); } +void +desktop_focus_view(struct view *view, bool raise) +{ + desktop_focus_view_internal(view, raise, /*allow_delay*/ false); +} + /* TODO: focus layer-shell surfaces also? */ void desktop_focus_view_or_surface(struct seat *seat, struct view *view, @@ -125,7 +184,7 @@ desktop_focus_view_or_surface(struct seat *seat, struct view *view, { assert(view || surface); if (view) { - desktop_focus_view(view, raise); + desktop_focus_view_internal(view, raise, /*allow_delay*/ true); #if HAVE_XWAYLAND } else { struct wlr_xwayland_surface *xsurface = diff --git a/src/interactive.c b/src/interactive.c index f4d8eea1..9214ba30 100644 --- a/src/interactive.c +++ b/src/interactive.c @@ -123,11 +123,12 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) cursor_shape = LAB_CURSOR_GRAB; break; case LAB_INPUT_STATE_RESIZE: { - if (view->shaded || view->fullscreen || - view->maximized == VIEW_AXIS_BOTH) { + if (view->shaded || view->fullscreen) { /* - * We don't allow resizing while shaded, - * fullscreen or maximized in both directions. + * We don't allow resizing while shaded or fullscreen. + * Maximized views are handled below by un-maximizing + * the axes being resized while keeping the current + * geometry as the starting point. */ return; } @@ -141,9 +142,9 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges) } /* - * If tiled or maximized in only one direction, reset - * tiled state and un-maximize the relevant axes, but - * keep the same geometry as the starting point. + * If tiled or maximized, reset tiled state and un-maximize + * the axes that are being resized, but keep the same + * geometry as the starting point. */ enum view_axis maximized = view->maximized; if (server.resize_edges & LAB_EDGES_LEFT_RIGHT) { diff --git a/src/seat.c b/src/seat.c index 93d108be..fe85dc26 100644 --- a/src/seat.c +++ b/src/seat.c @@ -328,6 +328,16 @@ configure_libinput(struct wlr_input_device *wlr_input_device) libinput_device_config_scroll_set_method(libinput_dev, dc->scroll_method); } + libinput_device_config_scroll_set_button(libinput_dev, + libinput_device_config_scroll_get_default_button(libinput_dev)); + if (dc->scroll_button < 0) { + wlr_log(WLR_INFO, "scroll button not configured"); + } else { + wlr_log(WLR_INFO, "scroll button configured (%d)", + dc->scroll_button); + libinput_device_config_scroll_set_button(libinput_dev, dc->scroll_button); + } + libinput_device_config_send_events_set_mode(libinput_dev, libinput_device_config_send_events_get_default_mode(libinput_dev)); if ((dc->send_events_mode != LIBINPUT_CONFIG_SEND_EVENTS_ENABLED diff --git a/src/server.c b/src/server.c index e4b27026..c792d8b4 100644 --- a/src/server.c +++ b/src/server.c @@ -89,6 +89,12 @@ reload_config_and_theme(void) /* Avoid UAF when dialog client is used during reconfigure */ action_prompts_destroy(); + /* + * Cancel any pending auto-raise before reloading config in case the + * raiseOnFocusDelay option was disabled or changed. + */ + desktop_cancel_pending_auto_raise(); + scaled_buffer_invalidate_sharing(); rcxml_finish(); rcxml_read(rc.config_file); diff --git a/src/view.c b/src/view.c index 1d43fcfa..9b6604ad 100644 --- a/src/view.c +++ b/src/view.c @@ -2521,6 +2521,10 @@ view_destroy(struct view *view) server.active_view = NULL; } + if (server.pending_auto_raise_view == view) { + desktop_cancel_pending_auto_raise(); + } + if (server.session_lock_manager->last_active_view == view) { server.session_lock_manager->last_active_view = NULL; }