Cursor: implement initial touch support

Still TODO are:
- Touch support for drags
- Mapping input devices to outputs (necessary for good multi-monitor
touch support)

Co-authored-by: Daan Vanoverloop <daan@vanoverloop.xyz>
This commit is contained in:
Isaac Freund 2022-06-22 00:34:05 +02:00
parent bd03484b28
commit 2eb013e214
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
3 changed files with 134 additions and 38 deletions

View File

@ -129,6 +129,14 @@ swipe_update: wl.Listener(*wlr.Pointer.event.SwipeUpdate) =
swipe_end: wl.Listener(*wlr.Pointer.event.SwipeEnd) = swipe_end: wl.Listener(*wlr.Pointer.event.SwipeEnd) =
wl.Listener(*wlr.Pointer.event.SwipeEnd).init(handleSwipeEnd), wl.Listener(*wlr.Pointer.event.SwipeEnd).init(handleSwipeEnd),
touch_up: wl.Listener(*wlr.Touch.event.Up) =
wl.Listener(*wlr.Touch.event.Up).init(handleTouchUp),
touch_down: wl.Listener(*wlr.Touch.event.Down) =
wl.Listener(*wlr.Touch.event.Down).init(handleTouchDown),
touch_motion: wl.Listener(*wlr.Touch.event.Motion) =
wl.Listener(*wlr.Touch.event.Motion).init(handleTouchMotion),
touch_frame: wl.Listener(void) = wl.Listener(void).init(handleTouchFrame),
pub fn init(self: *Self, seat: *Seat) !void { pub fn init(self: *Self, seat: *Seat) !void {
const wlr_cursor = try wlr.Cursor.create(); const wlr_cursor = try wlr.Cursor.create();
errdefer wlr_cursor.destroy(); errdefer wlr_cursor.destroy();
@ -170,6 +178,12 @@ pub fn init(self: *Self, seat: *Seat) !void {
wlr_cursor.events.pinch_update.add(&self.pinch_update); wlr_cursor.events.pinch_update.add(&self.pinch_update);
wlr_cursor.events.pinch_end.add(&self.pinch_end); wlr_cursor.events.pinch_end.add(&self.pinch_end);
seat.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor); seat.wlr_seat.events.request_set_cursor.add(&self.request_set_cursor);
// TODO(wlroots) handle the cancel event, blocked on wlroots 0.16.0
wlr_cursor.events.touch_up.add(&self.touch_up);
wlr_cursor.events.touch_down.add(&self.touch_down);
wlr_cursor.events.touch_motion.add(&self.touch_motion);
wlr_cursor.events.touch_frame.add(&self.touch_frame);
} }
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
@ -297,33 +311,13 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
} }
if (self.surfaceAt()) |result| { if (self.surfaceAt()) |result| {
switch (result.parent) { if (result.parent == .view and self.handlePointerMapping(event, result.parent.view)) {
.view => |view| { // If a mapping is triggered don't send events to clients.
// If there is an active mapping for this button which is return;
// handled we are done here
if (self.handlePointerMapping(event, view)) return;
// Otherwise focus the view
self.seat.focus(view);
},
.layer_surface => |layer_surface| {
self.seat.focusOutput(layer_surface.output);
// If a keyboard inteactive layer surface has been clicked on,
// give it keyboard focus.
if (layer_surface.wlr_layer_surface.current.keyboard_interactive == .exclusive) {
self.seat.setFocusRaw(.{ .layer = layer_surface });
} else {
self.seat.focus(null);
}
},
.xwayland_override_redirect => |override_redirect| {
if (!build_options.xwayland) unreachable;
if (override_redirect.xwayland_surface.overrideRedirectWantsFocus() and
override_redirect.xwayland_surface.icccmInputModel() != .none)
{
self.seat.setFocusRaw(.{ .xwayland_override_redirect = override_redirect });
}
},
} }
self.updateKeyboardFocus(result);
_ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state); _ = self.seat.wlr_seat.pointerNotifyButton(event.time_msec, event.button, event.state);
self.mode = .{ self.mode = .{
@ -334,14 +328,46 @@ fn handleButton(listener: *wl.Listener(*wlr.Pointer.event.Button), event: *wlr.P
.sy = result.sy, .sy = result.sy,
}, },
}; };
} else {
self.updateOutputFocus(self.wlr_cursor.x, self.wlr_cursor.y);
}
server.root.startTransaction(); server.root.startTransaction();
} else if (server.root.output_layout.outputAt(self.wlr_cursor.x, self.wlr_cursor.y)) |wlr_output| { }
// If the user clicked on empty space of an output, focus it.
fn updateKeyboardFocus(self: Self, result: SurfaceAtResult) void {
switch (result.parent) {
.view => |view| {
// Otherwise focus the view
self.seat.focus(view);
},
.layer_surface => |layer_surface| {
self.seat.focusOutput(layer_surface.output);
// If a keyboard inteactive layer surface has been clicked on,
// give it keyboard focus.
if (layer_surface.wlr_layer_surface.current.keyboard_interactive == .exclusive) {
self.seat.setFocusRaw(.{ .layer = layer_surface });
} else {
self.seat.focus(null);
}
},
.xwayland_override_redirect => |override_redirect| {
if (!build_options.xwayland) unreachable;
if (override_redirect.xwayland_surface.overrideRedirectWantsFocus() and
override_redirect.xwayland_surface.icccmInputModel() != .none)
{
self.seat.setFocusRaw(.{ .xwayland_override_redirect = override_redirect });
}
},
}
}
/// Focus the output at the given layout coordinates, if any
fn updateOutputFocus(self: Self, lx: f64, ly: f64) void {
if (server.root.output_layout.outputAt(lx, ly)) |wlr_output| {
const output = @intToPtr(*Output, wlr_output.data); const output = @intToPtr(*Output, wlr_output.data);
self.seat.focusOutput(output); self.seat.focusOutput(output);
self.seat.focus(null); self.seat.focus(null);
server.root.startTransaction();
} }
} }
@ -421,6 +447,71 @@ fn handleSwipeEnd(
); );
} }
fn handleTouchUp(
listener: *wl.Listener(*wlr.Touch.event.Up),
event: *wlr.Touch.event.Up,
) void {
const self = @fieldParentPtr(Self, "touch_up", listener);
self.seat.handleActivity();
self.seat.wlr_seat.touchNotifyUp(event.time_msec, event.touch_id);
}
fn handleTouchDown(
listener: *wl.Listener(*wlr.Touch.event.Down),
event: *wlr.Touch.event.Down,
) void {
const self = @fieldParentPtr(Self, "touch_down", listener);
self.seat.handleActivity();
var lx: f64 = undefined;
var ly: f64 = undefined;
self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
if (surfaceAtCoords(lx, ly)) |result| {
self.updateKeyboardFocus(result);
_ = self.seat.wlr_seat.touchNotifyDown(
result.surface,
event.time_msec,
event.touch_id,
result.sx,
result.sy,
);
} else {
self.updateOutputFocus(lx, ly);
}
server.root.startTransaction();
}
fn handleTouchMotion(
listener: *wl.Listener(*wlr.Touch.event.Motion),
event: *wlr.Touch.event.Motion,
) void {
const self = @fieldParentPtr(Self, "touch_motion", listener);
self.seat.handleActivity();
var lx: f64 = undefined;
var ly: f64 = undefined;
self.wlr_cursor.absoluteToLayoutCoords(event.device, event.x, event.y, &lx, &ly);
if (surfaceAtCoords(lx, ly)) |result| {
self.seat.wlr_seat.touchNotifyMotion(event.time_msec, event.touch_id, result.sx, result.sy);
}
}
fn handleTouchFrame(listener: *wl.Listener(void)) void {
const self = @fieldParentPtr(Self, "touch_frame", listener);
self.seat.handleActivity();
self.seat.wlr_seat.touchNotifyFrame();
}
/// Handle the mapping for the passed button if any. Returns true if there /// Handle the mapping for the passed button if any. Returns true if there
/// was a mapping and the button was handled. /// was a mapping and the button was handled.
fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool { fn handlePointerMapping(self: *Self, event: *wlr.Pointer.event.Button, view: *View) bool {
@ -544,10 +635,14 @@ const SurfaceAtResult = struct {
/// Find the surface under the cursor if any, and return information about that /// Find the surface under the cursor if any, and return information about that
/// surface and the cursor's position in surface local coords. /// surface and the cursor's position in surface local coords.
/// This function must be kept in sync with the rendering order in render.zig.
pub fn surfaceAt(self: Self) ?SurfaceAtResult { pub fn surfaceAt(self: Self) ?SurfaceAtResult {
const lx = self.wlr_cursor.x; return surfaceAtCoords(self.wlr_cursor.x, self.wlr_cursor.y);
const ly = self.wlr_cursor.y; }
/// Find the surface at the given layout coords if any, and return information about that
/// surface and the surface local coords.
/// This function must be kept in sync with the rendering order in render.zig.
fn surfaceAtCoords(lx: f64, ly: f64) ?SurfaceAtResult {
const wlr_output = server.root.output_layout.outputAt(lx, ly) orelse return null; const wlr_output = server.root.output_layout.outputAt(lx, ly) orelse return null;
const output = @intToPtr(*Output, wlr_output.data); const output = @intToPtr(*Output, wlr_output.data);

View File

@ -112,7 +112,7 @@ fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice)
keyboard.deinit(); keyboard.deinit();
util.gpa.destroy(keyboard); util.gpa.destroy(keyboard);
}, },
.pointer => { .pointer, .touch => {
device.deinit(); device.deinit();
util.gpa.destroy(device); util.gpa.destroy(device);
}, },
@ -121,6 +121,6 @@ fn handleDestroy(listener: *wl.Listener(*wlr.InputDevice), _: *wlr.InputDevice)
switch_device.deinit(); switch_device.deinit();
util.gpa.destroy(switch_device); util.gpa.destroy(switch_device);
}, },
.touch, .tablet_tool, .tablet_pad => unreachable, .tablet_tool, .tablet_pad => unreachable,
} }
} }

View File

@ -471,7 +471,7 @@ fn tryAddDevice(self: *Self, wlr_device: *wlr.InputDevice) !void {
try keyboard.init(self, wlr_device); try keyboard.init(self, wlr_device);
}, },
.pointer => { .pointer, .touch => {
const device = try util.gpa.create(InputDevice); const device = try util.gpa.create(InputDevice);
errdefer util.gpa.destroy(device); errdefer util.gpa.destroy(device);
@ -487,7 +487,7 @@ fn tryAddDevice(self: *Self, wlr_device: *wlr.InputDevice) !void {
}, },
// TODO Support these types of input devices. // TODO Support these types of input devices.
.touch, .tablet_tool, .tablet_pad => return, .tablet_tool, .tablet_pad => return,
} }
} }
@ -501,8 +501,9 @@ pub fn updateCapabilities(self: *Self) void {
if (device.seat == self) { if (device.seat == self) {
switch (device.wlr_device.type) { switch (device.wlr_device.type) {
.keyboard => capabilities.keyboard = true, .keyboard => capabilities.keyboard = true,
.touch => capabilities.touch = true,
.pointer, .switch_device => {}, .pointer, .switch_device => {},
.touch, .tablet_tool, .tablet_pad => unreachable, .tablet_tool, .tablet_pad => unreachable,
} }
} }
} }