Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 92 additions & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ tauri-plugin-global-shortcut = "2"
tauri-plugin-autostart = "2.5.1"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
regex-lite = "0.1.9"
tauri-plugin-liquid-glass = "0.1.6"

[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6"
objc2-foundation = { version = "0.3", features = ["NSProcessInfo", "NSString"] }
objc2-app-kit = { version = "0.3", features = ["NSEvent", "NSScreen", "NSGraphics"] }
objc2-foundation = { version = "0.3", features = ["NSKeyValueCoding", "NSProcessInfo", "NSString"] }
objc2-app-kit = { version = "0.3", features = ["NSClipView", "NSColor", "NSEvent", "NSGraphics", "NSScreen", "NSScrollView", "NSView", "NSWindow"] }
objc2-web-kit = { version = "0.3", features = ["WKPreferences", "WKWebView", "WKWebViewConfiguration"] }
1 change: 1 addition & 0 deletions src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"process:allow-restart",
"global-shortcut:default",
"autostart:default",
"liquid-glass:default",
"core:menu:default"
]
}
32 changes: 31 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::sync::{Arc, Mutex, OnceLock};
use serde::Serialize;
use tauri::Emitter;
use tauri_plugin_aptabase::EventTracker;
use tauri_plugin_liquid_glass::{GlassMaterialVariant, LiquidGlassConfig, LiquidGlassExt};
use tauri_plugin_log::{Target, TargetKind};
use uuid::Uuid;

Expand Down Expand Up @@ -199,6 +200,33 @@ fn hide_panel(app_handle: tauri::AppHandle) {
}
}

#[tauri::command]
fn set_liquid_glass_enabled(app_handle: tauri::AppHandle, enabled: bool) -> Result<(), String> {
use tauri::Manager;

let Some(window) = app_handle.get_webview_window("main") else {
return Ok(());
};

let config = if enabled {
LiquidGlassConfig {
corner_radius: 22.0,
variant: GlassMaterialVariant::Sidebar,
..Default::default()
}
} else {
LiquidGlassConfig {
enabled: false,
..Default::default()
}
};

app_handle
.liquid_glass()
.set_effect(&window, config)
.map_err(|error| error.to_string())
}

