dlauncher/extension/
mod.rs

1#![doc = include_str!("../../EXTENSIONS.md")]
2
3use std::sync::Arc;
4
5use libloading::{Library, Symbol};
6use log::debug;
7
8use crate::{
9  extension::{config::ExtensionConfig, query::Query},
10  launcher::{util::config::Config, window::Window},
11};
12
13pub mod config;
14pub mod query;
15pub mod response;
16
17/// Function signature used for native extensions
18pub type ExtensionOutputFunc = unsafe extern "C" fn(ExtensionContext) -> ExtensionExitCode;
19
20/// Return codes for native extensions
21pub enum ExtensionExitCode {
22  /// When the extension returns successfully
23  Ok,
24  /// If the extension encounters an error, it should return this with an explanation.
25  ///
26  /// ```
27  /// use dlauncher::extension::ExtensionExitCode;
28  /// ExtensionExitCode::Error("Failed to do something");
29  /// ```
30  Error(&'static str),
31}
32
33#[derive(Debug, Clone)]
34pub struct Extension {
35  /// The shared object library
36  pub library: Arc<Library>,
37  /// Copy of Dlauncher window for use in the extension
38  pub window: Window,
39  /// Copy of Dlauncher config for use in the extension
40  pub config: ExtensionConfig,
41  /// The extension's name, used for identification and more to keep extensions in line.
42  pub name: String,
43}
44
45#[derive(Debug, Clone)]
46pub struct ExtensionContext {
47  /// Extensions name
48  ///
49  /// Used for building responses
50  pub name: String,
51  /// The Dlauncher window, useful when directly interfacing with Dlauncher which allows for great flexibility.
52  pub window: Window,
53  /// Input when called through on_input(), this will be `None` if called through other functions that don't require an input.
54  pub input: Option<Query>,
55  /// The Dlauncher main configuration
56  pub config: ExtensionConfig,
57}
58
59impl Extension {
60  pub fn new(window: Window, config: Config, extension_name: String) -> Extension {
61    unsafe {
62      let filename = config.dir().join("extensions").join(&extension_name);
63      let library = Library::new(filename).unwrap();
64
65      Extension {
66        library: Arc::new(library),
67        window,
68        config: ExtensionConfig::new(&config, &extension_name),
69        name: extension_name,
70      }
71    }
72  }
73
74  /// on_input is called when a user types something into the input.
75  pub fn on_input(&self, input: &str) -> ExtensionExitCode {
76    unsafe {
77      let output: Symbol<ExtensionOutputFunc> = self.library.get(b"on_input").unwrap();
78
79      output(ExtensionContext {
80        name: self.name.clone(),
81        input: Some(Query::from_str(input)),
82        window: self.window.clone(),
83        config: self.config.clone(),
84      })
85    }
86  }
87
88  /// on_init is called when dlauncher is starting.
89  pub fn on_init(&self) -> ExtensionExitCode {
90    unsafe {
91      if let Ok(output) = self.library.get::<ExtensionOutputFunc>(b"on_init") {
92        output(ExtensionContext {
93          name: self.name.clone(),
94          input: None,
95          window: self.window.clone(),
96          config: self.config.clone(),
97        })
98      } else {
99        debug!("Extension {} has no on_init function, skipped", self.name);
100        ExtensionExitCode::Ok
101      }
102    }
103  }
104
105  /// on_open is called when the window is toggled open.
106  pub fn on_open(&self) -> ExtensionExitCode {
107    unsafe {
108      if let Ok(output) = self.library.get::<ExtensionOutputFunc>(b"on_open") {
109        output(ExtensionContext {
110          name: self.name.clone(),
111          input: None,
112          window: self.window.clone(),
113          config: self.config.clone(),
114        })
115      } else {
116        debug!("Extension {} has no on_open", self.name);
117        ExtensionExitCode::Ok
118      }
119    }
120  }
121}