Window title working again

This commit is contained in:
Gregory Schier
2024-05-30 00:11:55 -07:00
parent b942c22b20
commit e4022cf532
8 changed files with 378 additions and 81 deletions

View File

@@ -2216,6 +2216,13 @@
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -2244,6 +2251,13 @@
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -2498,6 +2512,69 @@
"clipboard-manager:deny-write-text"
]
},
{
"description": "deep-link:default -> Allows reading the opened deep link via the get_current command",
"type": "string",
"enum": [
"deep-link:default"
]
},
{
"description": "deep-link:allow-get-current -> Enables the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-get-current"
]
},
{
"description": "deep-link:allow-is-registered -> Enables the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-is-registered"
]
},
{
"description": "deep-link:allow-register -> Enables the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-register"
]
},
{
"description": "deep-link:allow-unregister -> Enables the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:allow-unregister"
]
},
{
"description": "deep-link:deny-get-current -> Denies the get_current command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-get-current"
]
},
{
"description": "deep-link:deny-is-registered -> Denies the is_registered command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-is-registered"
]
},
{
"description": "deep-link:deny-register -> Denies the register command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-register"
]
},
{
"description": "deep-link:deny-unregister -> Denies the unregister command without any pre-configured scope.",
"type": "string",
"enum": [
"deep-link:deny-unregister"
]
},
{
"type": "string",
"enum": [
@@ -5323,6 +5400,13 @@
"shell:allow-open"
]
},
{
"description": "shell:allow-spawn -> Enables the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:allow-spawn"
]
},
{
"description": "shell:allow-stdin-write -> Enables the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -5351,6 +5435,13 @@
"shell:deny-open"
]
},
{
"description": "shell:deny-spawn -> Denies the spawn command without any pre-configured scope.",
"type": "string",
"enum": [
"shell:deny-spawn"
]
},
{
"description": "shell:deny-stdin-write -> Denies the stdin_write command without any pre-configured scope.",
"type": "string",
@@ -5533,6 +5624,13 @@
"updater:allow-check"
]
},
{
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download"
]
},
{
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
"type": "string",
@@ -5540,6 +5638,13 @@
"updater:allow-download-and-install"
]
},
{
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-install"
]
},
{
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
"type": "string",
@@ -5547,6 +5652,13 @@
"updater:deny-check"
]
},
{
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download"
]
},
{
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
"type": "string",
@@ -5554,6 +5666,13 @@
"updater:deny-download-and-install"
]
},
{
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-install"
]
},
{
"description": "webview:default -> Default permissions for the plugin.",
"type": "string",
@@ -5897,6 +6016,13 @@
"window:allow-minimize"
]
},
{
"description": "window:allow-monitor-from-point -> Enables the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:allow-monitor-from-point"
]
},
{
"description": "window:allow-outer-position -> Enables the outer_position command without any pre-configured scope.",
"type": "string",
@@ -6331,6 +6457,13 @@
"window:deny-minimize"
]
},
{
"description": "window:deny-monitor-from-point -> Denies the monitor_from_point command without any pre-configured scope.",
"type": "string",
"enum": [
"window:deny-monitor-from-point"
]
},
{
"description": "window:deny-outer-position -> Denies the outer_position command without any pre-configured scope.",
"type": "string",

View File

@@ -69,7 +69,10 @@ mod plugin;
mod render;
mod updates;
mod window_menu;
mod tauri_plugin_traffic_light;
#[cfg(target_os = "macos")]
mod tauri_plugin_mac_window;
#[cfg(target_os = "windows")]
mod tauri_plugin_windows_window;
async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
let pool = &*db.lock().await;
@@ -1533,41 +1536,49 @@ async fn cmd_check_for_updates(
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_window_state::Builder::default().with_denylist(&["settings"]).build())
.plugin(tauri_plugin_window_state::Builder::default().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([
Target::new(TargetKind::Stdout),
Target::new(TargetKind::LogDir { file_name: None }),
Target::new(TargetKind::Webview),
])
.level_for("cookie_store", log::LevelFilter::Info)
.level_for("h2", log::LevelFilter::Info)
.level_for("hyper", log::LevelFilter::Info)
.level_for("hyper_rustls", log::LevelFilter::Info)
.level_for("reqwest", log::LevelFilter::Info)
.level_for("sqlx", log::LevelFilter::Warn)
.level_for("tao", log::LevelFilter::Info)
.level_for("tokio_util", log::LevelFilter::Info)
.level_for("tonic", log::LevelFilter::Info)
.level_for("tower", log::LevelFilter::Info)
.level_for("tracing", log::LevelFilter::Info)
.with_colors(ColoredLevelConfig::default())
.level(if is_dev() {
log::LevelFilter::Trace
} else {
log::LevelFilter::Info
})
.build(),
)
.plugin(tauri_plugin_fs::init());
#[cfg(target_os = "windows")] {
builder = builder.plugin(tauri_plugin_windows_window::init());
}
#[cfg(target_os = "macos")] {
builder = builder.plugin(tauri_plugin_mac_window::init());
}
builder.plugin(
tauri_plugin_log::Builder::default()
.targets([
Target::new(TargetKind::Stdout),
Target::new(TargetKind::LogDir { file_name: None }),
Target::new(TargetKind::Webview),
])
.level_for("cookie_store", log::LevelFilter::Info)
.level_for("h2", log::LevelFilter::Info)
.level_for("hyper", log::LevelFilter::Info)
.level_for("hyper_rustls", log::LevelFilter::Info)
.level_for("reqwest", log::LevelFilter::Info)
.level_for("sqlx", log::LevelFilter::Warn)
.level_for("tao", log::LevelFilter::Info)
.level_for("tokio_util", log::LevelFilter::Info)
.level_for("tonic", log::LevelFilter::Info)
.level_for("tower", log::LevelFilter::Info)
.level_for("tracing", log::LevelFilter::Info)
.with_colors(ColoredLevelConfig::default())
.level(if is_dev() {
log::LevelFilter::Trace
} else {
log::LevelFilter::Info
})
.build(),
)
.setup(|app| {
let app_data_dir = app.path().app_data_dir().unwrap();
let app_config_dir = app.path().app_config_dir().unwrap();
@@ -1744,10 +1755,9 @@ fn is_dev() -> bool {
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,
format!("nested_{}_{}", window.label(), label),
WebviewUrl::App(url.into()),
)
.resizable(true)
@@ -1756,14 +1766,7 @@ fn create_nested_window(window: &WebviewWindow, label: &str, url: &str, title: &
.title(title)
.parent(&window)
.unwrap()
.position(
(pos.x + 20) as f64,
(pos.y + 20) as f64,
)
.inner_size(
500.0f64,
300.0f64,
);
.inner_size(700.0f64, 600.0f64);
// Add macOS-only things
#[cfg(target_os = "macos")]
@@ -1827,7 +1830,7 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
handle.set_menu(menu).expect("Failed to set app menu");
let window_num = handle.webview_windows().len();
let label = format!("wnd_{}", window_num);
let label = format!("main_{}", window_num);
info!("Create new window label={label}");
let mut win_builder = tauri::WebviewWindowBuilder::new(
handle,

View File

@@ -1,3 +1,4 @@
use hex_color::HexColor;
use objc::{msg_send, sel, sel_impl};
use rand::{distributions::Alphanumeric, Rng};
use tauri::{Manager, plugin::{Builder, TauriPlugin}, Runtime, Window};
@@ -6,23 +7,108 @@ const WINDOW_CONTROL_PAD_X: f64 = 13.0;
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
struct UnsafeWindowHandle(*mut std::ffi::c_void);
unsafe impl Send for UnsafeWindowHandle {}
unsafe impl Sync for UnsafeWindowHandle {}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("traffic_light_positioner")
Builder::new("mac_window")
.on_window_ready(|window| {
#[cfg(target_os = "macos")]
setup_traffic_light_positioner(window);
#[cfg(target_os = "macos")] {
setup_traffic_light_positioner(&window);
let h = window.app_handle();
let window_for_theme = window.clone();
h.listen("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.clone(), color);
});
let window_for_title = window.clone();
h.listen("yaak_title_changed", move |ev| {
let payload = serde_json::from_str::<&str>(ev.payload())
.unwrap()
.trim();
update_window_title(window_for_title.clone(), payload.to_string());
});
}
return;
})
.build()
}
#[cfg(target_os = "macos")]
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) {
fn update_window_title<R: Runtime>(window: Window<R>, title: String) {
use cocoa::{
appkit::NSWindow,
base::nil,
foundation::NSString,
};
unsafe {
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
let window2 = window.clone();
let label = window.label().to_string();
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);
position_traffic_lights(
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
label,
);
});
}}
#[cfg(target_os = "macos")]
fn update_window_theme<R: Runtime>(window: Window<R>, color: HexColor) {
use cocoa::appkit::{
NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow,
};
let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
let label = window.label().to_string();
unsafe {
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
let window2 = window.clone();
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);
position_traffic_lights(
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
label,
);
});
}
}
#[cfg(target_os = "macos")]
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64, label: String) {
if label.starts_with("nested_") {
return;
}
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect;
let ns_window = ns_window_handle.0 as cocoa::base::id;
unsafe {
let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
@@ -59,7 +145,7 @@ struct WindowState<R: Runtime> {
}
#[cfg(target_os = "macos")]
pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
use cocoa::delegate;
use cocoa::appkit::NSWindow;
use cocoa::base::{BOOL, id};
@@ -67,11 +153,11 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
use objc::runtime::{Object, Sel};
use std::ffi::c_void;
// 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,
window.label().to_string(),
);
// Ensure they stay in place while resizing the window.
@@ -86,6 +172,7 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
func(ptr);
}
unsafe {
let ns_win = window
.ns_window()
@@ -115,11 +202,11 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
.expect("NS window should exist on state to handle resize")
as id;
#[cfg(target_os = "macos")]
position_traffic_lights(
UnsafeWindowHandle(id as *mut std::ffi::c_void),
UnsafeWindowHandle(id as *mut c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
state.window.label().to_string(),
);
});
@@ -248,9 +335,10 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
let id = state.window.ns_window().expect("Failed to emit event") as id;
position_traffic_lights(
UnsafeWindowHandle(id as *mut std::ffi::c_void),
UnsafeWindowHandle(id as *mut c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
state.window.label().to_string(),
);
});
@@ -309,10 +397,10 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
}
}
// Are we deallocing this properly ? (I miss safe Rust :( )
// Are we de-allocing this properly ? (I miss safe Rust :( )
let window_label = window.label().to_string();
let app_state = WindowState { window };
let app_state = WindowState { window: window.clone() };
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
let random_str: String = rand::thread_rng()
.sample_iter(&Alphanumeric)

