Me and mabox-noctalia labwc

A couple of day’s ago I started to investigate noctalia (labwc)

This topic will be random and impulsive.

So far I have converted some tools I like to use also in noctalia

mb-kb-wl : Searches the mabox-noctalia/rc.xml

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# mb-kb-wl - labwc version of mb-kb.
#
# This script reads the labwc keybinds configuration file
# ("$HOME/.config/mabox-noctalia/rc.xml") and writes them to a text file
# ("$HOME/.config/mabox-noctalia/kbinds.txt"). The keybinds are printed to the
# terminal, or shown in a separate window with --gui.
#
# Based on mb-kb, which is based on a script by wlourf 07/03/2010
# http://u-scripts.blogspot.com/2010/03/how-to-display-openboxs-shortcuts.html
#
# Written by damo <damo@bunsenlabs.org> for BunsenLabs Linux, April 2015
# Ported to Manjaro by napcok <napcok@gmail.com> March 2016
# Fix (multi-action keybinds): prefer Execute over first action - musqz
# labwc fork: labwc rc.xml has no XML namespace, actions can carry attributes
# (command="..."), and keybinds can use the name.action / command.action
# shorthand. Keys that only exist in labwc's built-in <default /> set are not
# listed because they are not in rc.xml.

import sys
import os
import datetime

try:
    from lxml import etree
except ImportError:
    import xml.etree.ElementTree as etree

cfg_dir = os.path.join(os.environ["HOME"], ".config", "mabox-noctalia")
rc_fpath = os.path.join(cfg_dir, "rc.xml")
kb_fpath = os.path.join(cfg_dir, "kbinds.txt")
arrShortcut = []
gui = False

# When a keybind has several actions, show the first one found in this list.
PREFERRED = ("Execute", "ShowMenu", "MoveResizeTo", "SnapToEdge",
             "GrowToEdge", "SendToDesktop", "GoToDesktop")


class Act:
    """One action of a keybind, whether written as <action> or as shorthand."""

    def __init__(self, name, getter, label=None):
        self.name = name
        self._get = getter
        self.label = label

    def get(self, key):
        return self._get(key)


def clean(text):
    if text is None:
        return None
    text = " ".join(text.split())
    return text or None#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# mb-kb-wl - labwc version of mb-kb.
#
# This script reads the labwc keybinds configuration file
# ("$HOME/.config/mabox-noctalia/rc.xml") and writes them to a text file
# ("$HOME/.config/mabox-noctalia/kbinds.txt"). The keybinds are printed to the
# terminal, or shown in a separate window with --gui.
#
# Based on mb-kb, which is based on a script by wlourf 07/03/2010
# http://u-scripts.blogspot.com/2010/03/how-to-display-openboxs-shortcuts.html
#
# Written by damo <damo@bunsenlabs.org> for BunsenLabs Linux, April 2015
# Ported to Manjaro by napcok <napcok@gmail.com> March 2016
# Fix (multi-action keybinds): prefer Execute over first action - musqz
# labwc fork: labwc rc.xml has no XML namespace, actions can carry attributes
# (command="..."), and keybinds can use the name.action / command.action
# shorthand. Keys that only exist in labwc's built-in <default /> set are not
# listed because they are not in rc.xml.

import sys
import os
import datetime

try:
    from lxml import etree
except ImportError:
    import xml.etree.ElementTree as etree

cfg_dir = os.path.join(os.environ["HOME"], ".config", "mabox-noctalia")
rc_fpath = os.path.join(cfg_dir, "rc.xml")
kb_fpath = os.path.join(cfg_dir, "kbinds.txt")
arrShortcut = []
gui = False

# When a keybind has several actions, show the first one found in this list.
PREFERRED = ("Execute", "ShowMenu", "MoveResizeTo", "SnapToEdge",
             "GrowToEdge", "SendToDesktop", "GoToDesktop")


class Act:
    """One action of a keybind, whether written as <action> or as shorthand."""

    def __init__(self, name, getter, label=None):
        self.name = name
        self._get = getter
        self.label = label

    def get(self, key):
        return self._get(key)


def clean(text):
    if text is None:
        return None
    text = " ".join(text.split())
    return text or None


def elem_value(el, key):
    """Value of key written as an attribute or as a child element."""
    value = clean(el.get(key))
    if value is not None:
        return value
    child = el.find(key)
    if child is not None:
        return clean(child.text)
    return None


