Initial commit
commit
6f0d10fa2f
@ -0,0 +1,2 @@
|
||||
/target
|
||||
**/*.rs.bk
|
@ -0,0 +1,79 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "misclick"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rlua 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"x11 2.18.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rlua"
|
||||
version = "0.16.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11"
|
||||
version = "2.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum cc 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "f52a465a666ca3d838ebbf08b241383421412fe7ebb463527bba275526d89f76"
|
||||
"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
|
||||
"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
|
||||
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
|
||||
"checksum rlua 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "62fc0e980c94fe9ef795b1bb3874649c8c6e9bb67d3b90d48380ba24c69c23ea"
|
||||
"checksum x11 2.18.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39697e3123f715483d311b5826e254b6f3cfebdd83cf7ef3358f579c3d68e235"
|
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "misclick"
|
||||
version = "0.1.0"
|
||||
authors = ["eater <=@eater.me>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
rlua = "0.16.3"
|
||||
maplit = "1.0.2"
|
||||
|
||||
[dependencies.x11]
|
||||
version = "2.18.1"
|
||||
features = ["xlib", "xtest"]
|
@ -0,0 +1,2 @@
|
||||
bind(k.x, m.n(12))
|
||||
sync(m.left, k.n(132))
|
@ -0,0 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
use crate::types::{Bind, Handler};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Config {
|
||||
pub binds: HashMap<Bind, Handler>
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
use rlua::{Lua, Context, Error};
|
||||
use crate::maps::init_lua;
|
||||
use crate::types::{Bind, Action, Handler};
|
||||
use crate::config::Config;
|
||||
use std::default::Default;
|
||||
|
||||
pub fn load_config(lua_config: &str) -> Result<Config, Error> {
|
||||
let lua = Lua::new();
|
||||
|
||||
let bind: Result<_, Error> = lua.context(|c: Context| {
|
||||
init_lua(c)?;
|
||||
|
||||
let g = c.globals();
|
||||
|
||||
let mut config = Config::default();
|
||||
|
||||
c.scope::<_, Result<_, Error>>(|scope| {
|
||||
g.set("sync", scope.create_function_mut(|_: Context, args: (Bind, Bind, Option<bool>)| {
|
||||
println!("config -> sync({:?}, {:?}, {:?})", args.0, args.1, args.2);
|
||||
config
|
||||
.binds
|
||||
.insert(args.0, Handler {
|
||||
action: Action::Sync(args.1),
|
||||
passthrough: args.2.unwrap_or(false),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})?)?;
|
||||
|
||||
c.load(lua_config).exec()?;
|
||||
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
Ok(config)
|
||||
});
|
||||
|
||||
Ok(bind?)
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
mod types;
|
||||
mod config;
|
||||
mod maps;
|
||||
mod lua_config;
|
||||
|
||||
extern crate x11;
|
||||
|
||||
use x11::xlib;
|
||||
use x11::xtest;
|
||||
|
||||
use std::mem;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ptr::null;
|
||||
use x11::xlib::{AnyModifier, ButtonPress, ButtonRelease, ButtonReleaseMask, ButtonPressMask, GrabModeAsync, RevertToNone, BadCursor, XEvent, xError, XAnyEvent, xEvent, Display, XKeyEvent, InputFocus, KeyPress, KeyRelease, KeyPressMask, KeyReleaseMask, XButtonEvent, CurrentTime, Button1Mask, Button2Mask, Window};
|
||||
use std::io::Cursor;
|
||||
use x11::xinput2::XINotifyDetailNone;
|
||||
use crate::types::{Bind, Action, Handler};
|
||||
use std::os::raw::c_long;
|
||||
use std::collections::HashMap;
|
||||
use crate::types::Bind::{Key, Mouse};
|
||||
|
||||
struct XInfo {
|
||||
root: u64,
|
||||
display: *mut Display,
|
||||
}
|
||||
|
||||
struct Subscription {
|
||||
on: Bind,
|
||||
handler: Handler,
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
pub fn subscribe(&self, xinfo: &XInfo) {
|
||||
unsafe {
|
||||
match self.on {
|
||||
Bind::Key(key) => {
|
||||
xlib::XGrabKey(xinfo.display, key as i32, AnyModifier, xinfo.root, 0, GrabModeAsync, GrabModeAsync);
|
||||
}
|
||||
|
||||
Bind::Mouse(button) => {
|
||||
xlib::XGrabButton(xinfo.display, button, AnyModifier, xinfo.root, 0, (ButtonReleaseMask | ButtonPressMask) as u32, GrabModeAsync, GrabModeAsync, xinfo.root, 0);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsubscribe(&self, xinfo: &XInfo) {
|
||||
unsafe {
|
||||
match self.on {
|
||||
Bind::Key(key) => {
|
||||
xlib::XUngrabKey(xinfo.display, key as i32, AnyModifier, xinfo.root);
|
||||
}
|
||||
|
||||
Bind::Mouse(button) => {
|
||||
xlib::XUngrabButton(xinfo.display, button, AnyModifier, xinfo.root);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(&self, pressed: bool, xinfo: &XInfo, coords: Target) {
|
||||
println!("Handling trigger={:?} pressed={:?} action={:?}", self.on, pressed, self.handler.action);
|
||||
|
||||
match &self.handler.action {
|
||||
Action::Sync(bind) => {
|
||||
match bind {
|
||||
Bind::Key(key) => {
|
||||
unsafe {
|
||||
xtest::XTestFakeKeyEvent(xinfo.display, *key, pressed as i32, CurrentTime);
|
||||
}
|
||||
}
|
||||
|
||||
Bind::Mouse(key) => {
|
||||
unsafe {
|
||||
xtest::XTestFakeButtonEvent(xinfo.display, *key, pressed as i32, CurrentTime);
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let config = lua_config::load_config("\
|
||||
|
||||
# sync up keys with others
|
||||
# tables defined in maps.rs
|
||||
# manual key codes can be given via k.n() or m.n()
|
||||
sync(k.n(52), m.left)\
|
||||
\
|
||||
").unwrap();
|
||||
|
||||
let display = unsafe { xlib::XOpenDisplay(null()) };
|
||||
let root = unsafe { xlib::XDefaultRootWindow(display) };
|
||||
|
||||
unsafe { xtest::XTestGrabControl(display, true as i32) };
|
||||
|
||||
let xinfo = XInfo { display, root };
|
||||
|
||||
let mut subs: HashMap<Bind, Subscription> = HashMap::new();
|
||||
|
||||
for (bind, handler) in config.binds.iter() {
|
||||
let sub = Subscription { handler: (*handler).clone(), on: (*bind).clone() };
|
||||
sub.subscribe(&xinfo);
|
||||
subs.insert((*bind).clone(), sub);
|
||||
}
|
||||
|
||||
let mut event: xlib::XEvent = unsafe { mem::MaybeUninit::uninit().assume_init() };
|
||||
loop {
|
||||
unsafe { xlib::XNextEvent(display, &mut event); }
|
||||
match event.get_type() {
|
||||
xlib::KeyPress => {
|
||||
let key = Key(unsafe { event.key.keycode });
|
||||
if let Some(sub) = subs.get(&key) {
|
||||
sub.handle(true, &xinfo, unsafe { event.key.to_coords() });
|
||||
}
|
||||
}
|
||||
|
||||
xlib::KeyRelease => {
|
||||
let key = Key(unsafe { event.key.keycode });
|
||||
if let Some(sub) = subs.get(&key) {
|
||||
sub.handle(false, &xinfo, unsafe { event.key.to_coords() });
|
||||
}
|
||||
}
|
||||
|
||||
xlib::ButtonPress => {
|
||||
let key = Mouse(unsafe { event.button.button });
|
||||
if let Some(sub) = subs.get(&key) {
|
||||
sub.handle(true, &xinfo, unsafe { event.button.to_coords() });
|
||||
}
|
||||
}
|
||||
|
||||
xlib::ButtonRelease => {
|
||||
let key = Mouse(unsafe { event.button.button });
|
||||
if let Some(sub) = subs.get(&key) {
|
||||
sub.handle(false, &xinfo, unsafe { event.button.to_coords() });
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Target {
|
||||
x: i32,
|
||||
y: i32,
|
||||
x_root: i32,
|
||||
y_root: i32,
|
||||
window: Window,
|
||||
subwindow: Window,
|
||||
}
|
||||
|
||||
trait ToCoords {
|
||||
fn to_coords(&self) -> Target;
|
||||
}
|
||||
|
||||
impl ToCoords for XKeyEvent {
|
||||
fn to_coords(&self) -> Target {
|
||||
Target {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
x_root: self.x_root,
|
||||
y_root: self.y_root,
|
||||
window: self.window,
|
||||
subwindow: self.subwindow,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToCoords for XButtonEvent {
|
||||
fn to_coords(&self) -> Target {
|
||||
Target {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
x_root: self.x_root,
|
||||
y_root: self.y_root,
|
||||
window: self.window,
|
||||
subwindow: self.subwindow,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
use rlua::{Result, Context};
|
||||
use crate::types::Bind;
|
||||
|
||||
pub fn init_lua(lua: Context) -> Result<()> {
|
||||
create_button_map(lua)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_button_map(lua: Context) -> Result<()> {
|
||||
let globals = lua.globals();
|
||||
|
||||
let mouse_table = lua.create_table()?;
|
||||
|
||||
mouse_table.set("n", lua.create_function(|_, n: u32| Ok(Bind::Mouse(n)))?)?;
|
||||
mouse_table.set("left", Bind::Mouse(1))?;
|
||||
mouse_table.set("middle", Bind::Mouse(2))?;
|
||||
mouse_table.set("right", Bind::Mouse(3))?;
|
||||
mouse_table.set("scroll_up", Bind::Mouse(4))?;
|
||||
mouse_table.set("scroll_down", Bind::Mouse(5))?;
|
||||
|
||||
globals.set("mouse", mouse_table.clone())?;
|
||||
globals.set("m", mouse_table)?;
|
||||
|
||||
let key_table = lua.create_table()?;
|
||||
key_table.set("n", lua.create_function(|_, n: u32| Ok(Bind::Key(n)))?)?;
|
||||
|
||||
globals.set("key", key_table.clone())?;
|
||||
globals.set("k", key_table.clone())?;
|
||||
|
||||
return Ok(());
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
use rlua::{FromLua, Context, Value, Error, ToLua};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Hash, Ord, PartialOrd, Eq, PartialEq, Debug, Clone)]
|
||||
pub enum Bind {
|
||||
Mouse(u32),
|
||||
Key(u32),
|
||||
Ping(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Handler {
|
||||
pub passthrough: bool,
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Action {
|
||||
Sync(Bind),
|
||||
Press(Bind),
|
||||
Release(Bind),
|
||||
Sleep(u32),
|
||||
List(Vec<Action>),
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Bind {
|
||||
fn from_lua(lua_value: Value<'lua>, _: Context<'lua>) -> Result<Self, Error> {
|
||||
if let Value::Integer(n) = lua_value {
|
||||
return Ok(Bind::Key(u32::try_from(n).map_err(|e| Error::external(e))?));
|
||||
}
|
||||
|
||||
if let Value::String(c) = lua_value {
|
||||
return Ok(Bind::Ping(String::from(c.to_str()?)));
|
||||
}
|
||||
|
||||
if let Value::Table(table) = lua_value {
|
||||
let typ: String = table.get("type")?;
|
||||
let bind = match typ.as_str() {
|
||||
"mouse" => Bind::Mouse(table.get("code")?),
|
||||
"key" => Bind::Key(table.get("code")?),
|
||||
"ping" => Bind::Ping(table.get("code")?),
|
||||
_ => return Err(Error::RuntimeError("Type should be one of mouse, key or ping".to_string()))
|
||||
};
|
||||
|
||||
return Ok(bind);
|
||||
}
|
||||
|
||||
Err(Error::RuntimeError("Can't create Bind from non-table".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> ToLua<'lua> for Bind {
|
||||
fn to_lua(self, lua: Context<'lua>) -> Result<Value<'lua>, Error> {
|
||||
let table = lua.create_table()?;
|
||||
table.set("type", match self {
|
||||
Bind::Key(_) => "key",
|
||||
Bind::Ping(_) => "ping",
|
||||
Bind::Mouse(_) => "mouse",
|
||||
})?;
|
||||
|
||||
match self {
|
||||
Bind::Key(n) => table.set("code", n)?,
|
||||
Bind::Mouse(n) => table.set("code", n)?,
|
||||
Bind::Ping(c) => table.set("code", c)?,
|
||||
}
|
||||
|
||||
Ok(table.to_lua(lua)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> FromLua<'lua> for Action {
|
||||
fn from_lua(lua_value: Value<'lua>, _: Context<'lua>) -> Result<Self, Error> {
|
||||
let table = if let Value::Table(table) = lua_value {
|
||||
table
|
||||
} else {
|
||||
return Err(Error::RuntimeError("Action should be a table".to_string()));
|
||||
};
|
||||
|
||||
let res = match table.get::<&str, String>("type")?.as_str() {
|
||||
"sync" => Action::Sync(table.get("bind")?),
|
||||
"press" => Action::Press(table.get("bind")?),
|
||||
"release" => Action::Release(table.get("bind")?),
|
||||
"sleep" => Action::Sleep(table.get("time")?),
|
||||
"list" => Action::List(table.get("items")?),
|
||||
_ => return Err(Error::RuntimeError("Action type is invalid, should be one of sync, release, press, sleep, list".to_string()))
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue