add support for location

This commit is contained in:
Alexander Mohr 2025-04-21 19:30:07 +02:00
parent 3e34d809be
commit 2453bbaf72
7 changed files with 151 additions and 120 deletions

View file

@ -17,7 +17,7 @@ Allow blur for Worf
layerrule = blur, worf layerrule = blur, worf
``` ```
## Additional functionality compared to Wofi (planed) ## Additional functionality compared to Wofi
* Support passing 'hidden' parameters that are not visible in the launcher but will be returned to the application * Support passing 'hidden' parameters that are not visible in the launcher but will be returned to the application
* Window switcher for hyprland * Window switcher for hyprland
* All arguments expect show are supported by config and args * All arguments expect show are supported by config and args
@ -32,6 +32,12 @@ layerrule = blur, worf
* `label`: Allows styling the label * `label`: Allows styling the label
* `row`: Allows styling to row, mainly used to disable hover effects * `row`: Allows styling to row, mainly used to disable hover effects
## Library
The launcher and UI can be used to build any launcher, as the ui, config and run logic is available as a separate crate.
This library is not available publicly yet as the interface is not stable enough.
## Breaking changes to Wofi ## Breaking changes to Wofi
* Runtime behaviour is not guaranteed to be the same and won't ever be, this includes error messages and themes. * Runtime behaviour is not guaranteed to be the same and won't ever be, this includes error messages and themes.
* Themes in general are mostly compatible. Worf is using the same entity ids, * Themes in general are mostly compatible. Worf is using the same entity ids,
@ -39,6 +45,7 @@ layerrule = blur, worf
* Configuration files are not 100% compatible, Worf is using toml files instead, for most part this only means strings have to be quoted * Configuration files are not 100% compatible, Worf is using toml files instead, for most part this only means strings have to be quoted
* Color files are not supported * Color files are not supported
* `line_wrap` is now called `line-wrap` * `line_wrap` is now called `line-wrap`
* Wofi has a C-API, that is not and won't be supported.
## Dropped arguments ## Dropped arguments
* `mode`, use show * `mode`, use show
@ -48,8 +55,3 @@ layerrule = blur, worf
### Dropped configuration options ### Dropped configuration options
* stylesheet -> use style instead * stylesheet -> use style instead
* color / colors -> GTK4 does not support color files * color / colors -> GTK4 does not support color files
## Not supported
* Wofi has a C-API, that is not and won't be supported.

12
examples/dmenu.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/bash
# A list of options, one per line
options="Option 1
Option 2
Option 3"
# Pipe options to wofi and capture the selection
selection=$(echo "$options" | cargo run -- --show dmenu)
# Do something with the selection
echo "You selected: $selection"

View file

