Skip to content

Commit

Permalink
Simplified the application by removing callback functions
Browse files Browse the repository at this point in the history
The cost-benefit ratio of supporting callback functions was too low, leading to
both code and API complexity. Therefore, callback functions have been removed
to streamline the codebase.

Changes:
- Updated README.md to reflect the updated binding mechanism and simplified usage.
- Removed callback function support from the main application logic.
- Refactored `src/main.rs` to use `ScriptManager` instead of `LuaManager`.
- Created a new `src/script_manager.rs` file to handle script management without callbacks.
- Adjusted the key binding examples and roadmap in the documentation.
  • Loading branch information
divanvisagie committed Jul 10, 2024
1 parent 0e7f726 commit bd8ec1d
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 133 deletions.
41 changes: 8 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ A wayland replacement for xbindkeys

🚧 **This project is currently under construction** 🚧

You can basically only bind functions to Alt+A and Alt+B and even then not many userspace applications will work because we still need to sort out user privelege and bringing in your variables since it runs as root... for now.

I have really only been able to get it to launch xterm bound to ALT+A.
You can bind direct bash commands to some key combos but not all combos are supported or tested. Performance is also untested so you may find
tasks like app switching to be a little unsatisfactory.

## Philosophy

Expand All @@ -16,45 +15,21 @@ wbindkeys uses lua for maximum configurability, because sometimes you need an if
### Config Example
```lua
-- Run alacritty on ALT+T
bind("ALT+T", "alacritty")

-- Run a lua function when calling
bind("ALT+L", function()
print("Hello, World!")
end)

-- Execute an external script
bind("ALT+X", "sh -c 'echo Hello, World!'")

-- Execute an external script from inside a function
bind("ALT+Y", function()
os.execute("sh -c 'echo Hello, World!'")
end)

bind("ALT+A", "alacritty")
bind("ALT+T", "flatpak run org.telegram.desktop")
```

# Roadmap
# Roadmap to 0.1.0
- [x] Hook into wayland keyboard events
- [x] Get a binding to execute a print from a lua binding config
- [x] Execute the command
- [ ] Implement full range of keymaps
- [ ] Fix for launching app in userspace on the users privelege level

- [x] Fix for launching app in userspace on the users privelege level
- [ ] Implement and test full range of keymaps
- [ ] Debian installer

# Development Setup

```sh
./configure
make
```

The following configuration allows usage without sudo:

```sh
sudo usermod -aG input yourusername
```
Creating a udev rule involves creating a file in /etc/udev/rules.d/ (e.g., 99-input.rules) with contents along the lines of:

```
ACTION=="add", KERNEL=="event*", SUBSYSTEM=="input", MODE="660", GROUP="input"
```
112 changes: 12 additions & 100 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
use input::event::keyboard::{KeyState, KeyboardEventTrait};
use input::{Event, Libinput, LibinputInterface};
use libc::{O_RDONLY, O_RDWR, O_WRONLY};
use mlua::Lua;
use parser::{parse_binding, Keys};
use std::collections::HashMap;
use parser::Keys;
use script_manager::ScriptManager;
use std::fs::{File, OpenOptions};
use std::os::unix::{fs::OpenOptionsExt, io::OwnedFd};
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::u32;

mod parser;
mod script_manager;

struct Interface;
struct WBindKeysInterface;

