Add CEF runtime to Linux builds (#494)

This commit is contained in:
Gregory Schier
2026-07-03 14:22:47 -07:00
committed by GitHub
parent 1206d5889d
commit cc05fec59d
21 changed files with 1410 additions and 210 deletions
+3
View File
@@ -13,6 +13,8 @@ crate-type = ["staticlib", "cdylib", "lib"]
[features]
cargo-clippy = []
default = []
cef = ["tauri/cef"]
wry = ["tauri/wry", "tauri/x11", "tauri/dbus"]
updater = []
license = ["yaak-license"]
@@ -82,6 +84,7 @@ yaak-mac-window = { workspace = true }
yaak-models = { workspace = true }
yaak-plugins = { workspace = true }
yaak-sse = { workspace = true }
yaak-system-appearance = { workspace = true }
yaak-sync = { workspace = true }
yaak-templates = { workspace = true }
yaak-tls = { workspace = true }
+53 -7
View File
@@ -82,6 +82,14 @@ mod uri_scheme;
mod window_menu;
mod ws_ext;
#[cfg(not(any(feature = "cef", feature = "wry")))]
compile_error!("Enable one Tauri runtime feature: `cef` or `wry`.");
#[cfg(feature = "cef")]
type TauriRuntime = tauri::Cef;
#[cfg(all(not(feature = "cef"), feature = "wry"))]
type TauriRuntime = tauri::Wry;
fn setup_window_menu<R: Runtime>(win: &WebviewWindow<R>) -> Result<()> {
#[allow(unused_variables)]
let menu = window_menu::app_menu(win.app_handle())?;
@@ -150,6 +158,22 @@ fn setup_window_menu<R: Runtime>(win: &WebviewWindow<R>) -> Result<()> {
Ok(())
}
fn initial_appearance_script<R: Runtime>(app_handle: &AppHandle<R>) -> Option<String> {
use yaak_system_appearance::{Appearance, InitialAppearanceSource};
let settings = app_handle.db().get_settings();
let (appearance, source) = match settings.appearance.as_str() {
"dark" => (Appearance::Dark, InitialAppearanceSource::Settings),
"light" => (Appearance::Light, InitialAppearanceSource::Settings),
_ => (
yaak_system_appearance::system_appearance()?,
InitialAppearanceSource::LinuxSystem,
),
};
Some(yaak_system_appearance::initialization_script(appearance, source))
}
/// Extension trait for easily creating a PluginContext from a WebviewWindow
pub trait PluginContextExt<R: Runtime> {
fn plugin_context(&self) -> PluginContext;
@@ -177,7 +201,7 @@ struct AppMetaData {
}
#[tauri::command]
async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
async fn cmd_metadata<R: Runtime>(app_handle: AppHandle<R>) -> YaakResult<AppMetaData> {
let app_data_dir = app_handle.path().app_data_dir()?;
let app_log_dir = app_handle.path().app_log_dir()?;
let vendored_plugin_dir =
@@ -962,7 +986,7 @@ async fn cmd_send_ephemeral_request<R: Runtime>(
mut request: HttpRequest,
environment_id: Option<&str>,
cookie_jar_id: Option<&str>,
window: WebviewWindow,
window: WebviewWindow<R>,
app_handle: AppHandle<R>,
) -> YaakResult<HttpResponse> {
let response = HttpResponse::default();
@@ -1588,20 +1612,22 @@ async fn cmd_get_workspace_meta<R: Runtime>(
}
#[tauri::command]
async fn cmd_new_child_window(
parent_window: WebviewWindow,
async fn cmd_new_child_window<R: Runtime>(
parent_window: WebviewWindow<R>,
url: &str,
label: &str,
title: &str,
inner_size: (f64, f64),
) -> YaakResult<()> {
let use_native_titlebar = parent_window.app_handle().db().get_settings().use_native_titlebar;
let initialization_script = initial_appearance_script(&parent_window.app_handle());
let win = yaak_window::window::create_child_window(
&parent_window,
url,
label,
title,
inner_size,
initialization_script,
use_native_titlebar,
)?;
setup_window_menu(&win)?;
@@ -1609,9 +1635,15 @@ async fn cmd_new_child_window(
}
#[tauri::command]
async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> YaakResult<()> {
async fn cmd_new_main_window<R: Runtime>(app_handle: AppHandle<R>, url: &str) -> YaakResult<()> {
let use_native_titlebar = app_handle.db().get_settings().use_native_titlebar;
let win = yaak_window::window::create_main_window(&app_handle, url, use_native_titlebar)?;
let initialization_script = initial_appearance_script(&app_handle);
let win = yaak_window::window::create_main_window(
&app_handle,
url,
initialization_script,
use_native_titlebar,
)?;
setup_window_menu(&win)?;
Ok(())
}
@@ -1631,8 +1663,9 @@ async fn cmd_check_for_updates<R: Runtime>(
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
#[cfg_attr(feature = "cef", tauri::cef_entry_point)]
pub fn run() {
let mut builder = tauri::Builder::default().plugin(
let mut builder = tauri::Builder::<TauriRuntime>::default().plugin(
Builder::default()
.targets([
Target::new(TargetKind::Stdout),
@@ -1706,6 +1739,10 @@ pub fn run() {
app.state::<yaak_models::query_manager::QueryManager>().inner().clone();
let app_id = app.config().identifier.to_string();
app.manage(yaak_crypto::manager::EncryptionManager::new(query_manager, app_id));
#[cfg(target_os = "linux")]
if let Some(state) = yaak_system_appearance::watch(app.app_handle().clone()) {
app.manage(state);
}
{
let app_handle = app.app_handle().clone();
@@ -1894,9 +1931,11 @@ pub fn run() {
match event {
RunEvent::Ready => {
let use_native_titlebar = app_handle.db().get_settings().use_native_titlebar;
let initialization_script = initial_appearance_script(app_handle);
if let Ok(win) = yaak_window::window::create_main_window(
app_handle,
"/",
initialization_script,
use_native_titlebar,
) {
let _ = setup_window_menu(&win);
@@ -1917,6 +1956,13 @@ pub fn run() {
});
}
RunEvent::WindowEvent { event: WindowEvent::Focused(true), label, .. } => {
#[cfg(target_os = "linux")]
if let Some(state) =
app_handle.try_state::<yaak_system_appearance::SystemAppearanceState>()
{
yaak_system_appearance::emit_change(app_handle, &state);
}
if cfg!(feature = "updater") {
// Run update check whenever the window is focused
let w = app_handle.get_webview_window(&label).unwrap();
+2 -1
View File
@@ -6,7 +6,8 @@
"beforeBuildCommand": "npm --prefix ../.. run client:tauri-before-build",
"beforeDevCommand": "npm --prefix ../.. run client:tauri-before-dev",
"devUrl": "http://localhost:1420",
"frontendDist": "../../dist/apps/yaak-client"
"frontendDist": "../../dist/apps/yaak-client",
"features": ["wry"]
},
"app": {
"withGlobalTauri": false,
@@ -1,7 +1,4 @@
{
"build": {
"features": ["updater", "license"]
},
"app": {
"security": {
"capabilities": [
+4
View File
@@ -12,6 +12,10 @@ crate-type = ["staticlib", "cdylib", "lib"]
[build-dependencies]
tauri-build = { version = "2.6.1", features = [] }
[features]
default = ["wry"]
wry = ["tauri/wry", "tauri/x11", "tauri/dbus"]
[dependencies]
log = { workspace = true }
serde_json = { workspace = true }
@@ -0,0 +1,12 @@
[package]
name = "yaak-system-appearance"
version = "0.1.0"
edition = "2024"
publish = false
[target.'cfg(target_os = "linux")'.dependencies]
dark-light = "2.0.0"
[dependencies]
log = { workspace = true }
tauri = { workspace = true }
@@ -0,0 +1,151 @@
use std::sync::{Arc, Mutex};
use std::time::Duration;
use log::{debug, warn};
use tauri::{AppHandle, Emitter, Runtime};
pub const INITIAL_APPEARANCE_GLOBAL: &str = "__YAAK_INITIAL_APPEARANCE__";
pub const INITIAL_APPEARANCE_SOURCE_GLOBAL: &str = "__YAAK_INITIAL_APPEARANCE_SOURCE__";
pub const SYSTEM_APPEARANCE_CHANGE_EVENT: &str = "system_appearance_change";
const SYSTEM_APPEARANCE_POLL_INTERVAL: Duration = Duration::from_secs(1);
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Appearance {
Dark,
Light,
}
impl Appearance {
pub fn as_str(self) -> &'static str {
match self {
Self::Dark => "dark",
Self::Light => "light",
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InitialAppearanceSource {
Settings,
LinuxSystem,
}
impl InitialAppearanceSource {
fn as_str(self) -> &'static str {
match self {
Self::Settings => "settings",
Self::LinuxSystem => "linux-system",
}
}
}
#[derive(Clone)]
pub struct SystemAppearanceState {
last_appearance: Arc<Mutex<Option<Appearance>>>,
}
pub fn initialization_script(appearance: Appearance, source: InitialAppearanceSource) -> String {
let appearance = appearance.as_str();
let source = source.as_str();
format!(
"window.{INITIAL_APPEARANCE_GLOBAL} = {appearance:?};\
window.{INITIAL_APPEARANCE_SOURCE_GLOBAL} = {source:?};"
)
}
#[cfg(target_os = "linux")]
pub fn system_appearance() -> Option<Appearance> {
if let Some(appearance) = gsettings_system_appearance() {
return Some(appearance);
}
match dark_light::detect() {
Ok(dark_light::Mode::Dark) => Some(Appearance::Dark),
Ok(dark_light::Mode::Light) => Some(Appearance::Light),
Ok(dark_light::Mode::Unspecified) => None,
Err(err) => {
debug!("Failed to detect Linux system appearance: {err:?}");
None
}
}
}
#[cfg(not(target_os = "linux"))]
pub fn system_appearance() -> Option<Appearance> {
None
}
#[cfg(target_os = "linux")]
pub fn watch<R: Runtime>(app_handle: AppHandle<R>) -> Option<SystemAppearanceState> {
let last_appearance = system_appearance();
if last_appearance.is_none() {
debug!("Linux system appearance detection unavailable");
return None;
}
let state = SystemAppearanceState { last_appearance: Arc::new(Mutex::new(last_appearance)) };
let thread_state = state.clone();
let _ = std::thread::spawn(move || {
loop {
std::thread::sleep(SYSTEM_APPEARANCE_POLL_INTERVAL);
emit_change(&app_handle, &thread_state);
}
});
Some(state)
}
#[cfg(not(target_os = "linux"))]
pub fn watch<R: Runtime>(_app_handle: AppHandle<R>) -> Option<SystemAppearanceState> {
None
}
#[cfg(target_os = "linux")]
pub fn emit_change<R: Runtime>(app_handle: &AppHandle<R>, state: &SystemAppearanceState) {
let appearance = system_appearance();
let mut last_appearance =
state.last_appearance.lock().expect("system appearance lock poisoned");
if appearance == *last_appearance {
return;
}
*last_appearance = appearance;
if let Some(appearance) = appearance {
let appearance = appearance.as_str();
debug!("System appearance changed to {appearance}");
if let Err(err) = app_handle.emit(SYSTEM_APPEARANCE_CHANGE_EVENT, appearance) {
warn!("Failed to emit system appearance change: {err:?}");
}
}
}
#[cfg(target_os = "linux")]
fn gsettings_system_appearance() -> Option<Appearance> {
let color_scheme = std::process::Command::new("gsettings")
.args(["get", "org.gnome.desktop.interface", "color-scheme"])
.output()
.ok()
.and_then(|output| String::from_utf8(output.stdout).ok())
.unwrap_or_default();
if color_scheme.contains("prefer-dark") {
return Some(Appearance::Dark);
}
if color_scheme.contains("prefer-light") {
return Some(Appearance::Light);
}
let gtk_theme = std::process::Command::new("gsettings")
.args(["get", "org.gnome.desktop.interface", "gtk-theme"])
.output()
.ok()
.and_then(|output| String::from_utf8(output.stdout).ok())
.unwrap_or_default();
if gtk_theme.to_lowercase().contains("dark") {
return Some(Appearance::Dark);
}
(!gtk_theme.trim().is_empty()).then_some(Appearance::Light)
}
+17 -6
View File
@@ -26,6 +26,7 @@ pub struct CreateWindowConfig<'s> {
pub navigation_tx: Option<mpsc::Sender<String>>,
pub close_tx: Option<mpsc::Sender<()>>,
pub data_dir_key: Option<String>,
pub initialization_script: Option<String>,
pub hidden: bool,
pub hide_titlebar: bool,
pub use_native_titlebar: bool,
@@ -59,6 +60,10 @@ pub fn create_window<R: Runtime>(
.maximized(maximized)
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
if let Some(script) = config.initialization_script {
win_builder = win_builder.initialization_script(script);
}
if let Some(key) = config.data_dir_key {
#[cfg(not(target_os = "macos"))]
{
@@ -138,11 +143,12 @@ pub fn create_window<R: Runtime>(
Ok(win)
}
pub fn create_main_window(
handle: &AppHandle,
pub fn create_main_window<R: Runtime>(
handle: &AppHandle<R>,
url: &str,
initialization_script: Option<String>,
use_native_titlebar: bool,
) -> tauri::Result<WebviewWindow> {
) -> tauri::Result<WebviewWindow<R>> {
let mut counter = 0;
let label = loop {
let label = format!("{MAIN_WINDOW_PREFIX}{counter}");
@@ -165,6 +171,8 @@ pub fn create_main_window(
100.0 + random::<f64>() * 20.0,
)),
restore_position: Some(counter == 0),
initialization_script,
hidden: true,
hide_titlebar: true,
use_native_titlebar,
..Default::default()
@@ -173,14 +181,15 @@ pub fn create_main_window(
create_window(handle, config)
}
pub fn create_child_window(
parent_window: &WebviewWindow,
pub fn create_child_window<R: Runtime>(
parent_window: &WebviewWindow<R>,
url: &str,
label: &str,
title: &str,
inner_size: (f64, f64),
initialization_script: Option<String>,
use_native_titlebar: bool,
) -> tauri::Result<WebviewWindow> {
) -> tauri::Result<WebviewWindow<R>> {
let app_handle = parent_window.app_handle();
let state_key = label.to_string();
let label = format!("{OTHER_WINDOW_PREFIX}_{label}");
@@ -202,6 +211,8 @@ pub fn create_child_window(
url,
inner_size: Some(inner_size),
position: Some(position),
initialization_script,
hidden: true,
hide_titlebar: true,
use_native_titlebar,
..Default::default()