@ -2,7 +2,7 @@ use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::{env, fmt, fs}; use std::{env, fmt, fs};
use anyhow::{Error, anyhow}; use anyhow::anyhow;
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@ -23,6 +23,14 @@ impl fmt::Display for ConfigurationError {
} }
} }
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)]
pub enum Anchor {
Top,
Left,
Bottom,
Right,
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize)] #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug, Serialize, Deserialize)]
pub enum MatchMethod { pub enum MatchMethod {
Fuzzy, Fuzzy,
@ -85,6 +93,20 @@ pub enum ArgsError {
InvalidParameter(String), InvalidParameter(String),
} }
impl FromStr for Anchor {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim() {
"top" => Ok(Anchor::Top),
"left" => Ok(Anchor::Left),
"bottom" => Ok(Anchor::Bottom),
"right" => Ok(Anchor::Right),
other => Err(format!("Invalid anchor: {}", other)),
}
}
}
impl FromStr for Mode { impl FromStr for Mode {
type Err = ArgsError; type Err = ArgsError;
@ -201,8 +223,11 @@ pub struct Config {
#[clap(short = 'q', long = "parse-search")] #[clap(short = 'q', long = "parse-search")]
pub parse_search: Option<bool>, pub parse_search: Option<bool>,
#[clap(short = 'l', long = "location")] /// set where the window is displayed.
pub location: Option<String>, /// can be used to anchor a window to an edge by
/// setting top,left for example
#[clap(short = 'l', long = "location", value_delimiter = ',', value_parser = clap::builder::ValueParser::new(Anchor::from_str))]
pub location: Option<Vec<Anchor>>,
#[clap(short = 'a', long = "no-actions")] #[clap(short = 'a', long = "no-actions")]
pub no_actions: Option<bool>, pub no_actions: Option<bool>,
@ -508,7 +533,6 @@ pub fn default_line_wrap() -> Option<WrapMode> {
// max_lines = lines; // max_lines = lines;
// columns = strtol(config_get(config, "columns", "1"), NULL, 10); // columns = strtol(config_get(config, "columns", "1"), NULL, 10);
// sort_order = config_get_mnemonic(config, "sort_order", "default", 2, "default", "alphabetical"); // sort_order = config_get_mnemonic(config, "sort_order", "default", 2, "default", "alphabetical");
// line_wrap = config_get_mnemonic(config, "line_wrap", "off", 4, "off", "word", "char", "word_char") - 1;
// bool global_coords = strcmp(config_get(config, "global_coords", "false"), "true") == 0; // bool global_coords = strcmp(config_get(config, "global_coords", "false"), "true") == 0;
// hide_search = strcmp(config_get(config, "hide_search", "false"), "true") == 0; // hide_search = strcmp(config_get(config, "hide_search", "false"), "true") == 0;
// char* search = map_get(config, "search"); // char* search = map_get(config, "search");

View file

@ -1,70 +1,26 @@
use anyhow::anyhow; use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::{env, fs, string};
use freedesktop_file_parser::DesktopFile; use freedesktop_file_parser::DesktopFile;
use gdk4::Display; use gdk4::Display;
use gtk4::prelude::*; use gtk4::prelude::*;
use gtk4::{IconLookupFlags, IconTheme, TextDirection}; use gtk4::{IconLookupFlags, IconTheme, TextDirection};
use home::home_dir; use home::home_dir;
use log::{debug, info, warn}; use log;
use regex::Regex; use regex::Regex;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::{env, fs, string};
#[derive(Debug)] #[derive(Debug)]
pub enum DesktopError { pub enum DesktopError {
MissingIcon, MissingIcon,
} }
//
// #[derive(Clone)]
// pub struct IconResolver {
// cache: HashMap<String, String>,
// }
//
// impl Default for IconResolver {
// #[must_use]
// fn default() -> IconResolver {
// Self::new()
// }
// }
//
// impl IconResolver {
// #[must_use]
// pub fn new() -> IconResolver {
// IconResolver {
// cache: HashMap::new(),
// }
// }
//
// pub fn icon_path_no_cache(&self, icon_name: &str) -> Result<String, DesktopError> {
// let icon = fetch_icon_from_theme(icon_name)
// .or_else(|_|
// fetch_icon_from_common_dirs(icon_name)
// .or_else(|_| default_icon()));
//
// icon
// }
//
// pub fn icon_path(&mut self, icon_name: &str) -> String {
// if let Some(icon_path) = self.cache.get(icon_name) {
// return icon_path.to_owned();
// }
//
// let icon = self.icon_path_no_cache(icon_name);
//
// self.cache
// .entry(icon_name.to_owned())
// .or_insert_with(|| icon.unwrap_or_default())
// .to_owned()
// }
// }
/// # Errors /// # Errors
/// ///
/// Will return `Err` if no icon can be found /// Will return `Err` if no icon can be found
pub fn default_icon() -> Result<String, DesktopError> { pub fn default_icon() -> Result<String, DesktopError> {
fetch_icon_from_theme("image-missing").map_err(|e| DesktopError::MissingIcon) fetch_icon_from_theme("image-missing").map_err(|_| DesktopError::MissingIcon)
} }
fn fetch_icon_from_theme(icon_name: &str) -> Result<String, DesktopError> { fn fetch_icon_from_theme(icon_name: &str) -> Result<String, DesktopError> {
@ -177,7 +133,7 @@ pub fn find_desktop_files() -> Vec<DesktopFile> {
.filter_map(|icon_dir| find_file_case_insensitive(&icon_dir, regex)) .filter_map(|icon_dir| find_file_case_insensitive(&icon_dir, regex))
.flat_map(|desktop_files| { .flat_map(|desktop_files| {
desktop_files.into_iter().filter_map(|desktop_file| { desktop_files.into_iter().filter_map(|desktop_file| {
debug!("loading desktop file {desktop_file:?}"); log::debug!("loading desktop file {desktop_file:?}");
fs::read_to_string(desktop_file) fs::read_to_string(desktop_file)
.ok() .ok()
.and_then(|content| freedesktop_file_parser::parse(&content).ok()) .and_then(|content| freedesktop_file_parser::parse(&content).ok())

View file

@ -1,5 +1,4 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
@ -15,13 +14,17 @@ use gtk4::prelude::{
ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt, GestureSingleExt, ApplicationExt, ApplicationExtManual, BoxExt, EditableExt, FlowBoxChildExt, GestureSingleExt,
GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt, GtkWindowExt, ListBoxRowExt, NativeExt, WidgetExt,
}; };
use gtk4::{Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, GestureClick, Image, Label, ListBox, ListBoxRow, Ordering, PolicyType, ScrolledWindow, SearchEntry, Widget, gdk, NaturalWrapMode}; use gtk4::{
Align, EventControllerKey, Expander, FlowBox, FlowBoxChild, GestureClick, Image, Label,
ListBox, ListBoxRow, NaturalWrapMode, Ordering, PolicyType, ScrolledWindow, SearchEntry,
Widget, gdk,
};
use gtk4::{Application, ApplicationWindow, CssProvider, Orientation}; use gtk4::{Application, ApplicationWindow, CssProvider, Orientation};
use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell}; use gtk4_layer_shell::{Edge, KeyboardMode, LayerShell};
use log; use log;
use crate::config; use crate::config;
use crate::config::{Animation, Config, MatchMethod, WrapMode}; use crate::config::{Anchor, Animation, Config, MatchMethod, WrapMode};
type ArcMenuMap<T> = Arc<Mutex<HashMap<FlowBoxChild, MenuItem<T>>>>; type ArcMenuMap<T> = Arc<Mutex<HashMap<FlowBoxChild, MenuItem<T>>>>;
type ArcProvider<T> = Arc<Mutex<dyn ItemProvider<T>>>; type ArcProvider<T> = Arc<Mutex<dyn ItemProvider<T>>>;
@ -32,6 +35,17 @@ pub trait ItemProvider<T: std::clone::Clone> {
fn get_sub_elements(&mut self, item: &MenuItem<T>) -> Option<Vec<MenuItem<T>>>; fn get_sub_elements(&mut self, item: &MenuItem<T>) -> Option<Vec<MenuItem<T>>>;
} }
impl From<&Anchor> for Edge {
fn from(value: &Anchor) -> Self {
match value {
Anchor::Top => Edge::Top,
Anchor::Left => Edge::Left,
Anchor::Bottom => Edge::Bottom,
Anchor::Right => Edge::Right,
}
}
}
impl From<config::Orientation> for Orientation { impl From<config::Orientation> for Orientation {
fn from(orientation: config::Orientation) -> Self { fn from(orientation: config::Orientation) -> Self {
match orientation { match orientation {
@ -44,9 +58,9 @@ impl From<config::Orientation> for Orientation {
impl From<&WrapMode> for NaturalWrapMode { impl From<&WrapMode> for NaturalWrapMode {
fn from(value: &WrapMode) -> Self { fn from(value: &WrapMode) -> Self {
match value { match value {
WrapMode::None => {NaturalWrapMode::None}, WrapMode::None => NaturalWrapMode::None,
WrapMode::Word => {NaturalWrapMode::Word}, WrapMode::Word => NaturalWrapMode::Word,
WrapMode::Inherit => {NaturalWrapMode::Inherit}, WrapMode::Inherit => NaturalWrapMode::Inherit,
} }
} }
} }
@ -141,8 +155,11 @@ fn build_ui<T, P>(
window.set_namespace(Some("worf")); window.set_namespace(Some("worf"));
} }
/// todo make this configurable config.location.as_ref().map(|location| {
//window.set_anchor(Edge::Top, true); for anchor in location {
window.set_anchor(anchor.into(), true);
}
});
let outer_box = gtk4::Box::new(config.orientation.unwrap().into(), 0); let outer_box = gtk4::Box::new(config.orientation.unwrap().into(), 0);
outer_box.set_widget_name("outer-box"); outer_box.set_widget_name("outer-box");
@ -951,7 +968,9 @@ fn percent_or_absolute(value: Option<&String>, base_value: i32) -> Option<i32> {
// highly unlikely that we are dealing with > i64 items // highly unlikely that we are dealing with > i64 items
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_wrap)]
pub fn sort_menu_items_alphabetically_honor_initial_score<T: std::clone::Clone>(items: &mut [MenuItem<T>]) { pub fn sort_menu_items_alphabetically_honor_initial_score<T: std::clone::Clone>(
items: &mut [MenuItem<T>],
) {
let mut regular_score = items.len() as i64; let mut regular_score = items.len() as i64;
items.sort_by(|l, r| l.label.cmp(&r.label)); items.sort_by(|l, r| l.label.cmp(&r.label));

View file

@ -1,21 +1,39 @@
use std::os::unix::prelude::CommandExt;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::{env, fmt, fs, io};
use anyhow::Context;
use freedesktop_file_parser::EntryType;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::config::{Config, expand_path}; use crate::config::{Config, expand_path};
use crate::desktop::{ use crate::desktop::{
default_icon, find_desktop_files, get_locale_variants, lookup_name_with_locale, default_icon, find_desktop_files, get_locale_variants, lookup_name_with_locale,
}; };
use crate::gui;
use crate::gui::{ItemProvider, MenuItem}; use crate::gui::{ItemProvider, MenuItem};
use crate::{config, desktop, gui};
use anyhow::{Context, Error, anyhow}; #[derive(Debug)]
use freedesktop_file_parser::EntryType; pub enum ModeError {
use gtk4::Image; UpdateCacheError(String),
use libc::option; MissingAction,
use regex::Regex; RunError(String),
use serde::{Deserialize, Serialize}; MissingCache,
use std::collections::HashMap; }
use std::os::unix::fs::PermissionsExt;
use std::os::unix::prelude::CommandExt; impl fmt::Display for ModeError {
use std::path::PathBuf; fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use std::process::{Command, Stdio}; match self {
use std::{env, fs, io}; ModeError::UpdateCacheError(s) => write!(f, "UpdateCacheError {s}"),
ModeError::MissingAction => write!(f, "MissingAction"),
ModeError::RunError(s) => write!(f, "RunError, {s}"),
ModeError::MissingCache => write!(f, "MissingCache"),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
struct DRunCache { struct DRunCache {
@ -139,7 +157,7 @@ impl<T: Clone> ItemProvider<T> for DRunProvider<T> {
self.items.clone() self.items.clone()
} }
fn get_sub_elements(&mut self, item: &MenuItem<T>) -> Option<Vec<MenuItem<T>>> { fn get_sub_elements(&mut self, _: &MenuItem<T>) -> Option<Vec<MenuItem<T>>> {
None None
} }
} }
@ -283,9 +301,7 @@ struct MathProvider<T: Clone> {
impl<T: std::clone::Clone> MathProvider<T> { impl<T: std::clone::Clone> MathProvider<T> {
fn new(menu_item_data: T) -> Self { fn new(menu_item_data: T) -> Self {
Self { Self { menu_item_data }
menu_item_data,
}
} }
fn contains_math_functions_or_starts_with_number(input: &str) -> bool { fn contains_math_functions_or_starts_with_number(input: &str) -> bool {
@ -327,7 +343,7 @@ impl<T: Clone> ItemProvider<T> for MathProvider<T> {
} }
} }
fn get_sub_elements(&mut self, item: &MenuItem<T>) -> Option<Vec<MenuItem<T>>> { fn get_sub_elements(&mut self, _: &MenuItem<T>) -> Option<Vec<MenuItem<T>>> {
None None
} }
} }
@ -366,7 +382,9 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
let trimmed_search = search.trim(); let trimmed_search = search.trim();
if trimmed_search.is_empty() { if trimmed_search.is_empty() {
self.drun_provider.get_elements(search_opt) self.drun_provider.get_elements(search_opt)
} else if MathProvider::<AutoRunType>::contains_math_functions_or_starts_with_number(trimmed_search) { } else if MathProvider::<AutoRunType>::contains_math_functions_or_starts_with_number(
trimmed_search,
) {
self.math_provider.get_elements(search_opt) self.math_provider.get_elements(search_opt)
} else if trimmed_search.starts_with("$") } else if trimmed_search.starts_with("$")
|| trimmed_search.starts_with("/") || trimmed_search.starts_with("/")
@ -392,7 +410,7 @@ impl ItemProvider<AutoRunType> for AutoItemProvider {
/// # Errors /// # Errors
/// ///
/// Will return `Err` if it was not able to spawn the process /// Will return `Err` if it was not able to spawn the process
pub fn d_run(config: &Config) -> anyhow::Result<()> { pub fn d_run(config: &Config) -> Result<(), ModeError> {
let provider = DRunProvider::new("".to_owned()); let provider = DRunProvider::new("".to_owned());
let cache_path = provider.cache_path.clone(); let cache_path = provider.cache_path.clone();
let mut cache = provider.cache.clone(); let mut cache = provider.cache.clone();
@ -400,9 +418,7 @@ pub fn d_run(config: &Config) -> anyhow::Result<()> {
// todo ues a arc instead of cloning the config // todo ues a arc instead of cloning the config
let selection_result = gui::show(config.clone(), provider); let selection_result = gui::show(config.clone(), provider);
match selection_result { match selection_result {
Ok(s) => { Ok(s) => update_drun_cache_and_run(cache_path, &mut cache, s)?,
update_drun_cache_and_run(cache_path, &mut cache, s)?;
}
Err(_) => { Err(_) => {
log::error!("No item selected"); log::error!("No item selected");
} }
@ -411,7 +427,7 @@ pub fn d_run(config: &Config) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
pub fn auto(config: &Config) -> anyhow::Result<()> { pub fn auto(config: &Config) -> Result<(), ModeError> {
let provider = AutoItemProvider::new(); let provider = AutoItemProvider::new();
let cache_path = provider.drun_provider.cache_path.clone(); let cache_path = provider.drun_provider.cache_path.clone();
let mut cache = provider.drun_provider.cache.clone(); let mut cache = provider.drun_provider.cache.clone();
@ -429,12 +445,9 @@ pub fn auto(config: &Config) -> anyhow::Result<()> {
} }
AutoRunType::File => { AutoRunType::File => {
if let Some(action) = selection_result.action { if let Some(action) = selection_result.action {
spawn_fork(&action, selection_result.working_dir.as_ref())? spawn_fork(&action, selection_result.working_dir.as_ref())?;
} }
} }
_ => {
todo!("not supported yet");
}
} }
} }
} }
@ -446,7 +459,7 @@ pub fn auto(config: &Config) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
pub fn file(config: &Config) -> Result<(), String> { pub fn file(config: &Config) -> Result<(), ModeError> {
let provider = FileItemProvider::new("".to_owned()); let provider = FileItemProvider::new("".to_owned());
// todo ues a arc instead of cloning the config // todo ues a arc instead of cloning the config
@ -454,7 +467,7 @@ pub fn file(config: &Config) -> Result<(), String> {
match selection_result { match selection_result {
Ok(s) => { Ok(s) => {
if let Some(action) = s.action { if let Some(action) = s.action {
spawn_fork(&action, s.working_dir.as_ref()).map_err(|e| e.to_string())?; spawn_fork(&action, s.working_dir.as_ref())?;
} }
} }
Err(_) => { Err(_) => {
@ -465,14 +478,13 @@ pub fn file(config: &Config) -> Result<(), String> {
Ok(()) Ok(())
} }
pub fn math(config: &Config) -> Result<(), String> { pub fn math(config: &Config) -> Result<(), ModeError> {
let provider = MathProvider::new("".to_owned()); let provider = MathProvider::new("".to_owned());
// todo ues a arc instead of cloning the config // todo ues a arc instead of cloning the config
let selection_result = gui::show(config.clone(), provider); let selection_result = gui::show(config.clone(), provider);
match selection_result { match selection_result {
Ok(_) => { Ok(_) => {}
}
Err(_) => { Err(_) => {
log::error!("No item selected"); log::error!("No item selected");
} }
@ -481,11 +493,15 @@ pub fn math(config: &Config) -> Result<(), String> {
Ok(()) Ok(())
} }
pub fn dmenu(_: &Config) -> Result<(), ModeError> {
Ok(())
}
fn update_drun_cache_and_run<T: Clone>( fn update_drun_cache_and_run<T: Clone>(
cache_path: Option<PathBuf>, cache_path: Option<PathBuf>,
cache: &mut HashMap<String, i64>, cache: &mut HashMap<String, i64>,
selection_result: MenuItem<T>, selection_result: MenuItem<T>,
) -> Result<(), Error> { ) -> Result<(), ModeError> {
if let Some(cache_path) = cache_path { if let Some(cache_path) = cache_path {
*cache.entry(selection_result.label).or_insert(0) += 1; *cache.entry(selection_result.label).or_insert(0) += 1;
if let Err(e) = save_cache_file(&cache_path, &cache) { if let Err(e) = save_cache_file(&cache_path, &cache) {
@ -496,7 +512,7 @@ fn update_drun_cache_and_run<T: Clone>(
if let Some(action) = selection_result.action { if let Some(action) = selection_result.action {
spawn_fork(&action, selection_result.working_dir.as_ref()) spawn_fork(&action, selection_result.working_dir.as_ref())
} else { } else {
Err(anyhow::anyhow!("cannot find drun action")) Err(ModeError::MissingAction)
} }
} }
@ -520,12 +536,13 @@ fn save_cache_file(path: &PathBuf, data: &HashMap<String, i64>) -> anyhow::Resul
fs::write(path, toml_string).map_err(|e| anyhow::anyhow!(e)) fs::write(path, toml_string).map_err(|e| anyhow::anyhow!(e))
} }
fn load_cache_file(cache_path: Option<&PathBuf>) -> anyhow::Result<HashMap<String, i64>> { fn load_cache_file(cache_path: Option<&PathBuf>) -> Result<HashMap<String, i64>, ModeError> {
let Some(path) = cache_path else { let Some(path) = cache_path else {
return Err(anyhow!("Cache is missing")); return Err(ModeError::MissingCache);
}; };
let toml_content = fs::read_to_string(path)?; let toml_content =
fs::read_to_string(path).map_err(|e| ModeError::UpdateCacheError(format!("{e}")))?;
let parsed: toml::Value = toml_content.parse().expect("Failed to parse TOML"); let parsed: toml::Value = toml_content.parse().expect("Failed to parse TOML");
let mut result: HashMap<String, i64> = HashMap::new(); let mut result: HashMap<String, i64> = HashMap::new();
@ -555,17 +572,18 @@ fn create_file_if_not_exists(path: &PathBuf) -> anyhow::Result<()> {
} }
} }
fn spawn_fork(cmd: &str, working_dir: Option<&String>) -> anyhow::Result<()> { fn spawn_fork(cmd: &str, working_dir: Option<&String>) -> Result<(), ModeError> {
// todo fix actions ?? // todo fix actions ??
// todo graphical disk map icon not working // todo graphical disk map icon not working
let parts = cmd.split(' ').collect::<Vec<_>>(); let parts = cmd.split(' ').collect::<Vec<_>>();
if parts.is_empty() { if parts.is_empty() {
return Err(anyhow!("empty command passed")); return Err(ModeError::MissingAction);
} }
if let Some(dir) = working_dir { if let Some(dir) = working_dir {
env::set_current_dir(dir)?; env::set_current_dir(dir)
.map_err(|e| ModeError::RunError(format!("cannot set workdir {e}")))?
} }
let exec = parts[0].replace('"', ""); let exec = parts[0].replace('"', "");

View file

@ -13,7 +13,7 @@ fn main() -> anyhow::Result<()> {
.init(); .init();
let args = config::parse_args(); let args = config::parse_args();
let mut config = config::load_config(Some(args)).map_err(|e| anyhow!(e))?; let config = config::load_config(Some(args)).map_err(|e| anyhow!(e))?;
if let Some(show) = &config.show { if let Some(show) = &config.show {
match show { match show {
@ -21,10 +21,10 @@ fn main() -> anyhow::Result<()> {
todo!("run not implemented") todo!("run not implemented")
} }
Mode::Drun => { Mode::Drun => {
mode::d_run(&config)?; mode::d_run(&config).map_err(|e| anyhow!(e))?;
} }
Mode::Dmenu => { Mode::Dmenu => {
todo!("dmenu not implemented") mode::dmenu(&config).map_err(|e| anyhow!(e))?;
} }
Mode::File => { Mode::File => {
mode::file(&config).map_err(|e| anyhow!(e))?; mode::file(&config).map_err(|e| anyhow!(e))?;
@ -33,7 +33,7 @@ fn main() -> anyhow::Result<()> {
mode::math(&config).map_err(|e| anyhow!(e))?; mode::math(&config).map_err(|e| anyhow!(e))?;
} }
Mode::Auto => { Mode::Auto => {
mode::auto(&config)?; mode::auto(&config).map_err(|e| anyhow!(e))?;
} }
} }