bergeramit / Clipbond

0 stars 0 forks source link

Research: how to set keyboard event listener for Mac #7

Open bergeramit opened 1 year ago

guyberger commented 1 year ago
use cli_clipboard::{ClipboardContext, ClipboardProvider};
use livesplit_hotkey::{Hook, Hotkey, KeyCode, Modifiers};
use std::{thread, time};

fn print_clipboard() {
    let mut ctx = ClipboardContext::new().unwrap();
    let clip_data = ctx.get_contents().unwrap();
    println!("{:?}", clip_data);
}
fn main() {
    let hook = Hook::new().unwrap();
    let hotkey = Hotkey {
        key_code: KeyCode::KeyC,
        modifiers: Modifiers::META,
    };

    hook.register(hotkey, print_clipboard);

    println!("Press cmd+c to see the copied text");

    loop {
        thread::sleep(time::Duration::from_secs(1));
    }
}

This baby will listen to CMD+C globally in our MacOS system on any app ! This bad boy then prints the clipboard's text so you can see for yourself it ain't faking it.

bergeramit commented 1 year ago

QA here: so after testing, I can safely say this does not work properly, the hook is called with the second-to-last clipboard value - which means you do not print the last CMD+C value (tested on windows as well and was able to reproduce it).

for example, CMD+C while marking "Apples" and then CMD+C while marking "Bees" will result in the hook printing "Apples". "Bees" will only be printed after we CMD+C something else.

bergeramit commented 1 year ago

Conjecture: Maybe the hook is called before the Window's Clipboard manager is called and thus you cannot take the current marked text because it did not arrive at the clipboard yet (same for Mac probably)

guyberger commented 1 year ago
use cli_clipboard::{macos_clipboard::MacOSClipboardContext, ClipboardContext, ClipboardProvider};
use livesplit_hotkey::{Hook, Hotkey, KeyCode, Modifiers};
use std::{thread, time};

fn print_clipboard() {
    thread::sleep(time::Duration::from_millis(100));
    let mut ctx: MacOSClipboardContext = ClipboardContext::new().unwrap();
    let clip_data = ctx.get_contents().unwrap();
    println!("{:?}", clip_data);
}
fn main() {
    let hook = Hook::new().unwrap();
    let hotkey = Hotkey {
        key_code: KeyCode::KeyC,
        modifiers: Modifiers::META,
    };

    hook.register(hotkey, move || {
        thread::spawn(print_clipboard);
    })
    .map_err(|e| eprintln!("Failed to register hotkey: {}", e))
    .unwrap();

    println!("Press cmd+c to see the copied text");

    loop {
        thread::sleep(time::Duration::from_secs(1));
    }
}

Indeed the hook was called before the system's clipboard was able to update. It was not possible to do both hooks simultaneously so it must be executed one after the other, the solution above uses a separate thread (to release the hook's handler) and after a small delay gets the updated value from the system's clipboard. [Race condition] We need to note that we must use some delay in this solution to allow the clipboard to update before we print.