def cmdargs():
    global rc_fpath, kb_fpath
    use_gui = False
    args = sys.argv[1:]
    while args:
        arg = args.pop(0)
        if arg == "--gui":
            use_gui = True
        elif arg == "--rc" and args:
            rc_fpath = os.path.expanduser(args.pop(0))
        elif arg == "--out" and args:
            kb_fpath = os.path.expanduser(args.pop(0))
        else:
            msg = "\n\n\tUSAGE: to display keybinds in a text window use 'mb-kb-wl --gui &>/dev/null'\n\n"
            msg += "\tRunning the script without args will send output to the terminal\n\n"
            msg += "\tOptional: --rc FILE  read another rc.xml\n"
            msg += "\t          --out FILE write the list to another text file\n\n"
            print(msg)
            sys.exit()
    return use_gui


def actions_of(keybind):
    acts = []
    for a in keybind.findall("action"):
        name = a.get("name")
        if not name:
            continue
        label = None
        if name == "Execute":
            child = a.find("name")
            if child is not None:
                label = clean(child.text)
        acts.append(Act(name, lambda key, el=a: elem_value(el, key), label))

    # Shorthand: <keybind key="F1" name.action="Execute" command.action="...">
    short = clean(keybind.get("name.action"))
    if short:
        acts.append(Act(short,
                        lambda key, kb=keybind: clean(kb.get(key + ".action"))))
    return acts


def pick(acts):
    for wanted in PREFERRED:
        for a in acts:
            if a.name == wanted:
                return a
    return acts[0]


def describe(a):
    if a.name == "Execute":
        return a.label or a.get("command") or a.get("execute") or "Execute"
    if a.name == "MoveResizeTo":
        parts = []
        for tag in ("x", "y", "width", "height"):
            value = a.get(tag)
            if value:
                parts.append(tag + ":" + value)
        return "MoveResizeTo " + " ".join(parts) if parts else "MoveResizeTo"
    if a.name == "ShowMenu":
        return a.get("menu") or "ShowMenu"
    extra = [a.get(t) for t in ("direction", "to") if a.get(t)]
    return " ".join([a.name] + extra)


def keyboard():
    tree = etree.parse(rc_fpath)
    root = tree.getroot()

    for kb in root.iter("keyboard"):
        for k in kb.findall("keybind"):
            key = k.get("key")
            if not key:
                continue

            acts = actions_of(k)
            if not acts:
                continue

            action = pick(acts)
            strType = "x " if action.name == "Execute" else "o "
            arrShortcut.append((strType, key, describe(action)))


def output_keybinds(arrShortcut, gui):
    for i in range(len(arrShortcut)):
        exe        = str(arrShortcut[i][0])
        keybinding = str(arrShortcut[i][1])
        execute    = str(arrShortcut[i][2])
        if gui:
            if len(execute) > 80:
                execute = execute[:75] + "....."
            line = "{:2}".format(i) + "\t" + "{:<16}".format(keybinding) \
                   + "\t" + execute
        else:
            line = exe + "{:<16}".format(keybinding) + "\t" + execute
        print(line)
        write_file(line)


def check_rcfile(fpath, mode):
    try:
        open(fpath, mode).close()
    except IOError:
        return False
    return True


def write_file(line):
    with open(kb_fpath, 'a') as f:
        f.write(line + "\n")


def check_txtfile(kb_fpath):
    try:
        open(kb_fpath, 'w').close()
    except IOError:
        return False
    return True


if __name__ == "__main__":
    gui = cmdargs()
    check_txtfile(kb_fpath)

    if gui:
        write_file(str(datetime.date.today()) + "\trc.xml KEYBINDS (labwc)")
        write_file("-------------------------------\n")

    if check_rcfile(rc_fpath, "r"):
        try:
            keyboard()
        except Exception as e:
            print("\nCan't parse rc.xml: " + str(e) + "\n\tCheck it with: xmllint --noout " + rc_fpath + "\n")
            sys.exit(1)
        output_keybinds(arrShortcut, gui)
    else:
        print("\nCan't open rc.xml for parsing\n\tCheck the filepath: " + rc_fpath + "\n")
        sys.exit(1)

    if gui:
        write_file("\n(labwc's built-in default keys are not listed here)")
        dlg  = "yad --text-info --title='Mabox labwc Keybinds' "
        dlg += "--window-icon=distributor-logo-mabox --image=/usr/share/icons/mabox_trans_64.png "
        dlg += "--text '\n<b>Key bindings</b> are configured in your <i>" + rc_fpath + "</i> file.  <a href=\"https://labwc.github.io/labwc-config.5.html\">docs</a>\n\t\t\t\t<b>S</b> - Shift key\n\t\t\t\t<b>C</b> - Control key\n\t\t\t\t<b>A</b> - Alt key\n\t\t\t\t<b>W</b> - Super key (usually bound to the Windows key)' "
        dlg += "--filename=" + kb_fpath + " "
        dlg += "--width=640 --height=560 --fontname=Monospace "
        dlg += "--button='Edit rc.xml:geany " + rc_fpath + "' --button='Reload labwc configuration:labwc -r' --button=Close"
        os.system(dlg)



