Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[hyprland/workspaces] Implement workspace taskbars #3868

Open
wants to merge 16 commits into
base: master
Choose a base branch
from

Conversation

pol-rivero
Copy link
Contributor

@pol-rivero pol-rivero commented Jan 4, 2025

Summary

This PR expands the functionality of hyprland/workspaces to make it behave more like the wlr/taskbar module. See #2656 for a high-level description.

A full fix of #2656 would require porting those changes to the sway/workspaces module as well. I don't use Sway and I'm not familiar on how it works, but if anyone is interested in giving it a go in a separate PR feel free to ping me and I'll do my best to help.

Config changes

Adds the following changes to the hyprland/workspaces config:

"hyprland/workspaces": {
    // EXISTING SETTINGS
    "format": "{icon}: {windows}", // Used even if "workspace-taskbar.enable" is true
    "format-window-separator": "", // Will be ignored if "workspace-taskbar.enable" is true
    "window-rewrite-default": "",  // Will be ignored if "workspace-taskbar.enable" is true
    "window-rewrite": {},          // Will be ignored if "workspace-taskbar.enable" is true
    // NEW SETTINGS
    "workspace-taskbar": {
        // Enable the workspace taskbar. Default: false
        "enable": true,
        // Format of the windows in the taskbar. Default: "{icon}". Allowed variables: {icon}, {title}
        "format": "{icon} {title:.20}",
        // Icon size in pixels. Default: 16
        "icon-size": 16,
        // Either the name of an installed icon theme or an array of themes (ordered by priority). If not set, the default icon theme is used.
        "icon-theme": "some_icon_theme",
        // Orientation of the taskbar (see screenshots below). Default: "horizontal".
        "orientation": "horizontal",
        // Command to run when a window is clicked. Default: "" (switch to the workspace as usual). Allowed variables: {address}, {button}
        "on-click-window": "/some/arbitrary/script {address} {button}"
    }
}

Notice how workspace-taskbar.enable defaults to false, so existing configs shouldn't be affected by these changes without opting in.

What's missing

I'm mainly waiting for your feedback on the config json names and structure. Once that's done I will update the wiki with the finalized spec.

Screenshots

Default CSS, old config file (defaults to old, text-based icons)

image

"hyprland/workspaces": {
    "format": "{icon}: {windows}",
    "format-window-separator": ",",
    "window-rewrite-default": "?",
    "window-rewrite": {
        "terminator": "",
        "code": "󰨞"
    }
},

Default CSS, new config file (default format is just the icon)

image

"hyprland/workspaces": {
    "format": "{icon}: {windows}",
    "workspace-taskbar": {
        "enable": true
    }
},

Default CSS, windows with first 10 chars of title and bigger icon

image

"hyprland/workspaces": {
    "format": "{icon}: {windows}",
    "workspace-taskbar": {
        "enable": true,
        "format": "{icon} {title:.10}",
        "icon-size": 18
    }
},

Tooltip shows full window title.

image

  • Note: The screenshot doesn't show my cursor, which was hovering the "Comparing" window.

Allows an arbitrary format

image

"hyprland/workspaces": {
    "format": "[{icon}] (( {windows} ))",
    "workspace-taskbar": {
        "enable": true,
        "format": " [{title:.5} | {icon}] "
    }
},

My setup with custom CSS

image

  • Background indicates selected workspace

Same CSS as above, vertical orientation

image

"hyprland/workspaces": {
    "format": "{icon}: {windows}",
    "workspace-taskbar": {
        "enable": true,
        "format": "{icon} {title:.20}",
        "icon-size": 18,
        "orientation": "vertical"
    }
},

Action when clicking window

You can use the on-click-window config to set the command that will be executed when a specific window is clicked.

  • The pattern {address} will be replaced with the address of the clicked window.
  • The pattern {button} will be replaced with the pressed button number. See GdkEventButton.button.

For example:

"on-click-window": "hyprctl dispatch focuswindow address:{address}"

I personally require a more complex logic, so I use on-click-window to call an external script.

@pol-rivero pol-rivero force-pushed the master branch 3 times, most recently from 42a17e1 to d207ec7 Compare January 10, 2025 15:47
@pol-rivero
Copy link
Contributor Author

@Alexays could you take a look at this PR?

