Cursor: Add a hide-cursor command

From the riverctl.1 man page:

*hide-cursor* *timeout* _timeout_
    Hide the cursor if it wasn't moved in the last _timeout_
    milliseconds until it is moved again.
    The default value is 0, which disables automatically hiding the
    cursor. Show the cursor again on any movement.

*hide-cursor* *when-typing* *enabled*|*disabled*
    Hide the cursor when pressing any non-modifier key. Show the cursor
    again on any movement.
This commit is contained in:
Duncan Overbruck 2022-02-28 00:39:10 +01:00 committed by Isaac Freund
parent 60fdefc3fd
commit 0b8758a422
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
6 changed files with 122 additions and 0 deletions

View File

@ -288,6 +288,15 @@ A complete list may be found in _/usr/include/linux/input-event-codes.h_
If the view to be focused is on an output that does not have focus, If the view to be focused is on an output that does not have focus,
focus is switched to that output. focus is switched to that output.
*hide-cursor* *timeout* _timeout_
Hide the cursor if it wasn't moved in the last _timeout_ milliseconds
until it is moved again. The default value is 0, which disables
automatically hiding the cursor. Show the cursor again on any movement.
*hide-cursor* *when-typing* *enabled*|*disabled*
Hide the cursor when pressing any non-modifier key. Show the cursor
again on any movement.
*set-cursor-warp* *disabled*|*on-output-change* *set-cursor-warp* *disabled*|*on-output-change*
Set the cursor warp mode. There are two available modes: Set the cursor warp mode. There are two available modes:

View File