def elem_value(el, key):
    """Value of key written as an attribute or as a child element."""
    value = clean(el.get(key))
    if value is not None:
        return value
    child = el.find(key)
    if child is not None:
        return clean(child.text)
    return None


def cmdargs():
    global rc_fpath, kb_fpath
    use_gui = False
    args = sys.argv[1:]
    while args:
        arg = args.pop(0)
        if arg == "--gui":
            use_gui = True
        elif arg == "--rc" and args:
            rc_fpath = os.path.expanduser(args.pop(0))
        elif arg == "--out" and args:
            kb_fpath = os.path.expanduser(args.pop(0))
        else:
            msg = "\n\n\tUSAGE: to display keybinds in a text window use 'mb-kb-wl --gui &>/dev/null'\n\n"
            msg += "\tRunning the script without args will send output to the terminal\n\n"
            msg += "\tOptional: --rc FILE  read another rc.xml\n"
            msg += "\t          --out FILE write the list to another text file\n\n"
            print(msg)
            sys.exit()
    return use_gui


def actions_of(keybind):
    acts = []
    for a in keybind.findall("action"):
        name = a.get("name")
        if not name:
            continue
        label = None
        if name == "Execute":
            child = a.find("name")
            if child is not None:
                label = clean(child.text)
        acts.append(Act(name, lambda key, el=a: elem_value(el, key), label))

    # Shorthand: <keybind key="F1" name.action="Execute" command.action="...">
    short = clean(keybind.get("name.action"))
    if short:
        acts.append(Act(short,
                        lambda key, kb=keybind: clean(kb.get(key + ".action"))))
    return acts


def pick(acts):
    for wanted in PREFERRED:
        for a in acts:
            if a.name == wanted:
                return a
    return acts[0]


def describe(a):
    if a.name == "Execute":
        return a.label or a.get("command") or a.get("execute") or "Execute"
    if a.name == "MoveResizeTo":
        parts = []
        for tag in ("x", "y", "width", "height"):
            value = a.get(tag)
            if value:
                parts.append(tag + ":" + value)
        return "MoveResizeTo " + " ".join(parts) if parts else "MoveResizeTo"
    if a.name == "ShowMenu":
        return a.get("menu") or "ShowMenu"
    extra = [a.get(t) for t in ("direction", "to") if a.get(t)]
    return " ".join([a.name] + extra)


def keyboard():
    tree = etree.parse(rc_fpath)
    root = tree.getroot()

    for kb in root.iter("keyboard"):
        for k in kb.findall("keybind"):
            key = k.get("key")
            if not key:
                continue

            acts = actions_of(k)
            if not acts:
                continue

            action = pick(acts)
            strType = "x " if action.name == "Execute" else "o "
            arrShortcut.append((strType, key, describe(action)))


def output_keybinds(arrShortcut, gui):
    for i in range(len(arrShortcut)):
        exe        = str(arrShortcut[i][0])
        keybinding = str(arrShortcut[i][1])
        execute    = str(arrShortcut[i][2])
        if gui:
            if len(execute) > 80:
                execute = execute[:75] + "....."
            line = "{:2}".format(i) + "\t" + "{:<16}".format(keybinding) \
                   + "\t" + execute
        else:
            line = exe + "{:<16}".format(keybinding) + "\t" + execute
        print(line)
        write_file(line)


def check_rcfile(fpath, mode):
    try:
        open(fpath, mode).close()
    except IOError:
        return False
    return True


def write_file(line):
    with open(kb_fpath, 'a') as f:
        f.write(line + "\n")


def check_txtfile(kb_fpath):
    try:
        open(kb_fpath, 'w').close()
    except IOError:
        return False
    return True


