Zinput

A cross platform, keyboard and mouse input library for Zig. This library is the backing behind both #ZClicker and #TeenyTask
33 Replies
Dumb Bird
Dumb Bird2mo ago
Alright so far Zinput has mouse support on Windows, Mac, and X11 Next thing is to get Wayland up in running Then it's on to keyboard support, but there is a good chance I will finish up #TeenyClicker before I finish up the keyboard side of Zinput I should probably log more stuff in here... Well, the library is going fine, most of all the ground work for emitting mouse events is done As far as a listening API goes, well. I'm not sure. I was thinking something like
const std = @import("std");
const Listener = @import("zinput").Listener;
const Button = @import("zinput").Button;

fn on_click(button: Button) void {
std.debug.print("You just clicked {any}", .{button});
}

fn on_move() void {
std.debug.print("Getting dizzy here...", .{});
}

pub fn main() !void {
var listener = try Listener.init();
defer listener.deint();

listener.on_click = on_click;
listener.on_move = on_move;
// You may give null if you just want to ignore it for right now.
// This is the default behavior if the function isn't supplied
listener.on_scroll = null;

listener.start();

// just chill or something

listener.stop();
}
const std = @import("std");
const Listener = @import("zinput").Listener;
const Button = @import("zinput").Button;

fn on_click(button: Button) void {
std.debug.print("You just clicked {any}", .{button});
}

fn on_move() void {
std.debug.print("Getting dizzy here...", .{});
}

pub fn main() !void {
var listener = try Listener.init();
defer listener.deint();

listener.on_click = on_click;
listener.on_move = on_move;
// You may give null if you just want to ignore it for right now.
// This is the default behavior if the function isn't supplied
listener.on_scroll = null;

listener.start();

// just chill or something

listener.stop();
}
@earth's penguin what do you think? .start() would likely create a seperate watching thread, where .stop() joins this thread
KeithBrown7526
KeithBrown75262mo ago
I like it
Dumb Bird
Dumb Bird2w ago
Alright back to finishing this What do I need to implement? Great question :) Well keyboard support for all systems, and the mouse listener for all systems That should cover it Actually Linux support is kind of there and it kind of isn't. I no longer have a mac system to actually test some code. The Zinput api is going to have to undergo a pretty major change to include both the linux backends As I want to have X11 and Wayland support in the same binary The issue is you can't both check it at runtime and import the backends accordingly at compile time. That just doesn't make sense So there are a few ways to go about fixing these I went for a solution which kind of hits a sweet middle. Though it would require a separate api usage for linux systems. This defeats some of my goals Pretty much there would be a X11Backend and WaylandBackend implementations. Which then the actual implementation used is selected at runtime via a thin linux wrapper called LinuxBackend That's the simple solution. This would require the backend having an init method so I can fetch which backend to use at runtime This doesn't align up with how everything else is structured. Previously using Zinput you'd do something like
const std = @import("std");
const zinput = @import("zinput")
const Mouse = zinput.Mouse;

pub fn main() !void {
const mouse = Mouse.new();
defer mouse.drop();

mouse.click(.left);
}
const std = @import("std");
const zinput = @import("zinput")
const Mouse = zinput.Mouse;

pub fn main() !void {
const mouse = Mouse.new();
defer mouse.drop();

mouse.click(.left);
}
But now the linux backend would require you initialize the LinuxBackend struct before using any methods. My simple solution is to rewrite how the api is structured. This also means you no longer need to handle the lifetimes of Keyboard or Mouse. That is all handled for you. The small downside is if you don't care for either Keyboard or Mouse both will be initialized. The upside is api simplicity, and to be honest the negatives aren't too bad. It's a one time slowdown and all uses henceforth should in theory provide the same speed as the current api. Under this logic it makes sense as to simplify the api and bring a good change into Zinput. Having support for both X11 and Wayland in one binary is super nice. This also expands Zinput making it so much easier to add even more backends if I ever choose to do so This would include controller support. Which I would love to add So overall it's a welcome change in my book. The api would look more like this now:
const std = @import("std");
const zinput = @import("zinput");

pub fn main() !void {
const input = try zinput.init();
defer input.deinit();

input.mouse.click(.left);
}
const std = @import("std");
const zinput = @import("zinput");