@pol-rivero pol-rivero force-pushed the master branch 2 times, most recently from 3cd5c21 to 074ff2d Compare January 24, 2025 19:17
Add a list of window titles and icons to each workspace (like wlr/taskbar but grouped by workspace).

Only implemented on hyprland for now.
Use format from config instead of hardcoding
- orientation
- icon-size
- icon-theme
This seems to be an old bug that has been made visible with the new workspace taskbars feature.
Sometimes, when closing a window and re-opening a window of the same program, hyprland reuses the window address. Since m_orphanWindowMap was not being cleaned up on window close, the new window would not be updated properly.
Fix another older bug where the title of a window will not be updated after moving it to another monitor.
In onWindowMoved, when moving an orphan window to the display of the current bar, that window should no longer be an orphan.
Use a vector instead of a map for for storing the workspace windows.
This orders the windows by the time they were added to the workspace, instead of sorting by address (which is effectively a random order). The new ordering seems to match the wlr/taskbar module
Windows were not being shown or updated unless the window-rewrite config were present.
- Add missing CSS class to manpage
- Fix rare segfault when address is not found (seems to only happen when compiled for production)
@pol-rivero
Copy link
Contributor Author

@Alexays I've been using this feature daily for 2 months and haven't encountered any issues.
Could you review this PR? It seems there are other people interested in this functionality.

@dinhokusanagi
Copy link

@Alexays Please

@n-connect
Copy link

@pol-rivero

Thanks for this PR! Is it possible to have such functionality in the improved hyprland/workspaces like this wlr/taskbar config below?

        "wlr/taskbar" :   
        {
                // "sort-by-app-id": "true"             
                "icon-size" : 16,
                "icon-theme" : "Numix-Circle",
                "on-click" : "activate",
                "on-click-middle" : "maximize",
                "on-click-right" : "close",  
                "tooltip-format" : "{title}"
        }

@pol-rivero
Copy link
Contributor Author

pol-rivero commented Mar 8, 2025

@n-connect
Yes, this config should be what you are looking for:

"hyprland/workspaces": {
    "format": "{icon}: {windows}",   // Change to your preferred format
    "workspace-taskbar": {
        "enable": true,
        "format": "{icon} {title:.20}",   // Change to your preferred format
        "icon-size": 16,
        "icon-theme": "Numix-Circle",
        "on-click-window": "windowClick.sh {address} {button}"
    }
}

Where windowClick.sh points to a script like this:

#!/bin/bash

address=$1

# https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html
button=$2

if [ $button -eq 1 ]; then
    # Left click: focus window. The cursor:no_warps lines are optional.
    hyprctl keyword cursor:no_warps true
    hyprctl dispatch focuswindow address:$address
    hyprctl keyword cursor:no_warps false
elif [ $button -eq 2 ]; then
    # Middle click: maximize window
    # TODO: Use the corresponding hyprctl dispatch command. I don't know what would 'maximize' mean in a tiling window manager like hyprland
elif [ $button -eq 3 ]; then
    # Right click: close window
    hyprctl dispatch closewindow address:$address
fi

@n-connect
Copy link

@pol-rivero,

Thanks for the full example, making local backup for test. Looking forward to this PR to come through. It really helps making a cleaner and smaller footprint - less occupied pixels on screen, by avoiding duplicated icons, and giving space for other possible stuff.

Another question, should this PR allow icon rewriting such as in the below pic? The 4 icons at right side of 1 ...
20250308_19h52m47s_grim

Yepp, this capability within wlr/taskbar is priceless - if one is looking maximizing possibility in an otherwise tiling WM - in an ultralight small screen laptop. I was not able to get work the fullscreenstate, 1 hyprland yet, but fullscreen binds only. Not the same as maximize :D

elif [ $button -eq 2 ]; then
    # Middle click: maximize window
    # TODO: Use the corresponding hyprctl dispatch command. I don't know what would 'maximize' mean in a tiling window manager like hyprland

@pol-rivero
Copy link
Contributor Author

@n-connect
The taskbar shows the app icon and therefore does not support rewriting (if workspace-taskbar.enable is set to true, the window-rewrite config will be ignored).