@ -36,6 +36,11 @@ pub const WarpCursorMode = enum {
@"on-output-change", @"on-output-change",
}; };
pub const HideCursorWhenTypingMode = enum {
disabled,
enabled,
};
/// Color of background in RGBA (alpha should only affect nested sessions) /// Color of background in RGBA (alpha should only affect nested sessions)
background_color: [4]f32 = [_]f32{ 0.0, 0.16862745, 0.21176471, 1.0 }, // Solarized base03 background_color: [4]f32 = [_]f32{ 0.0, 0.16862745, 0.21176471, 1.0 }, // Solarized base03
@ -85,6 +90,12 @@ repeat_rate: u31 = 25,
/// Keyboard repeat delay in milliseconds /// Keyboard repeat delay in milliseconds
repeat_delay: u31 = 600, repeat_delay: u31 = 600,
/// Cursor hide timeout in milliseconds
cursor_hide_timeout: u31 = 0,
/// Hide the cursor while typing
cursor_hide_when_typing: HideCursorWhenTypingMode = .disabled,
pub fn init() !Self { pub fn init() !Self {
var self = Self{ var self = Self{
.mode_to_id = std.StringHashMap(usize).init(util.gpa), .mode_to_id = std.StringHashMap(usize).init(util.gpa),

View File

@ -102,6 +102,10 @@ constraint: ?*wlr.PointerConstraintV1 = null,
/// Number of distinct buttons currently pressed /// Number of distinct buttons currently pressed
pressed_count: u32 = 0, pressed_count: u32 = 0,
hide_cursor_timer: *wl.EventSource,
hidden: bool = false,
axis: wl.Listener(*wlr.Pointer.event.Axis) = wl.Listener(*wlr.Pointer.event.Axis).init(handleAxis), axis: wl.Listener(*wlr.Pointer.event.Axis) = wl.Listener(*wlr.Pointer.event.Axis).init(handleAxis),
frame: wl.Listener(*wlr.Cursor) = wl.Listener(*wlr.Cursor).init(handleFrame), frame: wl.Listener(*wlr.Cursor) = wl.Listener(*wlr.Cursor).init(handleFrame),
button: wl.Listener(*wlr.Pointer.event.Button) = button: wl.Listener(*wlr.Pointer.event.Button) =
@ -136,12 +140,16 @@ pub fn init(self: *Self, seat: *Seat) !void {
const xcursor_manager = try wlr.XcursorManager.create(null, default_size); const xcursor_manager = try wlr.XcursorManager.create(null, default_size);
errdefer xcursor_manager.destroy(); errdefer xcursor_manager.destroy();
const event_loop = server.wl_server.getEventLoop();
self.* = .{ self.* = .{
.seat = seat, .seat = seat,
.wlr_cursor = wlr_cursor, .wlr_cursor = wlr_cursor,
.pointer_gestures = try wlr.PointerGesturesV1.create(server.wl_server), .pointer_gestures = try wlr.PointerGesturesV1.create(server.wl_server),
.xcursor_manager = xcursor_manager, .xcursor_manager = xcursor_manager,
.hide_cursor_timer = try event_loop.addTimer(*Self, handleHideCursorTimeout, self),
}; };
errdefer self.hide_cursor_timer.remove();
try self.hide_cursor_timer.timerUpdate(server.config.cursor_hide_timeout);
try self.setTheme(null, null); try self.setTheme(null, null);
// wlr_cursor *only* displays an image on screen. It does not move around // wlr_cursor *only* displays an image on screen. It does not move around
@ -165,6 +173,7 @@ pub fn init(self: *Self, seat: *Seat) !void {
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
self.hide_cursor_timer.remove();
self.xcursor_manager.destroy(); self.xcursor_manager.destroy();
self.wlr_cursor.destroy(); self.wlr_cursor.destroy();
} }
@ -250,6 +259,7 @@ fn handleAxis(listener: *wl.Listener(*wlr.Pointer.event.Axis), event: *wlr.Point
const self = @fieldParentPtr(Self, "axis", listener); const self = @fieldParentPtr(Self, "axis", listener);
self.seat.handleActivity(); self.seat.handleActivity();
self.unhide();
// Notify the client with pointer focus of the axis event. // Notify the client with pointer focus of the axis event.
self.seat.wlr_seat.pointerNotifyAxis( self.seat.wlr_seat.pointerNotifyAxis(
@ -265,6 +275,7 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
const self = @fieldParentPtr(Self, "button", listener); const self = @fieldParentPtr(Self, "button", listener);
self.seat.handleActivity(); self.seat.handleActivity();
self.unhide();
if (event.state == .released) { if (event.state == .released) {
assert(self.pressed_count > 0); assert(self.pressed_count > 0);
@ -487,6 +498,32 @@ fn handleRequestSetCursor(
} }
} }
pub fn hide(self: *Self) void {
if (self.pressed_count > 0) return;
self.hidden = true;
self.wlr_cursor.setImage(null, 0, 0, 0, 0, 0, 0);
self.image = .unknown;
self.seat.wlr_seat.pointerNotifyClearFocus();
self.hide_cursor_timer.timerUpdate(0) catch {
log.err("failed to update cursor hide timeout", .{});
};
}
pub fn unhide(self: *Self) void {
self.hide_cursor_timer.timerUpdate(server.config.cursor_hide_timeout) catch {
log.err("failed to update cursor hide timeout", .{});
};
if (!self.hidden) return;
self.hidden = false;
self.updateState();
}
fn handleHideCursorTimeout(self: *Self) callconv(.C) c_int {
log.debug("hide cursor timeout", .{});
self.hide();
return 0;
}
const SurfaceAtResult = struct { const SurfaceAtResult = struct {
surface: *wlr.Surface, surface: *wlr.Surface,
sx: f64, sx: f64,
@ -762,6 +799,8 @@ fn leaveMode(self: *Self, event: *wlr.Pointer.event.Button) void {
} }
fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64, unaccel_dx: f64, unaccel_dy: f64) void { fn processMotion(self: *Self, device: *wlr.InputDevice, time: u32, delta_x: f64, delta_y: f64, unaccel_dx: f64, unaccel_dy: f64) void {
self.unhide();
server.input_manager.relative_pointer_manager.sendRelativeMotion( server.input_manager.relative_pointer_manager.sendRelativeMotion(
self.seat.wlr_seat, self.seat.wlr_seat,
@as(u64, time) * 1000, @as(u64, time) * 1000,

View File

@ -91,8 +91,12 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
var handled = false; var handled = false;
var non_modifier_pressed = false;
// First check translated keysyms as xkb reports them // First check translated keysyms as xkb reports them
for (wlr_keyboard.xkb_state.?.keyGetSyms(keycode)) |sym| { for (wlr_keyboard.xkb_state.?.keyGetSyms(keycode)) |sym| {
if (!released and !isModifier(sym)) non_modifier_pressed = true;
// Handle builtin mapping only when keys are pressed // Handle builtin mapping only when keys are pressed
if (!released and handleBuiltinMapping(sym)) { if (!released and handleBuiltinMapping(sym)) {
handled = true; handled = true;
@ -125,6 +129,14 @@ fn handleKey(listener: *wl.Listener(*wlr.Keyboard.event.Key), event: *wlr.Keyboa
wlr_seat.setKeyboard(self.input_device); wlr_seat.setKeyboard(self.input_device);
wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state); wlr_seat.keyboardNotifyKey(event.time_msec, event.keycode, event.state);
} }
if (non_modifier_pressed and server.config.cursor_hide_when_typing == .enabled) {
self.seat.cursor.hide();
}
}
fn isModifier(keysym: xkb.Keysym) bool {
return @enumToInt(keysym) >= xkb.Keysym.Shift_L and @enumToInt(keysym) <= xkb.Keysym.Hyper_R;
} }
/// Simply pass modifiers along to the client /// Simply pass modifiers along to the client

View File

@ -59,6 +59,7 @@ const command_impls = std.ComptimeStringMap(
.{ "focus-output", @import("command/output.zig").focusOutput }, .{ "focus-output", @import("command/output.zig").focusOutput },
.{ "focus-previous-tags", @import("command/tags.zig").focusPreviousTags }, .{ "focus-previous-tags", @import("command/tags.zig").focusPreviousTags },
.{ "focus-view", @import("command/focus_view.zig").focusView }, .{ "focus-view", @import("command/focus_view.zig").focusView },
.{ "hide-cursor", @import("command/cursor.zig").cursor },
.{ "input", @import("command/input.zig").input }, .{ "input", @import("command/input.zig").input },
.{ "list-input-configs", @import("command/input.zig").listInputConfigs}, .{ "list-input-configs", @import("command/input.zig").listInputConfigs},
.{ "list-inputs", @import("command/input.zig").listInputs }, .{ "list-inputs", @import("command/input.zig").listInputs },

50
river/command/cursor.zig Normal file
View File

@ -0,0 +1,50 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2022 The River Developers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const util = @import("../util.zig");
const server = &@import("../main.zig").server;
const Config = @import("../Config.zig");
const Error = @import("../command.zig").Error;
const Seat = @import("../Seat.zig");
pub fn cursor(
_: *Seat,
args: []const [:0]const u8,
_: *?[]const u8,
) Error!void {
if (std.mem.eql(u8, "timeout", args[1])) {
if (args.len < 3) return Error.NotEnoughArguments;
if (args.len > 3) return Error.TooManyArguments;
server.config.cursor_hide_timeout = try std.fmt.parseInt(u31, args[2], 10);
var seat_it = server.input_manager.seats.first;
while (seat_it) |seat_node| : (seat_it = seat_node.next) {
const seat = &seat_node.data;
seat.cursor.unhide();
}
} else if (std.mem.eql(u8, "when-typing", args[1])) {
if (args.len < 3) return Error.NotEnoughArguments;
if (args.len > 3) return Error.TooManyArguments;
server.config.cursor_hide_when_typing = std.meta.stringToEnum(Config.HideCursorWhenTypingMode, args[2]) orelse
return Error.UnknownOption;
} else {
return Error.UnknownOption;
}
}