View File

@@ -0,0 +1,89 @@
use hex_color::HexColor;
use tauri::{ Runtime, Window};
use std::mem::transmute;
use std::{ptr, ffi::c_void, mem::size_of};
use tauri::plugin::{Builder, TauriPlugin};
use windows::Win32::UI::Controls::{WTA_NONCLIENT, WTNCA_NODRAWICON, WTNCA_NOSYSMENU, WTNCA_NOMIRRORHELP};
use windows::Win32::UI::Controls::SetWindowThemeAttribute;
use windows::Win32::UI::Controls::WTNCA_NODRAWCAPTION;
use windows::Win32::Graphics::Dwm::DWMWA_CAPTION_COLOR;
use windows::Win32::Foundation::COLORREF;
use windows::Win32::Foundation::BOOL;
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
use windows::Win32::Foundation::HWND;
use windows::Win32::Graphics::Dwm::{DWMWA_USE_IMMERSIVE_DARK_MODE};
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("windows_window")
.on_window_ready(|window| {
#[cfg(target_os = "windows")]
setup_win_window(window);
return;
})
.build()
}
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<R: Runtime>(window: Window<R>) {
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);
});
}

View File

@@ -32,7 +32,7 @@ export const SettingsDialog = ({ fullscreen }: Props) => {
{fullscreen && (
<div
data-tauri-drag-region
className="h-[38px] bg-background-highlight-secondary flex items-center justify-center border-b border-background-highlight"
className="h-[27px] bg-background-highlight-secondary flex items-center justify-center border-b border-background-highlight"
>
Settings
</div>

View File

@@ -14,7 +14,6 @@ import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { useDialog } from './DialogContext';
import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
import { SettingsDialog } from './Settings/SettingsDialog';
export function SettingsDropdown() {
const importData = useImportData();
@@ -26,13 +25,12 @@ export function SettingsDropdown() {
const routes = useAppRoutes();
const workspaceId = useActiveWorkspaceId();
const showSettings = () => {
dialog.show({
id: 'settings',
size: 'dynamic',
noScroll: true,
noPadding: true,
render: () => <SettingsDialog />,
const showSettings = async () => {
if (!workspaceId) return;
await invoke('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId }),
label: 'settings',
title: 'Yaak Settings',
});
};
@@ -63,20 +61,6 @@ export function SettingsDropdown() {
});
},
},
{
key: 'foo',
label: 'Foo',
hotKeyAction: 'hotkeys.showHelp',
leftSlot: <Icon icon="keyboard" />,
onSelect: async () => {
if (!workspaceId) return;
await invoke('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId }),
label: 'settings',
title: 'Yaak Settings',
});
},
},
{
key: 'import-data',
label: 'Import Data',

View File

@@ -40,7 +40,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
<div className="pointer-events-none">
<RecentRequestsDropdown />
</div>
<div className="flex-1 flex gap-1 items-center h-full justify-end pointer-events-none">
<div className="flex-1 flex gap-1 items-center h-full justify-end pointer-events-none pr-0.5">
<ImportCurlButton />
<SettingsDropdown />
{(osInfo?.osType === 'linux' || osInfo?.osType === 'windows') && (

View File

@@ -5,6 +5,7 @@ import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useOsInfo } from './useOsInfo';
import { emit } from '@tauri-apps/api/event';
export function useSyncWindowTitle() {
const activeRequest = useActiveRequest();
@@ -26,13 +27,12 @@ export function useSyncWindowTitle() {
newTitle += ` ${fallbackRequestName(activeRequest)}`;
}
// TODO: This resets the stoplight position so we can't use it on macOS yet. Perhaps
// we can
// TODO: This resets the stoplight position so we can't use it on macOS yet. So we send
// a custom command instead
if (osInfo?.osType !== 'macos') {
console.log('DO IT', osInfo?.osType);
getCurrent().setTitle(newTitle).catch(console.error);
} else {
// emit('yaak_title_changed', newTitle).catch(console.error);
emit('yaak_title_changed', newTitle).catch(console.error);
}
}, [activeEnvironment, activeRequest, activeWorkspace, osInfo?.osType]);
}