Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for X TEST extension to send fake click events #18

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions common.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ const common = @This();

pub const SocketReader = std.io.Reader(std.posix.socket_t, std.posix.RecvFromError, readSocket);

/// Sanity check that we're not running into data integrity (corruption) issues caused
/// by overflowing and wrapping around to the front ofq the buffer.
fn checkMessageLengthFitsInBuffer(message_length: usize, buffer_limit: usize) !void {
if(message_length > buffer_limit) {
std.debug.panic("Reply is bigger than our buffer (data corruption will ensue) {} > {}. In order to fix, increase the buffer size.", .{
message_length,
buffer_limit,
});
}
}

pub fn send(sock: std.posix.socket_t, data: []const u8) !void {
const sent = try x.writeSock(sock, data, 0);
if (sent != data.len) {
Expand Down Expand Up @@ -186,3 +197,67 @@ pub fn asReply(comptime T: type, msg_bytes: []align(4) u8) !*T {
fn readSocket(sock: std.posix.socket_t, buffer: []u8) !usize {
return x.readSock(sock, buffer, 0);
}

/// X server extension info.
pub const ExtensionInfo = struct {
extension_name: []const u8,
/// The extension opcode is used to identify which X extension a given request is
/// intended for (used as the major opcode). This essentially namespaces any extension
/// requests. The extension differentiates its own requests by using a minor opcode.
opcode: u8,
/// Extension error codes are added on top of this base error code.
base_error_code: u8,
};

pub const ExtensionVersion = struct {
major_version: u16,
minor_version: u16,
};

/// Determines whether the extension is available on the server.
pub fn getExtensionInfo(
sock: std.posix.socket_t,
buffer: *x.ContiguousReadBuffer,
comptime extension_name: []const u8,
) !?ExtensionInfo {
const reader = common.SocketReader{ .context = sock };
const buffer_limit = buffer.half_len;

{
const ext_name = comptime x.Slice(u16, [*]const u8).initComptime(extension_name);
var message_buffer: [x.query_extension.getLen(ext_name.len)]u8 = undefined;
x.query_extension.serialize(&message_buffer, ext_name);
try common.send(sock, &message_buffer);
}
const message_length = try x.readOneMsg(reader, @alignCast(buffer.nextReadBuffer()));
try checkMessageLengthFitsInBuffer(message_length, buffer_limit);
const optional_extension = blk: {
switch (x.serverMsgTaggedUnion(@alignCast(buffer.double_buffer_ptr))) {
.reply => |msg_reply| {
const msg: *x.ServerMsg.QueryExtension = @ptrCast(msg_reply);
if (msg.present == 0) {
std.log.info("{s} extension: not present", .{extension_name});
break :blk null;
}
std.debug.assert(msg.present == 1);
std.log.info("{s} extension: opcode={} base_error_code={}", .{
extension_name,
msg.major_opcode,
msg.first_error,
});
std.log.info("{s} extension: {}", .{ extension_name, msg });
break :blk ExtensionInfo{
.extension_name = extension_name,
.opcode = msg.major_opcode,
.base_error_code = msg.first_error,
};
},
else => |msg| {
std.log.err("expected a reply for `x.query_extension` but got {}", .{msg});
return error.ExpectedReplyButGotSomethingElse;
},
}
};

return optional_extension;
}
119 changes: 90 additions & 29 deletions testexample.zig
Original file line number Diff line number Diff line change
Expand Up @@ -211,52 +211,85 @@ pub fn main() !u8 {
};


{
const ext_name = comptime x.Slice(u16, [*]const u8).initComptime("RENDER");
var msg: [x.query_extension.getLen(ext_name.len)]u8 = undefined;
x.query_extension.serialize(&msg, ext_name);
try conn.send(&msg);
}
_ = try x.readOneMsg(conn.reader(), @alignCast(buf.nextReadBuffer()));
const opt_render_ext: ?struct { opcode: u8 } = blk: {
const opt_render_ext = try common.getExtensionInfo(
conn.sock,
&buf,
"RENDER"
);
if (opt_render_ext) |render_ext| {
const expected_version: common.ExtensionVersion = .{ .major_version = 0, .minor_version = 11 };
{
var msg: [x.render.query_version.len]u8 = undefined;
x.render.query_version.serialize(&msg, render_ext.opcode, .{
.major_version = expected_version.major_version,
.minor_version = expected_version.minor_version,
});
try conn.send(&msg);
}
_ = try x.readOneMsg(conn.reader(), @alignCast(buf.nextReadBuffer()));
switch (x.serverMsgTaggedUnion(@alignCast(buf.double_buffer_ptr))) {
.reply => |msg_reply| {
const msg: *x.ServerMsg.QueryExtension = @ptrCast(msg_reply);
if (msg.present == 0) {
std.log.info("RENDER extension: not present", .{});
break :blk null;
const msg: *x.render.query_version.Reply = @ptrCast(msg_reply);
std.log.info("X RENDER extension: version {}.{}", .{msg.major_version, msg.minor_version});
if (msg.major_version != expected_version.major_version) {
std.log.err("X RENDER extension major version is {} but we expect {}", .{
msg.major_version,
expected_version.major_version,
});
return 1;
}
if (msg.minor_version < expected_version.minor_version) {
std.log.err("X RENDER extension minor version is {}.{} but I've only tested >= {}.{})", .{
msg.major_version,
msg.minor_version,
expected_version.major_version,
expected_version.minor_version,
});
return 1;
}
std.debug.assert(msg.present == 1);
std.log.info("RENDER extension: opcode={}", .{msg.major_opcode});
break :blk .{ .opcode = msg.major_opcode };
},
else => |msg| {
std.log.err("expected a reply but got {}", .{msg});
return 1;
},

}
};
if (opt_render_ext) |render_ext| {
}

const opt_test_ext = try common.getExtensionInfo(
conn.sock,
&buf,
"XTEST"
);
if (opt_test_ext) |test_ext| {
const expected_version: common.ExtensionVersion = .{ .major_version = 2, .minor_version = 2 };
{
var msg: [x.render.query_version.len]u8 = undefined;
x.render.query_version.serialize(&msg, render_ext.opcode, .{
.major_version = 0,
.minor_version = 11,
var msg: [x.testext.get_version.len]u8 = undefined;
x.testext.get_version.serialize(&msg, .{
.ext_opcode = test_ext.opcode,
.wanted_major_version = expected_version.major_version,
.wanted_minor_version = expected_version.minor_version,
});
try conn.send(&msg);
}
_ = try x.readOneMsg(conn.reader(), @alignCast(buf.nextReadBuffer()));
switch (x.serverMsgTaggedUnion(@alignCast(buf.double_buffer_ptr))) {
.reply => |msg_reply| {
const msg: *x.render.query_version.Reply = @ptrCast(msg_reply);
std.log.info("RENDER extension: version {}.{}", .{msg.major_version, msg.minor_version});
if (msg.major_version != 0) {
std.log.err("xrender extension major version {} too new", .{msg.major_version});
const msg: *x.testext.get_version.Reply = @ptrCast(msg_reply);
std.log.info("XTEST extension: version {}.{}", .{msg.major_version, msg.minor_version});
if (msg.major_version != expected_version.major_version) {
std.log.err("XTEST extension major version is {} but we expect {}", .{
msg.major_version,
expected_version.major_version,
});
return 1;
}
if (msg.minor_version < 11) {
std.log.err("xrender extension minor version {} too old", .{msg.minor_version});
if (msg.minor_version < expected_version.minor_version) {
std.log.err("XTEST extension minor version is {}.{} but I've only tested >= {}.{})", .{
msg.major_version,
msg.minor_version,
expected_version.major_version,
expected_version.minor_version,
});
return 1;
}
},
Expand All @@ -273,6 +306,35 @@ pub fn main() !u8 {
try conn.send(&msg);
}

// Send a fake mouse left-click event
// if (opt_test_ext) |test_ext| {
// {
// var msg: [x.testext.fake_input.len]u8 = undefined;
// x.testext.fake_input.serialize(&msg, test_ext.opcode, .{
// .button_press = .{
// .event_type = x.testext.FakeEventType.button_press,
// .detail = 1,
// .delay_ms = 0,
// .device_id = null,
// },
// });
// try conn.send(&msg);
// }

// {
// var msg: [x.testext.fake_input.len]u8 = undefined;
// x.testext.fake_input.serialize(&msg, test_ext.opcode, .{
// .button_press = .{
// .event_type = x.testext.FakeEventType.button_release,
// .detail = 1,
// .delay_ms = 0,
// .device_id = null,
// },
// });
// try conn.send(&msg);
// }
// }

while (true) {
{
const recv_buf = buf.nextReadBuffer();
Expand Down Expand Up @@ -528,7 +590,6 @@ fn render(
try common.send(sock, &msg);
}
}

}

fn changeGcColor(sock: std.posix.socket_t, gc_id: u32, color: u32) !void {
Expand Down
1 change: 1 addition & 0 deletions x.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const windows = std.os.windows;
pub const inputext = @import("xinputext.zig");
pub const render = @import("xrender.zig");
pub const dbe = @import("xdbe.zig");
pub const testext = @import("xtest.zig");

// Expose some helpful stuff
pub const MappedFile = @import("MappedFile.zig");
Expand Down
Loading