diff --git a/flake.nix b/flake.nix index 7264c45..641ffc7 100644 --- a/flake.nix +++ b/flake.nix @@ -18,18 +18,30 @@ "i686-windows" "x86_64-windows" ]; - in flake-utils.lib.eachSystem systems (system: let - pkgs = nixpkgs.legacyPackages.${system}; - + lib = pkgs.lib; naersk-lib = naersk.lib.${system}; + isLinux = pkgs.stdenv.hostPlatform.isLinux; - alfis = { webgui ? true, doh ? true, edge ? false }: + guiBuildInputs = lib.optionals isLinux (with pkgs; [ + gtk3 + webkitgtk_4_1 + xdotool + libayatana-appindicator + ]); + + guiNativeBuildInputs = [ pkgs.pkg-config ] + ++ lib.optionals isLinux [ pkgs.makeWrapper pkgs.wrapGAppsHook ]; + + guiRuntimeTools = lib.optionals isLinux [ pkgs.kdePackages.kdialog ]; + guiRuntimeLibPath = lib.optionalString isLinux (lib.makeLibraryPath guiBuildInputs); + + alfis = { webgui ? true, doh ? true }: let features = builtins.concatStringsSep " " (builtins.concatMap - ({ option, features }: pkgs.lib.optionals option features) [ + ({ option, features }: lib.optionals option features) [ { option = webgui; features = [ "webgui" ]; @@ -38,55 +50,40 @@ option = doh; features = [ "doh" ]; } - { - option = edge; - features = [ "edge" ]; - } ]); in naersk-lib.buildPackage { pname = "alfis"; - nativeBuildInputs = with pkgs; [ pkg-config webkitgtk kdialog ]; - dontWrapQtApps = true; + root = ./.; + nativeBuildInputs = guiNativeBuildInputs; + buildInputs = guiBuildInputs; cargoBuildOptions = opts: opts ++ [ "--no-default-features" ] - ++ [ "--features" ''"${features}"'' ]; - root = ./.; + ++ lib.optionals (features != "") [ "--features" features ]; + preFixup = lib.optionalString isLinux '' + gappsWrapperArgs+=(--prefix PATH : "${lib.makeBinPath guiRuntimeTools}") + gappsWrapperArgs+=(--prefix LD_LIBRARY_PATH : "${guiRuntimeLibPath}") + ''; }; - - isWindows = builtins.elem system [ "i686-windows" "x86_64-windows" ]; in rec { - packages = { alfis = alfis { webgui = true; doh = true; - edge = false; }; alfisWithoutGUI = alfis { webgui = false; doh = true; - edge = false; - }; - } // pkgs.lib.optionalAttrs isWindows { - alfisEdge = alfis { - webgui = false; - doh = true; - edge = true; }; }; defaultPackage = packages.alfis; - apps = with flake-utils.lib; - { - alfis = mkApp { drv = packages.alfis; }; - alfisWithoutGUI = mkApp { drv = packages.alfisWithoutGUI; }; - } // pkgs.lib.optionalAttrs isWindows { - alfisEdge = mkApp { drv = packages.alfisEdge; }; - }; + apps = with flake-utils.lib; { + alfis = mkApp { drv = packages.alfis; }; + alfisWithoutGUI = mkApp { drv = packages.alfisWithoutGUI; }; + }; + defaultApp = apps.alfis; - devShell = import ./shell.nix { inherit pkgs; }; - }); } diff --git a/shell.nix b/shell.nix index ced1b6c..c695132 100644 --- a/shell.nix +++ b/shell.nix @@ -1,6 +1,21 @@ { pkgs ? import { } }: +let + runtimeLibs = with pkgs; [ + gtk3 + webkitgtk_4_1 + xdotool + libayatana-appindicator + ]; + + packages = with pkgs; [ + cargo + rustc + pkg-config + kdePackages.kdialog + ] ++ runtimeLibs; +in pkgs.mkShell { - buildInputs = - [ pkgs.cargo pkgs.rustc pkgs.webkitgtk pkgs.pkg-config pkgs.kdialog ]; + buildInputs = packages; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs; } diff --git a/src/main.rs b/src/main.rs index 7be3747..5a2912a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -148,6 +148,20 @@ fn main() { no_gui = true; } + #[cfg(all(feature = "webgui", target_os = "linux"))] + if !no_gui { + let running_via_sudo = env::var_os("SUDO_UID").is_some(); + let has_graphical_session = env::var_os("DISPLAY").is_some() || env::var_os("WAYLAND_DISPLAY").is_some(); + + if running_via_sudo { + warn!(target: LOG_TARGET_MAIN, "Running GUI via sudo is not supported on Linux, starting without GUI"); + no_gui = true; + } else if !has_graphical_session { + warn!(target: LOG_TARGET_MAIN, "No graphical session detected, starting without GUI"); + no_gui = true; + } + } + #[cfg(windows)] if opt_matches.opt_present("service") { let appdata = env::var("PROGRAMDATA").expect("Failed to get APPDATA directory"); diff --git a/src/web_ui.rs b/src/web_ui.rs index 8a23af7..85f26f6 100644 --- a/src/web_ui.rs +++ b/src/web_ui.rs @@ -3,6 +3,7 @@ extern crate serde; extern crate serde_json; extern crate tinyfiledialogs as tfd; +use std::panic::{self, AssertUnwindSafe}; use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; @@ -61,13 +62,8 @@ pub fn run_interface(context: Arc>, miner: Arc>, hid #[cfg(not(target_os = "windows"))] let icon = load_icon_from_png(); - let tray_icon = TrayIconBuilder::new() - .with_menu(Box::new(tray_menu)) - .with_tooltip(&title) - .with_icon(icon) - .with_menu_on_left_click(false) - .build() - .unwrap(); + let tray_icon = build_tray_icon(&title, tray_menu, icon); + let tray_available = tray_icon.is_some(); let window_size = tao::dpi::LogicalSize::new(1024, 720); // Get primary monitor and calculate center position @@ -90,7 +86,7 @@ pub fn run_interface(context: Arc>, miner: Arc>, hid .with_inner_size(window_size) .with_min_inner_size(tao::dpi::LogicalSize::new(773, 350)) .with_resizable(true) - .with_visible(!hide); + .with_visible(!hide || !tray_available); if let Some(position) = position { builder = builder.with_position(position); @@ -319,15 +315,17 @@ pub fn run_interface(context: Arc>, miner: Arc>, hid true }); - let proxy = event_loop.create_proxy(); - TrayIconEvent::set_event_handler(Some(move |event| { - let _ = proxy.send_event(UserEvent::TrayIconEvent(event)); - })); + if tray_available { + let proxy = event_loop.create_proxy(); + TrayIconEvent::set_event_handler(Some(move |event| { + let _ = proxy.send_event(UserEvent::TrayIconEvent(event)); + })); - let proxy = event_loop.create_proxy(); - MenuEvent::set_event_handler(Some(move |event| { - let _ = proxy.send_event(UserEvent::MenuEvent(event)); - })); + let proxy = event_loop.create_proxy(); + MenuEvent::set_event_handler(Some(move |event| { + let _ = proxy.send_event(UserEvent::MenuEvent(event)); + })); + } let proxy = event_loop.create_proxy(); @@ -340,7 +338,14 @@ pub fn run_interface(context: Arc>, miner: Arc>, hid event: WindowEvent::CloseRequested, .. } => { - window.set_visible(false); + if tray_available { + window.set_visible(false); + } else { + info!("Interface closed, exiting"); + post(Event::ActionQuit); + thread::sleep(Duration::from_millis(100)); + *control_flow = ControlFlow::Exit; + } } TaoEvent::UserEvent(user_event) => { let wv = webview_clone.lock().unwrap(); @@ -364,19 +369,21 @@ pub fn run_interface(context: Arc>, miner: Arc>, hid show_warning(&wv, &text); } UserEvent::TrayIconEvent(event) => { - match event { - TrayIconEvent::DoubleClick { button, .. } => { - if button == tray_icon::MouseButton::Left { - window.set_visible(true); - window.set_focus(); + if let Some(tray_icon) = tray_icon.as_ref() { + match event { + TrayIconEvent::DoubleClick { button, .. } => { + if button == tray_icon::MouseButton::Left { + window.set_visible(true); + window.set_focus(); + } } + TrayIconEvent::Enter { .. } => { + let nodes = connected_nodes.load(Ordering::SeqCst); + let title = format!("ALFIS {}\nConnected: {nodes}", env!("CARGO_PKG_VERSION")); + let _ = tray_icon.set_tooltip(Some(title)); + } + _ => {} } - TrayIconEvent::Enter { .. } => { - let nodes = connected_nodes.load(Ordering::SeqCst); - let title = format!("ALFIS {}\nConnected: {nodes}", env!("CARGO_PKG_VERSION")); - let _ = tray_icon.set_tooltip(Some(title)); - } - _ => {} } } UserEvent::MenuEvent(event) => { @@ -396,6 +403,34 @@ pub fn run_interface(context: Arc>, miner: Arc>, hid }); } +fn build_tray_icon(title: &str, tray_menu: Menu, icon: tray_icon::Icon) -> Option { + let previous_hook = panic::take_hook(); + panic::set_hook(Box::new(|_| {})); + + let result = panic::catch_unwind(AssertUnwindSafe(|| { + TrayIconBuilder::new() + .with_menu(Box::new(tray_menu)) + .with_tooltip(title) + .with_icon(icon) + .with_menu_on_left_click(false) + .build() + })); + + panic::set_hook(previous_hook); + + match result { + Ok(Ok(tray_icon)) => Some(tray_icon), + Ok(Err(error)) => { + warn!("Tray icon is unavailable: {error}"); + None + } + Err(_) => { + warn!("Tray icon is unavailable: failed to load appindicator library, continuing without tray support"); + None + } + } +} + #[derive(Debug)] enum UserEvent { EvalJs(String),