Skip to content

Commit 7a7d9b7

Browse files
committed
Compile core_graphics only in Mac
1 parent 6ee867d commit 7a7d9b7

File tree

2 files changed

+96
-89
lines changed

2 files changed

+96
-89
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ crate-type = ["cdylib"]
88

99
[dependencies]
1010
arboard = "3.3.0"
11-
core-graphics = "0.23.2"
1211
enigo = "0.2.0-rc2"
1312
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
1413
napi = { version = "2.12.2", default-features = false, features = ["napi4"] }
1514
napi-derive = "2.12.2"
1615

16+
[target.'cfg(target_os = "macos")'.dependencies]
17+
core-graphics = "0.23.2"
18+
1719
[build-dependencies]
1820
napi-build = "2.0.1"
1921

src/lib.rs

Lines changed: 93 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@ extern crate napi_derive;
66
use arboard::{ Clipboard, ImageData };
77
use enigo::{ Direction::{ Click, Press, Release }, Enigo, Key, Keyboard, Settings };
88
use std::{ thread, time::Duration };
9+
#[cfg(target_os = "macos")]
910
use core_graphics::{
10-
event::{
11-
CGEvent,
12-
CGEventTapLocation,
13-
KeyCode,
14-
CGEventFlags,
15-
},
11+
event::{ CGEvent, CGEventTapLocation, KeyCode, CGEventFlags },
1612
event_source::{ CGEventSource, CGEventSourceStateID },
1713
};
1814

15+
#[cfg(target_os = "macos")]
1916
static DEFAULT_MAC_CG_EVENT_WAIT_TIME_MS: u64 = 20;
17+
2018
static DEFAULT_PASTE_WAIT_TIME_MS: u32 = 30;
2119