if __name__ == "__main__":
    gui = cmdargs()
    check_txtfile(kb_fpath)

    if gui:
        write_file(str(datetime.date.today()) + "\trc.xml KEYBINDS (labwc)")
        write_file("-------------------------------\n")

    if check_rcfile(rc_fpath, "r"):
        try:
            keyboard()
        except Exception as e:
            print("\nCan't parse rc.xml: " + str(e) + "\n\tCheck it with: xmllint --noout " + rc_fpath + "\n")
            sys.exit(1)
        output_keybinds(arrShortcut, gui)
    else:
        print("\nCan't open rc.xml for parsing\n\tCheck the filepath: " + rc_fpath + "\n")
        sys.exit(1)

    if gui:
        write_file("\n(labwc's built-in default keys are not listed here)")
        dlg  = "yad --text-info --title='Mabox labwc Keybinds' "
        dlg += "--window-icon=distributor-logo-mabox --image=/usr/share/icons/mabox_trans_64.png "
        dlg += "--text '\n<b>Key bindings</b> are configured in your <i>" + rc_fpath + "</i> file.  <a href=\"https://labwc.github.io/labwc-config.5.html\">docs</a>\n\t\t\t\t<b>S</b> - Shift key\n\t\t\t\t<b>C</b> - Control key\n\t\t\t\t<b>A</b> - Alt key\n\t\t\t\t<b>W</b> - Super key (usually bound to the Windows key)' "
        dlg += "--filename=" + kb_fpath + " "
        dlg += "--width=640 --height=560 --fontname=Monospace "
        dlg += "--button='Edit rc.xml:geany " + rc_fpath + "' --button='Reload labwc configuration:labwc -r' --button=Close"
        os.system(dlg)

I also created a simplified wayland version of skippy-xd, skippy-wl.

Skippy-wl (No workspaces support)

skippy-wl --show

Jumpapp for x11/wayland. (No workspaces support)

yay -S jumpapp-ng

or manual

Fittsmon-wl : Fittsmon for wayland

On Wayland, use fittsmon-wl in your compositor's autostart. It takes the same options:

fittsmon-wl --monitor eDP-1 &

Created a quake like terminal with terminator. Kitty, I am not ready yet.

q-term-toggle

#!/bin/bash
T=q-term

if ! wlrctl toplevel find title:$T; then
    terminator -T $T &
elif wlrctl toplevel find title:$T state:focused; then
    wlrctl toplevel minimize title:$T
else
    wlrctl toplevel focus title:$T

<keybind key="F12">
  <action name="Execute" command="/home/user/bin/q-term-toggle"/>
</keybind>

Send to another monitor.

<!-- send focused window to monitor -->
    <keybind key="S-C-KP_7"><action name="MoveToOutput" output="HDMI-A-1" /></keybind>
    <keybind key="S-C-KP_Home"><action name="MoveToOutput" output="HDMI-A-1" /></keybind>
    <keybind key="S-C-KP_9"><action name="MoveToOutput" output="DP-1" /></keybind>
    <keybind key="S-C-KP_Prior"><action name="MoveToOutput" output="DP-1" /></keybind>

or with skippy-wl --multi-select. Send multiple windows.

#!/usr/bin/env bash
# Pick windows with skippy-wl, then send each to the dell monitor.
# Focuses a window, then fires the helper keybind W-A-C-9 (MoveToOutput in rc.xml) with wtype.
set -euo pipefail

ids=$(skippy-wl --show --multi-select) || exit 0

while IFS= read -r id; do
    skippy-wl --activate "$id" || continue
    sleep 0.15
    wtype -M logo -M alt -M ctrl -k 9 -m ctrl -m alt -m logo
    sleep 0.15
done <<< "$ids"

My first little tweaks demo.

skippy-wl – expose
jumpapp-ng – runhideraise
fittsmon-wl – screen edges mouse events
quake-term – script
tabdock – tabs from firefox browsers

wallpaper: Cyberpunk

Working on skippy-wl to work nice with noctalia. (coloring)

RC file

## skippy-wl config: copy to ~/.config/skippy-wl/skippy-wl.rc
## "key = value" lines grouped under [sections]. Read each time the overlay opens.
## Remove the leading # from an option to set it.

[layout]
## Pixels around the grid and between tiles.
margin = 48
spacing = 28

[tiles]
## Corner radius in pixels of each window preview and its frame (0-50). 0 = square.
radius = 12

[label]
## Window name on each tile. Colors are #RRGGBB or #RRGGBBAA.
show = true

