Krolyxon
Commit ab1dc3b

Branches

Tags

Refs →

remagesrcmain.rs

221 lines · 6.6 KB · Rust

1//! remage: a keyboard-centric image viewer.
2
3mod action;
4mod anim;
5mod app;
6mod cache;
7mod cli;
8mod config;
9mod decode;
10mod exec;
11mod input;
12mod ipc;
13mod render;
14mod scan;
15mod state;
16mod thumbs;
17mod ui;
18mod worker;
19
20use std::ffi::OsString;
21use std::io::{Read, Write};
22use std::os::unix::ffi::{OsStrExt, OsStringExt};
23use std::path::{Path, PathBuf};
24use std::time::Instant;
25
26use clap::Parser;
27use winit::event_loop::EventLoop;
28
29use app::{App, Options, Overrides, UserEvent};
30use state::queue::Queue;
31use state::{AppState, MsgKind, Settings};
32
33fn main() -> anyhow::Result<()> {
34 let started = Instant::now();
35 env_logger::init();
36 let cli = cli::Cli::parse();
37 if let Some(cli::Sub::Msg {
38 pid,
39 query,
40 subscribe,
41 action,
42 }) = cli.command
43 {
44 std::process::exit(ipc::client(ipc::ClientArgs {
45 pid,
46 query,
47 subscribe,
48 action,
49 }));
50 }
51 if cli.dump_config {
52 print!("{}", config::dump());
53 return Ok(());
54 }
55 if cli.check_config {
56 check_config(cli.config.as_deref());
57 }
58 log::info!("remage {}", env!("CARGO_PKG_VERSION"));
59
60 let (loaded, config_error) = match config::load(cli.config.as_deref()) {
61 Ok(l) => (l, None),
62 Err(e) => {
63 log::error!("{e}");
64 let mut d = config::defaults();
65 d.watch = e.watch;
66 (d, Some(e.message))
67 }
68 };
69 for w in &loaded.warnings {
70 log::warn!("{w}");
71 }
72 log::debug!("config loaded after {:.1?}", started.elapsed());
73 let cfg = &loaded.config;
74
75 let mut paths = cli.paths.clone();
76 if cli.stdin {
77 paths.extend(read_stdin_paths(cli.null)?);
78 }
79 let scan = if cli.stdin && paths.is_empty() {
80 // An empty list on stdin opens nothing, not the current directory.
81 scan::Scan::default()
82 } else {
83 scan::scan(
84 &paths,
85 &scan::ScanOptions {
86 recursive: cli.recursive || cfg.general.recursive,
87 // A list of files is just those files.
88 siblings: !cli.stdin && !cli.no_siblings && cfg.general.open_siblings,
89 sort: cli.sort.unwrap_or(cfg.general.sort),
90 reverse: cli.reverse || cfg.general.reverse,
91 },
92 )
93 };
94 log::debug!(
95 "scanned {} files ({} to sniff) in {:.1?}",
96 scan.files.len(),
97 scan.unknown.len(),
98 started.elapsed()
99 );
100
101 let overrides = Overrides {
102 fit: cli.fit,
103 zoom: cli.zoom.map(|z| z / 100.0),
104 bar: cli.no_bar.then_some(false),
105 };
106 let mut settings = Settings::from_config(cfg);
107 overrides.apply(&mut settings);
108 let start_index = cli.start.unwrap_or(1).saturating_sub(1);
109 let queue = Queue::new(scan.files, scan.start.as_deref(), start_index);
110 let bar = overrides.bar.unwrap_or(cfg.ui.statusbar);
111 let mut state = AppState::new(queue, settings, bar, Instant::now());
112 if cli.grid || cfg.general.start_mode == config::StartMode::Grid {
113 state.set_mode(state::Mode::Grid, Instant::now());
114 }
115 state.theme = loaded.theme.clone();
116 state.nearest = cfg.image.filter.nearest();
117 state.alpha_bg = cfg.image.alpha_background;
118 if let Some(err) = scan.errors.first() {
119 state.notify(format!("error: {err}"), MsgKind::Error, Instant::now());
120 }
121
122 let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
123 let proxy = event_loop.create_proxy();
124 if !scan.unknown.is_empty() {
125 let proxy = proxy.clone();
126 let unknown = scan.unknown;
127 std::thread::spawn(move || {
128 let found: Vec<_> = unknown.into_iter().filter(|p| decode::sniff(p)).collect();
129 if !found.is_empty() {
130 let _ = proxy.send_event(UserEvent::Found(found));
131 }
132 });
133 }
134 let server = {
135 let proxy = proxy.clone();
136 let proxy = std::sync::Mutex::new(proxy);
137 match ipc::serve(move |inc| {
138 let proxy = proxy.lock().unwrap_or_else(|e| e.into_inner());
139 proxy.send_event(UserEvent::Ipc(inc)).is_ok()
140 }) {
141 Ok(s) => {
142 log::debug!("IPC socket {}", s.path().display());
143 Some(s)
144 }
145 Err(e) => {
146 log::warn!("IPC socket: {e}");
147 None
148 }
149 }
150 };
151 let watch = {
152 let proxy = proxy.clone();
153 config::watch::spawn(cli.config.clone(), loaded.watch.clone(), move |r| {
154 let _ = proxy.send_event(UserEvent::Config(Box::new(r)));
155 })
156 };
157
158 let opts = Options {
159 fullscreen: cli.fullscreen || cfg.window.fullscreen,
160 geometry: cli.geometry,
161 class: cli.class.unwrap_or_else(|| cfg.window.class.clone()),
162 overrides,
163 private: cli.private,
164 socket: server.as_ref().map(|s| s.path().to_path_buf()),
165 };
166 let mut app = App::new(opts, state, proxy, loaded, watch);
167 if let Some(e) = config_error {
168 app.config_failed(&e);
169 }
170 event_loop.run_app(&mut app)?;
171 if cli.print_marked
172 && let Some(marked) = app.marked_output()
173 {
174 let sep = if cli.null { b'\0' } else { b'\n' };
175 let mut out = std::io::stdout().lock();
176 for p in marked {
177 out.write_all(p.as_os_str().as_bytes())?;
178 out.write_all(&[sep])?;
179 }
180 out.flush()?;
181 }
182 // Lets in-flight IPC replies (to a `quit`, say) go out, then removes the socket.
183 drop(server);
184 match app.error.take() {
185 Some(e) => Err(e),
186 None => Ok(()),
187 }
188}
189
190/// `-i`: paths from stdin, one per line (NUL-separated with `-0`).
191fn read_stdin_paths(null: bool) -> anyhow::Result<Vec<PathBuf>> {
192 let mut buf = Vec::new();
193 std::io::stdin().lock().read_to_end(&mut buf)?;
194 let sep = if null { 0 } else { b'\n' };
195 Ok(buf
196 .split(|b| *b == sep)
197 .filter(|l| !l.is_empty())
198 .map(|l| PathBuf::from(OsString::from_vec(l.to_vec())))
199 .collect())
200}
201
202/// `--check-config`: report and exit, non-zero on errors.
203fn check_config(path: Option<&Path>) -> ! {
204 match config::load(path) {
205 Ok(l) => {
206 for w in &l.warnings {
207 eprintln!("warning: {w}");
208 }
209 match path.map(Path::to_path_buf).or_else(config::default_path) {
210 Some(p) if p.is_file() => println!("{}: ok", p.display()),
211 Some(p) => println!("{} not found: using the defaults", p.display()),
212 None => println!("no config directory: using the defaults"),
213 }
214 std::process::exit(0)
215 }
216 Err(e) => {
217 eprintln!("{e}");
218 std::process::exit(1)
219 }
220 }
221}