LBCrion/sfwbar

How to add widgets dynamically?

Closed this issue · 13 comments

Hello,
trying sfwbar with labwc
I have been 'playing' around with sfwbar, to try and put together a satisfying config. and its mostly going good.
I find it powerful but also compicated with the configuration.

The idea i have is because there is still no (stable wayland protocoles) support for workspaces in a lot of bars and taskbars with labwc. So the only way is to manually add them to the config like:

`layout {
grid {
style = "desktops"
css = "* { -GtkWidget-direction: right; }"
label {
style = "desk"
value = "1"
action = "wtype -M win -P F1"
}
label {
style = "desk"
value = "2"
action = "wtype -M win -P F2"
}
label {
style = "desk"
value = "3"
action = "wtype -M win -P F3"
}
label {
style = "desk"
value = "4"
action = "wtype -M win -P F4"
}
}

}`

Then I got the idea to write a python script to find the workspaces in .config/labwc/rc.xml and the switching keybinds and add them to the bar like so with wtype:

`import xml.etree.ElementTree as et
from os.path import join, expanduser

rcfile = join( expanduser("~"), ".config/labwc/rc.xml" )
rctree = et.parse(rcfile)
rcroot = rctree.getroot()
meta_keys = {
"A": "alt",
"C": "ctrl",
"M": "altgr",
"S": "shift",
"W": "win"
}

def get_wtype(keyb):
l = keyb.split("-")
cmd = "wtype "
for i in l:
if i in meta_keys:
cmd += "-M " + meta_keys[i] + " "
else:
cmd += "-P " + i + " "
return cmd

keybinds = rcroot.findall("{}keyboard/{}keybind")
if keybinds:
for key in keybinds:
actions = key.findall("{}action")
if actions:
for action in actions:
if action.get("name") == "GoToDesktop":
tos = action.findall("{
}to")
if tos:
for ato in tos:
if ato.text.isdigit():
k = key.get("key")
print("label {\n style = "desk"\n value = "" + ato.text + "" \n action = "" + get_wtype(k) + ""\n}")`

this script then returns:

label { style = "desk" value = "1" action = "wtype -M win -P F1 " } label { style = "desk" value = "2" action = "wtype -M win -P F2 " } label { style = "desk" value = "3" action = "wtype -M win -P F3 " } label { style = "desk" value = "4" action = "wtype -M win -P F4 " }

Is there a way to add that in the config file?

Thanks,

This is some amazingly hacky solution, I love it!
Technically you could also add something like <action name="Execute" command="sh -c 'echo 2 > /tmp/current_workspace'" /> to each of the workspace switching keybinds and then have sfwbar read that file and have a (mostly synced) current-workspace indicator.

Edit:
AFAIR you need to encode the > in the command, e.g. &gt;.

Thanks for your your suggestion, I have seen this 'kind' of method in other cases but never thought of using it in this.
Thanks a bunch.

And that hacky :) method above, I got it when I was experimenting with eww bar for workspaces, taskbar and active window (eww doesn't have a taskbar widget yet) using tools like wlrctl and lswt, but didn't like it much.

Can PipeRead() also be used in SfwBarInit()? That would allow to have the initial state without sending a signal.

Hello,
Yeah I saw those actions, and a python script in config/wifi.widget, but I couldn't wrap my head around it (that's where I got these idea).

Sorry if I sound rude, but i couldn't find more examples in the documentation.

Thanks a lot

@Consolatis , Sure, you can use PipeRead in SfwbarInit
@salahoued , not at all. Sfwbar configuration is complex, unfortunately I haven't figured out a way to make it simpler without sacrificing configurability.

@LBCrion , Sorry for the trouble, but how can I add the output of the script the layout using Config.
labwc_workspaces.txt

@LBCrion , @Consolatis
Sorry I was about to report my results, when I saw your last comment.
I have successfully added the pager, without using Config (I still don't know how to properly use it) and here is my solution:

file : ~/.config/sfwbar/widgets/labwc_workspaces.widget to include in sfwbar.config I put this config (inspiration from config/wifi.widget) :

Scanner {
  file("/tmp/current_ws.txt", CheckTime) {
    CurrentWS = Grab()
  }
}
layout {
    action[0] = PipeRead "~/.local/bin/labwc-workspaces.py --ws-sfwbar"
    grid "labwc_ws" {
		style = "desktops"
        css = "* { -GtkWidget-direction: right; }"
	}
}

#CSS
label#desk, label#desk_active {
    padding: 0 5px;
}
#desktops {
    padding: 0 5px;
    margin: 0 3px;
    color: alpha(black, 0.80);
    background-color: alpha(white, 0.70);
}
label#desk_active {
    font-weight: bold;
    color: alpha(black, 1);
}

And here is the code for the python script ~/.local/bin/labwc-workspaces.py that generate workspace layout from ~/.config/labwc/rc.xml :

#!/usr/bin/env python3
import xml.etree.ElementTree as et
from os.path import join, expanduser
import sys

rcfile = join( expanduser("~"), ".config/labwc/rc.xml" )
rctree = et.parse(rcfile)
rcroot = rctree.getroot()
desks  = rcroot.find("{*}desktops")
num = desks.get("number")
names = desks.findall("{*}names/{*}name")
lnames = [ i.text for i in names ]
if not num:
    num = len(names)