## windowTitle, windowClass (the app id) or both.
option = both

## below (under the tile), or inside it: top-left, top, top-right, left, center, right, bottom-left, bottom, bottom-right.
position = bottom

## Move the label from its position; it may leave the tile.
offsetX = 0
offsetY = 0

## Box height in pixels (below also reserves it under the tile) and width as a fraction of the tile.
height = 28
width = 0.92
radius = 6

## opacity (0-255) scales border and background. backgroundHighlight is for the selected tile; unset = background.
#border = #ffffff59
#background = #16181d
#backgroundHighlight = #2d5aa8
opacity = 235

text = #ffffff
textOutline = #00000000

## Pango description or skippy-xd style (Name:size=12:weight=bold). Also sets the filter bar font.
font = Sans 11

[monitors]
## Windows per monitor: each (the ones on that monitor) or pointer (all of them on the monitor under the pointer).
mode = pointer

[switch]
## Switch mode (--next/--prev): releasing the modifiers held when the overlay opened picks the selected window.
pivot = true
## ms to wait before drawing; a tap shorter than that switches without drawing (0 = draw at once; needs pivot).
wait = 100
## ms: releasing the modifiers this long after the overlay was drawn or the last key press leaves it open (0 = always pick).
hold = 1000

[icons]
## App icon on each tile. Set theme to your icon theme: hicolor alone lacks many apps.
## size is in pixels (16-256); the theme's parents and hicolor are searched too.
enabled = true
size = 64
theme = hicolor
position = center

[animation]
## Fade and grow when the overlay opens. duration is in ms (0-1000); 0 shows it at once.
enabled = true
duration = 950

[background]
## PNG, JPEG, AVIF, HEIC or HEIF image shown behind the tiles, scaled to fill each monitor. Unset = plain color.
## colors.background = #00000000 makes the overlay see-through: windows show behind the tiles.
#image = /home/vogel/wallpapers/splash.png

## Shell command that prints the image path, run each time the overlay opens. Falls back to image if it fails or takes over 1 s.
image_cmd = noctalia msg wallpaper-get

## show: leave room for panels and docks. hide: the overlay covers them.
panels = show

[colors]
## #RRGGBB or #RRGGBBAA. With an image, setting it tints the image; leave it unset for the image alone.
background = #00000000
#highlight = #5999ff
#border = #ffffff2e

## Labwc theme file to take the highlight and border colors from (only a leading ~/ is expanded).
## Tile borders from the theme use 60% alpha. highlight and border above win.
theme_file = ~/.local/share/themes/noctalia/openbox-3/themerc

[hide]
## Windows whose app id, title or status matches are never shown, in --switch either. Comma separated globs, same syntax as --app-id and --title.
#app_id = org.keepassxc.KeePassXC
#title = Picture-in-Picture
## minimized, maximized, fullscreen or activated.
#status = minimized

[bind]
## xkb keysym names, comma separated. A line replaces the default keys of that action.
select = Return, KP_Enter
cancel = Escape
close = Delete
#minimize = m, M
left = Left
right = Right
up = Up
down = Down
#home = Home
#end = End
#next = Tab
#prev = ISO_Left_Tab

Skippy-wl → Multiselect → Action

Skippy-wl search / .

mb-music → Noctalia /music

Created a plugin to search music from mb-music.csv.

noctalia msg panel-open launcher /music

mabox-noctalia-ce

in development and mainly for inspiration

The installation creates Noctalia CE session at login. So no conflicts with mabox-noctalia or other sessions.

Alternative to colormenu for noctalia.

noctalia msg panel-toggle musqz/colors:picker

Noctalia mabox/notes plugin. (testing)

A simple notes manager. For me an alternative to xpad.

  • left panel is pinned (toggle with key)
  • top is main panel for all edits
<keybind key="W-n"><action name="Execute" command="noctalia msg panel-toggle mabox/notes:main"/></keybind>
    <keybind key="W-A-n"><action name="Execute" command="noctalia msg panel-toggle mabox/notes:pinned"/></keybind>

Hi @napcok,

The ./install.sh is improved.

To get the latest commits

git pull
./install.sh

Some bug fixes and added Tools to the menu.
Tools are custom plugins for the noctalia desktop I created.
Some inspired by tools from Mabox.

  • colors
  • notes
  • music
  • status

status

status - settings

Update

git pull
./install.sh

:slight_smile:

Snap/unsnap to pinned panel toggle.

A patch for noctalia to be able to this with pinned panels.