Giter Club home page Giter Club logo

penrose's Introduction

penrose - a tiling window manager library

Build crates.io version docs.rs Book Build

Penrose is a modular library for configuring your own X11 window manager in Rust.

This means that, unlike most other tiling window managers, Penrose is not a binary that you install on your system. Instead, you use it like a normal dependency in your own crate for writing your own window manager. Don't worry, the top level API is well documented and a lot of things will work out of the box, and if you fancy digging deeper you'll find lots of opportunities to customise things to your liking.

screenshot

tl;dr - getting started

The user facing docs for penrose are written using mdBook and published to GitHub Pages here. They cover some more general concepts about how to get up and running as opposed to the crate docs on docs.rs which are more aimed at covering the APIs themselves and are what you would expect from an open source library.

The current version of the docs build from the head of the develop branch can be found here.

If you want to have a look at how it all comes together then the examples directory of this repo has several different starting points for you to begin with and my personal set up can be found here. (You almost certainly don't want to use my set up in full but it should serve as a good reference for what a real use case looks like!)

Discord

First and foremost, you should refer to the docs (both the book and API docs) if you are looking to learn about Penrose and how it all works. The GitHub issue tracker is also an excellent resource for learning about solutions to common problems. That said, I have had repeated requests for somewhere that people can have more open ended discussions about things and chat about their individual set ups so with that in mind, you can join us on discord here if that sounds like your thing.


Project Goals

Understandable code

Penrose was born out of my failed attempts to refactor the dwm codebase into something that I could more easily understand and hack on. While I very much admire and aim for minimalism in code, I personally feel that it becomes a problem when your code base starts playing code golf to keep things short for the sake of it.

I certainly won't claim that Penrose has the cleanest code base you've ever seen, but it should be readable in addition to being fast.

Simple to configure

I've also tried my hand at Xmonad in the past. I love the setups people can achieve with it (this one is a personal favourite), but doing everything in Haskell was a deal breaker for me. I'm sure many people will say the same thing about Rust, but then at least I'm giving you some more options!

With Penrose, a simple window manager can be written in about 5 minutes and under 100 lines of code. It will be pretty minimal, but each additional feature (such as a status bar, scratch-pads, custom layouts, dynamic menus...) can be added in as little as a single line. If the functionality you want isn't available however, that leads us on to...

Easy to extend

dwm patches, qtile lazy APIs, i3 IPC configuration; all of these definitely work but they are not what I'm after. Again, the Xmonad model of companion libraries that you bring in and use as part of writing your own window manager has always felt like the right model for me for extending the window manager.

Penrose provides a set of traits and APIs for extending the minimal core library that is provided out of the box. By default, you essentially get an event loop and a nice clean split between the "pure" state manipulation APIs for managing your windows and a "diff and render" layer that interacts with the X server. There are enough built-in pieces to show how everything works and then some more interesting / useful code available in the extensions module.


Project Non-goals

An external config file

Parsing a config file and dynamically switching behaviour on the contents adds a large amount of complexity to the code, not to mention the need for validating the config file! By default, Penrose is configured statically in your main.rs and compiled each time you want to make changes (similar to Xmonad and dwm). There is no built-in support for hot reloading of changes or wrappers around the main window manager process.

That said, the extensibility of Penrose means that you are definitely able to define your own config file format and parse that as part of your startup, if that is something you want.

The choice is yours!

IPC / relying on external programs for core functionality

There are several places where Penrose makes use of external programs for utility functionality (reading the user's key-map or spawning a program launcher for example), but core window manager functionality is contained in the pure state data structures. This makes it a lot simpler to maintain the codebase and (importantly) provide a nice API to work with for extending the behaviour of your window manager.

As you might expect, you can definitely write your own extensions that provide some sort of IPC or client/server style mechanism if you want to mirror the kinds of things possible in other window managers such as i3 or bspwm, but that is not going to be supported in the core of the library itself.

penrose's People

Contributors

adder32 avatar albheim avatar beauslm avatar benjamin-davies avatar danacus avatar icelk avatar jasperspahl avatar jgraef avatar netzwerk2 avatar niloco avatar psychon avatar rational-curiosity avatar rinkimekari avatar siph avatar sminez avatar spyrosroum avatar stabor705 avatar sudormrfbin avatar teo8192 avatar tethyssvensson avatar thebashpotato avatar tshepang avatar zunixorn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

penrose's Issues

cycle_client no longer works in a hook

Describe the bug
between now and yesterday calling the cycle_client function from a remove client hook seems to have become a no op

To Reproduce
Steps to reproduce the behavior:
with the hook in place open multiple windows and then close them.

The expected behaviour is that the focus would change in the given direction but at present the focus remains the same regardless of the value passed to cycle_client. Neither Forward or backward make any change.

Additional context
This is the hook that I am using. Even if it is reduced to just the cycle_client command it has no effect now bt was working yesterday 15/9

struct MyHook {}
impl Hook for MyHook {
    fn remove_client(&mut self, _wm: &mut WindowManager, _id: WinId) {
        // check if we need to do something
        // if not bail else move down
        let ws = match _wm.workspace(&Selector::Focused) {
            Some(ws) => ws,
            None => return 
        };

        let the_len =  ws.len();
        
        if the_len < 2 { return; }

        let cref = _wm.client(&Selector::Focused).unwrap();
        let end_id = match _wm.client(&Selector::Index(the_len-1)) {
            Some(c) => c.id(),
            None => return
        };


         // check if this is the last item
         if cref.id() == end_id {
             return;
         }

        // swap down

        _wm.cycle_client(Direction::Backward);
    }
}

Layout coding: hiding a window

I've been trying to implement the monocle layout on penrose, and I'm stuck on how to hide windows.

The monocle layout - as far as I'm concerned - works this way: if a window is focused, it occupies the entire space reserved for the layout; and if it is not focused, it's just hidden.

I've tried to hide a window by setting its size to zero, but it doesn't seem to work. Would you happen to know what is happening?

Here's the function:

use penrose::client::Client;
use penrose::data_types::{Region, ResizeAction, WinId};
use penrose::layout::client_breakdown;

pub fn really_cool_layout(
    clients: &[&Client],
    focused: Option<WinId>,
    monitor_region: &Region,
    _: u32,
    _: f32,
) -> Vec<ResizeAction> {
    if let Some(fid) = focused {
        let (mx, my, mw, mh) = monitor_region.values();
        clients
            .iter()
            .map(|c| {
                let cid = c.id();
                if cid == fid {
                    (cid, Region::new(mx, my, mw, mh))
                } else {
                    (cid, Region::new(mx, my, 0, 0))
                }
            })
            .collect()
    } else {
        Vec::new()
    }
}

Reposition client

Floating clients should be repositionable using key bindings .

A/C

  • Action for moving a floating client by a specified delta is available as a key event handler

changing the order that windows are added

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
My current work flow is that I tend to have a main window open and then open secondary windows for running compiles or reading documentation.

It appears that this is not possible at present with penrose unless it can be done using an add/remove hook

Describe the solution you'd like
A clear and concise description of what you want to happen.
The ideal would be a simple flag that I could set that would cover the adding and removal of windows

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Using a hook to do this would be fine if there was an example of how this would work

Additional context
Add any other context or screenshots about the feature request here.
At hne moment I am loving the look of penrose and it covers most of the things that I was intending putting into the window manager that I started. The only thing missing other than the above would be the ability to change some internal viarables aka bspwm but that is just a thing that would be nice rather than anything that is really needed

gen_keybindings macro doesn't work properly if run_internal is provided before run_external

Describe the bug
I had to miss a lot of details on the title, so let me explain.
I've noticed that, in the gen_keybindings macro, if any keybinding entry with a run_internal action is provided before a keybinding entry with a run_external action, the macro seems to generate code that results in closure type mismatches.

Working Example
First of all, a simple working main.rs example:

#[macro_use]
extern crate penrose;

use penrose::{Config, Forward, WindowManager, XcbConnection};

fn main() {
    let config = Config::default();
    let key_bindings = gen_keybindings! {
        "a" => run_external!("something"),
        "b" => run_internal!(cycle_client, Forward);

        forall_workspaces: config.workspaces => {
            "M-{}" => focus_workspace,
            "M-S-{}" => client_to_workspace,
        }
    };

    let conn = XcbConnection::new();
    let mut wm = WindowManager::init(config, &conn);
    wm.grab_keys_and_run(key_bindings);
}

Non-working example

#[macro_use]
extern crate penrose;

use penrose::{Config, Forward, WindowManager, XcbConnection};

fn main() {
    let config = Config::default();
    let key_bindings = gen_keybindings! {
        "b" => run_internal!(cycle_client, Forward);

        forall_workspaces: config.workspaces => {
            "M-{}" => focus_workspace,
            "M-S-{}" => client_to_workspace,
        }
    };

    let conn = XcbConnection::new();
    let mut wm = WindowManager::init(config, &conn);
    wm.grab_keys_and_run(key_bindings);
}

Compiler error message:

error[E0308]: mismatched types
  --> src/main.rs:8:24
   |
8  |       let key_bindings = gen_keybindings! {
   |  ________________________^
9  | |         "b" => run_internal!(cycle_client, Forward);
10 | |
11 | |         forall_workspaces: config.workspaces => {
...  |
14 | |         }
15 | |     };
   | |_____^ expected closure, found a different closure
   |
   = note: expected closure `[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10]`
              found closure `[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10 i:_]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace
for more info)

error[E0308]: mismatched types
  --> src/main.rs:8:24
   |
8  |       let key_bindings = gen_keybindings! {
   |  ________________________^
9  | |         "b" => run_internal!(cycle_client, Forward);
10 | |
11 | |         forall_workspaces: config.workspaces => {
...  |
14 | |         }
15 | |     };
   | |_____^ expected closure, found a different closure
   |
   = note: expected closure `[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10]`
              found closure `[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10 i:_]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace
for more info)

error[E0308]: mismatched types
  --> src/main.rs:19:26
   |
9  |         "b" => run_internal!(cycle_client, Forward);
   |                ------------------------------------ the found closure
...
19 |     wm.grab_keys_and_run(key_bindings);
   |                          ^^^^^^^^^^^^ expected trait object `dyn std::ops::FnMut`, found closure
   |
   = note: expected struct `std::collections::HashMap<_, std::boxed::Box<(dyn for<'r, 's> std::ops::FnMut(&'r mut penrose::WindowManager<'s>) + 'static)>>`
              found struct `std::collections::HashMap<_, std::boxed::Box<[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10]>>`

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0308`.
error: could not compile `penrose-debugging`.

To learn more, run the command again with --verbose.

If simply swapping the run_external and run_internal entries' places, the error is similar, but slightly different:

error[E0308]: mismatched types
  --> src/main.rs:10:16
   |
9  |         "b" => run_internal!(cycle_client, Forward),
   |                ------------------------------------ the expected closure
10 |         "a" => run_external!("something");
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected closure, found trait object `dyn std::ops::FnMut`
   |
   = note: expected struct `std::boxed::Box<[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10]>`
              found struct `std::boxed::Box<(dyn for<'r, 's> std::ops::FnMut(&'r mut penrose::WindowManager<'s>) + 'static)>`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace
for more info)

error[E0308]: mismatched types
  --> src/main.rs:8:24
   |
8  |       let key_bindings = gen_keybindings! {
   |  ________________________^
9  | |         "b" => run_internal!(cycle_client, Forward),
10 | |         "a" => run_external!("something");
11 | |
...  |
15 | |         }
16 | |     };
   | |_____^ expected closure, found a different closure
   |
   = note: expected closure `[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10]`
              found closure `[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10 i:_]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace
for more info)

error[E0308]: mismatched types
  --> src/main.rs:8:24
   |
8  |       let key_bindings = gen_keybindings! {
   |  ________________________^
9  | |         "b" => run_internal!(cycle_client, Forward),
10 | |         "a" => run_external!("something");
11 | |
...  |
15 | |         }
16 | |     };
   | |_____^ expected closure, found a different closure
   |
   = note: expected closure `[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10]`
              found closure `[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10 i:_]`
   = note: no two closures, even if identical, have the same type
   = help: consider boxing your closure and/or using it as a trait object
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace
for more info)

error[E0308]: mismatched types
  --> src/main.rs:20:26
   |
9  |         "b" => run_internal!(cycle_client, Forward),
   |                ------------------------------------ the found closure
...
20 |     wm.grab_keys_and_run(key_bindings);
   |                          ^^^^^^^^^^^^ expected trait object `dyn std::ops::FnMut`, found closure
   |
   = note: expected struct `std::collections::HashMap<_, std::boxed::Box<(dyn for<'r, 's> std::ops::FnMut(&'r mut penrose::WindowManager<'s>) + 'static)>>`
              found struct `std::collections::HashMap<_, std::boxed::Box<[closure@/home/yohanan/.cache/cargo/git/checkouts/penrose-073b20e3311c97ea/e570756/src/core/macros.rs:27:18: 29:10]>>`

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0308`.
error: could not compile `penrose-debugging`.

To learn more, run the command again with --verbose.

Desktop (please complete the following information):

  • OS: GNU/Linux
  • Distribution: Arch
  • Version: 5.7.8-arch1-1

Additional context
Since it's a compile-time error, there's no additional context I could place here.

Feature request: Dynamic workspace creation

I'm currently using xmonad with a combination of DynamicWorkspaces and TopicSpace. The first one allows for modifying the list of workspaces at runtime, while the second one means that I do not have to care about the order of the workspaces while I am using xmonad.

Would you be interested in supporting these use-cases at some point in the future? And would you accept PRs that works towards this goal?

Allow access to Clients by ID or selection function

Similar to #15 , penrose should expose a way for users to get reference to specific Clients either by ID or Selector:

pub fn client(&self, selector: Selector<Client>) -> Option<&Client>;
pub fn client_mut(&mut self, selector: Selector<Client>) -> Option<&mut Client>;
pub fn clients_where(&self, selector: Selector<Client>) -> Vec<&Client>;
pub fn clients_where_mut(&mut self, selector: Selector<Client>) -> Vec<&mut Client>;

Cargo does not make a binary

when i run cargo build it does not create a binary to run in target/debug or target/release idk if that my fault or some kind of bug of the project

X atom types should be an enum that can be converted to a &str

Currently, the X atoms are held as a simple slice of static strings that are then referenced an copied in multiple places. These should really be an enum that can be converted to a &str in a similar way to WindowType in the draw module:

    /// An EWMH Window type
    pub enum WindowType {
        /// A dock / status bar
        Dock,
        /// A menu
        Menu,
        /// A normal window
        Normal,
    }
    impl WindowType {
        pub(crate) fn as_ewmh_str(&self) -> &str {
            match self {
                WindowType::Dock => "_NET_WM_WINDOW_TYPE_DOCK",
                WindowType::Menu => "_NET_WM_WINDOW_TYPE_MENU",
                WindowType::Normal => "_NET_WM_WINDOW_TYPE_NORMAL",
            }
        }
    }

This would also require altering struct / function definitions to use the new enum and updating existing uses in the code base.

applications should have penrose as a parent

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
At present it appears that if yo attemmpt to restart penrose then all applications that have been spawned by penrose are killed

Describe the solution you'd like
A clear and concise description of what you want to happen.
I wold prefer that the parent is set to init as is done with many other window managers.

This would allow penrose to use an exit hook and dump its crrent state to disk that could then be read in by the replacement instance.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
If there ase plants for another restart system that would better handle this I would be happy with that as well.

Additional context
Add any other context or screenshots about the feature request here.

New tiling mode: Paper

Im impressed with what you have acomplished in such a short time!
I have a feature request that could make Penrose really stick out from the crowd, if you are interested, and able.
Check out the gnome addon PaperWM; it is a paper-tiler written in JS. I tried for a while, and it is awesome. My work flow felt so smooth. I just cant use gnome...
I dont believe any true tiling wm has this feature and it could make Penrose really stand out.
The original idea apparently came from here: http://10gui.com/

Options to disable focus on mouse hover & mouse repositioning on window focus switching

Hello again! Before anything, I'd like to thank you for starting this project - it's been pretty fun to use and configure penrose, and it's pretty close to becoming my primary window manager.

So, there are two related mechanics on penrose which there doesn't seem to have options to disable them:

  • Focus on mouse hover: when the mouse cursor hovers over a window, the focus goes to said window.
  • Mouse repositioning on window focus switching: when a window is focused via keyboard (with the cycle_client and drag_client commands), the mouse cursor is automatically placed at the center of said focused window.

To solve that there could be two new related bool fields in the penrose::core::data_types::Config struct - something like focus_on_hover and reposition_cursor_on_focus.

dwm style tags extension

Using the existing hook points, it should be possible to implement a dwm style tags system instead of the default workspaces. This would require tracking the active tags for each window (defaulting to a single tag) and intercepting workspace switching to ensure that the correct windows were visible at all times. I'm not sure if the current Workspaces status bar widget would be able to handle displaying the active tags in the same way as the built in dwm bar but a modified widget should be possible.

layouts with follow_focus enabled should not trigger when focus enters a client they don't manage

This appears to be the underlying cause for #47 where the paper layout (with follow_focus enabled) and the scratchpad extension being used together results in infinite toggling between the layout windows and the scratchpad. Layouts should only be paying attention to windows managed by the workspace they are active for so the check around follow_focus should also be making sure that the client that just gained focus is actually managed by the active workspace.

Simple dwm style status bar

Is your feature request related to a problem? Please describe.
Currently Penrose requires an external status bar (such as Polybar or Lemonbar) and we don't yet fully support all of the EWMH proerties required to make that experience seemless. This also relies on configuring said bar externally and managing it.

Describe the solution you'd like
Ideally we want to have an easy to use / configure bar that makes use of the Hooks system but initially, an inbuilt dwm style bar that is driven by xsetroot would be nice (especially as existing dwm users can re-use their existing set ups). Going forward, I'd like to get my old Qtile bar working again but that'll be quite a bit more work I suspect.

Describe alternatives you've considered
Currently using Polybar which is fine but I want to have something a little more minimal and the build of Polybar in arch has now broken on me twice since I started using it due to external dependencies changing...

Additional details
It would be nice if eventually this can be a stand alone bar that runs without Penrose but that can be extracted at a later date. At that point, moving the bar (and maybe the draw abstraction?) to its own Crate is probably a good idea.

Requirements

  • New hook added for when the root window name is updated (to allow for re-render when the user updates from their external process)
  • Extract initial low level drawing logic out into a new trait so that it doesn't have to be the default XCB implementation if users don't want it.
  • Refactor current dummy implementation from draw to use the new traits
  • Simple, default status bar widgets:
    • Workspaces (indicating focused and those with active clients)
    • Focused window title (optional icon / default if not focused etc)
    • Static text

client_to_screen WindowManager method

One method that is currently missing from WindowManager is client_to_screen (analogous to client_to_workspace). It should exhibit the same behaviour of being a no-op if the target screen is not available so that end users can write safe keybindings.

Reparented zombies

#6 has mostly been resolved, but it's still possible to get zombies. Here's how: a common way to start window managers is to exec it as the last command in ~/.xinitrc. If any background process you spawned before exec hasn't finished, then it will be adopted by penrose as a zombie since penrose is unaware of its existence and doesn't configure relevant signal handling (as suggested in #6).

xmodmap
set-wallpaper&
exec penrose

In this example, set-wallpaper can turn into a penrose zombie.

Multi-monitor support

Really like the look of this. Iโ€™m currently an Awesome user but could see myself switching to Penrose in the future. I have a dual screen set up so multi-monitor support is one thing preventing me switching. You mentioned that multi-monitor support isnโ€™t working yet in the video but I figured it would be worth having an issue to track it.

InsertPosition::Index() documentation is incorrect

Describe the bug
when used as documented InsertPosition::Indexgices a bounds error

The documentation for this says that it will add to the end of the list if out of bounds but this check is not done so it will crash on the first window being opened if it is set to anything other than 0 making it non functional.
If applicable, add screenshots to help explain your problem.

Additional context

  • debug log output

Bar transparency

I like transparent status bars, so I made some changes to allow transparency which can be found here.

Some pretty significant changes to xcb_util and the draw module are required to make it work.

I'm not sure if you are interested in adding this feature to penrose. If not I'll just keep it in my own fork and close this issue.

Resize client

Floating windows are currently restricted to being the size that they were upon initial creation of when they were set to floating. It would be useful to be able to alter the size of floating windows

Describe the change / addition you'd like to see made

It should be possible to set the current size of a client through predefined increments (inc left/right/up/down by n pixels for example).

AC

  • A new position can be set absolutely (the float method on StackSet handles this)
  • Changes can be made to an existing floating window position through a custom action

Status bars should be redrawn on ExposeNotify

Currently things like screensavers wipe out the status bar until the next re-draw triggers via a hook. penrose should be listening for expose notify events from the X server and surfacing them via a hook providing the screen that received the event (there probably isn't any default actions we need or want to set up in the main WindowManager logic for this one). Status bars can then register for that hook to trigger the re-draw.

example of binding a key to a function that changes a config item

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
I have a funuction

fn inc_dec_gap_size(config : &mut Config, offset : i32) {
if config.gap_px > 0 {
config.gap_px = (config.gap_px as i32 + offset) as u32;
}
}

that I would like to bind to a key like

    "M-plus" => inc_dec_gap_size(&mut config, 1),

Describe the solution you'd like
A clear and concise description of what you want to happen.
I would like an example that show how to do this as the above does not work

Zombie processes

The way penrose works right now, run_external!() will produce zombie processes. In order to avoid that, one can install a signal handler function or simply configure SIGCHLD to be ignored. For instance, adding this after the WindowManager::init() call is sufficient:

unsafe { signal::signal(Signal::SIGCHLD, SigHandler::SigIgn) }.unwrap();

Tested with penrose 0.1.5 and nix 0.17.0

Alternatively, run_external!() could wait for process completion, but I don't think that would fit into the overall design.

looking for info

Is there an easy way to get the index position of a window given a window id

I am writing a hook to adjust the focsed window when the current window is closed and I need the position of the window in the stack to deal with the corner case where the window is the last window in the stack.

Keybindings issue

Hey,
I have a strange issue with the keybindings. I have tried to run your personal config(my-penrose-config) and the M-j, M-k, the keys for workspaces... are not working properly. For example, the M-j key, normally assigned to cycle clients forward, is assigned in my build to cycling screen(or something similar) and I have tried to remap it to M-t and the M-t is cycling forward correctly(but M-j keeps doing the cycle_screen even though I don't have anything binded on it anymore).
I have also tried to map the M-d shortcut to rofi but the mapping is not working(but with M-r for example, no problem).

It's quite hard to describe and my English is not perfect so if you want more precision and informations feel free to ask me.

Here is an example of my config :

/**
 * My personal penrose config (build from the head of develop)
 */
#[macro_use]
extern crate penrose;

use penrose::helpers::spawn;
use penrose::hooks::LayoutHook;
use penrose::layout::{bottom_stack, paper, side_stack};
use penrose::{
    Backward, Config, Forward, Layout, LayoutConf, Less, More, WindowManager, XcbConnection,
};
use simplelog::{LevelFilter, SimpleLogger};
use std::env;
use std::process::Command;

fn main() {
    SimpleLogger::init(LevelFilter::Info, simplelog::Config::default()).unwrap();

    let mut config = Config::default();
    config.workspaces = &["1", "2", "3", "4", "5", "6", "7", "8", "9"];
    config.fonts = &["Fira Code:size=10", "Iosevka Nerd Font:size=10"];
    config.floating_classes = &["rofi", "dmenu", "dunst", "polybar", "pinentry-gtk-2"];

    let follow_focus_conf = LayoutConf {
        floating: false,
        gapless: true,
        follow_focus: true,
    };
    let n_main = 1;
    let ratio = 0.6;
    config.layouts = vec![
        Layout::new("[side]", LayoutConf::default(), side_stack, n_main, ratio),
        Layout::new("[botm]", LayoutConf::default(), bottom_stack, n_main, ratio),
        Layout::new("[papr]", follow_focus_conf, paper, n_main, ratio),
        Layout::floating("[----]"),
    ];

    let script_path = |script| format!("{}/.config/scripts/{}", env::var("HOME").unwrap(), script);
    let power_script = script_path("power-menu.sh");

    let power_menu = Box::new(move |wm: &mut WindowManager| {
        match Command::new(&power_script).output() {
            Ok(choice) => {
                match String::from_utf8(choice.stdout).unwrap().as_str() {
                    "restart-wm\n" => wm.exit(),
                    _ => (), // other options are handled by the script
                }
            }
            Err(_) => return, // user exited without making a choice
        };
    });

    let key_bindings = gen_keybindings! {
        // Program launch
        //"M-d" => run_external!("rofi -show drun"),
        "M-r" => run_external!("/home/finch/.config/scripts/rofi-apps"),
        //"M-d" => run_external!("rofi -show drun"),
        "M-Return" => run_external!("kitty"),

        // actions
        "M-A-s" => run_external!("screenshot"),
        "M-A-k" => run_external!("toggle-kb-for-tada"),
        "M-A-l" => run_external!("lock-screen"),
        "M-A-m" => run_external!("xrandr --output HDMI-1 --auto --right-of eDP-1 "),

        // client management
        "M-t" => run_internal!(cycle_client, Forward), // With M-t, it's working ! But pressing M-j still do the cycle_screen even though this key is not bind in this config file.
        "M-k" => run_internal!(cycle_client, Backward), // cycle_screen ? 
        "M-S-j" => run_internal!(drag_client, Forward), // Same
        "M-S-k" => run_internal!(drag_client, Backward), // Same
        "M-q" => run_internal!(kill_client),

        // workspace management
        "M-Tab" => run_internal!(toggle_workspace),
        "M-bracketright" => run_internal!(cycle_screen, Forward),
        "M-bracketleft" => run_internal!(cycle_screen, Backward),
        "M-S-bracketright" => run_internal!(drag_workspace, Forward),
        "M-S-bracketleft" => run_internal!(drag_workspace, Backward),

        // Layout & window management
        "M-grave" => run_internal!(cycle_layout, Forward),
        "M-S-grave" => run_internal!(cycle_layout, Backward),
        "M-A-Up" => run_internal!(update_max_main, More),
        "M-A-Down" => run_internal!(update_max_main, Less),
        "M-A-Right" => run_internal!(update_main_ratio, More),
        "M-A-Left" => run_internal!(update_main_ratio, Less),
        "M-A-C-Escape" => run_internal!(exit),
        "M-A-Escape" => power_menu;

        forall_workspaces: config.workspaces => { // Not working at all
            "M-{}" => focus_workspace,
            "M-S-{}" => client_to_workspace,
        }
    };

    // Set the root X window name to be the active layout symbol so it can be picked up by polybar
    let active_layout_as_root_name: LayoutHook = |wm: &mut WindowManager, _, _| {
        wm.set_root_window_name(wm.current_layout_symbol());
    };
    config.layout_hooks.push(active_layout_as_root_name);

    let conn = XcbConnection::new();
    let mut wm = WindowManager::init(config, &conn);

    spawn(script_path("penrose-startup.sh"));
    active_layout_as_root_name(&mut wm, 0, 0);
    wm.grab_keys_and_run(key_bindings);
}

exposing the ring rotate feature at the manager level

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
At present the bility to rotate the stack is present in the Ring data type but it is not present at a level where it can be used for binding to a key.

This would be useful in implementing window movements like rotating windows through the master area with a single key press.

Describe the solution you'd like
A clear and concise description of what you want to happen.
I would like to have a simple function that takes a direction allowing for the windows of the stack to be rotated without moving the the focus from the pane that currently has iit

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

I have looked at doing this with varios window shuffling methods but they would be messy especially since the functionality already exists to do this cleanly.

Additional context
Add any other context or screenshots about the feature request here.

Selectors for all matching items not just the first

Each of the existing Selector based methods on WindowManager (workspace, client & mut variants) should also have corresponding all_*s variants that return a Vec of all matching elements rather than just the first. This would be useful for things such as cycling through all matching examples of a certain program for example (browser windows, terminals etc) or cycling through only occupied workspaces.

Could not resolve keysym XF86FullScreen

Im trying to run this on Alpine 3.12 x86_64 musl.
Host is a ThinkPad T410, 4gb ram, quad core 2.4ghz cpu.
When trying to run this with startx, I get the following error
The XKEYBOARD keymap compiler reports:

Internal error: Could not resolve keysym XF86FullScreen
Errors from xkbcomp are not fatal to the X server
xinit: connection to X server lost.

Im running on nightly rust, using the config in the example dir.
Also pulling the latest penrose from the git repo, rather than the crates.io, since thats outdated.
If you want any more information ill be happy to leave it here :)

Restart functionality, a la AwesomeWM

Describe the change / addition you'd like to see made

It'd be great if it was possible to restart a Penrose WM without closing and reopening everything, the same as you can with AwesomeWM. That way when you add some new hotkey you can simply run cargo install --path . followed by whatever hotkey is configured to trigger the restart action.

Describe alternatives you've considered

Alternatively I can just keep doing it the way I have been; logging out and back in.

Additional context

I have no idea if this is even possible to do, it could be that Awesome does it by cheating (not really restarting, but merely dropping state and re-reading the lua script) and that I'm asking the impossible.

One way or the other, keep up the great work, Penrose is a thing of beauty! If there's a way I can help I'd be glad to have a go at it.

Is it like Xmonad in terms of how configuration is done?

I am thinking about giving penrose a try. But I have a few questions regarding this project.

  1. Is it like XMonad where the user has the source code with them and they use a main file to code the tiling Window Manager in an empty file in a high level manner?
  2. Is it possible to add window decorations, just like with awesome WM?
    Full window
  3. Is it possible to add round corners around the windows?
  4. Does it come with other stuff such as a taskbar?
  5. Is it as easy to script penrose as it is with XMonad?

status bars partially overwritten

Describe the bug
As far as I can tell this is happening when new windows are created: approximately the first third of the bar is set to black but is correctly rendered on the next redraw. I haven't been able to confirm but I think this is due to new windows being mapped before they are initially positioned by the active layout.

To Reproduce
Steps to reproduce the behavior:

  1. Start penrose running with an active StatusBar containing a single Text widget long enough to fill the screen.
  2. Open a new window (e.g. terminal)

Expected behavior
Status bars are not overwritten

Desktop (please complete the following information):

  • OS: Linux
  • Distribution Arch

Additional context

  • minimal main.rs
#[macro_use]
extern crate penrose;

use penrose::{
    draw::{dwm_bar, TextStyle, XCBDraw},
    layout::{side_stack, Layout, LayoutConf},
    Backward, Config, Forward, Result, WindowManager, XcbConnection,
};
use simplelog::{LevelFilter, SimpleLogger};

const HEIGHT: usize = 18;
const PROFONT: &'static str = "ProFont For Powerline";

const BLACK: u32 = 0x282828;
const GREY: u32 = 0x3c3836;
const WHITE: u32 = 0xebdbb2;
const BLUE: u32 = 0x458588;

fn main() -> Result<()> {
    SimpleLogger::init(LevelFilter::Debug, simplelog::Config::default())?;
    let mut config = Config::default();
    config.hooks.push(Box::new(dwm_bar(
        Box::new(XCBDraw::new()?),
        0,
        HEIGHT,
        &TextStyle {
            font: PROFONT.to_string(),
            point_size: 11,
            fg: WHITE.into(),
            bg: Some(BLACK.into()),
            padding: (2.0, 2.0),
        },
        BLUE,
        GREY,
        &config.workspaces,
    )?));

    config.layouts = vec![Layout::new(
        "[side]",
        LayoutConf::default(),
        side_stack,
        1,
        0.6,
    )];

    let key_bindings = gen_keybindings! {
        "M-semicolon" => run_external!("dmenu_run"),
        "M-Return" => run_external!("st"),
        "M-S-q" => run_internal!(kill_client),
        "M-A-Escape" => run_internal!(exit);

        forall_workspaces: config.workspaces => {
            "M-{}" => focus_workspace,
            "M-S-{}" => client_to_workspace,
        }
    };

    let conn = XcbConnection::new()?;
    let mut wm = WindowManager::init(config, &conn);
    wm.grab_keys_and_run(key_bindings);

    Ok(())
}

Tiny workspace after changing monitors

Context

My greeter (I assume) (gdm3) detects all my monitors, but I really only want to use my 4k external monitor.

What happens

When I just start, what I get is all the windows take up all the space on both screens (mirrored screens), but it looks low resolution on the 4k monitor.

The way I normally switch monitors on i3 is:

xrandr --output eDP-1 --off
xrandr --output DP-3 --mode 3840x2160

Using the penrose example, this results in a really low resolution workspace in the top left of my 4k, and I can't figure out how I'd resize it.

focus bouncing when playiing video in monocle mode

Describe the bug
A clear and concise description of what the bug is.
when playing a video using mpv in monocle mode the focus will bounce between the video and the other window. This may be related to #52

If the mouse pointer is moved to the status bar this stops

To Reproduce
Steps to reproduce the behavior:
switch to monocle layout
open a terminal
run mpv -vo xv video,mp4
switch to another workspace and back

Desktop (please complete the following information):

  • OS: linux
  • Distribution debian
  • Version sid

Be able to toggle fullscreen


name: Toggle Fullscreen and Client side Fullscreen
about: I want to be able to toggle fullscreen with a keybind like M-f. And for the client such as firefox to be able to fullscreen itself such as when user fullscreens a video on youtube.
title: Toggle Fullscreen and Client side Fullscreen
labels: enhancement


Is your feature request related to a problem? Please describe.
I would like to be able to fullscreen a client or at least put it in a monocle. And also for some reason when I fullscreen in firefox, it can't, and it also seems that the browser itself slows down when I try to fullscreen. I assume it is waiting for the window manager to fulfill the request, and it slows down as a result while waiting. I'm just guessing though.

Describe the solution you'd like
Have a public function that allows for toggling fullscreen, or at least have clients request to fullscreen.

handle mouse events

penrose doesn't currently listen to any mouse events which blocks things like floating mode and click to focus. The window manager needs to take action based on user key bindings for at least:

  • reposition client
  • resize client

Click to focus would be nice as well but that is tied up with disabling focus follow mouse and all of the edge cases that come with that so it is probably best left for another issue.

Key input not captured when numlock is enabled.

Describe the bug
Whenever numlock is enabled, penrose stops capturing key events.

To Reproduce
Steps to reproduce the behavior:

  1. Enable numlock
  2. Try to use any configured key binding
  3. Observe that penrose ignores the key event and it will be passed to the focused client

Expected behavior
I expected penrose to respond to these key events regardless of whether numlock is enabled.

Desktop (please complete the following information):

  • OS: Linux
  • Distribution Arch Linux

Additional context

  • The minimal example can be used to reproduce the issue.
  • Here is the log where enabled numlock and pressed several keybinds. None of those are shown in the log. The only one it outputs is the exit keybind after I disabled numlock. penrose log
  • xmodmap -pke

Do you know why this might be happening? I'll do some more debugging and post more information if I find something.

Expose access to WindowManager.workspaces for user defined hooks and bindings

It would be really useful to have the following public methods available on the WindowManager, along with a new Selector enum to allow for either direct indexing or talking about elements based on their properties.

pub enum Selector<T> {
    Index(usize),
    WinId(WinId),  // see #16
    Condition(fn(&T) -> bool),
}

pub fn add_workspace(&mut self, index: usize, ws: Workspace);
pub fn remove_workspace(&mut self, selector: Selector<Workspace>) -> Option<Workspace>;
pub fn workspace(&self, selector: Selector<Workspace>) -> Option<&Workspace>;
pub fn workspace_mut(&mut self, selector: Selector<Workspace>) -> Option<&mut Workspace>;

This should allow for work on #5 to begin as a user/contrib level extension rather that requiring changes to the internals of penrose itself.

possible error in cycle workspace

Describe the bug
when usinng a combination of focus_workspace ans cycle_workspace cycle_workspace can use the previous workspace as its starting index rather than the current one.

To Reproduce
Steps to reproduce the behavior:
start at workspace 3
switch to workspace 1 using M-1
move to workspace 2 using cycle_worspace M-period

you end up at workspace 4 rather than workspace 1

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
cycle_workspace should take you to the next workspace from the current one
ie starting at workspace 1 cycle_workspacce(Forward) should take you to workspace 2

Desktop (please complete the following information):

  • OS: linux
  • Distribution debian
  • Version sid

Additional context

  • minimal main.rs
    /**
  • penrose :: minimal configuration
  • This file will give you a functional if incredibly minimal window manager that has multiple
  • workspaces and simple client/workspace movement. For a more fleshed out example see the
  • 'simple_config_with_hooks' example.
    */
    #[macro_use]
    extern crate penrose;

use penrose::{Backward, Config, Forward, Less, More, WindowManager, XcbConnection};

fn main() {
let config = Config::default();
let key_bindings = gen_keybindings! {
"M-j" => run_internal!(cycle_client, Forward),
"M-k" => run_internal!(cycle_client, Backward),
"M-S-j" => run_internal!(drag_client, Forward),
"M-S-k" => run_internal!(drag_client, Backward),
"M-S-q" => run_internal!(kill_client),
"M-Tab" => run_internal!(toggle_workspace),
"M-bracketright" => run_internal!(cycle_screen, Forward),
"M-bracketleft" => run_internal!(cycle_screen, Backward),
"M-S-bracketright" => run_internal!(drag_workspace, Forward),
"M-S-bracketleft" => run_internal!(drag_workspace, Backward),
"M-grave" => run_internal!(cycle_layout, Forward),
"M-S-grave" => run_internal!(cycle_layout, Backward),
"M-A-Up" => run_internal!(update_max_main, More),
"M-A-Down" => run_internal!(update_max_main, Less),
"M-A-Right" => run_internal!(update_main_ratio, More),
"M-A-Left" => run_internal!(update_main_ratio, Less),
"M-A-Escape" => run_internal!(exit),
"M-semicolon" => run_external!("dmenu_run"),
"M-Return" => run_external!("st"),
// bindings for cycle_workspace
"M-period" => run_internal!(cycle_workspace, Forward),
"M-comma" => run_internal!(cycle_workspace, Backward);

    forall_workspaces: config.workspaces => {
        "M-{}" => focus_workspace,
        "M-S-{}" => client_to_workspace,
    }
};

let conn = XcbConnection::new().unwrap();
let mut wm = WindowManager::init(config, &conn);
wm.grab_keys_and_run(key_bindings);

}

focus_or_spawn action

A focus_or_spawn action that checked to see if a given program was running and either focused that workspace & client or spawned the program on the current workspace would be a good addition to contrib::actions. It allows for key bindings that are based on the program you want to work with rather than having to remember where things are running.

Be able to map key -> action pairs using a template inside of gen_keybindings


name: Dynamic Keymap
about: Be able to give the gen_keybindings macro a vector of keys to iterate over and format each key into a general keymap template such as M-A-{}.
title: Dynamic Keymap
labels: enhancement


Is your feature request related to a problem? Please describe.
I want to be able to give a vector of keys to format into a general key binding.

Describe the solution you'd like
Either an extension of the gen_keybindings macro, for example a forall_vec option, or another macro that implements this for general use
Describe alternatives you've considered
I've only considered an extension of the macro such as an optional forall_vec option, or an entirely new macro for this purpose.

Edit (sminez):

As discussed in the comments below, this is more about taking key value pairs and mapping them within a template a la sxhkd. A full implementation like sxhkd is probably beyond the scope of this initial request as that involves forming the cross product of multiple expansions and also generating method / key names it seems. For this, the following example should be sufficient:

ctrl + super + {h,j,k,l} 
    bspc monitor -f {west,south,north,east}

something along the lines of the following is likely what we'll end up with (not valid methods in penrose but using the same example for clarity):

        map [ h, j, k, l ] in {
            "M-C-{}" => internal, move_focus [ west, south, north, east ];
        }

Make "xconnection" a feature

Is your feature request related to a problem? Please describe.
I was trying to reuse the xconnection module and define my own WindowMangager struct. I wish the xconnection module could be a standalone feature so that I don't need to compile everything else in the penrose crate.

Describe the solution you'd like
Make xconnection a feature that could be specified in dependencies in Cargo.toml.

Describe alternatives you've considered
Copy the xconnection module to my local package

Additional context
It'd be nice to make the crate more modular. Please let me know if you think it would make sense to use only the xconnection module. I'm also not sure if making it a feature is the best way.

scratchpad and paper layout don't work together

Describe the bug
when a scratch pad is opened on a workspace running seveal windows focus bounces betwwen and the window that has focus in the tiled layer

Expected behavior
The scratchpad ti get focus

Desktop (please complete the following information):

  • OS: linux
  • Distribution debian unstable

Client window not mapped correctly after being moved to another workspace.

Describe the bug
When moving a client to an undisplayed workspace the client will not be mapped and its window will be invisible when switching to said workspace for the first time.

To Reproduce
Steps to reproduce the behavior:

  1. Create a new client
  2. Move the client to another workspace (client_to_workspace)
  3. Focus the workspace the client was moved to (focus_workspace)
  4. The client is not visible

Expected behavior
I would expect the client to become visible when switching to its workspace.

Desktop

  • OS: Linux
  • Distribution Arch Linux

Additional context
I might have already found a solution. The client_to_workspace function calls XConn.unmap_window, however this causes Client.mapped to remain true.
This is the code I'm talking about:

penrose/src/core/manager.rs

Lines 675 to 706 in 21667d9

/// Move the focused client to the workspace matching 'selector'.
pub fn client_to_workspace(&mut self, selector: &Selector<Workspace>) {
let active_ws = Selector::Index(self.screens.focused().unwrap().wix);
if self.workspaces.equivalent_selectors(&selector, &active_ws) {
return;
}
if let Some(index) = self.workspaces.index(&selector) {
let res = self
.workspaces
.get_mut(self.active_ws_index())
.and_then(|ws| ws.remove_focused_client());
if let Some(id) = res {
self.add_client_to_workspace(index, id);
self.client_map.get_mut(&id).map(|c| c.set_workspace(index));
self.conn.set_client_workspace(id, index);
self.apply_layout(self.active_ws_index());
// layout & focus the screen we just landed on if the workspace is displayed
// otherwise unmap the window because we're no longer visible
if self.screens.iter().any(|s| s.wix == index) {
self.apply_layout(index);
let s = self.screens.focused().unwrap();
self.conn.warp_cursor(Some(id), s);
self.focus_screen(&Selector::Index(self.active_screen_index()));
} else {
self.conn.unmap_window(id);
}
};
}
}

Replacing self.conn.unmap_window(id); with self.unmap_window_if_needed(id); (near the bottom of this method) solves the issue for me. This way Client.mapped will be set to false.

Let me know if you want me to create a PR with this solution or if you prefer fixing this yourself.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.