If you want to change the icon that is shown for a specific app, you can do the following:

  1. Find the .desktop file for that app. You can find it in one of these directories:
    • ~/.local/share/applications
    • /usr/share/applications
    • /usr/local/share/applications
  2. Open it with a text editor
  3. Find the line that starts with Icon= and replace it with another icon from your theme. You can use a tool like nwg-icon-picker to find the available icon names.

@n-connect
Copy link

@pol-rivero

Fair enough. Either possibility of getting the same icons as in war/taskbar, or one with your samples above is perfect.

Default CSS, new config file (default format is just the icon)

image

"hyprland/workspaces": {
    "format": "{icon}: {windows}",
    "workspace-taskbar": {
        "enable": true
    }
},

I just need to git magic the latest numbered release, plus your PR into one local copy to do compile it. I hope I can find find my previous notes about cmus and its https stream PR :)

@pol-rivero
Copy link
Contributor Author

I just need to git magic the latest numbered release, plus your PR into one local copy to do compile it.

This PR is already up to date, you can just git clone https://github.com/pol-rivero/waybar.

Also let me know if you are using arch, then it's much easier to install just by modifying the PKGBUILD.

@n-connect
Copy link

n-connect commented Mar 9, 2025

@pol-rivero,

Thx, not Arc, but Debian stable, only the nwg-menu has and Arc icon/char. Following your base git clone, and build gave the workspace taskbar. On the other hand its version is below the actual release number (fixme, it the build# still marks it as 0.12.0)

waybar -v
Waybar v0.11.0-153-ge541936d (branch 'master')

So I'll try the tag/version clone and PR checkout for rebuild later.

The look of the bar with your code, with the same cut as above:
20250309_11h04m25s_grim

Working parts of the script:

  • button -eq 3 -> hyprctl dispatch closewindow address:$address

For some reason the activation do not works:

  • button -eq 1 -> hyprctl dispatch focuswindow address:$address
  • after the normal mouse click the mouse jumps into the middle of the screen and seeable, that the focused window is a different one, but that one do not got on the top, tried to fix, no joy yet (see script below)
  • my guess is that the focuswindow ask for a window parameter instead of address

Edit: after a sleep/session lock, a new error emerged:

  • all my clients (as hyprctl clients got hidden), however only one workspace (1) exist
  • no workspace change has helped
  • finally the wlr/taskbar maximise function could bring up the windows of the running [hyprland/hyprctl] clients.

I'm not sure if there a connection to this PR, just a background info

How should I get the address/window value for cli tests? Checked hyprctl clients output...
Can you please check out the below configs, what could be the issue in your opinion?

        "hyprland/workspaces": {
            "format": "{icon}: {windows}",   // Change to your preferred format
            "workspace-taskbar": {
                "enable": true,    
                "format": "{icon}",   // Change to your preferred format            
                "icon-size": 16,                     
                "icon-theme": "Numix-Circle",
                "on-click-window": "$HOME/.config/waybar/windowClick.sh {address} {button}"
            }    
        },

Edit2: fixed typo below

#!/bin/bash
                
address=$1

# https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html    
button=$2                      
         
if [ $button -eq 1 ]; then
    # Left click: focus window. The cursor:no_warps lines are optional.    
    #hyprctl keyword cursor:no_warps true
    hyprctl dispatch focuswindow address:$address
    hyprctl dispatch bringactivetotop        
    #hyprctl keyword cursor:no_warps false
elif [ $button -eq 2 ]; then
    # Middle click: maximize window    
    # TODO: Use the corresponding hyprctl dispatch command. I don't know what would 'maximize' mean in a tiling window manager like hyprland
    echo 'middle click'
elif [ $button -eq 3 ]; then       
    # Right click: close window                                                     
    hyprctl dispatch closewindow address:$address    
fi

@pol-rivero
Copy link
Contributor Author

Sorry, I don't understand what you mean. Is there a bug?

@n-connect
Copy link

Sorry, I don't understand what you mean. Is there a bug?

Sorry, short version:

  1. The normal mouse click is not working. This supposed to bring a window into the front and focus it (or in reversed order does not matter). The focus removed from the original window/client, but the icon(its windows) I've clicked on, has not brought on top. Eg. it looks like nothing changed - but I can see the focus actually changed, but only just that. The window did not bringed up compared to wlr/taskbar I'm using an ALT-Tab bind which quite similar, which serves the ALT-Tab purpose, but can't we used here unfortunally:
bind = ALT, Tab, cyclenext
bind = ALT, Tab, bringactivetotop 
  1. The windows closing right click works.

These two above are related to the shellscript you've sent earlier.

  1. New interesting possible bug: all my windows "got hidden somewhere" within the workspace. The are not seeable in workspace 1, after lid close/sleep. Hyprctl clients tells me they are on/in wp1. Not 100% sure waybar (and hpyrland workspace/taskbar) related, , although this never happened before, only since I've replaced the waybar with local build this moring. In the cli, where I've started waybar no separate errors.

This one is a completely generic one. I'll crosscheck with 0.12.0 packaged version if it happens again.

@pol-rivero
Copy link
Contributor Author

Ah I see.
Since closing the window works, that means the script is being called correctly with the address and button number.

You seem to have a typo on your script: hyprctl dispatch bringactivetotp is missing an O: bringactivetotOp. That's probably why it isn't working.

I don't know why the windows are being hidden, this change should not affect it at all. Let me know if it also happens with the 0.12.0 base.

@n-connect
Copy link

You seem to have a typo on your script: hyprctl dispatch bringactivetotp is missing an O: bringactivetotOp. That's probably why it isn't working.

Good catch, thx! For a "fresh" client/window it works now. I'll fix in the previos one, if someone get here from Google/etc search have a working script...

I'm still in the same XDG session where the clients got hidden. One of them with many tabs, a kitty in "hidden phase". That kitty can be bringed on top, so there must be some other issue :)

Thx again.

@pol-rivero
Copy link
Contributor Author

Glad to help :)

