Harden input ordering, portal activation and portable Linux builds
This commit is contained in:
parent
5623376284
commit
c1c2de8c4b
|
|
@ -323,10 +323,6 @@ pub fn forget(w: *native.Window) void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn focusLost() void {
|
|
||||||
if (s().selection) |offer_| unref(offer_);
|
|
||||||
s().selection = null;
|
|
||||||
}
|
|
||||||
pub fn releaseSeat() void {
|
pub fn releaseSeat() void {
|
||||||
while (s().transfers) |t| finish(t, abi.CANCELLED);
|
while (s().transfers) |t| finish(t, abi.CANCELLED);
|
||||||
s().selection = null;
|
s().selection = null;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,15 @@ const Keyboard = @import("Keyboard.zig");
|
||||||
const Data = @import("Data.zig");
|
const Data = @import("Data.zig");
|
||||||
const Text = @import("Text.zig");
|
const Text = @import("Text.zig");
|
||||||
|
|
||||||
|
const Key = struct {
|
||||||
|
code: u32,
|
||||||
|
down: bool,
|
||||||
|
repeated: bool,
|
||||||
|
sym: u32,
|
||||||
|
modifiers: u32,
|
||||||
|
text: [256]u8 = undefined,
|
||||||
|
len: usize = 0,
|
||||||
|
};
|
||||||
pub const State = struct {
|
pub const State = struct {
|
||||||
seat: ?*c.struct_wl_seat = null,
|
seat: ?*c.struct_wl_seat = null,
|
||||||
seat_name: u32 = 0,
|
seat_name: u32 = 0,
|
||||||
|
|
@ -29,6 +38,8 @@ pub const State = struct {
|
||||||
compose: ?*c.struct_xkb_compose_state = null,
|
compose: ?*c.struct_xkb_compose_state = null,
|
||||||
modifiers: u32 = 0,
|
modifiers: u32 = 0,
|
||||||
active: bool = false,
|
active: bool = false,
|
||||||
|
pending_key: ?Key = null,
|
||||||
|
key_sync: ?*c.struct_wl_callback = null,
|
||||||
serial: u32 = 0,
|
serial: u32 = 0,
|
||||||
serial_time: u64 = 0,
|
serial_time: u64 = 0,
|
||||||
pointer_serial: u32 = 0,
|
pointer_serial: u32 = 0,
|
||||||
|
|
@ -137,6 +148,7 @@ fn seatCapabilities(_: ?*anyopaque, seat: ?*c.struct_wl_seat, caps: u32) callcon
|
||||||
fn seatName(_: ?*anyopaque, _: ?*c.struct_wl_seat, _: [*c]const u8) callconv(.c) void {}
|
fn seatName(_: ?*anyopaque, _: ?*c.struct_wl_seat, _: [*c]const u8) callconv(.c) void {}
|
||||||
const seat_listener: c.struct_wl_seat_listener = .{ .capabilities = seatCapabilities, .name = seatName };
|
const seat_listener: c.struct_wl_seat_listener = .{ .capabilities = seatCapabilities, .name = seatName };
|
||||||
fn keymap(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, format: u32, fd: i32, size: u32) callconv(.c) void {
|
fn keymap(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, format: u32, fd: i32, size: u32) callconv(.c) void {
|
||||||
|
flushKey();
|
||||||
defer _ = c.close(fd);
|
defer _ = c.close(fd);
|
||||||
const s = state();
|
const s = state();
|
||||||
s.repeat_key = null;
|
s.repeat_key = null;
|
||||||
|
|
@ -182,31 +194,66 @@ fn modifiers(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, _: u32, depressed: u32,
|
||||||
const s = state();
|
const s = state();
|
||||||
if (s.state != null) _ = c.z_xkb_state_update_mask.?(s.state, depressed, latched, locked, 0, 0, group);
|
if (s.state != null) _ = c.z_xkb_state_update_mask.?(s.state, depressed, latched, locked, 0, 0, group);
|
||||||
updateModifiers();
|
updateModifiers();
|
||||||
|
flushKey();
|
||||||
}
|
}
|
||||||
fn key(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, serial: u32, _: u32, code: u32, pressed: u32) callconv(.c) void {
|
fn key(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, serial: u32, _: u32, code: u32, pressed: u32) callconv(.c) void {
|
||||||
const s = state();
|
const s = state();
|
||||||
|
flushKey();
|
||||||
if (code > 0xffff) return;
|
if (code > 0xffff) return;
|
||||||
provenance(serial);
|
provenance(serial);
|
||||||
if (code < s.held.len) s.held[code] = pressed != 0;
|
if (code < s.held.len) s.held[code] = pressed != 0;
|
||||||
// The compositor supplies the authoritative modifier/group mask. Running
|
// The compositor supplies the authoritative modifier/group mask. Running
|
||||||
// XKB key actions locally as well would apply latch/lock actions twice.
|
// XKB key actions locally as well would apply latch/lock actions twice.
|
||||||
deliverKey(code, pressed != 0, false);
|
s.pending_key = prepareKey(code, pressed != 0, false);
|
||||||
|
// wl_keyboard has no frame event. A sync boundary lets the following
|
||||||
|
// authoritative modifiers event supply the public post-event flags even
|
||||||
|
// when the key and modifier messages arrive in separate socket reads.
|
||||||
|
if (s.key_sync == null) {
|
||||||
|
s.key_sync = c.wl_display_sync(b().display);
|
||||||
|
if (s.key_sync != null) _ = c.wl_callback_add_listener(s.key_sync, &key_sync_listener, null) else flushKey();
|
||||||
|
}
|
||||||
if (pressed != 0 and s.repeat_rate > 0 and s.keymap != null and c.z_xkb_keymap_key_repeats.?(s.keymap, code + 8) != 0) {
|
if (pressed != 0 and s.repeat_rate > 0 and s.keymap != null and c.z_xkb_keymap_key_repeats.?(s.keymap, code + 8) != 0) {
|
||||||
s.repeat_key = code;
|
s.repeat_key = code;
|
||||||
s.repeat_at = runtime.now(b().app.io) + @as(u64, s.repeat_delay) * std.time.ns_per_ms;
|
s.repeat_at = runtime.now(b().app.io) + @as(u64, s.repeat_delay) * std.time.ns_per_ms;
|
||||||
} else if (s.repeat_key == code) s.repeat_key = null;
|
} else if (s.repeat_key == code) s.repeat_key = null;
|
||||||
}
|
}
|
||||||
|
fn prepareKey(code: u32, down: bool, repeated: bool) Key {
|
||||||
|
const s = state();
|
||||||
|
var value: Key = .{ .code = code, .down = down, .repeated = repeated, .sym = if (s.state != null) c.z_xkb_state_key_get_one_sym.?(s.state, code + 8) else 0, .modifiers = s.modifiers };
|
||||||
|
if (down and s.state != null) {
|
||||||
|
const length = c.z_xkb_state_key_get_utf8.?(s.state, code + 8, &value.text, value.text.len);
|
||||||
|
if (length > 0 and length < value.text.len) value.len = @intCast(length);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
fn deliverKey(code: u32, down: bool, repeated: bool) void {
|
fn deliverKey(code: u32, down: bool, repeated: bool) void {
|
||||||
|
emitKey(prepareKey(code, down, repeated));
|
||||||
|
}
|
||||||
|
fn emitKey(value: Key) void {
|
||||||
const s = state();
|
const s = state();
|
||||||
const w = s.key_focus orelse return;
|
const w = s.key_focus orelse return;
|
||||||
const sym = if (s.state != null) c.z_xkb_state_key_get_one_sym.?(s.state, code + 8) else 0;
|
|
||||||
var event = std.mem.zeroes(n.z_event);
|
var event = std.mem.zeroes(n.z_event);
|
||||||
event.type = n.UHDK_EVENT_KEY;
|
event.type = n.UHDK_EVENT_KEY;
|
||||||
event.input_id = id();
|
event.input_id = id();
|
||||||
event.data.key = .{ .physical_key = Keyboard.physical(code), .logical_key = if (s.state != null) Keyboard.logical(sym) else 0, .modifiers = s.modifiers, .pressed = @intFromBool(down), .repeat = @intFromBool(repeated) };
|
event.data.key = .{ .physical_key = Keyboard.physical(value.code), .logical_key = if (s.state != null) Keyboard.logical(value.sym) else 0, .modifiers = s.modifiers, .pressed = @intFromBool(value.down), .repeat = @intFromBool(value.repeated) };
|
||||||
send(w, &event);
|
send(w, &event);
|
||||||
if (down and w.text.enabled) Text.key(w, code, sym, event.data.key.logical_key, event.input_id);
|
// Resolve symbols/text before consuming a latched modifier, while reporting
|
||||||
|
// the compositor's post-event flags in the observational raw event.
|
||||||
|
if (value.down and w.text.enabled) Text.key(w, value.sym, event.data.key.logical_key, event.input_id, value.modifiers, value.text[0..value.len]);
|
||||||
}
|
}
|
||||||
|
pub fn flushKey() void {
|
||||||
|
if (state().pending_key) |value| {
|
||||||
|
state().pending_key = null;
|
||||||
|
emitKey(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn keySynced(_: ?*anyopaque, callback: ?*c.struct_wl_callback, _: u32) callconv(.c) void {
|
||||||
|
c.wl_callback_destroy(callback);
|
||||||
|
state().key_sync = null;
|
||||||
|
flushKey();
|
||||||
|
}
|
||||||
|
const key_sync_listener: c.struct_wl_callback_listener = .{ .done = keySynced };
|
||||||
|
|
||||||
fn repeatInfo(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, rate: i32, delay: i32) callconv(.c) void {
|
fn repeatInfo(_: ?*anyopaque, _: ?*c.struct_wl_keyboard, rate: i32, delay: i32) callconv(.c) void {
|
||||||
state().repeat_rate = @intCast(std.math.clamp(rate, 0, 1000));
|
state().repeat_rate = @intCast(std.math.clamp(rate, 0, 1000));
|
||||||
state().repeat_delay = @intCast(@max(0, delay));
|
state().repeat_delay = @intCast(@max(0, delay));
|
||||||
|
|
@ -216,7 +263,6 @@ const keyboard_listener: c.struct_wl_keyboard_listener = .{ .keymap = keymap, .e
|
||||||
pub fn tick() i32 {
|
pub fn tick() i32 {
|
||||||
const s = state();
|
const s = state();
|
||||||
const active = s.key_focus != null;
|
const active = s.key_focus != null;
|
||||||
if (!active) Data.focusLost();
|
|
||||||
if (active != s.active) {
|
if (active != s.active) {
|
||||||
s.active = active;
|
s.active = active;
|
||||||
var event = std.mem.zeroes(n.z_event);
|
var event = std.mem.zeroes(n.z_event);
|
||||||
|
|
@ -224,6 +270,7 @@ pub fn tick() i32 {
|
||||||
event.data.active = @intFromBool(active);
|
event.data.active = @intFromBool(active);
|
||||||
n.z_emit(runtime.toAppHandle(b().app), &event);
|
n.z_emit(runtime.toAppHandle(b().app), &event);
|
||||||
}
|
}
|
||||||
|
if (s.pending_key != null) return -1;
|
||||||
if (s.repeat_key) |code| {
|
if (s.repeat_key) |code| {
|
||||||
const now = runtime.now(b().app.io);
|
const now = runtime.now(b().app.io);
|
||||||
if (now >= s.repeat_at) {
|
if (now >= s.repeat_at) {
|
||||||
|
|
@ -235,7 +282,9 @@ pub fn tick() i32 {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
fn leaveKeyboard() void {
|
fn leaveKeyboard() void {
|
||||||
|
flushKey();
|
||||||
const s = state();
|
const s = state();
|
||||||
|
s.modifiers = 0;
|
||||||
s.repeat_key = null;
|
s.repeat_key = null;
|
||||||
if (s.key_focus) |w| {
|
if (s.key_focus) |w| {
|
||||||
for (&s.held, 0..) |*held, code| if (held.*) {
|
for (&s.held, 0..) |*held, code| if (held.*) {
|
||||||
|
|
@ -402,6 +451,8 @@ pub fn remove(name: u32) void {
|
||||||
fn releaseSeat() void {
|
fn releaseSeat() void {
|
||||||
Data.releaseSeat();
|
Data.releaseSeat();
|
||||||
const s = state();
|
const s = state();
|
||||||
|
if (s.key_sync != null) c.wl_callback_destroy(s.key_sync);
|
||||||
|
s.key_sync = null;
|
||||||
leaveKeyboard();
|
leaveKeyboard();
|
||||||
leavePointer();
|
leavePointer();
|
||||||
if (s.text_focus) |w| Text.forget(w);
|
if (s.text_focus) |w| Text.forget(w);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const n = @import("native");
|
||||||
const native = @import("native.zig");
|
const native = @import("native.zig");
|
||||||
const Bus = @import("Bus.zig");
|
const Bus = @import("Bus.zig");
|
||||||
const Files = @import("Files.zig");
|
const Files = @import("Files.zig");
|
||||||
|
const Input = @import("Input.zig");
|
||||||
const runtime = @import("../runtime.zig");
|
const runtime = @import("../runtime.zig");
|
||||||
const abi = @import("../abi.zig");
|
const abi = @import("../abi.zig");
|
||||||
const desktop = "org.freedesktop.portal.Desktop";
|
const desktop = "org.freedesktop.portal.Desktop";
|
||||||
|
|
@ -24,6 +25,7 @@ const Pending = struct {
|
||||||
handle: [:0]u8,
|
handle: [:0]u8,
|
||||||
token: [:0]u8,
|
token: [:0]u8,
|
||||||
exported: ?*c.struct_zxdg_exported_v2 = null,
|
exported: ?*c.struct_zxdg_exported_v2 = null,
|
||||||
|
activation: ?*c.struct_xdg_activation_token_v1 = null,
|
||||||
deadline: u64,
|
deadline: u64,
|
||||||
};
|
};
|
||||||
pub const State = struct {
|
pub const State = struct {
|
||||||
|
|
@ -309,7 +311,18 @@ pub fn request(cmd: *const n.z_command, id: u64) u32 {
|
||||||
return abi.OOM;
|
return abi.OOM;
|
||||||
};
|
};
|
||||||
_ = c.zxdg_exported_v2_add_listener(p.exported, &export_listener, p);
|
_ = c.zxdg_exported_v2_add_listener(p.exported, &export_listener, p);
|
||||||
} else submit(p, "") catch {
|
} else if (cmd.op == n.Z_URL and b().desktop.activation != null and Input.state().key_focus != null and
|
||||||
|
Input.state().serial != 0 and runtime.now(b().app.io) -| Input.state().serial_time <= 5 * std.time.ns_per_s)
|
||||||
|
{
|
||||||
|
p.activation = c.xdg_activation_v1_get_activation_token(b().desktop.activation) orelse {
|
||||||
|
destroy(p);
|
||||||
|
return abi.OOM;
|
||||||
|
};
|
||||||
|
_ = c.xdg_activation_token_v1_add_listener(p.activation, &activation_listener, p);
|
||||||
|
c.xdg_activation_token_v1_set_serial(p.activation, Input.state().serial, Input.state().seat);
|
||||||
|
c.xdg_activation_token_v1_set_surface(p.activation, Input.state().key_focus.?.surface);
|
||||||
|
c.xdg_activation_token_v1_commit(p.activation);
|
||||||
|
} else submit(p, "", null) catch {
|
||||||
destroy(p);
|
destroy(p);
|
||||||
return abi.OOM;
|
return abi.OOM;
|
||||||
};
|
};
|
||||||
|
|
@ -336,13 +349,21 @@ fn exported(raw: ?*anyopaque, _: ?*c.struct_zxdg_exported_v2, handle: [*c]const
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
defer b().app.gpa.free(parent);
|
defer b().app.gpa.free(parent);
|
||||||
submit(p, parent) catch {
|
submit(p, parent, null) catch {
|
||||||
complete(p, abi.OOM, null);
|
complete(p, abi.OOM, null);
|
||||||
destroy(p);
|
destroy(p);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const export_listener: c.struct_zxdg_exported_v2_listener = .{ .handle = exported };
|
const export_listener: c.struct_zxdg_exported_v2_listener = .{ .handle = exported };
|
||||||
fn submit(p: *Pending, parent: [*:0]const u8) !void {
|
fn activated(raw: ?*anyopaque, _: ?*c.struct_xdg_activation_token_v1, token: [*c]const u8) callconv(.c) void {
|
||||||
|
const p: *Pending = @ptrCast(@alignCast(raw.?));
|
||||||
|
submit(p, "", token) catch {
|
||||||
|
complete(p, abi.OOM, null);
|
||||||
|
destroy(p);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const activation_listener: c.struct_xdg_activation_token_v1_listener = .{ .done = activated };
|
||||||
|
fn submit(p: *Pending, parent: [*:0]const u8, activation_token: ?[*:0]const u8) !void {
|
||||||
const cmd = p.command.?;
|
const cmd = p.command.?;
|
||||||
const dialog = cmd.op == n.Z_DIALOG;
|
const dialog = cmd.op == n.Z_DIALOG;
|
||||||
const message = try Bus.method(desktop, path, if (dialog) chooser else "org.freedesktop.portal.OpenURI", if (!dialog) "OpenURI" else if (cmd.dialog.kind == n.UHDK_DIALOG_SAVE_FILE) "SaveFile" else "OpenFile");
|
const message = try Bus.method(desktop, path, if (dialog) chooser else "org.freedesktop.portal.OpenURI", if (!dialog) "OpenURI" else if (cmd.dialog.kind == n.UHDK_DIALOG_SAVE_FILE) "SaveFile" else "OpenFile");
|
||||||
|
|
@ -356,6 +377,7 @@ fn submit(p: *Pending, parent: [*:0]const u8) !void {
|
||||||
var options = try Bus.open(&args, c.DBUS_TYPE_ARRAY, "{sv}");
|
var options = try Bus.open(&args, c.DBUS_TYPE_ARRAY, "{sv}");
|
||||||
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&args, &options);
|
errdefer c.z_dbus_message_iter_abandon_container_if_open.?(&args, &options);
|
||||||
try Bus.option(&options, "handle_token", "s", p.token.ptr);
|
try Bus.option(&options, "handle_token", "s", p.token.ptr);
|
||||||
|
if (activation_token) |token| try Bus.option(&options, "activation_token", "s", token);
|
||||||
if (dialog) {
|
if (dialog) {
|
||||||
try Bus.option(&options, "modal", "b", @as(u32, @intFromBool(cmd.dialog.parent != null)));
|
try Bus.option(&options, "modal", "b", @as(u32, @intFromBool(cmd.dialog.parent != null)));
|
||||||
if (cmd.dialog.kind != n.UHDK_DIALOG_SAVE_FILE) {
|
if (cmd.dialog.kind != n.UHDK_DIALOG_SAVE_FILE) {
|
||||||
|
|
@ -485,6 +507,7 @@ fn destroy(p: *Pending) void {
|
||||||
while (link.* != p) link = &link.*.?.next;
|
while (link.* != p) link = &link.*.?.next;
|
||||||
link.* = p.next;
|
link.* = p.next;
|
||||||
if (p.exported != null) c.zxdg_exported_v2_destroy(p.exported);
|
if (p.exported != null) c.zxdg_exported_v2_destroy(p.exported);
|
||||||
|
if (p.activation != null) c.xdg_activation_token_v1_destroy(p.activation);
|
||||||
b().app.gpa.free(p.handle);
|
b().app.gpa.free(p.handle);
|
||||||
b().app.gpa.free(p.token);
|
b().app.gpa.free(p.token);
|
||||||
b().app.gpa.destroy(p);
|
b().app.gpa.destroy(p);
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,10 @@ NixOS with Plasma 6.5.6 and Mesa 25.2.6; other systems are not runtime-qualified
|
||||||
|
|
||||||
- One active seat owns keyboard, pointer, and clipboard state. The keyboard uses
|
- One active seat owns keyboard, pointer, and clipboard state. The keyboard uses
|
||||||
the compositor's XKB layout/masks, USB HID physical usages, client-side repeat,
|
the compositor's XKB layout/masks, USB HID physical usages, client-side repeat,
|
||||||
and locale-aware compose tables. Focus loss releases tracked keys and stops repeat.
|
and locale-aware compose tables. Raw events use the post-event modifier mask;
|
||||||
|
text lookup retains the pre-event state for latched modifiers. A Wayland sync
|
||||||
|
boundary flushes keys without modifier changes. Focus loss releases tracked
|
||||||
|
keys and stops repeat.
|
||||||
- Text input uses text-input-v3 when available and local XKB composition for
|
- Text input uses text-input-v3 when available and local XKB composition for
|
||||||
forwarded keys. IME batches retain their surrounding-text revision and UTF-8
|
forwarded keys. IME batches retain their surrounding-text revision and UTF-8
|
||||||
offsets until `done`. Surrounding text is cropped to the protocol's 4000-byte
|
offsets until `done`. Surrounding text is cropped to the protocol's 4000-byte
|
||||||
|
|
@ -138,7 +141,8 @@ NixOS with Plasma 6.5.6 and Mesa 25.2.6; other systems are not runtime-qualified
|
||||||
A private D-Bus connection subscribes before submission, correlates early
|
A private D-Bus connection subscribes before submission, correlates early
|
||||||
responses, closes cancelled requests, and handles portal-owner changes.
|
responses, closes cancelled requests, and handles portal-owner changes.
|
||||||
Portal discovery is asynchronous, so its capability can appear after `READY`.
|
Portal discovery is asynchronous, so its capability can appear after `READY`.
|
||||||
Connection/method setup is bounded; human interaction has no timeout.
|
Connection/method setup is bounded; human interaction has no timeout. URL
|
||||||
|
handoff includes an activation token when recent focused input permits it.
|
||||||
- Portal Settings supplies appearance, contrast and reduced-motion preferences
|
- Portal Settings supplies appearance, contrast and reduced-motion preferences
|
||||||
and live changes. Unknown/unavailable values retain neutral defaults. See the
|
and live changes. Unknown/unavailable values retain neutral defaults. See the
|
||||||
[Settings contract](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html)
|
[Settings contract](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html)
|
||||||
|
|
@ -178,9 +182,9 @@ does not stop or modify the desktop compositor.
|
||||||
|
|
||||||
The service fixtures own their compositor socket and D-Bus daemon. They exercise
|
The service fixtures own their compositor socket and D-Bus daemon. They exercise
|
||||||
keys/repeat/dead-key composition, IME revisions/deletion/preedit, scrolling,
|
keys/repeat/dead-key composition, IME revisions/deletion/preedit, scrolling,
|
||||||
clipboard size limits and early reader exit, URI drops, activation, portal
|
clipboard size limits/cancellation/invalid UTF-8/early reader exit, URI drops,
|
||||||
filters/results, response-before-reply, cancellation with a late alternate
|
activation, portal open/save options, live settings, response-before-reply,
|
||||||
handle, parent destruction and portal restart. They never open a real chooser
|
cancellation with a late alternate handle, parent destruction and portal restart. They never open a real chooser
|
||||||
or browser or inject input into the user's desktop. The live service mode checks
|
or browser or inject input into the user's desktop. The live service mode checks
|
||||||
portal discovery, icon submission and atomic-save watch behavior without touching
|
portal discovery, icon submission and atomic-save watch behavior without touching
|
||||||
the user's clipboard. These fixtures supplement live desktop testing; they do
|
the user's clipboard. These fixtures supplement live desktop testing; they do
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ fn deleted(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, before: u32, after:
|
||||||
w.text.after = after;
|
w.text.after = after;
|
||||||
}
|
}
|
||||||
fn done(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, serial: u32) callconv(.c) void {
|
fn done(_: ?*anyopaque, _: ?*c.struct_zwp_text_input_v3, serial: u32) callconv(.c) void {
|
||||||
|
Input.flushKey();
|
||||||
const w = Input.state().text_focus orelse return;
|
const w = Input.state().text_focus orelse return;
|
||||||
const t = &w.text;
|
const t = &w.text;
|
||||||
defer clearPending(w);
|
defer clearPending(w);
|
||||||
|
|
@ -276,25 +277,26 @@ pub fn deinit(w: *native.Window) void {
|
||||||
forget(w);
|
forget(w);
|
||||||
w.owner.gpa.free(w.text.surrounding);
|
w.owner.gpa.free(w.text.surrounding);
|
||||||
}
|
}
|
||||||
pub fn key(w: *native.Window, code: u32, sym: u32, logical: u32, input_id: u64) void {
|
pub fn key(w: *native.Window, sym: u32, logical: u32, input_id: u64, modifiers: u32, plain: []const u8) void {
|
||||||
const s = Input.state();
|
const s = Input.state();
|
||||||
const t = &w.text;
|
const t = &w.text;
|
||||||
if (t.ime_composing or s.state == null) return;
|
if (t.ime_composing or s.state == null) return;
|
||||||
const edit = Keyboard.edit(logical, s.modifiers);
|
const edit = Keyboard.edit(logical, modifiers);
|
||||||
if (edit != 0) {
|
if (edit != 0) {
|
||||||
if (s.compose != null) c.z_xkb_compose_state_reset.?(s.compose);
|
if (s.compose != null) c.z_xkb_compose_state_reset.?(s.compose);
|
||||||
endAt(w, input_id);
|
endAt(w, input_id);
|
||||||
var e = std.mem.zeroes(n.z_event);
|
var e = std.mem.zeroes(n.z_event);
|
||||||
e.type = n.UHDK_EVENT_EDIT_COMMAND;
|
e.type = n.UHDK_EVENT_EDIT_COMMAND;
|
||||||
e.input_id = input_id;
|
e.input_id = input_id;
|
||||||
e.data.edit = .{ .command = edit, .extend_selection = @intFromBool(s.modifiers & n.UHDK_MOD_SHIFT != 0) };
|
e.data.edit = .{ .command = edit, .extend_selection = @intFromBool(modifiers & n.UHDK_MOD_SHIFT != 0) };
|
||||||
t.last_edit = input_id;
|
t.last_edit = input_id;
|
||||||
Input.send(w, &e);
|
Input.send(w, &e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (s.modifiers & (n.UHDK_MOD_CONTROL | n.UHDK_MOD_ALT | n.UHDK_MOD_SUPER) != 0) return;
|
if (modifiers & (n.UHDK_MOD_CONTROL | n.UHDK_MOD_ALT | n.UHDK_MOD_SUPER) != 0) return;
|
||||||
var buffer: [256]u8 = undefined;
|
var buffer: [256]u8 = undefined;
|
||||||
var length: c_int = 0;
|
var length: c_int = @intCast(plain.len);
|
||||||
|
@memcpy(buffer[0..plain.len], plain);
|
||||||
if (s.compose != null) {
|
if (s.compose != null) {
|
||||||
_ = c.z_xkb_compose_state_feed.?(s.compose, sym);
|
_ = c.z_xkb_compose_state_feed.?(s.compose, sym);
|
||||||
switch (c.z_xkb_compose_state_get_status.?(s.compose)) {
|
switch (c.z_xkb_compose_state_get_status.?(s.compose)) {
|
||||||
|
|
@ -312,9 +314,9 @@ pub fn key(w: *native.Window, code: u32, sym: u32, logical: u32, input_id: u64)
|
||||||
c.z_xkb_compose_state_reset.?(s.compose);
|
c.z_xkb_compose_state_reset.?(s.compose);
|
||||||
endAt(w, input_id);
|
endAt(w, input_id);
|
||||||
},
|
},
|
||||||
else => length = c.z_xkb_state_key_get_utf8.?(s.state, code + 8, &buffer, buffer.len),
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else length = c.z_xkb_state_key_get_utf8.?(s.state, code + 8, &buffer, buffer.len);
|
|
||||||
if (length <= 0 or length >= buffer.len or buffer[0] < 32 or buffer[0] == 127) return;
|
if (length <= 0 or length >= buffer.len or buffer[0] < 32 or buffer[0] == 127) return;
|
||||||
var e = std.mem.zeroes(n.z_event);
|
var e = std.mem.zeroes(n.z_event);
|
||||||
e.type = n.UHDK_EVENT_TEXT_COMMIT;
|
e.type = n.UHDK_EVENT_TEXT_COMMIT;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <fcntl.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "wayland-client-core.h"
|
#include "wayland-client-core.h"
|
||||||
#include "wayland-egl-core.h"
|
#include "wayland-egl-core.h"
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,10 @@ async def main(binary):
|
||||||
async def delayed_reply(message, handle, delay, is_url=False):
|
async def delayed_reply(message, handle, delay, is_url=False):
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
bus.send(Message.new_method_return(message, 'o', [handle]))
|
bus.send(Message.new_method_return(message, 'o', [handle]))
|
||||||
if is_url: response(handle)
|
if is_url:
|
||||||
|
for key, value in [('color-scheme', 2), ('contrast', 0), ('reduced-motion', 0)]:
|
||||||
|
bus.send(Message.new_signal(PATH, 'org.freedesktop.portal.Settings', 'SettingChanged', 'ssv', ['org.freedesktop.appearance', key, Variant('u', value)]))
|
||||||
|
response(handle)
|
||||||
|
|
||||||
async def restart():
|
async def restart():
|
||||||
await bus.release_name(DESKTOP)
|
await bus.release_name(DESKTOP)
|
||||||
|
|
@ -65,11 +68,13 @@ async def main(binary):
|
||||||
handle_path = PATH + '/request/' + message.sender[1:].replace('.', '_') + '/' + token
|
handle_path = PATH + '/request/' + message.sender[1:].replace('.', '_') + '/' + token
|
||||||
if message.member == 'OpenURI':
|
if message.member == 'OpenURI':
|
||||||
assert title == 'https://example.org/test'
|
assert title == 'https://example.org/test'
|
||||||
|
assert options['activation_token'].value == 'fixture-token'
|
||||||
schedule(delayed_reply(message, handle_path, .35, True))
|
schedule(delayed_reply(message, handle_path, .35, True))
|
||||||
seen.add('url')
|
seen.add('url')
|
||||||
return True
|
return True
|
||||||
seen.add(title)
|
seen.add(title)
|
||||||
assert message.signature == 'ssa{sv}'
|
assert message.signature == 'ssa{sv}'
|
||||||
|
if message.member == 'OpenFile':
|
||||||
assert options['multiple'].value and not options['directory'].value
|
assert options['multiple'].value and not options['directory'].value
|
||||||
assert options['filters'].value == [['Text', [[0, '*.txt'], [0, '*.md']]]]
|
assert options['filters'].value == [['Text', [[0, '*.txt'], [0, '*.md']]]]
|
||||||
if title == 'success':
|
if title == 'success':
|
||||||
|
|
@ -79,6 +84,13 @@ async def main(binary):
|
||||||
# must already be subscribed using its chosen handle token.
|
# must already be subscribed using its chosen handle token.
|
||||||
response(handle_path, results={'uris': Variant('as', ['file:///tmp/a%20b%25%FF.txt', 'file:///tmp/second'])})
|
response(handle_path, results={'uris': Variant('as', ['file:///tmp/a%20b%25%FF.txt', 'file:///tmp/second'])})
|
||||||
bus.send(Message.new_method_return(message, 'o', [handle_path]))
|
bus.send(Message.new_method_return(message, 'o', [handle_path]))
|
||||||
|
elif title == 'save':
|
||||||
|
assert message.member == 'SaveFile'
|
||||||
|
assert options['current_name'].value == 'book.txt'
|
||||||
|
assert options['accept_label'].value == 'Store'
|
||||||
|
assert bytes(options['current_folder'].value) == b'/tmp\0'
|
||||||
|
bus.send(Message.new_method_return(message, 'o', [handle_path]))
|
||||||
|
response(handle_path, results={'uris': Variant('as', ['file:///tmp/save.txt'])})
|
||||||
elif title == 'cancel':
|
elif title == 'cancel':
|
||||||
# Return a different handle after local cancellation; both
|
# Return a different handle after local cancellation; both
|
||||||
# the predicted and returned paths need Close calls.
|
# the predicted and returned paths need Close calls.
|
||||||
|
|
@ -102,7 +114,7 @@ async def main(binary):
|
||||||
bus.add_message_handler(handle)
|
bus.add_message_handler(handle)
|
||||||
await asyncio.to_thread(run, binary, 'portal', dict(os.environ, DBUS_SESSION_BUS_ADDRESS=address))
|
await asyncio.to_thread(run, binary, 'portal', dict(os.environ, DBUS_SESSION_BUS_ADDRESS=address))
|
||||||
await asyncio.sleep(.1)
|
await asyncio.sleep(.1)
|
||||||
assert {'success', 'cancel', 'url', 'restart', 'dismiss'} <= seen, seen
|
assert {'success', 'save', 'cancel', 'url', 'restart', 'dismiss'} <= seen, seen
|
||||||
assert any(p.endswith('_late') for p in closed), closed
|
assert any(p.endswith('_late') for p in closed), closed
|
||||||
assert len(closed) >= 2, closed
|
assert len(closed) >= 2, closed
|
||||||
print('private portal protocol/cancellation/restart fixture: PASS')
|
print('private portal protocol/cancellation/restart fixture: PASS')
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,28 @@ static void portals(uhdk_app *app) {
|
||||||
files->ptr[0], UHDK_FILE_NATIVE_PATH, bytes, sizeof(bytes), &len
|
files->ptr[0], UHDK_FILE_NATIVE_PATH, bytes, sizeof(bytes), &len
|
||||||
));
|
));
|
||||||
assert(len == 14 && memcmp(bytes, "/tmp/a b%\xff.txt", 14) == 0);
|
assert(len == 14 && memcmp(bytes, "/tmp/a b%\xff.txt", 14) == 0);
|
||||||
|
uhdk_preferences prefs = {0};
|
||||||
|
prefs.struct_size = sizeof(prefs);
|
||||||
|
OK(uhdk_app_get_preferences(app, &prefs));
|
||||||
|
assert(prefs.appearance == UHDK_APPEARANCE_DARK && prefs.reduced_motion && prefs.high_contrast);
|
||||||
|
uhdk_file *directory;
|
||||||
|
OK(
|
||||||
|
uhdk_file_create_from_native_path(app, (uhdk_bytes){(const uint8_t *)"/tmp", 4}, &directory)
|
||||||
|
);
|
||||||
|
desc.title = text("save");
|
||||||
|
desc.kind = UHDK_DIALOG_SAVE_FILE;
|
||||||
|
desc.suggested_name = text("book.txt");
|
||||||
|
desc.accept_label = text("Store");
|
||||||
|
desc.initial_directory = directory;
|
||||||
|
OK(uhdk_file_dialog_show_async(app, &desc, &id));
|
||||||
|
r = wait_result(app, id);
|
||||||
|
OK(r.status);
|
||||||
|
assert(((const uhdk_file_list *)r.data)->len == 1);
|
||||||
|
uhdk_file_release(directory);
|
||||||
|
desc.kind = UHDK_DIALOG_OPEN_FILES;
|
||||||
|
desc.initial_directory = NULL;
|
||||||
|
desc.suggested_name = text("");
|
||||||
|
desc.accept_label = text("");
|
||||||
desc.title = text("cancel");
|
desc.title = text("cancel");
|
||||||
OK(uhdk_file_dialog_show_async(app, &desc, &id));
|
OK(uhdk_file_dialog_show_async(app, &desc, &id));
|
||||||
// Allow method submission, then cancel while the mock withholds its reply.
|
// Allow method submission, then cancel while the mock withholds its reply.
|
||||||
|
|
@ -169,12 +191,17 @@ static void portals(uhdk_app *app) {
|
||||||
OK(uhdk_app_poll_events(app, 100000000, &events));
|
OK(uhdk_app_poll_events(app, 100000000, &events));
|
||||||
OK(uhdk_request_cancel(app, id));
|
OK(uhdk_request_cancel(app, id));
|
||||||
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
|
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
|
||||||
|
OK(uhdk_shell_open_url_async(app, text("https://example.org/test"), &id));
|
||||||
|
OK(wait_result(app, id).status);
|
||||||
|
OK(uhdk_app_get_preferences(app, &prefs));
|
||||||
|
assert(
|
||||||
|
prefs.appearance == UHDK_APPEARANCE_LIGHT && !prefs.reduced_motion && !prefs.high_contrast
|
||||||
|
);
|
||||||
desc.title = text("parent");
|
desc.title = text("parent");
|
||||||
OK(uhdk_file_dialog_show_async(app, &desc, &id));
|
OK(uhdk_file_dialog_show_async(app, &desc, &id));
|
||||||
uhdk_window_destroy(w);
|
uhdk_window_destroy(w);
|
||||||
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
|
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
|
||||||
OK(uhdk_shell_open_url_async(app, text("https://example.org/test"), &id));
|
|
||||||
OK(wait_result(app, id).status);
|
|
||||||
desc.parent = NULL;
|
desc.parent = NULL;
|
||||||
desc.title = text("restart");
|
desc.title = text("restart");
|
||||||
OK(uhdk_file_dialog_show_async(app, &desc, &id));
|
OK(uhdk_file_dialog_show_async(app, &desc, &id));
|
||||||
|
|
@ -200,19 +227,28 @@ static void input(uhdk_app *app) {
|
||||||
state.selection_len = 1;
|
state.selection_len = 1;
|
||||||
OK(uhdk_window_set_text_state(w, &state));
|
OK(uhdk_window_set_text_state(w, &state));
|
||||||
unsigned key = 0, repeat = 0, commit = 0, preedit = 0, pointer = 0, scroll = 0, dropped = 0,
|
unsigned key = 0, repeat = 0, commit = 0, preedit = 0, pointer = 0, scroll = 0, dropped = 0,
|
||||||
copy = 0, compose = 0;
|
copy = 0, compose = 0, captured = 0, released = 0;
|
||||||
|
uint64_t acknowledged = 0, capture_request = 0;
|
||||||
|
int published = 0;
|
||||||
uint64_t end = deadline(app);
|
uint64_t end = deadline(app);
|
||||||
while (uhdk_app_get_time_ns(app) < end && !(key && repeat && commit >= 2 && preedit &&
|
while (uhdk_app_get_time_ns(app) < end &&
|
||||||
pointer && scroll && dropped && copy && compose)) {
|
!(key && repeat && commit >= 2 && preedit && pointer && scroll && dropped && copy &&
|
||||||
|
compose && captured && released)) {
|
||||||
uhdk_event_list events;
|
uhdk_event_list events;
|
||||||
OK(uhdk_app_poll_events(app, 50000000, &events));
|
OK(uhdk_app_poll_events(app, 50000000, &events));
|
||||||
for (uint64_t i = 0; i < events.len; ++i) {
|
for (uint64_t i = 0; i < events.len; ++i) {
|
||||||
const uhdk_event *e = &events.ptr[i];
|
const uhdk_event *e = &events.ptr[i];
|
||||||
|
if (e->input_id > acknowledged)
|
||||||
|
acknowledged = e->input_id;
|
||||||
switch (e->type) {
|
switch (e->type) {
|
||||||
case UHDK_EVENT_KEY: {
|
case UHDK_EVENT_KEY: {
|
||||||
const uhdk_key_event *k = e->data;
|
const uhdk_key_event *k = e->data;
|
||||||
if (k->physical_key == 4 && k->logical_key == 'a' && k->pressed)
|
if (k->physical_key == 4 && k->logical_key == 'a' && k->pressed)
|
||||||
key++;
|
key++;
|
||||||
|
if (k->physical_key == 224)
|
||||||
|
assert(!!(k->modifiers & UHDK_MOD_CONTROL) == !!k->pressed);
|
||||||
|
if (k->logical_key == 'A' && k->pressed)
|
||||||
|
assert(!(k->modifiers & UHDK_MOD_SHIFT));
|
||||||
if (k->repeat)
|
if (k->repeat)
|
||||||
repeat++;
|
repeat++;
|
||||||
break;
|
break;
|
||||||
|
|
@ -241,6 +277,22 @@ static void input(uhdk_app *app) {
|
||||||
preedit++;
|
preedit++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case UHDK_EVENT_POINTER_BUTTON:
|
||||||
|
if (((const uhdk_pointer_event *)e->data)->pressed)
|
||||||
|
OK(uhdk_window_set_pointer_mode_async(
|
||||||
|
w, UHDK_POINTER_CAPTURE, &capture_request
|
||||||
|
));
|
||||||
|
break;
|
||||||
|
case UHDK_EVENT_POINTER_MODE_CHANGED:
|
||||||
|
if (((const uhdk_pointer_mode_event *)e->data)->mode == UHDK_POINTER_CAPTURE)
|
||||||
|
captured++;
|
||||||
|
else
|
||||||
|
released++;
|
||||||
|
break;
|
||||||
|
case UHDK_EVENT_REQUEST_DONE:
|
||||||
|
if (((const uhdk_request_result *)e->data)->id == capture_request)
|
||||||
|
OK(((const uhdk_request_result *)e->data)->status);
|
||||||
|
break;
|
||||||
case UHDK_EVENT_POINTER_MOVE: {
|
case UHDK_EVENT_POINTER_MOVE: {
|
||||||
const uhdk_pointer_event *p = e->data;
|
const uhdk_pointer_event *p = e->data;
|
||||||
if (p->position.x == 12 && p->position.y == 18)
|
if (p->position.x == 12 && p->position.y == 18)
|
||||||
|
|
@ -263,6 +315,13 @@ static void input(uhdk_app *app) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (repeat && !published) {
|
||||||
|
state.surrounding_text = text("abcd");
|
||||||
|
state.revision = 78;
|
||||||
|
state.acknowledged_input_id = acknowledged;
|
||||||
|
OK(uhdk_window_set_text_state(w, &state));
|
||||||
|
published = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
|
|
@ -286,6 +345,17 @@ static void input(uhdk_app *app) {
|
||||||
OK(r.status);
|
OK(r.status);
|
||||||
const uhdk_text *t = r.data;
|
const uhdk_text *t = r.data;
|
||||||
assert(t->len == 11 && memcmp(t->ptr, "hello café", 11) == 0);
|
assert(t->len == 11 && memcmp(t->ptr, "hello café", 11) == 0);
|
||||||
|
OK(uhdk_clipboard_read_text_async(app, 100, &id));
|
||||||
|
uhdk_event_list pause;
|
||||||
|
OK(uhdk_app_poll_events(app, 100000000, &pause));
|
||||||
|
OK(uhdk_request_cancel(app, id));
|
||||||
|
assert(wait_result(app, id).status == UHDK_STATUS_CANCELLED);
|
||||||
|
OK(uhdk_clipboard_read_text_async(app, 100, &id));
|
||||||
|
assert(wait_result(app, id).status == UHDK_STATUS_INVALID_ARGUMENT);
|
||||||
|
OK(uhdk_clipboard_read_text_async(app, 0, &id));
|
||||||
|
r = wait_result(app, id);
|
||||||
|
OK(r.status);
|
||||||
|
assert(((const uhdk_text *)r.data)->len == 0);
|
||||||
OK(uhdk_clipboard_write_text_async(app, text("local copy"), &id));
|
OK(uhdk_clipboard_write_text_async(app, text("local copy"), &id));
|
||||||
OK(wait_result(app, id).status);
|
OK(wait_result(app, id).status);
|
||||||
OK(uhdk_window_request_focus_async(w, &id));
|
OK(uhdk_window_request_focus_async(w, &id));
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,8 @@ class Compositor:
|
||||||
self.tokens = False
|
self.tokens = False
|
||||||
self.finished_drop = False
|
self.finished_drop = False
|
||||||
self.outgoing = None
|
self.outgoing = None
|
||||||
|
self.receives = 0
|
||||||
|
self.held = []
|
||||||
|
|
||||||
def event(self, obj, name, *values):
|
def event(self, obj, name, *values):
|
||||||
interface = INTERFACES[self.objects[obj]]
|
interface = INTERFACES[self.objects[obj]]
|
||||||
|
|
@ -176,7 +178,15 @@ class Compositor:
|
||||||
elif iface == 'wl_data_offer' and name == 'receive':
|
elif iface == 'wl_data_offer' and name == 'receive':
|
||||||
mime, fd = args
|
mime, fd = args
|
||||||
assert not fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_NONBLOCK
|
assert not fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_NONBLOCK
|
||||||
os.write(fd, b'file:///tmp/drop%20file\r\n' if mime == 'text/uri-list' else 'hello café'.encode())
|
if mime == 'text/uri-list':
|
||||||
|
payload = b'file:///tmp/drop%20file\r\n'
|
||||||
|
else:
|
||||||
|
self.receives += 1
|
||||||
|
if self.receives == 3:
|
||||||
|
self.held.append(fd)
|
||||||
|
return
|
||||||
|
payload = b'\xff' if self.receives == 4 else b'' if self.receives == 5 else 'hello café'.encode()
|
||||||
|
os.write(fd, payload)
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
elif iface == 'wl_data_offer' and name == 'finish':
|
elif iface == 'wl_data_offer' and name == 'finish':
|
||||||
self.finished_drop = True
|
self.finished_drop = True
|
||||||
|
|
@ -207,6 +217,7 @@ class Compositor:
|
||||||
self.event(keyboard, 'key', 203, 3, 48, 1)
|
self.event(keyboard, 'key', 203, 3, 48, 1)
|
||||||
self.event(pointer, 'enter', 204, surface, 10 * 256, 10 * 256)
|
self.event(pointer, 'enter', 204, surface, 10 * 256, 10 * 256)
|
||||||
self.event(pointer, 'motion', 4, 12 * 256, 18 * 256)
|
self.event(pointer, 'motion', 4, 12 * 256, 18 * 256)
|
||||||
|
self.event(pointer, 'button', 204, 4, 0x110, 1)
|
||||||
self.event(pointer, 'axis_source', 0)
|
self.event(pointer, 'axis_source', 0)
|
||||||
self.event(pointer, 'axis', 5, 0, 5 * 256)
|
self.event(pointer, 'axis', 5, 0, 5 * 256)
|
||||||
self.event(pointer, 'axis_value120', 0, 60)
|
self.event(pointer, 'axis_value120', 0, 60)
|
||||||
|
|
@ -225,15 +236,29 @@ class Compositor:
|
||||||
text = self.ids.get('zwp_text_input_v3')
|
text = self.ids.get('zwp_text_input_v3')
|
||||||
if text and self.surfaces and not getattr(self, 'text_entered', False):
|
if text and self.surfaces and not getattr(self, 'text_entered', False):
|
||||||
self.text_entered = True
|
self.text_entered = True
|
||||||
self.event(text, 'enter', next(iter(self.surfaces)))
|
surface = next(iter(self.surfaces))
|
||||||
|
self.event(text, 'enter', surface)
|
||||||
|
if not self.inject:
|
||||||
|
keyboard = self.ids['wl_keyboard']
|
||||||
|
self.event(keyboard, 'enter', 190, surface, b'')
|
||||||
|
self.event(keyboard, 'key', 191, 1, 30, 1)
|
||||||
|
self.event(keyboard, 'key', 192, 2, 30, 0)
|
||||||
if self.later and time.monotonic() >= self.later[0]:
|
if self.later and time.monotonic() >= self.later[0]:
|
||||||
_, text, serial = self.later; self.later = None
|
_, text, serial = self.later; self.later = None
|
||||||
|
assert self.text_serial > serial, (self.text_serial, serial)
|
||||||
|
self.event(self.ids['wl_pointer'], 'button', 206, 6, 0x110, 0)
|
||||||
keyboard = self.ids['wl_keyboard']
|
keyboard = self.ids['wl_keyboard']
|
||||||
self.event(keyboard, 'key', 206, 6, 48, 0)
|
self.event(keyboard, 'key', 206, 6, 48, 0)
|
||||||
|
self.event(keyboard, 'key', 207, 7, 29, 1)
|
||||||
self.event(keyboard, 'modifiers', 207, 4, 0, 0, 0)
|
self.event(keyboard, 'modifiers', 207, 4, 0, 0, 0)
|
||||||
self.event(keyboard, 'key', 208, 7, 46, 1)
|
self.event(keyboard, 'key', 208, 7, 46, 1)
|
||||||
self.event(keyboard, 'key', 209, 8, 46, 0)
|
self.event(keyboard, 'key', 209, 8, 46, 0)
|
||||||
|
self.event(keyboard, 'key', 210, 9, 29, 0)
|
||||||
self.event(keyboard, 'modifiers', 210, 0, 0, 0, 0)
|
self.event(keyboard, 'modifiers', 210, 0, 0, 0, 0)
|
||||||
|
self.event(keyboard, 'modifiers', 210, 0, 1, 0, 0)
|
||||||
|
self.event(keyboard, 'key', 210, 9, 30, 1)
|
||||||
|
self.event(keyboard, 'modifiers', 210, 0, 0, 0, 0)
|
||||||
|
self.event(keyboard, 'key', 210, 9, 30, 0)
|
||||||
self.event(keyboard, 'key', 211, 9, 40, 1)
|
self.event(keyboard, 'key', 211, 9, 40, 1)
|
||||||
self.event(keyboard, 'key', 212, 10, 40, 0)
|
self.event(keyboard, 'key', 212, 10, 40, 0)
|
||||||
self.event(keyboard, 'key', 213, 11, 18, 1)
|
self.event(keyboard, 'key', 213, 11, 18, 1)
|
||||||
|
|
@ -266,7 +291,7 @@ def run(binary, mode, env=None):
|
||||||
finally:
|
finally:
|
||||||
if process.poll() is None: process.kill(); process.wait()
|
if process.poll() is None: process.kill(); process.wait()
|
||||||
server.close()
|
server.close()
|
||||||
for fd in compositor.fds: os.close(fd)
|
for fd in compositor.fds + compositor.held: os.close(fd)
|
||||||
if compositor.outgoing is not None: os.close(compositor.outgoing)
|
if compositor.outgoing is not None: os.close(compositor.outgoing)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue