feat(tauri): 맥에서도 빌드되게 Win32 모듈을 cfg(windows) 로 분리

windows crate 를 Windows 타깃 전용 의존성으로 옮기고, paste/capture 는 다른 OS 에서
같은 API 의 껍데기(_stub)로 컴파일. hwnd 접근은 window::native_handle 하나로.
Windows 동작은 그대로. 맥에선 붙여넣기·영역 캡처가 'Windows 에서만 됨'으로 응답.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 21:24:17 +09:00
co-authored by Claude Fable 5.1
parent 54f75aa089
commit 506fbf601d
8 changed files with 787 additions and 738 deletions
-15
View File
@@ -100,9 +100,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0 OR MIT", "license": "Apache-2.0 OR MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -120,9 +117,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0 OR MIT", "license": "Apache-2.0 OR MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -140,9 +134,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0 OR MIT", "license": "Apache-2.0 OR MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -160,9 +151,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0 OR MIT", "license": "Apache-2.0 OR MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -180,9 +168,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "Apache-2.0 OR MIT", "license": "Apache-2.0 OR MIT",
"optional": true, "optional": true,
"os": [ "os": [
+3
View File
@@ -24,6 +24,9 @@ serde_json = "1"
rusqlite = { version = "0.32.1", features = ["bundled"] } rusqlite = { version = "0.32.1", features = ["bundled"] }
base64 = "0.22" base64 = "0.22"
png = "0.17" png = "0.17"
# Win32 (붙여넣기·영역 캡처) — Windows 빌드에서만. 맥/리눅스는 shell/*_stub.rs 껍데기.
[target.'cfg(windows)'.dependencies]
windows = { version = "0.61.3", features = [ windows = { version = "0.61.3", features = [
"Win32_Foundation", "Win32_Foundation",
"Win32_Graphics_Gdi", "Win32_Graphics_Gdi",
@@ -116,10 +116,7 @@ pub async fn paste_code(
.lock() .lock()
.map(|target| target.clone()) .map(|target| target.clone())
.map_err(|_| "붙여넣기 대상 상태를 읽지 못함".to_string()); .map_err(|_| "붙여넣기 대상 상태를 읽지 못함".to_string());
let palette_hwnd = window::main_window(&app) let palette_hwnd = window::native_handle(&app).unwrap_or_default();
.and_then(|window| window.hwnd().ok())
.map(|hwnd| hwnd.0 as isize)
.unwrap_or_default();
let worker_app = app.clone(); let worker_app = app.clone();
let result = tauri::async_runtime::spawn_blocking(move || { let result = tauri::async_runtime::spawn_blocking(move || {
let _paste_guard = paste_guard; let _paste_guard = paste_guard;
+1 -3
View File
@@ -125,9 +125,7 @@ fn chat_toggle_target(visible: bool, current: &str, last_snap: &str) -> Option<S
/// 팔레트를 띄우기 직전에 대상 창 값을 복사해 둔다. 대상이 닫혀도 배지 문자열은 유지됨. /// 팔레트를 띄우기 직전에 대상 창 값을 복사해 둔다. 대상이 닫혀도 배지 문자열은 유지됨.
fn show_palette(app: &AppHandle) -> tauri::Result<()> { fn show_palette(app: &AppHandle) -> tauri::Result<()> {
let own_hwnd = window::main_window(app) let own_hwnd = window::native_handle(app);
.and_then(|window| window.hwnd().ok())
.map(|hwnd| hwnd.0 as isize);
let captured = paste::capture_foreground(own_hwnd); let captured = paste::capture_foreground(own_hwnd);
let state = app.state::<paste::PasteState>(); let state = app.state::<paste::PasteState>();
let target = match state.lock() { let target = match state.lock() {
@@ -0,0 +1,10 @@
//! `capture.rs` 의 Windows 외 껍데기 — 영역 캡처 오버레이는 GDI 전용이라 맥/리눅스에선 아무것도 안 함.
use tauri::AppHandle;
pub fn start<F>(_app: AppHandle, _on_captured: F)
where
F: FnOnce(AppHandle, String) + Send + 'static,
{
eprintln!("[capture] 영역 캡처는 Windows 에서만 됨");
}
+9
View File
@@ -7,8 +7,17 @@
//! 일부러 `init(ShellConfig)` 같은 통합 진입점을 안 만들었다 — 세 모듈이 서로 독립이고 //! 일부러 `init(ShellConfig)` 같은 통합 진입점을 안 만들었다 — 세 모듈이 서로 독립이고
//! 콜백도 각자 달라서, 감싸봐야 그냥 넘겨주기만 하는 껍데기가 하나 더 생길 뿐임. //! 콜백도 각자 달라서, 감싸봐야 그냥 넘겨주기만 하는 껍데기가 하나 더 생길 뿐임.
// capture·paste 는 Win32 전용. 다른 OS 는 같은 API 의 껍데기(_stub)로 컴파일만 되게 함 — 맥에서 개발용.
#[cfg(windows)]
pub mod capture;
#[cfg(not(windows))]
#[path = "capture_stub.rs"]
pub mod capture; pub mod capture;
pub mod hotkey; pub mod hotkey;
#[cfg(windows)]
pub mod paste;
#[cfg(not(windows))]
#[path = "paste_stub.rs"]
pub mod paste; pub mod paste;
pub mod tray; pub mod tray;
pub mod window; pub mod window;
@@ -0,0 +1,31 @@
//! `paste.rs` 의 Windows 외 껍데기 — 직전 앱 기억·키 입력 붙여넣기는 Win32 전용이라
//! 맥/리눅스에선 "대상 없음"으로만 동작함. 공개 API(타입·함수 시그니처)는 paste.rs 와 같아야 함.
use std::sync::Mutex;
use std::time::Duration;
pub const DEFAULT_PASTE_DELAY: Duration = Duration::from_millis(80);
#[derive(Clone, Debug, Default)]
pub struct PasteTarget {
pub name: String,
pub app: String,
}
pub type PasteState = Mutex<PasteTarget>;
pub fn capture_foreground(_excluded_hwnd: Option<isize>) -> Option<PasteTarget> {
None
}
pub fn paste(
text: &str,
_target: &PasteTarget,
_palette_hwnd: isize,
_delay: Duration,
) -> Result<(), String> {
if text.is_empty() {
return Err("붙여넣을 코드가 비었음".to_string());
}
Err("직전 앱 붙여넣기는 Windows 에서만 됨".to_string())
}
@@ -20,6 +20,22 @@ pub fn main_window(app: &AppHandle) -> Option<WebviewWindow> {
} }
/// 창 표시 + 포커스. (핫키/트레이 소환용) /// 창 표시 + 포커스. (핫키/트레이 소환용)
/// 붙여넣기 대상에서 우리 창을 빼거나 포커스 비교할 때 쓰는 네이티브 창 핸들(HWND).
/// Windows 에서만 값이 있고 다른 OS 는 None — 호출부가 알아서 "대상 없음"으로 처리함.
pub fn native_handle(app: &AppHandle) -> Option<isize> {
#[cfg(windows)]
{
main_window(app)
.and_then(|window| window.hwnd().ok())
.map(|hwnd| hwnd.0 as isize)
}
#[cfg(not(windows))]
{
let _ = app;
None
}
}
pub fn show(app: &AppHandle) -> tauri::Result<()> { pub fn show(app: &AppHandle) -> tauri::Result<()> {
if let Some(w) = main_window(app) { if let Some(w) = main_window(app) {
w.show()?; w.show()?;