Krolyxon
Commit e3295f9

Branches

Tags

Refs →

remagesrcmain.rs

222 lines · 6.7 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 decode::set_decoders(&cfg.decoders);
75
76 let mut paths = cli.paths.clone();
77 if cli.stdin {
78 paths.extend(read_stdin_paths(cli.null)?);
79 }
80 let scan = if cli.stdin && paths.is_empty() {
81 // An empty list on stdin opens nothing, not the current directory.
82 scan::Scan::default()
83 } else {
84 scan::scan(
85 &paths,
86 &scan::ScanOptions {
87 recursive: cli.recursive || cfg.general.recursive,
88 // A list of files is just those files.
89 siblings: !cli.stdin && !cli.no_siblings && cfg.general.open_siblings,
90 sort: cli.sort.unwrap_or(cfg.general.sort),
91 reverse: cli.reverse || cfg.general.reverse,
92 },
93 )
94 };
95 log::debug!(
96 "scanned {} files ({} to sniff) in {:.1?}",
97 scan.files.len(),
98 scan.unknown.len(),
99 started.elapsed()
100 );
101
102 let overrides = Overrides {
103 fit: cli.fit,
104 zoom: cli.zoom.map(|z| z / 100.0),
105 bar: cli.no_bar.then_some(false),
106 };
107 let mut settings = Settings::from_config(cfg);
108 overrides.apply(&mut settings);
109 let start_index = cli.start.unwrap_or(1).saturating_sub(1);
110 let queue = Queue::new(scan.files, scan.start.as_deref(), start_index);
111 let bar = overrides.bar.unwrap_or(cfg.ui.statusbar);
112 let mut state = AppState::new(queue, settings, bar, Instant::now());
113 if cli.grid || cfg.general.start_mode == config::StartMode::Grid {
114 state.set_mode(state::Mode::Grid, Instant::now());
115 }
116 state.theme = loaded.theme.clone();
117 state.nearest = cfg.image.filter.nearest();
118 state.alpha_bg = cfg.image.alpha_background;
119 if let Some(err) = scan.errors.first() {
120 state.notify(format!("error: {err}"), MsgKind::Error, Instant::now());
121 }
122
123 let event_loop = EventLoop::<UserEvent>::with_user_event().build()?;
124 let proxy = event_loop.create_proxy();
125 if !scan.unknown.is_empty() {
126 let proxy = proxy.clone();
127 let unknown = scan.unknown;
128 std::thread::spawn(move || {
129 let found: Vec<_> = unknown.into_iter().filter(|p| decode::sniff(p)).collect();
130 if !found.is_empty() {
131 let _ = proxy.send_event(UserEvent::Found(found));
132 }
133 });
134 }
135 let server = {
136 let proxy = proxy.clone();
137 let proxy = std::sync::Mutex::new(proxy);
138 match ipc::serve(move |inc| {
139 let proxy = proxy.lock().unwrap_or_else(|e| e.into_inner());
140 proxy.send_event(UserEvent::Ipc(inc)).is_ok()
141 }) {
142 Ok(s) => {
143 log::debug!("IPC socket {}", s.path().display());
144 Some(s)
145 }
146 Err(e) => {
147 log::warn!("IPC socket: {e}");
148 None
149 }
150 }
151 };
152 let watch = {
153 let proxy = proxy.clone();
154 config::watch::spawn(cli.config.clone(), loaded.watch.clone(), move |r| {
155 let _ = proxy.send_event(UserEvent::Config(Box::new(r)));
156 })
157 };
158
159 let opts = Options {
160 fullscreen: cli.fullscreen || cfg.window.fullscreen,
161 geometry: cli.geometry,
162 class: cli.class.unwrap_or_else(|| cfg.window.class.clone()),
163 overrides,
164 private: cli.private,
165 socket: server.as_ref().map(|s| s.path().to_path_buf()),
166 };
167 let mut app = App::new(opts, state, proxy, loaded, watch);
168 if let Some(e) = config_error {
169 app.config_failed(&e);
170 }
171 event_loop.run_app(&mut app)?;
172 if cli.print_marked
173 && let Some(marked) = app.marked_output()
174 {
175 let sep = if cli.null { b'\0' } else { b'\n' };
176 let mut out = std::io::stdout().lock();
177 for p in marked {
178 out.write_all(p.as_os_str().as_bytes())?;
179 out.write_all(&[sep])?;
180 }
181 out.flush()?;
182 }
183 // Lets in-flight IPC replies (to a `quit`, say) go out, then removes the socket.
184 drop(server);
185 match app.error.take() {
186 Some(e) => Err(e),
187 None => Ok(()),
188 }
189}
190
191/// `-i`: paths from stdin, one per line (NUL-separated with `-0`).
192fn read_stdin_paths(null: bool) -> anyhow::Result<Vec<PathBuf>> {
193 let mut buf = Vec::new();
194 std::io::stdin().lock().read_to_end(&mut buf)?;
195 let sep = if null { 0 } else { b'\n' };
196 Ok(buf
197 .split(|b| *b == sep)
198 .filter(|l| !l.is_empty())
199 .map(|l| PathBuf::from(OsString::from_vec(l.to_vec())))
200 .collect())
201}
202
203/// `--check-config`: report and exit, non-zero on errors.
204fn check_config(path: Option<&Path>) -> ! {
205 match config::load(path) {
206 Ok(l) => {
207 for w in &l.warnings {
208 eprintln!("warning: {w}");
209 }
210 match path.map(Path::to_path_buf).or_else(config::default_path) {
211 Some(p) if p.is_file() => println!("{}: ok", p.display()),
212 Some(p) => println!("{} not found: using the defaults", p.display()),
213 None => println!("no config directory: using the defaults"),
214 }
215 std::process::exit(0)
216 }
217 Err(e) => {
218 eprintln!("{e}");
219 std::process::exit(1)
220 }
221 }
222}