Fix NixOS GUI issues

Fixes #380, #414
This commit is contained in:
2026-04-13 09:27:21 +03:00
parent 489f16e462
commit 071567fa73
4 changed files with 124 additions and 63 deletions
+30 -33
View File
@@ -18,18 +18,30 @@
"i686-windows" "i686-windows"
"x86_64-windows" "x86_64-windows"
]; ];
in flake-utils.lib.eachSystem systems (system: in flake-utils.lib.eachSystem systems (system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
lib = pkgs.lib;
naersk-lib = naersk.lib.${system}; 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 let
features = builtins.concatStringsSep " " (builtins.concatMap features = builtins.concatStringsSep " " (builtins.concatMap
({ option, features }: pkgs.lib.optionals option features) [ ({ option, features }: lib.optionals option features) [
{ {
option = webgui; option = webgui;
features = [ "webgui" ]; features = [ "webgui" ];
@@ -38,55 +50,40 @@
option = doh; option = doh;
features = [ "doh" ]; features = [ "doh" ];
} }
{
option = edge;
features = [ "edge" ];
}
]); ]);
in naersk-lib.buildPackage { in naersk-lib.buildPackage {
pname = "alfis"; pname = "alfis";
nativeBuildInputs = with pkgs; [ pkg-config webkitgtk kdialog ]; root = ./.;
dontWrapQtApps = true; nativeBuildInputs = guiNativeBuildInputs;
buildInputs = guiBuildInputs;
cargoBuildOptions = opts: cargoBuildOptions = opts:
opts ++ [ "--no-default-features" ] opts ++ [ "--no-default-features" ]
++ [ "--features" ''"${features}"'' ]; ++ lib.optionals (features != "") [ "--features" features ];
root = ./.; 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 { in rec {
packages = { packages = {
alfis = alfis { alfis = alfis {
webgui = true; webgui = true;
doh = true; doh = true;
edge = false;
}; };
alfisWithoutGUI = alfis { alfisWithoutGUI = alfis {
webgui = false; webgui = false;
doh = true; doh = true;
edge = false;
};
} // pkgs.lib.optionalAttrs isWindows {
alfisEdge = alfis {
webgui = false;
doh = true;
edge = true;
}; };
}; };
defaultPackage = packages.alfis; defaultPackage = packages.alfis;
apps = with flake-utils.lib; apps = with flake-utils.lib; {
{ alfis = mkApp { drv = packages.alfis; };
alfis = mkApp { drv = packages.alfis; }; alfisWithoutGUI = mkApp { drv = packages.alfisWithoutGUI; };
alfisWithoutGUI = mkApp { drv = packages.alfisWithoutGUI; }; };
} // pkgs.lib.optionalAttrs isWindows {
alfisEdge = mkApp { drv = packages.alfisEdge; };
};
defaultApp = apps.alfis; defaultApp = apps.alfis;
devShell = import ./shell.nix { inherit pkgs; }; devShell = import ./shell.nix { inherit pkgs; };
}); });
} }
+17 -2
View File
@@ -1,6 +1,21 @@
{ pkgs ? import <nixpkgs> { } }: { pkgs ? import <nixpkgs> { } }:
let
runtimeLibs = with pkgs; [
gtk3
webkitgtk_4_1
xdotool
libayatana-appindicator
];
packages = with pkgs; [
cargo
rustc
pkg-config
kdePackages.kdialog
] ++ runtimeLibs;
in
pkgs.mkShell { pkgs.mkShell {
buildInputs = buildInputs = packages;
[ pkgs.cargo pkgs.rustc pkgs.webkitgtk pkgs.pkg-config pkgs.kdialog ]; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath runtimeLibs;
} }
+14
View File
@@ -148,6 +148,20 @@ fn main() {
no_gui = true; 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)] #[cfg(windows)]
if opt_matches.opt_present("service") { if opt_matches.opt_present("service") {
let appdata = env::var("PROGRAMDATA").expect("Failed to get APPDATA directory"); let appdata = env::var("PROGRAMDATA").expect("Failed to get APPDATA directory");
+63 -28
View File
@@ -3,6 +3,7 @@ extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate tinyfiledialogs as tfd; extern crate tinyfiledialogs as tfd;
use std::panic::{self, AssertUnwindSafe};
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread; use std::thread;
@@ -61,13 +62,8 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, hid
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
let icon = load_icon_from_png(); let icon = load_icon_from_png();
let tray_icon = TrayIconBuilder::new() let tray_icon = build_tray_icon(&title, tray_menu, icon);
.with_menu(Box::new(tray_menu)) let tray_available = tray_icon.is_some();
.with_tooltip(&title)
.with_icon(icon)
.with_menu_on_left_click(false)
.build()
.unwrap();
let window_size = tao::dpi::LogicalSize::new(1024, 720); let window_size = tao::dpi::LogicalSize::new(1024, 720);
// Get primary monitor and calculate center position // Get primary monitor and calculate center position
@@ -90,7 +86,7 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, hid
.with_inner_size(window_size) .with_inner_size(window_size)
.with_min_inner_size(tao::dpi::LogicalSize::new(773, 350)) .with_min_inner_size(tao::dpi::LogicalSize::new(773, 350))
.with_resizable(true) .with_resizable(true)
.with_visible(!hide); .with_visible(!hide || !tray_available);
if let Some(position) = position { if let Some(position) = position {
builder = builder.with_position(position); builder = builder.with_position(position);
@@ -319,15 +315,17 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, hid
true true
}); });
let proxy = event_loop.create_proxy(); if tray_available {
TrayIconEvent::set_event_handler(Some(move |event| { let proxy = event_loop.create_proxy();
let _ = proxy.send_event(UserEvent::TrayIconEvent(event)); TrayIconEvent::set_event_handler(Some(move |event| {
})); let _ = proxy.send_event(UserEvent::TrayIconEvent(event));
}));
let proxy = event_loop.create_proxy(); let proxy = event_loop.create_proxy();
MenuEvent::set_event_handler(Some(move |event| { MenuEvent::set_event_handler(Some(move |event| {
let _ = proxy.send_event(UserEvent::MenuEvent(event)); let _ = proxy.send_event(UserEvent::MenuEvent(event));
})); }));
}
let proxy = event_loop.create_proxy(); let proxy = event_loop.create_proxy();
@@ -340,7 +338,14 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, hid
event: WindowEvent::CloseRequested, 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) => { TaoEvent::UserEvent(user_event) => {
let wv = webview_clone.lock().unwrap(); let wv = webview_clone.lock().unwrap();
@@ -364,19 +369,21 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, hid
show_warning(&wv, &text); show_warning(&wv, &text);
} }
UserEvent::TrayIconEvent(event) => { UserEvent::TrayIconEvent(event) => {
match event { if let Some(tray_icon) = tray_icon.as_ref() {
TrayIconEvent::DoubleClick { button, .. } => { match event {
if button == tray_icon::MouseButton::Left { TrayIconEvent::DoubleClick { button, .. } => {
window.set_visible(true); if button == tray_icon::MouseButton::Left {
window.set_focus(); 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) => { UserEvent::MenuEvent(event) => {
@@ -396,6 +403,34 @@ pub fn run_interface(context: Arc<Mutex<Context>>, miner: Arc<Mutex<Miner>>, hid
}); });
} }
fn build_tray_icon(title: &str, tray_menu: Menu, icon: tray_icon::Icon) -> Option<tray_icon::TrayIcon> {
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)] #[derive(Debug)]
enum UserEvent { enum UserEvent {
EvalJs(String), EvalJs(String),