Implement more GPUI services on windows. (#8940)

Small White and Mikayla Maki created

### Description

This is a part of #8809 , impl the following functions:

- `os_version`
- `local_timezone`
- `double_click_interval`
- `set_cursor_style`
- `open_url`
- `reveal_path`

Release Notes:
- N/A

---------

Co-authored-by: Mikayla Maki <mikayla@zed.dev>

Change summary

Cargo.toml                                   |  20 ++
crates/gpui/src/platform/windows/platform.rs | 146 ++++++++++++++++++---
2 files changed, 140 insertions(+), 26 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -215,7 +215,10 @@ hex = "0.4.3"
 ignore = "0.4.22"
 indoc = "1"
 # We explicitly disable http2 support in isahc.
-isahc = { version = "1.7.2", default-features = false, features = ["static-curl", "text-decoding"] }
+isahc = { version = "1.7.2", default-features = false, features = [
+    "static-curl",
+    "text-decoding",
+] }
 itertools = "0.11.0"
 lazy_static = "1.4.0"
 linkify = "0.10.0"
@@ -238,7 +241,10 @@ semver = "1.0"
 serde = { version = "1.0", features = ["derive", "rc"] }
 serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
 serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
-serde_json_lenient = { version = "0.1", features = ["preserve_order", "raw_value"] }
+serde_json_lenient = { version = "0.1", features = [
+    "preserve_order",
+    "raw_value",
+] }
 serde_repr = "0.1"
 sha2 = "0.10"
 shellexpand = "2.1.0"
@@ -249,7 +255,11 @@ sysinfo = "0.29.10"
 tempfile = "3.9.0"
 thiserror = "1.0.29"
 tiktoken-rs = "0.5.7"
-time = { version = "0.3", features = ["serde", "serde-well-known", "formatting"] }
+time = { version = "0.3", features = [
+    "serde",
+    "serde-well-known",
+    "formatting",
+] }
 toml = "0.8"
 tower-http = "0.4.4"
 tree-sitter = { version = "0.20", features = ["wasm"] }
@@ -312,11 +322,15 @@ sys-locale = "0.3.1"
 [workspace.dependencies.windows]
 version = "0.53.0"
 features = [
+    "Wdk_System_SystemServices",
     "Win32_Graphics_Gdi",
     "Win32_Graphics_DirectComposition",
     "Win32_UI_WindowsAndMessaging",
     "Win32_UI_Input_KeyboardAndMouse",
+    "Win32_UI_Shell",
+    "Win32_System_SystemInformation",
     "Win32_System_SystemServices",
+    "Win32_System_Time",
     "Win32_Security",
     "Win32_System_Threading",
     "Win32_System_DataExchange",

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -17,14 +17,28 @@ use futures::channel::oneshot::Receiver;
 use parking_lot::Mutex;
 use time::UtcOffset;
 use util::{ResultExt, SemanticVersion};
-use windows::Win32::{
-    Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE},
-    Graphics::DirectComposition::DCompositionWaitForCompositorClock,
-    System::Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
-    UI::WindowsAndMessaging::{
-        DispatchMessageW, EnumThreadWindows, PeekMessageW, PostQuitMessage, SystemParametersInfoW,
-        TranslateMessage, MSG, PM_REMOVE, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES,
-        SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE,
+use windows::{
+    core::{HSTRING, PCWSTR},
+    Wdk::System::SystemServices::RtlGetVersion,
+    Win32::{
+        Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE},
+        Graphics::DirectComposition::DCompositionWaitForCompositorClock,
+        System::{
+            Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
+            Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID},
+        },
+        UI::{
+            Input::KeyboardAndMouse::GetDoubleClickTime,
+            Shell::ShellExecuteW,
+            WindowsAndMessaging::{
+                DispatchMessageW, EnumThreadWindows, LoadImageW, MsgWaitForMultipleObjects,
+                PeekMessageW, PostQuitMessage, SetCursor, SystemParametersInfoW, TranslateMessage,
+                HCURSOR, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_IBEAM, IDC_NO, IDC_SIZENS, IDC_SIZEWE,
+                IMAGE_CURSOR, LR_DEFAULTSIZE, LR_SHARED, MSG, PM_REMOVE, QS_ALLINPUT,
+                SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SW_SHOWDEFAULT,
+                SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE,
+            },
+        },
     },
 };
 
@@ -300,9 +314,16 @@ impl Platform for WindowsPlatform {
         WindowAppearance::Dark
     }
 
-    // todo!("windows")
     fn open_url(&self, url: &str) {
-        // todo!("windows")
+        let url_string = url.to_string();
+        self.background_executor()
+            .spawn(async move {
+                if url_string.is_empty() {
+                    return;
+                }
+                open_target(url_string.as_str());
+            })
+            .detach();
     }
 
     // todo!("windows")
@@ -320,9 +341,22 @@ impl Platform for WindowsPlatform {
         unimplemented!()
     }
 
-    // todo!("windows")
     fn reveal_path(&self, path: &Path) {
-        unimplemented!()
+        let Ok(file_full_path) = path.canonicalize() else {
+            log::error!("unable to parse file path");
+            return;
+        };
+        self.background_executor()
+            .spawn(async move {
+                let Some(path) = file_full_path.to_str() else {
+                    return;
+                };
+                if path.is_empty() {
+                    return;
+                }
+                open_target(path);
+            })
+            .detach();
     }
 
     fn on_become_active(&self, callback: Box<dyn FnMut()>) {
@@ -365,11 +399,20 @@ impl Platform for WindowsPlatform {
     }
 
     fn os_version(&self) -> Result<SemanticVersion> {
-        Ok(SemanticVersion {
-            major: 1,
-            minor: 0,
-            patch: 0,
-        })
+        let mut info = unsafe { std::mem::zeroed() };
+        let status = unsafe { RtlGetVersion(&mut info) };
+        if status.is_ok() {
+            Ok(SemanticVersion {
+                major: info.dwMajorVersion as _,
+                minor: info.dwMinorVersion as _,
+                patch: info.dwBuildNumber as _,
+            })
+        } else {
+            Err(anyhow::anyhow!(
+                "unable to get Windows version: {}",
+                std::io::Error::last_os_error()
+            ))
+        }
     }
 
     fn app_version(&self) -> Result<SemanticVersion> {
@@ -385,14 +428,28 @@ impl Platform for WindowsPlatform {
         Err(anyhow!("not yet implemented"))
     }
 
-    // todo!("windows")
     fn local_timezone(&self) -> UtcOffset {
-        UtcOffset::from_hms(9, 0, 0).unwrap()
+        let mut info = unsafe { std::mem::zeroed() };
+        let ret = unsafe { GetTimeZoneInformation(&mut info) };
+        if ret == TIME_ZONE_ID_INVALID {
+            log::error!(
+                "Unable to get local timezone: {}",
+                std::io::Error::last_os_error()
+            );
+            return UtcOffset::UTC;
+        }
+        // Windows treat offset as:
+        // UTC = localtime + offset
+        // so we add a minus here
+        let hours = -info.Bias / 60;
+        let minutes = -info.Bias % 60;
+
+        UtcOffset::from_hms(hours as _, minutes as _, 0).unwrap()
     }
 
-    // todo!("windows")
     fn double_click_interval(&self) -> Duration {
-        Duration::from_millis(100)
+        let millis = unsafe { GetDoubleClickTime() };
+        Duration::from_millis(millis as _)
     }
 
     // todo!("windows")
@@ -400,8 +457,31 @@ impl Platform for WindowsPlatform {
         Err(anyhow!("not yet implemented"))
     }
 
-    // todo!("windows")
-    fn set_cursor_style(&self, style: CursorStyle) {}
+    fn set_cursor_style(&self, style: CursorStyle) {
+        let handle = match style {
+            CursorStyle::IBeam | CursorStyle::IBeamCursorForVerticalLayout => unsafe {
+                load_cursor(IDC_IBEAM)
+            },
+            CursorStyle::Crosshair => unsafe { load_cursor(IDC_CROSS) },
+            CursorStyle::PointingHand | CursorStyle::DragLink => unsafe { load_cursor(IDC_HAND) },
+            CursorStyle::ResizeLeft | CursorStyle::ResizeRight | CursorStyle::ResizeLeftRight => unsafe {
+                load_cursor(IDC_SIZEWE)
+            },
+            CursorStyle::ResizeUp | CursorStyle::ResizeDown | CursorStyle::ResizeUpDown => unsafe {
+                load_cursor(IDC_SIZENS)
+            },
+            CursorStyle::OperationNotAllowed => unsafe { load_cursor(IDC_NO) },
+            _ => unsafe { load_cursor(IDC_ARROW) },
+        };
+        if handle.is_err() {
+            log::error!(
+                "Error loading cursor image: {}",
+                std::io::Error::last_os_error()
+            );
+            return;
+        }
+        let _ = unsafe { SetCursor(HCURSOR(handle.unwrap().0)) };
+    }
 
     // todo!("windows")
     fn should_auto_hide_scrollbars(&self) -> bool {
@@ -437,3 +517,23 @@ impl Platform for WindowsPlatform {
         Task::ready(Err(anyhow!("register_url_scheme unimplemented")))
     }
 }
+
+unsafe fn load_cursor(name: PCWSTR) -> Result<HANDLE> {
+    LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED).map_err(|e| anyhow!(e))
+}
+
+fn open_target(target: &str) {
+    unsafe {
+        let ret = ShellExecuteW(
+            None,
+            windows::core::w!("open"),
+            &HSTRING::from(target),
+            None,
+            None,
+            SW_SHOWDEFAULT,
+        );
+        if ret.0 <= 32 {
+            log::error!("Unable to open target: {}", std::io::Error::last_os_error());
+        }
+    }
+}