libawm/contrib/
actions.rs

1//! Additional helper functions and actions for use with penrose.
2use crate::{
3    core::{
4        bindings::KeyEventHandler,
5        client::Client,
6        data_types::RelativePosition,
7        helpers::{spawn, spawn_for_output},
8        layout::Layout,
9        manager::WindowManager,
10        ring::Selector,
11        workspace::Workspace,
12        xconnection::XConn,
13    },
14    Result,
15};
16
17/**
18 * Jump to, or create, a [Workspace]
19 *
20 * Call 'get_name' to obtain a Workspace name and check to see if there is currently a Workspace
21 * with that name being managed by the WindowManager. If there is no existing workspace with the
22 * given name, create it with the supplied available layouts. If a matching Workspace _does_
23 * already exist then simply switch focus to it. This action is most useful when combined with the
24 * DefaultWorkspace hook that allows for auto populating named Workspaces when first focusing them.
25 */
26pub fn create_or_switch_to_workspace<X: XConn>(
27    get_name: fn() -> Option<String>,
28    layouts: Vec<Layout>,
29) -> KeyEventHandler<X> {
30    Box::new(move |wm: &mut WindowManager<X>| {
31        if let Some(s) = get_name() {
32            let name = &s;
33            let cond = |ws: &Workspace| ws.name() == name;
34            let sel = Selector::Condition(&cond);
35            if wm.workspace(&sel).is_none() {
36                wm.push_workspace(Workspace::new(name, layouts.clone()))?;
37            }
38            wm.focus_workspace(&sel)
39        } else {
40            Ok(())
41        }
42    })
43}
44
45/**
46 * Focus a [Client] with the given class as `WM_CLASS` or spawn the program with the given command
47 * if no such Client exists.
48 *
49 * This is useful for key bindings that are based on the program you want to work with rather than
50 * having to remember where things are running.
51 */
52pub fn focus_or_spawn<X: XConn>(
53    class: impl Into<String>,
54    command: impl Into<String>,
55) -> KeyEventHandler<X> {
56    let (class, command) = (class.into(), command.into());
57
58    Box::new(move |wm: &mut WindowManager<X>| {
59        let cond = |c: &Client| c.class() == class;
60        if let Some(client) = wm.client(&Selector::Condition(&cond)) {
61            let workspace = client.workspace();
62            wm.focus_workspace(&Selector::Index(workspace))
63        } else {
64            spawn(&command)
65        }
66    })
67}
68
69/**
70 * Detect the current monitor set up and arrange the monitors if needed using [xrandr][1].
71 *
72 * NOTE
73 * - Primary monitor will be set to `primary`
74 * - Monitor resolution is set using the --auto flag in xrandr
75 * - Only supports one and two monitor setups.
76 *
77 * [1]: https://wiki.archlinux.org/index.php/Xrandr
78 */
79pub fn update_monitors_via_xrandr(
80    primary: &str,
81    secondary: &str,
82    position: RelativePosition,
83) -> Result<()> {
84    let raw = spawn_for_output("xrandr")?;
85    let secondary_line = raw
86        .lines()
87        .find(|line| line.starts_with(secondary))
88        .ok_or_else(|| perror!("unable to find secondary monitor in xrandr output"))?;
89    let status = secondary_line
90        .split(' ')
91        .nth(1)
92        .ok_or_else(|| perror!("unexpected xrandr output"))?;
93
94    let position_flag = match position {
95        RelativePosition::Left => "--left-of",
96        RelativePosition::Right => "--right-of",
97        RelativePosition::Above => "--above",
98        RelativePosition::Below => "--below",
99    };
100
101    // force the primary monitor
102    spawn(format!("xrandr --output {} --primary --auto", primary))?;
103
104    match status {
105        "disconnected" => spawn(format!("xrandr --output {} --off", secondary)),
106        "connected" => spawn(format!(
107            "xrandr --output {} --auto {} {}",
108            secondary, position_flag, primary
109        )),
110        _ => Ok(()),
111    }
112}