impl LibinputInterface for Interface {
impl LibinputInterface for WBindKeysInterface {
fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<OwnedFd, i32> {
let file = OpenOptions::new()
.custom_flags(flags)
Expand All @@ -34,104 +32,18 @@ impl LibinputInterface for Interface {
}
}

#[derive(Debug)]
enum Bindtype {
Command(String),
Function(mlua::Function<'static>),
}

struct LuaManager {
lua: &'static Lua,
actions: Arc<Mutex<HashMap<Vec<u32>, Bindtype>>>,
}

impl LuaManager {
fn new() -> Self {
let lua = Box::leak(Box::new(Lua::new()));
let actions = Arc::new(Mutex::new(HashMap::new()));

LuaManager { lua, actions }
}

fn register_functions(&self) -> Result<(), mlua::Error> {
let actions_str = Arc::clone(&self.actions);
let actions_fun = Arc::clone(&self.actions);

let basic_bind =
self.lua
.create_function(move |_, (binding, target): (String, String)| {
println!("Binding key: {:?}", binding);
println!("Target: {:?}", target);
let mut actions_lock = actions_str.lock().unwrap();
let binding = parse_binding(&binding);
let target = Bindtype::Command(target);
actions_lock.insert(binding, target);
Ok(())
})?;
self.lua.globals().set("bind", basic_bind)?;

let fun_bind =
self.lua
.create_function(move |_, (binding, target): (String, mlua::Function)| {
println!("Binding key: {:?}", binding);
println!("Target: {:?}", target);
let mut actions_lock = actions_fun.lock().unwrap();
let binding = parse_binding(&binding);

let target = Bindtype::Function(target);
actions_lock.insert(binding, target);
Ok(())
})?;
self.lua.globals().set("bind_fn", fun_bind)?;

Ok(())
}

fn load_script(&self, script: &str) -> Result<(), mlua::Error> {
self.lua.load(script).exec()
}

fn handle_action(&self, total_combo: Vec<u32>, state: KeyState) {
if let Some(action) = self.actions.lock().unwrap().get(&total_combo) {
if state == KeyState::Pressed {
println!("Action: {:?}", action);

match action {
Bindtype::Function(func) => {
let result = func.call::<_, ()>(());
println!("Result output {:?}", result);
}
Bindtype::Command(command) => {
// run_command_as_user(command);
Command::new("sh")
.arg("-c")
.arg(command)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Failed to execute command");
}
}
}
}
}
}

fn main() {
let mut input = Libinput::new_with_udev(Interface);
let mut input = Libinput::new_with_udev(WBindKeysInterface);
input.udev_assign_seat("seat0").unwrap();

let lua_manager = LuaManager::new();
lua_manager.register_functions().unwrap();
let script_manager = ScriptManager::new();
script_manager.register_functions().unwrap();

let script = r#"
bind_fn("Alt+A", function()
print("Hello from lua")
os.execute("alacritty")
end)
bind("Alt+C", "alacritty")
bind("Alt+A", "alacritty")
bind("Alt+C", "flatpak run org.telegram.desktop")
"#;
lua_manager.load_script(script).unwrap();
script_manager.load_script(script).unwrap();

let mut active_keys = Vec::new();
loop {
Expand Down Expand Up @@ -159,7 +71,7 @@ fn main() {
.copied()
.collect::<Vec<u32>>();

lua_manager.handle_action(total_combo, state);
script_manager.handle_action(total_combo, state);

}
_ => {} // Ignore non-keyboard events
Expand Down
72 changes: 72 additions & 0 deletions src/script_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

use input::event::keyboard::KeyState;
use mlua::Lua;
use std::collections::HashMap;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::u32;

use crate::parser::parse_binding;

#[derive(Debug)]
enum Bindtype {
Command(String),
}

pub struct ScriptManager {
lua: &'static Lua,
actions: Arc<Mutex<HashMap<Vec<u32>, Bindtype>>>,
}

impl ScriptManager {
pub fn new() -> Self {
let lua = Box::leak(Box::new(Lua::new()));
let actions = Arc::new(Mutex::new(HashMap::new()));

ScriptManager { lua, actions }
}

pub fn register_functions(&self) -> Result<(), mlua::Error> {
let actions_str = Arc::clone(&self.actions);

let basic_bind =
self.lua
.create_function(move |_, (binding, target): (String, String)| {
println!("Binding key: {:?}", binding);
println!("Target: {:?}", target);
let mut actions_lock = actions_str.lock().unwrap();
let binding = parse_binding(&binding);
let target = Bindtype::Command(target);
actions_lock.insert(binding, target);
Ok(())
})?;
self.lua.globals().set("bind", basic_bind)?;

Ok(())
}

pub fn load_script(&self, script: &str) -> Result<(), mlua::Error> {
self.lua.load(script).exec()
}

pub fn handle_action(&self, total_combo: Vec<u32>, state: KeyState) {
if let Some(action) = self.actions.lock().unwrap().get(&total_combo) {
if state == KeyState::Pressed {
println!("Action: {:?}", action);

match action {
Bindtype::Command(command) => {
// run_command_as_user(command);
Command::new("sh")
.arg("-c")
.arg(command)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Failed to execute command");
}
}
}
}
}
}

0 comments on commit bd8ec1d

Please sign in to comment.