Fix window stoplights

This commit is contained in:
Gregory Schier
2024-05-29 22:59:34 -07:00
parent 8cd3961f87
commit 14b3abf76c
17 changed files with 321 additions and 357 deletions

View File

@@ -69,10 +69,7 @@ mod plugin;
mod render;
mod updates;
mod window_menu;
#[cfg(target_os = "macos")]
mod mac;
#[cfg(target_os = "windows")]
mod win;
mod tauri_plugin_traffic_light;
async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
let pool = &*db.lock().await;
@@ -1502,8 +1499,14 @@ async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String>
}
#[tauri::command]
async fn cmd_new_window(window: WebviewWindow, url: &str) -> Result<(), String> {
create_window(&window.app_handle(), Some(url));
async fn cmd_new_window(app_handle: AppHandle, url: &str) -> Result<(), String> {
create_window(&app_handle, url);
Ok(())
}
#[tauri::command]
async fn cmd_new_nested_window(window: WebviewWindow, url: &str, label: &str, title: &str) -> Result<(), String> {
create_nested_window(&window, label, url, title);
Ok(())
}
@@ -1532,12 +1535,13 @@ async fn cmd_check_for_updates(
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(tauri_plugin_window_state::Builder::default().with_denylist(&["settings"]).build())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_traffic_light::init())
.plugin(
tauri_plugin_log::Builder::default()
.targets([
@@ -1663,6 +1667,7 @@ pub fn run() {
cmd_list_http_responses,
cmd_list_workspaces,
cmd_metadata,
cmd_new_nested_window,
cmd_new_window,
cmd_request_to_curl,
cmd_dismiss_notification,
@@ -1691,7 +1696,7 @@ pub fn run() {
.run(|app_handle, event| {
match event {
RunEvent::Ready => {
create_window(app_handle, None);
create_window(app_handle, "/");
let h = app_handle.clone();
tauri::async_runtime::spawn(async move {
let info = analytics::track_launch_event(&h).await;
@@ -1737,7 +1742,84 @@ fn is_dev() -> bool {
}
}
fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
fn create_nested_window(window: &WebviewWindow, label: &str, url: &str, title: &str) -> WebviewWindow {
info!("Create new nested window label={label}");
let pos = window.outer_position().unwrap();
let mut win_builder = tauri::WebviewWindowBuilder::new(
window,
label,
WebviewUrl::App(url.into()),
)
.resizable(true)
.fullscreen(false)
.disable_drag_drop_handler() // Required for frontend Dnd on windows
.title(title)
.parent(&window)
.unwrap()
.position(
(pos.x + 20) as f64,
(pos.y + 20) as f64,
)
.inner_size(
500.0f64,
300.0f64,
);
// Add macOS-only things
#[cfg(target_os = "macos")]
{
win_builder = win_builder
.hidden_title(true)
.title_bar_style(TitleBarStyle::Overlay);
}
// Add non-MacOS things
#[cfg(not(target_os = "macos"))]
{
win_builder = win_builder.decorations(false);
}
let win = win_builder.build().expect("failed to build window");
// Tauri doesn't support shadows when hiding decorations, so we add our own
// #[cfg(any(windows, target_os = "macos"))]
// set_shadow(&win, true).unwrap();
let win2 = win.clone();
win.on_menu_event(move |w, event| {
if !w.is_focused().unwrap() {
return;
}
match event.id().0.as_str() {
"quit" => exit(0),
"close" => _ = w.close(),
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
"zoom_in" => w.emit("zoom_in", true).unwrap(),
"zoom_out" => w.emit("zoom_out", true).unwrap(),
"settings" => w.emit("settings", true).unwrap(),
"refresh" => win2.eval("location.reload()").unwrap(),
"open_feedback" => {
_ = win2
.app_handle()
.shell()
.open("https://yaak.canny.io", None)
}
"toggle_devtools" => {
if win2.is_devtools_open() {
win2.close_devtools();
} else {
win2.open_devtools();
}
}
_ => {}
}
});
win
}
fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
let menu = app_menu(handle).unwrap();
// This causes the window to not be clickable (in AppImage), so disable on Linux
@@ -1745,11 +1827,12 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
handle.set_menu(menu).expect("Failed to set app menu");
let window_num = handle.webview_windows().len();
let window_id = format!("wnd_{}", window_num);
let label = format!("wnd_{}", window_num);
info!("Create new window label={label}");
let mut win_builder = tauri::WebviewWindowBuilder::new(
handle,
window_id,
WebviewUrl::App(url.unwrap_or_default().into()),
label,
WebviewUrl::App(url.into()),
)
.resizable(true)
.fullscreen(false)
@@ -1774,16 +1857,12 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
// Add non-MacOS things
#[cfg(not(target_os = "macos"))]
{
// Doesn't seem to work from Rust, here, so we do it in JS
win_builder = win_builder.decorations(false);
// Doesn't seem to work from Rust, here, so we do it in main.tsx
// win_builder = win_builder.decorations(false);
}
let win = win_builder.build().expect("failed to build window");
// Tauri doesn't support shadows when hiding decorations, so we add our own
// #[cfg(any(windows, target_os = "macos"))]
// set_shadow(&win, true).unwrap();
let win2 = win.clone();
win.on_menu_event(move |w, event| {
if !w.is_focused().unwrap() {
@@ -1792,12 +1871,11 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
match event.id().0.as_str() {
"quit" => exit(0),
"close" => w.close().unwrap(),
"close" => _ = w.close(),
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
"zoom_in" => w.emit("zoom_in", true).unwrap(),
"zoom_out" => w.emit("zoom_out", true).unwrap(),
"settings" => w.emit("settings", true).unwrap(),
"duplicate_request" => w.emit("duplicate_request", true).unwrap(),
"refresh" => win2.eval("location.reload()").unwrap(),
"open_feedback" => {
_ = win2
@@ -1816,19 +1894,6 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
}
});
#[cfg(target_os = "macos")]
{
use mac::setup_mac_window;
let mut m_win = win.clone();
setup_mac_window(&mut m_win);
};
#[cfg(target_os = "windows")]
{
use win::setup_win_window;
let mut m_win = win.clone();
setup_win_window(&mut m_win);
}
win
}

View File

@@ -1,89 +1,44 @@
// Borrowed from our friends at Hoppscotch
// https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs
use hex_color::HexColor;
use tauri::{Manager, WebviewWindow};
struct UnsafeWindowHandle(*mut std::ffi::c_void);
unsafe impl Send for UnsafeWindowHandle {}
unsafe impl Sync for UnsafeWindowHandle {}
use objc::{msg_send, sel, sel_impl};
use rand::{distributions::Alphanumeric, Rng};
use tauri::{Manager, plugin::{Builder, TauriPlugin}, Runtime, Window};
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
#[cfg(target_os = "macos")]
fn update_window_title(window: &WebviewWindow, title: String) {
use cocoa::{
appkit::NSWindow,
base::nil,
foundation::NSString,
};
struct UnsafeWindowHandle(*mut std::ffi::c_void);
unsafe impl Send for UnsafeWindowHandle {}
unsafe impl Sync for UnsafeWindowHandle {}
unsafe {
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
let _ = window.run_on_main_thread(move || {
let win_title = NSString::alloc(nil).init_str(&title);
let handle = window_handle;
NSWindow::setTitle_(handle.0 as cocoa::base::id, win_title);
set_window_controls_pos(handle.0 as cocoa::base::id);
});
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("traffic_light_positioner")
.on_window_ready(|window| {
#[cfg(target_os = "macos")]
setup_traffic_light_positioner(window);
return;
})
.build()
}
#[cfg(target_os = "macos")]
fn update_window_theme(window: &WebviewWindow, color: HexColor) {
use cocoa::{
appkit::{NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow},
base::nil,
foundation::NSString,
};
let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) {
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect;
let ns_window = ns_window_handle.0 as cocoa::base::id;
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)
};
let title = NSString::alloc(nil).init_str("My Title");
NSWindow::setTitle_(handle.0 as cocoa::base::id, title);
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
set_window_controls_pos(handle.0 as cocoa::base::id);
});
}
}
#[cfg(target_os = "macos")]
fn set_window_controls_pos(window: cocoa::base::id) {
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 close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let miniaturize =
ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
let zoom = ns_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 + WINDOW_CONTROL_PAD_Y;
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;
title_bar_rect.origin.y = NSView::frame(ns_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];
@@ -91,7 +46,7 @@ fn set_window_controls_pos(window: cocoa::base::id) {
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button);
rect.origin.x = WINDOW_CONTROL_PAD_X + (i as f64 * space_between);
rect.origin.x = x + (i as f64 * space_between);
button.setFrameOrigin(rect.origin);
}
}
@@ -99,12 +54,12 @@ fn set_window_controls_pos(window: cocoa::base::id) {
#[cfg(target_os = "macos")]
#[derive(Debug)]
struct AppState {
window: WebviewWindow,
struct WindowState<R: Runtime> {
window: Window<R>,
}
#[cfg(target_os = "macos")]
pub fn setup_mac_window(window: &mut WebviewWindow) {
pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
use cocoa::delegate;
use cocoa::appkit::NSWindow;
use cocoa::base::{BOOL, id};
@@ -112,16 +67,30 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
use objc::runtime::{Object, Sel};
use std::ffi::c_void;
fn with_app_state<F: FnOnce(&mut AppState) -> T, T>(this: &Object, func: F) {
// Do the initial positioning
position_traffic_lights(
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
);
// Ensure they stay in place while resizing the window.
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
this: &Object,
func: F,
) {
let ptr = unsafe {
let x: *mut c_void = *this.get_ivar("yaakApp");
&mut *(x as *mut AppState)
let x: *mut c_void = *this.get_ivar("app_box");
&mut *(x as *mut WindowState<R>)
};
func(ptr);
}
unsafe {
let ns_win = window.ns_window().unwrap() as id;
let ns_win = window
.ns_window()
.expect("NS Window should exist to mount traffic light delegate.")
as id;
let current_delegate: id = ns_win.delegate();
@@ -137,12 +106,21 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
let _: () = msg_send![super_del, windowWillClose: notification];
}
}
extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) {
extern "C" fn on_window_did_resize<R: Runtime>(this: &Object, _cmd: Sel, notification: id) {
unsafe {
with_app_state(&*this, |state| {
let id = state.window.ns_window().unwrap() as id;
with_window_state(&*this, |state: &mut WindowState<R>| {
let id = state
.window
.ns_window()
.expect("NS window should exist on state to handle resize")
as id;
set_window_controls_pos(id);
#[cfg(target_os = "macos")]
position_traffic_lights(
UnsafeWindowHandle(id as *mut std::ffi::c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
);
});
let super_del: id = *this.get_ivar("super_delegate");
@@ -222,43 +200,75 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
}
}
extern "C" fn on_window_did_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
extern "C" fn on_window_did_enter_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_app_state(&*this, |state| {
state.window.emit("did-enter-fullscreen", ()).unwrap();
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("did-enter-fullscreen", ())
.expect("Failed to emit event");
});
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) {
extern "C" fn on_window_will_enter_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_app_state(&*this, |state| {
state.window.emit("will-enter-fullscreen", ()).unwrap();
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("will-enter-fullscreen", ())
.expect("Failed to emit event");
});
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) {
extern "C" fn on_window_did_exit_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_app_state(&*this, |state| {
state.window.emit("did-exit-fullscreen", ()).unwrap();
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("did-exit-fullscreen", ())
.expect("Failed to emit event");
let id = state.window.ns_window().unwrap() as id;
set_window_controls_pos(id);
let id = state.window.ns_window().expect("Failed to emit event") as id;
position_traffic_lights(
UnsafeWindowHandle(id as *mut std::ffi::c_void),
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) {
extern "C" fn on_window_will_exit_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_app_state(&*this, |state| {
state.window.emit("will-exit-fullscreen", ()).unwrap();
with_window_state(&*this, |state: &mut WindowState<R>| {
state
.window
.emit("will-exit-fullscreen", ())
.expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
@@ -299,44 +309,29 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
}
}
// 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 w = window.clone();
let app_state = AppState { window: w };
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
set_window_controls_pos(ns_win);
let window_label = window.label().to_string();
ns_win.setDelegate_(delegate!("MainWindowDelegate", {
let app_state = WindowState { window };
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
let random_str: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(20)
.map(char::from)
.collect();
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
// delegate with the same name.
let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str);
ns_win.setDelegate_(delegate!(&delegate_name, {
window: id = ns_win,
yaakApp: *mut c_void = app_box,
app_box: *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),
(windowDidResize:) => on_window_did_resize::<R> 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),
@@ -347,36 +342,13 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
(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),
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen::<R> as extern fn(&Object, Sel, id),
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> 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)
}))
}
let app = window.app_handle();
let window = window.clone();
// Control window theme based on app update_window
let window_for_theme = window.clone();
app.listen_any("yaak_bg_changed", move |ev| {
let payload = serde_json::from_str::<&str>(ev.payload())
.unwrap()
.trim();
let color = HexColor::parse_rgb(payload).unwrap();
update_window_theme(&window_for_theme, color);
});
let window_for_title = window.clone();
app.listen_any("yaak_title_changed", move |ev| {
let title = serde_json::from_str::<&str>(ev.payload())
.unwrap()
.trim();
update_window_title(&window_for_title, title.to_string());
});
}

View File

@@ -19,6 +19,7 @@ pub struct YaakUpdater {
pub enum UpdateMode {
Stable,
Beta,
Alpha,
}
impl Display for UpdateMode {
@@ -26,6 +27,7 @@ impl Display for UpdateMode {
let s = match self {
UpdateMode::Stable => "stable",
UpdateMode::Beta => "beta",
UpdateMode::Alpha => "alpha",
};
write!(f, "{}", s)
}
@@ -35,6 +37,7 @@ impl UpdateMode {
pub fn new(mode: &str) -> UpdateMode {
match mode {
"beta" => UpdateMode::Beta,
"alpha" => UpdateMode::Alpha,
_ => UpdateMode::Stable,
}
}

View File

@@ -1,79 +0,0 @@
// Borrowed from our friends at Hoppscotch
// https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs
use std::mem::transmute;
use hex_color::HexColor;
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::{WTA_NONCLIENT, WTNCA_NODRAWICON, WTNCA_NOMIRRORHELP, WTNCA_NOSYSMENU};
use windows::Win32::UI::Controls::SetWindowThemeAttribute;
use windows::Win32::UI::Controls::WTNCA_NODRAWCAPTION;
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::<BOOL>().try_into().unwrap()
).unwrap();
DwmSetWindowAttribute(
HWND(hwnd.0),
DWMWA_CAPTION_COLOR,
ptr::addr_of!(final_color) as *const c_void,
size_of::<COLORREF>().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 };
SetWindowThemeAttribute(
HWND(hwnd.0),
WTA_NONCLIENT,
ptr::addr_of!(options) as *const c_void,
size_of::<WinThemeAttribute>().try_into().unwrap()
).unwrap();
}
}
#[cfg(target_os = "windows")]
pub fn setup_win_window(window: &mut WebviewWindow) {
let win_handle = window.hwnd().unwrap();
let win_clone = win_handle.clone();
window.listen_global("yaak_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(23, 23, 23));
}