pub fn main() !void {
const input = try zinput.init();
defer input.deinit();

input.mouse.click(.left);
}
Maybe this could use some rethinking I would actually like to take use of some newer Zig features to allow people to select certain parts of the api to compile with This lazy dependency check is actually quite useful. As once I add controller support, or anything along those lines people likely aren't going to need them. For example my autoclicker #ZClicker would only need mouse support on the major platforms. No need to include keyboard and controller support It's just weird bloat. To do this though keyboard and mouse must be separate parts of the backend. They should work individually, separate from one another. Where this current api doesn't allow for this. Actually the original api worked really quite well for this Linux support was the tricky zone as I want to have runtime detection for Wayland and X11 Seeing as I want a universal api, changes to linux would result in changes to windows and macos Some thinking will be required in this case. Mice and keyboards already have init functions on them. Maybe I could just check for Wayland or X11 there then switch the backend accordingly? This sounds like a pretty decent idea As for modulating code I might as well get the framework for that now while I'm rewriting Zinput Whoops. After some digging it turns out Zig’s lazy code evaluation effectively means time is only spent compiling the code you reference. So no need to do a bunch of modularization :) This first version is likely going to have some code repetition in a few places This makes it just simple for me Seeing as MacOS is the only system that treats the cursor position as a f32 while all other systems use i32 I'll likely have to do some casting I might still support f32 if you use the MacOS backend directly, but otherwise it should be an i32
KeithBrown7526
how does a float for a cursor positon work? how are you on .5 of a pixel?
Dumb Bird
Dumb Bird2w ago
I'm not actually sure they use the float part of it. The type that a cursor position is stored with is a NSPoint Which has two f32's
KeithBrown7526
i see
Dumb Bird
Dumb Bird2w ago
Not actually sure if ths is how it works though As I no longer have a mac to test :(
KeithBrown7526
yea, in that case I would just cast it to an int
Dumb Bird
Dumb Bird2w ago
Holy crap Finally Support for runtime linux backend detection in the same binary is done The api is so trash, but now that I understand it works just fine it's back to the drawing board
KeithBrown7526
does it work though
Dumb Bird
Dumb Bird2w ago
Yes Wayland support isn't finished but from testing it does work. Same binary, running on Wayland and X11 give the same results I just need to rewrite the api again so I can be happy. I now understand what needs to go into it to make everything work. I can plan around this. I'll likely just use Mermaid to plan out an api Ok the mindmap is done
mindmap
root((**Zinput**))
)**backend**(
::icon(fa fa-cogs)
win32
::icon(fa-brands fa-windows)
macos
::icon(fa-brands fa-apple)
linux
::icon(fa-brands fa-linux)
x11
wayland
{{**API**}}
::icon(fa fa-toolbox)
(**Keyboard**)
::icon(fa fa-keyboard)
*init*
*deinit*

[Listener]
::icon(fa fa-headphones)
*init*
*deinit*
*start*
*stop*
on_press
on_release
on_hold
[Control]
::icon(fa fa-up-down-left-right)
press
release
tap
[Enums]
::icon(fa fa-database)
Key
KeyState
(**Mouse**)
::icon(fa fa-mouse)
*init*
*deinit*

[Listener]
::icon(fa fa-headphones)
*init*
*deinit*
*start*
*stop*
on_press
on_release
on_move
on_scroll
[Control]
::icon(fa fa-up-down-left-right)
getPosition
moveTo
moveBy
scroll
press
pressAt
release
releaseAt
click
clickAt
[Enums]
::icon(fa fa-database)
Button
ButtonState
Position
mindmap
root((**Zinput**))
)**backend**(
::icon(fa fa-cogs)
win32
::icon(fa-brands fa-windows)
macos
::icon(fa-brands fa-apple)
linux
::icon(fa-brands fa-linux)
x11
wayland
{{**API**}}
::icon(fa fa-toolbox)
(**Keyboard**)
::icon(fa fa-keyboard)
*init*
*deinit*

[Listener]
::icon(fa fa-headphones)
*init*
*deinit*
*start*
*stop*
on_press
on_release
on_hold
[Control]
::icon(fa fa-up-down-left-right)
press
release
tap
[Enums]
::icon(fa fa-database)
Key
KeyState
(**Mouse**)
::icon(fa fa-mouse)
*init*
*deinit*

[Listener]
::icon(fa fa-headphones)
*init*
*deinit*
*start*
*stop*
on_press
on_release
on_move
on_scroll
[Control]
::icon(fa fa-up-down-left-right)
getPosition
moveTo
moveBy
scroll
press
pressAt
release
releaseAt
click
clickAt
[Enums]
::icon(fa fa-database)
Button
ButtonState
Position
Dumb Bird
Dumb Bird2w ago
There it is on mermaid live
Online FlowChart & Diagrams Editor - Mermaid Live Editor
Simplify documentation and avoid heavy tools. Open source Visio Alternative. Commonly used for explaining your code! Mermaid is a simple markdown-like script language for generating charts from text via javascript.
Dumb Bird
Dumb Bird2w ago
No description
Dumb Bird
Dumb Bird2w ago
There it is as a PNG Maybe we have like "generic types" / an interface Which the platform apis implement? Linux backend detection in the same binary is implemented Again... Now with the api rewrite. Now it's just time to implement the backends Of course mouse listening is still something I need to implement. I cannot do so on MacOS though as I no longer have my MacBook Air X11 control backend is done Wayland backend isn't but the detection works just fine Ok I went ahead and created a small simple logo
Dumb Bird
Dumb Bird2w ago
No description
Dumb Bird
Dumb Bird2w ago
Maybe I should add a background to this...
Dumb Bird
Dumb Bird2w ago
No description
Dumb Bird
Dumb Bird2w ago
Ok a big chunk of Zinput is done now Listening support isn't done. But I would say Zinput is about 40% done now After listening support it'll be keyboard. It's very likely Zinput will ship without Listening support. Or at the very least no keyboard support This is just simply because it takes quite a bit of work to get all of these up and I would like to start using Zinput in some tools Of course this won't be a full release by any means, it'll just be a quick thing posted up for primarily me Also the Github repo is now up! I'll implement support for zig fetch once I actually push out a 1.0.0 release I do have some work ahead of me though...
Dumb Bird
Dumb Bird2w ago
No description
anic17
anic172w ago
I like this logo more than Atleg's one
Dumb Bird
Dumb Bird2w ago
Atleg's logo was horrible. I really had no ideas so I made something silly. This one actually had some time put into it.
anic17
anic172w ago
:abyss:
Dumb Bird
Dumb Bird2w ago
If you have no ideas the simple solution is to mash some things together, add color to the background, some shadow and boom. Logo You can now easily install Zinput using zig fetch --save https://github.com/ZackeryRSmith/zinput/archive/master.tar.gz Though Zinput itself isn't in a state where it can be easily compiled cross platform without manually linking headers As such it still can't be used yet even though you can install it really easily
Dumb Bird
Dumb Bird2w ago
Dumb Bird
Dumb Bird2w ago
That is how easy it is to install Zinput now 35 seconds to create a new zig project, install, and then setup Zinput Helpful for me have, so I'm keeping it here:
const zinput_dep = b.dependency("zinput", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zinput", zinput_dep.module("zinput"));
const zinput_dep = b.dependency("zinput", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zinput", zinput_dep.module("zinput"));
KeithBrown7526
imagine not knowing out to add a dependency (its ok, i dont either)
Dumb Bird
Dumb Bird2w ago
I doooooooo I just don't want to keep writing it over and over
KeithBrown7526
mhm sure
Dumb Bird
Dumb Bird2w ago
I'll murder you
KeithBrown7526
please do
Dumb Bird
Dumb Bird2w ago
To avoid needing tons of work: If you want to compile on Linux you must have both x11 and wayland installed on your system They're both needed to compile a single Linux binary which can be run on both x11 and wayland systems I may add compile options so you can disable x11 or wayland in your binary If I didn't do this I would have to ship my code with x11 and wayland source. So I'm not doing that... This is what Mach Core does for example, but I refuse to do so. You know what. Nevermind. Forget everything I have said I will do it Mach's way, as it allows for effortless cross-platform compilation. I want this. Otherwise my library would require you have system headers to compile. God, slimslag is amazing
Dumb Bird
Dumb Bird2w ago
GitHub
GitHub - hexops-graveyard/mach-system-sdk: DirectX 12 headers for M...
DirectX 12 headers for MinGW/Zig, cross-compile DX12/Metal/etc with Zig, etc. - hexops-graveyard/mach-system-sdk
Dumb Bird
Dumb Bird2w ago
He just provides a simple and easy way to get all platform sdks and make cross compiling easy Man, this guy is one of the best Zig programmers It actually goes out and clones the correct sdk depending on which system you're trying to compile to That's just so awesome OK scratch that again. I will support this at some point. For right now though getting the actual library done is more important. This will be done at a later date. Alright fetching and everything properly works, cross compiling still isn't a think, but ya know :)