meta_keys = {
    "A": "alt",         # Mod1
    "C": "ctrl",
    "H": "",        # Mod3
    "M": "altgr",        # Mod5
    "S": "shift",
    "W": "win"          # Mod4, super_L
    }
def get_workspace_number():
    return num
def get_workspace_names():
    return lnames
def get_wtype(keyb):
    l = keyb.split("-")
    cmd = "wtype "
    for i in l:
        if i in meta_keys:
            cmd += "-M " + meta_keys[i] + " "
        else:
            cmd += "-P " + i + " "
    return cmd
def get_keybinds(action_name):
    keybinds = rcroot.findall("{*}keyboard/{*}keybind")
    action_binds = {}
    if keybinds:
        for key in keybinds:
            k = key.get("key")
            wtype = get_wtype(k)
            actions = key.findall("{*}action" )
            if actions:
                for action in actions:
                    if action.get("name") == action_name:
                        if action_name == "Execute":
                            cmd = action.get("command")
                            cmds = action.findall("{*}command")
                            if cmd:
                                action_binds[cmd] = wtype
                            else:
                                if cmds:
                                    for cm in cmds:
                                        action_binds[cm.text] = wtype
                        else:
                            tos = action.findall("{*}to")
                            if tos:
                                for ato in tos:
                                    if ato.text not in ["current", "left", "right", "last"]:
                                        action_binds[ato.text] = wtype
    return action_binds
def usage():
    print( "Usage: " + sys.argv[0] + " --ws-num | --ws-names | --ws-names-lines | --ws-json | --ws-keys ActionName | --ws-sfwbar" )
if __name__ == "__main__":
    if len( sys.argv ) <= 1:
        usage()
    else:
        if sys.argv[1] == "--ws-num":
            print(num)
        elif  sys.argv[1] == "--ws-names":
            print(lnames)
        elif sys.argv[1] == "--ws-names-lines":
            for i in lnames:
                print(i)
        elif sys.argv[1] == "--ws-json":
            json_str = '{ "number": ' + str(num) + ', "names": ['
            json_names = ""
            for i in lnames:
                json_names += f'"{i}", '

            json_str += json_names.rstrip(", ") + "] }"
            print(json_str)
        elif sys.argv[1] == "--ws-keys":
            if sys.argv[2]:
                actions = get_keybinds(sys.argv[2])
                #print(json.dumps(actions))

            else:
                usage()
        elif sys.argv[1] == "--ws-sfwbar":
            with open("/tmp/number_ws.txt", "w") as fout:
                fout.write( str(num) + "\n" )
            actions = get_keybinds("GoToDesktop")
            print('grid "labwc_ws" { \n style = "desktops" \n css = "* { -GtkWidget-direction: right; }"\n')
            #print('grid "labwc_ws" {\n')
            for i in actions:
                print('label "lwc_' + i + '" {\n style = If(CurrentWS = ' + i + ', "desk_active", "desk")\n value = "' + i + '" \n action = "' + actions[i] + '"\n}')
            print('}')

        else:
            usage()

Script to get the current workspace ~/.local/bin/current_ws.sh for current workspace indicator

It should be Known that this script should be executed every time the user switchs to a diffrent workspace using the keybinds in $HOME_CONFIG/labwc/rc.xml, or sends a window to diffrent workspace with wraping set to yes (following the window to the new workspace)

#!/usr/bin/env bash

CURRENT_WS_FILE="/tmp/current_ws.txt"
NUMBER_WS_FILE="/tmp/number_ws.txt"

num_ws=1
curr_ws=1
new_ws=1

if [ ! -f "$NUMBER_WS_FILE" ]; then
    labwc-workspaces.py --ws-num > $NUMBER_WS_FILE
else
    num_ws=$(cat $NUMBER_WS_FILE)
fi

if [ ! -f "$CURRENT_WS_FILE" ]; then
    echo "$curr_ws" > "$CURRENT_WS_FILE"
else
    curr_ws=$(cat $CURRENT_WS_FILE)
fi

case $1 in
    left)
        if [ $curr_ws -gt 1 ]; then
            new_ws=$(($curr_ws - 1))
            echo "$new_ws" > $CURRENT_WS_FILE
        fi
    ;;
    right)
        if [ $curr_ws -lt $num_ws ]; then
            new_ws=$(($curr_ws + 1))
            echo "$new_ws" > "$CURRENT_WS_FILE"
        fi
    ;;
    current)
        echo "current"
    ;;
    *)
        echo "$1" > $CURRENT_WS_FILE
    ;;
esac

Because the taskbar shows all windows from every workspace, the indicator won't be updated if a window from a diffrent workspace got activated (needs a workaround)

You can use ``` for code blocks, otherwise its quite hard to read. Something like

***python
words=("some", "python", "code", "with", "multiple")
print(' '.join(words) + " lines")
***

The python keyword is optional and just improves the highlighting. Replace *** in the example with ```

@Consolatis , thanks I was working on better writing it again, so this is a quick solution for the time being
Sorry for the trouble

Nice. Hacks like this remind me why maintaining the flexibility in sfwbar is worth it.

One change I would recommend is adding a CheckTime to the scanner, to avoid constantly opening the file, I.e.:

Scanner {
  file("/tmp/current_ws.txt", CheckTime) {
    CurrentWS = Grab()
  }
}

Credit to @Consolatis , for that idea.
Thanks for the 'tip'.