diff --git a/config.toml b/config.toml index fd716e41..67d2af07 100644 --- a/config.toml +++ b/config.toml @@ -30,7 +30,7 @@ notification-thumbnail = "screenshot" # how long we are waiting after notification thumbnail was sent before exiting (exit early or quit). This is based on the temporary file mtime. # for fast machines, the default value of 250ms is ridiculously high. notification-grace-period = 250 -# Disable notifications +# Disable notifications - may be ignored in case of errors that can't be reported via terminal disable-notifications = false # Exit directly after copy/save action. 0.21.0: change to list of triggers @@ -70,7 +70,6 @@ default-fill-shapes = false default-round-caps = true # experimental feature: adjust history size for brush input smoothing (0: disabled, default: 0, try e.g. 5 or 10) brush-smooth-history-size = 10 - # Actions to trigger on right click (order is important) # [possible values: save-to-clipboard, save-to-file, save-to-file-as, copy-filepath-to-clipboard, exit] actions-on-right-click = [] diff --git a/src/main.rs b/src/main.rs index b8213dc4..34706fbc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -698,7 +698,7 @@ fn main() -> Result<()> { // run the application match run_satty() { Err(e) => { - eprintln!("Error: {e}"); + notification::log_error(&format!("Error: {e}")); Err(e) } Ok(v) => Ok(v), diff --git a/src/notification.rs b/src/notification.rs index b52ab48c..7d440a22 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -7,19 +7,36 @@ use relm4::gtk::gdk_pixbuf::{InterpType, Pixbuf}; use relm4::gtk::prelude::Cast; use relm4::gtk::{IconLookupFlags, IconTheme, TextDirection, gio}; use satty_cli::command_line::NotificationThumbnail; +use std::io::IsTerminal; use tempfile::NamedTempFile; +pub fn log_error(msg: &str) { + let msg = &format!("❌️ {msg}").to_string(); + eprintln!("{msg}"); + if !std::io::stderr().is_terminal() { + show_notification(msg, None, gio::NotificationPriority::Urgent); + } +} + +pub fn log_warning(msg: &str) { + let msg = &format!("⚠️ {msg}").to_string(); + eprintln!("{msg}"); + if !std::io::stderr().is_terminal() { + show_notification(msg, None, gio::NotificationPriority::High); + } +} + pub fn log_result(msg: &str, notify: bool) { eprintln!("{msg}"); - if notify && !APP_CONFIG.read().disable_notifications() { - show_notification(msg, None); + if !std::io::stderr().is_terminal() && notify && !APP_CONFIG.read().disable_notifications() { + show_notification(msg, None, gio::NotificationPriority::Normal); } } pub fn log_result_with_pixbuf(msg: &str, pixbuf: Pixbuf) { eprintln!("{msg}"); - if APP_CONFIG.read().disable_notifications() { + if std::io::stderr().is_terminal() || APP_CONFIG.read().disable_notifications() { return; } @@ -82,13 +99,14 @@ pub fn log_result_with_pixbuf(msg: &str, pixbuf: Pixbuf) { _ => None, }; - show_notification(msg, icon); + show_notification(msg, icon, gio::NotificationPriority::Normal); } -fn show_notification(msg: &str, icon: Option) { +fn show_notification(msg: &str, icon: Option, priority: gio::NotificationPriority) { // construct let notification = Notification::new("Satty"); notification.set_body(Some(msg)); + notification.set_priority(priority); if let Some(i) = icon { notification.set_icon(&i); diff --git a/src/sketch_board.rs b/src/sketch_board.rs index 31028718..4f5a6a18 100644 --- a/src/sketch_board.rs +++ b/src/sketch_board.rs @@ -23,7 +23,7 @@ use crate::image_loading; use crate::ime::pango_adapter::spans_from_pango_attrs; use crate::keybindings::{ActionTrigger, ShortcutCommand, ShortcutRegistry}; use crate::math::{Vec2D, crop_rect_in_bounds}; -use crate::notification::{log_result, log_result_with_pixbuf}; +use crate::notification::{log_error, log_result, log_result_with_pixbuf, log_warning}; use crate::style::{Color, Size, Style}; use crate::tools::{ ImagePlacement, PointerTool, RenderingMode, TextTool, Tool, ToolEvent, ToolUpdateResult, Tools, @@ -310,10 +310,7 @@ fn pixbuf_from_file_list(file_list: &gdk::FileList) -> Option { match image_loading::pixbuf_from_file(&path) { Ok(pixbuf) => Some(pixbuf), Err(e) => { - log_result( - &format!("Error loading image: {e}"), - !APP_CONFIG.read().disable_notifications(), - ); + log_error(&format!("Error loading image: {e}")); None } } @@ -541,9 +538,9 @@ impl SketchBoard { let mut output_filename = if panic::catch_unwind(|| delayed_format.to_string()).is_ok() { delayed_format.to_string() } else { - eprintln!( + log_warning(&format!( "Warning: Could not format filename {output_filename} due to chrono format error, falling back to literal filename." - ); + )); output_filename.to_owned() }; @@ -554,7 +551,7 @@ impl SketchBoard { home_dir.push(tilde_stripped); output_filename = home_dir.to_string_lossy().into_owned(); } else { - log_result("~ found but could not determine homedir", true); + log_error("~ found but could not determine homedir"); return None; } } @@ -644,16 +641,14 @@ impl SketchBoard { // TODO: we could support more data types if output_filename != "-" && !output_filename.ends_with(".png") { - log_result( - "The only supported format is png, but the filename does not end in png", - true, - ); + log_error("The only supported format is png, but the filename does not end in png"); return; } let data = match image.save_to_bufferv("png", &Vec::new()) { Ok(d) => d, Err(e) => { + log_error(&format!("Error serializing image: {e}")); eprintln!("Error serializing image: {e}"); return; } @@ -664,15 +659,12 @@ impl SketchBoard { let stdout = io::stdout(); let mut handle = stdout.lock(); if let Err(e) = handle.write_all(&data) { - eprintln!("Error writing image to stdout: {e}"); + log_error(&format!("Error writing image to stdout: {e}")); } return; } match Self::write_image_file(Path::new(&output_filename), &data) { - Err(e) => log_result( - &format!("Error while saving file '{output_filename}': {e}"), - true, - ), + Err(e) => log_error(&format!("Error while saving file '{output_filename}': {e}")), Ok(_) => { // Store the filepath for copy-filepath action *self.last_saved_filepath.borrow_mut() = Some(output_filename.clone()); @@ -704,7 +696,7 @@ impl SketchBoard { let data = match pixbuf.save_to_bufferv("png", &Vec::new()) { Ok(d) => d, Err(e) => { - eprintln!("Error serializing image: {e}"); + log_error(&format!("Error serializing image: {e}")); return; } }; @@ -748,10 +740,9 @@ impl SketchBoard { }; match Self::write_image_file(Path::new(&output_filename), &data) { - Err(e) => log_result( - &format!("Error while saving file '{output_filename}': {e}"), - true, - ), + Err(e) => { + log_error(&format!("Error while saving file '{output_filename}': {e}")) + } Ok(_) => { exit_app = APP_CONFIG.read().early_exit_save_as(); filename = Some(output_filename.clone()); @@ -864,7 +855,7 @@ impl SketchBoard { }; match result { - Err(e) => log_result(&format!("Error copying filepath: {e}"), true), + Err(e) => log_error(&format!("Error copying filepath: {e}")), Ok(()) => log_result(&format!("Filepath copied to clipboard: {}", filepath), true), } } @@ -2159,12 +2150,14 @@ impl Component for SketchBoard { let error_count = crate::configuration::config_error_count(); if error_count > 0 { - eprintln!( - "⚠️ {} error{} occurred during configuration loading.", + log_warning(&format!( + "{} error{} occurred during configuration loading. \ + Please run from terminal and check the output!", error_count, if error_count == 1 { "" } else { "s" } - ); + )); } + ComponentParts { model, widgets } } } diff --git a/src/tools/image.rs b/src/tools/image.rs index c555b0fb..2c8cae50 100644 --- a/src/tools/image.rs +++ b/src/tools/image.rs @@ -8,11 +8,10 @@ use relm4::{RelmWidgetExt, Sender, gtk}; use crate::tools::drag_box::draw_rect_marker; use crate::{ - configuration::APP_CONFIG, femtovg_area::create_image_from_pixbuf, image_loading, math::{self, Vec2D}, - notification::log_result, + notification::log_error, sketch_board::{MouseButton, MouseEventMsg, MouseEventType, SketchBoardInput}, tools::hit_test_rectangle, }; @@ -263,10 +262,7 @@ impl ImageTool { { match image_loading::pixbuf_from_file(&path) { Ok(pixbuf) => sender.emit(SketchBoardInput::ImagePlaced(pixbuf, placement)), - Err(e) => log_result( - &format!("Error loading image: {e}"), - !APP_CONFIG.read().disable_notifications(), - ), + Err(e) => log_error(&format!("Error loading image: {e}")), } } dialog.destroy();