#[tauri::command]
fn open_devtools(#[allow(unused)] app_handle: tauri::AppHandle) {
#[cfg(debug_assertions)]
Expand Down Expand Up @@ -489,9 +517,11 @@ pub fn run() {
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_autostart::Builder::new().build())
.plugin(tauri_plugin_liquid_glass::init())
.invoke_handler(tauri::generate_handler![
init_panel,
hide_panel,
set_liquid_glass_enabled,
open_devtools,
start_probe_batch,
list_plugins,
Expand All @@ -505,7 +535,7 @@ pub fn run() {
#[cfg(target_os = "macos")]
{
app_nap::disable_app_nap();
webkit_config::disable_webview_suspension(app.handle());
webkit_config::configure_webview(app.handle());
}

use tauri::Manager;
Expand Down
12 changes: 7 additions & 5 deletions src-tauri/src/panel.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use tauri::{AppHandle, Manager, Position, Size};
use tauri_nspanel::{tauri_panel, CollectionBehavior, ManagerExt, PanelLevel, StyleMask, WebviewWindowExt};
use tauri_nspanel::{
CollectionBehavior, ManagerExt, PanelLevel, StyleMask, WebviewWindowExt, tauri_panel,
};

/// Macro to get existing panel or initialize it if needed.
/// Returns Option<Panel> - Some if panel is available, None on error.
Expand Down Expand Up @@ -174,7 +176,8 @@ pub fn position_panel_at_tray_icon(
None => {
log::warn!(
"No monitor found for cursor at ({:.0}, {:.0}), using primary",
mouse_x, mouse_y
mouse_x,
mouse_y
);
match window.primary_monitor() {
Ok(Some(m)) => m,
Expand Down Expand Up @@ -204,9 +207,8 @@ pub fn position_panel_at_tray_icon(
let panel_width = match (window.outer_size(), window.scale_factor()) {
(Ok(s), Ok(win_scale)) => s.width as f64 / win_scale,
_ => {
let conf: serde_json::Value =
serde_json::from_str(include_str!("../tauri.conf.json"))
.expect("tauri.conf.json must be valid JSON");
let conf: serde_json::Value = serde_json::from_str(include_str!("../tauri.conf.json"))
.expect("tauri.conf.json must be valid JSON");
conf["app"]["windows"][0]["width"]
.as_f64()
.expect("width must be set in tauri.conf.json")
Expand Down
52 changes: 40 additions & 12 deletions src-tauri/src/webkit_config.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,53 @@
//! WebKit configuration for disabling background suspension on macOS.
//! WebKit configuration for macOS panel behavior.
//!
//! By default, WebKit suspends JavaScript execution when the webview is not visible.
//! This module disables that behavior so auto-update timers continue to fire.
//! We keep JavaScript active while the panel is hidden and force the WKWebView
//! itself fully transparent so native liquid-glass can show through the app
//! container instead of only around it.

use tauri::Manager;

pub fn disable_webview_suspension(app_handle: &tauri::AppHandle) {
pub fn configure_webview(app_handle: &tauri::AppHandle) {
let Some(window) = app_handle.get_webview_window("main") else {
log::warn!("webkit_config: main window not found");
return;
};

if let Err(e) = window.with_webview(|webview| {
unsafe {
use objc2_web_kit::{WKInactiveSchedulingPolicy, WKWebView};
let wk_webview: &WKWebView = &*webview.inner().cast();
let config = wk_webview.configuration();
let prefs = config.preferences();
prefs.setInactiveSchedulingPolicy(WKInactiveSchedulingPolicy::None);
log::info!("WebKit inactiveSchedulingPolicy set to None");
if let Err(e) = window.with_webview(|webview| unsafe {
use objc2::sel;
use objc2_app_kit::NSColor;
use objc2_foundation::{NSNumber, NSObjectNSKeyValueCoding, NSObjectProtocol, ns_string};
use objc2_web_kit::{WKInactiveSchedulingPolicy, WKWebView};

let wk_webview: &WKWebView = &*webview.inner().cast();
let clear = NSColor::clearColor();
let no = NSNumber::numberWithBool(false);
let config = wk_webview.configuration();
let prefs = config.preferences();

prefs.setInactiveSchedulingPolicy(WKInactiveSchedulingPolicy::None);

config.setValue_forKey(Some(&no), ns_string!("drawsBackground"));
wk_webview.setValue_forKey(Some(&no), ns_string!("drawsBackground"));

if wk_webview.respondsToSelector(sel!(setUnderPageBackgroundColor:)) {
wk_webview.setUnderPageBackgroundColor(Some(&clear));
}

if let Some(scroll_view) = wk_webview.enclosingScrollView() {
scroll_view.setDrawsBackground(false);
scroll_view.setBackgroundColor(&clear);

let clip_view = scroll_view.contentView();
clip_view.setDrawsBackground(false);
clip_view.setBackgroundColor(&clear);
}

if let Some(ns_window) = wk_webview.window() {
ns_window.setOpaque(false);
ns_window.setBackgroundColor(Some(&clear));
}

log::info!("Configured transparent WKWebView and disabled inactive scheduling");
}) {
log::warn!("Failed to configure WebKit scheduling: {e}");
}
Expand Down
26 changes: 26 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -381,18 +381,44 @@ describe("App", () => {
// Dark
await userEvent.click(await screen.findByRole("radio", { name: "Dark" }))
expect(document.documentElement.classList.contains("dark")).toBe(true)
expect(document.documentElement.classList.contains("glass")).toBe(false)

// Light
await userEvent.click(await screen.findByRole("radio", { name: "Light" }))
expect(document.documentElement.classList.contains("dark")).toBe(false)
expect(document.documentElement.classList.contains("glass")).toBe(false)

// Glass
await userEvent.click(await screen.findByRole("radio", { name: "Glass" }))
expect(document.documentElement.classList.contains("dark")).toBe(false)
expect(document.documentElement.classList.contains("glass")).toBe(true)

// Back to system should subscribe to matchMedia changes
await userEvent.click(await screen.findByRole("radio", { name: "System" }))
expect(mq.addEventListener).toHaveBeenCalled()
expect(document.documentElement.classList.contains("glass")).toBe(false)

mmSpy.mockRestore()
})

it("syncs native liquid glass mode when running in tauri", async () => {
state.isTauriMock.mockReturnValue(true)

render(<App />)
const settingsButtons = await screen.findAllByRole("button", { name: "Settings" })
await userEvent.click(settingsButtons[0])

await userEvent.click(await screen.findByRole("radio", { name: "Glass" }))
await waitFor(() =>
expect(state.invokeMock).toHaveBeenCalledWith("set_liquid_glass_enabled", { enabled: true })
)

await userEvent.click(await screen.findByRole("radio", { name: "Light" }))
await waitFor(() =>
expect(state.invokeMock).toHaveBeenCalledWith("set_liquid_glass_enabled", { enabled: false })
)
})

it("loads plugins, normalizes settings, and renders overview", async () => {
state.isTauriMock.mockReturnValue(true)
render(<App />)
Expand Down
Loading
Loading