rebase send-shortcut

This commit is contained in:
elviosak 2026-04-29 20:22:56 -03:00
commit 13a487fb35
24 changed files with 256 additions and 44 deletions

View file

@ -217,9 +217,9 @@ If you have not created an rc.xml config file, default bindings will be:
| `super`-`mouse-right` | resize window | `super`-`mouse-right` | resize window
| `super`-`arrow` | resize window to fill half the output | `super`-`arrow` | resize window to fill half the output
| `alt`-`space` | show the window menu | `alt`-`space` | show the window menu
| `XF86AudioLowerVolume` | amixer sset Master 5%- | `XF86AudioLowerVolume` | pactl set-sink-volume @DEFAULT_SINK@ -5%
| `XF86AudioRaiseVolume` | amixer sset Master 5%+ | `XF86AudioRaiseVolume` | pactl set-sink-volume @DEFAULT_SINK@ +5%
| `XF86AudioMute` | amixer sset Master toggle | `XF86AudioMute` | pactl set-sink-mute @DEFAULT_SINK@ toggle
| `XF86MonBrightnessUp` | brightnessctl set +10% | `XF86MonBrightnessUp` | brightnessctl set +10%
| `XF86MonBrightnessDown` | brightnessctl set 10%- | `XF86MonBrightnessDown` | brightnessctl set 10%-

View file

