From 9c29be09bf21d9760d617965dfaba95e6e770620 Mon Sep 17 00:00:00 2001 From: cerberus Date: Mon, 23 Feb 2026 17:56:46 +0100 Subject: [PATCH] first commit --- __init__.py | 0 __pycache__/config.cpython-313.pyc | Bin 0 -> 1349 bytes __pycache__/config.cpython-314.pyc | Bin 0 -> 1346 bytes config.py | 50 ++ modules/__init__.py | 0 modules/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 145 bytes modules/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 147 bytes modules/__pycache__/groups.cpython-313.pyc | Bin 0 -> 4750 bytes modules/__pycache__/groups.cpython-314.pyc | Bin 0 -> 5227 bytes modules/__pycache__/hooks.cpython-313.pyc | Bin 0 -> 1937 bytes modules/__pycache__/hooks.cpython-314.pyc | Bin 0 -> 2202 bytes modules/__pycache__/keys.cpython-313.pyc | Bin 0 -> 6663 bytes modules/__pycache__/keys.cpython-314.pyc | Bin 0 -> 6939 bytes modules/__pycache__/layouts.cpython-313.pyc | Bin 0 -> 1881 bytes modules/__pycache__/layouts.cpython-314.pyc | Bin 0 -> 2039 bytes modules/__pycache__/screens.cpython-313.pyc | Bin 0 -> 6090 bytes modules/__pycache__/screens.cpython-314.pyc | Bin 0 -> 6424 bytes modules/groups.py | 161 ++++++ modules/hooks.py | 46 ++ modules/keys.py | 161 ++++++ modules/layouts.py | 80 +++ modules/screens.py | 257 ++++++++++ plugins/__init__.py | 0 plugins/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 145 bytes plugins/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 147 bytes .../graphical_notifications.cpython-313.pyc | Bin 0 -> 23590 bytes .../graphical_notifications.cpython-314.pyc | Bin 0 -> 26931 bytes .../__pycache__/notifications.cpython-313.pyc | Bin 0 -> 23568 bytes plugins/graphical_notifications.py | 462 ++++++++++++++++++ plugins/notifications.py | 458 +++++++++++++++++ plugins/notifications_copy.py | 456 +++++++++++++++++ popups/__init__.py | 0 popups/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 144 bytes popups/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 146 bytes .../brightness_notification.cpython-313.pyc | Bin 0 -> 903 bytes popups/__pycache__/calendar.cpython-313.pyc | Bin 0 -> 2952 bytes popups/__pycache__/calendar.cpython-314.pyc | Bin 0 -> 3044 bytes popups/__pycache__/monitor.cpython-313.pyc | Bin 0 -> 855 bytes popups/__pycache__/monitor.cpython-314.pyc | Bin 0 -> 864 bytes .../__pycache__/mpris2_layout.cpython-313.pyc | Bin 0 -> 1936 bytes .../__pycache__/mpris2_layout.cpython-314.pyc | Bin 0 -> 2050 bytes popups/__pycache__/network.cpython-313.pyc | Bin 0 -> 1447 bytes popups/__pycache__/network.cpython-314.pyc | Bin 0 -> 1490 bytes popups/__pycache__/powermenu.cpython-313.pyc | Bin 0 -> 1678 bytes popups/__pycache__/powermenu.cpython-314.pyc | Bin 0 -> 1733 bytes .../__pycache__/powermenu_sub.cpython-313.pyc | Bin 0 -> 2038 bytes .../__pycache__/powermenu_sub.cpython-314.pyc | Bin 0 -> 2120 bytes popups/__pycache__/settings.cpython-313.pyc | Bin 0 -> 1602 bytes popups/__pycache__/settings.cpython-314.pyc | Bin 0 -> 1672 bytes popups/__pycache__/start_menu.cpython-313.pyc | Bin 0 -> 3560 bytes popups/__pycache__/start_menu.cpython-314.pyc | Bin 0 -> 3762 bytes .../volume_notification.cpython-313.pyc | Bin 0 -> 879 bytes .../volume_notification.cpython-314.pyc | Bin 0 -> 903 bytes popups/calendar.py | 135 +++++ popups/monitor.py | 31 ++ popups/mpris2_layout.py | 125 +++++ popups/network.py | 48 ++ popups/powermenu.py | 70 +++ popups/powermenu_sub.py | 90 ++++ popups/settings.py | 59 +++ popups/start_menu.py | 186 +++++++ popups/volume_notification.py | 40 ++ res/images/no_cover.svg | 22 + res/images/normal.png | Bin 0 -> 3895 bytes res/images/standby_rotated.png | Bin 0 -> 488 bytes res/images/standby_rotated_.png | Bin 0 -> 5174 bytes res/scripts/autoclicker.py | 37 ++ res/scripts/autostart.sh | 40 ++ res/scripts/keybinds.py | 67 +++ res/themes/__pycache__/colors.cpython-313.pyc | Bin 0 -> 772 bytes res/themes/__pycache__/colors.cpython-314.pyc | Bin 0 -> 774 bytes res/themes/colors.py | 25 + 72 files changed, 3106 insertions(+) create mode 100644 __init__.py create mode 100644 __pycache__/config.cpython-313.pyc create mode 100644 __pycache__/config.cpython-314.pyc create mode 100644 config.py create mode 100644 modules/__init__.py create mode 100644 modules/__pycache__/__init__.cpython-313.pyc create mode 100644 modules/__pycache__/__init__.cpython-314.pyc create mode 100644 modules/__pycache__/groups.cpython-313.pyc create mode 100644 modules/__pycache__/groups.cpython-314.pyc create mode 100644 modules/__pycache__/hooks.cpython-313.pyc create mode 100644 modules/__pycache__/hooks.cpython-314.pyc create mode 100644 modules/__pycache__/keys.cpython-313.pyc create mode 100644 modules/__pycache__/keys.cpython-314.pyc create mode 100644 modules/__pycache__/layouts.cpython-313.pyc create mode 100644 modules/__pycache__/layouts.cpython-314.pyc create mode 100644 modules/__pycache__/screens.cpython-313.pyc create mode 100644 modules/__pycache__/screens.cpython-314.pyc create mode 100644 modules/groups.py create mode 100644 modules/hooks.py create mode 100644 modules/keys.py create mode 100644 modules/layouts.py create mode 100644 modules/screens.py create mode 100644 plugins/__init__.py create mode 100644 plugins/__pycache__/__init__.cpython-313.pyc create mode 100644 plugins/__pycache__/__init__.cpython-314.pyc create mode 100644 plugins/__pycache__/graphical_notifications.cpython-313.pyc create mode 100644 plugins/__pycache__/graphical_notifications.cpython-314.pyc create mode 100644 plugins/__pycache__/notifications.cpython-313.pyc create mode 100644 plugins/graphical_notifications.py create mode 100644 plugins/notifications.py create mode 100644 plugins/notifications_copy.py create mode 100644 popups/__init__.py create mode 100644 popups/__pycache__/__init__.cpython-313.pyc create mode 100644 popups/__pycache__/__init__.cpython-314.pyc create mode 100644 popups/__pycache__/brightness_notification.cpython-313.pyc create mode 100644 popups/__pycache__/calendar.cpython-313.pyc create mode 100644 popups/__pycache__/calendar.cpython-314.pyc create mode 100644 popups/__pycache__/monitor.cpython-313.pyc create mode 100644 popups/__pycache__/monitor.cpython-314.pyc create mode 100644 popups/__pycache__/mpris2_layout.cpython-313.pyc create mode 100644 popups/__pycache__/mpris2_layout.cpython-314.pyc create mode 100644 popups/__pycache__/network.cpython-313.pyc create mode 100644 popups/__pycache__/network.cpython-314.pyc create mode 100644 popups/__pycache__/powermenu.cpython-313.pyc create mode 100644 popups/__pycache__/powermenu.cpython-314.pyc create mode 100644 popups/__pycache__/powermenu_sub.cpython-313.pyc create mode 100644 popups/__pycache__/powermenu_sub.cpython-314.pyc create mode 100644 popups/__pycache__/settings.cpython-313.pyc create mode 100644 popups/__pycache__/settings.cpython-314.pyc create mode 100644 popups/__pycache__/start_menu.cpython-313.pyc create mode 100644 popups/__pycache__/start_menu.cpython-314.pyc create mode 100644 popups/__pycache__/volume_notification.cpython-313.pyc create mode 100644 popups/__pycache__/volume_notification.cpython-314.pyc create mode 100644 popups/calendar.py create mode 100644 popups/monitor.py create mode 100644 popups/mpris2_layout.py create mode 100644 popups/network.py create mode 100644 popups/powermenu.py create mode 100644 popups/powermenu_sub.py create mode 100644 popups/settings.py create mode 100644 popups/start_menu.py create mode 100644 popups/volume_notification.py create mode 100644 res/images/no_cover.svg create mode 100644 res/images/normal.png create mode 100644 res/images/standby_rotated.png create mode 100644 res/images/standby_rotated_.png create mode 100644 res/scripts/autoclicker.py create mode 100755 res/scripts/autostart.sh create mode 100755 res/scripts/keybinds.py create mode 100644 res/themes/__pycache__/colors.cpython-313.pyc create mode 100644 res/themes/__pycache__/colors.cpython-314.pyc create mode 100644 res/themes/colors.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/__pycache__/config.cpython-313.pyc b/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40a608f7c04eade3a05027e3f1fd5c1e6d084d17 GIT binary patch literal 1349 zcmb7E&2QsG6dxycKI=Gf()5eccC|t(hh$d*3ek!^uon^u#G1ouiDWtUB(>sryw7&C z*;8`r6%I%ox$>WIth5(1VzmbZQg20-5J%qF3FS(pd48Y2_ug+txmDE)!QUT${eAwU zOvrD-*!)Us<+djg@;xz#Aq_}h0;!*a+_A88gM42Gxvzkd+WA4DUxZ@61f|rL2jzYR zDygmvs%KeMVX)O#K~25IL9MTWc1(tdIYjIs;tdgRh&V&U8DhOrf_j%g!zhD}r-`S9 zXB$u3sK5?>ck%2QRoEY@Pe0l$aQA)s@twZWjIG`?LaaO?h6;yYzVkmSYiVT;D>XPY z>O<`wF&c($G>w+A{fYcQf_HJ|Bclz+lhWzVwP<;oPnXac(U<%XhduW-NN8MvH*FWx;9UQVvtBQ__GS{Xf6KFs!F*?q}Yj+N?BFf8XXLIrjsw{|H=7-n1*#CrUGv>q| z4LfTd0J$fC&yMh|O-OVCMfeNC|8L2f4=9R|c$vJmDaq)7vT&*!=Q(53ke57nl+puL z5k1>)e7DT2sVi%NG?HED^=G=mSYgQaFa~ykK#tPq0p-kqR zbL@=`EIV>9@C`7ZH-Xye{l-Ae&1+IqgqiERGxrtch55|4W|Ye|1C*H_QJC@C+%wZf z*BjK)ez~3{I;U8p{l^m`*iYmZ86o_=?m3&$u1#S?Ar8BpY>IBmZ8tMI!Gf0_W&D2% zXIO@R8zlS%#Z6w4q_^aQx8y#`H{|43a_~FZe?!i03Z!{&sU?-hvX~UBOF1d2OC>Y) z6AYBKWdW1g`&UFyTJ0;+NVfM?b0-nbccL6-yCXAjsv* z-l})`c=h;O`Z|A*R2!=&iBh?IzIuLXuFN0Guay&YE}vko()#Y3WcMK1*+bN%dGu#l ZX%}wtMLoBy-RML#ttWbGrNM`|-9P&_n!5l1 literal 0 HcmV?d00001 diff --git a/__pycache__/config.cpython-314.pyc b/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ead62287db4ba137dfa3c4c91395f8e3aa35074 GIT binary patch literal 1346 zcmb7E&5zqe6dxycKG$*Lr0H&!C8Vv80#c%)LaS9p4^+rGfQrSTD#}`pJ=wM5c+7ab zX?k+F>=kYtx$>WI+(Qp$MAZWViQ7s=h$C<8gyqUeGk)*)-uun_82SB%T0^jY|LgCw zA7q4n70PZ^w!)iFC4>@mf=p?G42elbfff2fFHDMt%w$7hN~RYlC8NyBMuk-}U7l2p z8mncpGO2%MXTZi}AY(y`4#1LKT6R1paTxwgI#(OyXJc#x5kUpH>B^?!Yrb77?%b-Yn|}tqW^^ zr;cW}v%rBn+SWeuM6V*cC%kkwXS5&}Yvu(mro6So>@c*bD1z&A;`?M}Wizto#EB!` zno%*EIVAzJ9N%*myp>HX01N1iVeLlMOVc*xEfGHDg$Mfw%NtU|w1+1AB?N><|iPE;CG*ys`4Ftk7)* zP0*ijb75x~VzmDt5s>|e-atnXe|LOFmU!%7I>R)M#-luoan{>$u8hJpuRP5A|1rG> zVerpD(a&&P6(vb}jqbfhci?=52EU-g-%#%rx_?zd?OTbK)>=t9E!Pt{t*D8TtNH*8 zRV^ujQ@3}C^t98xM6LA3K_>4Dxs#m@J*lU97u=8TT%ukoyxVEBlc*31)Pg~-UL0?Z zFCJ|keT!cf57T;U^Eg#%7tc1&F0769ef6a>05EwBzDno2uhRX)^yUGurtSWpRi#_H XDwg#^(zw!*xU`w-oo_Vy0aW)7*BqCh literal 0 HcmV?d00001 diff --git a/config.py b/config.py new file mode 100644 index 0000000..92077a2 --- /dev/null +++ b/config.py @@ -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 diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/modules/__pycache__/__init__.cpython-313.pyc b/modules/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b00aa526632b057afb7ad1354bba4a0d4a77ca5 GIT binary patch literal 145 zcmey&%ge<81T9CuWPs?$AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl;${fzwFRQ=@C zqNLQK(qetR>P{wCAAftgHh(Vb_lhJP_LlF~@{~08C%RoOPKQ~oB zIkhM$wWzdMUoSa7FD)}&zpx}TCsjW;KczG$wOBtsJ~J<~BtBlRpz;=n4Mfzgh!to6 S$ckbR;}bI@BV!RWkOctISs}Ur literal 0 HcmV?d00001 diff --git a/modules/__pycache__/groups.cpython-313.pyc b/modules/__pycache__/groups.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87377846c86a92b98e70a9a0df6b540608d8b68a GIT binary patch literal 4750 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 literal 0 HcmV?d00001 diff --git a/modules/__pycache__/hooks.cpython-313.pyc b/modules/__pycache__/hooks.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f477448d1c244f008d1abe539fb5c8c236051b50 GIT binary patch literal 1937 zcmah~&2Jk;6rb5$f35T3k`R$7NhcqraZ&A7P$Ft8LV{E%Ndxl6a7cyOtk)l~-gRfz zO;ZVE#DVtITq5<@EB^u#|3ln1GMWYn2`=U2$VikMZ`R&*6sQFnD>73-rMf* zu!>-G*MF+~7((bbvGIq}C%S(Caey?W;dQizDPEJPM8sZNm#OTn3Jt+3uZPznG(ym= z$UM>%E%Y8vV;a4MbFcYE2WxJ8K>L&yoLpOETsPI_H(rYHGI4+z;cWVX1(it;+ zn_`vwU)Kc!pu;xW7U$l`Nj^1La{&I?kw@2X@65*AVV|3e`qW^ZM|l9+mnNtT-f!Mp z`X-$+9J^F4r@au;)MRO9&}!XfX}#e(%+;xzVwGfwOAg~oU3V*7HSgASyVzhRP%Jk|X0SQ%+6<4=_F{o?+oN6K^; z#L<7FPl0prKgdI(oTiD$}1{QY3{6Uf6}-lPHzj;|&aD$MYql4CSj z0RS^io2zB&H0rES)9XA6&)}W@fT$=En(*YyPQi5wp4?R`P$BTKi!e3O?@Fw#OgvF0 z_HRE^CXdB#yajM*EoHK;%(s;JBW2MuX7uw1)DRMmh`j9PVESC(5^(eA9jsv}gy?pV zn`1#C`gyekdSd91fts|1tU$Ibm*u2#Z(r5m?TKMkP1|K;*Re0U#7i<#YFJh;PO@a) zHLM1!-Z6>mkX?Pxf^Z$q~uUzV6kYHs%A+y#(F8&^-jZ?w9wN%#$OpM` z2OWe6x(UrE4w_}OESGQ+du^d6+Coz1QtAp;&rq06Al&cpQb1w8tpnt5{R`s2m>?>@{v$Uah_Bo5;sK7iDXKJ*$I z6T4AgtlzAts4==ASTX27qRH%-e8|hAM!n$J&|5^W;0gc8aB6kSgtr%k!bf`8Lv`7D z*(`dYQ1Q<=RbSC;uM@4%6fgu5>}!}hGRF9?DTL>qqjS&E$S-K_WC*=J^*GZSPalq5 zIYfz07)jypXTP0oDBzM7CMS}FF9_~jM^GI#;Lf)2Y*&il NIl(=DA}G(je*ky6rsDtr literal 0 HcmV?d00001 diff --git a/modules/__pycache__/hooks.cpython-314.pyc b/modules/__pycache__/hooks.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb68d001b0d8312396f192afb06e2c0970239840 GIT binary patch literal 2202 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 TFz9R6JBzws##!H4lb)_)%- literal 0 HcmV?d00001 diff --git a/modules/__pycache__/layouts.cpython-313.pyc b/modules/__pycache__/layouts.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59c1f83ab8c9920731a7fe47a44af41da3063be4 GIT binary patch literal 1881 zcmZvbNpIUm6vs)Ck|0-yv^h9%+L}RoF(8bk2}Oj9Jfxyp zU6AHd^wgk0*Kd$-6D9`>0~AP56sT_$g53KaEk$h5fk^&;Z<+UI(eqqxMu746`Y)Z2 zLH&g=8QHOTUQ7tW&w?)K;tgR>Wa1j?#ErzB#H2OhP~H%92`sXn+>u!FFnN%hOfmhw zGnOoUC?3pB?GpuaT-H;1T37UpKBKF87G}&fl*8ml;=3Z`6ep3>ZvBxb z$8;s8t1-QxFTTuOis|K;UWvtDi0O+ly&BVNF})ts8!^2Z(_82Ins}JT^I&=7Qtiqa zF0&@`ltoV5o)0RyPyANrOal8ETpFP!@sv%NVLCi}^h`7v<1yOb=o$4m%*+@Ic<|zu7Mluo4!T4(ls6G`0RwM*RlQ=rqg!&gX}S} zT0M*Sw0;cR+H$)m$6RqtpScEg21SS3ZoSLsU6c0fo==$1Gkud$qi3@AAb(`_s7;(Y zJj9@kt6g%p2R%W$H7L8xXc&%b)5dkvq6e;JQ8t*dVDBLq>fL-O)aJNinMcPy1Tee= zJU_ukv^55k(JHsimJhz@+BDnrHtAWu$7O7oFGM>EC(?Y?pEcvSf|#a?1-|Sr;VG} zqJ?R;+s4z@#&sIdv+~}#@qP9-SMVY$glsV!7JRks3z{E!w*EEwD_y`lQt5nr9_x07gA3%`5#w*SUt^b2bt|K36@bx z$jmpZD0en=w8YtW01>}q9{t7Ft zBCbX5*8-&$t|M=tJL+>2aVv61eO^MojP7WWR{%o^w`W^Oua4@VzRR;W;rRar?`Qw1 literal 0 HcmV?d00001 diff --git a/modules/__pycache__/layouts.cpython-314.pyc b/modules/__pycache__/layouts.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2427448834d4ba5d539a1e77ace97c5eec0824c0 GIT binary patch literal 2039 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 literal 0 HcmV?d00001 diff --git a/modules/groups.py b/modules/groups.py new file mode 100644 index 0000000..30b3d27 --- /dev/null +++ b/modules/groups.py @@ -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, + ), + ], + ) +) diff --git a/modules/hooks.py b/modules/hooks.py new file mode 100644 index 0000000..d8a4a87 --- /dev/null +++ b/modules/hooks.py @@ -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 diff --git a/modules/keys.py b/modules/keys.py new file mode 100644 index 0000000..5392ebc --- /dev/null +++ b/modules/keys.py @@ -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()), +] diff --git a/modules/layouts.py b/modules/layouts.py new file mode 100644 index 0000000..37c7a5b --- /dev/null +++ b/modules/layouts.py @@ -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, +) diff --git a/modules/screens.py b/modules/screens.py new file mode 100644 index 0000000..1cd3f73 --- /dev/null +++ b/modules/screens.py @@ -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="{summary}\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, +) diff --git a/plugins/__init__.py b/plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/__pycache__/__init__.cpython-313.pyc b/plugins/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7fe6595e1560513c2d0eb6a3326d6c0c89bc3d36 GIT binary patch literal 145 zcmey&%ge<81U*N;WPs?$AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl;${fzwFRQ=@C zqNLQK(qetR>P{wCAAftgHh(Vb_lhJP_LlF~@{~08C%RoOPKQ~oB zIkhM$wWzdMUoSa7FD)}&zpx}TCsn^7r!+k?uUJ1mJ~J<~BtBlRpz;=nO>TZlX-=wL W5i8IDkQK!s#wTV*M#ds$APWH3cp=LG literal 0 HcmV?d00001 diff --git a/plugins/__pycache__/graphical_notifications.cpython-313.pyc b/plugins/__pycache__/graphical_notifications.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..172ff0402189d99f3b70cd3d3db74a2c37a03143 GIT binary patch literal 23590 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 literal 0 HcmV?d00001 diff --git a/plugins/__pycache__/graphical_notifications.cpython-314.pyc b/plugins/__pycache__/graphical_notifications.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20258791aab810183edf130bd391001756559abe GIT binary patch literal 26931 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 b`tuld@Xz?1L;JZu+n;wh&+_MX6a4=Vy`#4h literal 0 HcmV?d00001 diff --git a/plugins/__pycache__/notifications.cpython-313.pyc b/plugins/__pycache__/notifications.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..770ad759be3c57d2c7d5c42a2ef65f7bdba71052 GIT binary patch literal 23568 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 literal 0 HcmV?d00001 diff --git a/plugins/graphical_notifications.py b/plugins/graphical_notifications.py new file mode 100644 index 0000000..a899ac8 --- /dev/null +++ b/plugins/graphical_notifications.py @@ -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. '{summary}\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]) diff --git a/plugins/notifications.py b/plugins/notifications.py new file mode 100644 index 0000000..da61987 --- /dev/null +++ b/plugins/notifications.py @@ -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. '{summary}\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]) diff --git a/plugins/notifications_copy.py b/plugins/notifications_copy.py new file mode 100644 index 0000000..69e9fbe --- /dev/null +++ b/plugins/notifications_copy.py @@ -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. '{summary}\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]) diff --git a/popups/__init__.py b/popups/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/popups/__pycache__/__init__.cpython-313.pyc b/popups/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c1715baf0bc1256698bfd0e72cfdaa76095aefb GIT binary patch literal 144 zcmey&%ge<81XGTF$pF!hK?DpiLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNpW`WgATsrt#O zMMs zE9yad_2$Kce}c!{mI0w62)Q-n;7RbCZA8BrzWIHR`Cc=NR7yrbli$Sn97gB|7yeRV z2^KE^yhaLAgagzRsIbX#=pfV#({NLyVpF2hChCN5ASDb->nurnwImTtU zg_JlfSxG3#op?tAdrCh*7Mp&L9G+2rOw(?^q|N zYEPrXN|@!JlCd{F!j|Tm$M}ghA)d?PfwGNHU6u%-VwpWmXKx^e(GTcnoyBzHj_Q!U z>xrOWe0up|whw@X`^H`4{X0&xI*W|VUeCfT(#5umX(i4?mj{mZiS0V(bIjyEq1d3r zvwKV$6GuJ0JSHqMz@{;9nRH_I+yRRotD0pRHj56HK-MXDu(9JTGk&Qr!Phzyb<4vu z0cx7XfK_BB+S&-y6%c5avpi=xU@X}s7V)U6V~d=ytgZnh6q;5?8Z}HCx&>8GZvr$! zFkBXo?cUO*BK;RHO2%5(bSF&KweE1)U91Z|Q(`%-N|a?OcB<|GkKo|RsYBrpA-3_% zH7%^djHu>R$NYJ!E+N*?bSWP+<3J63r45b`cf?;Tzii6%vKe9C4OL<*F~I{6yMMX8cU~ZF8F0`X0;r*^*x@`K6m@d(+bP zc`on6&ae8pqF=ZIdtOY$<@p+l7yRs1uD#sgv3~2UKV83le(kznT=m!1{nFaJl#I(4 S<&AV?p4cphrl>@>pqxLD#T)1V literal 0 HcmV?d00001 diff --git a/popups/__pycache__/calendar.cpython-313.pyc b/popups/__pycache__/calendar.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2826373cf606c720fafc4b311c7140315ad5dfb GIT binary patch literal 2952 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 literal 0 HcmV?d00001 diff --git a/popups/__pycache__/monitor.cpython-314.pyc b/popups/__pycache__/monitor.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31c4aad6f8c28d31fcc6373bb11e57a105dec819 GIT binary patch literal 864 zcmYjPOK;Oa5Z?8>d9}rqLa3BhNJvqU!>tztoT@}cRb`>Vp^9ZYTic404eNCuw{YSg zAi;?P7k&_uwJImnLvNK@;L5C%LPy@6$Ii~q_l-9j^$G$x-}o306@-3D=SILU*A^Q@ScwOIz(}U7hP)gBzu<^~|ottrl|iRm2xu;}L3`7qVCz3;i8W zQUZo?oD3qu+lsLF=p>)GB=vc6L|*x)H0PpP2H(tbu&6~WKMLrHH}Kgo#Io328lY8J zS7X?w=-}VkKI)tO1sA)DtGb%2_tC)(Cgd7Vn)Cah9ORl?)tNuR`*=_Pe|BT_b*N!& z%_{)51wd7~FyTCT3(@2&JRE)gB<148>uq5L(Niy@hrC@88l%UV&_bGKLI>PD7Dh~x zDCWYX6F*G&sj%Z@Ks-A3Ml{cemlFSoh(?%Fa3C6{NjMa>fU*H$-mFF?@WWxmXg(eY zE2Lx2-~k}C%H~{poQlO;ju-fBx2-aXkIB}Bmc{hAtuV=;Fw&CyOoDv8E}TwGN2C)H z77&(a9S5E`OrlOX!OlbuE$fWvIN_8zlT%^NZ6>L$!OC`Eo1(M&dSTUzdP9DV&5C?v2yRsZhfhIw>OJr`+Bw3Fs9p=O{7;p?7ZJ8?yeN~*DiIWFa1WkZd^e* zv7%Q6yCMhzlrT^{FgXs$WNg-1v?~#Hs!|x2~g9PwwZv06tQgV@p{RwXPF&4 ziBIi;Bd8JrwVW!%0WPS=ax3CeAwWbzXc-BKr~+hxM4}wH@@DI#6{FnDGav8evM7i0 zkQ?gdhVRG~(1@QK?d1;mIR!Y!mLtHHLJy&FG%+7u4nu4beovu;U?uc0#6<5$&>{3F zilHLJry(HmN+eE5nDPl+xV2eL0XK*{on~(H!zR+ z{t{kiyw7)$W!5n594R-5jmuR`S1@h4!wkDo0%3aQ|y*aQSX+UM5~fErX{y+ zykcI2)6l&ET8z5Ceyi~4iZP?Pq|TPO#6!$v7Vad^IJh~E^rk!h0 zi}l5xvsE$vJa=vEoj<;;aK*xP#)FJF#TZwbx{bL~Cr!qq!n$S$^N>SaZT;3`gNN44 z8f)-y1Di&J@km3{Ez@Z7NW15Rb;?Yabp~!rI*n^)%kCzHDAD)aweKoCY@4;31@yYL z(y}|TeYmmcV?2PY5XOkjBS5)E=&G^R$$b9u*4LGu!z;xwO#v&Yg((X4RJ=J&3zHEh z>t`KG3>dL%%=zo)-Xywv1a~d#vtiFM)%+<$9g5o~X}K*0D0i=3{-iqwjsag-*DF2h zuRwJbHyNf}Vd4-3>&6TZL3i9NCVa8g~5c;fWg#S>^&JN5kQXI@?UOxlH<#P5C0d~&yJJJfV%H5eD%-jcJyqtDo+*0S(( z^f-{fm6Kc!b~`A`^3T%r-+_=E*pm%((6xosgX+yo2f(I z(CAiF_4<>ZI^?MnUN-Ly=DfkIu<5*)DR`OToort$zB44n27TPHr%vtR0J@JG{TB}S zM(>SFduqYU7W{U-jys76^cs+2nM;LF=dM<-r*EL|-_SOrZ~g=_)e%q43r-c_p!8Nz z&dZF7&w(i~KXv8B&HS{No4j&#Gk17rBnfO|Lb-b2!AqfE?g6_(>HikIy%u;&PKLK% T3raA29CT$ha3MjD!5;qucx_f^ literal 0 HcmV?d00001 diff --git a/popups/__pycache__/mpris2_layout.cpython-314.pyc b/popups/__pycache__/mpris2_layout.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eefa5c4c9bba79a81147864c2eb26c2089a55bc9 GIT binary patch literal 2050 zcmbtUO>7%Q6dv2_-^5PVcAUgX)1(24n_`EyG#9`E2}n>&+etv;5HA~hyk4^FS!Ty> zl2bwA2&#mHT22+>0GF!Aax3CeAwV2jw2XvAQ~|0$A}E!(@@BIR5mc2(pRBiU=6&cDG*<=uG$HE9*KsAIz3ur#XLkDFXUI@=ecw}DT%6ybZ z=VLr}5a9@qFGr>jR&ca-6UUZAIDWL)BSZ)%mZOkO;uP+yWpO`F;|w0agILAcIbR7v z0V0R>cBoxpcrTIga8de2(%%a8&TD&++6# za2&(OeU20R<0vQbQ~2auVrOSOft6`gn!789rI6@1Sf{%}*R+PtR!fS=ouy94!Nk(J z*(Im+b=u(~ZOx{5d3T>0Z z*&qy=DqU8(oS$_VHK3=iaoo2%y(Zl45!`oKpACmhnC3Si;V{xQX~*p-K)HSG@+Ss- z!ERIo)-OSDowPY&LgDhjBTZvgM4>Qgm(spYHVF&Z063x8y1RP6vTR6V@)9%Hg7!jV zTU@gVZ_$QGH7O$oqn&m`BzLnS3WN$Yg2b=h{`eG@*r*(1a=yj8?i`0rA4d#qm zuE^9a3e%9UTV{O~?vx76FY^|$;Zvs;WiFF)MA5(Q8t7oUTXy7IDsxJ$Rg*K>W)_uB z{)e%+#7F%{A=0PM;)TUy+NtMWKlAF+XJ{L8NIdvS`Q&cJc9`iN)1Y1OQp?V|NIY%R zM#m!0vXejp&kMN%{8l6s3jK(V{1uLd!rKVkRzHeAb0NApIPED3FO~NCGG70HH!ybT z<=X>?y^-`iv| z3UE-mv#8+Z$K~(9v^P9`<%QeBN4&zJD|5FCGxx?az&0V3s|WVK6zUcBSrwN4Z^2vV a!*7N%v8~r42%1kKZb%KklV&I2jQ<8v(o#VH literal 0 HcmV?d00001 diff --git a/popups/__pycache__/network.cpython-313.pyc b/popups/__pycache__/network.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..37bfb98374411db091491b57bab1a6f95819bafc GIT binary patch literal 1447 zcmb7EF>l*O6h4ZiL`s%r$+na#cC1=J5GJ+cA_)ScMN)3usGOM!Kv=^_Z^csvh5x^#uDV#OxHedU8wLm}BH$gLzHg7Dk(^f}UDa zx71~Hd;HW`btY7e>+1hdO~0j@SyRg*B=p%lf+=fGn;&p}O`!6Zp54ZbNNr=c&+L>1 z{*yY{sWlAP)7#@N9ewN7&4}SQyP$;ZpuSa0J z@X&A!3&XDZSr^^6-vF#iq4NWeYHNXzZTvdBk~tch2don8b0g>Dk0t z!3~?KYbsUU7U|V&b_BejRB95(qY6jY-o;ck4X4&5rti|3mTk4#Y40X_#kVj%&*(LOf`Z%^@&&hBg_F#zthqF6=UT%zBZ1BD%OKP4+jV6Q)aH zU&(7RM|do3sFVE*d(n++QUO^=ORNtX?0E1e1FIR1H+QL0w^nN|X?j|e%3b2tc1EU8 z<(}Q}S~NzwhG~0!s&-f-*sS_4jq}SI-R6L&_-L@ll}{LG5={VZ8=n0H*E~W~Z!Kzd z1E|oa!j2{bPlYNLb}(xnOyy7+cve*6mDVD_Z;M9TMxXee%P*-=prvw)ba2_kP{-iA z)PALIpH7WAYXrxK6((|(P4^yyr|2Y||07*|kuC;v?*#e9 z@pobABjTQ(bAo>l# zb3o%8JdK|alsJEnA1WVqNW*XAkAVMq@RGPcGB_0lL3oAouh5m(==$H1Bt*{;hiN2d zg6!oWtp(|YU_KXQ^TA><$QAcLIm%rRvdjB-j|95l5@w>rI%7V)F_TMm;jUcFO7o_j<4N4T=+PYIK6x+7I`WUZ^eaAg)onQ0Q}{E A$^ZZW literal 0 HcmV?d00001 diff --git a/popups/__pycache__/network.cpython-314.pyc b/popups/__pycache__/network.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..058f24d4817e6acd860acdb423a716a48087996b GIT binary patch literal 1490 zcmbtTL2nyH6rT0&+Piif$Bwa!o74_QCEHLtsF8dCQB;Xy7d1rNs$3FVYkO?3V(%=o zvvr(PxP=p(IKU+*RJag{3rBuJL=?J=(gQ+8>MfNcab;%hO~Zi`W6zuK?c4d@n>XLC z%*LaDjps{W_db^a_>()qn45xeS^!`V)PXM4Kvf_@p5st0R27NnW3eVxWg`1ns)ef& z5~(Ugsj5WHgDqtV=&~NxBlS65(bX+kkG`D?k^sQ`*kxaz*%CL>ui^n>Eb%o~2_I!a zpRK49yrnLIwFwMzCH!2fuKbT&bC+@@gIpz%#Pn1akQsfx2(vp7{24L(kO8T0 z?2gzv)5Y%NCfaGV4ZN+1RNX+fYu|^KOXC-?M&N*^F5CyuHn2euzUyWf?fCH9p9+P- zIwV7cw+r_T%jiP9tA1ibtMI_EoQG5*aEDN_(_N)<7ei>#$Q_puWWBSSwgxv_$g*II zn8>;b+a@E{RH`~XG;EkGgn9p{)Ik=Z3b&5A3#r;NtVRd5T!+r}%xH>7h1_ zb`XYLj9jZt)r%-i43O)^4MQKAxUH!;&ff3~ zz_T1BxJh!ux8f8x^W1P9w5FHNAOKvhBAerVX7<1)vy-tA3ec-{2|NPzHv9dd3!75# zd@~#%J@hZ(iIvDCVhfLijj6nXcllZubg8~yKh@>Rlr0bRFcU)LMVp1Y9e@0Fn<~xj zYQsStqJ^n!Bd4)5Sw>VInr+gf5o8-JlZ>c3V45(Q5F8ri&6{-hp(fE7hJAyWk70u# znz-aNng*uAhzdLFnvD5jsM3WNWZHpL_PH?omf79%yVgRO_lt)6#t6B@;cdEJprvvT z4PdziaT8+KDVLZcI%c=*3%P7_wK`>MqP>zmqBB#>q+q#tnUk>T++k)9oF;Qmlf_?? zMKArjms_0v7xJgqil_ONC-+YBmGgv>%It^FQb0|78T%r3ARMm!eCwCue(X2p?wGNF zbuCVnzS;CEnA98(+!Ft+Fz-1{V(64e4-VMULVbjt$zikpz6Sdsu;G;a1C;z)d4n*& zSgi2c+4^Of;yv!R1m6Obhw|M=D^kejg=+Uj( z@$$W+g^$nYqw?&_43M+O(v{z(q|Z^Daxel$^Isf??;bDTK3e#|r#QQMB@)_`&)1^D JM}mKje*orZe98a- literal 0 HcmV?d00001 diff --git a/popups/__pycache__/powermenu.cpython-313.pyc b/popups/__pycache__/powermenu.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..385880dfe8f238ec8f49e04de640a4d7b3d30ab0 GIT binary patch literal 1678 zcma)6O>7%Q6yCMH8~@aCVkb@sPT3YzY>MqrR6(Sw792=ww^f^MPdTjC_IP)d^^TdH ztz&ZumvRQF2sl;Z0tf-(1}Cn)IfR2XY9*=)B%es-N=Te|v$2EKG$K#C`{vEg_wBs* z&D&u-9u>g;IQ^-a4-3L$9*iw88Vt|F;I5zws#p=qA`uln4pai=APM^8U?o(Rh*Xw| z3~^{tAXA2vlLwq2C$f;OcRS!u7{t3>#amikC!4tdOI^n;ueFYBom#ii6}^K!$|C;c zO|(U8BFB$x0eled;m@>6mI68s{(+T7!?!<6 zEL=5~H5WIi$2I_{nCJ|!=~Q#D8wa&iVVp7|xQb3qm( zqn^|#W{HVFtLlU$OskGG>}b?#B4}CoAO{j&0m6HWa1oj0w)D2cLavG1(CpIt)UqKR z+i!E1ORFesxoF}hD%KEzd_A{VfW0&eFwkcc0F0FFmq1rpxE z?kjL4wuJ|Ar5}#>r)T?#WPc|0R2Jrxd(oZLxqf;Pf=KfB*>rz?p`T3klk@ya&Gnbg zejEBe`c1U==8fI#dM~5)mtKC5K6CG_o%FdU^RYTY(em$^IwUf4i}AW8(UfvPFNP z27N?yx8Q41pcvbm7Tp)#*PLtdwLr4q$XR%>@Wm#sdp5d6@(}W#b<^7%Q6rT0|_^*x=JF!b}soPXlOd7jDQ8-k!;6QBBR_Rjp6nV8?kG-p`cg*Z; z9h-}}lrtOE5vC^Q!KuQ6c&YCsjLpdt|AJeNb&P(>tSAd6M0A``iy5am2j zr7R#}RbBuEW~nKZDee^28Phd zFE8^+zHWP%N`&1(D!2GXA&PmY&`1+G#I^d6${plty%F1@a@T4QlPV^(S|*{%Hu7Ak z8MN=Onfvx{jA(i{d>BDx)7sg zlZqHN3JOj#U+^>!HHvD%RJIm6;@iFxptHzMW*qq5u4yAG9H6Lr}OA-_i~n?+&= zZ-dKQ{BoXNEt#kdO9sR&RL?CHnHx>3RSFK5I{Z;_8Do*Pq2m=hEQVmla_@L}ff?cb z?p|WuJphjrc|Vfyr)KBB7~4%R`I&h(iKc#?$@p_wKb7`VbNoxs`sXfx zBYhY9I=21Vot;H>JNK@CZsBp};=`5Q%+hc!9*qsp28!7O#mo`KwVlPA+qqj~#W!{{ zS0)t?5sxZXb{4DKxpz)dJnHJ|&SGUdR~;+9zMClohCRy77io2$B@5gxL}Oqc+{6QZTYz9NONKDvf%s@86ZctJ>9~{(J z3t`XTuw~z^459bu;X@Ckle_1ibVF Sy!=;46hgz;4WS|gdj9}SJ+UYN literal 0 HcmV?d00001 diff --git a/popups/__pycache__/powermenu_sub.cpython-313.pyc b/popups/__pycache__/powermenu_sub.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d80918d8c2037d9d538d7a972383237fb74c5281 GIT binary patch literal 2038 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 literal 0 HcmV?d00001 diff --git a/popups/__pycache__/settings.cpython-314.pyc b/popups/__pycache__/settings.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22b2c91240c57eecc9d88f5a145f5fd31bf28a10 GIT binary patch literal 1672 zcmb_b%WoS+7@x6s?Oi9%!+BAs#A_;AHVt+nRq_P^Nke5PB7{vzC6vTk+hcoGduN%Q zZET;+2{>{=h!cWK;6je&zlexZmQi{@s7RF%N|Z=kneiiSC6IFHXU{jkZ)WFv&2MfF zMI(UAljK*`@4^85A&kDndf==L0ce9AV89&6LJAWC2XcXIkOn(An3J+Hl{;9_%*Fdb0O0n(^R_X#9b6xOw%3}gd1?ZqLehugoPeqM7Wb!A}sv!=f zn%Qddd9Xsf2L;^7mrQbB4>ENfJD&4CvON~PfH{ilEOudTAjc#o#pI4Bu0lK8fB1!_ zX=@fGxPol$o>{9orh`aJ{lMY&hH1NZnM6^8vS7I~&*Tb0$Y$Xsk5X(eFvad2)m5gt zRlJ+Gir6ONj+j)&Hf4&?uGKxfoyiABnopbEjBU1I9R zE*q|6&qetn&#Pb-@4Mb)H|?Llatl*<>MJ8n?YM@yXxcf5X4{Q1nc@ zjqS2kX}edL)uvtPbbs!mHLY3KjC%oV zdXy*D!Irl}>fe<@tjkCeK!6pAy~ebccwm zC5kvNyNE-u zFmwup8Uk|6k0<GuT1;#89#Z$pT4mtA5EwH`1NmhkK$=RIlH&~)5?!4`>n&d zr33Bu`B+4bolXLI>QH*+H)*uv;?2vsXdKSHd!Q|zkE!x9JRN`0$Lyti9ArK^oZCFm Y?g=L!!;^*A)IeK4&%y{?f}L*s3yEf|+5i9m literal 0 HcmV?d00001 diff --git a/popups/__pycache__/start_menu.cpython-313.pyc b/popups/__pycache__/start_menu.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2f19faf47f7f4f48112b954c7ff6a6f3bb4b9db GIT binary patch literal 3560 zcmcInZD=FM8D4#VSdwkYmTdWaT04$?I;)a>>7{j&JCVD@cPI7I#FJ}ETq;)5Xtj8? ztIY2D3kugjIVgqx%5e>aUdf+JxX>%~$5BduG`|X^*yomP1{+M1;yME5VtSX5RUF=b3ljXWrfYa5yMH`f%)eeK{cre`J+za(0CM*FktgKmu|Ug@S`P zL?%0n&Vq}$xa=yr3m)R(vb*Rl_=vCIC;mc!1PVbC z^pQR;`-}aBD2Z}8P>dA@$N}GVrm~E`BCY9`NrSxcQbUq44RHBoylT=OF22&9>YyQ+Sj%O7wN#bJ5-9Z> z*fbSQHb4$(*j&-cQmKJ83+g>heFYO%DV>6A?&0HC|&U1pKvqtEk5kK z0)|9Rl?CYnS7~Aw7LuPd{LYq8J*0a&XRv;&YzGaCa6;pdmDjl4t}SgZ@!;9HkCv5INcd z^kW~ZkMk8CTa0w|E%bGzgK}Iz6Q?_tnQ@@U=OTyJpF~sW#1XC^Jy^FZflUUw@~7fR zqmyVF&MR|7cKBo*@kiqL0@!2tbH_3CNE}(PNA7dSk$fbM8Ne}ngg8FgKTmzCUp#@L zhxqVC?(e73ljuw6%fQCDIe#vIo|+42jw_CZ(e7>zO7t|a>KS;>%(+ED7Sd;5P`(LV zXkCXfa*d}y=U<*DjAPVamS;+aUNhwK38?8eF@Y)WCAcIejhu!Ef{O-Q(Z+>q~ckneTvlFE79O1D0BO>eY#)$#^}A zD|KCb1*;W+DNbBL&o8V;zS_i^h$PJr&*^Hl#W>jdH)H@PSknoXiEe3C>Z&xV)MM1K zs#14N*G#|wxuL9M!uUeHMuq6An)Z+wR0-2V23f{>2|E!BZeL;l&Ub&-8V5oC=e^72 zzOQd~aek1O-+OT48{hxc@4GF(2ILK;T2(RZ8*3&e)NQhG;i}0q)FTs&HR>wMr>O6o zWtzG+yFO}|SZatoe=3*LVQ;yzA~%(aX%U=`kVyt+jAaKXIY0)L1}c(Ps_7NWpuXjj zq$;vTef5seSBLX}rn(8S$E@pB!aRuv9yZfpS*k2`?A6=QEd!S-lB%*+!(svGAFsdL zVynsj`Q2)3a`sR8wn=_^{YHy%UH;ok>=b{Tm(yT}2Pb~h>A!R9TE}+ZyCF}_`fne5 zbvMry{{6;X874ydOCCa@v}}RPnQ#u&3x*<+!EAM^#X?X==k2_FCiwna%`<=Kn)&T~ z+TXT|2*dcMK5n6h=LjPYtmV_2QbjS>XsoVOaY@%o?LDhlTE;X|QFSl?v{qE5vP45= zom4R?ad##&uoU%oz#1(UcUsIs@Z!)zI`0f6Q>VR$Dy6bSsAG*fR@1%2%doP25f*z% zfMv|dXygLcUIa%xuQwXNGQt8eVV(!x+mu!`@LNl(n2N5E9@fQzEMbw6gxDarQP)?} zPQt)YugYIr3`ToVq_h8%-%a+u!?{hEhiF!|xkiIP^-h&s0xitzjGu$PtHO3zwEbZ_ z(r@=h?Y@|OOtcfn?7{eUEOj%u8JqsdD})of@q`V(MAD8A+k?Z~iN|ivZziVg;jiuW z2f~q$288g?ZhXX!C+!5}FktlkMCM*1vz?m0b#XIw+8)Z-$>a8L+8#~W$x(YGwb$z( zitdGbVfK?kIKDeLVh_VqHk+$SvhhPA`;AR)k4)Y2Y>s5Nlan`BHj}w-eRp8=hvtvg z-(24~d1)(ExbJd~MfXxs&^gIH~%2pBdzV z!s)Hd{Dyep=oC(EWzKDg=Z{WdW-D`HL%eucVcUlMbkn5m~B#X zK6BPGQqz8jn_07t8&I(5M+{DT+S}$^w)<_s*-Rsy*3MT#o6`fG#)D4=h+!r8w b5D^^HJHo_{FtsCOc7*YLZ_Vk*bA|r}7}$r~ literal 0 HcmV?d00001 diff --git a/popups/__pycache__/start_menu.cpython-314.pyc b/popups/__pycache__/start_menu.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f67f6b8da3fad1d0f019e3852fd50d8cbcd28fa GIT binary patch literal 3762 zcmc&%ZEPb)8Q%4`9e*TtVkgcec5>I`d@=UrT8IR+?%lOH`>t2gZMmWs8h} z?(A&7B%}j@PC`ihJ6$Uz4)Nz&UDZS4#|a^R&|gRhg@(}WD3Kzdh!Y4-#3v+vAZGR> zF{p5-s^xZkmkQ(U%i@C_=#Xv!~vbxa0K@nvf&Pr?$?SL<-S))dhS zH5$+W#i|`!{edoA1Bi8MXK2^ofvSulYVfwxSaX8@-1K)a>17UF zK74kC6S(SiffrnYyUHy)j^+eUKFuatWtq;hN|ia>OaEMbV}1Q&1-}qj@Z0n3mg9t= z5E2H2@N&Shv}A_c=~?YmKZ3}=S&dSJvFdR3AG95G>(`#oT0v){nSt%viKnp zKS@#MJ^_>;&YDvXK{qGF`na0!=VUb`d`2h;MY_A3Ua)tULQt4r2wL&!@>lq!2`5%Z zZI$kMRd>}boLTT#TDrkMPPg_aOKnWN^Z>za;^~Om3h_0d0(C?JEqcnRVh2cJ*W9(_`@bwncPGF3rx))2v}A*N zrzE}p8Y}lo#0Qb6N?4kANiT>j3_b{x&mDb$YKY$|{*6sQ;=;=$}Vbho6_#48~H zRN}5nXNdotjxki{Hzza<#7@?%KVK|Dx|m{Jmb!8S>j>ncXqthUWYrNshTKF+f(=ov zwO~Wnh<~*vDzc;!f7=!Y+cW@F)yDK}#ckL`j6EcHs3xJh*jTY$73)A<1GR>zC`_yA z?1262*WZ%<`Rnz2GxNVMS&sPO^&8TkUtq!deo4yF)jv4-3;WTXTi0x7efx$qJ0G}x z?3MkJY346C?n=}eEq^T0172L!X=Y*Sc;ch(L88VybjCJ!yCgjsdhZ9_Cx7es^^H<4 zU^#*%zPXD3bje}N=*T1Y_+eLU$asS!+Hw=rpjxvQsQ_^mkZ41Jbj4KLP~^r63D+TN z0#q{-7Uk)yNWjLa(fiK;vadch3`wl7Q>%-J@Ee3*e?AvOJ_eCp1`xX~AVym=k4W?a zP?u<~orj$cr4M4{Ml88#f_B9Bt~a{ zj*BGsN5+jYI+e{f&7|4*(JcF)n%y0rz2)5*FYKmgZm#X5i%x%kc;dVGdz-IsZk?)Z zXD;4%yQbp3Oq|-BF>R(lf=y%U)a_^9zWnCppPbyzpWn(p)5{D}gVP@wZv6g)C&5L)wx}AS^E4$claKadQ z%n6_qA1iOty+kMyyv%e`}4=z2%e_z zeS<@1X@g1(L*R=Y*whv96nc(!*;QRD((1mO=lMM@y~j;{z-9jK^75_&j#dw%96z_m Vo!H}M_qhBXH{J8KTztti_%96_fNB5$ literal 0 HcmV?d00001 diff --git a/popups/__pycache__/volume_notification.cpython-313.pyc b/popups/__pycache__/volume_notification.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..058b48e54e616d52915143e0182def0432717def GIT binary patch literal 879 zcmYjO&2G~`5MC#?6FW)M{5OE0fKcU1BycRM$cal@(Fg;lNR|_?9V?DEtnIYnRE|6V z6>#bi9)U;T*qgNy2ZTV%iAFteW!4E2v#Z%}W@o>hpQ}PaMIb+KpN*U}LO;0iPpWHh zbqe4e(vdD4psqlLZH`k1scxF4yCN045|y@5KYa)3X;@-k&?O1p^hNN?X6Nn~YDZKw3=oagsA@)N|HHC>G=gr#04+Hl#W~xmnb|{H_i%C-w z7$>V^&g2nxol(f7W7i5tEHg7q&vks38LfdlBVIJY>)*a~*5GT0i32af3jy+3WI<^v z6Mb`n=_VMo#8ri>GANcG5|2dG7+{Yav(mr>NGQ~6Oib#yJ`@VBB-b2x#&EGp9Q)Qf zM3esG%aEx#bi+AQ2j+0Rt}Hu*dMWXOr&@-UC=OcT2v6YPpVNTy7P4!vLf69v^oN>3 zYsx3n3JLMXZb(I*FP|`#d8qR8=+NjL^0mv+Q97FuG3C9 z+X-3FUNy|{N$A?{5N1yN)^yIY`x9bC9)3(UpyF){-h#SJ3xexO@KOqOlv7D|}b#ak&;(Z@^wwR7G9Zky4FI uP3~Q7h-ltBw=bIaJlV=fKT89Dp@13KnSF+Xw^z=nQ>AHuE%%ooVoYRecZ`qHNfQO`tiUK0rE?s4~67dwJ3-Cen+0iYwT z16}U!FkJZ;?{c0b`%=29YuoB9q9Zf!w4TvLc9Uf>x&LB>ZYqw;Z^xMfP*3O!+wv@y zD0*Q7)E3Y9u_n-rgTqM=9U7L2hc%HF+h`n)b?BMIoxnYFib6uw7<>*72~Ef5Gtadk zcI;SwHEF8n+&2PblaRFt{66|fMcaAce0b00tVxxTYgrzoN+0?J;+je&!r4+b@=4&n zhE%f=h7LxdZ&7)S0^@MDO{p?~t}`H1K5#8Epvj40damQsIDhR+b8dWX zQmN;Ku%=PTH%AcXSZX-WRe`Gorf8;*JQQN12R(E^3q6yW2(#LZk%=AGXO%H1&M?i8 zA-kBR3w>+uQj`DTOQNyacgd7$J+nWY*OThADlzhcr?n(4U>MZN0FKzkAIAaaJ)%2BBjRW?o@pH{Y_#UfwDa + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/images/normal.png b/res/images/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..505e12c93392262f693add945b7fdf9e671f7dd7 GIT binary patch literal 3895 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 literal 0 HcmV?d00001 diff --git a/res/images/standby_rotated.png b/res/images/standby_rotated.png new file mode 100644 index 0000000000000000000000000000000000000000..1e6536775ca4cfa88f0c7037a21bc4906f5c1b8f GIT binary patch literal 488 zcmeAS@N?(olHy`uVBq!ia0vp^wLsh`zy>7uI%Ki{Db50q$YKVD7&j1R6q(}e$iToj z%hSa%q$2L^WkbdW1BL?z4_tY)CLUg+kXh3aF?luHuKv$E?`NOmjkxwve1pzn=Z1@l w2VMvylyDeWu}H`=_3$yewKI$g(;`gd%b8hUYKiWD1&lfdPgg&ebxsLQ0JM*jQvd(} literal 0 HcmV?d00001 diff --git a/res/images/standby_rotated_.png b/res/images/standby_rotated_.png new file mode 100644 index 0000000000000000000000000000000000000000..556c87f20efa65fcdd61a20c6d9bdd02bc0d3fb1 GIT binary patch literal 5174 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 rechtsklick_intervall: + mouse.click(Button.left) + letzter_klick = jetzt + + except KeyboardInterrupt: + print("Skript beendet.") + + +if __name__ == "__main__": + shift_spam_mit_rechtsklick() diff --git a/res/scripts/autostart.sh b/res/scripts/autostart.sh new file mode 100755 index 0000000..2ffa68a --- /dev/null +++ b/res/scripts/autostart.sh @@ -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 diff --git a/res/scripts/keybinds.py b/res/scripts/keybinds.py new file mode 100755 index 0000000..2c0fcf8 --- /dev/null +++ b/res/scripts/keybinds.py @@ -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...") diff --git a/res/themes/__pycache__/colors.cpython-313.pyc b/res/themes/__pycache__/colors.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5ccfb8b94a4157cfb488c5f91263085b144a94c GIT binary patch literal 772 zcmXw$yKd7^7=VxKIQOL2q$%{4rUkOpcgL0x8v_f=nV2G9j-3c6PEX=O+1N66fq^&R zjgY5Gh}|eJ!2ieMSo!>YzmAV=Wz%Za39LW;?~@-QA-^&HFX-*s+{5h~Q3xee7z=cV ziog<;fMr?%R%s1brww3}wt#KA3*4jozyo>+Jfa<7mmULq^aS{fJ_nxCGvGPB0AA8Q z@QMx;;jO?)bUnPus$QgwCoT`ZR0e9GDVkc0mPxpRk*?{A7G`zaHBb1=jbW(Scx9p& zskp`0ZQa7spiISZ3=>O-vMkd#4J-p{Ctt#Bj4^1`5l{4=j$8PdFGk4t$3camMD*j_r(8H!Hb! zsS9b@PGCWLaf*){rfu4GR$ioh5#zv9EzP#x4~485@$}wHK92*JfBZ={_z2QALL4e=#36P6| zAmr7~-p;R5`>~dbU{7011U!uR-?*BUk K|JvkT_{e_`+s~^2 literal 0 HcmV?d00001 diff --git a/res/themes/__pycache__/colors.cpython-314.pyc b/res/themes/__pycache__/colors.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3169ef96e097f3a3973ca5ea3684883d0d2adec GIT binary patch literal 774 zcmXw$%Wl&^6hOyyoOjY^(iHki(*hAv{fO<@5@N%O4U5^asyz1CiR#4ZB!-q1VwGiA zEcgTd2zgZru{Y`uaBnTf%JDf@$gPW`tQCcg+(q^I{kp z4qlneA|1E*BWGk|8BmsHx~7FSg0gMPw@fS(YV6yg4HZTjRLDZzA7kMuoCS;-Sa?cc z`KG61;VCTexI4;Z!SOA9-!SzcD<{I6M;tfXR-o~$e8=NB`HX|H>%w;^k>Gp|?KeNEvhpGoix>w?w++YsFqE=pB+^eTxxETJas87#;y;$@ z>Li(Q)#rk7kuKFsKbeQqNWEE2W3CFmR96!|gPWhkiCA7P?y~BeSrVi%e=B-$f&{T5Tzq1Ov