cosmic_files/
lib.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: GPL-3.0-only
3
4use cosmic::app::Settings;
5use cosmic::iced::Limits;
6use std::path::PathBuf;
7use std::{env, fs};
8#[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
9use std::process;
10use tracing_subscriber::layer::SubscriberExt;
11use tracing_subscriber::util::SubscriberInitExt;
12
13use crate::app::{App, Flags};
14use crate::config::{Config, State};
15use crate::tab::Location;
16
17pub mod app;
18mod archive;
19pub mod channel;
20pub mod clipboard;
21pub mod config;
22mod context_action;
23pub mod dialog;
24mod key_bind;
25pub(crate) mod large_image;
26pub(crate) mod load_image;
27mod localize;
28mod menu;
29mod mime_app;
30pub mod mime_icon;
31mod mounter;
32mod mouse_area;
33pub mod operation;
34pub mod russh;
35mod spawn_detached;
36pub mod tab;
37mod thumbnail_cacher;
38mod thumbnailer;
39pub mod sequencing;
40pub(crate) mod trash;
41mod zoom;
42
43pub(crate) type FxOrderMap<K, V> = ordermap::OrderMap<K, V, rustc_hash::FxBuildHasher>;
44
45#[allow(dead_code)]
46pub(crate) fn err_str<T: ToString>(err: T) -> String {
47    err.to_string()
48}
49
50pub fn desktop_dir() -> PathBuf {
51    if let Some(path) = dirs::desktop_dir() {
52        path
53    } else {
54        let path = home_dir().join("Desktop");
55        log::warn!(
56            "failed to locate desktop directory, falling back to {}",
57            path.display()
58        );
59        path
60    }
61}
62
63pub fn home_dir() -> PathBuf {
64    if let Some(home) = dirs::home_dir() {
65        home
66    } else {
67        let path = PathBuf::from("/");
68        log::warn!(
69            "failed to locate home directory, falling back to {}",
70            path.display()
71        );
72        path
73    }
74}
75
76pub fn is_wayland() -> bool {
77    matches!(
78        cosmic::app::cosmic::windowing_system(),
79        Some(cosmic::app::cosmic::WindowingSystem::Wayland)
80    )
81}
82
83/// Runs application in desktop mode
84#[rustfmt::skip]
85pub fn desktop() -> Result<(), Box<dyn std::error::Error>> {
86    let log_format = tracing_subscriber::fmt::format()
87        .pretty()
88        .without_time()
89        .with_line_number(true)
90        .with_file(true)
91        .with_target(false)
92        .with_thread_names(true);
93
94    let log_layer = tracing_subscriber::fmt::Layer::default()
95        .with_writer(std::io::stderr)
96        .event_format(log_format);
97
98    tracing_subscriber::registry()
99        .with(tracing_subscriber::EnvFilter::from_env("RUST_LOG"))
100        .with(log_layer)
101        .init();
102
103    localize::localize();
104
105    let (config_handler, config) = Config::load();
106    let (state_handler, state) = State::load();
107
108    let mut settings = Settings::default();
109    settings = settings.theme(config.app_theme.theme());
110    settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
111    settings = settings.exit_on_close(false);
112    settings = settings.transparent(true);
113    #[cfg(all(feature = "wayland", feature = "desktop-applet"))]
114    {
115        settings = settings.no_main_window(true);
116    }
117
118    let locations = vec![tab::Location::Desktop(desktop_dir(), String::new(), config.desktop)];
119    let flags = Flags {
120        config_handler,
121        config,
122        state_handler,
123        state,
124        mode: app::Mode::Desktop,
125        locations,
126        uris: Vec::new()
127    };
128    cosmic::app::run::<App>(settings, flags)?;
129
130    Ok(())
131}
132
133/// Runs application with these settings
134#[rustfmt::skip]
135pub fn main() -> Result<(), Box<dyn std::error::Error>> {
136    let log_format = tracing_subscriber::fmt::format()
137        .pretty()
138        .with_line_number(true)
139        .with_file(true)
140        .with_target(false)
141        .with_thread_names(true);
142
143    let log_layer = tracing_subscriber::fmt::Layer::default()
144        .with_writer(std::io::stderr)
145        .event_format(log_format);
146
147    tracing_subscriber::registry()
148        .with(tracing_subscriber::EnvFilter::from_default_env())
149        .with(log_layer)
150        .init();
151
152    localize::localize();
153
154    let (config_handler, config) = Config::load();
155    let (state_handler, state) = State::load();
156
157    let mut daemonize = true;
158    let mut locations = Vec::new();
159    let mut uris = Vec::new();
160    for arg in env::args().skip(1) {
161        let location = if &arg == "--no-daemon" {
162            daemonize = false;
163            continue;
164        } else if &arg == "--trash" {
165            Location::Trash
166        } else if &arg == "--recents" {
167            if config.show_recents {
168                Location::Recents
169            } else {
170                log::warn!("recents feature is disabled in config");
171                continue;
172            }
173        } else if &arg == "--network" {
174            Location::Network("network:///".to_string(), fl!("networks"), None)
175        } else {
176            //TODO: support more URLs
177            let path = match url::Url::parse(&arg) {
178                Ok(url) if url.scheme() == "file" => if let Ok(path) = url.to_file_path() { path } else {
179                    log::warn!("invalid argument {arg:?}");
180                    continue;
181                },
182                Ok(url) => {
183                    uris.push(url);
184                    continue;
185                }
186                _ => PathBuf::from(arg),
187            };
188            match fs::canonicalize(&path) {
189                Ok(absolute) => Location::Path(absolute),
190                Err(err) => {
191                    log::warn!("failed to canonicalize {}: {}", path.display(), err);
192                    continue;
193                }
194            }
195        };
196        locations.push(location);
197    }
198
199    if daemonize {
200        #[cfg(all(unix, not(any(target_os = "macos", target_os = "redox"))))]
201        match fork::daemon(true, true) {
202            Ok(fork::Fork::Child) => (),
203            Ok(fork::Fork::Parent(_child_pid)) => process::exit(0),
204            Err(err) => {
205                eprintln!("failed to daemonize: {err:?}");
206                process::exit(1);
207            }
208        }
209    }
210
211    let mut settings = Settings::default();
212    settings = settings.theme(config.app_theme.theme());
213    settings = settings.size_limits(Limits::NONE.min_width(360.0).min_height(180.0));
214    settings = settings.exit_on_close(false);
215
216    #[cfg(feature = "jemalloc")]
217    {
218        settings = settings.default_mmap_threshold(None);
219    }
220
221    let flags = Flags {
222        config_handler,
223        config,
224        state_handler,
225        state,
226        mode: app::Mode::App,
227        locations,
228        uris
229    };
230    cosmic::app::run::<App>(settings, flags)?;
231
232    Ok(())
233}