[][src]Module penrose::core::hooks

Hook for adding additional functionality around standard WindowManager actions

Overview

Hooks are the primary way of injecting custom functionality into penrose when you want to go beyond simply binding actions to key presses. There are multiple points in normal WindowManager execution that will trigger the running of user defined hooks, during which you will have complete control over the window manager state and (importantly) block the event loop until your hook exits. For details of what hook points are available, see each of the trait methods outlined below. Note that a single Hook can register itself to be called at multiple hook points (all, if desired!) and that hooks are allways called in the order that they are registered with the WindowManager on init (i.e. the order of the Vec itself).

Implementing Hook

As an example of how to write a hook and register it, lets implement a simple hook that logs each new client that is added to a particular workspace, noting if we've seen it before or not. Completely pointless, but it will serve as a nice starting point to show what is happening.

use penrose::{
    core::{
        data_types::WinId,
        hooks::Hook,
        xconnection::XConn
    },
    xcb::XcbConnection,
    Config, Result, WindowManager, logging_error_handler
};

use std::collections::{HashMap, HashSet};

// Start with the struct itself which will contain any internal state we need to track
pub struct LogAddedClients {
    seen: HashMap<usize, HashSet<WinId>>,
}

// It is idiomatic for Hooks to provide a `new` method that returns a pre-boxed struct
// so that you can add it straight into your hooks Vector in your main.rs
impl LogAddedClients {
    pub fn new() -> Box<Self> {
        Box::new(Self { seen: HashMap::new() })
    }
}

// As we only care about one of the hook points, that is the only method we need to
// implement: all other Hook methods for this struct will be no-ops
impl<X: XConn> Hook<X> for LogAddedClients {
    fn client_added_to_workspace(
        &mut self,
        wm: &mut WindowManager<X>,
        id: WinId,
        wix: usize
    ) -> Result<()> {
        let clients = self.seen.entry(wix).or_insert(HashSet::new());
        let msg = if clients.contains(&id) {
            format!("'{}' has been on '{}' before!", id, wix)
        } else {
            clients.insert(id);
            format!("'{}' was added to '{}' for the first time", id, wix)
        };

        wm.log(&msg)
    }
}

// Now we simply pass our hook to the WindowManager when we create it
fn main() -> penrose::Result<()> {
    let mut manager = WindowManager::new(
        Config::default(),
        XcbConnection::new()?,
        vec![LogAddedClients::new()],
        logging_error_handler()
    );

    manager.init()?;

    // rest of your startup logic here

    Ok(())
}

Now, whenever a Client is added to a Workspace (either because it has been newly created, or because it has been moved from one workspace to another) our hook will be called, and our log message will be included in the penrose log stream. More complicated hooks can be built that listen to multiple triggers, but most of the time you will likely only need to implement a single method. For an example of a more complex set up, see the Scratchpad extension which uses multiple hooks to spawn and manage a client program outside of normal WindowManager operation.

When hooks are called

Each Hook trigger will be called as part of normal execution of WindowManager methods at a point that should be relatively intuitive based on the name of the method. Each method provides a more detailed explanation of exactly what conditions it will be called under. If you would like to see exactly which user level actions lead to specific triggers, try turning on DEBUG logging in your logging config as part of your main.rs and lookk for the "Running hooks" message that each trigger logs out.

Please see the documentation on each of the individual methods for more details.

WindowManager execution with user defined Hooks

As mentioned above, each time a hook trigger point is reached the WindowManager stops normal execution (including responding to XEvents) and each of the registered hooks is called in turn. If the hook implements the method associated with the trigger that has been hit, then your logic will be run and you will have a mutable reference to the current WindowManager state, giving you complete control over what happens next. Note that method calls on the WindowManager itself will (of course) resolve immediately, but that any actions which generate XEvents will only be processed once all hooks have run and control has returned to the manager itself.

Traits

Hook

User defined functionality triggered by WindowManager actions.

Type Definitions

Hooks

Utility type for defining hooks in your penrose configuration.