Compare commits
2 Commits
7bee7ac143
...
05b485b924
| Author | SHA1 | Date | |
|---|---|---|---|
| 05b485b924 | |||
| 9c29be09bf |
0
__init__.py
Normal file
0
__init__.py
Normal file
BIN
__pycache__/config.cpython-313.pyc
Normal file
BIN
__pycache__/config.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/config.cpython-314.pyc
Normal file
BIN
__pycache__/config.cpython-314.pyc
Normal file
Binary file not shown.
50
config.py
Normal file
50
config.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from modules.screens import screens, widget_defaults
|
||||||
|
from modules.keys import keys, mouse
|
||||||
|
from modules.layouts import layouts, floating_layout
|
||||||
|
from modules.groups import groups
|
||||||
|
from modules.hooks import *
|
||||||
|
|
||||||
|
from libqtile import qtile
|
||||||
|
from libqtile.backend.wayland.inputs import InputConfig
|
||||||
|
|
||||||
|
# fixing the import warning
|
||||||
|
widget_defaults = widget_defaults
|
||||||
|
screens = screens
|
||||||
|
keys = keys
|
||||||
|
mouse = mouse
|
||||||
|
layouts = layouts
|
||||||
|
floating_layout = floating_layout
|
||||||
|
groups = groups
|
||||||
|
|
||||||
|
# General Setup
|
||||||
|
dgroups_key_binder = None
|
||||||
|
dgroups_app_rules = [] # type: list
|
||||||
|
follow_mouse_focus = True
|
||||||
|
bring_front_click = True
|
||||||
|
floats_kept_above = True
|
||||||
|
cursor_warp = True
|
||||||
|
auto_fullscreen = True
|
||||||
|
focus_on_window_activation = "smart"
|
||||||
|
reconfigure_screens = True
|
||||||
|
auto_minimize = True
|
||||||
|
|
||||||
|
|
||||||
|
wmname = "Qtile"
|
||||||
|
|
||||||
|
# Wayland configuration
|
||||||
|
if qtile.core.name == "X11":
|
||||||
|
term = "urvx"
|
||||||
|
elif qtile.core.name == "wayland":
|
||||||
|
term = "foot"
|
||||||
|
|
||||||
|
wl_input_rules = {
|
||||||
|
"type:keyboard": InputConfig(
|
||||||
|
kb_repeat_delay=200,
|
||||||
|
kb_repeat_rate=60,
|
||||||
|
kb_layout="de",
|
||||||
|
kb_options="nodeadkeys",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
# xcursor theme (string or None) and size (integer) for Wayland backend
|
||||||
|
wl_xcursor_theme = None
|
||||||
|
wl_xcursor_size = 18
|
||||||
0
modules/__init__.py
Normal file
0
modules/__init__.py
Normal file
BIN
modules/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
modules/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
modules/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/groups.cpython-313.pyc
Normal file
BIN
modules/__pycache__/groups.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/groups.cpython-314.pyc
Normal file
BIN
modules/__pycache__/groups.cpython-314.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/hooks.cpython-313.pyc
Normal file
BIN
modules/__pycache__/hooks.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/hooks.cpython-314.pyc
Normal file
BIN
modules/__pycache__/hooks.cpython-314.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/keys.cpython-313.pyc
Normal file
BIN
modules/__pycache__/keys.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/keys.cpython-314.pyc
Normal file
BIN
modules/__pycache__/keys.cpython-314.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/layouts.cpython-313.pyc
Normal file
BIN
modules/__pycache__/layouts.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/layouts.cpython-314.pyc
Normal file
BIN
modules/__pycache__/layouts.cpython-314.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/screens.cpython-313.pyc
Normal file
BIN
modules/__pycache__/screens.cpython-313.pyc
Normal file
Binary file not shown.
BIN
modules/__pycache__/screens.cpython-314.pyc
Normal file
BIN
modules/__pycache__/screens.cpython-314.pyc
Normal file
Binary file not shown.
161
modules/groups.py
Normal file
161
modules/groups.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
from libqtile.config import Group, Key, Match, DropDown, ScratchPad
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
from .keys import keys, mod
|
||||||
|
import re
|
||||||
|
|
||||||
|
group_screen_map = {
|
||||||
|
"0": 0,
|
||||||
|
"1": 0,
|
||||||
|
"2": 0,
|
||||||
|
"3": 0,
|
||||||
|
"4": 2,
|
||||||
|
"5": 2,
|
||||||
|
"6": 2,
|
||||||
|
"7": 1,
|
||||||
|
"8": 1,
|
||||||
|
"9": 1,
|
||||||
|
"f1": 2,
|
||||||
|
"f2": 1,
|
||||||
|
"f3": 2,
|
||||||
|
"f4": 1,
|
||||||
|
"f5": 1,
|
||||||
|
"f6": 1,
|
||||||
|
"f7": 2,
|
||||||
|
"f8": 0,
|
||||||
|
"f9": 0,
|
||||||
|
"f10": 0,
|
||||||
|
"f11": 0,
|
||||||
|
"f12": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
groups = [
|
||||||
|
Group(
|
||||||
|
name="0",
|
||||||
|
label="",
|
||||||
|
matches=[
|
||||||
|
Match(wm_class=re.compile(r"^(steam)")),
|
||||||
|
Match(wm_class=re.compile(r"^(Minecraft*)")),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Group(name="1", label=""),
|
||||||
|
Group(name="2", label=""),
|
||||||
|
Group(name="3", label=""),
|
||||||
|
Group(name="4", label=""),
|
||||||
|
Group(name="5", label=""),
|
||||||
|
Group(name="6", label=""),
|
||||||
|
Group(name="7", label=""),
|
||||||
|
Group(name="8", label=""),
|
||||||
|
Group(name="9", label=""),
|
||||||
|
# Groups on function-keys
|
||||||
|
Group(
|
||||||
|
name="f1",
|
||||||
|
label="",
|
||||||
|
matches=[Match(wm_class=re.compile(r"^(firefox)$"))],
|
||||||
|
),
|
||||||
|
Group(
|
||||||
|
name="f2",
|
||||||
|
label=" ",
|
||||||
|
matches=[
|
||||||
|
Match(wm_class="discord"),
|
||||||
|
Match(wm_class=re.compile(r"^(com.github.th_ch.youtube_music)$")),
|
||||||
|
Match(wm_class="RMPC"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Group(
|
||||||
|
name="f3",
|
||||||
|
label="",
|
||||||
|
matches=[Match(wm_class=re.compile(r"^(joplin)$"))],
|
||||||
|
),
|
||||||
|
Group(
|
||||||
|
name="f4",
|
||||||
|
label="",
|
||||||
|
),
|
||||||
|
Group(
|
||||||
|
name="f5",
|
||||||
|
label=" ",
|
||||||
|
matches=[Match(wm_class="AFFiNE")],
|
||||||
|
),
|
||||||
|
Group(
|
||||||
|
name="f6",
|
||||||
|
label=" ",
|
||||||
|
matches=[Match(wm_class="bitwarden")],
|
||||||
|
),
|
||||||
|
Group(
|
||||||
|
name="f7",
|
||||||
|
label=" ",
|
||||||
|
matches=[Match(wm_class=re.compile(r"^(XPipe)$"))],
|
||||||
|
),
|
||||||
|
Group(name="f8", label=""),
|
||||||
|
Group(name="f9", label=""),
|
||||||
|
Group(name="f10", label=""),
|
||||||
|
Group(name="f11", label=""),
|
||||||
|
Group(name="f12", label=""),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def go_to_group(name: str):
|
||||||
|
def _inner(qtile):
|
||||||
|
screen = group_screen_map.get(name, 0)
|
||||||
|
if len(qtile.screens) <= 2:
|
||||||
|
qtile.groups_map[name].toscreen()
|
||||||
|
else:
|
||||||
|
qtile.focus_screen(screen)
|
||||||
|
qtile.groups_map[name].toscreen()
|
||||||
|
|
||||||
|
return _inner
|
||||||
|
|
||||||
|
|
||||||
|
for i in groups:
|
||||||
|
keys.append(Key([mod], i.name, lazy.function(go_to_group(i.name))))
|
||||||
|
|
||||||
|
|
||||||
|
def go_to_group_and_move_window(name: str):
|
||||||
|
def _inner(qtile):
|
||||||
|
screen = group_screen_map.get(name, 0)
|
||||||
|
if len(qtile.screens) <= 2:
|
||||||
|
qtile.current_window.togroup(name, switch_group=False)
|
||||||
|
else:
|
||||||
|
qtile.current_window.togroup(name, switch_group=False)
|
||||||
|
qtile.focus_screen(screen)
|
||||||
|
qtile.groups_map[name].toscreen()
|
||||||
|
|
||||||
|
return _inner
|
||||||
|
|
||||||
|
|
||||||
|
for i in groups:
|
||||||
|
keys.append(
|
||||||
|
Key([mod, "shift"], i.name, lazy.function(go_to_group_and_move_window(i.name)))
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(1, 12):
|
||||||
|
group_name = f"f{i}"
|
||||||
|
key = f"F{i}"
|
||||||
|
keys.append
|
||||||
|
|
||||||
|
groups.append(
|
||||||
|
ScratchPad(
|
||||||
|
"scratchpad",
|
||||||
|
[
|
||||||
|
DropDown(
|
||||||
|
"term",
|
||||||
|
"kitty",
|
||||||
|
width=0.7,
|
||||||
|
height=0.9,
|
||||||
|
x=0.15,
|
||||||
|
y=0.05,
|
||||||
|
opacity=0.8,
|
||||||
|
on_focus_lost_hide=True,
|
||||||
|
),
|
||||||
|
DropDown(
|
||||||
|
"calc",
|
||||||
|
"qalculate-gtk",
|
||||||
|
width=0.3,
|
||||||
|
height=0.6,
|
||||||
|
x=0.35,
|
||||||
|
y=0.2,
|
||||||
|
opacity=1,
|
||||||
|
on_focus_lost_hide=False,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
46
modules/hooks.py
Normal file
46
modules/hooks.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# _ _ _ _ _
|
||||||
|
# __ _| |_(_) | ___ | |__ ___ ___ | | _____
|
||||||
|
# / _` | __| | |/ _ \ | '_ \ / _ \ / _ \| |/ / __|
|
||||||
|
# | (_| | |_| | | __/ | | | | (_) | (_) | <\__ \
|
||||||
|
# \__, |\__|_|_|\___| |_| |_|\___/ \___/|_|\_\___/
|
||||||
|
# |_|
|
||||||
|
# by cerberus
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
from libqtile import hook, qtile
|
||||||
|
import subprocess
|
||||||
|
import os.path
|
||||||
|
from libqtile.config import Match
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# HOOK startup
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Client management
|
||||||
|
FULLSCREEN_RULES = [Match(wm_class="flameshot")]
|
||||||
|
|
||||||
|
|
||||||
|
@hook.subscribe.startup_once
|
||||||
|
def autostart():
|
||||||
|
autostartscript = "~/.config/qtile/res/scripts/autostart.sh"
|
||||||
|
home = os.path.expanduser(autostartscript)
|
||||||
|
subprocess.Popen([home])
|
||||||
|
|
||||||
|
|
||||||
|
@hook.subscribe.startup_complete
|
||||||
|
def go_to_group1():
|
||||||
|
qtile.focus_screen(0)
|
||||||
|
qtile.groups_map["1"].toscreen()
|
||||||
|
|
||||||
|
|
||||||
|
@hook.subscribe.client_managed
|
||||||
|
def force_fullscreen(client) -> None:
|
||||||
|
"""
|
||||||
|
Some clients won't start fullscreen (exclusive to wayland)
|
||||||
|
With this function we force clients defined in FULLSCREEN_RULES to enter fullscreen
|
||||||
|
"""
|
||||||
|
|
||||||
|
if any(client.match(rule) for rule in FULLSCREEN_RULES):
|
||||||
|
client.fullscreen = True
|
||||||
161
modules/keys.py
Normal file
161
modules/keys.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# _ _ _ _
|
||||||
|
# __ _| |_(_) | ___ | | _____ _ _ ___
|
||||||
|
# / _` | __| | |/ _ \ | |/ / _ \ | | / __|
|
||||||
|
# | (_| | |_| | | __/ | < __/ |_| \__ \
|
||||||
|
# \__, |\__|_|_|\___| |_|\_\___|\__, |___/
|
||||||
|
# |_| |___/
|
||||||
|
# by cerberus
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
from libqtile.config import Key, Drag, Click
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
|
||||||
|
# from screens import notifier
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Set default apps
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
terminal = "kitty"
|
||||||
|
browser = "firefox"
|
||||||
|
filemanager = "nemo"
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Keybindings
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
mod = "mod4" # SUPER KEY
|
||||||
|
alt = "mod1"
|
||||||
|
|
||||||
|
# KeybindStart
|
||||||
|
keys = [
|
||||||
|
# KB_GROUP-Focus Window
|
||||||
|
Key([mod], "h", lazy.layout.left(), desc="Move focus to left"),
|
||||||
|
Key([mod], "l", lazy.layout.right(), desc="Move focus to right"),
|
||||||
|
Key([mod], "j", lazy.layout.down(), desc="Move focus down"),
|
||||||
|
Key([mod], "k", lazy.layout.up(), desc="Move focus up"),
|
||||||
|
# KB_GROUP-Move Window
|
||||||
|
# Key([mod, "shift"], "h", lazy.layout.shuffle_left(), desc="Move window to the left"),
|
||||||
|
# Key([mod, "shift"], "l", lazy.layout.shuffle_right(), desc="Move window to the right"),
|
||||||
|
Key(
|
||||||
|
[mod, "shift"],
|
||||||
|
"h",
|
||||||
|
lazy.layout.swap_left(),
|
||||||
|
desc="Move window to the left - Monad",
|
||||||
|
),
|
||||||
|
Key(
|
||||||
|
[mod, "shift"],
|
||||||
|
"l",
|
||||||
|
lazy.layout.swap_right(),
|
||||||
|
desc="Move window to the left - Monad",
|
||||||
|
),
|
||||||
|
Key([mod, "shift"], "j", lazy.layout.shuffle_down(), desc="Move window down"),
|
||||||
|
Key([mod, "shift"], "k", lazy.layout.shuffle_up(), desc="Move window up"),
|
||||||
|
# KB_GROUP-Resize Window
|
||||||
|
Key([mod], "m", lazy.layout.shrink(), desc="Grow window to the top"),
|
||||||
|
Key([mod], "i", lazy.layout.grow(), desc="Grow window to the bottom"),
|
||||||
|
# KB_GROUP-Window Controls
|
||||||
|
Key([mod], "n", lazy.layout.normalize(), desc="Normalize all window sizes"),
|
||||||
|
Key([mod, "shift"], "n", lazy.layout.reset(), desc="Reset all window sizes"),
|
||||||
|
Key([mod], "t", lazy.window.toggle_floating(), desc="Toggle floating"),
|
||||||
|
Key([mod], "o", lazy.layout.maximize(), desc="Maximize window"),
|
||||||
|
Key(
|
||||||
|
[mod, "shift"],
|
||||||
|
"s",
|
||||||
|
lazy.layout.toggle_auto_maximize(),
|
||||||
|
desc="Toggle auto maximize",
|
||||||
|
),
|
||||||
|
Key([mod], "f", lazy.window.toggle_fullscreen(), desc="Toggle full screen"),
|
||||||
|
Key([mod, "shift"], "space", lazy.layout.flip(), desc="Flip windows"),
|
||||||
|
Key(
|
||||||
|
[mod, "shift"],
|
||||||
|
"Return",
|
||||||
|
lazy.layout.toggle_split(),
|
||||||
|
desc="Toggle between split and unsplit sides of stack",
|
||||||
|
),
|
||||||
|
# KB_GROUP-System Controls
|
||||||
|
Key([mod], "Tab", lazy.next_layout(), desc="Toggle between layouts"),
|
||||||
|
Key([mod], "c", lazy.window.kill(), desc="Kill focused window"),
|
||||||
|
Key([mod, "control"], "r", lazy.reload_config(), desc="Reload the config"),
|
||||||
|
# KB_GROUP-Audio and Media Control
|
||||||
|
Key([], "XF86AudioMute", lazy.spawn("pamixer -t"), desc="Mute Audio"),
|
||||||
|
Key([], "XF86AudioLowerVolume", lazy.spawn("pamixer -d 2"), desc="Lower Volume"),
|
||||||
|
Key([], "XF86AudioRaiseVolume", lazy.spawn("pamixer -i 2"), desc="Next Song"),
|
||||||
|
Key([], "XF86AudioPrev", lazy.spawn("playerctl previous"), desc="Previous Media"),
|
||||||
|
Key(
|
||||||
|
[], "XF86AudioPlay", lazy.spawn("playerctl play-pause"), desc="Play Pause Media"
|
||||||
|
),
|
||||||
|
Key([], "XF86AudioNext", lazy.spawn("playerctl next"), desc="Next Media"),
|
||||||
|
# MPD player
|
||||||
|
# NOTE: requires rmpc to be installed
|
||||||
|
Key(["control"], "XF86AudioPrev", lazy.spawn("rmpc prev"), desc="Previous Media"),
|
||||||
|
Key(
|
||||||
|
["control"],
|
||||||
|
"XF86AudioPlay",
|
||||||
|
lazy.spawn("rmpc togglepause"),
|
||||||
|
desc="Play Pause Media",
|
||||||
|
),
|
||||||
|
Key(["control"], "XF86AudioNext", lazy.spawn("rmpc next"), desc="Next Media"),
|
||||||
|
# KB_GROUP-Rofi Menus
|
||||||
|
Key([mod], "r", lazy.spawn("rofi -show drun -show-icons"), desc="Spawn Rofi D-Run"),
|
||||||
|
# KB_GROUP-ScratchPad
|
||||||
|
Key(
|
||||||
|
["control"],
|
||||||
|
"1",
|
||||||
|
lazy.group["scratchpad"].dropdown_toggle("term"),
|
||||||
|
desc="PopUp Terminal",
|
||||||
|
),
|
||||||
|
Key(
|
||||||
|
["control"],
|
||||||
|
"2",
|
||||||
|
lazy.group["scratchpad"].dropdown_toggle("calc"),
|
||||||
|
desc="Calculator",
|
||||||
|
),
|
||||||
|
# KB_GROUP-Programs
|
||||||
|
Key([mod], "Return", lazy.spawn(terminal), desc="Launch terminal"),
|
||||||
|
Key([mod], "v", lazy.spawn("copyq toggle"), desc="Shows Clipboard"),
|
||||||
|
Key([mod, "shift"], "q", lazy.spawn("flameshot gui"), desc="Screenshot selection"),
|
||||||
|
Key(
|
||||||
|
[mod, "shift"], "b", lazy.spawn(browser), desc="Opens Browser on current screen"
|
||||||
|
),
|
||||||
|
Key(
|
||||||
|
[mod, "shift"],
|
||||||
|
"p",
|
||||||
|
lazy.spawn("firefox --private-window"),
|
||||||
|
desc="Opens Private Browser on current screen",
|
||||||
|
),
|
||||||
|
Key([mod, "shift"], "e", lazy.spawn(filemanager), desc="Opens File Manager"),
|
||||||
|
Key(
|
||||||
|
[mod, "control"],
|
||||||
|
"a",
|
||||||
|
lazy.spawn("python /home/cerberus/.config/qtile/res/scripts/autoclicker.py"),
|
||||||
|
desc="Autoclicker On",
|
||||||
|
),
|
||||||
|
Key(
|
||||||
|
[mod, "control"],
|
||||||
|
"s",
|
||||||
|
lazy.spawn("pkill -f autoclicker.py"),
|
||||||
|
desc="Autoclicker Off",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Drag floating layouts
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
mouse = [
|
||||||
|
Drag(
|
||||||
|
[mod],
|
||||||
|
"Button1",
|
||||||
|
lazy.window.set_position_floating(),
|
||||||
|
start=lazy.window.get_position(),
|
||||||
|
),
|
||||||
|
Drag(
|
||||||
|
[mod], "Button3", lazy.window.set_size_floating(), start=lazy.window.get_size()
|
||||||
|
),
|
||||||
|
Click([mod], "Button2", lazy.window.bring_to_front()),
|
||||||
|
]
|
||||||
80
modules/layouts.py
Normal file
80
modules/layouts.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# _ _ _ _ _
|
||||||
|
# __ _| |_(_) | ___ | | __ _ _ _ ___ _ _| |_ ___
|
||||||
|
# / _` | __| | |/ _ \ | |/ _` | | | |/ _ \| | | | __/ __|
|
||||||
|
# | (_| | |_| | | __/ | | (_| | |_| | (_) | |_| | |_\__ \
|
||||||
|
# \__, |\__|_|_|\___| |_|\__,_|\__, |\___/ \__,_|\__|___/
|
||||||
|
# |_| |___/
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
from libqtile import layout
|
||||||
|
from libqtile.config import Match
|
||||||
|
|
||||||
|
layout_defaults = dict(
|
||||||
|
margin=3,
|
||||||
|
border_width=0,
|
||||||
|
# border_focus=gruvbox_dark["red"],
|
||||||
|
# border_normal=gruvbox_dark["fg1"],
|
||||||
|
grow_amount=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
floating_layout_defaults = layout_defaults.copy()
|
||||||
|
|
||||||
|
layouts = [
|
||||||
|
# layout.Max(**layout_defaults),
|
||||||
|
# layout.Plasma(),
|
||||||
|
# layout.Columns(),
|
||||||
|
# layout.Matrix(),
|
||||||
|
layout.MonadTall(
|
||||||
|
name="Monad",
|
||||||
|
auto_maximize=True,
|
||||||
|
change_ratio=0.05,
|
||||||
|
change_size=20,
|
||||||
|
ratio=0.55,
|
||||||
|
min_ratio=0.30,
|
||||||
|
max_ratio=0.75,
|
||||||
|
single_border_width=0,
|
||||||
|
**layout_defaults,
|
||||||
|
),
|
||||||
|
layout.Bsp(
|
||||||
|
name="bsp",
|
||||||
|
ratio=0.5,
|
||||||
|
lower_right=True,
|
||||||
|
**layout_defaults,
|
||||||
|
),
|
||||||
|
layout.Spiral(**layout_defaults),
|
||||||
|
# layout.Stack(num_stacks=2),
|
||||||
|
# layout.MonadWide(**layout_defaults),
|
||||||
|
# layout.RatioTile(**layout_defaults),
|
||||||
|
# layout.Tile(name="Tile", **layout_defaults),
|
||||||
|
# layout.TreeTab(**layout_defaults),
|
||||||
|
layout.VerticalTile(**layout_defaults),
|
||||||
|
# layout.Zoomy(**layout_defaults),
|
||||||
|
]
|
||||||
|
|
||||||
|
floating_layout = layout.Floating(
|
||||||
|
float_rules=[
|
||||||
|
# Run the utility of `xprop` to see the wm class and name of an X client.
|
||||||
|
*layout.Floating.default_float_rules,
|
||||||
|
Match(wm_class="confirmreset"), # gitk
|
||||||
|
Match(wm_class="makebranch"), # gitk
|
||||||
|
Match(wm_class="maketag"), # gitk
|
||||||
|
Match(wm_class="ssh-askpass"), # ssh-askpass
|
||||||
|
Match(wm_class="nm-connection-editor"), # networkmanager
|
||||||
|
Match(title="branchdialog"), # gitk
|
||||||
|
Match(title="pinentry"), # GPG key password entry
|
||||||
|
Match(title="FloatWindow"),
|
||||||
|
Match(wm_class="qalculate-qt"),
|
||||||
|
Match(wm_class="copyq"),
|
||||||
|
Match(wm_class="nitrogen"),
|
||||||
|
Match(wm_class="nemo-preview-start"),
|
||||||
|
Match(wm_class="wireguird"),
|
||||||
|
Match(wm_class="blueman-manager"),
|
||||||
|
Match(wm_class="pavucontrol"),
|
||||||
|
Match(wm_class="org.gnome.FileRoller"),
|
||||||
|
Match(wm_class="lximage-qt"),
|
||||||
|
],
|
||||||
|
**floating_layout_defaults,
|
||||||
|
)
|
||||||
257
modules/screens.py
Normal file
257
modules/screens.py
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
# _ _ _
|
||||||
|
# __ _| |_(_) | ___ ___ ___ _ __ ___ ___ _ __ ___
|
||||||
|
# / _` | __| | |/ _ \ / __|/ __| '__/ _ \/ _ \ '_ \/ __|
|
||||||
|
# | (_| | |_| | | __/ \__ \ (__| | | __/ __/ | | \__ \
|
||||||
|
# \__, |\__|_|_|\___| |___/\___|_| \___|\___|_| |_|___/
|
||||||
|
# |_|
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
from libqtile.config import Screen
|
||||||
|
from libqtile import bar
|
||||||
|
from libqtile.widget import mpd2widget
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
from qtile_extras import widget
|
||||||
|
from qtile_extras.widget.groupbox2 import GroupBoxRule
|
||||||
|
|
||||||
|
# from plugins.notifications import Notifier
|
||||||
|
from plugins.graphical_notifications import Notifier
|
||||||
|
|
||||||
|
from popups.powermenu import power_menu
|
||||||
|
from popups.start_menu import start_menu
|
||||||
|
from popups.calendar import calendar
|
||||||
|
from popups.mpris2_layout import MPRIS2_LAYOUT
|
||||||
|
from popups.volume_notification import VOL_POPUP
|
||||||
|
|
||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# GroupBox2 rules
|
||||||
|
# --------------------------------------------------------
|
||||||
|
def get_groupbox_rules(monitor_specific=True):
|
||||||
|
# Base rules applied to all GroupBoxes
|
||||||
|
rules = [
|
||||||
|
GroupBoxRule(text_colour=gruvbox_dark["bg3"]).when(
|
||||||
|
focused=False, occupied=True
|
||||||
|
),
|
||||||
|
GroupBoxRule(text_colour=gruvbox_dark["aqua"]).when(
|
||||||
|
focused=False, occupied=False
|
||||||
|
),
|
||||||
|
GroupBoxRule(text_colour=gruvbox_dark["fg3"]).when(focused=True),
|
||||||
|
GroupBoxRule(text_colour=gruvbox_dark["red"]).when(
|
||||||
|
focused=False, occupied=True, urgent=True
|
||||||
|
),
|
||||||
|
GroupBoxRule(visible=False).when(focused=False, occupied=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add extra rule for a specific monitor (e.g., show "X" as label)
|
||||||
|
if monitor_specific:
|
||||||
|
rules.append(GroupBoxRule(text=""))
|
||||||
|
return rules
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# Widget Defaults
|
||||||
|
# --------------------------------------------------------
|
||||||
|
widget_defaults = dict(
|
||||||
|
font="Roboto Flex",
|
||||||
|
fontsize=20,
|
||||||
|
foreground=gruvbox_dark["fg1"],
|
||||||
|
)
|
||||||
|
extension_defaults = widget_defaults.copy()
|
||||||
|
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# Screens
|
||||||
|
# --------------------------------------------------------
|
||||||
|
bar.Bar
|
||||||
|
screens = [
|
||||||
|
Screen(
|
||||||
|
# Center Screen
|
||||||
|
top=bar.Bar(
|
||||||
|
[
|
||||||
|
widget.TextBox(
|
||||||
|
text="",
|
||||||
|
fontsize=24,
|
||||||
|
foreground=gruvbox_dark["blue"],
|
||||||
|
mouse_callbacks={"Button1": lazy.function(start_menu)},
|
||||||
|
),
|
||||||
|
widget.GroupBox2(
|
||||||
|
padding=5,
|
||||||
|
fontsize=22,
|
||||||
|
font="Open Sans",
|
||||||
|
center_aligned=True,
|
||||||
|
visible_groups=[
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"0",
|
||||||
|
"f8",
|
||||||
|
"f9",
|
||||||
|
"f10",
|
||||||
|
"f11",
|
||||||
|
"f12",
|
||||||
|
],
|
||||||
|
hide_unused=True,
|
||||||
|
rules=get_groupbox_rules(monitor_specific=False),
|
||||||
|
),
|
||||||
|
widget.Spacer(),
|
||||||
|
widget.Systray(
|
||||||
|
icon_size=21,
|
||||||
|
),
|
||||||
|
widget.Spacer(length=6),
|
||||||
|
widget.Clock(mouse_callbacks={"Button1": lazy.function(calendar)}),
|
||||||
|
widget.Spacer(length=2),
|
||||||
|
widget.TextBox(
|
||||||
|
font="Open Sans",
|
||||||
|
fontsize=20,
|
||||||
|
text=" ",
|
||||||
|
mouse_callbacks={"Button1": lazy.function(power_menu)},
|
||||||
|
),
|
||||||
|
widget.PulseVolumeExtra(
|
||||||
|
mode="popup",
|
||||||
|
fmt="",
|
||||||
|
popup_layout=VOL_POPUP,
|
||||||
|
popup_hide_timeout=3,
|
||||||
|
popup_show_args={"relative_to": 8, "y": -70},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
background=gruvbox_dark["bg0_hard"],
|
||||||
|
opacity=0.75,
|
||||||
|
size=32,
|
||||||
|
margin=[3, 3, 0, 3],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Screen(
|
||||||
|
# Right Screen
|
||||||
|
top=bar.Bar(
|
||||||
|
[
|
||||||
|
widget.TextBox(
|
||||||
|
text="",
|
||||||
|
fontsize=24,
|
||||||
|
foreground=gruvbox_dark["blue"],
|
||||||
|
mouse_callbacks={"Button1": lazy.function(start_menu)},
|
||||||
|
),
|
||||||
|
widget.GroupBox2(
|
||||||
|
padding=6,
|
||||||
|
fontsize=22,
|
||||||
|
margin=7,
|
||||||
|
font="Open Sans",
|
||||||
|
center_aligned=True,
|
||||||
|
visible_groups=["7", "8", "9", "f2", "f4", "f5", "f6"],
|
||||||
|
hide_unused=True,
|
||||||
|
rules=get_groupbox_rules(monitor_specific=False),
|
||||||
|
),
|
||||||
|
widget.Spacer(status_format="{play_status} {artist}/{title}"),
|
||||||
|
widget.WidgetBox(
|
||||||
|
fontsize=22,
|
||||||
|
text_closed="",
|
||||||
|
text_open="",
|
||||||
|
widgets=[
|
||||||
|
widget.Memory(
|
||||||
|
format=" {MemPercent}%",
|
||||||
|
font="Open Sans",
|
||||||
|
),
|
||||||
|
widget.CPU(
|
||||||
|
format=" {load_percent}%",
|
||||||
|
font="Open Sans",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
widget.Spacer(length=6),
|
||||||
|
widget.Clock(mouse_callbacks={"Button1": lazy.function(calendar)}),
|
||||||
|
widget.Spacer(length=2),
|
||||||
|
widget.TextBox(
|
||||||
|
font="Open Sans",
|
||||||
|
fontsize=20,
|
||||||
|
text=" ",
|
||||||
|
mouse_callbacks={"Button1": lazy.function(power_menu)},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
background=gruvbox_dark["bg0_hard"],
|
||||||
|
opacity=0.75,
|
||||||
|
size=32,
|
||||||
|
margin=[3, 3, 0, 3],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Screen(
|
||||||
|
# Left Screen
|
||||||
|
top=bar.Bar(
|
||||||
|
[
|
||||||
|
widget.TextBox(
|
||||||
|
text="",
|
||||||
|
fontsize=24,
|
||||||
|
foreground=gruvbox_dark["blue"],
|
||||||
|
mouse_callbacks={"Button1": lazy.function(start_menu)},
|
||||||
|
),
|
||||||
|
widget.GroupBox2(
|
||||||
|
padding=6,
|
||||||
|
fontsize=22,
|
||||||
|
font="Open Sans",
|
||||||
|
center_aligned=True,
|
||||||
|
visible_groups=[
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6",
|
||||||
|
"f1",
|
||||||
|
"f7",
|
||||||
|
"f3",
|
||||||
|
],
|
||||||
|
hide_unused=True,
|
||||||
|
rules=get_groupbox_rules(monitor_specific=False),
|
||||||
|
),
|
||||||
|
widget.Spacer(length=20),
|
||||||
|
widget.Mpris2(
|
||||||
|
name="mpris2",
|
||||||
|
width=350,
|
||||||
|
scroll=True,
|
||||||
|
scroll_clear=True,
|
||||||
|
foreground=gruvbox_dark["fg1"],
|
||||||
|
format="{xesam:title} - {xesam:artist}",
|
||||||
|
paused_text="{track} ",
|
||||||
|
popup_layout=MPRIS2_LAYOUT,
|
||||||
|
poll_interval=15,
|
||||||
|
popup_show_args={"relative_to": 2, "relative_to_bar": True, "y": 3},
|
||||||
|
mouse_callbacks={"Button1": lazy.widget["mpris2"].toggle_player()},
|
||||||
|
),
|
||||||
|
widget.Spacer(),
|
||||||
|
widget.Clock(mouse_callbacks={"Button1": lazy.function(calendar)}),
|
||||||
|
widget.Spacer(length=2),
|
||||||
|
widget.TextBox(
|
||||||
|
fontsize=20,
|
||||||
|
text=" ",
|
||||||
|
mouse_callbacks={"Button1": lazy.function(power_menu)},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
background=gruvbox_dark["bg0_hard"],
|
||||||
|
opacity=0.75,
|
||||||
|
size=32,
|
||||||
|
margin=[3, 3, 0, 3],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
notifier = Notifier(
|
||||||
|
x=int((2560 / 2) - (350 / 2)),
|
||||||
|
y=38,
|
||||||
|
width=350,
|
||||||
|
height=80,
|
||||||
|
format="<b>{summary}</b>\n{app_name}\n{body}",
|
||||||
|
# file_name='/home/cerberus/.config/qtile/normal.png', # Not working
|
||||||
|
foreground=gruvbox_dark["fg1"],
|
||||||
|
background=(
|
||||||
|
gruvbox_dark["bg0_hard"],
|
||||||
|
gruvbox_dark["bg0_hard"],
|
||||||
|
gruvbox_dark["orange"],
|
||||||
|
),
|
||||||
|
horizontal_padding=10,
|
||||||
|
vertical_padding=10,
|
||||||
|
opacity=0.65,
|
||||||
|
border_width=0,
|
||||||
|
font="Open Sans",
|
||||||
|
font_size=16,
|
||||||
|
overflow="more_width",
|
||||||
|
fullscreen="show",
|
||||||
|
screen=2,
|
||||||
|
actions=True,
|
||||||
|
wrap=True,
|
||||||
|
)
|
||||||
0
plugins/__init__.py
Normal file
0
plugins/__init__.py
Normal file
BIN
plugins/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
plugins/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
plugins/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
plugins/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
plugins/__pycache__/graphical_notifications.cpython-313.pyc
Normal file
BIN
plugins/__pycache__/graphical_notifications.cpython-313.pyc
Normal file
Binary file not shown.
BIN
plugins/__pycache__/graphical_notifications.cpython-314.pyc
Normal file
BIN
plugins/__pycache__/graphical_notifications.cpython-314.pyc
Normal file
Binary file not shown.
BIN
plugins/__pycache__/notifications.cpython-313.pyc
Normal file
BIN
plugins/__pycache__/notifications.cpython-313.pyc
Normal file
Binary file not shown.
462
plugins/graphical_notifications.py
Normal file
462
plugins/graphical_notifications.py
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
"""
|
||||||
|
Qtile plugin that acts as a notification server and draws notification windows.
|
||||||
|
|
||||||
|
Clicking on a notification will trigger the default action, e.g. telling Firefox to open
|
||||||
|
the tab that sent the notification. If you want access to a notification's non-default
|
||||||
|
actions then you need to disable the "actions" capability of the `Notifier` by passing
|
||||||
|
`actions=False`.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
from graphical_notifications import Notifier
|
||||||
|
|
||||||
|
notifier = Notifier()
|
||||||
|
|
||||||
|
keys.extend([
|
||||||
|
Key([mod], 'grave', lazy.function(notifier.prev)),
|
||||||
|
Key([mod, 'shift'], 'grave', lazy.function(notifier.next)),
|
||||||
|
Key(['control'], 'space', lazy.function(notifier.close)),
|
||||||
|
])
|
||||||
|
|
||||||
|
Qtile versions known to work: 0.17 - 0.18
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from libqtile import configurable, hook, images, pangocffi, qtile
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
from libqtile.log_utils import logger
|
||||||
|
from libqtile.notify import notifier, ClosedReason
|
||||||
|
from libqtile.popup import Popup
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from cairocffi import ImageSurface
|
||||||
|
|
||||||
|
from libqtile.core.manager import Qtile
|
||||||
|
|
||||||
|
try:
|
||||||
|
from libqtile.notify import Notification
|
||||||
|
except ImportError: # no dbus_next
|
||||||
|
Notification = Any # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class Notifier(configurable.Configurable):
|
||||||
|
"""
|
||||||
|
This class provides a full graphical notification manager for the
|
||||||
|
org.freedesktop.Notifications service implemented in libqtile.notify.
|
||||||
|
|
||||||
|
The format option determines what text is shown on the popup windows, and supports
|
||||||
|
markup and new line characters e.g. '<b>{summary}</b>\n{body}'. Available
|
||||||
|
placeholders are summary, body and app_name.
|
||||||
|
|
||||||
|
Foreground and background colours can be specified either as tuples/lists of 3
|
||||||
|
strings, corresponding to low, normal and critical urgencies, or just a single
|
||||||
|
string which will then be used for all urgencies. The timeout and border options can
|
||||||
|
be set in the same way.
|
||||||
|
|
||||||
|
The max_windows option limits how many popup windows can be drawn at a time. When
|
||||||
|
more notifications are recieved while the maximum number are already drawn,
|
||||||
|
notifications are queued and displayed when existing notifications are closed.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- text overflow
|
||||||
|
- select screen / follow mouse/keyboard focus
|
||||||
|
- critical notifications to replace any visible non-critical notifs immediately?
|
||||||
|
- hints: image-path, desktop-entry (for icon)
|
||||||
|
- hints: Notifier parameters set for single notification?
|
||||||
|
- hints: progress value e.g. int:value:42 with drawing
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
defaults = [
|
||||||
|
("x", 32, "x position on screen to start drawing notifications."),
|
||||||
|
("y", 64, "y position on screen to start drawing notifications."),
|
||||||
|
("width", 192, "Width of notifications."),
|
||||||
|
("height", 64, "Height of notifications."),
|
||||||
|
("format", "{summary}\n{body}", "Text format."),
|
||||||
|
(
|
||||||
|
"foreground",
|
||||||
|
("#ffffff", "#ffffff", "#ffffff"),
|
||||||
|
"Foreground colour of notifications, in ascending order of urgency.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"background",
|
||||||
|
("#111111", "#111111", "#111111"),
|
||||||
|
"Background colour of notifications, in ascending order of urgency.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"border",
|
||||||
|
("#111111", "#111111", "#111111"),
|
||||||
|
"Border colours in ascending order of urgency. Or None for none.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"timeout",
|
||||||
|
(5000, 5000, 0),
|
||||||
|
"Millisecond timeout duration, in ascending order of urgency.",
|
||||||
|
),
|
||||||
|
("opacity", 1.0, "Opacity of notifications."),
|
||||||
|
("border_width", 4, "Line width of drawn borders."),
|
||||||
|
("corner_radius", None, "Corner radius for round corners, or None."),
|
||||||
|
("font", "sans", "Font used in notifications."),
|
||||||
|
("fontsize", 14, "Size of font."),
|
||||||
|
("fontshadow", None, "Color for text shadows, or None for no shadows."),
|
||||||
|
("text_alignment", "left", "Text alignment: left, center or right."),
|
||||||
|
("horizontal_padding", 4, "Padding at sides of text."),
|
||||||
|
("vertical_padding", 4, "Padding at top and bottom of text."),
|
||||||
|
("line_spacing", 4, "Space between lines."),
|
||||||
|
(
|
||||||
|
"overflow",
|
||||||
|
"truncate",
|
||||||
|
"How to deal with too much text: more_width, more_height, or truncate.",
|
||||||
|
),
|
||||||
|
("max_windows", 2, "Maximum number of windows to show at once."),
|
||||||
|
("gap", 12, "Vertical gap between popup windows."),
|
||||||
|
("sticky_history", True, "Disable timeout when browsing history."),
|
||||||
|
("icon_size", 36, "Pixel size of any icons."),
|
||||||
|
("fullscreen", "show", "What to do when in fullscreen: show, hide, or queue."),
|
||||||
|
("screen", "focus", "How to select a screen: focus, mouse, or an int."),
|
||||||
|
("actions", True, "Whether to enable the actions capability."),
|
||||||
|
]
|
||||||
|
capabilities = {"body", "body-markup", "actions"}
|
||||||
|
# specification: https://developer.gnome.org/notification-spec/
|
||||||
|
|
||||||
|
def __init__(self, **config) -> None:
|
||||||
|
configurable.Configurable.__init__(self, **config)
|
||||||
|
self.add_defaults(Notifier.defaults)
|
||||||
|
self._hidden: List[Popup] = []
|
||||||
|
self._shown: List[Popup] = []
|
||||||
|
self._queue: List[Notification] = []
|
||||||
|
self._positions: List[Tuple[int, int]] = []
|
||||||
|
self._scroll_popup: Optional[Popup] = None
|
||||||
|
self._current_id: int = 0
|
||||||
|
self._notif_id: Optional[int] = None
|
||||||
|
self._paused: bool = False
|
||||||
|
self._icons: Dict[str, Tuple[ImageSurface, int]] = {}
|
||||||
|
|
||||||
|
self._make_attr_list("foreground")
|
||||||
|
self._make_attr_list("background")
|
||||||
|
self._make_attr_list("timeout")
|
||||||
|
self._make_attr_list("border")
|
||||||
|
|
||||||
|
hook.subscribe.startup(lambda: asyncio.create_task(self._configure()))
|
||||||
|
|
||||||
|
if self.actions is False:
|
||||||
|
Notifier.capabilities.remove("actions")
|
||||||
|
|
||||||
|
def _make_attr_list(self, attr: str) -> None:
|
||||||
|
"""
|
||||||
|
Turns '#000000' into ('#000000', '#000000', '#000000')
|
||||||
|
"""
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if not isinstance(value, (tuple, list)):
|
||||||
|
setattr(self, attr, (value,) * 3)
|
||||||
|
|
||||||
|
async def _configure(self) -> None:
|
||||||
|
"""
|
||||||
|
This method needs to be called to set up the Notifier with the Qtile manager and
|
||||||
|
create the required popup windows.
|
||||||
|
"""
|
||||||
|
if self.horizontal_padding is None:
|
||||||
|
self.horizontal_padding = self.fontsize // 2
|
||||||
|
if self.vertical_padding is None:
|
||||||
|
self.vertical_padding = self.fontsize // 2
|
||||||
|
|
||||||
|
popup_config = {
|
||||||
|
"x": self.x,
|
||||||
|
"y": self.y,
|
||||||
|
"width": self.width,
|
||||||
|
"height": self.height,
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt in Popup.defaults:
|
||||||
|
key = opt[0]
|
||||||
|
if hasattr(self, key):
|
||||||
|
value = getattr(self, key)
|
||||||
|
if isinstance(value, (tuple, list)):
|
||||||
|
popup_config[key] = value[1]
|
||||||
|
else:
|
||||||
|
popup_config[key] = value
|
||||||
|
|
||||||
|
for win in range(self.max_windows):
|
||||||
|
popup = Popup(qtile, **popup_config)
|
||||||
|
popup.win.process_button_click = self._process_button_click(popup)
|
||||||
|
popup.notif = None
|
||||||
|
self._hidden.append(popup)
|
||||||
|
self._positions.append(
|
||||||
|
(
|
||||||
|
self.x,
|
||||||
|
self.y + win * (self.height + 2 * self.border_width + self.gap),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear defunct callbacks left when reloading the config
|
||||||
|
notifier.callbacks.clear()
|
||||||
|
notifier.close_callbacks.clear()
|
||||||
|
|
||||||
|
await notifier.register(
|
||||||
|
self._notify, Notifier.capabilities, on_close=self._on_close
|
||||||
|
)
|
||||||
|
logger.info("Notification server started up successfully")
|
||||||
|
|
||||||
|
def _process_button_click(self, popup: Popup) -> Callable:
|
||||||
|
def _(x: int, y: int, button: int) -> None:
|
||||||
|
if button == 1:
|
||||||
|
self._act(popup)
|
||||||
|
self._close(popup, reason=ClosedReason.dismissed)
|
||||||
|
if button == 3:
|
||||||
|
self._close(popup, reason=ClosedReason.dismissed)
|
||||||
|
|
||||||
|
return _
|
||||||
|
|
||||||
|
def _notify(self, notif: Notification) -> None:
|
||||||
|
"""
|
||||||
|
This method is registered with the NotificationManager to handle notifications
|
||||||
|
received via dbus. They will either be drawn now or queued to be drawn soon.
|
||||||
|
"""
|
||||||
|
if self._paused:
|
||||||
|
self._queue.append(notif)
|
||||||
|
return
|
||||||
|
|
||||||
|
if qtile.current_window and qtile.current_window.fullscreen:
|
||||||
|
if self.fullscreen != "show":
|
||||||
|
if self.fullscreen == "queue":
|
||||||
|
if self._unfullscreen not in hook.subscriptions:
|
||||||
|
hook.subscribe.float_change(self._unfullscreen)
|
||||||
|
self._queue.append(notif)
|
||||||
|
return
|
||||||
|
|
||||||
|
if notif.replaces_id:
|
||||||
|
for popup in self._shown:
|
||||||
|
if notif.replaces_id == popup.notif.replaces_id:
|
||||||
|
self._shown.remove(popup)
|
||||||
|
self._send(notif, popup)
|
||||||
|
self._reposition()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._hidden:
|
||||||
|
self._send(notif, self._hidden.pop())
|
||||||
|
else:
|
||||||
|
self._queue.append(notif)
|
||||||
|
|
||||||
|
def _on_close(self, nid: int) -> None:
|
||||||
|
for popup in self._shown:
|
||||||
|
self._close(popup, nid=nid, reason=ClosedReason.method)
|
||||||
|
|
||||||
|
def _unfullscreen(self) -> None:
|
||||||
|
"""
|
||||||
|
Begin displaying of queue notifications after leaving fullscreen.
|
||||||
|
"""
|
||||||
|
if not qtile.current_window.fullscreen:
|
||||||
|
hook.unsubscribe.float_change(self._unfullscreen)
|
||||||
|
self._renotify()
|
||||||
|
|
||||||
|
def _renotify(self) -> None:
|
||||||
|
"""
|
||||||
|
If we hold off temporarily on sending notifications and accumulate a queue, we
|
||||||
|
should use this to send the queue through self._notify again.
|
||||||
|
"""
|
||||||
|
queue = self._queue.copy()
|
||||||
|
self._queue.clear()
|
||||||
|
while queue:
|
||||||
|
self._notify(queue.pop(0))
|
||||||
|
|
||||||
|
def _send(
|
||||||
|
self, notif: Notification, popup: Popup, timeout: Optional[int] = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Draw the desired notification using the specified Popup instance.
|
||||||
|
"""
|
||||||
|
text = self._get_text(notif)
|
||||||
|
|
||||||
|
if "urgency" in notif.hints:
|
||||||
|
urgency = notif.hints["urgency"].value
|
||||||
|
else:
|
||||||
|
urgency = 1
|
||||||
|
|
||||||
|
self._current_id += 1
|
||||||
|
popup.id = self._current_id # Used for closing the popup
|
||||||
|
popup.notif = notif # Used for finding the visible popup's notif for actions
|
||||||
|
if popup not in self._shown:
|
||||||
|
self._shown.append(popup)
|
||||||
|
popup.x, popup.y = self._get_coordinates()
|
||||||
|
popup.place()
|
||||||
|
icon = self._load_icon(notif)
|
||||||
|
popup.unhide()
|
||||||
|
|
||||||
|
popup.background = self.background[urgency]
|
||||||
|
popup.layout.colour = self.foreground[urgency]
|
||||||
|
popup.clear()
|
||||||
|
|
||||||
|
# Icon logic
|
||||||
|
if icon:
|
||||||
|
popup.draw_image(
|
||||||
|
icon[0],
|
||||||
|
self.horizontal_padding,
|
||||||
|
1 + (self.height - icon[1]) / 2,
|
||||||
|
)
|
||||||
|
popup.horizontal_padding += self.icon_size + self.horizontal_padding / 2
|
||||||
|
|
||||||
|
# Notification text drawing
|
||||||
|
for num, line in enumerate(text.split("\n")):
|
||||||
|
popup.layout.text = line
|
||||||
|
y = self.vertical_padding + num * (popup.layout.height + self.line_spacing)
|
||||||
|
popup.draw_text(y=y)
|
||||||
|
|
||||||
|
if self.border_width:
|
||||||
|
popup.set_border(self.border[urgency])
|
||||||
|
popup.draw()
|
||||||
|
|
||||||
|
if icon:
|
||||||
|
popup.horizontal_padding = self.horizontal_padding
|
||||||
|
|
||||||
|
if timeout is None:
|
||||||
|
if notif.timeout is None or notif.timeout < 0:
|
||||||
|
timeout = self.timeout[urgency]
|
||||||
|
else:
|
||||||
|
timeout = notif.timeout
|
||||||
|
elif timeout < 0:
|
||||||
|
timeout = self.timeout[urgency]
|
||||||
|
if timeout > 0:
|
||||||
|
qtile.call_later(timeout / 1000, self._close, popup, self._current_id)
|
||||||
|
|
||||||
|
def _get_text(self, notif: Notification) -> str:
|
||||||
|
summary = ""
|
||||||
|
body = ""
|
||||||
|
app_name = ""
|
||||||
|
if notif.summary:
|
||||||
|
summary = pangocffi.markup_escape_text(notif.summary)
|
||||||
|
if notif.body:
|
||||||
|
body = pangocffi.markup_escape_text(notif.body)
|
||||||
|
if notif.app_name:
|
||||||
|
app_name = pangocffi.markup_escape_text(notif.app_name)
|
||||||
|
return self.format.format(summary=summary, body=body, app_name=app_name)
|
||||||
|
|
||||||
|
def _get_coordinates(self) -> Tuple[int, int]:
|
||||||
|
x, y = self._positions[len(self._shown) - 1]
|
||||||
|
if isinstance(self.screen, int):
|
||||||
|
screen = qtile.screens[self.screen]
|
||||||
|
elif self.screen == "focus":
|
||||||
|
screen = qtile.current_screen
|
||||||
|
elif self.screen == "mouse":
|
||||||
|
screen = qtile.find_screen(*qtile.mouse_position)
|
||||||
|
return x + screen.x, y + screen.y
|
||||||
|
|
||||||
|
def _close(self, popup: Popup, nid: Optional[int] = None, reason=1) -> None:
|
||||||
|
"""
|
||||||
|
Close the specified Popup instance.
|
||||||
|
"""
|
||||||
|
if popup in self._shown:
|
||||||
|
if nid is not None and popup.id != nid:
|
||||||
|
return
|
||||||
|
self._shown.remove(popup)
|
||||||
|
if self._scroll_popup is popup:
|
||||||
|
self._scroll_popup = None
|
||||||
|
self._notif_id = None
|
||||||
|
popup.hide()
|
||||||
|
if self._queue and not self._paused:
|
||||||
|
self._send(self._queue.pop(0), popup)
|
||||||
|
else:
|
||||||
|
self._hidden.append(popup)
|
||||||
|
notifier._service.NotificationClosed(popup.notif.id, reason)
|
||||||
|
self._reposition()
|
||||||
|
|
||||||
|
def _act(self, popup: Popup) -> None:
|
||||||
|
"""
|
||||||
|
Execute the actions specified by the notification visible on a clicked popup.
|
||||||
|
"""
|
||||||
|
# Currently this always invokes default action
|
||||||
|
# actions = {i: l for i, l in zip(notif.actions[:-1:2], notif.actions[1::2])}
|
||||||
|
if popup.notif.actions:
|
||||||
|
notifier._service.ActionInvoked(popup.notif.id, popup.notif.actions[0])
|
||||||
|
|
||||||
|
def _reposition(self) -> None:
|
||||||
|
for index, shown in enumerate(self._shown):
|
||||||
|
shown.x, shown.y = self._positions[index]
|
||||||
|
shown.place()
|
||||||
|
|
||||||
|
def _load_icon(self, notif: Notification) -> Optional[Tuple[ImageSurface, int]]:
|
||||||
|
if not notif.app_icon:
|
||||||
|
return None
|
||||||
|
if notif.app_icon in self._icons:
|
||||||
|
return self._icons.get(notif.app_icon)
|
||||||
|
try:
|
||||||
|
img = images.Img.from_path(notif.app_icon)
|
||||||
|
if img.width > img.height:
|
||||||
|
img.resize(width=self.icon_size)
|
||||||
|
else:
|
||||||
|
img.resize(height=self.icon_size)
|
||||||
|
surface, _ = images._decode_to_image_surface(
|
||||||
|
img.bytes_img, img.width, img.height
|
||||||
|
)
|
||||||
|
self._icons[notif.app_icon] = surface, surface.get_height()
|
||||||
|
except (FileNotFoundError, images.LoadingError, IsADirectoryError) as e:
|
||||||
|
logger.exception(e)
|
||||||
|
self._icons[notif.app_icon] = None
|
||||||
|
return self._icons[notif.app_icon]
|
||||||
|
|
||||||
|
def close(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Close the oldest of all visible popup windows.
|
||||||
|
"""
|
||||||
|
if self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
|
|
||||||
|
def close_all(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Close all popup windows.
|
||||||
|
"""
|
||||||
|
self._queue.clear()
|
||||||
|
while self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
|
|
||||||
|
def prev(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Display the previous notification in the history.
|
||||||
|
"""
|
||||||
|
if notifier.notifications:
|
||||||
|
if self._scroll_popup is None:
|
||||||
|
if self._hidden:
|
||||||
|
self._scroll_popup = self._hidden.pop(0)
|
||||||
|
else:
|
||||||
|
self._scroll_popup = self._shown[0]
|
||||||
|
self._notif_id = len(notifier.notifications)
|
||||||
|
if self._notif_id > 0:
|
||||||
|
self._notif_id -= 1
|
||||||
|
self._send(
|
||||||
|
notifier.notifications[self._notif_id],
|
||||||
|
self._scroll_popup,
|
||||||
|
0 if self.sticky_history else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def next(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Display the next notification in the history.
|
||||||
|
"""
|
||||||
|
if self._scroll_popup:
|
||||||
|
if self._notif_id < len(notifier.notifications) - 1:
|
||||||
|
self._notif_id += 1
|
||||||
|
if self._scroll_popup in self._shown:
|
||||||
|
self._shown.remove(self._scroll_popup)
|
||||||
|
self._send(
|
||||||
|
notifier.notifications[self._notif_id],
|
||||||
|
self._scroll_popup,
|
||||||
|
0 if self.sticky_history else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def pause(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Pause display of notifications on screen. Notifications will be queued and
|
||||||
|
presented as usual when this is called again.
|
||||||
|
"""
|
||||||
|
if self._paused:
|
||||||
|
self._paused = False
|
||||||
|
self._renotify()
|
||||||
|
else:
|
||||||
|
self._paused = True
|
||||||
|
while self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
458
plugins/notifications.py
Normal file
458
plugins/notifications.py
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
"""
|
||||||
|
Qtile plugin that acts as a notification server and draws notification windows.
|
||||||
|
|
||||||
|
Clicking on a notification will trigger the default action, e.g. telling Firefox to open
|
||||||
|
the tab that sent the notification. If you want access to a notification's non-default
|
||||||
|
actions then you need to disable the "actions" capability of the `Notifier` by passing
|
||||||
|
`actions=False`.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
from graphical_notifications import Notifier
|
||||||
|
|
||||||
|
notifier = Notifier()
|
||||||
|
|
||||||
|
keys.extend([
|
||||||
|
Key([mod], 'grave', lazy.function(notifier.prev)),
|
||||||
|
Key([mod, 'shift'], 'grave', lazy.function(notifier.next)),
|
||||||
|
Key(['control'], 'space', lazy.function(notifier.close)),
|
||||||
|
])
|
||||||
|
|
||||||
|
Qtile versions known to work: 0.17 - 0.18
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from libqtile import configurable, hook, images, pangocffi, qtile
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
from libqtile.log_utils import logger
|
||||||
|
from libqtile.notify import notifier, ClosedReason
|
||||||
|
from libqtile.popup import Popup
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from cairocffi import ImageSurface
|
||||||
|
|
||||||
|
from libqtile.core.manager import Qtile
|
||||||
|
|
||||||
|
try:
|
||||||
|
from libqtile.notify import Notification
|
||||||
|
except ImportError: # no dbus_next
|
||||||
|
Notification = Any # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class Notifier(configurable.Configurable):
|
||||||
|
"""
|
||||||
|
This class provides a full graphical notification manager for the
|
||||||
|
org.freedesktop.Notifications service implemented in libqtile.notify.
|
||||||
|
|
||||||
|
The format option determines what text is shown on the popup windows, and supports
|
||||||
|
markup and new line characters e.g. '<b>{summary}</b>\n{body}'. Available
|
||||||
|
placeholders are summary, body and app_name.
|
||||||
|
|
||||||
|
Foreground and background colours can be specified either as tuples/lists of 3
|
||||||
|
strings, corresponding to low, normal and critical urgencies, or just a single
|
||||||
|
string which will then be used for all urgencies. The timeout and border options can
|
||||||
|
be set in the same way.
|
||||||
|
|
||||||
|
The max_windows option limits how many popup windows can be drawn at a time. When
|
||||||
|
more notifications are recieved while the maximum number are already drawn,
|
||||||
|
notifications are queued and displayed when existing notifications are closed.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- text overflow
|
||||||
|
- select screen / follow mouse/keyboard focus
|
||||||
|
- critical notifications to replace any visible non-critical notifs immediately?
|
||||||
|
- hints: image-path, desktop-entry (for icon)
|
||||||
|
- hints: Notifier parameters set for single notification?
|
||||||
|
- hints: progress value e.g. int:value:42 with drawing
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
defaults = [
|
||||||
|
("x", 32, "x position on screen to start drawing notifications."),
|
||||||
|
("y", 64, "y position on screen to start drawing notifications."),
|
||||||
|
("width", 192, "Width of notifications."),
|
||||||
|
("height", 64, "Height of notifications."),
|
||||||
|
("format", "{summary}\n{body}", "Text format."),
|
||||||
|
(
|
||||||
|
"foreground",
|
||||||
|
("#ffffff", "#ffffff", "#ffffff"),
|
||||||
|
"Foreground colour of notifications, in ascending order of urgency.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"background",
|
||||||
|
("#111111", "#111111", "#111111"),
|
||||||
|
"Background colour of notifications, in ascending order of urgency.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"border",
|
||||||
|
("#111111", "#111111", "#111111"),
|
||||||
|
"Border colours in ascending order of urgency. Or None for none.",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"timeout",
|
||||||
|
(5000, 5000, 0),
|
||||||
|
"Millisecond timeout duration, in ascending order of urgency.",
|
||||||
|
),
|
||||||
|
("opacity", 1.0, "Opacity of notifications."),
|
||||||
|
("border_width", 4, "Line width of drawn borders."),
|
||||||
|
("corner_radius", None, "Corner radius for round corners, or None."),
|
||||||
|
("font", "sans", "Font used in notifications."),
|
||||||
|
("fontsize", 14, "Size of font."),
|
||||||
|
("fontshadow", None, "Color for text shadows, or None for no shadows."),
|
||||||
|
("text_alignment", "left", "Text alignment: left, center or right."),
|
||||||
|
("horizontal_padding", None, "Padding at sides of text."),
|
||||||
|
("vertical_padding", None, "Padding at top and bottom of text."),
|
||||||
|
("line_spacing", 4, "Space between lines."),
|
||||||
|
(
|
||||||
|
"overflow",
|
||||||
|
"truncate",
|
||||||
|
"How to deal with too much text: more_width, more_height, or truncate.",
|
||||||
|
),
|
||||||
|
("max_windows", 2, "Maximum number of windows to show at once."),
|
||||||
|
("gap", 12, "Vertical gap between popup windows."),
|
||||||
|
("sticky_history", True, "Disable timeout when browsing history."),
|
||||||
|
("icon_size", 36, "Pixel size of any icons."),
|
||||||
|
("fullscreen", "show", "What to do when in fullscreen: show, hide, or queue."),
|
||||||
|
("screen", "focus", "How to select a screen: focus, mouse, or an int."),
|
||||||
|
("actions", True, "Whether to enable the actions capability."),
|
||||||
|
]
|
||||||
|
capabilities = {"body", "body-markup", "actions"}
|
||||||
|
# specification: https://developer.gnome.org/notification-spec/
|
||||||
|
|
||||||
|
def __init__(self, **config) -> None:
|
||||||
|
configurable.Configurable.__init__(self, **config)
|
||||||
|
self.add_defaults(Notifier.defaults)
|
||||||
|
self._hidden: List[Popup] = []
|
||||||
|
self._shown: List[Popup] = []
|
||||||
|
self._queue: List[Notification] = []
|
||||||
|
self._positions: List[Tuple[int, int]] = []
|
||||||
|
self._scroll_popup: Optional[Popup] = None
|
||||||
|
self._current_id: int = 0
|
||||||
|
self._notif_id: Optional[int] = None
|
||||||
|
self._paused: bool = False
|
||||||
|
self._icons: Dict[str, Tuple[ImageSurface, int]] = {}
|
||||||
|
|
||||||
|
self._make_attr_list("foreground")
|
||||||
|
self._make_attr_list("background")
|
||||||
|
self._make_attr_list("timeout")
|
||||||
|
self._make_attr_list("border")
|
||||||
|
|
||||||
|
hook.subscribe.startup(lambda: asyncio.create_task(self._configure()))
|
||||||
|
|
||||||
|
if self.actions is False:
|
||||||
|
Notifier.capabilities.remove("actions")
|
||||||
|
|
||||||
|
def _make_attr_list(self, attr: str) -> None:
|
||||||
|
"""
|
||||||
|
Turns '#000000' into ('#000000', '#000000', '#000000')
|
||||||
|
"""
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if not isinstance(value, (tuple, list)):
|
||||||
|
setattr(self, attr, (value,) * 3)
|
||||||
|
|
||||||
|
async def _configure(self) -> None:
|
||||||
|
"""
|
||||||
|
This method needs to be called to set up the Notifier with the Qtile manager and
|
||||||
|
create the required popup windows.
|
||||||
|
"""
|
||||||
|
if self.horizontal_padding is None:
|
||||||
|
self.horizontal_padding = self.fontsize // 2
|
||||||
|
if self.vertical_padding is None:
|
||||||
|
self.vertical_padding = self.fontsize // 2
|
||||||
|
|
||||||
|
popup_config = {
|
||||||
|
"x": self.x,
|
||||||
|
"y": self.y,
|
||||||
|
"width": self.width,
|
||||||
|
"height": self.height,
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt in Popup.defaults:
|
||||||
|
key = opt[0]
|
||||||
|
if hasattr(self, key):
|
||||||
|
value = getattr(self, key)
|
||||||
|
if isinstance(value, (tuple, list)):
|
||||||
|
popup_config[key] = value[1]
|
||||||
|
else:
|
||||||
|
popup_config[key] = value
|
||||||
|
|
||||||
|
for win in range(self.max_windows):
|
||||||
|
popup = Popup(qtile, **popup_config)
|
||||||
|
popup.win.process_button_click = self._process_button_click(popup)
|
||||||
|
popup.notif = None
|
||||||
|
self._hidden.append(popup)
|
||||||
|
self._positions.append(
|
||||||
|
(
|
||||||
|
self.x,
|
||||||
|
self.y + win * (self.height + 2 * self.border_width + self.gap),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear defunct callbacks left when reloading the config
|
||||||
|
notifier.callbacks.clear()
|
||||||
|
notifier.close_callbacks.clear()
|
||||||
|
|
||||||
|
await notifier.register(
|
||||||
|
self._notify, Notifier.capabilities, on_close=self._on_close
|
||||||
|
)
|
||||||
|
logger.info("Notification server started up successfully")
|
||||||
|
|
||||||
|
def _process_button_click(self, popup: Popup) -> Callable:
|
||||||
|
def _(x: int, y: int, button: int) -> None:
|
||||||
|
if button == 1:
|
||||||
|
self._act(popup)
|
||||||
|
self._close(popup, reason=ClosedReason.dismissed)
|
||||||
|
if button == 3:
|
||||||
|
self._close(popup, reason=ClosedReason.dismissed)
|
||||||
|
|
||||||
|
return _
|
||||||
|
|
||||||
|
def _notify(self, notif: Notification) -> None:
|
||||||
|
"""
|
||||||
|
This method is registered with the NotificationManager to handle notifications
|
||||||
|
received via dbus. They will either be drawn now or queued to be drawn soon.
|
||||||
|
"""
|
||||||
|
if self._paused:
|
||||||
|
self._queue.append(notif)
|
||||||
|
return
|
||||||
|
|
||||||
|
if qtile.current_window and qtile.current_window.fullscreen:
|
||||||
|
if self.fullscreen != "show":
|
||||||
|
if self.fullscreen == "queue":
|
||||||
|
if self._unfullscreen not in hook.subscriptions:
|
||||||
|
hook.subscribe.float_change(self._unfullscreen)
|
||||||
|
self._queue.append(notif)
|
||||||
|
return
|
||||||
|
|
||||||
|
if notif.replaces_id:
|
||||||
|
for popup in self._shown:
|
||||||
|
if notif.replaces_id == popup.notif.replaces_id:
|
||||||
|
self._shown.remove(popup)
|
||||||
|
self._send(notif, popup)
|
||||||
|
self._reposition()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._hidden:
|
||||||
|
self._send(notif, self._hidden.pop())
|
||||||
|
else:
|
||||||
|
self._queue.append(notif)
|
||||||
|
|
||||||
|
def _on_close(self, nid: int) -> None:
|
||||||
|
for popup in self._shown:
|
||||||
|
self._close(popup, nid=nid, reason=ClosedReason.method)
|
||||||
|
|
||||||
|
def _unfullscreen(self) -> None:
|
||||||
|
"""
|
||||||
|
Begin displaying of queue notifications after leaving fullscreen.
|
||||||
|
"""
|
||||||
|
if not qtile.current_window.fullscreen:
|
||||||
|
hook.unsubscribe.float_change(self._unfullscreen)
|
||||||
|
self._renotify()
|
||||||
|
|
||||||
|
def _renotify(self) -> None:
|
||||||
|
"""
|
||||||
|
If we hold off temporarily on sending notifications and accumulate a queue, we
|
||||||
|
should use this to send the queue through self._notify again.
|
||||||
|
"""
|
||||||
|
queue = self._queue.copy()
|
||||||
|
self._queue.clear()
|
||||||
|
while queue:
|
||||||
|
self._notify(queue.pop(0))
|
||||||
|
|
||||||
|
def _send(
|
||||||
|
self, notif: Notification, popup: Popup, timeout: Optional[int] = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Draw the desired notification using the specified Popup instance.
|
||||||
|
"""
|
||||||
|
text = self._get_text(notif)
|
||||||
|
|
||||||
|
if "urgency" in notif.hints:
|
||||||
|
urgency = notif.hints["urgency"].value
|
||||||
|
else:
|
||||||
|
urgency = 1
|
||||||
|
|
||||||
|
self._current_id += 1
|
||||||
|
popup.id = self._current_id # Used for closing the popup
|
||||||
|
popup.notif = notif # Used for finding the visible popup's notif for actions
|
||||||
|
if popup not in self._shown:
|
||||||
|
self._shown.append(popup)
|
||||||
|
popup.x, popup.y = self._get_coordinates()
|
||||||
|
popup.place()
|
||||||
|
icon = self._load_icon(notif)
|
||||||
|
popup.unhide()
|
||||||
|
|
||||||
|
popup.background = self.background[urgency]
|
||||||
|
popup.layout.colour = self.foreground[urgency]
|
||||||
|
popup.clear()
|
||||||
|
|
||||||
|
if icon:
|
||||||
|
popup.draw_image(
|
||||||
|
icon[0],
|
||||||
|
self.horizontal_padding,
|
||||||
|
1 + (self.height - icon[1]) / 2,
|
||||||
|
)
|
||||||
|
popup.horizontal_padding += self.icon_size + self.horizontal_padding / 2
|
||||||
|
|
||||||
|
for num, line in enumerate(text.split("\n")):
|
||||||
|
popup.layout.text = line
|
||||||
|
y = self.vertical_padding + num * (popup.layout.height + self.line_spacing)
|
||||||
|
popup.draw_text(y=y)
|
||||||
|
if self.border_width:
|
||||||
|
popup.set_border(self.border[urgency])
|
||||||
|
popup.draw()
|
||||||
|
if icon:
|
||||||
|
popup.horizontal_padding = self.horizontal_padding
|
||||||
|
|
||||||
|
if timeout is None:
|
||||||
|
if notif.timeout is None or notif.timeout < 0:
|
||||||
|
timeout = self.timeout[urgency]
|
||||||
|
else:
|
||||||
|
timeout = notif.timeout
|
||||||
|
elif timeout < 0:
|
||||||
|
timeout = self.timeout[urgency]
|
||||||
|
if timeout > 0:
|
||||||
|
qtile.call_later(timeout / 1000, self._close, popup, self._current_id)
|
||||||
|
|
||||||
|
def _get_text(self, notif: Notification) -> str:
|
||||||
|
summary = ""
|
||||||
|
body = ""
|
||||||
|
app_name = ""
|
||||||
|
if notif.summary:
|
||||||
|
summary = pangocffi.markup_escape_text(notif.summary)
|
||||||
|
if notif.body:
|
||||||
|
body = pangocffi.markup_escape_text(notif.body)
|
||||||
|
if notif.app_name:
|
||||||
|
app_name = pangocffi.markup_escape_text(notif.app_name)
|
||||||
|
return self.format.format(summary=summary, body=body, app_name=app_name)
|
||||||
|
|
||||||
|
def _get_coordinates(self) -> Tuple[int, int]:
|
||||||
|
x, y = self._positions[len(self._shown) - 1]
|
||||||
|
if isinstance(self.screen, int):
|
||||||
|
screen = qtile.screens[self.screen]
|
||||||
|
elif self.screen == "focus":
|
||||||
|
screen = qtile.current_screen
|
||||||
|
elif self.screen == "mouse":
|
||||||
|
screen = qtile.find_screen(*qtile.mouse_position)
|
||||||
|
return x + screen.x, y + screen.y
|
||||||
|
|
||||||
|
def _close(self, popup: Popup, nid: Optional[int] = None, reason=1) -> None:
|
||||||
|
"""
|
||||||
|
Close the specified Popup instance.
|
||||||
|
"""
|
||||||
|
if popup in self._shown:
|
||||||
|
if nid is not None and popup.id != nid:
|
||||||
|
return
|
||||||
|
self._shown.remove(popup)
|
||||||
|
if self._scroll_popup is popup:
|
||||||
|
self._scroll_popup = None
|
||||||
|
self._notif_id = None
|
||||||
|
popup.hide()
|
||||||
|
if self._queue and not self._paused:
|
||||||
|
self._send(self._queue.pop(0), popup)
|
||||||
|
else:
|
||||||
|
self._hidden.append(popup)
|
||||||
|
notifier._service.NotificationClosed(popup.notif.id, reason)
|
||||||
|
self._reposition()
|
||||||
|
|
||||||
|
def _act(self, popup: Popup) -> None:
|
||||||
|
"""
|
||||||
|
Execute the actions specified by the notification visible on a clicked popup.
|
||||||
|
"""
|
||||||
|
# Currently this always invokes default action
|
||||||
|
# actions = {i: l for i, l in zip(notif.actions[:-1:2], notif.actions[1::2])}
|
||||||
|
if popup.notif.actions:
|
||||||
|
notifier._service.ActionInvoked(popup.notif.id, popup.notif.actions[0])
|
||||||
|
|
||||||
|
def _reposition(self) -> None:
|
||||||
|
for index, shown in enumerate(self._shown):
|
||||||
|
shown.x, shown.y = self._positions[index]
|
||||||
|
shown.place()
|
||||||
|
|
||||||
|
def _load_icon(self, notif: Notification) -> Optional[Tuple[ImageSurface, int]]:
|
||||||
|
if not notif.app_icon:
|
||||||
|
return None
|
||||||
|
if notif.app_icon in self._icons:
|
||||||
|
return self._icons.get(notif.app_icon)
|
||||||
|
try:
|
||||||
|
img = images.Img.from_path(notif.app_icon)
|
||||||
|
if img.width > img.height:
|
||||||
|
img.resize(width=self.icon_size)
|
||||||
|
else:
|
||||||
|
img.resize(height=self.icon_size)
|
||||||
|
surface, _ = images._decode_to_image_surface(
|
||||||
|
img.bytes_img, img.width, img.height
|
||||||
|
)
|
||||||
|
self._icons[notif.app_icon] = surface, surface.get_height()
|
||||||
|
except (FileNotFoundError, images.LoadingError, IsADirectoryError) as e:
|
||||||
|
logger.exception(e)
|
||||||
|
self._icons[notif.app_icon] = None
|
||||||
|
return self._icons[notif.app_icon]
|
||||||
|
|
||||||
|
def close(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Close the oldest of all visible popup windows.
|
||||||
|
"""
|
||||||
|
if self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
|
|
||||||
|
def close_all(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Close all popup windows.
|
||||||
|
"""
|
||||||
|
self._queue.clear()
|
||||||
|
while self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
|
|
||||||
|
def prev(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Display the previous notification in the history.
|
||||||
|
"""
|
||||||
|
if notifier.notifications:
|
||||||
|
if self._scroll_popup is None:
|
||||||
|
if self._hidden:
|
||||||
|
self._scroll_popup = self._hidden.pop(0)
|
||||||
|
else:
|
||||||
|
self._scroll_popup = self._shown[0]
|
||||||
|
self._notif_id = len(notifier.notifications)
|
||||||
|
if self._notif_id > 0:
|
||||||
|
self._notif_id -= 1
|
||||||
|
self._send(
|
||||||
|
notifier.notifications[self._notif_id],
|
||||||
|
self._scroll_popup,
|
||||||
|
0 if self.sticky_history else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def next(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Display the next notification in the history.
|
||||||
|
"""
|
||||||
|
if self._scroll_popup:
|
||||||
|
if self._notif_id < len(notifier.notifications) - 1:
|
||||||
|
self._notif_id += 1
|
||||||
|
if self._scroll_popup in self._shown:
|
||||||
|
self._shown.remove(self._scroll_popup)
|
||||||
|
self._send(
|
||||||
|
notifier.notifications[self._notif_id],
|
||||||
|
self._scroll_popup,
|
||||||
|
0 if self.sticky_history else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def pause(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Pause display of notifications on screen. Notifications will be queued and
|
||||||
|
presented as usual when this is called again.
|
||||||
|
"""
|
||||||
|
if self._paused:
|
||||||
|
self._paused = False
|
||||||
|
self._renotify()
|
||||||
|
else:
|
||||||
|
self._paused = True
|
||||||
|
while self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
456
plugins/notifications_copy.py
Normal file
456
plugins/notifications_copy.py
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
"""
|
||||||
|
Qtile plugin that acts as a notification server and draws notification windows.
|
||||||
|
|
||||||
|
Clicking on a notification will trigger the default action, e.g. telling Firefox to open
|
||||||
|
the tab that sent the notification. If you want access to a notification's non-default
|
||||||
|
actions then you need to disable the "actions" capability of the `Notifier` by passing
|
||||||
|
`actions=False`.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
from graphical_notifications import Notifier
|
||||||
|
|
||||||
|
notifier = Notifier()
|
||||||
|
|
||||||
|
keys.extend([
|
||||||
|
Key([mod], 'grave', lazy.function(notifier.prev)),
|
||||||
|
Key([mod, 'shift'], 'grave', lazy.function(notifier.next)),
|
||||||
|
Key(['control'], 'space', lazy.function(notifier.close)),
|
||||||
|
])
|
||||||
|
|
||||||
|
Qtile versions known to work: 0.17 - 0.18
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from libqtile import configurable, hook, images, pangocffi, qtile
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
from libqtile.log_utils import logger
|
||||||
|
from libqtile.notify import notifier, ClosedReason
|
||||||
|
from libqtile.popup import Popup
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from cairocffi import ImageSurface
|
||||||
|
|
||||||
|
from libqtile.core.manager import Qtile
|
||||||
|
try:
|
||||||
|
from libqtile.notify import Notification
|
||||||
|
except ImportError: # no dbus_next
|
||||||
|
Notification = Any # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class Notifier(configurable.Configurable):
|
||||||
|
"""
|
||||||
|
This class provides a full graphical notification manager for the
|
||||||
|
org.freedesktop.Notifications service implemented in libqtile.notify.
|
||||||
|
|
||||||
|
The format option determines what text is shown on the popup windows, and supports
|
||||||
|
markup and new line characters e.g. '<b>{summary}</b>\n{body}'. Available
|
||||||
|
placeholders are summary, body and app_name.
|
||||||
|
|
||||||
|
Foreground and background colours can be specified either as tuples/lists of 3
|
||||||
|
strings, corresponding to low, normal and critical urgencies, or just a single
|
||||||
|
string which will then be used for all urgencies. The timeout and border options can
|
||||||
|
be set in the same way.
|
||||||
|
|
||||||
|
The max_windows option limits how many popup windows can be drawn at a time. When
|
||||||
|
more notifications are recieved while the maximum number are already drawn,
|
||||||
|
notifications are queued and displayed when existing notifications are closed.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- text overflow
|
||||||
|
- select screen / follow mouse/keyboard focus
|
||||||
|
- critical notifications to replace any visible non-critical notifs immediately?
|
||||||
|
- hints: image-path, desktop-entry (for icon)
|
||||||
|
- hints: Notifier parameters set for single notification?
|
||||||
|
- hints: progress value e.g. int:value:42 with drawing
|
||||||
|
|
||||||
|
"""
|
||||||
|
defaults = [
|
||||||
|
('x', 32, 'x position on screen to start drawing notifications.'),
|
||||||
|
('y', 64, 'y position on screen to start drawing notifications.'),
|
||||||
|
('width', 192, 'Width of notifications.'),
|
||||||
|
('height', 64, 'Height of notifications.'),
|
||||||
|
('format', '{summary}\n{body}', 'Text format.'),
|
||||||
|
(
|
||||||
|
'foreground',
|
||||||
|
('#ffffff', '#ffffff', '#ffffff'),
|
||||||
|
'Foreground colour of notifications, in ascending order of urgency.',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'background',
|
||||||
|
('#111111', '#111111', '#111111'),
|
||||||
|
'Background colour of notifications, in ascending order of urgency.',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'border',
|
||||||
|
('#111111', '#111111', '#111111'),
|
||||||
|
'Border colours in ascending order of urgency. Or None for none.',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'timeout',
|
||||||
|
(5000, 5000, 0),
|
||||||
|
'Millisecond timeout duration, in ascending order of urgency.',
|
||||||
|
),
|
||||||
|
('opacity', 1.0, 'Opacity of notifications.'),
|
||||||
|
('border_width', 4, 'Line width of drawn borders.'),
|
||||||
|
('corner_radius', None, 'Corner radius for round corners, or None.'),
|
||||||
|
('font', 'sans', 'Font used in notifications.'),
|
||||||
|
('font_size', 14, 'Size of font.'),
|
||||||
|
('fontshadow', None, 'Color for text shadows, or None for no shadows.'),
|
||||||
|
('text_alignment', 'left', 'Text alignment: left, center or right.'),
|
||||||
|
('horizontal_padding', None, 'Padding at sides of text.'),
|
||||||
|
('vertical_padding', None, 'Padding at top and bottom of text.'),
|
||||||
|
('line_spacing', 4, 'Space between lines.'),
|
||||||
|
(
|
||||||
|
'overflow',
|
||||||
|
'truncate',
|
||||||
|
'How to deal with too much text: more_width, more_height, or truncate.',
|
||||||
|
),
|
||||||
|
('max_windows', 2, 'Maximum number of windows to show at once.'),
|
||||||
|
('gap', 12, 'Vertical gap between popup windows.'),
|
||||||
|
('sticky_history', True, 'Disable timeout when browsing history.'),
|
||||||
|
('icon_size', 36, 'Pixel size of any icons.'),
|
||||||
|
('fullscreen', 'show', 'What to do when in fullscreen: show, hide, or queue.'),
|
||||||
|
('screen', 'focus', 'How to select a screen: focus, mouse, or an int.'),
|
||||||
|
('actions', True, 'Whether to enable the actions capability.'),
|
||||||
|
]
|
||||||
|
capabilities = {'body', 'body-markup', 'actions'}
|
||||||
|
# specification: https://developer.gnome.org/notification-spec/
|
||||||
|
|
||||||
|
def __init__(self, **config) -> None:
|
||||||
|
configurable.Configurable.__init__(self, **config)
|
||||||
|
self.add_defaults(Notifier.defaults)
|
||||||
|
self._hidden: List[Popup] = []
|
||||||
|
self._shown: List[Popup] = []
|
||||||
|
self._queue: List[Notification] = []
|
||||||
|
self._positions: List[Tuple[int, int]] = []
|
||||||
|
self._scroll_popup: Optional[Popup] = None
|
||||||
|
self._current_id: int = 0
|
||||||
|
self._notif_id: Optional[int] = None
|
||||||
|
self._paused: bool = False
|
||||||
|
self._icons: Dict[str, Tuple[ImageSurface, int]] = {}
|
||||||
|
|
||||||
|
self._make_attr_list('foreground')
|
||||||
|
self._make_attr_list('background')
|
||||||
|
self._make_attr_list('timeout')
|
||||||
|
self._make_attr_list('border')
|
||||||
|
|
||||||
|
hook.subscribe.startup(lambda: asyncio.create_task(self._configure()))
|
||||||
|
|
||||||
|
if self.actions is False:
|
||||||
|
Notifier.capabilities.remove("actions")
|
||||||
|
|
||||||
|
def _make_attr_list(self, attr: str) -> None:
|
||||||
|
"""
|
||||||
|
Turns '#000000' into ('#000000', '#000000', '#000000')
|
||||||
|
"""
|
||||||
|
value = getattr(self, attr)
|
||||||
|
if not isinstance(value, (tuple, list)):
|
||||||
|
setattr(self, attr, (value,) * 3)
|
||||||
|
|
||||||
|
async def _configure(self) -> None:
|
||||||
|
"""
|
||||||
|
This method needs to be called to set up the Notifier with the Qtile manager and
|
||||||
|
create the required popup windows.
|
||||||
|
"""
|
||||||
|
if self.horizontal_padding is None:
|
||||||
|
self.horizontal_padding = self.font_size // 2
|
||||||
|
if self.vertical_padding is None:
|
||||||
|
self.vertical_padding = self.font_size // 2
|
||||||
|
|
||||||
|
popup_config = {
|
||||||
|
"x": self.x,
|
||||||
|
"y": self.y,
|
||||||
|
"width": self.width,
|
||||||
|
"height": self.height,
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt in Popup.defaults:
|
||||||
|
key = opt[0]
|
||||||
|
if hasattr(self, key):
|
||||||
|
value = getattr(self, key)
|
||||||
|
if isinstance(value, (tuple, list)):
|
||||||
|
popup_config[key] = value[1]
|
||||||
|
else:
|
||||||
|
popup_config[key] = value
|
||||||
|
|
||||||
|
for win in range(self.max_windows):
|
||||||
|
popup = Popup(qtile, **popup_config)
|
||||||
|
popup.win.process_button_click = self._process_button_click(popup)
|
||||||
|
popup.notif = None
|
||||||
|
self._hidden.append(popup)
|
||||||
|
self._positions.append(
|
||||||
|
(
|
||||||
|
self.x,
|
||||||
|
self.y + win * (self.height + 2 * self.border_width + self.gap)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear defunct callbacks left when reloading the config
|
||||||
|
notifier.callbacks.clear()
|
||||||
|
notifier.close_callbacks.clear()
|
||||||
|
|
||||||
|
await notifier.register(
|
||||||
|
self._notify, Notifier.capabilities, on_close=self._on_close
|
||||||
|
)
|
||||||
|
logger.info("Notification server started up successfully")
|
||||||
|
|
||||||
|
def _process_button_click(self, popup: Popup) -> Callable:
|
||||||
|
def _(x: int, y: int, button: int) -> None:
|
||||||
|
if button == 1:
|
||||||
|
self._act(popup)
|
||||||
|
self._close(popup, reason=ClosedReason.dismissed)
|
||||||
|
if button == 3:
|
||||||
|
self._close(popup, reason=ClosedReason.dismissed)
|
||||||
|
return _
|
||||||
|
|
||||||
|
def _notify(self, notif: Notification) -> None:
|
||||||
|
"""
|
||||||
|
This method is registered with the NotificationManager to handle notifications
|
||||||
|
received via dbus. They will either be drawn now or queued to be drawn soon.
|
||||||
|
"""
|
||||||
|
if self._paused:
|
||||||
|
self._queue.append(notif)
|
||||||
|
return
|
||||||
|
|
||||||
|
if qtile.current_window and qtile.current_window.fullscreen:
|
||||||
|
if self.fullscreen != 'show':
|
||||||
|
if self.fullscreen == 'queue':
|
||||||
|
if self._unfullscreen not in hook.subscriptions:
|
||||||
|
hook.subscribe.float_change(self._unfullscreen)
|
||||||
|
self._queue.append(notif)
|
||||||
|
return
|
||||||
|
|
||||||
|
if notif.replaces_id:
|
||||||
|
for popup in self._shown:
|
||||||
|
if notif.replaces_id == popup.notif.replaces_id:
|
||||||
|
self._shown.remove(popup)
|
||||||
|
self._send(notif, popup)
|
||||||
|
self._reposition()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._hidden:
|
||||||
|
self._send(notif, self._hidden.pop())
|
||||||
|
else:
|
||||||
|
self._queue.append(notif)
|
||||||
|
|
||||||
|
def _on_close(self, nid: int) -> None:
|
||||||
|
for popup in self._shown:
|
||||||
|
self._close(popup, nid=nid, reason=ClosedReason.method)
|
||||||
|
|
||||||
|
def _unfullscreen(self) -> None:
|
||||||
|
"""
|
||||||
|
Begin displaying of queue notifications after leaving fullscreen.
|
||||||
|
"""
|
||||||
|
if not qtile.current_window.fullscreen:
|
||||||
|
hook.unsubscribe.float_change(self._unfullscreen)
|
||||||
|
self._renotify()
|
||||||
|
|
||||||
|
def _renotify(self) -> None:
|
||||||
|
"""
|
||||||
|
If we hold off temporarily on sending notifications and accumulate a queue, we
|
||||||
|
should use this to send the queue through self._notify again.
|
||||||
|
"""
|
||||||
|
queue = self._queue.copy()
|
||||||
|
self._queue.clear()
|
||||||
|
while queue:
|
||||||
|
self._notify(queue.pop(0))
|
||||||
|
|
||||||
|
def _send(
|
||||||
|
self, notif: Notification, popup: Popup, timeout: Optional[int] = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Draw the desired notification using the specified Popup instance.
|
||||||
|
"""
|
||||||
|
text = self._get_text(notif)
|
||||||
|
|
||||||
|
if "urgency" in notif.hints:
|
||||||
|
urgency = notif.hints["urgency"].value
|
||||||
|
else:
|
||||||
|
urgency = 1
|
||||||
|
|
||||||
|
self._current_id += 1
|
||||||
|
popup.id = self._current_id # Used for closing the popup
|
||||||
|
popup.notif = notif # Used for finding the visible popup's notif for actions
|
||||||
|
if popup not in self._shown:
|
||||||
|
self._shown.append(popup)
|
||||||
|
popup.x, popup.y = self._get_coordinates()
|
||||||
|
popup.place()
|
||||||
|
icon = self._load_icon(notif)
|
||||||
|
popup.unhide()
|
||||||
|
|
||||||
|
popup.background = self.background[urgency]
|
||||||
|
popup.foreground = self.foreground[urgency]
|
||||||
|
popup.clear()
|
||||||
|
|
||||||
|
if icon:
|
||||||
|
popup.draw_image(
|
||||||
|
icon[0],
|
||||||
|
self.horizontal_padding,
|
||||||
|
1 + (self.height - icon[1]) / 2,
|
||||||
|
)
|
||||||
|
popup.horizontal_padding += self.icon_size + self.horizontal_padding / 2
|
||||||
|
|
||||||
|
for num, line in enumerate(text.split('\n')):
|
||||||
|
popup.text = line
|
||||||
|
y = self.vertical_padding + num * (popup.layout.height + self.line_spacing)
|
||||||
|
popup.draw_text(y=y)
|
||||||
|
if self.border_width:
|
||||||
|
popup.set_border(self.border[urgency])
|
||||||
|
popup.draw()
|
||||||
|
popup.win.bring_to_front() # Patch to bring the notification on top of all windows
|
||||||
|
if icon:
|
||||||
|
popup.horizontal_padding = self.horizontal_padding
|
||||||
|
|
||||||
|
if timeout is None:
|
||||||
|
if notif.timeout is None or notif.timeout < 0:
|
||||||
|
timeout = self.timeout[urgency]
|
||||||
|
else:
|
||||||
|
timeout = notif.timeout
|
||||||
|
elif timeout < 0:
|
||||||
|
timeout = self.timeout[urgency]
|
||||||
|
if timeout > 0:
|
||||||
|
qtile.call_later(timeout / 1000, self._close, popup, self._current_id)
|
||||||
|
|
||||||
|
def _get_text(self, notif: Notification) -> str:
|
||||||
|
summary = ''
|
||||||
|
body = ''
|
||||||
|
app_name = ''
|
||||||
|
if notif.summary:
|
||||||
|
summary = pangocffi.markup_escape_text(notif.summary)
|
||||||
|
if notif.body:
|
||||||
|
body = pangocffi.markup_escape_text(notif.body)
|
||||||
|
if notif.app_name:
|
||||||
|
app_name = pangocffi.markup_escape_text(notif.app_name)
|
||||||
|
return self.format.format(summary=summary, body=body, app_name=app_name)
|
||||||
|
|
||||||
|
def _get_coordinates(self) -> Tuple[int, int]:
|
||||||
|
x, y = self._positions[len(self._shown) - 1]
|
||||||
|
if isinstance(self.screen, int):
|
||||||
|
screen = qtile.screens[self.screen]
|
||||||
|
elif self.screen == 'focus':
|
||||||
|
screen = qtile.current_screen
|
||||||
|
elif self.screen == 'mouse':
|
||||||
|
screen = qtile.find_screen(*qtile.mouse_position)
|
||||||
|
return x + screen.x, y + screen.y
|
||||||
|
|
||||||
|
def _close(self, popup: Popup, nid: Optional[int] = None, reason=1) -> None:
|
||||||
|
"""
|
||||||
|
Close the specified Popup instance.
|
||||||
|
"""
|
||||||
|
if popup in self._shown:
|
||||||
|
if nid is not None and popup.id != nid:
|
||||||
|
return
|
||||||
|
self._shown.remove(popup)
|
||||||
|
if self._scroll_popup is popup:
|
||||||
|
self._scroll_popup = None
|
||||||
|
self._notif_id = None
|
||||||
|
popup.hide()
|
||||||
|
if self._queue and not self._paused:
|
||||||
|
self._send(self._queue.pop(0), popup)
|
||||||
|
else:
|
||||||
|
self._hidden.append(popup)
|
||||||
|
notifier._service.NotificationClosed(popup.notif.id, reason)
|
||||||
|
self._reposition()
|
||||||
|
|
||||||
|
def _act(self, popup: Popup) -> None:
|
||||||
|
"""
|
||||||
|
Execute the actions specified by the notification visible on a clicked popup.
|
||||||
|
"""
|
||||||
|
# Currently this always invokes default action
|
||||||
|
#actions = {i: l for i, l in zip(notif.actions[:-1:2], notif.actions[1::2])}
|
||||||
|
if popup.notif.actions:
|
||||||
|
notifier._service.ActionInvoked(popup.notif.id, popup.notif.actions[0])
|
||||||
|
|
||||||
|
def _reposition(self) -> None:
|
||||||
|
for index, shown in enumerate(self._shown):
|
||||||
|
shown.x, shown.y = self._positions[index]
|
||||||
|
shown.place()
|
||||||
|
|
||||||
|
def _load_icon(self, notif: Notification) -> Optional[Tuple[ImageSurface, int]]:
|
||||||
|
if not notif.app_icon:
|
||||||
|
return None
|
||||||
|
if notif.app_icon in self._icons:
|
||||||
|
return self._icons.get(notif.app_icon)
|
||||||
|
try:
|
||||||
|
img = images.Img.from_path(notif.app_icon)
|
||||||
|
if img.width > img.height:
|
||||||
|
img.resize(width=self.icon_size)
|
||||||
|
else:
|
||||||
|
img.resize(height=self.icon_size)
|
||||||
|
surface, _ = images._decode_to_image_surface(
|
||||||
|
img.bytes_img, img.width, img.height
|
||||||
|
)
|
||||||
|
self._icons[notif.app_icon] = surface, surface.get_height()
|
||||||
|
except (FileNotFoundError, images.LoadingError, IsADirectoryError) as e:
|
||||||
|
logger.exception(e)
|
||||||
|
self._icons[notif.app_icon] = None
|
||||||
|
return self._icons[notif.app_icon]
|
||||||
|
|
||||||
|
def close(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Close the oldest of all visible popup windows.
|
||||||
|
"""
|
||||||
|
if self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
|
|
||||||
|
def close_all(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Close all popup windows.
|
||||||
|
"""
|
||||||
|
self._queue.clear()
|
||||||
|
while self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
|
|
||||||
|
def prev(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Display the previous notification in the history.
|
||||||
|
"""
|
||||||
|
if notifier.notifications:
|
||||||
|
if self._scroll_popup is None:
|
||||||
|
if self._hidden:
|
||||||
|
self._scroll_popup = self._hidden.pop(0)
|
||||||
|
else:
|
||||||
|
self._scroll_popup = self._shown[0]
|
||||||
|
self._notif_id = len(notifier.notifications)
|
||||||
|
if self._notif_id > 0:
|
||||||
|
self._notif_id -= 1
|
||||||
|
self._send(
|
||||||
|
notifier.notifications[self._notif_id],
|
||||||
|
self._scroll_popup,
|
||||||
|
0 if self.sticky_history else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def next(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Display the next notification in the history.
|
||||||
|
"""
|
||||||
|
if self._scroll_popup:
|
||||||
|
if self._notif_id < len(notifier.notifications) - 1:
|
||||||
|
self._notif_id += 1
|
||||||
|
if self._scroll_popup in self._shown:
|
||||||
|
self._shown.remove(self._scroll_popup)
|
||||||
|
self._send(
|
||||||
|
notifier.notifications[self._notif_id],
|
||||||
|
self._scroll_popup,
|
||||||
|
0 if self.sticky_history else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def pause(self, _qtile=None) -> None:
|
||||||
|
"""
|
||||||
|
Pause display of notifications on screen. Notifications will be queued and
|
||||||
|
presented as usual when this is called again.
|
||||||
|
"""
|
||||||
|
if self._paused:
|
||||||
|
self._paused = False
|
||||||
|
self._renotify()
|
||||||
|
else:
|
||||||
|
self._paused = True
|
||||||
|
while self._shown:
|
||||||
|
self._close(self._shown[0])
|
||||||
0
popups/__init__.py
Normal file
0
popups/__init__.py
Normal file
BIN
popups/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
popups/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
popups/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/brightness_notification.cpython-313.pyc
Normal file
BIN
popups/__pycache__/brightness_notification.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/calendar.cpython-313.pyc
Normal file
BIN
popups/__pycache__/calendar.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/calendar.cpython-314.pyc
Normal file
BIN
popups/__pycache__/calendar.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/monitor.cpython-313.pyc
Normal file
BIN
popups/__pycache__/monitor.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/monitor.cpython-314.pyc
Normal file
BIN
popups/__pycache__/monitor.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/mpris2_layout.cpython-313.pyc
Normal file
BIN
popups/__pycache__/mpris2_layout.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/mpris2_layout.cpython-314.pyc
Normal file
BIN
popups/__pycache__/mpris2_layout.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/network.cpython-313.pyc
Normal file
BIN
popups/__pycache__/network.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/network.cpython-314.pyc
Normal file
BIN
popups/__pycache__/network.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/powermenu.cpython-313.pyc
Normal file
BIN
popups/__pycache__/powermenu.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/powermenu.cpython-314.pyc
Normal file
BIN
popups/__pycache__/powermenu.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/powermenu_sub.cpython-313.pyc
Normal file
BIN
popups/__pycache__/powermenu_sub.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/powermenu_sub.cpython-314.pyc
Normal file
BIN
popups/__pycache__/powermenu_sub.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/settings.cpython-313.pyc
Normal file
BIN
popups/__pycache__/settings.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/settings.cpython-314.pyc
Normal file
BIN
popups/__pycache__/settings.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/start_menu.cpython-313.pyc
Normal file
BIN
popups/__pycache__/start_menu.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/start_menu.cpython-314.pyc
Normal file
BIN
popups/__pycache__/start_menu.cpython-314.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/volume_notification.cpython-313.pyc
Normal file
BIN
popups/__pycache__/volume_notification.cpython-313.pyc
Normal file
Binary file not shown.
BIN
popups/__pycache__/volume_notification.cpython-314.pyc
Normal file
BIN
popups/__pycache__/volume_notification.cpython-314.pyc
Normal file
Binary file not shown.
135
popups/calendar.py
Normal file
135
popups/calendar.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
from libqtile import qtile
|
||||||
|
|
||||||
|
from qtile_extras import widget
|
||||||
|
from qtile_extras.popup.toolkit import (PopupRelativeLayout,
|
||||||
|
PopupWidget,
|
||||||
|
)
|
||||||
|
|
||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
|
||||||
|
# https://discord.com/channels/955163559086665728/1166312212223250482/1322614846155657370
|
||||||
|
# check for this PR to change back the code with the message contents to the prev code of the widget:
|
||||||
|
# PopupWidget(
|
||||||
|
# pos_x=0.051,
|
||||||
|
# pos_y=0.415,
|
||||||
|
# height=0.6,
|
||||||
|
# width=0.9,
|
||||||
|
# widget=widget.GenPollCommand(
|
||||||
|
# cmd="cal",
|
||||||
|
# shell=True,
|
||||||
|
# font='mono',
|
||||||
|
# fontsize=20,
|
||||||
|
# markup=False,
|
||||||
|
# # background=gruvbox_dark["blue"],
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
def parse_cal():
|
||||||
|
process = subprocess.run(
|
||||||
|
"cal",
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
body = process.stdout.strip()
|
||||||
|
lines = body.splitlines()
|
||||||
|
maxlen = max(len(l) for l in lines)
|
||||||
|
output = []
|
||||||
|
for line in body.splitlines():
|
||||||
|
if len(line) < maxlen:
|
||||||
|
line += " " * (maxlen - len(line))
|
||||||
|
output.append(line)
|
||||||
|
return "\n".join(output).strip("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def calendar(qtile):
|
||||||
|
layout = PopupRelativeLayout(
|
||||||
|
qtile,
|
||||||
|
rows=7,
|
||||||
|
cols=9,
|
||||||
|
width=300,
|
||||||
|
height=310,
|
||||||
|
opacity=0.8,
|
||||||
|
hide_on_mouse_leave=True,
|
||||||
|
close_on_click=False,
|
||||||
|
border_width=0,
|
||||||
|
background=gruvbox_dark["bg0_soft"],
|
||||||
|
controls=[
|
||||||
|
PopupWidget(
|
||||||
|
pos_x=0,
|
||||||
|
pos_y=0,
|
||||||
|
height=0.2,
|
||||||
|
width=0.9,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
widget=widget.Wttr(
|
||||||
|
fontsize=40,
|
||||||
|
format='%c'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PopupWidget(
|
||||||
|
pos_x=0.3,
|
||||||
|
pos_y=0.05,
|
||||||
|
height=0.05,
|
||||||
|
width=0.9,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
widget=widget.Wttr(
|
||||||
|
font='Open Sans Bold',
|
||||||
|
fontsize=18,
|
||||||
|
format='Actual: %t'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PopupWidget(
|
||||||
|
pos_x=0.3,
|
||||||
|
pos_y=0.12,
|
||||||
|
height=0.05,
|
||||||
|
width=0.9,
|
||||||
|
widget=widget.Wttr(
|
||||||
|
font='Open Sans',
|
||||||
|
fontsize=14,
|
||||||
|
format='Feels: %f'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PopupWidget(
|
||||||
|
pos_x=0.05,
|
||||||
|
pos_y=0.2,
|
||||||
|
height=0.05,
|
||||||
|
width=0.9,
|
||||||
|
widget=widget.Wttr(
|
||||||
|
font='Open Sans',
|
||||||
|
fontsize=14,
|
||||||
|
format='Wind: %w Prec: %p'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PopupWidget(
|
||||||
|
pos_x=0.05,
|
||||||
|
pos_y=0.25,
|
||||||
|
height=0.11,
|
||||||
|
width=0.9,
|
||||||
|
widget=widget.Wttr(
|
||||||
|
font='Open Sans Bold',
|
||||||
|
fontsize=14,
|
||||||
|
format='City: %l', # \nFeel;%f Wind: %w'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PopupWidget(
|
||||||
|
pos_x=0.051,
|
||||||
|
pos_y=0.38,
|
||||||
|
height=0.6,
|
||||||
|
width=0.9,
|
||||||
|
widget=widget.GenPollText(
|
||||||
|
func=parse_cal,
|
||||||
|
font='mono',
|
||||||
|
fontsize=20,
|
||||||
|
markup=False,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
layout.show(relative_to=3,
|
||||||
|
relative_to_bar=True,
|
||||||
|
y=3,
|
||||||
|
x=-3,
|
||||||
|
)
|
||||||
31
popups/monitor.py
Normal file
31
popups/monitor.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from libqtile import qtile
|
||||||
|
|
||||||
|
from qtile_extras import widget
|
||||||
|
from qtile_extras.popup.toolkit import (
|
||||||
|
PopupRelativeLayout,
|
||||||
|
PopupWidget,
|
||||||
|
)
|
||||||
|
|
||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
|
||||||
|
|
||||||
|
def monitor(qtile):
|
||||||
|
layout = PopupRelativeLayout(
|
||||||
|
qtile,
|
||||||
|
rows=7,
|
||||||
|
cols=9,
|
||||||
|
width=600,
|
||||||
|
height=420,
|
||||||
|
opacity=0.8,
|
||||||
|
hide_on_mouse_leave=True,
|
||||||
|
close_on_click=False,
|
||||||
|
border_width=0,
|
||||||
|
background=gruvbox_dark["bg0_soft"],
|
||||||
|
controls=[],
|
||||||
|
)
|
||||||
|
layout.show(
|
||||||
|
relative_to=5,
|
||||||
|
relative_to_bar=True,
|
||||||
|
# y=3,
|
||||||
|
# x=-3,
|
||||||
|
)
|
||||||
125
popups/mpris2_layout.py
Normal file
125
popups/mpris2_layout.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
from qtile_extras.popup.toolkit import (
|
||||||
|
PopupRelativeLayout,
|
||||||
|
PopupImage,
|
||||||
|
PopupText,
|
||||||
|
PopupSlider
|
||||||
|
)
|
||||||
|
image='/home/cerberus/.config/qtile/res/images/no_cover.svg'
|
||||||
|
|
||||||
|
MPRIS2_LAYOUT = PopupRelativeLayout(
|
||||||
|
None,
|
||||||
|
width=400,
|
||||||
|
height=200,
|
||||||
|
opacity=0.7,
|
||||||
|
background=gruvbox_dark["bg0_soft"],
|
||||||
|
hide_on_mouse_leave=True,
|
||||||
|
controls=[
|
||||||
|
PopupText(
|
||||||
|
"",
|
||||||
|
name="title",
|
||||||
|
font='Open Sans Bold',
|
||||||
|
fontsize=18,
|
||||||
|
pos_x=0.35,
|
||||||
|
pos_y=0.1,
|
||||||
|
width=0.55,
|
||||||
|
height=0.14,
|
||||||
|
h_align="left",
|
||||||
|
v_align="top",
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
"",
|
||||||
|
name="artist",
|
||||||
|
font='Open Sans Medium',
|
||||||
|
fontsize=14,
|
||||||
|
pos_x=0.35,
|
||||||
|
pos_y=0.24,
|
||||||
|
width=0.55,
|
||||||
|
height=0.14,
|
||||||
|
h_align="left",
|
||||||
|
v_align="middle",
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
"",
|
||||||
|
name="album",
|
||||||
|
font='Open Sans',
|
||||||
|
fontsize=14,
|
||||||
|
pos_x=0.35,
|
||||||
|
pos_y=0.38,
|
||||||
|
width=0.55,
|
||||||
|
height=0.14,
|
||||||
|
h_align="left",
|
||||||
|
v_align="bottom",
|
||||||
|
),
|
||||||
|
PopupImage(
|
||||||
|
name="artwork",
|
||||||
|
filename=image,
|
||||||
|
pos_x=0.1,
|
||||||
|
pos_y=0.1,
|
||||||
|
width=0.21,
|
||||||
|
height=0.42,
|
||||||
|
),
|
||||||
|
PopupSlider(name="progress", pos_x=0.1, pos_y=0.6, width=0.8, height=0.1, marker_size=0),
|
||||||
|
PopupText(
|
||||||
|
name="previous",
|
||||||
|
text='',
|
||||||
|
fontsize=30,
|
||||||
|
mask=True,
|
||||||
|
pos_x=0.125,
|
||||||
|
pos_y=0.8,
|
||||||
|
width=0.15,
|
||||||
|
height=0.1,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
name="play_pause",
|
||||||
|
text='',
|
||||||
|
fontsize=30,
|
||||||
|
mask=True,
|
||||||
|
pos_x=0.325,
|
||||||
|
pos_y=0.8,
|
||||||
|
width=0.15,
|
||||||
|
height=0.1,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
name="stop",
|
||||||
|
text='',
|
||||||
|
fontsize=30,
|
||||||
|
mask=True,
|
||||||
|
pos_x=0.525,
|
||||||
|
pos_y=0.8,
|
||||||
|
width=0.15,
|
||||||
|
height=0.1,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
name="next",
|
||||||
|
text='',
|
||||||
|
fontsize=30,
|
||||||
|
mask=True,
|
||||||
|
pos_x=0.725,
|
||||||
|
pos_y=0.8,
|
||||||
|
width=0.15,
|
||||||
|
height=0.1,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
close_on_click=False,
|
||||||
|
)
|
||||||
48
popups/network.py
Normal file
48
popups/network.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from libqtile import qtile
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
|
||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
|
||||||
|
from qtile_extras.popup.menu import (
|
||||||
|
PopupMenu,
|
||||||
|
PopupMenuItem,
|
||||||
|
PopupMenuSeparator,
|
||||||
|
)
|
||||||
|
|
||||||
|
items=[
|
||||||
|
PopupMenuItem(
|
||||||
|
show_icon=False,
|
||||||
|
text=' Network Manager',
|
||||||
|
font='Open Sans',
|
||||||
|
fontsize=16,
|
||||||
|
can_focus=True,
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("nm-connection-editor")},
|
||||||
|
),
|
||||||
|
PopupMenuSeparator(),
|
||||||
|
PopupMenuItem(
|
||||||
|
show_icon=False,
|
||||||
|
text=' Wireguard',
|
||||||
|
font='Open Sans',
|
||||||
|
fontsize=16,
|
||||||
|
highlight_method='text',
|
||||||
|
can_focus=True,
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("wireguird")},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def network_menu(qtile):
|
||||||
|
layout = PopupMenu.generate(
|
||||||
|
qtile,
|
||||||
|
pos_x=100,
|
||||||
|
pos_y=100,
|
||||||
|
width=225,
|
||||||
|
opacity=0.7,
|
||||||
|
menuitems=items,
|
||||||
|
background=gruvbox_dark["bg0_soft"]
|
||||||
|
)
|
||||||
|
layout.show(relative_to=1, relative_to_bar=True, y=136, x=220)
|
||||||
70
popups/powermenu.py
Normal file
70
popups/powermenu.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from libqtile.lazy import lazy
|
||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
from qtile_extras.popup.toolkit import (
|
||||||
|
PopupRelativeLayout,
|
||||||
|
PopupText,
|
||||||
|
)
|
||||||
|
# qtile/resources/themes/colors.py
|
||||||
|
def power_menu(qtile):
|
||||||
|
layout = PopupRelativeLayout(
|
||||||
|
qtile,
|
||||||
|
width=800,
|
||||||
|
height=250,
|
||||||
|
opacity=0.7,
|
||||||
|
# border=gruvbox_dark["red"],
|
||||||
|
# border_width=3,
|
||||||
|
background=gruvbox_dark["bg0_soft"],
|
||||||
|
initial_focus=None,
|
||||||
|
controls=[
|
||||||
|
|
||||||
|
PopupText(
|
||||||
|
# Lock betterlockscreen --lock blur
|
||||||
|
text="",
|
||||||
|
fontsize=80,
|
||||||
|
pos_y=0,
|
||||||
|
pos_x=0.1,
|
||||||
|
width=0.2,
|
||||||
|
height=1,
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("betterlockscreen --lock blur")},
|
||||||
|
highlight_method='text',
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Hybrid Sleep systemctl hybrid-sleep
|
||||||
|
text="",
|
||||||
|
fontsize=80,
|
||||||
|
pos_y=0,
|
||||||
|
pos_x=0.32,
|
||||||
|
width=0.2,
|
||||||
|
height=1,
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("systemctl hybrid-sleep")},
|
||||||
|
highlight_method='text',
|
||||||
|
highlight=gruvbox_dark["yellow"],
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Hibernate systemctl hibernate
|
||||||
|
text="",
|
||||||
|
fontsize=80,
|
||||||
|
pos_y=0,
|
||||||
|
pos_x=0.55,
|
||||||
|
width=0.2,
|
||||||
|
height=1,
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("systemctl hibernate")},
|
||||||
|
highlight_method='text',
|
||||||
|
highlight=gruvbox_dark["orange"],
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Power off systemctl poweroff
|
||||||
|
text="",
|
||||||
|
fontsize=80,
|
||||||
|
pos_y=0,
|
||||||
|
pos_x=0.8,
|
||||||
|
width=0.2,
|
||||||
|
height=1,
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("systemctl poweroff")},
|
||||||
|
highlight_method='text',
|
||||||
|
highlight=gruvbox_dark["red"],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
layout.show(relative_to=5, relative_to_bar=True, hide_on_timeout=5)
|
||||||
90
popups/powermenu_sub.py
Normal file
90
popups/powermenu_sub.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
from pydoc import importfile
|
||||||
|
from libqtile import qtile
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
|
||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
|
||||||
|
from qtile_extras.popup.toolkit import (
|
||||||
|
PopupRelativeLayout,
|
||||||
|
PopupImage,
|
||||||
|
PopupText,
|
||||||
|
)
|
||||||
|
|
||||||
|
def powermenu_2(qtile):
|
||||||
|
layout = PopupRelativeLayout(
|
||||||
|
qtile,
|
||||||
|
width=170,
|
||||||
|
height=50,
|
||||||
|
opacity=0.7,
|
||||||
|
hide_on_mouse_leave=True,
|
||||||
|
close_on_click=False,
|
||||||
|
border_width=0,
|
||||||
|
background=gruvbox_dark["bg0_soft"],
|
||||||
|
controls=[
|
||||||
|
PopupText(
|
||||||
|
# Lock
|
||||||
|
text='',
|
||||||
|
fontsize=22,
|
||||||
|
pos_x=0.07,
|
||||||
|
pos_y=0.05,
|
||||||
|
height=0.8,
|
||||||
|
width=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("betterlockscreen --lock blur")},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Reboot
|
||||||
|
text='',
|
||||||
|
fontsize=22,
|
||||||
|
pos_x=0.3,
|
||||||
|
pos_y=0.05,
|
||||||
|
height=0.8,
|
||||||
|
width=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("systemctl reboot")},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Suspend
|
||||||
|
text='',
|
||||||
|
fontsize=22,
|
||||||
|
pos_x=0.54,
|
||||||
|
pos_y=0.05,
|
||||||
|
height=0.8,
|
||||||
|
width=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("systemctl suspend")},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Shutdown
|
||||||
|
text='',
|
||||||
|
fontsize=22,
|
||||||
|
pos_x=0.78,
|
||||||
|
pos_y=0.05,
|
||||||
|
height=0.8,
|
||||||
|
width=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("systemctl poweroff")},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
layout.show(relative_to=1, relative_to_bar=True, y=136, x=30)
|
||||||
59
popups/settings.py
Normal file
59
popups/settings.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from libqtile import qtile
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
|
||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
|
||||||
|
from qtile_extras.popup.menu import (
|
||||||
|
PopupMenu,
|
||||||
|
PopupMenuItem,
|
||||||
|
PopupMenuSeparator,
|
||||||
|
)
|
||||||
|
|
||||||
|
items = [
|
||||||
|
PopupMenuItem(# Wallpaper setting
|
||||||
|
show_icon=False,
|
||||||
|
text=' Nitrogen Wallpaper',
|
||||||
|
font='Open Sans',
|
||||||
|
fontsize=16,
|
||||||
|
can_focus=True,
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("nitrogen")},
|
||||||
|
),
|
||||||
|
PopupMenuSeparator(),
|
||||||
|
PopupMenuItem(# Arandr
|
||||||
|
show_icon=False,
|
||||||
|
text=' Arandr Display',
|
||||||
|
font='Open Sans',
|
||||||
|
fontsize=16,
|
||||||
|
can_focus=True,
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("arandr")},),
|
||||||
|
PopupMenuSeparator(),
|
||||||
|
PopupMenuItem(# VS-Code qtile config
|
||||||
|
show_icon=False,
|
||||||
|
text=' Qtile Config',
|
||||||
|
font='Open Sans',
|
||||||
|
fontsize=16,
|
||||||
|
can_focus=True,
|
||||||
|
highlight_method='text',
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("code /home/cerberus/.config/")},),
|
||||||
|
]
|
||||||
|
|
||||||
|
def settings(qtile):
|
||||||
|
layout = PopupMenu.generate(
|
||||||
|
qtile,
|
||||||
|
pos_x=100,
|
||||||
|
pos_y=100,
|
||||||
|
width=225,
|
||||||
|
opacity=0.7,
|
||||||
|
menuitems=items,
|
||||||
|
background=gruvbox_dark["bg0_soft"]
|
||||||
|
)
|
||||||
|
layout.show(relative_to=1, relative_to_bar=True, y=75, x=325)
|
||||||
|
|
||||||
186
popups/start_menu.py
Normal file
186
popups/start_menu.py
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
from libqtile import qtile
|
||||||
|
from libqtile.lazy import lazy
|
||||||
|
|
||||||
|
from qtile_extras import widget
|
||||||
|
from qtile_extras.popup.toolkit import (
|
||||||
|
PopupRelativeLayout,
|
||||||
|
PopupImage,
|
||||||
|
PopupText,
|
||||||
|
PopupWidget,
|
||||||
|
)
|
||||||
|
|
||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
from popups.settings import settings
|
||||||
|
from popups.network import network_menu
|
||||||
|
from popups.powermenu_sub import powermenu_2
|
||||||
|
from popups.monitor import monitor
|
||||||
|
|
||||||
|
|
||||||
|
def start_menu(qtile):
|
||||||
|
layout = PopupRelativeLayout(
|
||||||
|
qtile,
|
||||||
|
width=350,
|
||||||
|
height=150,
|
||||||
|
opacity=0.7,
|
||||||
|
hide_on_mouse_leave=True,
|
||||||
|
close_on_click=False,
|
||||||
|
border_width=0,
|
||||||
|
background=gruvbox_dark["bg0_soft"],
|
||||||
|
controls=[
|
||||||
|
# Row 1
|
||||||
|
PopupImage(
|
||||||
|
# Qtile logo
|
||||||
|
pos_x=0,
|
||||||
|
pos_y=0,
|
||||||
|
height=0.3,
|
||||||
|
width=0.3,
|
||||||
|
mask=True,
|
||||||
|
colour=gruvbox_dark["blue"],
|
||||||
|
filename="/home/cerberus/.config/qtile/res/images/standby_rotated.png",
|
||||||
|
),
|
||||||
|
PopupWidget(
|
||||||
|
# Welcome banner, fetching user name from $USER
|
||||||
|
pos_x=0.241,
|
||||||
|
pos_y=0.12,
|
||||||
|
height=0.15,
|
||||||
|
width=0.8,
|
||||||
|
widget=widget.GenPollCommand(
|
||||||
|
foreground=gruvbox_dark["orange"],
|
||||||
|
cmd="echo Welcome $USER",
|
||||||
|
shell=True,
|
||||||
|
font="Open Sans Bold",
|
||||||
|
fontsize=20,
|
||||||
|
width=250,
|
||||||
|
scroll=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# PopupText(
|
||||||
|
# # Steam Gamemode (Controller)
|
||||||
|
# pos_x=0,
|
||||||
|
# pos_y=0,
|
||||||
|
# width=0.3,
|
||||||
|
# height=0.3,
|
||||||
|
# can_focus=True,
|
||||||
|
# v_align="middle",
|
||||||
|
# h_align="center",
|
||||||
|
# background=gruvbox_dark["green"],
|
||||||
|
# ),
|
||||||
|
PopupText(
|
||||||
|
# Steam Gamemode (Controller)
|
||||||
|
text="",
|
||||||
|
fontsize=34,
|
||||||
|
pos_x=0.271,
|
||||||
|
pos_y=0.4,
|
||||||
|
width=0.34,
|
||||||
|
height=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method="text",
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
background=gruvbox_dark["bg2"],
|
||||||
|
mouse_callbacks={
|
||||||
|
"Button1": lazy.spawn("steam steam://open/bigpicture")
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Settings
|
||||||
|
text="",
|
||||||
|
fontsize=22,
|
||||||
|
pos_x=0.631,
|
||||||
|
pos_y=0.4,
|
||||||
|
width=0.34,
|
||||||
|
height=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method="text",
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
background=gruvbox_dark["bg2"],
|
||||||
|
mouse_callbacks={"Button1": lazy.function(settings)},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Power-Menu Popup
|
||||||
|
text="",
|
||||||
|
fontsize=24,
|
||||||
|
pos_x=0.035,
|
||||||
|
pos_y=0.7,
|
||||||
|
width=0.22,
|
||||||
|
height=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method="text",
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
background=gruvbox_dark["bg2"],
|
||||||
|
mouse_callbacks={"Button1": lazy.function(powermenu_2)},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Bluetooth
|
||||||
|
text="",
|
||||||
|
fontsize=22,
|
||||||
|
pos_x=0.271,
|
||||||
|
pos_y=0.7,
|
||||||
|
width=0.22,
|
||||||
|
height=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method="text",
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
background=gruvbox_dark["bg2"],
|
||||||
|
mouse_callbacks={"Button1": lazy.function(monitor)},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Network Popup
|
||||||
|
text="",
|
||||||
|
fontsize=24,
|
||||||
|
pos_x=0.511,
|
||||||
|
pos_y=0.7,
|
||||||
|
width=0.22,
|
||||||
|
height=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method="text",
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
background=gruvbox_dark["bg2"],
|
||||||
|
mouse_callbacks={"Button1": lazy.function(network_menu)},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# Audiocontrol
|
||||||
|
text="",
|
||||||
|
fontsize=24,
|
||||||
|
pos_x=0.75,
|
||||||
|
pos_y=0.7,
|
||||||
|
width=0.22,
|
||||||
|
height=0.2,
|
||||||
|
can_focus=True,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
highlight_method="text",
|
||||||
|
foreground=gruvbox_dark["fg0"],
|
||||||
|
highlight=gruvbox_dark["green"],
|
||||||
|
background=gruvbox_dark["bg2"],
|
||||||
|
mouse_callbacks={"Button1": lazy.spawn("pavucontrol")},
|
||||||
|
),
|
||||||
|
PopupText(
|
||||||
|
# "extras"
|
||||||
|
text="extras",
|
||||||
|
font="Open Sans Bold",
|
||||||
|
foreground=gruvbox_dark["fg2"],
|
||||||
|
fontsize=10,
|
||||||
|
pos_x=0.055,
|
||||||
|
pos_y=0.57,
|
||||||
|
height=0.05,
|
||||||
|
width=0.15,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
layout.show(relative_to=1, relative_to_bar=True, y=3, x=3)
|
||||||
|
|
||||||
40
popups/volume_notification.py
Normal file
40
popups/volume_notification.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from res.themes.colors import gruvbox_dark
|
||||||
|
|
||||||
|
from qtile_extras.popup.toolkit import (
|
||||||
|
PopupRelativeLayout,
|
||||||
|
PopupText,
|
||||||
|
PopupSlider
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
VOL_POPUP = PopupRelativeLayout(
|
||||||
|
width=150,
|
||||||
|
height=150,
|
||||||
|
opacity=0.7,
|
||||||
|
background=gruvbox_dark["bg0_soft"],
|
||||||
|
controls=[
|
||||||
|
PopupText(
|
||||||
|
text="",
|
||||||
|
fontsize=60,
|
||||||
|
foreground=gruvbox_dark["fg1"],
|
||||||
|
pos_x=0,
|
||||||
|
pos_y=0,
|
||||||
|
height=0.8,
|
||||||
|
width=0.8,
|
||||||
|
v_align="middle",
|
||||||
|
h_align="center",
|
||||||
|
),
|
||||||
|
PopupSlider(
|
||||||
|
name="volume",
|
||||||
|
pos_x=0.1,
|
||||||
|
pos_y=0.7,
|
||||||
|
width=0.8,
|
||||||
|
height=0.2,
|
||||||
|
colour_below=gruvbox_dark["blue"],
|
||||||
|
bar_border_margin=1,
|
||||||
|
bar_size=8,
|
||||||
|
marker_size=0,
|
||||||
|
end_margin=0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
22
res/images/no_cover.svg
Normal file
22
res/images/no_cover.svg
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
|
||||||
|
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||||
|
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 1069 1069" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:serif="http://www.serif.com/" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
|
||||||
|
<rect height="1066.67" id="Turn-table" style="fill:none;" width="1066.67" x="0.031" y="1.589"/>
|
||||||
|
|
||||||
|
<g>
|
||||||
|
|
||||||
|
<path d="M533.364,253.673c-155.226,-0 -281.25,126.024 -281.25,281.25c0,155.226 126.024,281.25 281.25,281.25c155.226,-0 281.25,-126.024 281.25,-281.25c0,-155.226 -126.024,-281.25 -281.25,-281.25Zm0,62.5c120.731,-0 218.75,98.018 218.75,218.75c0,120.731 -98.019,218.75 -218.75,218.75c-120.731,-0 -218.75,-98.019 -218.75,-218.75c0,-120.732 98.019,-218.75 218.75,-218.75Z" style="fill-opacity:0.5;"/>
|
||||||
|
|
||||||
|
<path d="M533.364,441.173c-51.742,-0 -93.75,42.008 -93.75,93.75c0,51.742 42.008,93.75 93.75,93.75c51.742,-0 93.75,-42.008 93.75,-93.75c0,-51.742 -42.008,-93.75 -93.75,-93.75Zm0,62.5c17.247,-0 31.25,14.002 31.25,31.25c0,17.247 -14.003,31.25 -31.25,31.25c-17.247,-0 -31.25,-14.003 -31.25,-31.25c0,-17.248 14.003,-31.25 31.25,-31.25Z"/>
|
||||||
|
|
||||||
|
<path d="M981.281,284.922c-0.001,-109.306 -88.611,-197.916 -197.917,-197.916c-145.235,0 -354.765,0 -500,0c-52.491,0 -102.832,20.852 -139.948,57.968c-37.117,37.117 -57.969,87.458 -57.969,139.949c0,145.234 0,354.765 0,500c0,109.306 88.61,197.916 197.917,197.916c114.831,0 261.544,0 346.724,0c53.127,0 104.024,-21.359 141.241,-59.273c44.362,-45.194 109.311,-111.359 153.275,-156.147c36.326,-37.006 56.677,-86.789 56.677,-138.644l-0,-343.853Zm-62.5,-0.002l-0,343.855c-0,35.479 -13.925,69.542 -38.779,94.862l-153.276,156.147c-25.463,25.94 -60.287,40.555 -96.636,40.555c-0.004,0 -346.726,0 -346.726,0c-74.79,-0.001 -135.417,-60.628 -135.417,-135.416c0,-145.235 0,-354.766 0,-500c0,-35.915 14.267,-70.359 39.663,-95.754c25.396,-25.396 59.839,-39.663 95.754,-39.663c145.235,0 354.765,0 500,0c74.788,0 135.415,60.627 135.417,135.414Z"/>
|
||||||
|
|
||||||
|
<path d="M386.267,637.826l-145.833,145.833c-12.196,12.196 -12.196,31.998 -0,44.194c12.195,12.196 31.998,12.196 44.194,0l145.833,-145.833c12.196,-12.196 12.196,-31.999 0,-44.194c-12.196,-12.196 -31.998,-12.196 -44.194,-0Z"/>
|
||||||
|
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
BIN
res/images/normal.png
Normal file
BIN
res/images/normal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
res/images/standby_rotated.png
Normal file
BIN
res/images/standby_rotated.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 488 B |
BIN
res/images/standby_rotated_.png
Normal file
BIN
res/images/standby_rotated_.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
37
res/scripts/autoclicker.py
Normal file
37
res/scripts/autoclicker.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
from pynput.keyboard import Controller as KeyboardController, Key
|
||||||
|
from pynput.mouse import Controller as MouseController, Button
|
||||||
|
|
||||||
|
|
||||||
|
keyboard = KeyboardController()
|
||||||
|
mouse = MouseController()
|
||||||
|
|
||||||
|
# Intervall für Rechtsklick in Sekunden
|
||||||
|
rechtsklick_intervall = 4.50
|
||||||
|
|
||||||
|
|
||||||
|
def shift_spam_mit_rechtsklick():
|
||||||
|
letzter_klick = time.time()
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# Shift drücken und loslassen
|
||||||
|
# keyboard.press(Key.shift)
|
||||||
|
# time.sleep(0.05)
|
||||||
|
# keyboard.release(Key.shift)
|
||||||
|
|
||||||
|
# Kurze Pause zwischen den Shift-Spams
|
||||||
|
# time.sleep(0.1)
|
||||||
|
|
||||||
|
# Zeit für Rechtsklick?
|
||||||
|
jetzt = time.time()
|
||||||
|
if jetzt - letzter_klick >= rechtsklick_intervall:
|
||||||
|
mouse.click(Button.left)
|
||||||
|
letzter_klick = jetzt
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Skript beendet.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
shift_spam_mit_rechtsklick()
|
||||||
40
res/scripts/autostart.sh
Executable file
40
res/scripts/autostart.sh
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ___ _____ ___ _ _____ ____ _ _
|
||||||
|
# / _ \_ _|_ _| | | ____| / ___|| |_ __ _ _ __| |_
|
||||||
|
# | | | || | | || | | _| \___ \| __/ _` | '__| __|
|
||||||
|
# | |_| || | | || |___| |___ ___) | || (_| | | | |_
|
||||||
|
# \__\_\|_| |___|_____|_____| |____/ \__\__,_|_| \__|
|
||||||
|
#
|
||||||
|
# by cerberus
|
||||||
|
# -----------------------------------------------------
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Essentials
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Load polkit agent
|
||||||
|
gnome-keyring-daemon --start --components=pkcs11,secrets,ssh &
|
||||||
|
/usr/lib/mate-polkit/polkit-mate-authentication-agent-1 &
|
||||||
|
mpd &
|
||||||
|
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Configure Screens
|
||||||
|
# -----------------------------------------------------
|
||||||
|
"$HOME"/.screenlayout/screens.sh &
|
||||||
|
# -----------------------------------------------------
|
||||||
|
# Autostart Applications
|
||||||
|
# -----------------------------------------------------
|
||||||
|
picom & # Compositor
|
||||||
|
nitrogen --restore & # Wallpaper Manager
|
||||||
|
copyq & # Clipboard Manager
|
||||||
|
flameshot & # Screenshot Tool
|
||||||
|
discord & # Discord
|
||||||
|
steam -silent & # Steam
|
||||||
|
firefox & # Firefox
|
||||||
|
youtube-music & # YT-Music
|
||||||
|
bitwarden-desktop & # Bitwarden Passwordmanager
|
||||||
|
joplin & # Note Taking
|
||||||
|
affine & # Hand-Written Notes
|
||||||
|
"$HOME"/Documents/scripts/wacom_screen_config.sh & # Grpahic tablet
|
||||||
|
# kitty --app-id "RMPC" --execute rmpc --theme=.config/rmpc/gruvbox.ron &
|
||||||
|
sleep 1 &
|
||||||
|
qtile cmd-obj -o cmd -f reload_config
|
||||||
67
res/scripts/keybinds.py
Executable file
67
res/scripts/keybinds.py
Executable file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Define the file path using Path and expand user directory
|
||||||
|
file_path = Path("~/.config/qtile/modules/keys.py").expanduser()
|
||||||
|
|
||||||
|
# Initialize a flag for header printing
|
||||||
|
header_printed = False
|
||||||
|
|
||||||
|
|
||||||
|
def capitalize_first_letter(s):
|
||||||
|
"""Capitalize the first letter of each key."""
|
||||||
|
return s.capitalize() if s else ""
|
||||||
|
|
||||||
|
|
||||||
|
def replace_keys(key):
|
||||||
|
"""Replace Mod and Control with Super and Ctrl."""
|
||||||
|
if key == "mod":
|
||||||
|
return "Super"
|
||||||
|
if key == "xf86calculator":
|
||||||
|
return "Calculator"
|
||||||
|
elif key == "control":
|
||||||
|
return "Ctrl"
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
with open(file_path, "r") as file:
|
||||||
|
for line in file:
|
||||||
|
# Skip lines that contain "# Key("
|
||||||
|
if "# Key(" in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check for KB_GROUP headers
|
||||||
|
if "# KB_GROUP-" in line:
|
||||||
|
if header_printed:
|
||||||
|
print("") # Add a blank line before the next header
|
||||||
|
# Print the header in bold yellow, removing "KB_GROUP-"
|
||||||
|
print(
|
||||||
|
f"\n\033[1;33m{line.strip().replace('KB_GROUP-', '').strip()}\033[0m\n"
|
||||||
|
)
|
||||||
|
header_printed = True
|
||||||
|
|
||||||
|
# Check for Key bindings
|
||||||
|
match = re.search(r'Key\(\[(.*?)\], "(.*?)", lazy\..*, desc="(.*)"\)', line)
|
||||||
|
if match:
|
||||||
|
# Get the modifier keys
|
||||||
|
keys = match.group(1).replace("'", "").replace('"', "").split(", ")
|
||||||
|
# Get the main key
|
||||||
|
key = match.group(2)
|
||||||
|
# Get the description
|
||||||
|
description = match.group(3)
|
||||||
|
|
||||||
|
# Prepare the key strings for each key position
|
||||||
|
mod1 = (
|
||||||
|
capitalize_first_letter(replace_keys(keys[0])) if len(keys) > 0 else ""
|
||||||
|
)
|
||||||
|
mod2 = (
|
||||||
|
capitalize_first_letter(replace_keys(keys[1])) if len(keys) > 1 else ""
|
||||||
|
)
|
||||||
|
key_str = capitalize_first_letter(key) # The main key
|
||||||
|
|
||||||
|
# Print the keys in their respective columns
|
||||||
|
print(f" {mod1:<6} {mod2:<8} {key_str:<30}{description}")
|
||||||
|
|
||||||
|
# Wait for user input before exiting
|
||||||
|
input("Press [Enter] to exit...")
|
||||||
BIN
res/themes/__pycache__/colors.cpython-313.pyc
Normal file
BIN
res/themes/__pycache__/colors.cpython-313.pyc
Normal file
Binary file not shown.
BIN
res/themes/__pycache__/colors.cpython-314.pyc
Normal file
BIN
res/themes/__pycache__/colors.cpython-314.pyc
Normal file
Binary file not shown.
25
res/themes/colors.py
Normal file
25
res/themes/colors.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# --------------------------------------------------------
|
||||||
|
# Gruvbox Dark Theme Colors
|
||||||
|
# --------------------------------------------------------
|
||||||
|
gruvbox_dark = {
|
||||||
|
"bg0_hard": "#1d2021", # Background, hard
|
||||||
|
"bg0_soft": "#32302f", # Background, soft
|
||||||
|
"bg0_normal": "#282828", # Background, normal
|
||||||
|
"bg1": "#3c3836", # Secondary background
|
||||||
|
"bg2": "#504945", # Background, darker
|
||||||
|
"bg3": "#665c54", # Background, lighter
|
||||||
|
"bg4": "#7c6f64", # Background, lightest
|
||||||
|
|
||||||
|
"fg0": "#fbf1c7", # Foreground, light
|
||||||
|
"fg1": "#ebdbb2", # Foreground, normal
|
||||||
|
"fg2": "#d5c4a1", # Foreground, slightly dark
|
||||||
|
"fg3": "#bdae93", # Foreground, dark
|
||||||
|
|
||||||
|
"red": "#cc241d", # Red
|
||||||
|
"orange": "#d65d0e", # Orange
|
||||||
|
"yellow": "#d79921", # Yellow
|
||||||
|
"green": "#98971a", # Green
|
||||||
|
"aqua": "#689d6a", # Aqua
|
||||||
|
"blue": "#458588", # Blue
|
||||||
|
"purple": "#b16286" # Purple
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user