|
diff --git a/plugins/windows/permissions/default.toml b/plugins/windows/permissions/default.toml
index d72b93cd53..fed8586181 100644
--- a/plugins/windows/permissions/default.toml
+++ b/plugins/windows/permissions/default.toml
@@ -11,4 +11,8 @@ permissions = [
"allow-window-navigate",
"allow-window-emit-navigate",
"allow-window-is-visible",
+ "allow-window-set-overlay-bounds",
+ "allow-window-remove-overlay-bounds",
+ "allow-set-fake-window-bounds",
+ "allow-remove-fake-window",
]
diff --git a/plugins/windows/permissions/schemas/schema.json b/plugins/windows/permissions/schemas/schema.json
index 763425f6cd..c8e1f37278 100644
--- a/plugins/windows/permissions/schemas/schema.json
+++ b/plugins/windows/permissions/schemas/schema.json
@@ -294,6 +294,30 @@
"PermissionKind": {
"type": "string",
"oneOf": [
+ {
+ "description": "Enables the remove_fake_window command without any pre-configured scope.",
+ "type": "string",
+ "const": "allow-remove-fake-window",
+ "markdownDescription": "Enables the remove_fake_window command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the remove_fake_window command without any pre-configured scope.",
+ "type": "string",
+ "const": "deny-remove-fake-window",
+ "markdownDescription": "Denies the remove_fake_window command without any pre-configured scope."
+ },
+ {
+ "description": "Enables the set_fake_window_bounds command without any pre-configured scope.",
+ "type": "string",
+ "const": "allow-set-fake-window-bounds",
+ "markdownDescription": "Enables the set_fake_window_bounds command without any pre-configured scope."
+ },
+ {
+ "description": "Denies the set_fake_window_bounds command without any pre-configured scope.",
+ "type": "string",
+ "const": "deny-set-fake-window-bounds",
+ "markdownDescription": "Denies the set_fake_window_bounds command without any pre-configured scope."
+ },
{
"description": "Enables the window_close command without any pre-configured scope.",
"type": "string",
@@ -451,10 +475,10 @@
"markdownDescription": "Denies the window_show command without any pre-configured scope."
},
{
- "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-window-show`\n- `allow-window-hide`\n- `allow-window-destroy`\n- `allow-window-position`\n- `allow-window-resize-default`\n- `allow-window-get-floating`\n- `allow-window-set-floating`\n- `allow-window-navigate`\n- `allow-window-emit-navigate`\n- `allow-window-is-visible`",
+ "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-window-show`\n- `allow-window-hide`\n- `allow-window-destroy`\n- `allow-window-position`\n- `allow-window-resize-default`\n- `allow-window-get-floating`\n- `allow-window-set-floating`\n- `allow-window-navigate`\n- `allow-window-emit-navigate`\n- `allow-window-is-visible`\n- `allow-window-set-overlay-bounds`\n- `allow-window-remove-overlay-bounds`\n- `allow-set-fake-window-bounds`\n- `allow-remove-fake-window`",
"type": "string",
"const": "default",
- "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-window-show`\n- `allow-window-hide`\n- `allow-window-destroy`\n- `allow-window-position`\n- `allow-window-resize-default`\n- `allow-window-get-floating`\n- `allow-window-set-floating`\n- `allow-window-navigate`\n- `allow-window-emit-navigate`\n- `allow-window-is-visible`"
+ "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-window-show`\n- `allow-window-hide`\n- `allow-window-destroy`\n- `allow-window-position`\n- `allow-window-resize-default`\n- `allow-window-get-floating`\n- `allow-window-set-floating`\n- `allow-window-navigate`\n- `allow-window-emit-navigate`\n- `allow-window-is-visible`\n- `allow-window-set-overlay-bounds`\n- `allow-window-remove-overlay-bounds`\n- `allow-set-fake-window-bounds`\n- `allow-remove-fake-window`"
}
]
}
diff --git a/plugins/windows/src/commands.rs b/plugins/windows/src/commands.rs
index 679e8df34d..373bf1b0a9 100644
--- a/plugins/windows/src/commands.rs
+++ b/plugins/windows/src/commands.rs
@@ -1,4 +1,5 @@
-use crate::{HyprWindow, KnownPosition, WindowsPluginExt};
+use crate::{HyprWindow, KnownPosition, WindowsPluginExt, FakeWindowBounds, OverlayBound};
+use std::collections::HashMap;
#[tauri::command]
#[specta::specta]
@@ -119,29 +120,28 @@ pub async fn window_emit_navigate(
Ok(())
}
-#[tauri::command]
-#[specta::specta]
-pub async fn window_set_overlay_bounds(
- window: tauri::Window,
- state: tauri::State<'_, crate::OverlayState>,
+async fn update_bounds(
+ window: &tauri::Window,
+ state: &tauri::State<'_, FakeWindowBounds>,
name: String,
- bounds: crate::OverlayBound,
+ bounds: OverlayBound,
) -> Result<(), String> {
- let mut state = state.bounds.write().await;
+ #[cfg(debug_assertions)]
+ println!("Setting bounds for {}: {:?}", name, bounds);
+ let mut state = state.0.write().await;
let map = state.entry(window.label().to_string()).or_default();
map.insert(name, bounds);
-
+ #[cfg(debug_assertions)]
+ println!("Total bounds for window {}: {}", window.label(), map.len());
Ok(())
}
-#[tauri::command]
-#[specta::specta]
-pub async fn window_remove_overlay_bounds(
- window: tauri::Window,
- state: tauri::State<'_, crate::OverlayState>,
+async fn remove_bounds(
+ window: &tauri::Window,
+ state: &tauri::State<'_, FakeWindowBounds>,
name: String,
) -> Result<(), String> {
- let mut state = state.bounds.write().await;
+ let mut state = state.0.write().await;
let Some(map) = state.get_mut(window.label()) else {
return Ok(());
};
@@ -154,3 +154,45 @@ pub async fn window_remove_overlay_bounds(
Ok(())
}
+
+#[tauri::command]
+#[specta::specta]
+pub async fn window_set_overlay_bounds(
+ window: tauri::Window,
+ state: tauri::State<'_, FakeWindowBounds>,
+ name: String,
+ bounds: OverlayBound,
+) -> Result<(), String> {
+ update_bounds(&window, &state, name, bounds).await
+}
+
+#[tauri::command]
+#[specta::specta]
+pub async fn window_remove_overlay_bounds(
+ window: tauri::Window,
+ state: tauri::State<'_, FakeWindowBounds>,
+ name: String,
+) -> Result<(), String> {
+ remove_bounds(&window, &state, name).await
+}
+
+#[tauri::command]
+#[specta::specta]
+pub async fn set_fake_window_bounds(
+ window: tauri::Window,
+ name: String,
+ bounds: OverlayBound,
+ state: tauri::State<'_, FakeWindowBounds>,
+) -> Result<(), String> {
+ update_bounds(&window, &state, name, bounds).await
+}
+
+#[tauri::command]
+#[specta::specta]
+pub async fn remove_fake_window(
+ window: tauri::Window,
+ name: String,
+ state: tauri::State<'_, FakeWindowBounds>,
+) -> Result<(), String> {
+ remove_bounds(&window, &state, name).await
+}
diff --git a/plugins/windows/src/ext.rs b/plugins/windows/src/ext.rs
index 7756553d01..cc2b18c2c9 100644
--- a/plugins/windows/src/ext.rs
+++ b/plugins/windows/src/ext.rs
@@ -353,8 +353,9 @@ impl HyprWindow {
.min_inner_size(900.0, 600.0)
.build()?,
Self::Control => {
- let window = self
- .window_builder(app, "/app/control")
+ let mut builder = WebviewWindow::builder(app, self.label(), WebviewUrl::App("/app/control".into()))
+ .title("")
+ .disable_drag_drop_handler()
.maximized(false)
.resizable(false)
.fullscreen(false)
@@ -369,32 +370,58 @@ impl HyprWindow {
)
.skip_taskbar(true)
.position(0.0, 0.0)
- .transparent(true)
- .build()?;
+ .transparent(true);
+
+ #[cfg(target_os = "macos")]
+ {
+ builder = builder
+ .title_bar_style(tauri::TitleBarStyle::Overlay)
+ .hidden_title(true);
+ }
+
+ #[cfg(not(target_os = "macos"))]
+ {
+ builder = builder.decorations(false);
+ }
+
+ let window = builder.build()?;
#[cfg(target_os = "macos")]
{
app.run_on_main_thread({
let window = window.clone();
-
- #[allow(deprecated)]
move || {
- use tauri_nspanel::cocoa::appkit::{NSWindowCollectionBehavior,NSMainMenuWindowLevel};
- use tauri_nspanel::WebviewWindowExt as NSPanelWebviewWindowExt;
-
- if let Ok(panel) = window.to_panel() {
- panel.set_level(NSMainMenuWindowLevel);
- panel.set_collection_behaviour(
- NSWindowCollectionBehavior::NSWindowCollectionBehaviorTransient
- | NSWindowCollectionBehavior::NSWindowCollectionBehaviorMoveToActiveSpace
- | NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
- | NSWindowCollectionBehavior::NSWindowCollectionBehaviorIgnoresCycle,
- );
-
+ use tauri_nspanel::cocoa::base::{id, YES};
+ use tauri_nspanel::cocoa::appkit::{NSWindow, NSWindowButton};
+ use tauri_nspanel::objc::{msg_send, sel, sel_impl};
+
+ // Hide traffic lights using cocoa APIs
+ if let Ok(ns_window) = window.ns_window() {
+ unsafe {
+ let ns_window: id = ns_window as *mut std::ffi::c_void as id;
+
+ // Get and hide the standard window buttons only
+ let close_button: id = NSWindow::standardWindowButton_(ns_window, NSWindowButton::NSWindowCloseButton);
+ let miniaturize_button: id = NSWindow::standardWindowButton_(ns_window, NSWindowButton::NSWindowMiniaturizeButton);
+ let zoom_button: id = NSWindow::standardWindowButton_(ns_window, NSWindowButton::NSWindowZoomButton);
+
+ if !close_button.is_null() {
+ let _: () = msg_send![close_button, setHidden: YES];
+ }
+ if !miniaturize_button.is_null() {
+ let _: () = msg_send![miniaturize_button, setHidden: YES];
+ }
+ if !zoom_button.is_null() {
+ let _: () = msg_send![zoom_button, setHidden: YES];
+ }
+
+ // Make title bar transparent instead of changing style mask
+ let _: () = msg_send![ns_window, setTitlebarAppearsTransparent: YES];
+ let _: () = msg_send![ns_window, setMovableByWindowBackground: YES];
+ }
}
}
- })
- .ok();
+ }).ok();
}
crate::spawn_overlay_listener(app.clone(), window.clone());
diff --git a/plugins/windows/src/lib.rs b/plugins/windows/src/lib.rs
index 8429e470df..ef1a2327d7 100644
--- a/plugins/windows/src/lib.rs
+++ b/plugins/windows/src/lib.rs
@@ -8,6 +8,7 @@ pub use errors::*;
pub use events::*;
pub use ext::*;
use overlay::*;
+pub use overlay::{FakeWindowBounds, OverlayBound};
const PLUGIN_NAME: &str = "windows";
@@ -47,6 +48,8 @@ fn make_specta_builder() -> tauri_specta::Builder {
commands::window_is_visible,
commands::window_set_overlay_bounds,
commands::window_remove_overlay_bounds,
+ commands::set_fake_window_bounds,
+ commands::remove_fake_window,
])
.error_handling(tauri_specta::ErrorHandlingMode::Throw)
}
@@ -69,6 +72,11 @@ pub fn init() -> tauri::plugin::TauriPlugin {
app.manage(state);
}
+ {
+ let fake_bounds_state = FakeWindowBounds::default();
+ app.manage(fake_bounds_state);
+ }
+
Ok(())
})
.build()
diff --git a/plugins/windows/src/overlay.rs b/plugins/windows/src/overlay.rs
index b789e6b38c..62af308d8c 100644
--- a/plugins/windows/src/overlay.rs
+++ b/plugins/windows/src/overlay.rs
@@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc, time::Duration};
use tauri::{AppHandle, Manager, WebviewWindow};
use tokio::{sync::RwLock, time::sleep};
-#[derive(Debug, Default, serde::Serialize, serde::Deserialize, specta::Type)]
+#[derive(Debug, Default, serde::Serialize, serde::Deserialize, specta::Type, Clone, Copy)]
pub struct OverlayBound {
pub x: f64,
pub y: f64,
@@ -15,19 +15,41 @@ pub struct OverlayState {
pub bounds: Arc>>>,
}
+pub struct FakeWindowBounds(pub Arc>>>);
+
+impl Default for FakeWindowBounds {
+ fn default() -> Self {
+ Self(Arc::new(RwLock::new(HashMap::new())))
+ }
+}
+
pub fn spawn_overlay_listener(app: AppHandle, window: WebviewWindow) {
window.set_ignore_cursor_events(true).ok();
tokio::spawn(async move {
- let state = app.state::();
+ let state = app.state::();
+ let mut last_ignore_state = true;
+ let mut last_focus_state = false;
loop {
- sleep(Duration::from_millis(1000 / 20)).await;
+ // Reduced polling frequency from 20Hz to 10Hz
+ sleep(Duration::from_millis(1000 / 10)).await;
- let map = state.bounds.read().await;
+ let map = state.0.read().await;
let Some(windows) = map.get(window.label()) else {
- window.set_ignore_cursor_events(true).ok();
+ if !last_ignore_state {
+ window.set_ignore_cursor_events(true).ok();
+ last_ignore_state = true;
+ }
+ continue;
+ };
+
+ if windows.is_empty() {
+ if !last_ignore_state {
+ window.set_ignore_cursor_events(true).ok();
+ last_ignore_state = true;
+ }
continue;
};
@@ -36,18 +58,24 @@ pub fn spawn_overlay_listener(app: AppHandle, window: WebviewWindow) {
window.cursor_position(),
window.scale_factor(),
) else {
- let _ = window.set_ignore_cursor_events(true);
+ if !last_ignore_state {
+ let _ = window.set_ignore_cursor_events(true);
+ last_ignore_state = true;
+ }
continue;
};
let mut ignore = true;
- for bounds in windows.values() {
+ for (name, bounds) in windows.iter() {
let x_min = (window_position.x as f64) + bounds.x * scale_factor;
let x_max = (window_position.x as f64) + (bounds.x + bounds.width) * scale_factor;
let y_min = (window_position.y as f64) + bounds.y * scale_factor;
let y_max = (window_position.y as f64) + (bounds.y + bounds.height) * scale_factor;
+ // println!("Checking bounds for {}: mouse({}, {}) vs bounds({}-{}, {}-{})",
+ // name, mouse_position.x, mouse_position.y, x_min, x_max, y_min, y_max);
+
if mouse_position.x >= x_min
&& mouse_position.x <= x_max
&& mouse_position.y >= y_min
@@ -58,15 +86,18 @@ pub fn spawn_overlay_listener(app: AppHandle, window: WebviewWindow) {
}
}
- window.set_ignore_cursor_events(ignore).ok();
+ // Only update cursor events if state changed
+ if ignore != last_ignore_state {
+ window.set_ignore_cursor_events(ignore).ok();
+ last_ignore_state = ignore;
+ }
let focused = window.is_focused().unwrap_or(false);
- if !ignore {
- if !focused {
- window.set_focus().ok();
- }
- } else if focused {
- window.set_ignore_cursor_events(ignore).ok();
+ if !ignore && !focused && !last_focus_state {
+ window.set_focus().ok();
+ last_focus_state = true;
+ } else if ignore && last_focus_state {
+ last_focus_state = false;
}
}
});
|