Skip to content

Commit 9dcd287

Browse files
authored
fix(drawing): basic multi display support (#49)
1 parent 2b312b8 commit 9dcd287

12 files changed

Lines changed: 216 additions & 148 deletions

File tree

rust-toolchain.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[toolchain]
22
profile = "default"
3-
channel = "1.93.1"
3+
channel = "1.94.1"

src/action/word_picker.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::{
66
};
77
use objc2::rc::{Retained, autoreleasepool};
88
use objc2_app_kit::NSFontAttributeName;
9-
use objc2_core_foundation::CGSize;
109
use objc2_foundation::{NSMutableAttributedString, NSRange};
1110
use regex::Regex;
1211
use std::sync::OnceLock;
@@ -45,7 +44,7 @@ pub struct WordPicker {
4544
}
4645

4746
impl WordPicker {
48-
pub fn new(text: String, screen_size: CGSize, theme: GlyphlowTheme, drawer: &UIDrawer) -> Self {
47+
pub fn new(text: String, screen_ratio: f64, theme: GlyphlowTheme, drawer: &UIDrawer) -> Self {
4948
let (word_strings, offsets) = multilingual_split(&text);
5049
let digits = digits_by_length(word_strings.len());
5150
let mut words = Vec::new();
@@ -54,7 +53,6 @@ impl WordPicker {
5453
words.push(Word { text, label });
5554
}
5655

57-
let CGSize { width, height } = screen_size;
5856
let word_picker = Self {
5957
raw: text,
6058
words,
@@ -63,7 +61,7 @@ impl WordPicker {
6361
is_searching: false,
6462
label_prefix: String::new(),
6563
text_prefix: String::new(),
66-
screen_ratio: width / (height + 0.01),
64+
screen_ratio,
6765
theme,
6866
matched: Vec::new(),
6967
};

src/app_engine/drawing.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::{
44
TEXT_ACTION_MENU_ITEMS,
55
ax_element::{ElementOfInterest, Target},
66
config::RoleOfInterest,
7-
util::Frame,
87
};
98
use objc2::rc::autoreleasepool;
109

@@ -28,16 +27,10 @@ impl AppEngine {
2827
/// Change selected element of interest
2928
/// Draw/Update the frame box of selected element
3029
pub(super) fn select(&mut self, eoi: ElementOfInterest) {
31-
self.drawer
32-
.draw_frame(&eoi.frame.invert_y(self.screen_size.height));
30+
self.drawer.draw_frame(&eoi.frame);
3331
self.selected = Some(eoi);
3432
}
3533

36-
pub(super) fn draw_frame_instant(&self, frame: &Frame) {
37-
self.drawer
38-
.draw_frame_instant(&frame.invert_y(self.screen_size.height));
39-
}
40-
4134
/// Draw/Update hint boxes
4235
pub(super) fn draw_hints(&mut self) {
4336
autoreleasepool(|_| {
@@ -46,7 +39,7 @@ impl AppEngine {
4639
&self.drawer.root,
4740
&self.config.theme,
4841
self.key_prefix.len(),
49-
self.screen_size,
42+
&self.overlay_frame,
5043
);
5144
}
5245
})
@@ -69,7 +62,7 @@ impl AppEngine {
6962
hb.set_visible(visible);
7063

7164
if visible {
72-
hb.refresh(prefix_len, self.screen_size, &self.config.theme);
65+
hb.refresh(prefix_len, &self.overlay_frame, &self.config.theme);
7366
nothing_visible = false;
7467
}
7568
}

src/app_engine/filtering.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ impl AppEngine {
2424
hint_boxes_from_frames(
2525
len,
2626
iter,
27-
&Frame::from_origion(self.screen_size),
27+
&self.overlay_frame,
2828
&self.config.theme,
2929
self.config.colored_frame_min_size as f64,
3030
)
@@ -85,9 +85,7 @@ impl AppEngine {
8585
self.drawer.clear_menus_instant();
8686
self.clear_cache();
8787
// NOTE: for images with parts out of sight
88-
let frame = frame
89-
.intersect(&Frame::from_origion(self.screen_size))
90-
.unwrap_or(frame);
88+
let frame = frame.intersect(&self.overlay_frame).unwrap_or(frame);
9189
match perform_ocr(&frame, &self.config.ocr_languages).await {
9290
Ok(ocr_res) if !ocr_res.is_empty() => {
9391
self.ocr_cache = Some(ocr_res);

src/app_engine/interaction.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::{
44
action::{WordPicker, get_dictionary_attributed_string},
55
ax_element::{ElementOfInterest, GetAttribute, SetAttribute},
66
config::RoleOfInterest,
7-
util::Frame,
87
};
98
use accessibility::{AXUIElement, AXUIElementActions, AXUIElementAttributes};
109
use accessibility_sys::{
@@ -32,9 +31,8 @@ impl AppEngine {
3231
let mouse_loc = NSEvent::mouseLocation();
3332

3433
if self.config.theme.enable_animation {
35-
let color = &self.config.theme.hint_bg_color;
3634
self.drawer
37-
.draw_trail(mouse_loc.x, mouse_loc.y, end_x, end_y, color);
35+
.draw_trail(mouse_loc.x, mouse_loc.y, end_x, end_y);
3836
}
3937
self.simulate_event(&EventType::MouseMove { x: end_x, y: end_y });
4038
}
@@ -127,8 +125,7 @@ impl AppEngine {
127125
.and_then(|eoi| eoi.element())
128126
.and_then(|ele| ele.parent().ok())
129127
{
130-
let screen_frame = Frame::from_origion(self.screen_size);
131-
let frame = parent_element.get_frame(screen_frame);
128+
let frame = parent_element.get_frame(self.overlay_frame);
132129
self.select(ElementOfInterest::new(
133130
parent_element,
134131
None,
@@ -175,12 +172,10 @@ impl AppEngine {
175172
TextAction::Split => {
176173
self.set_mode(Mode::WordPicking);
177174
self.clear_cache();
178-
let word_picker = WordPicker::new(
179-
text,
180-
self.screen_size,
181-
self.config.theme.clone(),
182-
&self.drawer,
183-
);
175+
let (w, h) = self.drawer.current_screen_frame.size();
176+
let screen_ratio = w / (h + 0.01);
177+
let word_picker =
178+
WordPicker::new(text, screen_ratio, self.config.theme.clone(), &self.drawer);
184179
self.hint_width = word_picker.digits;
185180

186181
self.word_picker = Some(word_picker);

src/app_engine/lifecycle.rs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
config::{GlyphlowConfig, RoleOfInterest, VisibilityCheckingLevel},
66
os_util::get_focused_window,
77
user_interface::{HintBox, hint_label_from_index, resolve_collisions},
8-
util::{Frame, digits_by_length},
8+
util::digits_by_length,
99
};
1010
use accessibility::AXUIElementAttributes;
1111
use log::Level;
@@ -20,7 +20,7 @@ const DEBUG_TIMEOUT: u64 = 5;
2020

2121
impl AppEngine {
2222
pub(super) fn set_mode(&self, mode: Mode) {
23-
log::log!(Level::Trace, "Set mode: {mode:?}");
23+
log::trace!("Set mode: {mode:?}");
2424
if let Ok(mut state) = self.state.lock() {
2525
*state = mode;
2626
}
@@ -73,16 +73,16 @@ impl AppEngine {
7373
}
7474

7575
pub(super) fn get_app_window_info(&mut self) {
76-
let screen_frame = Frame::from_origion(self.screen_size);
7776
let Some(app_win_info) = get_focused_window(
78-
screen_frame,
77+
self.overlay_frame,
7978
&self.last_app_window_info,
8079
self.config.electron_initial_wait_ms,
8180
) else {
8281
return;
8382
};
8483

85-
self.draw_frame_instant(&app_win_info.frame);
84+
self.drawer.select_screen_frame(&app_win_info.frame);
85+
self.drawer.draw_frame_instant(&app_win_info.frame);
8686
self.last_app_window_info = app_win_info;
8787
}
8888

@@ -129,7 +129,7 @@ impl AppEngine {
129129
let window_frame = if focused_only {
130130
self.last_app_window_info.frame
131131
} else {
132-
Frame::from_origion(self.screen_size)
132+
self.overlay_frame
133133
};
134134
let _ = std::thread::spawn(move || {
135135
traverse(safe_root, frame, window_frame, target, vis_level, result_tx);
@@ -142,7 +142,7 @@ impl AppEngine {
142142
const HINTBOX_FLUSH_BATCH_SIZE: usize = 5;
143143

144144
pub(super) fn activate(&mut self, target: Target) {
145-
log::log!(Level::Debug, "Start traversing, target: {target:?}");
145+
log::debug!("Start traversing, target: {target:?}");
146146
self.clear_cache();
147147
self.drawer.clear_menus();
148148
let result_rx = self.ui_element_traverse_on_activation(target);
@@ -162,7 +162,7 @@ impl AppEngine {
162162
}
163163
}
164164
});
165-
log::log!(Level::Debug, "Finish traversing");
165+
log::debug!("Finish traversing");
166166
}
167167

168168
fn handle_element_found(
@@ -175,7 +175,7 @@ impl AppEngine {
175175
if let Some(idx) = self.element_cache.add_by_target(ele, &self.target) {
176176
let eoi = &self.element_cache.cache[idx];
177177

178-
let screen_frame = Frame::from_origion(self.screen_size);
178+
let screen_frame = self.overlay_frame;
179179
let frame = eoi.frame.intersect(&screen_frame).unwrap_or(screen_frame);
180180

181181
let (x, y) = frame.center();
@@ -186,7 +186,7 @@ impl AppEngine {
186186
// Draw frames for large enough elements
187187
let frame = if w.max(h) >= self.config.colored_frame_min_size as f64 {
188188
*color_idx += 1;
189-
Some(eoi.frame.invert_y(self.screen_size.height))
189+
Some(eoi.frame)
190190
} else {
191191
None
192192
};
@@ -200,16 +200,14 @@ impl AppEngine {
200200
})
201201
.flatten();
202202

203-
let mut hb = HintBox::new(
204-
idx,
205-
hint_label_from_index(idx, None),
206-
x,
207-
self.screen_size.height - y,
208-
frame,
209-
color,
210-
);
203+
let mut hb = HintBox::new(idx, hint_label_from_index(idx, None), x, y, frame, color);
211204

212-
hb.draw(&self.drawer.root, &self.config.theme, 0, self.screen_size);
205+
hb.draw(
206+
&self.drawer.root,
207+
&self.config.theme,
208+
0,
209+
&self.overlay_frame,
210+
);
213211
if need_flush {
214212
CATransaction::flush();
215213
}

src/app_engine/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ use crate::{
44
ax_element::{ElementCache, ElementOfInterest, Target},
55
config::{GlyphlowConfig, RoleOfInterest, WorkFlowAction},
66
os_util::AppWindowInfo,
7-
user_interface::{HintBox, UIDrawer, get_main_screen_size},
7+
user_interface::{HintBox, UIDrawer, get_screen_frames},
88
util::Frame,
99
};
1010
use log::Level;
1111
use objc2::MainThreadMarker;
12-
use objc2_core_foundation::CGSize;
1312
use std::{
1413
collections::VecDeque,
1514
path::PathBuf,
@@ -66,7 +65,7 @@ pub struct AppEngine {
6665
pub(super) hint_boxes: Vec<HintBox>,
6766
pub(super) element_cache: ElementCache,
6867
pub(super) key_prefix: String,
69-
pub(super) screen_size: CGSize,
68+
pub(super) overlay_frame: Frame,
7069
pub(super) drawer: UIDrawer,
7170
/// Which elements of interest to look for
7271
pub(super) target: Target,
@@ -99,8 +98,9 @@ impl AppEngine {
9998
timeout_sender: Sender<usize>,
10099
) -> Self {
101100
let mtm = MainThreadMarker::new().expect("Not on main thread");
102-
let screen_size = get_main_screen_size(mtm);
103-
let drawer = UIDrawer::new(screen_size, mtm, &config.theme);
101+
let screen_frames = get_screen_frames(mtm);
102+
let overlay_frame = Frame::union_of_frames(&screen_frames);
103+
let drawer = UIDrawer::new(screen_frames, overlay_frame, mtm, &config.theme);
104104

105105
Self {
106106
state,
@@ -114,7 +114,7 @@ impl AppEngine {
114114
key_prefix: String::new(),
115115
target: Target::default(),
116116
hint_width: 0,
117-
screen_size,
117+
overlay_frame,
118118
drawer,
119119
config,
120120
timeout_sender,
@@ -123,7 +123,7 @@ impl AppEngine {
123123
temp_file,
124124
word_picker: None,
125125
ocr_cache: None,
126-
last_app_window_info: AppWindowInfo::default(screen_size),
126+
last_app_window_info: AppWindowInfo::default(overlay_frame),
127127
multi_selection: MultiSeletionState::default(),
128128
pending_workflow_actions: VecDeque::new(),
129129
}
@@ -211,7 +211,7 @@ impl AppEngine {
211211
if let Some(text) = text_from_clipboard() {
212212
self.selected = Some(ElementOfInterest::pseudo(
213213
None,
214-
Frame::from_origion(self.screen_size),
214+
self.last_app_window_info.frame,
215215
));
216216
self.update_selected_text_and_show_menu(text);
217217
} else {

src/os_util.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use core_foundation::{
99
};
1010
use objc2::rc::Retained;
1111
use objc2_app_kit::{NSRunningApplication, NSWorkspace};
12-
use objc2_core_foundation::CGSize;
1312

1413
use crate::{ax_element::GetAttribute, util::Frame};
1514

@@ -49,13 +48,13 @@ pub struct AppWindowInfo {
4948
}
5049

5150
impl AppWindowInfo {
52-
pub fn default(screen_size: CGSize) -> Self {
51+
pub fn default(screen_frame: Frame) -> Self {
5352
Self {
5453
window: AXUIElement::system_wide(),
5554
bundle_id: String::new(),
5655
pid: -1,
5756
is_electron: false,
58-
frame: Frame::from_origion(screen_size),
57+
frame: screen_frame,
5958
}
6059
}
6160

@@ -119,7 +118,7 @@ pub fn get_focused_window(
119118
.bundleIdentifier()
120119
.map(|s| s.to_string())
121120
.unwrap_or_default();
122-
log::log!(log::Level::Trace, "Focused app bundle id: {:?}", bundle_id);
121+
log::trace!("Focused app bundle id: {:?}", bundle_id);
123122

124123
let pid = app.processIdentifier();
125124
let is_electron = check_is_electron_app(&app).unwrap_or_default();

0 commit comments

Comments
 (0)