diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index ac45e88814..377cb2b230 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -54,9 +54,11 @@ windows = { version = "0.58.0", features = [ "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_System_LibraryLoader", + "Win32_System_Com", "Win32_UI_Controls", "Win32_UI_WindowsAndMessaging", "Win32_UI_HiDpi", + "Win32_UI_Shell", ] } # macOS-specific dependencies diff --git a/desktop/platform/win/build.rs b/desktop/platform/win/build.rs index 73d8a2fd51..7adda46f1f 100644 --- a/desktop/platform/win/build.rs +++ b/desktop/platform/win/build.rs @@ -10,13 +10,13 @@ fn main() { // TODO: Replace with actual version res.set_version_info(winres::VersionInfo::FILEVERSION, { const MAJOR: u64 = 0; - const MINOR: u64 = 999; + const MINOR: u64 = 0; const PATCH: u64 = 0; const RELEASE: u64 = 0; (MAJOR << 48) | (MINOR << 32) | (PATCH << 16) | RELEASE }); - res.set("FileVersion", "0.999.0.0"); - res.set("ProductVersion", "0.999.0.0"); + res.set("FileVersion", "0.0.0.0"); + res.set("ProductVersion", "0.0.0.0"); res.set("OriginalFilename", "Graphite.exe"); diff --git a/desktop/src/app.rs b/desktop/src/app.rs index 9aa45fb3c8..f72f84c8e8 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -456,6 +456,10 @@ impl ApplicationHandler for App { let render_state = RenderState::new(self.window.as_ref().unwrap(), self.wgpu_context.clone()); self.render_state = Some(render_state); + if let Some(window) = &self.window.as_ref() { + window.show(); + } + self.resize(); self.desktop_wrapper.init(self.wgpu_context.clone()); diff --git a/desktop/src/consts.rs b/desktop/src/consts.rs index 0ce2e21e75..f56b2a2d70 100644 --- a/desktop/src/consts.rs +++ b/desktop/src/consts.rs @@ -1,5 +1,5 @@ pub(crate) const APP_NAME: &str = "Graphite"; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "windows"))] pub(crate) const APP_ID: &str = "art.graphite.Graphite"; pub(crate) const APP_DIRECTORY_NAME: &str = "graphite"; diff --git a/desktop/src/window.rs b/desktop/src/window.rs index 76b8ca1de5..d61b1afa5e 100644 --- a/desktop/src/window.rs +++ b/desktop/src/window.rs @@ -52,6 +52,7 @@ impl Window { .with_min_surface_size(winit::dpi::LogicalSize::new(400, 300)) .with_surface_size(winit::dpi::LogicalSize::new(1200, 800)) .with_resizable(true) + .with_visible(false) .with_theme(Some(winit::window::Theme::Dark)); attributes = native::NativeWindowImpl::configure(attributes, event_loop); @@ -67,6 +68,11 @@ impl Window { } } + pub(crate) fn show(&self) { + self.winit_window.set_visible(true); + self.winit_window.focus_window(); + } + pub(crate) fn request_redraw(&self) { self.winit_window.request_redraw(); } diff --git a/desktop/src/window/win.rs b/desktop/src/window/win.rs index afd53db18f..70a229a7e4 100644 --- a/desktop/src/window/win.rs +++ b/desktop/src/window/win.rs @@ -1,9 +1,10 @@ -use winit::dpi::PhysicalSize; +use windows::Win32::System::Com::{COINIT_APARTMENTTHREADED, CoInitializeEx}; +use windows::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID; +use windows::core::HSTRING; use winit::event_loop::ActiveEventLoop; -use winit::icon::Icon; -use winit::platform::windows::{WinIcon, WindowAttributesWindows}; use winit::window::{Window, WindowAttributes}; +use crate::consts::APP_ID; use crate::event::AppEventScheduler; pub(super) struct NativeWindowImpl { @@ -11,11 +12,16 @@ pub(super) struct NativeWindowImpl { } impl super::NativeWindow for NativeWindowImpl { + fn init() { + let app_id = HSTRING::from(APP_ID); + unsafe { + let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED).ok(); + SetCurrentProcessExplicitAppUserModelID(&app_id).ok(); + } + } + fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { - let icon = WinIcon::from_resource(1, Some(PhysicalSize::new(256, 256))).ok().map(|icon| Icon(std::sync::Arc::new(icon))); - let win_window = WindowAttributesWindows::default().with_taskbar_icon(icon); - let icon = WinIcon::from_resource(1, None).ok().map(|icon| Icon(std::sync::Arc::new(icon))); - attributes.with_window_icon(icon).with_platform_attributes(Box::new(win_window)) + attributes } fn new(window: &dyn Window, _app_event_scheduler: AppEventScheduler) -> Self { diff --git a/desktop/src/window/win/native_handle.rs b/desktop/src/window/win/native_handle.rs index 17ff1f19fa..61250000ed 100644 --- a/desktop/src/window/win/native_handle.rs +++ b/desktop/src/window/win/native_handle.rs @@ -30,7 +30,7 @@ pub(super) struct NativeWindowHandle { impl NativeWindowHandle { pub(super) fn new(window: &dyn Window) -> NativeWindowHandle { // Extract Win32 HWND from winit. - let hwnd = match window.window_handle().expect("No window handle").as_raw() { + let main = match window.window_handle().expect("No window handle").as_raw() { RawWindowHandle::Win32(h) => HWND(h.hwnd.get() as *mut std::ffi::c_void), _ => panic!("Not a Win32 window"), }; @@ -57,47 +57,61 @@ impl NativeWindowHandle { None, HINSTANCE(std::ptr::null_mut()), // Pass the main window's HWND to WM_NCCREATE so the helper can store it. - Some(&hwnd as *const _ as _), + Some(&main as *const _ as _), ) } .expect("CreateWindowExW failed"); // Subclass the main window. // https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-setwindowlongptra - let prev_window_message_handler = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, main_window_handle_message as isize) }; + let prev_window_message_handler = unsafe { SetWindowLongPtrW(main, GWLP_WNDPROC, main_window_handle_message as isize) }; if prev_window_message_handler == 0 { let _ = unsafe { DestroyWindow(helper) }; panic!("SetWindowLongPtrW failed"); } - let inner = NativeWindowHandle { - main: hwnd, + let native_handle = NativeWindowHandle { + main, helper, prev_window_message_handler, }; - registry::insert(&inner); + registry::insert(&native_handle); // Place the helper over the main window and show it without activation. - unsafe { position_helper(hwnd, helper) }; + unsafe { position_helper(main, helper) }; let _ = unsafe { ShowWindow(helper, SW_SHOWNOACTIVATE) }; // DwmExtendFrameIntoClientArea is needed to keep native window frame (but no titlebar). // https://learn.microsoft.com/windows/win32/api/dwmapi/nf-dwmapi-dwmextendframeintoclientarea // https://learn.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute let mut boarder_size: u32 = 1; - let _ = unsafe { DwmGetWindowAttribute(hwnd, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &mut boarder_size as *mut _ as *mut _, size_of::() as u32) }; + let _ = unsafe { DwmGetWindowAttribute(main, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, &mut boarder_size as *mut _ as *mut _, size_of::() as u32) }; let margins = MARGINS { cxLeftWidth: 0, cxRightWidth: 0, cyBottomHeight: 0, cyTopHeight: boarder_size as i32, }; - let _ = unsafe { DwmExtendFrameIntoClientArea(hwnd, &margins) }; + let _ = unsafe { DwmExtendFrameIntoClientArea(main, &margins) }; + + let hinst = unsafe { GetModuleHandleW(None) }.unwrap(); + + // Set taskbar icon + if let Ok(big) = unsafe { LoadImageW(hinst, PCWSTR(1usize as *const u16), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), LR_SHARED) } { + unsafe { SetClassLongPtrW(main, GCLP_HICON, big.0 as isize) }; + unsafe { SendMessageW(main, WM_SETICON, WPARAM(ICON_BIG as usize), LPARAM(big.0 as isize)) }; + } + + // Set window icon + if let Ok(small) = unsafe { LoadImageW(hinst, PCWSTR(1usize as *const u16), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED) } { + unsafe { SetClassLongPtrW(main, GCLP_HICONSM, small.0 as isize) }; + unsafe { SendMessageW(main, WM_SETICON, WPARAM(ICON_SMALL as usize), LPARAM(small.0 as isize)) }; + } // Force window update - let _ = unsafe { SetWindowPos(hwnd, None, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) }; + let _ = unsafe { SetWindowPos(main, None, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER) }; - inner + native_handle } pub(super) fn destroy(&self) { @@ -105,7 +119,7 @@ impl NativeWindowHandle { // Undo subclassing and destroy the helper window. let _ = unsafe { SetWindowLongPtrW(self.main, GWLP_WNDPROC, self.prev_window_message_handler) }; - if self.helper.0 != std::ptr::null_mut() { + if !self.helper.is_invalid() { let _ = unsafe { DestroyWindow(self.helper) }; } } @@ -264,16 +278,17 @@ unsafe extern "system" fn helper_window_handle_message(hwnd: HWND, msg: u32, wpa unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } } +const RESIZE_BAND_THICKNESS: i32 = 8; + // Position the helper window to match the main window's location and size (plus the resize band size). unsafe fn position_helper(main: HWND, helper: HWND) { let mut r = RECT::default(); let _ = unsafe { GetWindowRect(main, &mut r) }; - const RESIZE_BAND_SIZE: i32 = 8; - let x = r.left - RESIZE_BAND_SIZE; - let y = r.top - RESIZE_BAND_SIZE; - let w = (r.right - r.left) + RESIZE_BAND_SIZE * 2; - let h = (r.bottom - r.top) + RESIZE_BAND_SIZE * 2; + let x = r.left - RESIZE_BAND_THICKNESS; + let y = r.top - RESIZE_BAND_THICKNESS; + let w = (r.right - r.left) + RESIZE_BAND_THICKNESS * 2; + let h = (r.bottom - r.top) + RESIZE_BAND_THICKNESS * 2; let _ = unsafe { SetWindowPos(helper, main, x, y, w, h, SWP_NOACTIVATE) }; } @@ -285,7 +300,6 @@ unsafe fn calculate_hit(helper: HWND, lparam: LPARAM) -> u32 { let mut r = RECT::default(); let _ = unsafe { GetWindowRect(helper, &mut r) }; - const RESIZE_BAND_THICKNESS: i32 = 8; let on_top = y < (r.top + RESIZE_BAND_THICKNESS) as u32; let on_right = x >= (r.right - RESIZE_BAND_THICKNESS) as u32; let on_bottom = y >= (r.bottom - RESIZE_BAND_THICKNESS) as u32;