From 048835e94a6dd03e0ad1bc43c95a6489a7e285a3 Mon Sep 17 00:00:00 2001 From: Holger Rapp Date: Mon, 4 Mar 2019 15:41:26 +0100 Subject: [PATCH] A new version to unbreak on Mojave Mojave has bugs that broke our use case[1]. So here is a reimplementation, this time in Rust. This is the third time I implemented the move_window functionality (Python, JavaScript, Rust). I hope this one lasts us for longer before it breaks again. [1] https://github.com/JXA-Cookbook/JXA-Cookbook/issues/44#issuecomment-436503343 --- .gitignore | 2 + Cargo.lock | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 13 ++++ src/main.rs | 168 +++++++++++++++++++++++++++++++++++++++++++++ watcher.lua | 25 +++++++ 5 files changed, 401 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs create mode 100644 watcher.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..142b6bf --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,193 @@ +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cocoa" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "core-graphics" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "itoa" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "move_window" +version = "0.1.0" +dependencies = [ + "cocoa 0.18.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", + "osascript 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "objc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "osascript" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_derive" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.15.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +"checksum cocoa 0.18.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cf79daa4e11e5def06e55306aa3601b87de6b5149671529318da048f67cdd77b" +"checksum core-foundation 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e2640d6d0bf22e82bed1b73c6aef8d5dd31e5abe6666c57e6d45e2649f4f887" +"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" +"checksum core-graphics 0.17.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)" = "413f3dfc802c5dc91dc570b05125b6cda9855edfaa9825c9849807876376e70e" +"checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +"checksum objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9833ab0efe5361b1e2122a0544a5d3359576911a42cb098c2e59be8650807367" +"checksum osascript 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "38731fa859ef679f1aec66ca9562165926b442f298467f76f5990f431efe87dc" +"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" +"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" +"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" +"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" +"checksum syn 0.15.27 (registry+https://github.com/rust-lang/crates.io-index)" = "525bd55255f03c816e5d7f615587bd13030c7103354fadb104993dcee6a788ec" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5727462 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "move_window" +version = "0.1.0" +authors = ["Holger Rapp "] +edition = "2018" + +[dependencies] +objc = "0.2.5" +core-foundation = "0.6.3" +cocoa = "0.18.4" +serde_derive = "1.0.89" +osascript = "0.3.0" +serde = "1.0.89" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..000e2a6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,168 @@ +// TODO(sirver): The crate does not seem to work with Rust 2018 yet, so we need this old school +// import. +#[macro_use] +extern crate objc; + +use cocoa::appkit::NSScreen; +use cocoa::base::nil; +use cocoa::foundation::{NSDictionary, NSString}; +use objc::runtime::Class; +use objc::runtime::Object; +use osascript::JavaScript; +use serde_derive::Serialize; +use std::ffi::CStr; + +#[derive(Serialize)] +struct MoveWindowParams { + app_name: String, + r: Rect, +} + +#[derive(Debug, Serialize)] +struct Rect { + x: i32, + y: i32, + width: i32, + height: i32, +} + +#[derive(Debug)] +struct MoveParameters { + screen_index: usize, + x_ratio: i32, + y_ratio: i32, + x_start: i32, + x_end: i32, + y_start: i32, + y_end: i32, +} + +fn next_integer(a: &mut ::std::iter::Peekable>) -> Result { + let c = a.next().ok_or_else(|| "No more items".to_string())?; + let v = c + .to_digit(10) + .ok_or_else(|| "Next not an integer.".to_string())?; + Ok(v as i32) +} + +impl MoveParameters { + pub fn from_command(s: &str) -> Result { + let mut i = s.chars().peekable(); + + let mut params = MoveParameters { + screen_index: next_integer(&mut i)? as usize, + x_ratio: 1, + y_ratio: 1, + x_start: 0, + x_end: 0, + y_start: 0, + y_end: 0, + }; + + params.x_ratio = match i.peek() { + Some(_) => next_integer(&mut i)?, + None => return Ok(params), + }; + params.x_start = match i.peek() { + Some(_) => next_integer(&mut i)?, + None => return Ok(params), + }; + params.x_end = params.x_start; + params.x_end = match i.peek() { + Some(&'-') => { + i.next(); + next_integer(&mut i)? + } + Some(_) => return Err("Expected number.".to_string()), + None => return Ok(params), + }; + + params.y_ratio = match i.peek() { + Some(_) => next_integer(&mut i)?, + None => return Ok(params), + }; + params.y_start = match i.peek() { + Some(_) => next_integer(&mut i)?, + None => return Ok(params), + }; + params.y_end = params.y_start; + params.y_end = match i.peek() { + Some(&'-') => { + i.next(); + next_integer(&mut i)? + } + Some(_) => return Err("Expected number.".to_string()), + None => return Ok(params), + }; + + Ok(params) + } +} + +fn get_screen_dimensions(screen_index: usize) -> Rect { + let s = unsafe { + let screens = NSScreen::screens(nil); + let screen: *mut Object = msg_send![screens, objectAtIndex: screen_index]; + screen.visibleFrame() + }; + Rect { + x: s.origin.x as i32, + y: s.origin.x as i32, + width: s.size.width as i32, + height: s.size.height as i32, + } +} + +fn get_frontmost_application_name() -> String { + let app_name = unsafe { + let workspace_class = Class::get("NSWorkspace").unwrap(); + let wspace: *mut Object = msg_send![workspace_class, sharedWorkspace]; + let active_app: *mut Object = msg_send![wspace, activeApplication]; + let v = active_app.objectForKey_(NSString::alloc(nil).init_str("NSApplicationName")); + let k = v.UTF8String(); + CStr::from_ptr(k) + }; + app_name.to_str().unwrap().to_string() +} + +fn main() { + let args: Vec = std::env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: move_window "); + std::process::exit(1); + } + let params = match MoveParameters::from_command(&args[1]) { + Ok(v) => v, + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + }; + + let screen = get_screen_dimensions(params.screen_index); + let width = f64::from(screen.width) / f64::from(params.x_ratio); + let height = f64::from(screen.height) / f64::from(params.y_ratio); + let frame = Rect { + x: (f64::from(screen.x) + width * f64::from(params.x_start)).round() as i32, + y: (f64::from(screen.y) + height * f64::from(params.y_start)).round() as i32, + width: (width * f64::from(params.x_end - params.x_start + 1)).round() as i32, + height: (height * f64::from(params.y_end - params.y_start + 1)).round() as i32, + }; + + let app_name = get_frontmost_application_name(); + let script = JavaScript::new( + " + var app = Application($params.app_name); + app.windows[0].bounds = { + x: $params.r.x, + y: $params.r.y, + width: $params.r.width, + height: $params.r.height, + }; + ", + ); + + script + .execute_with_params::<_, ()>(MoveWindowParams { app_name, r: frame }) + .unwrap(); +} diff --git a/watcher.lua b/watcher.lua new file mode 100644 index 0000000..dcffb7e --- /dev/null +++ b/watcher.lua @@ -0,0 +1,25 @@ +function is_rust(p) + if p:find("target") ~= nil then return false end + return p:ext() == "rs" or p:ext() == "toml" +end + +return { + { + should_run = is_rust, + redirect_stderr = "/tmp/cargo.err", + commands = { + -- { + -- name = "Running cargo check", + -- command = "cargo check --release --color=always", + -- }, + { + name = "Running cargo clippy", + command = "cargo clippy --release --color=always", + }, + { + name = "Running cargo build", + command = "cargo build --release --color=always", + }, + } + }, +}