linux appimage build with docker, egl fallback, and webkitgtk fixes
This commit is contained in:
@@ -5,6 +5,13 @@ use tauri::Manager;
|
||||
|
||||
mod database;
|
||||
mod commands;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path = "os_detection_windows.rs"]
|
||||
mod os_detection;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "os_detection_linux.rs"]
|
||||
mod os_detection;
|
||||
|
||||
pub struct AppState {
|
||||
@@ -13,16 +20,73 @@ pub struct AppState {
|
||||
}
|
||||
|
||||
fn get_data_dir() -> PathBuf {
|
||||
let exe_path = std::env::current_exe().unwrap();
|
||||
let data_dir = exe_path.parent().unwrap().join("data");
|
||||
// On Linux AppImage: $APPIMAGE points to the .AppImage file itself.
|
||||
// Store data next to the AppImage so it's fully portable.
|
||||
let base = if let Ok(appimage_path) = std::env::var("APPIMAGE") {
|
||||
PathBuf::from(appimage_path)
|
||||
.parent()
|
||||
.map(|p| p.to_path_buf())
|
||||
.unwrap_or_else(|| std::env::current_exe().unwrap().parent().unwrap().to_path_buf())
|
||||
} else {
|
||||
std::env::current_exe().unwrap().parent().unwrap().to_path_buf()
|
||||
};
|
||||
let data_dir = base.join("data");
|
||||
std::fs::create_dir_all(&data_dir).ok();
|
||||
data_dir
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
/// On Linux AppImage, install a .desktop file and icon into the user's local
|
||||
/// XDG directories so GNOME/KDE can show the correct dock icon. Also cleans up
|
||||
/// stale entries if the AppImage has been moved or deleted.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn install_desktop_entry() {
|
||||
let appimage_path = match std::env::var("APPIMAGE") {
|
||||
Ok(p) => p,
|
||||
Err(_) => return, // Not running as AppImage
|
||||
};
|
||||
let appdir = std::env::var("APPDIR").unwrap_or_default();
|
||||
let home = match std::env::var("HOME") {
|
||||
Ok(h) => h,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
// Install .desktop file
|
||||
let apps_dir = format!("{home}/.local/share/applications");
|
||||
std::fs::create_dir_all(&apps_dir).ok();
|
||||
|
||||
let desktop_content = format!(
|
||||
"[Desktop Entry]\n\
|
||||
Name=ZeroClock\n\
|
||||
Comment=Local time tracking with invoicing\n\
|
||||
Exec={appimage_path}\n\
|
||||
Icon=zeroclock\n\
|
||||
Type=Application\n\
|
||||
Terminal=false\n\
|
||||
StartupWMClass=zeroclock\n\
|
||||
Categories=Office;ProjectManagement;\n"
|
||||
);
|
||||
std::fs::write(format!("{apps_dir}/zeroclock.desktop"), &desktop_content).ok();
|
||||
|
||||
// Install icons from the AppImage's bundled hicolor theme
|
||||
if !appdir.is_empty() {
|
||||
for size in &["32x32", "128x128", "256x256@2"] {
|
||||
let src = format!("{appdir}/usr/share/icons/hicolor/{size}/apps/zeroclock.png");
|
||||
if std::path::Path::new(&src).exists() {
|
||||
let dest_dir = format!("{home}/.local/share/icons/hicolor/{size}/apps");
|
||||
std::fs::create_dir_all(&dest_dir).ok();
|
||||
std::fs::copy(&src, format!("{dest_dir}/zeroclock.png")).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
env_logger::init();
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
install_desktop_entry();
|
||||
|
||||
let data_dir = get_data_dir();
|
||||
let db_path = data_dir.join("timetracker.db");
|
||||
|
||||
@@ -154,8 +218,62 @@ pub fn run() {
|
||||
commands::update_recurring_invoice,
|
||||
commands::delete_recurring_invoice,
|
||||
commands::check_recurring_invoices,
|
||||
commands::quit_app,
|
||||
commands::get_platform,
|
||||
commands::play_sound,
|
||||
])
|
||||
.setup(|app| {
|
||||
// On Wayland, `decorations: false` in tauri.conf.json is ignored due to a
|
||||
// GTK bug. We set an empty CSD titlebar so the compositor doesn't add
|
||||
// rectangular SSD, then strip GTK's default CSD shadow/border via CSS.
|
||||
// See: https://github.com/tauri-apps/tauri/issues/6562
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use gtk::prelude::{CssProviderExt, GtkWindowExt, WidgetExt};
|
||||
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
if let Ok(gtk_window) = window.gtk_window() {
|
||||
// Strip GTK's CSD shadow and border from the decoration node
|
||||
let provider = gtk::CssProvider::new();
|
||||
provider.load_from_data(b"\
|
||||
decoration { \
|
||||
box-shadow: none; \
|
||||
margin: 0; \
|
||||
border: none; \
|
||||
} \
|
||||
").ok();
|
||||
if let Some(screen) = WidgetExt::screen(>k_window) {
|
||||
gtk::StyleContext::add_provider_for_screen(
|
||||
&screen,
|
||||
&provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION + 1,
|
||||
);
|
||||
}
|
||||
|
||||
// Set an empty zero-height CSD titlebar so the compositor
|
||||
// doesn't add rectangular server-side decorations.
|
||||
// Suppress the harmless "called on a realized window" GTK warning
|
||||
// by briefly redirecting stderr to /dev/null.
|
||||
let empty = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
empty.set_size_request(-1, 0);
|
||||
empty.set_visible(true);
|
||||
unsafe {
|
||||
extern "C" { fn dup(fd: i32) -> i32; fn dup2(fd: i32, fd2: i32) -> i32; fn close(fd: i32) -> i32; }
|
||||
let saved = dup(2);
|
||||
if let Ok(devnull) = std::fs::File::open("/dev/null") {
|
||||
use std::os::unix::io::AsRawFd;
|
||||
dup2(devnull.as_raw_fd(), 2);
|
||||
gtk_window.set_titlebar(Some(&empty));
|
||||
dup2(saved, 2);
|
||||
} else {
|
||||
gtk_window.set_titlebar(Some(&empty));
|
||||
}
|
||||
if saved >= 0 { close(saved); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
|
||||
|
||||
Reference in New Issue
Block a user