2220
/// Insert the given text at the current cursor position.
@@ -25,17 +23,17 @@ static DEFAULT_PASTE_WAIT_TIME_MS: u32 = 30;
2523
///
2624
/// ##### Arguments
2725
/// * `text` - Text to be inserted
28-
/// * `insertWithPaste` - An optional boolean that sets whether to insert text with the paste method.
26+
/// * `insertWithPaste` - An optional boolean that sets whether to insert text with the paste method.
2927
/// Default to false. (Setting true to use the paste method is useful to bypass
30-
/// some limitations in the default insert method. For example, the default
28+
/// some limitations in the default insert method. For example, the default
3129
/// insert method may not work for some apps, and in Mac, it doesn't work
3230
/// when certain key, such as Cmd, is pressed during insert.)
3331
/// * `arrowKeyToClickBeforeInsert` - An optional string that sets which arrow key to click before
3432
/// inserting text. Can be either "left" or "right". Default to None.
3533
/// * `pasteWaitTimeMs` - An optional number that sets how long to wait after performing the paste
3634
/// operation before restoring the previous clipboard state. Default to 30ms.
3735
/// `pasteWaitTimeMs` is only used when using the paste method, i.e. when
38-
/// `insertWithPaste` is set to true. (Beware of setting this value too low,
36+
/// `insertWithPaste` is set to true. (Beware of setting this value too low,
3937
/// as it may end up pasting the previous clipboard text/image)
4038
#[napi]
4139
pub fn insert_text(
@@ -54,7 +52,7 @@ pub fn insert_text(
5452
let insert_with_paste = insert_with_paste.unwrap_or(false);
5553
if !insert_with_paste {
5654
// Insert text using default method
57-
// Note: This may not work for some apps, and in Mac, it doesn't work when
55+
// Note: This may not work for some apps, and in Mac, it doesn't work when
5856
// certain key, such as Cmd, is pressed during insert (https://github.com/enigo-rs/enigo/issues/297)
5957
enigo.text(&text).unwrap();
6058
} else {
@@ -64,12 +62,12 @@ pub fn insert_text(
6462
// 1. Save clipboard existing text or image
6563
let clipboard_text = clipboard.get_text().unwrap_or(String::new());
6664
let clipboard_image = clipboard
67-
.get_image()
68-
.unwrap_or(ImageData { width: 0, height: 0, bytes: [].as_ref().into() });
65+
.get_image()
66+
.unwrap_or(ImageData { width: 0, height: 0, bytes: [].as_ref().into() });
6967

7068
// 2. Clear clipboard
7169
clipboard.clear().unwrap();
72-
70+
7371
// 3. Set text to be inserted to clipboard
7472
clipboard.set_text(&text).unwrap();
7573

@@ -103,9 +101,7 @@ pub fn insert_text(
103101
/// * `arrowKeyToClickBeforePaste` - An optional string that sets which arrow key to click before
104102
/// pasting. Can be either "left" or "right". Default to None.
105103
#[napi]
106-
pub fn paste(
107-
arrow_key_to_click_before_paste: Option<String>,
108-
){
104+
pub fn paste(arrow_key_to_click_before_paste: Option<String>) {
109105
let mut enigo = Enigo::new(&Settings::default()).unwrap();
110106

111107
let arrow_key = arrow_key_to_click_before_paste.unwrap_or(String::new());
@@ -116,87 +112,96 @@ pub fn paste(
116112
_paste(&mut enigo);
117113
}
118114

119-
/// Simulate arrow key click (left or right)
115+
/// Simulate arrow key click (left or right) - Mac
116+
#[cfg(target_os = "macos")]
120117
fn _click_arrow_key(enigo: &mut Enigo, arrow_key: String) {
121-
if cfg!(target_os = "macos") {
122-
let arrow_key_code = if arrow_key == "left" {
123-
KeyCode::LEFT_ARROW
124-
} else {
125-
KeyCode::RIGHT_ARROW
126-
};
127-
128-
let event_source_state_id = CGEventSourceStateID::CombinedSessionState;
129-
let event_source = CGEventSource::new(event_source_state_id).unwrap();
130-
let event_tap_location = CGEventTapLocation::HID;
131-
132-
let press_arrow_key_event = CGEvent::new_keyboard_event(
133-
event_source.clone(),
134-
arrow_key_code,
135-
true
136-
).unwrap();
137-
press_arrow_key_event.post(event_tap_location);
138-
139-
let release_arrow_key_event = CGEvent::new_keyboard_event(
140-
event_source.clone(),
141-
arrow_key_code,
142-
false
143-
).unwrap();
144-
release_arrow_key_event.post(event_tap_location);
145-
thread::sleep(Duration::from_millis(DEFAULT_MAC_CG_EVENT_WAIT_TIME_MS));
118+
let arrow_key_code = if arrow_key == "left" {
119+
KeyCode::LEFT_ARROW
146120
} else {
147-
let key = if arrow_key == "left" { Key::LeftArrow } else { Key::RightArrow };
148-
enigo.key(key, Click).unwrap();
149-
}
121+
KeyCode::RIGHT_ARROW
122+
};
123+
124+
let event_source_state_id = CGEventSourceStateID::CombinedSessionState;
125+
let event_source = CGEventSource::new(event_source_state_id).unwrap();
126+
let event_tap_location = CGEventTapLocation::HID;
127+
128+
let press_arrow_key_event = CGEvent::new_keyboard_event(
129+
event_source.clone(),
130+
arrow_key_code,
131+
true
132+
).unwrap();
133+
press_arrow_key_event.post(event_tap_location);
134+
135+
let release_arrow_key_event = CGEvent::new_keyboard_event(
136+
event_source.clone(),
137+
arrow_key_code,
138+
false
139+
).unwrap();
140+
release_arrow_key_event.post(event_tap_location);
141+
thread::sleep(Duration::from_millis(DEFAULT_MAC_CG_EVENT_WAIT_TIME_MS));
142+
}
143+
144+
/// Simulate arrow key click (left or right) - Windows
145+
#[cfg(not(target_os = "macos"))]
146+
fn _click_arrow_key(enigo: &mut Enigo, arrow_key: String) {
147+
let key = if arrow_key == "left" { Key::LeftArrow } else { Key::RightArrow };
148+
enigo.key(key, Click).unwrap();
150149
}
151150

152151
// Define CG key code for "v" key
153152
// Reference: https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.13.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h#L206
153+
#[cfg(target_os = "macos")]
154154
static V_KEY_CODE: u16 = 0x09;
155155

156-
/// Simulate Ctrl+ V (Cmd + V in Mac) keyboard input to perform paste
157-
///
158-
/// Windows calls into Enigo to simulate keyboard input. But for MacOS, it calls into
159-
/// Mac's Core Graphics CGEvent libary directly to work around 2 issues with Enigo's current
160-
/// implementation, which casues additional delay (https://github.com/enigo-rs/enigo/issues/105)
156+
/// Simulate Ctrl+ V (Cmd + V in Mac) keyboard input to perform paste - Mac
157+
///
158+
/// Windows calls into Enigo to simulate keyboard input. But for Mac, it calls into
159+
/// Mac's Core Graphics CGEvent library directly to work around 2 issues with Enigo's current
160+
/// implementation, which causes additional delay (https://github.com/enigo-rs/enigo/issues/105)
161161
/// and subjects to mouse movement/keyboard interruption (https://github.com/enigo-rs/enigo/issues/201).
162162
/// Calling into CGEvent and setting event flag solves both issues.
163+
#[cfg(target_os = "macos")]
163164
fn _paste(enigo: &mut Enigo) {
164-
if cfg!(target_os = "macos") {
165-
// Implementation reference: https://stackoverflow.com/questions/2008126/cgeventpost-possible-bug-when-simulating-keyboard-events
166-
167-
// Event source state id reference: https://developer.apple.com/documentation/coregraphics/cgeventsourcestateid
168-
let event_source_state_id = CGEventSourceStateID::CombinedSessionState;
169-
let event_source = CGEventSource::new(event_source_state_id).unwrap();
170-
// Event tap location reference: https://developer.apple.com/documentation/coregraphics/cgeventtaplocation
171-
let event_tap_location = CGEventTapLocation::HID;
172-
173-
let press_cmd_v_event = CGEvent::new_keyboard_event(
174-
event_source.clone(),
175-
V_KEY_CODE,
176-
true
177-
).unwrap();
178-
press_cmd_v_event.set_flags(CGEventFlags::CGEventFlagCommand); // Set flags to Cmd
179-
press_cmd_v_event.post(event_tap_location);
180-
181-
let release_v_event = CGEvent::new_keyboard_event(
182-
event_source.clone(),
183-
V_KEY_CODE,
184-
false
185-
).unwrap();
186-
release_v_event.set_flags(CGEventFlags::CGEventFlagNull); // Reset flags to null
187-
release_v_event.post(event_tap_location);
188-
189-
// Release Cmd Key for completeness. May or may not be necessary
190-
// given Apple's documentation is not clear on this.
191-
let release_cmd_event = CGEvent::new_keyboard_event(
192-
event_source.clone(),
193-
KeyCode::COMMAND,
194-
false
195-
).unwrap();
196-
release_cmd_event.post(event_tap_location);
197-
} else {
198-
enigo.key(Key::Control, Press).unwrap();
199-
enigo.key(Key::Unicode('v'), Click).unwrap();
200-
enigo.key(Key::Control, Release).unwrap();
201-
}
165+
// Implementation reference: https://stackoverflow.com/questions/2008126/cgeventpost-possible-bug-when-simulating-keyboard-events
166+
167+
// Event source state id reference: https://developer.apple.com/documentation/coregraphics/cgeventsourcestateid
168+
let event_source_state_id = CGEventSourceStateID::CombinedSessionState;
169+
let event_source = CGEventSource::new(event_source_state_id).unwrap();
170+
// Event tap location reference: https://developer.apple.com/documentation/coregraphics/cgeventtaplocation
171+
let event_tap_location = CGEventTapLocation::HID;
172+
173+
let press_cmd_v_event = CGEvent::new_keyboard_event(
174+
event_source.clone(),
175+
V_KEY_CODE,
176+
true
177+
).unwrap();
178+
press_cmd_v_event.set_flags(CGEventFlags::CGEventFlagCommand); // Set flags to Cmd
179+
press_cmd_v_event.post(event_tap_location);
180+
181+
let release_v_event = CGEvent::new_keyboard_event(
182+
event_source.clone(),
183+
V_KEY_CODE,
184+
false
185+
).unwrap();
186+
release_v_event.set_flags(CGEventFlags::CGEventFlagNull); // Reset flags to null
187+
release_v_event.post(event_tap_location);
188+
189+
// Release Cmd Key for completeness. May or may not be necessary
190+
// given Apple's documentation is not clear on this.
191+
let release_cmd_event = CGEvent::new_keyboard_event(
192+
event_source.clone(),
193+
KeyCode::COMMAND,
194+
false
195+
).unwrap();
196+
release_cmd_event.post(event_tap_location);
197+
}
198+
199+
/// Simulate Ctrl+ V (Cmd + V in Mac) keyboard input to perform paste - Windows
200+
///
201+
/// Windows calls into Enigo to simulate keyboard input
202+
#[cfg(not(target_os = "macos"))]
203+
fn _paste(enigo: &mut Enigo) {
204+
enigo.key(Key::Control, Press).unwrap();
205+
enigo.key(Key::Unicode('v'), Click).unwrap();
206+
enigo.key(Key::Control, Release).unwrap();
202207
}

0 commit comments

Comments
 (0)