@ -46,6 +46,7 @@ struct conf {
uint32_t button_text; uint32_t button_text;
uint32_t button_background; uint32_t button_background;
uint32_t details_background; uint32_t details_background;
uint32_t details_border_color;
uint32_t background; uint32_t background;
uint32_t text; uint32_t text;
uint32_t button_border; uint32_t button_border;
@ -60,6 +61,7 @@ struct conf {
ssize_t button_gap_close; ssize_t button_gap_close;
ssize_t button_margin_right; ssize_t button_margin_right;
ssize_t button_padding; ssize_t button_padding;
ssize_t details_margin;
}; };
struct pointer { 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, get_text_size(cairo, nag->conf->font_description, &text_width,
&text_height, NULL, 1, true, "%s", button->text); &text_height, NULL, 1, true, "%s", button->text);
int border = nag->conf->button_border_thickness; int border = nag->conf->details_border_thickness;
int padding = nag->conf->button_padding; 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, cairo_rectangle(cairo, button->x, button->y,
button->width, button->height); button->width, button->height);
cairo_fill(cairo); 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_set_source_u32(cairo, nag->conf->button_text);
cairo_move_to(cairo, button->x + border + padding, 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, render_text(cairo, nag->conf->font_description, 1, true,
"%s", button->text); "%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); NULL, 1, true, "%s", nag->details.button_down.text);
int text_width = up_width > down_width ? up_width : down_width; int text_width = up_width > down_width ? up_width : down_width;
int border = nag->conf->button_border_thickness; int border = nag->conf->details_border_thickness;
int padding = nag->conf->button_padding; int padding = (nag->conf->button_padding / 3) + 2;
return text_width + border * 2 + padding * 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; uint32_t width = nag->width;
int border = nag->conf->details_border_thickness; int border = nag->conf->details_border_thickness;
int margin = nag->conf->details_margin;
int padding = nag->conf->message_padding; int padding = nag->conf->message_padding;
int decor = padding + border; int decor = margin + border;
nag->details.x = decor; nag->details.x = decor;
nag->details.y = y + 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; bool show_buttons = nag->details.offset > 0;
int button_width = get_detailed_scroll_button_width(cairo, nag); int button_width = get_detailed_scroll_button_width(cairo, nag);
if (show_buttons) { if (show_buttons) {
nag->details.width -= button_width; nag->details.width += border - button_width;
pango_layout_set_width(layout, pango_layout_set_width(layout,
(nag->details.width - padding * 2) * PANGO_SCALE); (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) { if (!show_buttons) {
show_buttons = true; show_buttons = true;
nag->details.width -= button_width; nag->details.width += border - button_width;
pango_layout_set_width(layout, pango_layout_set_width(layout,
(nag->details.width - padding * 2) * PANGO_SCALE); (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); nag->details.visible_lines = pango_layout_get_line_count(layout);
int border_rect_height = nag->details.height + 2 * border;
if (show_buttons) { if (show_buttons) {
nag->details.button_up.x = nag->details.x + nag->details.width; 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.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); 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.x = nag->details.x + nag->details.width;
nag->details.button_down.y = 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.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); 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_set_source_u32(cairo, nag->conf->details_background);
cairo_rectangle(cairo, nag->details.x, nag->details.y, cairo_rectangle(cairo, nag->details.x, nag->details.y,
nag->details.width, nag->details.height); 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->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->width = text_width + padding * 2;
button->height = text_height + 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->keyboard_focus = ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE;
conf->bar_border_thickness = 2; conf->bar_border_thickness = 2;
conf->message_padding = 8; conf->message_padding = 8;
conf->details_border_thickness = 3;
conf->button_border_thickness = 3; conf->button_border_thickness = 3;
conf->button_gap = 20; conf->button_gap = 20;
conf->button_gap_close = 15; conf->button_gap_close = 15;
conf->button_margin_right = 2; conf->button_margin_right = 2;
conf->button_padding = 3; conf->button_padding = 3;
conf->button_background = 0x680A0AFF; conf->button_background = 0x680A0AFF;
conf->details_margin = 11;
conf->details_border_thickness = 3;
conf->details_background = 0x680A0AFF; conf->details_background = 0x680A0AFF;
conf->details_border_color = 0x680A0AFF;
conf->background = 0x900000FF; conf->background = 0x900000FF;
conf->text = 0xFFFFFFFF; conf->text = 0xFFFFFFFF;
conf->button_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_BORDER_BOTTOM,
TO_COLOR_BUTTON_BG, TO_COLOR_BUTTON_BG,
TO_COLOR_DETAILS, TO_COLOR_DETAILS,
TO_COLOR_DETAILS_BORDER,
TO_COLOR_TEXT, TO_COLOR_TEXT,
TO_COLOR_BUTTON_TEXT, TO_COLOR_BUTTON_TEXT,
TO_THICK_BAR_BORDER, TO_THICK_BAR_BORDER,
TO_PADDING_MESSAGE, TO_PADDING_MESSAGE,
TO_THICK_DET_BORDER, TO_THICK_DETAILS_BORDER,
TO_THICK_BTN_BORDER, TO_THICK_BTN_BORDER,
TO_GAP_BTN, TO_GAP_BTN,
TO_GAP_BTN_DISMISS, TO_GAP_BTN_DISMISS,
TO_MARGIN_BTN_RIGHT, TO_MARGIN_BTN_RIGHT,
TO_PADDING_BTN, TO_PADDING_BTN,
TO_MARGIN_DETAILS,
}; };
static const struct option opts[] = { 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}, {"button-text-color", required_argument, NULL, TO_COLOR_BUTTON_TEXT},
{"border-bottom-size", required_argument, NULL, TO_THICK_BAR_BORDER}, {"border-bottom-size", required_argument, NULL, TO_THICK_BAR_BORDER},
{"message-padding", required_argument, NULL, TO_PADDING_MESSAGE}, {"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-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-border-size", required_argument, NULL, TO_THICK_BTN_BORDER},
{"button-gap", required_argument, NULL, TO_GAP_BTN}, {"button-gap", required_argument, NULL, TO_GAP_BTN},
{"button-dismiss-gap", required_argument, NULL, TO_GAP_BTN_DISMISS}, {"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-border-size size Thickness for the details border.\n"
" --details-background-color RRGGBB[AA]\n" " --details-background-color RRGGBB[AA]\n"
" Details background color.\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-border-size size Thickness for the button border.\n"
" --button-gap gap Size of the gap between buttons\n" " --button-gap gap Size of the gap between buttons\n"
" --button-dismiss-gap gap Size of the gap for dismiss button.\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); fprintf(stderr, "Invalid details background color: %s\n", optarg);
} }
break; 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 */ case TO_COLOR_TEXT: /* Text color */
if (!parse_color(optarg, &conf->text)) { if (!parse_color(optarg, &conf->text)) {
fprintf(stderr, "Invalid text color: %s\n", optarg); 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 */ case TO_PADDING_MESSAGE: /* Message padding */
conf->message_padding = strtol(optarg, NULL, 0); conf->message_padding = strtol(optarg, NULL, 0);
break; 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); conf->details_border_thickness = strtol(optarg, NULL, 0);
break; break;
case TO_THICK_BTN_BORDER: /* Button border thickness */ 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 */ case TO_PADDING_BTN: /* Padding for the button text */
conf->button_padding = strtol(optarg, NULL, 0); conf->button_padding = strtol(optarg, NULL, 0);
break; break;
case TO_MARGIN_DETAILS:
conf->details_margin = strtol(optarg, NULL, 0);
break;
default: /* Help or unknown flag */ default: /* Help or unknown flag */
fprintf(c == 'h' ? stdout : stderr, "%s", usage); fprintf(c == 'h' ? stdout : stderr, "%s", usage);
return LAB_EXIT_FAILURE; return LAB_EXIT_FAILURE;

View file

@ -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

View file

@ -1,5 +1,13 @@
# Example autostart file # 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 <unit>
#
# systemctl --user --no-block start labwc-session.target
# Set background color. # Set background color.
swaybg -c '#113344' >/dev/null 2>&1 & swaybg -c '#113344' >/dev/null 2>&1 &

View file

@ -95,6 +95,12 @@ _labnag_ [options...]
*--details-border-size* <size> *--details-border-size* <size>
Set the thickness for the details border. Set the thickness for the details border.
*--details-border-color* <RRGGBB[AA]>
Set the color of the details border.
*--details-margin* <margin>
Set the margin for the details.
*--button-border-size* <size> *--button-border-size* <size>
Set the thickness for the button border. Set the thickness for the button border.

View file

@ -514,7 +514,7 @@ Actions that execute other actions. Used in keyboard/mouse bindings.
"right-occupied" directions will not wrap. "right-occupied" directions will not wrap.
*tiled* [up|right|down|left|up-left|up-right|down-left|down-right|center|any] *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. indicated screen edge.
*tiled_region* *tiled_region*

View file

@ -493,6 +493,13 @@ this is for compatibility with Openbox.
*<focus><raiseOnFocus>* [yes|no] *<focus><raiseOnFocus>* [yes|no]
Raise window to top when focused. Default is no. Raise window to top when focused. Default is no.
*<focus><raiseOnFocusDelay>* [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 ## WINDOW SNAPPING
Windows may be "snapped" to an edge or user-defined region of an output when 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 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. brightnessctl, respectively.
*<keyboard><repeatRate>* *<keyboard><repeatRate>*
@ -1140,6 +1147,7 @@ Note: To rotate touch events with output rotation, use the libinput
<disableWhileTyping></disableWhileTyping> <disableWhileTyping></disableWhileTyping>
<clickMethod></clickMethod> <clickMethod></clickMethod>
<scrollMethod></scrollMethod> <scrollMethod></scrollMethod>
<scrollButton></scrollButton>
<sendEventsMode></sendEventsMode> <sendEventsMode></sendEventsMode>
<calibrationMatrix></calibrationMatrix> <calibrationMatrix></calibrationMatrix>
<scrollFactor>1.0</scrollFactor> <scrollFactor>1.0</scrollFactor>
@ -1244,19 +1252,24 @@ Note: To rotate touch events with output rotation, use the libinput
The default method depends on the touchpad hardware. The default method depends on the touchpad hardware.
*<libinput><device><scrollMethod>* [none|twofinger|edge] *<libinput><device><scrollMethod>* [none|twofinger|edge|onbutton]
Configure the method by which physical movements on a touchpad are Configure the method by which physical movements are mapped to scroll events.
mapped to scroll events.
The scroll methods available are: The scroll methods available are:
- *twofinger* - Scroll by two fingers being placed on the surface of the - *twofinger* - Scroll by two fingers being placed on the surface of the
touchpad, then moving those fingers vertically or horizontally. touchpad, then moving those fingers vertically or horizontally.
- *edge* - Scroll by moving a single finger along the right edge - *edge* - Scroll by moving a single finger along the right edge
(vertical scroll) or bottom edge (horizontal scroll). (vertical scroll) or bottom edge (horizontal scroll).
- *onbutton* - Scroll by pressing a button.
- *none* - No scroll events will be produced. - *none* - No scroll events will be produced.
The default method depends on the touchpad hardware. The default method depends on the touchpad hardware.
*<libinput><device><scrollButton>* [button]
Set the button used for the *onbutton* scroll method.
*button* is the decimal form of a value from `linux/input-event-codes.h`.
*<libinput><device><sendEventsMode>* [yes|no|disabledOnExternalMouse] *<libinput><device><sendEventsMode>* [yes|no|disabledOnExternalMouse]
Optionally enable or disable sending any device events. Optionally enable or disable sending any device events.

View file

@ -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 systemd, the command `systemctl --user unset-environment` will be invoked to
actually remove the variables from the activation environment. 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 # ENVIRONMENT VARIABLES
Set the environment variables listed below to enable specific debug options. Set the environment variables listed below to enable specific debug options.

View file

@ -158,6 +158,8 @@
<followMouse>no</followMouse> <followMouse>no</followMouse>
<followMouseRequiresMovement>yes</followMouseRequiresMovement> <followMouseRequiresMovement>yes</followMouseRequiresMovement>
<raiseOnFocus>no</raiseOnFocus> <raiseOnFocus>no</raiseOnFocus>
<!-- Delay (ms) before applying raise-on-focus. 0 = immediate. -->
<raiseOnFocusDelay>0</raiseOnFocusDelay>
</focus> </focus>
<snapping> <snapping>
@ -292,13 +294,13 @@
<action name="ShowMenu" menu="client-menu" atCursor="no" /> <action name="ShowMenu" menu="client-menu" atCursor="no" />
</keybind> </keybind>
<keybind key="XF86AudioLowerVolume"> <keybind key="XF86AudioLowerVolume">
<action name="Execute" command="amixer sset Master 5%-" /> <action name="Execute" command="pactl set-sink-volume @DEFAULT_SINK@ -5%" />
</keybind> </keybind>
<keybind key="XF86AudioRaiseVolume"> <keybind key="XF86AudioRaiseVolume">
<action name="Execute" command="amixer sset Master 5%+" /> <action name="Execute" command="pactl set-sink-volume @DEFAULT_SINK@ +5%" />
</keybind> </keybind>
<keybind key="XF86AudioMute"> <keybind key="XF86AudioMute">
<action name="Execute" command="amixer sset Master toggle" /> <action name="Execute" command="pactl set-sink-mute @DEFAULT_SINK@ toggle" />
</keybind> </keybind>
<keybind key="XF86MonBrightnessUp"> <keybind key="XF86MonBrightnessUp">
<action name="Execute" command="brightnessctl set +10%" /> <action name="Execute" command="brightnessctl set +10%" />
@ -592,7 +594,7 @@
- accelProfile [flat|adaptive] - accelProfile [flat|adaptive]
- tapButtonMap [lrm|lmr] - tapButtonMap [lrm|lmr]
- clickMethod [none|buttonAreas|clickfinger] - clickMethod [none|buttonAreas|clickfinger]
- scrollMethod [twoFinger|edge|none] - scrollMethod [twoFinger|edge|onbutton|none]
- sendEventsMode [yes|no|disabledOnExternalMouse] - sendEventsMode [yes|no|disabledOnExternalMouse]
- calibrationMatrix [six float values split by space] - calibrationMatrix [six float values split by space]
- scrollFactor [float] - scrollFactor [float]
@ -618,6 +620,7 @@
<!-- <disableWhileTyping>yes</disableWhileTyping> --> <!-- <disableWhileTyping>yes</disableWhileTyping> -->
<!-- <clickMethod>buttonAreas</clickMethod> --> <!-- <clickMethod>buttonAreas</clickMethod> -->
<!-- <scrollMethod>twofinger</scrollMethod> --> <!-- <scrollMethod>twofinger</scrollMethod> -->
<!-- <scrollButton>274</scrollButton> -->
<!-- <sendEventsMode>yes</sendEventsMode> --> <!-- <sendEventsMode>yes</sendEventsMode> -->
<!-- <calibrationMatrix>1 0 0 0 1 0</calibrationMatrix> --> <!-- <calibrationMatrix>1 0 0 0 1 0</calibrationMatrix> -->
<scrollFactor>1.0</scrollFactor> <scrollFactor>1.0</scrollFactor>

View file

@ -3,3 +3,11 @@
# This file is executed as a shell script when labwc is preparing to terminate # This file is executed as a shell script when labwc is preparing to terminate
# itself. # itself.
# For further details see labwc-config(5). # 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

View file

@ -88,21 +88,21 @@ static struct key_combos {
.action = "Execute", .action = "Execute",
.attributes[0] = { .attributes[0] = {
.name = "command", .name = "command",
.value = "amixer sset Master 5%-", .value = "pactl set-sink-volume @DEFAULT_SINK@ -5%",
}, },
}, { }, {
.binding = "XF86AudioRaiseVolume", .binding = "XF86AudioRaiseVolume",
.action = "Execute", .action = "Execute",
.attributes[0] = { .attributes[0] = {
.name = "command", .name = "command",
.value = "amixer sset Master 5%+", .value = "pactl set-sink-volume @DEFAULT_SINK@ +5%",
}, },
}, { }, {
.binding = "XF86AudioMute", .binding = "XF86AudioMute",
.action = "Execute", .action = "Execute",
.attributes[0] = { .attributes[0] = {
.name = "command", .name = "command",
.value = "amixer sset Master toggle", .value = "pactl set-sink-mute @DEFAULT_SINK@ toggle",
}, },
}, { }, {
.binding = "XF86MonBrightnessUp", .binding = "XF86MonBrightnessUp",

View file

@ -31,6 +31,7 @@ struct libinput_category {
int dwt; /* -1 or libinput_config_dwt_state */ int dwt; /* -1 or libinput_config_dwt_state */
int click_method; /* -1 or libinput_config_click_method */ int click_method; /* -1 or libinput_config_click_method */
int scroll_method; /* -1 or libinput_config_scroll_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 */ int send_events_mode; /* -1 or libinput_config_send_events_mode */
bool have_calibration_matrix; bool have_calibration_matrix;
double scroll_factor; double scroll_factor;

View file

@ -90,6 +90,7 @@ struct rcxml {
bool focus_follow_mouse; bool focus_follow_mouse;
bool focus_follow_mouse_requires_movement; bool focus_follow_mouse_requires_movement;
bool raise_on_focus; bool raise_on_focus;
uint32_t raise_on_focus_delay_ms;
/* theme */ /* theme */
char *theme_name; char *theme_name;

View file

@ -150,6 +150,11 @@ struct seat {
struct server { struct server {
struct wl_display *wl_display; struct wl_display *wl_display;
struct wl_event_loop *wl_event_loop; /* Can be used for timer events */ 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_renderer *renderer;
struct wlr_allocator *allocator; struct wlr_allocator *allocator;
struct wlr_backend *backend; struct wlr_backend *backend;
@ -343,6 +348,13 @@ void xdg_shell_finish(void);
*/ */
void desktop_focus_view(struct view *view, bool raise); 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 * desktop_focus_view_or_surface() - like desktop_focus_view() but can
* also focus other (e.g. xwayland-unmanaged) surfaces * also focus other (e.g. xwayland-unmanaged) surfaces

View file

@ -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_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'] icons = ['labwc-symbolic.svg', 'labwc.svg']
foreach icon : icons foreach icon : icons
icon_path = join_paths('data', icon) icon_path = join_paths('data', icon)

View file

@ -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('icon', type: 'feature', value: 'enabled', description: 'Enable window icons')
option('labnag', type: 'feature', value: 'auto', description: 'Build labnag notification daemon') option('labnag', type: 'feature', value: 'auto', description: 'Build labnag notification daemon')
option('nls', type: 'feature', value: 'auto', description: 'Enable native language support') 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('static_analyzer', type: 'feature', value: 'disabled', description: 'Run gcc static analyzer')
option('test', type: 'feature', value: 'disabled', description: 'Run tests') option('test', type: 'feature', value: 'disabled', description: 'Run tests')
option('sections', type: 'feature', value: 'disabled', description: 'Show unused functions') option('sections', type: 'feature', value: 'disabled', description: 'Show unused functions')

View file

@ -25,6 +25,7 @@ libinput_category_init(struct libinput_category *l)
l->dwt = -1; l->dwt = -1;
l->click_method = -1; l->click_method = -1;
l->scroll_method = -1; l->scroll_method = -1;
l->scroll_button = -1;
l->send_events_mode = -1; l->send_events_mode = -1;
l->have_calibration_matrix = false; l->have_calibration_matrix = false;
l->scroll_factor = 1.0; l->scroll_factor = 1.0;

View file

@ -893,9 +893,19 @@ fill_libinput_category(xmlNode *node)
} else if (!strcasecmp(content, "twofinger")) { } else if (!strcasecmp(content, "twofinger")) {
category->scroll_method = category->scroll_method =
LIBINPUT_CONFIG_SCROLL_2FG; LIBINPUT_CONFIG_SCROLL_2FG;
} else if (!strcasecmp(content, "onbutton")) {
category->scroll_method =
LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN;
} else { } else {
wlr_log(WLR_ERROR, "invalid scrollMethod"); 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")) { } else if (!strcasecmp(key, "sendEventsMode")) {
category->send_events_mode = category->send_events_mode =
get_send_events_mode(content); 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); set_bool(content, &rc.focus_follow_mouse_requires_movement);
} else if (!strcasecmp(nodename, "raiseOnFocus.focus")) { } else if (!strcasecmp(nodename, "raiseOnFocus.focus")) {
set_bool(content, &rc.raise_on_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")) { } else if (!strcasecmp(nodename, "doubleClickTime.mouse")) {
long doubleclick_time_parsed = strtol(content, NULL, 10); long doubleclick_time_parsed = strtol(content, NULL, 10);
if (doubleclick_time_parsed > 0) { if (doubleclick_time_parsed > 0) {
@ -1522,6 +1535,7 @@ rcxml_init(void)
rc.focus_follow_mouse = false; rc.focus_follow_mouse = false;
rc.focus_follow_mouse_requires_movement = true; rc.focus_follow_mouse_requires_movement = true;
rc.raise_on_focus = false; rc.raise_on_focus = false;
rc.raise_on_focus_delay_ms = 0;
rc.doubleclick_time = 500; rc.doubleclick_time = 500;

View file

@ -167,7 +167,7 @@ cycle_begin(enum lab_cycle_dir direction,
struct view *active_view = server.active_view; struct view *active_view = server.active_view;
if (active_view && active_view->cycle_link.next) { 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; server.cycle.selected_view = active_view;
} else { } else {
/* Otherwise, select the first view in the cycle list */ /* Otherwise, select the first view in the cycle list */

View file

@ -9,6 +9,7 @@
#include <wlr/types/wlr_subcompositor.h> #include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_xdg_shell.h> #include <wlr/types/wlr_xdg_shell.h>
#include "common/scene-helpers.h" #include "common/scene-helpers.h"
#include "config/rcxml.h"
#include "dnd.h" #include "dnd.h"
#include "labwc.h" #include "labwc.h"
#include "layers.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 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); assert(view);
/* /*
@ -103,8 +147,17 @@ desktop_focus_view(struct view *view, bool raise)
workspaces_switch_to(view->workspace, /*update_focus*/ false); 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) { 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(); 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? */ /* TODO: focus layer-shell surfaces also? */
void void
desktop_focus_view_or_surface(struct seat *seat, struct view *view, 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); assert(view || surface);
if (view) { if (view) {
desktop_focus_view(view, raise); desktop_focus_view_internal(view, raise, /*allow_delay*/ true);
#if HAVE_XWAYLAND #if HAVE_XWAYLAND
} else { } else {
struct wlr_xwayland_surface *xsurface = struct wlr_xwayland_surface *xsurface =

View file

@ -123,11 +123,12 @@ interactive_begin(struct view *view, enum input_mode mode, enum lab_edge edges)
cursor_shape = LAB_CURSOR_GRAB; cursor_shape = LAB_CURSOR_GRAB;
break; break;
case LAB_INPUT_STATE_RESIZE: { case LAB_INPUT_STATE_RESIZE: {
if (view->shaded || view->fullscreen || if (view->shaded || view->fullscreen) {
view->maximized == VIEW_AXIS_BOTH) {
/* /*
* We don't allow resizing while shaded, * We don't allow resizing while shaded or fullscreen.
* fullscreen or maximized in both directions. * Maximized views are handled below by un-maximizing
* the axes being resized while keeping the current
* geometry as the starting point.
*/ */
return; 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 * If tiled or maximized, reset tiled state and un-maximize
* tiled state and un-maximize the relevant axes, but * the axes that are being resized, but keep the same
* keep the same geometry as the starting point. * geometry as the starting point.
*/ */
enum view_axis maximized = view->maximized; enum view_axis maximized = view->maximized;
if (server.resize_edges & LAB_EDGES_LEFT_RIGHT) { if (server.resize_edges & LAB_EDGES_LEFT_RIGHT) {

View file

@ -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_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_set_mode(libinput_dev,
libinput_device_config_send_events_get_default_mode(libinput_dev)); libinput_device_config_send_events_get_default_mode(libinput_dev));
if ((dc->send_events_mode != LIBINPUT_CONFIG_SEND_EVENTS_ENABLED if ((dc->send_events_mode != LIBINPUT_CONFIG_SEND_EVENTS_ENABLED

View file

@ -89,6 +89,12 @@ reload_config_and_theme(void)
/* Avoid UAF when dialog client is used during reconfigure */ /* Avoid UAF when dialog client is used during reconfigure */
action_prompts_destroy(); 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(); scaled_buffer_invalidate_sharing();
rcxml_finish(); rcxml_finish();
rcxml_read(rc.config_file); rcxml_read(rc.config_file);

View file

@ -2521,6 +2521,10 @@ view_destroy(struct view *view)
server.active_view = NULL; 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) { if (server.session_lock_manager->last_active_view == view) {
server.session_lock_manager->last_active_view = NULL; server.session_lock_manager->last_active_view = NULL;
} }