diff --git a/index.html b/index.html
index 8ae63972..ab267537 100644
--- a/index.html
+++ b/index.html
@@ -121,6 +121,10 @@
}
+
+
+
+
Pachtop
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 8e367910..4817e2b3 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -84,7 +84,10 @@ name = "app"
version = "0.7.1"
dependencies = [
"chrono",
+ "cocoa 0.25.0",
+ "hex_color",
"log",
+ "objc",
"serde",
"serde_json",
"sysinfo",
@@ -93,8 +96,11 @@ dependencies = [
"tauri-plugin-autostart",
"tauri-plugin-log",
"tauri-plugin-store",
+ "tauri-plugin-window-state",
"tokio",
"ts-rs",
+ "windows 0.52.0",
+ "winver",
]
[[package]]
@@ -347,6 +353,15 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -652,6 +667,22 @@ dependencies = [
"objc",
]
+[[package]]
+name = "cocoa"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
+dependencies = [
+ "bitflags 1.3.2",
+ "block",
+ "cocoa-foundation",
+ "core-foundation",
+ "core-graphics 0.23.2",
+ "foreign-types 0.5.0",
+ "libc",
+ "objc",
+]
+
[[package]]
name = "cocoa-foundation"
version = "0.1.2"
@@ -1740,6 +1771,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+[[package]]
+name = "hex_color"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0"
+dependencies = [
+ "rand 0.8.5",
+]
+
[[package]]
name = "home"
version = "0.5.9"
@@ -3944,7 +3984,7 @@ dependencies = [
"bitflags 1.3.2",
"cairo-rs",
"cc",
- "cocoa",
+ "cocoa 0.24.1",
"core-foundation",
"core-graphics 0.22.3",
"crossbeam-channel",
@@ -4027,7 +4067,7 @@ dependencies = [
"anyhow",
"base64 0.21.7",
"bytes",
- "cocoa",
+ "cocoa 0.24.1",
"dirs-next",
"dunce",
"embed_plist",
@@ -4179,6 +4219,21 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "tauri-plugin-window-state"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa47eaa4047a7b51064caff32f0c6282e2c5adc6ceacdd493ecf1b01fa4b0eaa"
+dependencies = [
+ "bincode",
+ "bitflags 2.5.0",
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "thiserror",
+]
+
[[package]]
name = "tauri-runtime"
version = "0.14.3"
@@ -4207,7 +4262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef2af45aeb15b1cadb4ca91248423f4438a0864b836298cecb436892afbfdff4"
dependencies = [
"arboard",
- "cocoa",
+ "cocoa 0.24.1",
"gtk",
"percent-encoding",
"rand 0.8.5",
@@ -5123,6 +5178,16 @@ dependencies = [
"windows-targets 0.48.5",
]
+[[package]]
+name = "windows"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
+dependencies = [
+ "windows-core 0.52.0",
+ "windows-targets 0.52.5",
+]
+
[[package]]
name = "windows"
version = "0.56.0"
@@ -5553,6 +5618,15 @@ dependencies = [
"windows-sys 0.48.0",
]
+[[package]]
+name = "winver"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e0e7162b9e282fd75a0a832cce93994bdb21208d848a418cd05a5fdd9b9ab33"
+dependencies = [
+ "windows 0.48.0",
+]
+
[[package]]
name = "wl-clipboard-rs"
version = "0.8.1"
@@ -5581,7 +5655,7 @@ checksum = "3c689900e022bb67b0d9728fb817bbef2b9da7ebd6c79aade5f0c32fe4c18c73"
dependencies = [
"base64 0.13.1",
"block",
- "cocoa",
+ "cocoa 0.24.1",
"core-graphics 0.22.3",
"crossbeam-channel",
"dunce",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index c3e47fef..84f44d20 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -19,14 +19,28 @@ chrono = {version = "0.4.23", features= ["serde"] }
# rusqlite = { version = "0.28.0", features = ["bundled", "chrono"] }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
-tauri = { version = "1.6.5", features = ["api-all", "system-tray", "updater"] }
+tauri = { version = "1.6.5", features = ["api-all", "system-tray", "updater", "window-start-dragging"] }
sysinfo = "0.29.11"
tauri-plugin-log = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
+tauri-plugin-window-state = "0.1.0"
log = "^0.4"
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
ts-rs = "8.1"
+hex_color = "3.0.0"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+cocoa = "0.25.0"
+objc = "0.2.7"
+
+[target.'cfg(target_os = "windows")'.dependencies]
+windows = { version = "0.52.0", features = [
+ "Win32_Graphics_Dwm",
+ "Win32_Foundation",
+ "Win32_UI_Controls",
+] }
+winver = "1"
# surrealdb = { version = "1.5.0" }
diff --git a/src-tauri/src/mac/mod.rs b/src-tauri/src/mac/mod.rs
new file mode 100644
index 00000000..61b63f17
--- /dev/null
+++ b/src-tauri/src/mac/mod.rs
@@ -0,0 +1 @@
+pub mod window;
diff --git a/src-tauri/src/mac/window.rs b/src-tauri/src/mac/window.rs
new file mode 100644
index 00000000..1330bfba
--- /dev/null
+++ b/src-tauri/src/mac/window.rs
@@ -0,0 +1,390 @@
+use hex_color::HexColor;
+use tauri::{App, Manager, Runtime, Window};
+
+// If anything breaks on macOS, this should be the place which is broken
+// We have to override Tauri (Tao) 's built-in NSWindowDelegate implementation with a
+// custom implementation so we can emit events on full screen mode changes.
+// Our custom implementation tries to mock the Tauri implementation. So please do refer to the relevant parts
+
+// Apple's NSWindowDelegate reference: https://developer.apple.com/documentation/appkit/nswindowdelegate?language=objc
+// Tao's Window Delegate Implementation: https://github.com/tauri-apps/tao/blob/dev/src/platform_impl/macos/window_delegate.rs
+
+#[allow(dead_code)]
+pub enum ToolbarThickness {
+ Thick,
+ Medium,
+ Thin,
+}
+
+const WINDOW_CONTROL_PAD_X: f64 = 15.0;
+const WINDOW_CONTROL_PAD_Y: f64 = 23.0;
+
+pub trait WindowExt {
+ #[cfg(target_os = "macos")]
+ fn set_transparent_titlebar(&self);
+}
+
+#[cfg(target_os = "macos")]
+unsafe fn set_transparent_titlebar(id: cocoa::base::id) {
+ use cocoa::appkit::NSWindow;
+
+ id.setTitlebarAppearsTransparent_(cocoa::base::YES);
+ id.setTitleVisibility_(cocoa::appkit::NSWindowTitleVisibility::NSWindowTitleHidden);
+}
+
+struct UnsafeWindowHandle(*mut std::ffi::c_void);
+unsafe impl Send for UnsafeWindowHandle {}
+unsafe impl Sync for UnsafeWindowHandle {}
+
+#[cfg(target_os = "macos")]
+fn update_window_theme(window: &tauri::Window, color: HexColor) {
+ use cocoa::appkit::{
+ NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow,
+ };
+
+ let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
+
+ unsafe {
+ let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
+
+ let _ = window.run_on_main_thread(move || {
+ let handle = window_handle;
+
+ let selected_appearance = if brightness >= 128 {
+ NSAppearance(NSAppearanceNameVibrantLight)
+ } else {
+ NSAppearance(NSAppearanceNameVibrantDark)
+ };
+
+ NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
+ set_window_controls_pos(
+ handle.0 as cocoa::base::id,
+ WINDOW_CONTROL_PAD_X,
+ WINDOW_CONTROL_PAD_Y,
+ );
+ });
+ }
+}
+
+#[cfg(target_os = "macos")]
+fn set_window_controls_pos(window: cocoa::base::id, x: f64, y: f64) {
+ use cocoa::{
+ appkit::{NSView, NSWindow, NSWindowButton},
+ foundation::NSRect,
+ };
+
+ unsafe {
+ let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
+ let miniaturize = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
+ let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
+
+ let title_bar_container_view = close.superview().superview();
+
+ let close_rect: NSRect = msg_send![close, frame];
+ let button_height = close_rect.size.height;
+
+ let title_bar_frame_height = button_height + y;
+ let mut title_bar_rect = NSView::frame(title_bar_container_view);
+ title_bar_rect.size.height = title_bar_frame_height;
+ title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
+ let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
+
+ let window_buttons = vec![close, miniaturize, zoom];
+ let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
+
+ for (i, button) in window_buttons.into_iter().enumerate() {
+ let mut rect: NSRect = NSView::frame(button);
+ rect.origin.x = x + (i as f64 * space_between);
+ button.setFrameOrigin(rect.origin);
+ }
+ }
+}
+
+impl WindowExt for Window {
+ #[cfg(target_os = "macos")]
+ fn set_transparent_titlebar(&self) {
+ unsafe {
+ let id = self.ns_window().unwrap() as cocoa::base::id;
+
+ set_transparent_titlebar(id);
+
+ set_window_controls_pos(id, WINDOW_CONTROL_PAD_X, WINDOW_CONTROL_PAD_Y);
+ }
+ }
+}
+
+#[cfg(target_os = "macos")]
+#[derive(Debug)]
+struct HoppAppState {
+ window: Window,
+}
+
+#[cfg(target_os = "macos")]
+pub fn setup_mac_window(app: &mut App) {
+ use cocoa::appkit::NSWindow;
+ use cocoa::base::{id, BOOL};
+ use cocoa::foundation::NSUInteger;
+ use objc::runtime::{Object, Sel};
+ use std::ffi::c_void;
+
+ fn with_hopp_app T, T>(this: &Object, func: F) {
+ let ptr = unsafe {
+ let x: *mut c_void = *this.get_ivar("hoppApp");
+ &mut *(x as *mut HoppAppState)
+ };
+ func(ptr);
+ }
+
+ let window = app.get_window("main").unwrap();
+
+ unsafe {
+ let ns_win = window.ns_window().unwrap() as id;
+
+ let current_delegate: id = ns_win.delegate();
+
+ extern "C" fn on_window_should_close(this: &Object, _cmd: Sel, sender: id) -> BOOL {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ msg_send![super_del, windowShouldClose: sender]
+ }
+ }
+ extern "C" fn on_window_will_close(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowWillClose: notification];
+ }
+ }
+ extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ with_hopp_app(&*this, |state| {
+ let id = state.window.ns_window().unwrap() as id;
+
+ set_window_controls_pos(id, WINDOW_CONTROL_PAD_X, WINDOW_CONTROL_PAD_Y);
+ });
+
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowDidResize: notification];
+ }
+ }
+ extern "C" fn on_window_did_move(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowDidMove: notification];
+ }
+ }
+ extern "C" fn on_window_did_change_backing_properties(
+ this: &Object,
+ _cmd: Sel,
+ notification: id,
+ ) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
+ }
+ }
+ extern "C" fn on_window_did_become_key(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowDidBecomeKey: notification];
+ }
+ }
+ extern "C" fn on_window_did_resign_key(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowDidResignKey: notification];
+ }
+ }
+ extern "C" fn on_dragging_entered(this: &Object, _cmd: Sel, notification: id) -> BOOL {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ msg_send![super_del, draggingEntered: notification]
+ }
+ }
+ extern "C" fn on_prepare_for_drag_operation(
+ this: &Object,
+ _cmd: Sel,
+ notification: id,
+ ) -> BOOL {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ msg_send![super_del, prepareForDragOperation: notification]
+ }
+ }
+ extern "C" fn on_perform_drag_operation(this: &Object, _cmd: Sel, sender: id) -> BOOL {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ msg_send![super_del, performDragOperation: sender]
+ }
+ }
+ extern "C" fn on_conclude_drag_operation(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, concludeDragOperation: notification];
+ }
+ }
+ extern "C" fn on_dragging_exited(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, draggingExited: notification];
+ }
+ }
+ extern "C" fn on_window_will_use_full_screen_presentation_options(
+ this: &Object,
+ _cmd: Sel,
+ window: id,
+ proposed_options: NSUInteger,
+ ) -> NSUInteger {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
+ }
+ }
+ extern "C" fn on_window_did_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ with_hopp_app(&*this, |state| {
+ state.window.emit("did-enter-fullscreen", ()).unwrap();
+ });
+
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
+ }
+ }
+ extern "C" fn on_window_will_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ with_hopp_app(&*this, |state| {
+ state.window.emit("will-enter-fullscreen", ()).unwrap();
+ });
+
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
+ }
+ }
+ extern "C" fn on_window_did_exit_full_screen(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ with_hopp_app(&*this, |state| {
+ state.window.emit("did-exit-fullscreen", ()).unwrap();
+
+ let id = state.window.ns_window().unwrap() as id;
+ set_window_controls_pos(id, WINDOW_CONTROL_PAD_X, WINDOW_CONTROL_PAD_Y);
+ });
+
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowDidExitFullScreen: notification];
+ }
+ }
+ extern "C" fn on_window_will_exit_full_screen(this: &Object, _cmd: Sel, notification: id) {
+ unsafe {
+ with_hopp_app(&*this, |state| {
+ state.window.emit("will-exit-fullscreen", ()).unwrap();
+ });
+
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
+ }
+ }
+ extern "C" fn on_window_did_fail_to_enter_full_screen(
+ this: &Object,
+ _cmd: Sel,
+ window: id,
+ ) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
+ }
+ }
+ extern "C" fn on_effective_appearance_did_change(
+ this: &Object,
+ _cmd: Sel,
+ notification: id,
+ ) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
+ }
+ }
+ extern "C" fn on_effective_appearance_did_changed_on_main_thread(
+ this: &Object,
+ _cmd: Sel,
+ notification: id,
+ ) {
+ unsafe {
+ let super_del: id = *this.get_ivar("super_delegate");
+ let _: () = msg_send![
+ super_del,
+ effectiveAppearanceDidChangedOnMainThread: notification
+ ];
+ }
+ }
+
+ // extern fn on_dealloc(this: &Object, cmd: Sel) {
+ // unsafe {
+ // let super_del: id = *this.get_ivar("super_delegate");
+ // let _: () = msg_send![super_del, dealloc];
+ // }
+ // }
+
+ // extern fn on_mark_is_checking_zoomed_in(this: &Object, cmd: Sel) {
+ // unsafe {
+ // let super_del: id = *this.get_ivar("super_delegate");
+ // let _: () = msg_send![super_del, markIsCheckingZoomedIn];
+ // }
+ // }
+
+ // extern fn on_clear_is_checking_zoomed_in(this: &Object, cmd: Sel) {
+ // unsafe {
+ // let super_del: id = *this.get_ivar("super_delegate");
+ // let _: () = msg_send![super_del, clearIsCheckingZoomedIn];
+ // }
+ // }
+
+ // Are we deallocing this properly ? (I miss safe Rust :( )
+ let app_state = HoppAppState { window };
+ let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
+
+ ns_win.setDelegate_(delegate!("MainWindowDelegate", {
+ window: id = ns_win,
+ hoppApp: *mut c_void = app_box,
+ toolbar: id = cocoa::base::nil,
+ super_delegate: id = current_delegate,
+ // (dealloc) => on_dealloc as extern fn(&Object, Sel),
+ // (markIsCheckingZoomedIn) => on_mark_is_checking_zoomed_in as extern fn(&Object, Sel),
+ // (clearIsCheckingZoomedIn) => on_clear_is_checking_zoomed_in as extern fn(&Object, Sel),
+ (windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
+ (windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
+ (windowDidResize:) => on_window_did_resize as extern fn(&Object, Sel, id),
+ (windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
+ (windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
+ (windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
+ (windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
+ (draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
+ (prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
+ (performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
+ (concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
+ (draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
+ (window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
+ (windowDidEnterFullScreen:) => on_window_did_enter_full_screen as extern fn(&Object, Sel, id),
+ (windowWillEnterFullScreen:) => on_window_will_enter_full_screen as extern fn(&Object, Sel, id),
+ (windowDidExitFullScreen:) => on_window_did_exit_full_screen as extern fn(&Object, Sel, id),
+ (windowWillExitFullScreen:) => on_window_will_exit_full_screen as extern fn(&Object, Sel, id),
+ (windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
+ (effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
+ (effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
+ }))
+ }
+
+ app.get_window("main").unwrap().set_transparent_titlebar();
+
+ let window_handle = app.get_window("main").unwrap();
+ update_window_theme(&window_handle, HexColor::WHITE);
+
+ // Control window theme based on app update_window
+ app.listen_global("hopp-bg-changed", move |ev| {
+ let payload = serde_json::from_str::<&str>(ev.payload().unwrap())
+ .unwrap()
+ .trim();
+
+ let color = HexColor::parse_rgb(payload).unwrap();
+
+ update_window_theme(&window_handle, color);
+ });
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 75e94bdf..682dbf03 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -3,6 +3,20 @@
windows_subsystem = "windows"
)]
+#[cfg(target_os = "macos")]
+#[macro_use]
+extern crate cocoa;
+
+#[cfg(target_os = "macos")]
+#[macro_use]
+extern crate objc;
+
+#[cfg(target_os = "macos")]
+mod mac;
+
+#[cfg(target_os = "windows")]
+mod win;
+
mod app;
mod metrics;
mod models;
@@ -24,6 +38,8 @@ fn build_and_run_app(app: AppState) {
let auto_start_plugin = tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, Some(vec![]));
+ let window_state_plugin = tauri_plugin_window_state::Builder::default().build();
+
let system_tray = SystemTray::new()
.with_menu(SystemTrayMenu::new().add_item(CustomMenuItem::new("quit".to_string(), "Quit")));
@@ -31,6 +47,7 @@ fn build_and_run_app(app: AppState) {
.plugin(log_plugin)
.plugin(store_plugin)
.plugin(auto_start_plugin)
+ .plugin(window_state_plugin)
.setup(|app| {
let window = app.get_window("main").unwrap();
let state = AppState::new();
@@ -49,6 +66,20 @@ fn build_and_run_app(app: AppState) {
}
});
+ if cfg!(target_os = "macos") {
+ #[cfg(target_os = "macos")]
+ use mac::window::setup_mac_window;
+
+ #[cfg(target_os = "macos")]
+ setup_mac_window(app);
+ } else if cfg!(target_os = "windows") {
+ #[cfg(target_os = "windows")]
+ use win::window::setup_win_window;
+
+ #[cfg(target_os = "windows")]
+ setup_win_window(app);
+ }
+
Ok(())
})
.system_tray(system_tray)
diff --git a/src-tauri/src/win/mod.rs b/src-tauri/src/win/mod.rs
new file mode 100644
index 00000000..61b63f17
--- /dev/null
+++ b/src-tauri/src/win/mod.rs
@@ -0,0 +1 @@
+pub mod window;
diff --git a/src-tauri/src/win/window.rs b/src-tauri/src/win/window.rs
new file mode 100644
index 00000000..7d8c0da5
--- /dev/null
+++ b/src-tauri/src/win/window.rs
@@ -0,0 +1,96 @@
+use hex_color::HexColor;
+use tauri::App;
+use tauri::Manager;
+
+use std::mem::transmute;
+use std::{ffi::c_void, mem::size_of, ptr};
+
+use windows::Win32::UI::Controls::{
+ WTA_NONCLIENT, WTNCA_NODRAWICON, WTNCA_NOMIRRORHELP, WTNCA_NOSYSMENU,
+};
+
+use windows::Win32::Foundation::BOOL;
+use windows::Win32::Foundation::COLORREF;
+use windows::Win32::Foundation::HWND;
+use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
+use windows::Win32::Graphics::Dwm::DWMWA_CAPTION_COLOR;
+use windows::Win32::Graphics::Dwm::DWMWA_USE_IMMERSIVE_DARK_MODE;
+use windows::Win32::UI::Controls::SetWindowThemeAttribute;
+use windows::Win32::UI::Controls::WTNCA_NODRAWCAPTION;
+
+use winver::WindowsVersion;
+
+fn hex_color_to_colorref(color: HexColor) -> COLORREF {
+ // TODO: Remove this unsafe, This operation doesn't need to be unsafe!
+ unsafe { COLORREF(transmute::<[u8; 4], u32>([color.r, color.g, color.b, 0])) }
+}
+
+struct WinThemeAttribute {
+ flag: u32,
+ mask: u32,
+}
+
+#[cfg(target_os = "windows")]
+fn update_bg_color(hwnd: &HWND, bg_color: HexColor) {
+ let use_dark_mode = BOOL::from(true);
+
+ let final_color = hex_color_to_colorref(bg_color);
+
+ unsafe {
+ DwmSetWindowAttribute(
+ HWND(hwnd.0),
+ DWMWA_USE_IMMERSIVE_DARK_MODE,
+ ptr::addr_of!(use_dark_mode) as *const c_void,
+ size_of::().try_into().unwrap(),
+ )
+ .unwrap();
+ }
+
+ let version = WindowsVersion::detect().unwrap();
+ if version >= WindowsVersion::new(10, 0, 22000) {
+ unsafe {
+ DwmSetWindowAttribute(
+ HWND(hwnd.0),
+ DWMWA_CAPTION_COLOR,
+ ptr::addr_of!(final_color) as *const c_void,
+ size_of::().try_into().unwrap(),
+ )
+ .unwrap();
+ }
+ }
+
+ let flags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON;
+ let mask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON | WTNCA_NOSYSMENU | WTNCA_NOMIRRORHELP;
+ let options = WinThemeAttribute { flag: flags, mask };
+
+ unsafe {
+ SetWindowThemeAttribute(
+ HWND(hwnd.0),
+ WTA_NONCLIENT,
+ ptr::addr_of!(options) as *const c_void,
+ size_of::().try_into().unwrap(),
+ )
+ .unwrap();
+ }
+}
+
+#[cfg(target_os = "windows")]
+pub fn setup_win_window(app: &mut App) {
+ let window = app.get_window("main").unwrap();
+ let win_handle = window.hwnd().unwrap();
+
+ let win_clone = win_handle.clone();
+
+ //TODO: Update this to update based on theme
+ app.listen_global("hopp-bg-changed", move |ev| {
+ let payload = serde_json::from_str::<&str>(ev.payload().unwrap())
+ .unwrap()
+ .trim();
+
+ let color = HexColor::parse_rgb(payload).unwrap();
+
+ update_bg_color(&HWND(win_clone.0), color);
+ });
+
+ update_bg_color(&HWND(win_handle.0), HexColor::rgb(9, 9, 11));
+}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 89c16253..fe908328 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -111,7 +111,6 @@
"title": "pachtop",
"alwaysOnTop": false,
"width": 1920,
- "minWidth": 450,
"decorations": true
}
]