Last active
March 21, 2025 13:41
-
-
Save sminez/b08252dc04f646b47efb5738ac46a112 to your computer and use it in GitHub Desktop.
Simple mouse warping for OSX
This file contains hidden or 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
// A quick and dirty tool for warping the mouse cursor between screens on OSX. | |
use core_graphics::{ | |
display::{CGDisplay, CGPoint, CGRect}, | |
event::{CGEvent, CGEventTapLocation, CGEventType, CGMouseButton}, | |
event_source::{CGEventSource, CGEventSourceStateID}, | |
}; | |
use std::{env, process::exit}; | |
const KNOWN_ARGS: [&str; 4] = ["--back", "--no-click", "-h", "--help"]; | |
fn main() { | |
let args: Vec<_> = env::args().skip(1).collect(); | |
let unknown: Vec<_> = args.iter().filter(|a| !KNOWN_ARGS.contains(&a.as_str())).collect(); | |
if !unknown.is_empty() { | |
eprintln!("unknown args: {unknown:?}"); | |
usage(); | |
exit(1); | |
} else if args.iter().any(|a| a == "-h" || a == "--help") { | |
usage(); | |
exit(0); | |
} | |
let forward = !args.iter().any(|a| a == "--back"); | |
let click = !args.iter().any(|a| a == "--no-click"); | |
warp(forward, click); | |
} | |
fn usage() { | |
println!("usage: mouse-mover [--back] [--no-click] [-h|--help]"); | |
println!(" move the mouse to an adjacent screen and click to set focus"); | |
println!(" pass --back to move to the previous screen instead of the next"); | |
println!(" pass --no-click to prevent left clicking to set focus"); | |
} | |
fn warp(forward: bool, click: bool) { | |
let displays: Vec<_> = CGDisplay::active_displays() | |
.unwrap() | |
.into_iter() | |
.map(|id| CGDisplay::new(id)) | |
.collect(); | |
if displays.len() == 1 { | |
return; // No other displays to move to | |
} | |
let current_pos = get_mouse_position(); | |
let i = displays | |
.iter() | |
.position(|&d| d.bounds().contains(¤t_pos)) | |
.unwrap(); | |
let j = if forward { | |
if i == displays.len() - 1 { | |
0 | |
} else { | |
i + 1 | |
} | |
} else { | |
if i == 0 { | |
displays.len() - 1 | |
} else { | |
i - 1 | |
} | |
}; | |
let p = mid_point(displays[j].bounds()); | |
CGDisplay::warp_mouse_cursor_position(p).unwrap(); | |
if click { | |
left_click_at(p); | |
} | |
} | |
fn mid_point(r: CGRect) -> CGPoint { | |
let dw = r.size.width / 2.0; | |
let dh = r.size.height / 2.0; | |
let mut p = r.origin; | |
p.x += dw; | |
p.y += dh; | |
p | |
} | |
// The CGEvent API is doc hidden so you'll need to read the source | |
// https://github.com/servo/core-foundation-rs/blob/main/core-graphics/src/event.rs#L645 | |
fn get_mouse_position() -> CGPoint { | |
let source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState).unwrap(); | |
CGEvent::new(source).unwrap().location() | |
} | |
fn left_click_at(p: CGPoint) { | |
for ty in [CGEventType::LeftMouseDown, CGEventType::LeftMouseUp] { | |
let source = CGEventSource::new(CGEventSourceStateID::HIDSystemState).unwrap(); | |
let evt = CGEvent::new_mouse_event(source, ty, p, CGMouseButton::Left).unwrap(); | |
evt.post(CGEventTapLocation::HID); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment