Skip to main content

cmdpal/cmd/common/
open_url.rs

1//! Builder for creating commands that open URLs in the system's default browser.
2
3use crate::{
4    cmd::{BaseCommand, BaseCommandBuilder, CommandResult, InvokableCommand},
5    icon::{IconData, IconInfo},
6    utils::ComBuilder,
7};
8use windows_core::ComObject;
9
10/// Builder for a command that opens a URL in the system's default browser.
11pub struct OpenUrlCommandBuilder {
12    base: ComObject<BaseCommand>,
13    target_fn: Box<dyn Send + Sync + Fn() -> String>,
14    result: CommandResult,
15}
16
17fn open_url_base_cmd() -> ComObject<BaseCommand> {
18    BaseCommandBuilder::new()
19        .name("Open")
20        .icon(IconInfo::new(IconData::from("\u{E8A7}")))
21        .build()
22}
23
24impl OpenUrlCommandBuilder {
25    /// Creates a new `OpenUrlCommandBuilder` with a static target URL.
26    pub fn new(target: String) -> Self {
27        Self {
28            base: open_url_base_cmd(),
29            target_fn: Box::new(move || target.clone()),
30            result: CommandResult::Dismiss,
31        }
32    }
33
34    /// Creates a new `OpenUrlCommandBuilder` with a function that returns the target URL.
35    pub fn new_dyn<F>(target_fn: F) -> Self
36    where
37        F: Send + Sync + Fn() -> String + 'static,
38    {
39        Self {
40            base: open_url_base_cmd(),
41            target_fn: Box::new(target_fn),
42            result: CommandResult::Dismiss,
43        }
44    }
45
46    /// Overrides the base command with a custom one.
47    ///
48    /// By default, it uses a command with the name "Open" and an OpenInNewWindow icon "\u{E8A7}".
49    pub fn base(mut self, base: ComObject<BaseCommand>) -> Self {
50        self.base = base;
51        self
52    }
53
54    /// Overrides the action to be performed when the URL is opened.
55    ///
56    /// By default, it is set to [`CommandResult::Dismiss`].
57    pub fn result(mut self, result: CommandResult) -> Self {
58        self.result = result;
59        self
60    }
61}
62
63impl ComBuilder for OpenUrlCommandBuilder {
64    type Output = InvokableCommand;
65    fn build_unmanaged(self) -> InvokableCommand {
66        InvokableCommand {
67            base: self.base,
68            func: Box::new(move |_| {
69                shell_helper::open_in_shell(&(self.target_fn)())?;
70                Ok(self.result.clone())
71            }),
72        }
73    }
74}
75
76mod shell_helper {
77    use windows::Win32::UI::Shell::{SHELLEXECUTEINFOW, ShellExecuteExW};
78    use windows::Win32::UI::{Shell::SEE_MASK_NOCLOSEPROCESS, WindowsAndMessaging::SW_SHOWNORMAL};
79
80    pub(super) fn open_in_shell(target: &str) -> windows_core::Result<()> {
81        let mut sei = SHELLEXECUTEINFOW::default();
82        sei.cbSize = std::mem::size_of::<SHELLEXECUTEINFOW>() as u32;
83        sei.fMask = SEE_MASK_NOCLOSEPROCESS;
84        sei.lpFile = windows_core::PCWSTR::from_raw(windows_core::HSTRING::from(target).as_ptr());
85        sei.nShow = SW_SHOWNORMAL.0;
86
87        unsafe { ShellExecuteExW(&mut sei) }
88    }
89}