@n-connect
Copy link

n-connect commented Mar 10, 2025

@pol-rivero

Made another build with latest release version + this PR merged:

git clone --branch 0.12.0 https://github.com/Alexays/Waybar.git
cd Waybar;git pull origin pull/3868/head

As a result got Waybar v0.12.0-16-ge541936d (branch 'HEAD') .
Edit:
From now on I'm using it as normal w/o wlr/taskbar. Let you know if something comes up.

20250310_10h26m27s_grim

Final, fully working windowClick.sh:

#!/bin/bash

address=$1

# https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html
button=$2

if [ $button -eq 1 ]; then
    # Left click: focus window. 
    # The cursor:no_warps are optional. Set to not catch and move the cursor into the middle of screen as a result of "focuswindow"     
    hyprctl keyword cursor:no_warps true 
    hyprctl dispatch focuswindow address:$address
    hyprctl dispatch bringactivetotop
    hyprctl keyword cursor:no_warps false 
elif [ $button -eq 2 ]; then
    # Middle click: maximize window
    hyprctl dispatch fullscreenstate 1       
elif [ $button -eq 3 ]; then
    # Right click: close window
    hyprctl dispatch closewindow address:$address
fi

@n-connect
Copy link

n-connect commented Mar 10, 2025

@pol-rivero

Because of missing lib/dev packages the following modules has not been compiled in:

[2025-03-10 10:58:16.726] [warning] module privacy: Unknown module: privacy
[2025-03-10 10:58:16.726] [warning] module mpris: Unknown module: mpris
[2025-03-10 10:58:16.731] [warning] Waybar has been built without rfkill support.

@Alexays , The following additional packages are necessary to install to build waybar, at least on Debian. Would you accept a README.md update with the below packages? (edit)

apt install libfftw3-dev libiniparser-dev libupower-glib-dev libpipewire-0.3-dev libplayerctl-dev libjack-dev libwireplumber-0.5-dev libsndio-dev libgtk-layer-shell-dev libasound2-dev portaudio19-dev libsdl2-dev

With the above only epoll-shim and libinotify are missing during meson setup build its possible both are FreeBSD related.

"Original" / debian package based waybar with mpris/audio/privacy:

20250310_10h56m07s_grim
Fixed dependency built waybar v0.12.0 (PR3868 enabled) with mpris/audio/privacy:

20250310_12h34m00s_grim

@n-connect
Copy link

n-connect commented Mar 17, 2025

@pol-rivero

Glad to help :)

