tauri-plugin-decor 1.0.0

Opinionated window decoration controls for Tauri apps.
Documentation
use std::io::prelude::*;
use std::process::Command;

use linicon::{lookup_icon, IconType};
use tauri::Manager;

use crate::config;

pub(crate) use crate::html::apply_runtime_style;

fn read_dconf(path: &str) -> Option<String> {
    let out = Command::new("dconf").args(["read", path]).output().ok()?;
    Some(
        String::from_utf8_lossy(&out.stdout)
            .replace(['\'', '"', '\n'], ""),
    )
}

fn find_svg_icon(name: String) -> Option<String> {
    lookup_icon(name).into_iter().find_map(|icon| match icon {
        Ok(icon) if icon.icon_type == IconType::SVG => {
            let mut data = String::new();
            std::fs::File::open(icon.path)
                .ok()?
                .read_to_string(&mut data)
                .ok()?;
            Some(data)
        }
        _ => None,
    })
}

fn apply_style_replacements(src: &str) -> String {
    let height_px = format!("\"{}px\"", config::titlebar_height());
    let width_px = format!("\"{}px\"", config::button_width());
    src.replace("\"32px\"", &height_px)
        .replace("\"46px\"", &width_px)
        .replace(config::DEFAULT_CLOSE_HOVER_BG, &config::close_hover_bg())
        .replace(config::DEFAULT_BUTTON_HOVER_BG, &config::button_hover_bg())
}

fn enabled_controls<R: tauri::Runtime>(webview: &tauri::Webview<R>) -> Vec<String> {
    let win = webview.app_handle().get_webview_window(webview.label());
    let mut out = Vec::with_capacity(3);
    if win.as_ref().and_then(|w| w.is_minimizable().ok()).unwrap_or(true) {
        out.push("minimize".into());
    }
    if win.as_ref().and_then(|w| w.is_maximizable().ok()).unwrap_or(true)
        && win.as_ref().and_then(|w| w.is_resizable().ok()).unwrap_or(true)
    {
        out.push("maximize".into());
    }
    if win.as_ref().and_then(|w| w.is_closable().ok()).unwrap_or(true) {
        out.push("close".into());
    }
    out
}

fn dconf_button_layout() -> Option<Vec<String>> {
    read_dconf("/org/gnome/desktop/wm/preferences/button-layout").map(|raw| {
        raw.trim_start_matches("appmenu:")
            .split(',')
            .map(str::to_string)
            .collect()
    })
}

pub(crate) fn handle_page_load<R: tauri::Runtime>(webview: &tauri::Webview<R>) {
    if !crate::overlay::contains(webview.label()) && !config::auto_titlebar() {
        return;
    }

    let titlebar = apply_style_replacements(include_str!("js/titlebar.js"));
    if let Err(e) = webview.eval(&titlebar) {
        eprintln!("decor error: {e:?}");
        return;
    }

    let mut platform = apply_style_replacements(include_str!("js/linux.js"));

    let mut controls = enabled_controls(webview);
    for control in controls.iter().chain(std::iter::once(&"restore".to_string())) {
        let icon = find_svg_icon(format!("window-{control}-symbolic"))
            .or_else(|| find_svg_icon(format!("window-{control}")));
        if let Some(data) = icon {
            platform = platform.replace(&format!("@win-{control}"), &data);
        }
    }

    if let Some(layout) = dconf_button_layout() {
        controls = layout;
    }

    let controls_base = apply_style_replacements(include_str!("js/controls.js"));
    let platform = platform.replacen(
        "[\"minimize\", \"maximize\", \"close\"]",
        &format!("{controls:?}"),
        1,
    );

    let _ = webview.eval(&format!(";(() => {{\n{controls_base}\n{platform}\n}})();"));
}