@@ -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",
@@ -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());
+ }
+ }
+}