-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make window platform agnostic, add 'wasd mouse' example
- Loading branch information
Showing
12 changed files
with
354 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import map2 | ||
|
||
def on_window_change(active_window_class): | ||
print("active window class: {}".format(active_window_class)) | ||
|
||
|
||
window = map2.Window() | ||
window.on_window_change(on_window_change) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
''' | ||
Move the mouse using the 'w', 'a', 's', 'd' directional keys. | ||
''' | ||
|
||
import map2 | ||
import time | ||
import subprocess | ||
import threading | ||
|
||
map2.default(layout = "us") | ||
|
||
# an easy to use interval utility that allows us to run a function on a timer | ||
class setInterval: | ||
def __init__(self, interval, action): | ||
self.interval = interval | ||
self.action = action | ||
self.stopEvent = threading.Event() | ||
thread = threading.Thread(target = self.__setInterval) | ||
thread.start() | ||
|
||
def __setInterval(self): | ||
nextTime = time.time() + self.interval | ||
while not self.stopEvent.wait(nextTime - time.time()): | ||
nextTime += self.interval | ||
self.action() | ||
|
||
def cancel(self): | ||
self.stopEvent.set() | ||
|
||
# read from input devices | ||
reader_kbd = map2.Reader(patterns=["/dev/input/by-id/example-keyboard"]) | ||
reader_mouse = map2.Reader(patterns=["/dev/input/by-id/example-mouse"]) | ||
|
||
# add new virtual output devices | ||
writer_kbd = map2.Writer(clone_from = "/dev/input/by-id/example-keyboard") | ||
writer_mouse = map2.Writer(clone_from = "/dev/input/by-id/example-mouse") | ||
|
||
# add mappers | ||
mapper_kbd = map2.Mapper() | ||
mapper_mouse = map2.Mapper() | ||
|
||
# to move the mouse programmatically, we need a virtual reader | ||
out_mouse = map2.VirtualReader() | ||
|
||
# setup the event routing | ||
map2.link([reader_kbd, mapper_kbd, writer_kbd]) | ||
map2.link([reader_mouse, mapper_mouse, writer_mouse]) | ||
map2.link([out_mouse, writer_mouse]) | ||
|
||
|
||
# we keep a map of intervals that maps each key to the associated interval | ||
intervals = {} | ||
|
||
def mouse_ctrl(key, state, axis, multiplier): | ||
def inner_fn(): | ||
# if the key was released, remove and cancel the corresponding interval | ||
if state == 0: | ||
if key in intervals: | ||
intervals.pop(key).cancel() | ||
return | ||
|
||
# this function will move our mouse using the virtual reader from earlier | ||
def send(): | ||
value = 15 * multiplier | ||
out_mouse.send("{{relative {} {}}}".format(axis, value)) | ||
|
||
# we call it once to move the mouse a bit immediately on key down | ||
send() | ||
# and register an interval that will continue to move it on a timer | ||
intervals[key] = setInterval(0.02, send) | ||
return inner_fn | ||
|
||
|
||
# setup the key mappings | ||
mapper_kbd.map("w up", mouse_ctrl("w", 0, "Y", -1)) | ||
mapper_kbd.map("w down", mouse_ctrl("w", 1, "Y", -1)) | ||
mapper_kbd.nop("w repeat") | ||
|
||
mapper_kbd.map("a up", mouse_ctrl("a", 0, "X", -1)) | ||
mapper_kbd.map("a down", mouse_ctrl("a", 1, "X", -1)) | ||
mapper_kbd.nop("a repeat") | ||
|
||
mapper_kbd.map("s up", mouse_ctrl("s", 0, "Y", 1)) | ||
mapper_kbd.map("s down", mouse_ctrl("s", 1, "Y", 1)) | ||
mapper_kbd.nop("s repeat") | ||
|
||
mapper_kbd.map("d up", mouse_ctrl("d", 0, "X", 1)) | ||
mapper_kbd.map("d down", mouse_ctrl("d", 1, "X", 1)) | ||
mapper_kbd.nop("d repeat") | ||
|
||
|
||
# Keep running forever | ||
map2.wait() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use std::process::Command; | ||
|
||
pub enum Platform { | ||
Hyprland, | ||
X11, | ||
Unknown, | ||
} | ||
|
||
|
||
pub fn get_platform() -> Platform { | ||
if platform_is_hyprland() { | ||
return Platform::Hyprland; | ||
} | ||
if platform_is_x11() { | ||
return Platform::X11; | ||
} | ||
Platform::Unknown | ||
} | ||
|
||
fn platform_is_hyprland() -> bool { | ||
Command::new("printenv") | ||
.arg("HYPRLAND_INSTANCE_SIGNATURE") | ||
.status() | ||
.map(|status| status.success()) | ||
.unwrap_or(false) | ||
} | ||
|
||
fn platform_is_x11() -> bool { | ||
Command::new("printenv") | ||
.arg("XDG_SESSION_TYPE") | ||
.output() | ||
.map(|info| info.status.success() && String::from_utf8_lossy(&info.stdout) == "x11") | ||
.unwrap_or(false) | ||
} | ||
|
||
// fn platform_is_sway() -> bool { | ||
// Command::new("printenv") | ||
// .arg("SWAYSOCK") | ||
// .status() | ||
// .map(|status| status.success()) | ||
// .unwrap_or(false) | ||
// } | ||
|
||
// for kde/kwin (wayland) | ||
// https://unix.stackexchange.com/questions/706477/is-there-a-way-to-get-list-of-windows-on-kde-wayland | ||
|
||
// for gnome | ||
// https://github.com/ActivityWatch/aw-watcher-window/pull/46/files |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
use std::panic::catch_unwind; | ||
use crate::*; | ||
use crate::python::*; | ||
use hyprland::event_listener::EventListenerMutable as EventListener; | ||
use crate::window::window_base::{ActiveWindowInfo, WindowControlMessage, WindowHandler}; | ||
|
||
pub fn hyprland_window_handler() -> WindowHandler { | ||
Box::new(|exit_rx: oneshot::Receiver<()>, | ||
subscription_rx: mpsc::Receiver<WindowControlMessage>| -> Result<()> { | ||
let mut subscriptions = Arc::new(Mutex::new(HashMap::new())); | ||
|
||
let prev_hook = std::panic::take_hook(); | ||
std::panic::set_hook(Box::new(|_info| {})); | ||
|
||
let mut event_listener = catch_unwind(|| EventListener::new()) | ||
.map_err(|err| anyhow!( | ||
"hyprland connection error: {}", | ||
err.downcast::<String>().unwrap_or(Box::new("unknown".to_string()) | ||
)))?; | ||
|
||
std::panic::set_hook(prev_hook); | ||
|
||
event_listener.add_active_window_change_handler(move |info, _| { | ||
if exit_rx.try_recv().is_ok() { return; } | ||
|
||
while let Ok(msg) = subscription_rx.try_recv() { | ||
match msg { | ||
WindowControlMessage::Subscribe(id, callback) => { subscriptions.lock().unwrap().insert(id, callback); } | ||
WindowControlMessage::Unsubscribe(id) => { subscriptions.lock().unwrap().remove(&id); } | ||
} | ||
} | ||
|
||
if let Some(info) = info { | ||
let val = ActiveWindowInfo { | ||
class: info.window_class.clone(), | ||
instance: "".to_string(), | ||
name: info.window_title.clone(), | ||
}; | ||
|
||
Python::with_gil(|py| { | ||
for callback in subscriptions.lock().unwrap().values() { | ||
let is_callable = callback.as_ref(py).is_callable(); | ||
if !is_callable { continue; } | ||
|
||
let _ = callback.call(py, (val.class.clone(), ), None); | ||
} | ||
}); | ||
} | ||
}); | ||
event_listener.start_listener()?; | ||
Ok(()) | ||
}) | ||
} |
Oops, something went wrong.