Thanks for all the info setting up workspace taskbars.
Today run into a problem again in clamshell mode - with external monitor. First it correctly moved all windows into the same kind of positifion on the external monitor as it were in the notebook's screen, but closing the lid messed it up.

  • the existing windows (hyprctl clients) just thrown everywhere,
  • in a way not even part of the windows can be seen, so I can drag them

Edit:
I've set up a keyboard windows' moving bind set, but I've got also an idea (beside I've fixed my clamshell.sh's logic/way :) ):

  1. Can workspace taskbars recognize, if I do mouse click with a modifier key - Super, SHIFT, ALT, CTRL?
  2. if yes, I would use this or something simliar to move windows top left hyprctl dispatch moveactive exact 0 0 with a mouse click + modifier. What "number" should I use az 1-2-3 used for LMC, RMC, MMC ?

Thx

@pol-rivero
Copy link
Contributor Author

@n-connect

Unfortunately, I don't think it's possible to get the keyboard state from a GDK mouse-click event. Feel free to correct me if I'm wrong, though.

Similarly, getting the keyboard state inside the script is hard, especially without root access. I found this answer, but I wasn't able to make it work in wayland.

However, you could easily do something like a double-click or triple-click to bring back the window:

#!/bin/bash

address=$1
# https://api.gtkd.org/gdk.c.types.GdkEventButton.button.html
button=$2

# Globals for double-click detection
LAST_CLICK_FILE="/tmp/waybar_taskbar_last_click"
DOUBLE_CLICK_THRESHOLD=0.3  # seconds

is_double_click() {
    current_time=$(date +%s.%N)
    
    if [ -f "$LAST_CLICK_FILE" ]; then
        last_time=$(cat "$LAST_CLICK_FILE")
        time_diff=$(awk "BEGIN {print $current_time - $last_time}")
        
        if [ "$(echo "$time_diff < $DOUBLE_CLICK_THRESHOLD" | bc -l)" -eq 1 ]; then
            # Double-click detected
            rm -f "$LAST_CLICK_FILE"
            return 0
        fi
    fi
    
    echo "$current_time" > "$LAST_CLICK_FILE"
    return 1  # Not a double-click.
}

on_double_click() {
    # Put your double-click action here. For example:
    hyprctl dispatch moveactive exact 0 0
}

if [ "$button" -eq 1 ]; then
    if is_double_click; then
        on_double_click
        exit 0
    fi

    # Left click: focus window. 
    # The cursor:no_warps are optional. Set to not catch and move the cursor into the middle of screen as a result of "focuswindow"
    hyprctl keyword cursor:no_warps true 
    hyprctl dispatch focuswindow address:$address
    hyprctl dispatch bringactivetotop
    hyprctl keyword cursor:no_warps false 
elif [ "$button" -eq 2 ]; then
    # Middle click: maximize window.
    hyprctl dispatch fullscreenstate 1       
elif [ "$button" -eq 3 ]; then
    # Right click: close window.
    hyprctl dispatch closewindow address:$address
fi

@n-connect
Copy link

n-connect commented Mar 17, 2025

@pol-rivero

Unfortunately, I don't think it's possible to get the keyboard state from a GDK mouse-click event. Feel free to correct me if I'm wrong, though.

Similarly, getting the keyboard state inside the script is hard, especially without root access. I found this answer, but I wasn't able to make it work in wayland.

Thx for the answer and the details. Based on your answer I recon there's no keyboard-press detection code in waybar at all. Thinking about it, it makes sense, maybe I'm the first one tries to solve an issue coming from a different part of hyprland, via a 4th type of click.

(The root cause why I've had the whole modkey + mouse-click idea is, that the hyprctl keyword monitor,disable works as intended in clamshell [mode[close lid] , but the monitor,enable for the same monitor fails and throws and error [open lid]. Moved to dpms off/dpms on while it gets fixed / find out why it fails/wrong).

Edit: checked the stackoverflow link, I'm not sure if its the only direction, eg. you need root level access for this. I've checked all "hypr" processes - All of them running in my user's name. Still the various binds works well. About mouse I'm using 'autowaybar' which polls mouse position to determine if waybar should be unhidden or not. So I'm guessing none of them needs root access - there's should be a different solution, I'll just need to check Hyprland's code around 'bind'. Anyway the real problem is still with monitor disable/enable.

Thx again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants