Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions openless-all/app/scripts/windows-ui-config.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,39 @@ function assertMatch(source, pattern, name) {
const raw = await readFile(new URL('../src-tauri/tauri.conf.json', import.meta.url), 'utf-8');
const config = JSON.parse(raw);
const capsuleWindow = config.app.windows.find((window) => window.label === 'capsule');
const mainWindow = config.app.windows.find((window) => window.label === 'main');
const libRs = await readFile(new URL('../src-tauri/src/lib.rs', import.meta.url), 'utf-8');
const coordinatorRs = await readFile(new URL('../src-tauri/src/coordinator.rs', import.meta.url), 'utf-8');
const capsuleTsx = await readFile(new URL('../src/components/Capsule.tsx', import.meta.url), 'utf-8');
const capsuleLayoutTs = await readFile(new URL('../src/lib/capsuleLayout.ts', import.meta.url), 'utf-8');
const windowChromeTsx = await readFile(new URL('../src/components/WindowChrome.tsx', import.meta.url), 'utf-8');
const floatingShellTsx = await readFile(new URL('../src/components/FloatingShell.tsx', import.meta.url), 'utf-8');

if (!capsuleWindow) {
throw new Error('capsule window config missing');
}
if (!mainWindow) {
throw new Error('main window config missing');
}
assertEqual(capsuleWindow.width, 220, 'windows capsule config keeps translation-capable width baseline');
assertEqual(capsuleWindow.height, 110, 'windows capsule config keeps translation-capable height baseline');
assertEqual(capsuleWindow.transparent, true, 'capsule window should keep transparent visuals');
assertEqual(capsuleWindow.alwaysOnTop, true, 'capsule window should stay above the focused app while recording');
assertEqual(mainWindow.decorations, false, 'windows main window should use only custom titlebar');
assertEqual(mainWindow.visible, false, 'windows main window should stay hidden until the intended first show point');

if (!/function WindowsResizeHandles\(\)/.test(windowChromeTsx)) {
throw new Error('windows frameless shell should expose explicit resize handles');
}

if (!/startResizeDragging\(direction\)/.test(windowChromeTsx)) {
throw new Error('windows resize handles should delegate edge dragging to Tauri');
}

if (!/borderRadius:\s*'var\(--ol-window-console-radius\)'/.test(floatingShellTsx)) {
throw new Error('floating shell should consume the shared window-console radius');
}

assertMatch(
coordinatorRs,
/let visible = !matches!\(state,\s*CapsuleState::Idle\);/,
Expand Down
3 changes: 3 additions & 0 deletions openless-all/app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ objc2-foundation = "0.2"
objc2-app-kit = "0.2"

[target.'cfg(target_os = "windows")'.dependencies]
raw-window-handle = "0.6"
windows = { version = "0.58", features = [
"Win32_Foundation",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_UI_Shell",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_WindowsAndMessaging",
Expand Down
111 changes: 107 additions & 4 deletions openless-all/app/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,18 @@ pub fn run() {
#[cfg(target_os = "windows")]
{
use window_vibrancy::apply_mica;
if let Err(e) = apply_mica(&main, None) {
log::warn!("[main] mica failed: {e}");
}
// The window starts hidden so Windows native chrome can be disabled before
// the first show; doing this after the native frame is visible is unreliable.
if let Err(e) = main.set_decorations(false) {
log::warn!("[main] disable native decorations failed: {e}");
}
if let Err(e) = apply_mica(&main, None) {
log::warn!("[main] mica failed: {e}");
}
apply_windows_rounded_frame(&main);
}
if let Err(e) = main.show() {
log::warn!("[main] initial show failed: {e}");
}
}

Expand Down Expand Up @@ -214,10 +220,16 @@ pub fn run() {
RunEvent::Reopen { .. } => show_main_window(app),
RunEvent::WindowEvent { label, event, .. } => {
if label == "main" {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
if let tauri::WindowEvent::CloseRequested { ref api, .. } = event {
api.prevent_close();
hide_main_window(app);
}
#[cfg(target_os = "windows")]
if matches!(event, tauri::WindowEvent::Resized(_) | tauri::WindowEvent::ScaleFactorChanged { .. }) {
if let Some(main) = app.get_webview_window("main") {
apply_windows_rounded_frame(&main);
}
}
}
}
RunEvent::Exit => {
Expand All @@ -229,6 +241,97 @@ pub fn run() {
});
}

#[cfg(target_os = "windows")]
fn apply_windows_rounded_frame<R: Runtime>(window: &tauri::WebviewWindow<R>) {
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use windows::Win32::Foundation::{BOOL, HWND, RECT};
use windows::Win32::Graphics::Dwm::{
DwmSetWindowAttribute, DWMWA_BORDER_COLOR, DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_ROUND,
};
use windows::Win32::Graphics::Gdi::{CreateRoundRectRgn, SetWindowRgn, HRGN};
use windows::Win32::UI::WindowsAndMessaging::{
GetWindowLongW, GetWindowRect, SetWindowLongW, SetWindowPos, GWL_STYLE, SWP_FRAMECHANGED,
SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WS_CAPTION, WS_THICKFRAME,
};

let handle = match window.window_handle().map(|h| h.as_raw()) {
Ok(RawWindowHandle::Win32(handle)) => handle,
Ok(other) => {
log::warn!("[main] unexpected raw window handle for DWM frame: {other:?}");
return;
}
Err(e) => {
log::warn!("[main] read raw window handle failed: {e}");
return;
}
};
let hwnd = HWND(handle.hwnd.get() as *mut core::ffi::c_void);

unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE);
let desired_style = (style | WS_THICKFRAME.0 as i32) & !(WS_CAPTION.0 as i32);
if style != desired_style {
SetWindowLongW(hwnd, GWL_STYLE, desired_style);
if let Err(e) = SetWindowPos(
hwnd,
HWND::default(),
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED,
) {
log::warn!("[main] refresh native frame after style update failed: {e}");
}
}

if window.is_maximized().unwrap_or(false) {
let _ = SetWindowRgn(hwnd, HRGN::default(), BOOL(1));
return;
}

let corner_preference = DWMWCP_ROUND;
if let Err(e) = DwmSetWindowAttribute(
hwnd,
DWMWA_WINDOW_CORNER_PREFERENCE,
&corner_preference as *const _ as *const core::ffi::c_void,
std::mem::size_of_val(&corner_preference) as u32,
) {
log::warn!("[main] set DWM rounded corners failed: {e}");
}

// Remove DWM's fallback 1px light border; the React shell draws the visual stroke.
let border_color_none: u32 = 0xFFFFFFFE;
if let Err(e) = DwmSetWindowAttribute(
hwnd,
DWMWA_BORDER_COLOR,
&border_color_none as *const _ as *const core::ffi::c_void,
std::mem::size_of_val(&border_color_none) as u32,
) {
log::warn!("[main] remove DWM border color failed: {e}");
}

let mut rect = RECT::default();
if let Err(e) = GetWindowRect(hwnd, &mut rect) {
log::warn!("[main] read window rect for rounded region failed: {e}");
return;
}
let width = rect.right - rect.left;
let height = rect.bottom - rect.top;
if width <= 0 || height <= 0 {
return;
}
let region = CreateRoundRectRgn(0, 0, width + 1, height + 1, 18, 18);
if region.is_invalid() {
log::warn!("[main] create rounded window region failed");
return;
}
if SetWindowRgn(hwnd, region, BOOL(1)) == 0 {
log::warn!("[main] apply rounded window region failed");
}
}
}

#[tauri::command]
fn restart_app(app: AppHandle) {
// macOS:自动更新会让新装的 .app 带 com.apple.quarantine(无论 Tauri updater
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"minWidth": 980,
"minHeight": 640,
"resizable": true,
"decorations": true,
"decorations": false,
"transparent": true,
"shadow": true,
"hiddenTitle": true,
Expand Down
2 changes: 1 addition & 1 deletion openless-all/app/src/components/FloatingShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ function FloatingShellBody({ os, initialTab, initialSettings }: { os: OS; initia
flex: 1, minWidth: 0,
overflow: 'hidden',
background: 'var(--ol-surface)',
borderRadius: os === 'mac' ? 20 : 14,
borderRadius: 'var(--ol-window-console-radius)',
border: '0.5px solid rgba(0,0,0,0.06)',
boxShadow: '0 1px 0 rgba(255,255,255,0.8) inset, 0 8px 24px -12px rgba(15,17,22,0.10), 0 2px 6px -2px rgba(15,17,22,0.06)',
display: 'flex',
Expand Down
Loading
Loading