From 6bdb152808c39bc3776c34e13c07b25d18272824 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Tue, 14 Jul 2020 17:34:29 +0200 Subject: [PATCH] cursor: make xcursor theme configurable - add a new command to set the theme - export the theme of the default seat through environment variables --- doc/riverctl.1.scd | 6 +++ river/Cursor.zig | 80 +++++++++++++++++++++------------ river/command.zig | 6 ++- river/command/xcursor_theme.zig | 38 ++++++++++++++++ 4 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 river/command/xcursor_theme.zig diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd index 3c03adf..eef17cb 100644 --- a/doc/riverctl.1.scd +++ b/doc/riverctl.1.scd @@ -124,6 +124,12 @@ that tag 1 through 9 are visible. - *outer_padding* _non-negative_integer_ - *view_padding* _non-negative_integer_ +*xcursor-theme* _theme_name_ [_size_] + Set the xcursor theme to _theme_name_ and optionally set the + _size_. The theme of the default seat determines the default + for XWayland and made available through the _XCURSOR_THEME_ and + _XCURSOR_SIZE_ environment variables. + # EXAMPLES Bind bemenu-run to Super+P: diff --git a/river/Cursor.zig b/river/Cursor.zig index 7b72729..5c3e1b1 100644 --- a/river/Cursor.zig +++ b/river/Cursor.zig @@ -39,6 +39,8 @@ const CursorMode = enum { Resize, }; +const default_size = 24; + seat: *Seat, wlr_cursor: *c.wlr_cursor, wlr_xcursor_manager: *c.wlr_xcursor_manager, @@ -71,35 +73,14 @@ pub fn init(self: *Self, seat: *Seat) !void { // Creates a wlroots utility for tracking the cursor image shown on screen. self.wlr_cursor = c.wlr_cursor_create() orelse return error.OutOfMemory; - - // Creates an xcursor manager, another wlroots utility which loads up - // Xcursor themes to source cursor images from and makes sure that cursor - // images are available at all scale factors on the screen (necessary for - // HiDPI support). We add a cursor theme at scale factor 1 to begin with. - self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, 24) orelse return error.OutOfMemory; c.wlr_cursor_attach_output_layout(self.wlr_cursor, seat.input_manager.server.root.wlr_output_layout); - if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, 1) == 0) { - if (build_options.xwayland) { - if (c.wlr_xcursor_manager_get_xcursor( - self.wlr_xcursor_manager, - "left_ptr", - 1, - )) |wlr_xcursor| { - const image: *c.wlr_xcursor_image = wlr_xcursor.*.images[0]; - c.wlr_xwayland_set_cursor( - seat.input_manager.server.wlr_xwayland, - image.buffer, - image.width * 4, - image.width, - image.height, - @intCast(i32, image.hotspot_x), - @intCast(i32, image.hotspot_y), - ); - } - } - } else { - log.err(.cursor, "failed to load an xcursor theme", .{}); - } + + // This is here so that self.wlr_xcursor_manager doesn't need to be an + // optional pointer. This isn't optimal as it does a needless allocation, + // but this is not a hot path. + self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(null, default_size) orelse + return error.OutOfMemory; + try self.setTheme(null, null); self.mode = CursorMode.Passthrough; self.grabbed_view = undefined; @@ -136,6 +117,49 @@ pub fn deinit(self: *Self) void { c.wlr_cursor_destroy(self.wlr_cursor); } +/// Set the cursor theme for the given seat, as well as the xwayland theme if +/// this is the default seat. +pub fn setTheme(self: *Self, theme: ?[*:0]const u8, size: ?u32) !void { + const server = self.seat.input_manager.server; + + c.wlr_xcursor_manager_destroy(self.wlr_xcursor_manager); + self.wlr_xcursor_manager = c.wlr_xcursor_manager_create(theme, size orelse default_size) orelse + return error.OutOfMemory; + + // For each output, ensure a theme of the proper scale is loaded + var it = server.root.outputs.first; + while (it) |node| : (it = node.next) { + const wlr_output = node.data.wlr_output; + if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, wlr_output.scale) != 0) + log.err(.cursor, "failed to load xcursor theme '{}' at scale {}", .{ theme, wlr_output.scale }); + } + + // If this cursor belongs to the default seat, set the xcursor environment + // variables and the xwayland cursor theme. + if (self.seat == self.seat.input_manager.default_seat) { + const size_str = try std.fmt.allocPrint0(util.gpa, "{}", .{size}); + defer util.gpa.free(size_str); + if (c.setenv("XCURSOR_SIZE", size_str, 1) < 0) return error.OutOfMemory; + if (theme) |t| if (c.setenv("XCURSOR_THEME", t, 1) < 0) return error.OutOfMemory; + + if (build_options.xwayland) { + if (c.wlr_xcursor_manager_load(self.wlr_xcursor_manager, 1) == 0) { + const wlr_xcursor = c.wlr_xcursor_manager_get_xcursor(self.wlr_xcursor_manager, "left_ptr", 1).?; + const image: *c.wlr_xcursor_image = wlr_xcursor.*.images[0]; + c.wlr_xwayland_set_cursor( + server.wlr_xwayland, + image.buffer, + image.width * 4, + image.width, + image.height, + @intCast(i32, image.hotspot_x), + @intCast(i32, image.hotspot_y), + ); + } else log.err(.cursor, "failed to load xcursor theme '{}' at scale 1", .{theme}); + } + } +} + fn handleAxis(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { // This event is forwarded by the cursor when a pointer emits an axis event, // for example when you move the scroll wheel. diff --git a/river/command.zig b/river/command.zig index f71f18f..8974f94 100644 --- a/river/command.zig +++ b/river/command.zig @@ -39,6 +39,7 @@ const impl = struct { const toggleFocusedTags = @import("command/tags.zig").toggleFocusedTags; const toggleFullscreen = @import("command/toggle_fullscreen.zig").toggleFullscreen; const toggleViewTags = @import("command/tags.zig").toggleViewTags; + const xcursorTheme = @import("command/xcursor_theme.zig").xcursorTheme; const zoom = @import("command/zoom.zig").zoom; }; @@ -79,9 +80,10 @@ const str_to_impl_fn = [_]struct { .{ .name = "spawn", .impl = impl.spawn }, .{ .name = "toggle-float", .impl = impl.toggleFloat }, .{ .name = "toggle-focused-tags", .impl = impl.toggleFocusedTags }, - .{ .name = "toggle-view-tags", .impl = impl.toggleViewTags }, - .{ .name = "zoom", .impl = impl.zoom }, .{ .name = "toggle-fullscreen", .impl = impl.toggleFullscreen }, + .{ .name = "toggle-view-tags", .impl = impl.toggleViewTags }, + .{ .name = "xcursor-theme", .impl = impl.xcursorTheme }, + .{ .name = "zoom", .impl = impl.zoom }, }; // zig fmt: on diff --git a/river/command/xcursor_theme.zig b/river/command/xcursor_theme.zig new file mode 100644 index 0000000..f7a94ed --- /dev/null +++ b/river/command/xcursor_theme.zig @@ -0,0 +1,38 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2020 Isaac Freund +// +// 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 . + +const std = @import("std"); + +const Error = @import("../command.zig").Error; +const Seat = @import("../Seat.zig"); + +pub fn xcursorTheme( + allocator: *std.mem.Allocator, + seat: *Seat, + args: []const []const u8, + out: *?[]const u8, +) Error!void { + if (args.len < 2) return Error.NotEnoughArguments; + if (args.len > 3) return Error.TooManyArguments; + + // TODO: get rid of this allocation + const name = try std.cstr.addNullByte(allocator, args[1]); + defer allocator.free(name); + const size = if (args.len == 3) try std.fmt.parseInt(u32, args[2], 10) else null; + + try seat.cursor.setTheme(name, size); +}