Skip to content

Commit 987be14

Browse files
Still trying to fix Windows (#2471)
* Still trying to fix Windows * Update windows_sampler_tests.rs * /shrug * Another one * Update main.rs
1 parent abb0eeb commit 987be14

File tree

6 files changed

+112
-86
lines changed

6 files changed

+112
-86
lines changed

electron-app/magnifier/rust-sampler/src/main.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ fn run() -> Result<(), String> {
3030

3131
// Create channels for command communication
3232
let (cmd_tx, cmd_rx): (std::sync::mpsc::Sender<Command>, Receiver<Command>) = channel();
33-
33+
3434
// Spawn stdin reader thread
3535
thread::spawn(move || {
3636
let stdin = io::stdin();
3737
let mut reader = stdin.lock();
3838
let mut line = String::new();
39-
39+
4040
loop {
4141
line.clear();
4242
match reader.read_line(&mut line) {
@@ -67,12 +67,36 @@ fn run() -> Result<(), String> {
6767
}
6868
});
6969

70+
// Get DPI scale - Windows needs it, others use 1.0
71+
let dpi_scale = get_dpi_scale();
72+
73+
fn get_dpi_scale() -> f64 {
74+
#[cfg(target_os = "windows")]
75+
{
76+
// On Windows, get DPI scale directly from system
77+
use windows::Win32::Graphics::Gdi::{GetDC, GetDeviceCaps, LOGPIXELSX, ReleaseDC};
78+
unsafe {
79+
let hdc = GetDC(None);
80+
if !hdc.is_invalid() {
81+
let dpi = GetDeviceCaps(hdc, LOGPIXELSX);
82+
let _ = ReleaseDC(None, hdc);
83+
return dpi as f64 / 96.0;
84+
}
85+
}
86+
1.0 // Fallback
87+
}
88+
#[cfg(not(target_os = "windows"))]
89+
{
90+
1.0
91+
}
92+
}
93+
7094
// Main loop - wait for commands from channel
7195
loop {
7296
match cmd_rx.recv() {
7397
Ok(Command::Start { grid_size, sample_rate }) => {
7498
eprintln!("Starting sampling: grid_size={}, sample_rate={}", grid_size, sample_rate);
75-
if let Err(e) = run_sampling_loop(&mut *sampler, grid_size, sample_rate, &cmd_rx) {
99+
if let Err(e) = run_sampling_loop(&mut *sampler, grid_size, sample_rate, dpi_scale, &cmd_rx) {
76100
eprintln!("Sampling loop error: {}", e);
77101
send_error(&e);
78102
}
@@ -99,6 +123,7 @@ fn run_sampling_loop(
99123
sampler: &mut dyn PixelSampler,
100124
initial_grid_size: usize,
101125
sample_rate: u64,
126+
dpi_scale: f64,
102127
cmd_rx: &std::sync::mpsc::Receiver<Command>,
103128
) -> Result<(), String> {
104129
use std::sync::mpsc::TryRecvError;
@@ -134,8 +159,8 @@ fn run_sampling_loop(
134159

135160
let loop_start = std::time::Instant::now();
136161

137-
// Get cursor position
138-
let cursor = match sampler.get_cursor_position() {
162+
// Get cursor position (returns physical coordinates for Electron window positioning)
163+
let physical_cursor = match sampler.get_cursor_position() {
139164
Ok(pos) => pos,
140165
Err(_e) => {
141166
// On Wayland/some platforms, we can't get cursor position directly
@@ -146,17 +171,24 @@ fn run_sampling_loop(
146171

147172
// Sample every frame regardless of cursor movement for smooth updates
148173
// This ensures the UI is responsive even if cursor position can't be tracked
149-
last_cursor = cursor.clone();
174+
last_cursor = physical_cursor.clone();
175+
176+
// Convert physical coordinates back to virtual for sampling
177+
// We know dpi_scale is available here since it's declared at function scope
178+
let virtual_cursor = Point {
179+
x: (physical_cursor.x as f64 / dpi_scale) as i32,
180+
y: (physical_cursor.y as f64 / dpi_scale) as i32,
181+
};
150182

151183
// Sample center pixel
152-
let center_color = sampler.sample_pixel(cursor.x, cursor.y)
184+
let center_color = sampler.sample_pixel(virtual_cursor.x, virtual_cursor.y)
153185
.unwrap_or_else(|e| {
154186
eprintln!("Failed to sample center pixel: {}", e);
155187
Color::new(128, 128, 128)
156188
});
157189

158190
// Sample grid
159-
let grid = sampler.sample_grid(cursor.x, cursor.y, current_grid_size, 1.0)
191+
let grid = sampler.sample_grid(virtual_cursor.x, virtual_cursor.y, current_grid_size, 1.0)
160192
.unwrap_or_else(|e| {
161193
eprintln!("Failed to sample grid: {}", e);
162194
vec![vec![Color::new(128, 128, 128); current_grid_size]; current_grid_size]
@@ -169,7 +201,7 @@ fn run_sampling_loop(
169201
.collect();
170202

171203
let pixel_data = PixelData {
172-
cursor: cursor.clone(),
204+
cursor: physical_cursor.clone(),
173205
center: center_color.into(),
174206
grid: grid_data,
175207
timestamp: SystemTime::now()

electron-app/magnifier/rust-sampler/src/sampler/linux.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ impl PixelSampler for LinuxSampler {
252252
Ok(Point { x: root_x, y: root_y })
253253
}
254254
}
255-
255+
256256
fn sample_grid(&mut self, center_x: i32, center_y: i32, grid_size: usize, _scale_factor: f64) -> Result<Vec<Vec<Color>>, String> {
257257
// Ensure we have a fresh screenshot
258258
self.ensure_fresh_screenshot()?;

electron-app/magnifier/rust-sampler/src/sampler/wayland_portal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ impl PixelSampler for WaylandPortalSampler {
392392
Ok(Point { x: root_x, y: root_y })
393393
}
394394
}
395-
395+
396396
fn sample_grid(&mut self, center_x: i32, center_y: i32, grid_size: usize, _scale_factor: f64) -> Result<Vec<Vec<Color>>, String> {
397397
self.ensure_screenshot_captured()?;
398398

electron-app/magnifier/rust-sampler/src/sampler/windows.rs

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@ use windows::Win32::UI::WindowsAndMessaging::GetCursorPos;
1010

1111
pub struct WindowsSampler {
1212
hdc: HDC,
13-
dpi_scale: f64,
13+
pub dpi_scale: f64,
1414
}
1515

1616
impl WindowsSampler {
1717
pub fn new() -> Result<Self, String> {
1818
unsafe {
1919
let hdc = GetDC(None);
20-
20+
2121
if hdc.is_invalid() {
2222
return Err("Failed to get device context".to_string());
2323
}
24-
24+
2525
// Get DPI scaling factor
2626
// GetDeviceCaps returns DPI (e.g., 96 for 100%, 192 for 200%)
2727
// Standard DPI is 96, so scale = actual_dpi / 96
2828
let dpi = GetDeviceCaps(hdc, LOGPIXELSX);
2929
let dpi_scale = dpi as f64 / 96.0;
30-
31-
Ok(WindowsSampler {
30+
31+
Ok(WindowsSampler {
3232
hdc,
3333
dpi_scale,
3434
})
@@ -47,14 +47,12 @@ impl Drop for WindowsSampler {
4747
impl PixelSampler for WindowsSampler {
4848
fn sample_pixel(&mut self, x: i32, y: i32) -> Result<Color, String> {
4949
unsafe {
50-
// Electron is DPI-aware, so:
51-
// - GetCursorPos returns VIRTUAL pixels (e.g., 0-2559 at 200% on 5120 wide screen)
52-
// - GetPixel expects PHYSICAL pixels (e.g., 0-5119)
53-
// We must convert: physical = virtual * dpi_scale
54-
let physical_x = (x as f64 * self.dpi_scale) as i32;
55-
let physical_y = (y as f64 * self.dpi_scale) as i32;
56-
57-
let color_ref = GetPixel(self.hdc, physical_x, physical_y);
50+
// On Windows, for a DPI-unaware process (which this Rust subprocess is):
51+
// - GetCursorPos returns VIRTUALIZED coordinates (e.g., 0-2559 at 200% on 5120 wide screen)
52+
// - GetDC(None) returns a VIRTUALIZED DC that also uses virtual coordinates
53+
// - GetPixel on that DC expects the SAME virtualized coordinates
54+
// NO conversion needed - both APIs work in the same virtualized space
55+
let color_ref = GetPixel(self.hdc, x, y);
5856

5957
// Check for error (CLR_INVALID is returned on error)
6058
// COLORREF is a newtype wrapper around u32
@@ -77,13 +75,18 @@ impl PixelSampler for WindowsSampler {
7775
fn get_cursor_position(&self) -> Result<Point, String> {
7876
unsafe {
7977
let mut point = POINT { x: 0, y: 0 };
80-
78+
8179
GetCursorPos(&mut point)
8280
.map_err(|e| format!("Failed to get cursor position: {}", e))?;
83-
81+
82+
// Convert from virtual coordinates (returned by GetCursorPos) to physical coordinates
83+
// Electron (per-monitor DPI aware) expects physical coordinates for window positioning
84+
let physical_x = (point.x as f64 * self.dpi_scale) as i32;
85+
let physical_y = (point.y as f64 * self.dpi_scale) as i32;
86+
8487
Ok(Point {
85-
x: point.x,
86-
y: point.y,
88+
x: physical_x,
89+
y: physical_y,
8790
})
8891
}
8992
}
@@ -94,15 +97,10 @@ impl PixelSampler for WindowsSampler {
9497
unsafe {
9598
let half_size = (grid_size / 2) as i32;
9699

97-
// Electron is DPI-aware, so GetCursorPos returns virtual coordinates
98-
// but GetDC/BitBlt use physical coordinates
99-
// Convert: physical = virtual * dpi_scale
100-
let physical_center_x = (center_x as f64 * self.dpi_scale) as i32;
101-
let physical_center_y = (center_y as f64 * self.dpi_scale) as i32;
102-
103-
// Calculate capture region in physical pixel coordinates
104-
let x_start = physical_center_x - half_size;
105-
let y_start = physical_center_y - half_size;
100+
// For a DPI-unaware process, all GDI operations use virtualized coordinates
101+
// No conversion needed
102+
let x_start = center_x - half_size;
103+
let y_start = center_y - half_size;
106104
let width = grid_size as i32;
107105
let height = grid_size as i32;
108106

@@ -222,19 +220,15 @@ impl WindowsSampler {
222220
let half_size = (grid_size / 2) as i32;
223221
let mut grid = Vec::with_capacity(grid_size);
224222

225-
// Convert virtual cursor coordinates to physical for DC sampling
226-
let physical_center_x = (center_x as f64 * self.dpi_scale) as i32;
227-
let physical_center_y = (center_y as f64 * self.dpi_scale) as i32;
228-
223+
// For DPI-unaware process, use coordinates directly
229224
for row in 0..grid_size {
230225
let mut row_pixels = Vec::with_capacity(grid_size);
231226
for col in 0..grid_size {
232-
// Calculate physical pixel coordinates
233-
let physical_x = physical_center_x + (col as i32 - half_size);
234-
let physical_y = physical_center_y + (row as i32 - half_size);
227+
// Calculate pixel coordinates (no conversion needed)
228+
let x = center_x + (col as i32 - half_size);
229+
let y = center_y + (row as i32 - half_size);
235230

236-
// Sample using physical coordinates
237-
let color_ref = GetPixel(self.hdc, physical_x, physical_y);
231+
let color_ref = GetPixel(self.hdc, x, y);
238232

239233
let color = if color_ref.0 == CLR_INVALID {
240234
Color::new(128, 128, 128) // Gray fallback for out-of-bounds

electron-app/magnifier/rust-sampler/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub trait PixelSampler {
8282

8383
/// Get cursor position
8484
fn get_cursor_position(&self) -> Result<Point, String>;
85-
85+
8686
/// Sample a grid of pixels around a center point
8787
fn sample_grid(&mut self, center_x: i32, center_y: i32, grid_size: usize, _scale_factor: f64) -> Result<Vec<Vec<Color>>, String> {
8888
let half_size = (grid_size / 2) as i32;

electron-app/magnifier/rust-sampler/tests/windows_sampler_tests.rs

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,33 @@ impl PixelSampler for MockWindowsSampler {
5252
}
5353

5454
fn get_cursor_position(&self) -> Result<Point, String> {
55-
// Simulate GetCursorPos (returns virtual coordinates)
56-
Ok(Point { x: 100, y: 100 })
55+
// Simulate Windows sampler behavior: return physical coordinates
56+
// (virtual coordinates converted to physical for Electron compatibility)
57+
let virtual_x = 100;
58+
let virtual_y = 100;
59+
let physical_x = (virtual_x as f64 * self.dpi_scale) as i32;
60+
let physical_y = (virtual_y as f64 * self.dpi_scale) as i32;
61+
Ok(Point { x: physical_x, y: physical_y })
5762
}
5863

59-
// Override sample_grid to simulate DPI-aware behavior
64+
// Override sample_grid to simulate production behavior (virtual coordinates)
6065
fn sample_grid(&mut self, center_x: i32, center_y: i32, grid_size: usize, _scale_factor: f64) -> Result<Vec<Vec<Color>>, String> {
6166
let half_size = (grid_size / 2) as i32;
6267
let mut grid = Vec::with_capacity(grid_size);
63-
64-
// Convert center from virtual to physical pixels (matches real implementation)
65-
let physical_center_x = (center_x as f64 * self.dpi_scale) as i32;
66-
let physical_center_y = (center_y as f64 * self.dpi_scale) as i32;
67-
68+
69+
// Production sample_grid operates in virtual coordinates (no DPI scaling)
6870
for row in 0..grid_size {
6971
let mut row_pixels = Vec::with_capacity(grid_size);
7072
for col in 0..grid_size {
71-
// Work in physical pixel space (what BitBlt/GetPixel use)
72-
let physical_x = physical_center_x + (col as i32 - half_size);
73-
let physical_y = physical_center_y + (row as i32 - half_size);
74-
73+
// Calculate virtual pixel coordinates (matches production behavior)
74+
let virtual_x = center_x + (col as i32 - half_size);
75+
let virtual_y = center_y + (row as i32 - half_size);
76+
77+
// Convert virtual to physical for bounds checking and color calculation
78+
// (since screen_width/screen_height are physical dimensions)
79+
let physical_x = (virtual_x as f64 * self.dpi_scale) as i32;
80+
let physical_y = (virtual_y as f64 * self.dpi_scale) as i32;
81+
7582
// Sample in physical space
7683
if physical_x < 0 || physical_y < 0 || physical_x >= self.screen_width || physical_y >= self.screen_height {
7784
row_pixels.push(Color::new(128, 128, 128));
@@ -84,7 +91,7 @@ impl PixelSampler for MockWindowsSampler {
8491
}
8592
grid.push(row_pixels);
8693
}
87-
94+
8895
Ok(grid)
8996
}
9097
}
@@ -681,39 +688,32 @@ fn test_windows_sampler_dpi_grid_edge_alignment() {
681688
assert_eq!(grid.len(), grid_size);
682689
assert_eq!(grid[0].len(), grid_size);
683690

684-
// Verify center pixel matches what we expect at the physical coordinates
685-
// Virtual (1000, 500) -> Physical (2000, 1000)
686-
let dpi_scale = 2.0;
687-
let physical_center_x = (virtual_center_x as f64 * dpi_scale) as i32; // 2000
688-
let physical_center_y = (virtual_center_y as f64 * dpi_scale) as i32; // 1000
689-
691+
// Verify center pixel matches what we expect
692+
// Mock sample_grid operates in virtual coordinates like production
690693
let center_idx = grid_size / 2; // 2 for a 5x5 grid
691694
let center_pixel = &grid[center_idx][center_idx];
692-
693-
// The mock sampler generates colors based on physical coordinates:
694-
// b = physical_x % 256, g = physical_y % 256, r = (physical_x + physical_y) % 256
695-
let expected_b = (physical_center_x % 256) as u8;
696-
let expected_g = (physical_center_y % 256) as u8;
697-
let expected_r = ((physical_center_x + physical_center_y) % 256) as u8;
698-
695+
696+
// Center samples at virtual position (1000, 500) -> physical (2000, 1000)
697+
// Colors are based on physical coordinates
698+
let expected_b = (2000 % 256) as u8; // 2000 % 256 = 224
699+
let expected_g = (1000 % 256) as u8; // 1000 % 256 = 232
700+
let expected_r = ((2000 + 1000) % 256) as u8; // 3000 % 256 = 200
701+
699702
assert_eq!(center_pixel.r, expected_r, "Center pixel R component mismatch");
700703
assert_eq!(center_pixel.g, expected_g, "Center pixel G component mismatch");
701704
assert_eq!(center_pixel.b, expected_b, "Center pixel B component mismatch");
702-
703-
// Verify corner pixels sample the correct physical locations
704-
// Top-left: offset (-2, -2) from center -> physical (1998, 998)
705+
706+
// Grid samples at virtual offsets from center (1000, 500)
707+
// Virtual half_size = 2 for 5x5 grid
708+
// Top-left: virtual (998, 498) -> physical (1996, 996)
705709
let top_left = &grid[0][0];
706-
let tl_physical_x = physical_center_x - 2; // 1998
707-
let tl_physical_y = physical_center_y - 2; // 998
708-
assert_eq!(top_left.b, (tl_physical_x % 256) as u8);
709-
assert_eq!(top_left.g, (tl_physical_y % 256) as u8);
710-
711-
// Bottom-right: offset (2, 2) from center -> physical (2002, 1002)
710+
assert_eq!(top_left.b, (1996 % 256) as u8); // 1996 % 256 = 220
711+
assert_eq!(top_left.g, (996 % 256) as u8); // 996 % 256 = 228
712+
713+
// Bottom-right: virtual (1002, 502) -> physical (2004, 1004)
712714
let bottom_right = &grid[4][4];
713-
let br_physical_x = physical_center_x + 2; // 2002
714-
let br_physical_y = physical_center_y + 2; // 1002
715-
assert_eq!(bottom_right.b, (br_physical_x % 256) as u8);
716-
assert_eq!(bottom_right.g, (br_physical_y % 256) as u8);
715+
assert_eq!(bottom_right.b, (2004 % 256) as u8); // 2004 % 256 = 228
716+
assert_eq!(bottom_right.g, (1004 % 256) as u8); // 1004 % 256 = 236
717717
}
718718

719719
#[test]

0 commit comments

Comments
 (0)