Skip to content

Commit 76407ad

Browse files
committed
fix(Core): add GetWindowRectangle to macOS
1 parent f8526bc commit 76407ad

7 files changed

Lines changed: 161 additions & 27 deletions

File tree

SnapX.Core/ScreenCapture/Rust/bindings/snapxrust.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1513,6 +1513,7 @@ internal record WindowData(
15131513
string @appName,
15141514
string @title,
15151515
uint @processId,
1516+
uint @hwnd,
15161517
int @x,
15171518
int @y,
15181519
uint @width,
@@ -1532,6 +1533,7 @@ public override WindowData Read(BigEndianStream stream)
15321533
@appName: FfiConverterString.INSTANCE.Read(stream),
15331534
@title: FfiConverterString.INSTANCE.Read(stream),
15341535
@processId: FfiConverterUInt32.INSTANCE.Read(stream),
1536+
@hwnd: FfiConverterUInt32.INSTANCE.Read(stream),
15351537
@x: FfiConverterInt32.INSTANCE.Read(stream),
15361538
@y: FfiConverterInt32.INSTANCE.Read(stream),
15371539
@width: FfiConverterUInt32.INSTANCE.Read(stream),
@@ -1548,6 +1550,7 @@ public override int AllocationSize(WindowData value)
15481550
+ FfiConverterString.INSTANCE.AllocationSize(value.@appName)
15491551
+ FfiConverterString.INSTANCE.AllocationSize(value.@title)
15501552
+ FfiConverterUInt32.INSTANCE.AllocationSize(value.@processId)
1553+
+ FfiConverterUInt32.INSTANCE.AllocationSize(value.@hwnd)
15511554
+ FfiConverterInt32.INSTANCE.AllocationSize(value.@x)
15521555
+ FfiConverterInt32.INSTANCE.AllocationSize(value.@y)
15531556
+ FfiConverterUInt32.INSTANCE.AllocationSize(value.@width)
@@ -1562,6 +1565,7 @@ public override void Write(WindowData value, BigEndianStream stream)
15621565
FfiConverterString.INSTANCE.Write(value.@appName, stream);
15631566
FfiConverterString.INSTANCE.Write(value.@title, stream);
15641567
FfiConverterUInt32.INSTANCE.Write(value.@processId, stream);
1568+
FfiConverterUInt32.INSTANCE.Write(value.@hwnd, stream);
15651569
FfiConverterInt32.INSTANCE.Write(value.@x, stream);
15661570
FfiConverterInt32.INSTANCE.Write(value.@y, stream);
15671571
FfiConverterUInt32.INSTANCE.Write(value.@width, stream);
Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
1-
use std::process::Command;
21
use camino::Utf8Path;
2+
use std::{env, path::PathBuf, process::Command};
33

44
fn main() {
5-
println!("cargo:rerun-if-changed=build.rs");
6-
let out_dir = "bindings";
7-
let udl_file = "src/snapxrust.udl";
8-
let cargo = Utf8Path::new("Cargo.toml");
9-
uniffi_build::generate_scaffolding(udl_file).unwrap();
10-
// if Command::new("$HOME/.cargo/bin/uniffi-bindgen-cs").arg("--version").output().is_err() {
11-
// Using fork to bump uniffi to 0.30
12-
println!("Installing uniffi-bindgen-cs updated fork!");
13-
Command::new("cargo")
14-
.arg("install")
15-
.arg("uniffi-bindgen-cs")
16-
.arg("--git")
17-
.arg("https://github.com/sensslen/uniffi-bindgen-cs")
18-
.arg("--branch")
19-
.arg("main")
20-
.status()
21-
.expect("Failed to install uniffi-bindgen-cs");
22-
// }
23-
let _ = Command::new("$HOME/.cargo/bin/uniffi-bindgen-cs").arg(udl_file)
24-
.arg("--config").arg(cargo).arg("--out-dir").arg(out_dir).output();
5+
println!("cargo:rerun-if-changed=build.rs");
6+
let out_dir = "bindings";
7+
let udl_file = "src/snapxrust.udl";
8+
let cargo = Utf8Path::new("Cargo.toml");
9+
uniffi_build::generate_scaffolding(udl_file).unwrap();
10+
// if Command::new("$HOME/.cargo/bin/uniffi-bindgen-cs").arg("--version").output().is_err() {
11+
// Using fork to bump uniffi to 0.30
12+
println!("Installing uniffi-bindgen-cs updated fork!");
13+
Command::new("cargo")
14+
.arg("install")
15+
.arg("uniffi-bindgen-cs")
16+
.arg("--git")
17+
.arg("https://github.com/sensslen/uniffi-bindgen-cs")
18+
.arg("--branch")
19+
.arg("main")
20+
.status()
21+
.expect("Failed to install uniffi-bindgen-cs");
22+
// }
23+
let home = env::var("HOME")
24+
.or_else(|_| env::var("USERPROFILE"))
25+
.expect("Could not find HOME or USERPROFILE environment variables");
26+
27+
let mut bindgen_path = PathBuf::from(home).join(".cargo/bin/uniffi-bindgen-cs");
28+
29+
if cfg!(target_os = "windows") {
30+
bindgen_path.set_extension("exe");
31+
}
32+
33+
let output = Command::new(bindgen_path)
34+
.arg(udl_file)
35+
.arg("--config")
36+
.arg(cargo)
37+
.arg("--out_dir")
38+
.arg(out_dir)
39+
.output();
2540
}

SnapX.Core/ScreenCapture/Rust/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub struct WindowData {
2828
pub app_name: String,
2929
pub title: String,
3030
pub process_id: u32,
31+
pub hwnd: u32,
3132
pub x: i32,
3233
pub y: i32,
3334
pub width: u32,
@@ -52,11 +53,13 @@ pub fn get_window_list() -> Vec<WindowData> {
5253
let is_maximized = window.is_maximized().unwrap_or(false);
5354
let process_id = window.pid().unwrap_or(0);
5455
let is_focused = window.is_focused().unwrap_or(false);
56+
let hwnd = window.id().unwrap_or_default();
5557

5658
list.push(WindowData {
5759
app_name,
5860
title,
5961
process_id,
62+
hwnd,
6063
x,
6164
y,
6265
width,

SnapX.Core/ScreenCapture/Rust/src/snapxrust.udl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dictionary WindowData {
1414
string app_name;
1515
string title;
1616
u32 process_id;
17+
u32 hwnd;
1718
i32 x;
1819
i32 y;
1920
u32 width;

SnapX.Core/Utils/Native/LinuxAPI.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ internal static bool IsGNOME()
3131
&& sessionVersion.Contains("gnome", StringComparison.OrdinalIgnoreCase);
3232
}
3333

34+
public override Rectangle GetWindowRectangle(WindowInfo window)
35+
{
36+
return GetWindowRectangleX11(window.Handle);
37+
}
38+
3439
public override Rectangle GetWindowRectangle(IntPtr windowHandle)
3540
{
3641
return GetWindowRectangleX11(windowHandle);

SnapX.Core/Utils/Native/MacOSAPI.cs

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,22 +85,68 @@ struct CGPoint
8585
public double Y;
8686
}
8787

88-
[DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")]
88+
private const string CoreGraphicsLib =
89+
"/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics";
90+
91+
[DllImport(CoreGraphicsLib)]
8992
static extern CGPoint CGEventGetLocation(IntPtr eventRef);
9093

91-
[DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")]
94+
[DllImport(CoreGraphicsLib)]
9295
static extern IntPtr CGEventCreate(IntPtr source);
9396

94-
[DllImport("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics")]
97+
[DllImport(CoreGraphicsLib)]
9598
static extern IntPtr CFRelease(IntPtr eventRef);
9699

100+
private const string CoreFoundationLib =
101+
"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
102+
103+
// Options for window list
104+
private const uint kCGWindowListOptionIncludingWindow = 1 << 3;
105+
106+
[DllImport(CoreGraphicsLib)]
107+
private static extern IntPtr CGWindowListCopyWindowInfo(uint option, uint relativeToWindow);
108+
109+
[DllImport(CoreFoundationLib)]
110+
private static extern int CFArrayGetCount(IntPtr theArray);
111+
112+
[DllImport(CoreFoundationLib)]
113+
private static extern IntPtr CFArrayGetValueAtIndex(IntPtr theArray, int idx);
114+
[DllImport(CoreFoundationLib)]
115+
internal static extern IntPtr CFStringCreateWithCString(
116+
IntPtr alloc,
117+
string str,
118+
uint encoding
119+
);
120+
121+
[DllImport(CoreFoundationLib)]
122+
internal static extern IntPtr CFDictionaryGetValue(IntPtr theDict, IntPtr key);
123+
124+
[DllImport(CoreGraphicsLib)]
125+
[return: MarshalAs(UnmanagedType.I1)]
126+
internal static extern bool CGRectMakeWithDictionaryRepresentation(
127+
IntPtr dict,
128+
out CGRect rect
129+
);
130+
[StructLayout(LayoutKind.Sequential)]
131+
public struct CGRect
132+
{
133+
public double X;
134+
public double Y;
135+
public double Width;
136+
public double Height;
137+
}
138+
139+
// Standard UTF8 encoding ID for CFString
140+
internal const uint kCFStringEncodingUTF8 = 0x08000100;
141+
97142
public override Point GetCursorPosition()
98143
{
99144
var ev = CGEventCreate(IntPtr.Zero);
100145
var point = CGEventGetLocation(ev);
101146
CFRelease(ev);
102147
return new Point((int)point.X, (int)point.Y);
103148
}
149+
104150
public void ShowWindow(WindowInfo window)
105151
{
106152
if (window.ProcessId == 0)
@@ -121,6 +167,66 @@ public void ShowWindow(WindowInfo window)
121167
using var process = Process.Start(psi);
122168
process?.WaitForExit();
123169
}
170+
public override Rectangle GetWindowRectangle(WindowInfo window)
171+
{
172+
return base.GetWindowRectangle(window.Handle);
173+
}
174+
public override Rectangle GetWindowRectangle(IntPtr windowHandle)
175+
{
176+
uint windowId = (uint)windowHandle.ToInt32();
177+
178+
IntPtr arrayRef = CGWindowListCopyWindowInfo(kCGWindowListOptionIncludingWindow, windowId);
179+
if (arrayRef == IntPtr.Zero)
180+
return Rectangle.Empty;
181+
182+
try
183+
{
184+
int count = CFArrayGetCount(arrayRef);
185+
if (count == 0)
186+
return Rectangle.Empty;
187+
188+
IntPtr dictRef = CFArrayGetValueAtIndex(arrayRef, 0);
189+
190+
return ExtractCGRectFromDict(dictRef);
191+
}
192+
finally
193+
{
194+
CFRelease(arrayRef);
195+
}
196+
}
197+
private Rectangle ExtractCGRectFromDict(IntPtr dictRef)
198+
{
199+
IntPtr key = CFStringCreateWithCString(
200+
IntPtr.Zero,
201+
"kCGWindowBounds",
202+
kCFStringEncodingUTF8
203+
);
204+
205+
try
206+
{
207+
IntPtr boundsDict = CFDictionaryGetValue(dictRef, key);
208+
209+
if (
210+
boundsDict != IntPtr.Zero
211+
&& CGRectMakeWithDictionaryRepresentation(boundsDict, out CGRect cgRect)
212+
)
213+
{
214+
return new Rectangle(
215+
(int)cgRect.X,
216+
(int)cgRect.Y,
217+
(int)cgRect.Width,
218+
(int)cgRect.Height
219+
);
220+
}
221+
}
222+
finally
223+
{
224+
if (key != IntPtr.Zero)
225+
CFRelease(key);
226+
}
227+
228+
return Rectangle.Empty;
229+
}
124230

125231
public override List<WindowInfo> GetWindowList()
126232
{
@@ -139,9 +245,7 @@ public override List<WindowInfo> GetWindowList()
139245
IsMinimized = raw.isMinimized,
140246
IsVisible = !raw.isMinimized,
141247
IsActive = raw.isFocused,
142-
143-
// Not exposed
144-
Handle = IntPtr.Zero,
248+
Handle = (nint)(nuint)raw.hwnd,
145249
})
146250
.ToList();
147251

SnapX.Core/Utils/Native/Methods.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ public static Point GetCursorPosition()
8484

8585
public static Rectangle GetWindowRectangle(IntPtr windowHandle = 0) =>
8686
NativeAPI.GetWindowRectangle(windowHandle);
87+
public static Rectangle GetWindowRectangle(WindowInfo window) =>
88+
NativeAPI.GetWindowRectangle(window);
8789

8890
public static WindowInfo? GetForegroundWindow()
8991
{

0 commit comments

Comments
 (0)