Skip to main content

skim_run/
apps.rs

1use std::{borrow::Cow, cmp::min, ops::Deref, process::Command, thread::sleep, time::Duration};
2
3use anyhow::Context as _;
4use applications::{AppInfo, AppInfoContext, common::SearchPath};
5use fork::daemon;
6use skim::prelude::*;
7
8use crate::SkimRun;
9
10#[derive(Default)]
11pub struct App {
12    app: applications::App,
13}
14impl Deref for App {
15    type Target = applications::App;
16
17    fn deref(&self) -> &Self::Target {
18        &self.app
19    }
20}
21impl From<applications::App> for App {
22    fn from(value: applications::App) -> Self {
23        Self { app: value }
24    }
25}
26
27impl SkimItem for App {
28    fn text(&self) -> Cow<str> {
29        if self.app.name.is_empty() {
30            Cow::Borrowed(
31                self.app
32                    .app_desktop_path
33                    .file_name()
34                    .unwrap()
35                    .to_str()
36                    .unwrap()
37                    .strip_suffix(".desktop")
38                    .unwrap(),
39            )
40        } else {
41            Cow::Borrowed(&self.app.name)
42        }
43    }
44    fn preview(&self, context: PreviewContext) -> ItemPreview {
45        let width = u32::try_from(context.width).unwrap_or(16);
46        let height = u32::try_from(context.height).unwrap_or(16);
47        let size = min(16, min(height, width));
48        if self.icon_path.is_some() {
49            let conf = viuer::Config {
50                width: Some(size),
51                x: 0,
52                y: 0,
53                transparent: true,
54                restore_cursor: false,
55                use_kitty: false,
56                truecolor: true,
57                ..Default::default()
58            };
59            let img = image::DynamicImage::ImageRgb8(image::RgbImage::new(size, size));
60            let _ = viuer::print(&img, &conf);
61            let _ = viuer::print_from_file(self.icon_path.clone().unwrap(), &conf);
62        }
63
64        ItemPreview::Text(String::new())
65    }
66    fn output(&self) -> Cow<str> {
67        if self.app_path_exe.is_some() {
68            self.app_path_exe
69                .as_ref()
70                .unwrap()
71                .as_path()
72                .to_string_lossy()
73        } else {
74            Cow::default()
75        }
76    }
77}
78
79#[derive(Default, Clone, Copy)]
80pub struct Apps;
81
82impl SkimRun for Apps {
83    fn get(&self) -> Vec<std::sync::Arc<(dyn skim::SkimItem + 'static)>> {
84        let mut ctx = AppInfoContext::new(vec![SearchPath::new(std::path::PathBuf::from("/"), 1)]);
85        ctx.refresh_apps().unwrap(); // must refresh apps before getting them
86
87        ctx.get_all_apps()
88            .into_iter()
89            .map(|a| Arc::new(App::from(a)) as Arc<dyn SkimItem>)
90            .collect()
91    }
92    fn run(&self, output: &SkimOutput) -> anyhow::Result<()> {
93        let item = output
94            .selected_items
95            .first()
96            .context("Could not find selected item")?;
97        if let Ok(fork::Fork::Child) = daemon(false, false) {
98            Command::new(item.output().into_owned())
99                .spawn()
100                .context("Failed to spawn app")?;
101            sleep(Duration::from_secs(10));
102        }
103        Ok(())
104    }
105    fn set_options(&self, opts: &mut SkimOptions) {
106        opts.preview = Some(String::new());
107        opts.preview_window = String::from("left:16");
108    }
109}