From 5fb19f10389b70ee2389755106092a9ef6c64598 Mon Sep 17 00:00:00 2001 From: David Moc Date: Sun, 24 May 2026 11:48:07 +0200 Subject: Go --- .emacs | 647 ++++++++++++++++++++++++++++++ dunst/dmenu.sh | 11 + dunst/dunstrc | 107 +++++ kitty/kitty.conf | 190 +++++++++ kitty/ssh_picker.sh | 202 ++++++++++ polybar/config.ini | 456 +++++++++++++++++++++ polybar/config.ini~ | 168 ++++++++ polybar/launch.sh | 41 ++ polybar/launch.sh~ | 11 + polybar/scripts/awake-status.sh | 17 + polybar/scripts/awake-toggle.sh | 122 ++++++ polybar/scripts/bar-mode-status.sh | 18 + polybar/scripts/bar-mode-toggle.sh | 48 +++ polybar/scripts/clipboard-menu.sh | 45 +++ polybar/scripts/display-brightness.sh | 39 ++ polybar/scripts/display-status.sh | 17 + polybar/scripts/display-toggle.sh | 40 ++ polybar/scripts/dunst-status.sh | 26 ++ polybar/scripts/kb-status.sh | 26 ++ polybar/scripts/kb-switch.sh | 36 ++ polybar/scripts/kb-switch.sh~ | 17 + polybar/scripts/mic-status.sh | 21 + polybar/scripts/mic-toggle.sh | 13 + polybar/scripts/music-status.sh | 38 ++ polybar/scripts/net-speed.sh | 64 +++ polybar/scripts/polkit-agent.sh | 29 ++ polybar/scripts/power-menu.sh | 33 ++ polybar/scripts/scratchpad-status.sh | 18 + polybar/scripts/screenshot-menu.sh | 73 ++++ polybar/scripts/temp-status.sh | 36 ++ polybar/scripts/updates-action.sh | 16 + polybar/scripts/updates-status.sh | 17 + polybar/scripts/wg-pc-status.sh | 32 ++ polybar/scripts/wg-pc-toggle.sh | 82 ++++ quickshell/gruvbar/README.md | 34 ++ quickshell/gruvbar/launch.sh | 10 + quickshell/gruvbar/scripts/action.sh | 83 ++++ quickshell/gruvbar/scripts/status.sh | 229 +++++++++++ quickshell/gruvbar/shell.qml | 631 +++++++++++++++++++++++++++++ sddm/10-gruvbox-void.conf | 4 + sddm/themes/gruvbox-void/Main.qml | 331 +++++++++++++++ sddm/themes/gruvbox-void/metadata.desktop | 16 + sddm/themes/gruvbox-void/theme.conf | 3 + 43 files changed, 4097 insertions(+) create mode 100755 .emacs create mode 100755 dunst/dmenu.sh create mode 100644 dunst/dunstrc create mode 100644 kitty/kitty.conf create mode 100755 kitty/ssh_picker.sh create mode 100644 polybar/config.ini create mode 100644 polybar/config.ini~ create mode 100755 polybar/launch.sh create mode 100755 polybar/launch.sh~ create mode 100755 polybar/scripts/awake-status.sh create mode 100755 polybar/scripts/awake-toggle.sh create mode 100755 polybar/scripts/bar-mode-status.sh create mode 100755 polybar/scripts/bar-mode-toggle.sh create mode 100755 polybar/scripts/clipboard-menu.sh create mode 100755 polybar/scripts/display-brightness.sh create mode 100755 polybar/scripts/display-status.sh create mode 100755 polybar/scripts/display-toggle.sh create mode 100755 polybar/scripts/dunst-status.sh create mode 100755 polybar/scripts/kb-status.sh create mode 100755 polybar/scripts/kb-switch.sh create mode 100755 polybar/scripts/kb-switch.sh~ create mode 100755 polybar/scripts/mic-status.sh create mode 100755 polybar/scripts/mic-toggle.sh create mode 100755 polybar/scripts/music-status.sh create mode 100755 polybar/scripts/net-speed.sh create mode 100755 polybar/scripts/polkit-agent.sh create mode 100755 polybar/scripts/power-menu.sh create mode 100755 polybar/scripts/scratchpad-status.sh create mode 100755 polybar/scripts/screenshot-menu.sh create mode 100755 polybar/scripts/temp-status.sh create mode 100755 polybar/scripts/updates-action.sh create mode 100755 polybar/scripts/updates-status.sh create mode 100755 polybar/scripts/wg-pc-status.sh create mode 100755 polybar/scripts/wg-pc-toggle.sh create mode 100644 quickshell/gruvbar/README.md create mode 100755 quickshell/gruvbar/launch.sh create mode 100755 quickshell/gruvbar/scripts/action.sh create mode 100755 quickshell/gruvbar/scripts/status.sh create mode 100644 quickshell/gruvbar/shell.qml create mode 100644 sddm/10-gruvbox-void.conf create mode 100644 sddm/themes/gruvbox-void/Main.qml create mode 100644 sddm/themes/gruvbox-void/metadata.desktop create mode 100644 sddm/themes/gruvbox-void/theme.conf diff --git a/.emacs b/.emacs new file mode 100755 index 0000000..12a58c5 --- /dev/null +++ b/.emacs @@ -0,0 +1,647 @@ +;;; init.el --- Personal Emacs config -*- lexical-binding: t; -*- + +;; ------------------------- +;; Basic UI +;; ------------------------- +(setq inhibit-startup-message t + confirm-kill-processes nil + ring-bell-function 'ignore) + +(tool-bar-mode -1) +(menu-bar-mode -1) +(scroll-bar-mode -1) +(setq backup-directory-alist `(("." . ,(expand-file-name "/tmp/backups/" user-emacs-directory)))) + +(setq auto-save-hook nil) +(push '(fullscreen . maximized) default-frame-alist) + +(setq-default + indent-tabs-mode t + tab-width 8 + window-combination-resize t + history-delete-duplicates t) + +;; ------------------------- +;; Package setup +;; ------------------------- +(require 'package) + +(setq package-archives + '(("melpa" . "https://melpa.org/packages/") + ("gnu" . "https://elpa.gnu.org/packages/") + ("nongnu" . "https://elpa.nongnu.org/nongnu/"))) + +(package-initialize) + +(unless package-archive-contents + (package-refresh-contents)) + +(unless (package-installed-p 'use-package) + (package-install 'use-package)) + +(require 'use-package) +(setq use-package-always-ensure t) + +;; ------------------------- +;; Theme +;; ------------------------- +(use-package gruvbox-theme) +(load-theme 'gruvbox t) + +;; ------------------------- +;; Completion stack +;; ------------------------- +(use-package vertico + :init + (vertico-mode)) + +(use-package orderless + :init + (setq completion-styles '(orderless basic))) + +(use-package marginalia + :init + (marginalia-mode)) + +(use-package consult + :bind (("C-x b" . consult-buffer) + ("C-x p f" . project-find-file))) + +(use-package savehist + :ensure nil + :init + (savehist-mode)) + +(use-package embark + :bind (("C-." . embark-act) + ("C-;" . embark-dwim))) + +(use-package embark-consult) + +(use-package corfu + :init + (global-corfu-mode) + :custom + (corfu-auto t) + (corfu-cycle t) + (corfu-auto-delay 0.1) + (corfu-auto-prefix 1) + :bind (:map corfu-map + ("C-t" . corfu-next) + ("C-n" . corfu-previous) + ("" . corfu-quit))) + +(use-package cape + :init + (add-to-list 'completion-at-point-functions #'cape-file) + (add-to-list 'completion-at-point-functions #'cape-dabbrev)) + +;; ------------------------- +;; Icons + UI polish +;; ------------------------- +(use-package nerd-icons) + +(use-package nerd-icons-completion + :after marginalia + :init + (nerd-icons-completion-mode) + (add-hook 'marginalia-mode-hook + #'nerd-icons-completion-marginalia-setup)) + +;; ------------------------- +;; File browser: Dirvish +;; ------------------------- +(use-package dirvish + :init + ;; Makes ordinary `dired' buffers use Dirvish automatically. + (dirvish-override-dired-mode) + :custom + (dirvish-quick-access-entries + '(("h" "~/" "Home") + ("d" "~/Downloads/" "Downloads") + ("c" "~/Code/" "Code") + ("e" "~/.emacs.d/" "Emacs"))) + (dirvish-mode-line-format + '(:left (sort symlink) :right (omit yank index))) + (dirvish-attributes + '(nerd-icons file-time file-size collapse subtree-state vc-state git-msg)) + (dirvish-side-attributes + '(vc-state file-size nerd-icons collapse)) + :config + ;; Preview images, GIFs, PDFs, archives, audio, and video thumbnails. + ;; + ;; Recommended system packages: + ;; Arch: sudo pacman -S ffmpegthumbnailer libvips poppler mpv imv + ;; Debian/Ubuntu: sudo apt install ffmpegthumbnailer libvips-tools poppler-utils mpv imv + ;; + ;; Notes: + ;; - Images preview automatically at point. + ;; - Videos preview as thumbnails; open them in mpv for playback. + (setq dirvish-preview-dispatchers + '(image gif video audio epub archive font pdf fallback)) + + ;; Open media asynchronously so Emacs/EXWM does not block while mpv/imv is open. + (defun my/open-file-externally (&optional file) + "Open FILE asynchronously using a suitable external program." + (interactive) + (let* ((file (or file (dired-get-file-for-visit))) + (lower (downcase file)) + (program + (cond + ((string-match-p "\\.\\(mp4\\|mkv\\|webm\\|mov\\|avi\\)\\'" lower) "mpv") + ((string-match-p "\\.\\(png\\|jpg\\|jpeg\\|gif\\|webp\\|svg\\)\\'" lower) "imv") + (t "xdg-open")))) + (start-process "open-file-externally" nil program file))) + + ;; Dired's `!' runs a foreground shell command unless you add `&'. + ;; This keeps `o' as a non-blocking media opener. + + :bind + (("C-c f d" . dirvish) + ("C-c f j" . dirvish-side) + :map dirvish-mode-map + ("q" . quit-window) + ("TAB" . dirvish-subtree-toggle) + ("M-t" . dired-next-line) + ("M-n" . dired-previous-line) + ("RET" . dired-find-file) + ("o" . my/open-file-externally) + ("SPC" . dirvish-show-history) + ("a" . dirvish-quick-access) + ("f" . dirvish-file-info-menu) + ("y" . dirvish-yank-menu) + ("N" . dirvish-narrow) + ("^" . dired-up-directory))) + +;; ------------------------- +;; Which-key +;; ------------------------- +(use-package which-key + :config + (which-key-mode) + (setq which-key-idle-delay 0.5)) + +;; ------------------------- +;; Git +;; ------------------------- +(use-package magit + :bind (("C-x g" . magit-status))) + +(use-package diff-hl + :init + (global-diff-hl-mode) + (add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh) + (add-hook 'prog-mode-hook #'diff-hl-mode)) + +;; ------------------------- +;; Project +;; ------------------------- +(use-package project + :ensure nil) + +;; ------------------------- +;; Browser / URL handling +;; ------------------------- +(defvar my/browser "firefox") + +(defun my/open-browser () + "Open the external browser." + (interactive) + (start-process-shell-command "browser" nil my/browser)) + +(defun my/browse-url (url &optional _new-window) + "Open URL in the external browser." + (start-process-shell-command + "browser-url" nil + (format "%s %s" my/browser (shell-quote-argument url)))) + +(setq browse-url-browser-function #'my/browse-url) + +;; ------------------------- +;; Dvorak-friendly movement fallbacks +;; +;; C-s and C-r are left as normal Emacs search keys: +;; C-s = isearch-forward +;; C-r = isearch-backward +;; +;; Dvorak movement is available on M-h/M-t/M-n/M-s: +;; M-h = left +;; M-t = down +;; M-n = up +;; M-s = right +;; ------------------------- +(global-set-key (kbd "C-s") #'isearch-forward) +(global-set-key (kbd "C-r") #'isearch-backward) + +(global-set-key (kbd "M-h") #'backward-char) +(global-set-key (kbd "M-t") #'next-line) +(global-set-key (kbd "M-n") #'previous-line) +(global-set-key (kbd "M-s") #'forward-char) + +(use-package avy + :bind (("C-:" . avy-goto-char) + ("C-'" . avy-goto-word-1))) + +(use-package repeat + :ensure nil + :init + (repeat-mode)) + +;; ------------------------- +;; Typst +;; ------------------------- +;; Requires system binaries: +;; typst - Typst compiler +;; tinymist - Typst language server +;; +;; Arch/Artix: +;; paru -S typst tinymist +;; +;; Then in Emacs: +;; M-x treesit-install-language-grammar RET typst RET + +(use-package treesit + :ensure nil + :init + (add-to-list 'treesit-language-source-alist + '(typst "https://github.com/uben0/tree-sitter-typst"))) + +(use-package typst-ts-mode + :mode ("\\.typ\\'" . typst-ts-mode) + :hook ((typst-ts-mode . eglot-ensure) + (typst-ts-mode . visual-line-mode)) + :custom + (typst-ts-mode-watch-options "--open") + :bind (:map typst-ts-mode-map + ("C-c C-c" . typst-ts-compile) + ("C-c C-w" . typst-ts-watch-mode) + ("C-c C-p" . typst-ts-preview))) +;; ------------------------- +;; Leader-like keybindings using C-c +;; ------------------------- + +;; GPT +(global-set-key (kbd "C-c a a") #'gptel) +(global-set-key (kbd "C-c a s") #'gptel-send) +(global-set-key (kbd "C-c a r") #'gptel-rewrite) + +;; Buffers +(global-set-key (kbd "C-c b b") #'consult-buffer) +(global-set-key (kbd "C-c b d") #'kill-current-buffer) +(global-set-key (kbd "C-c b n") #'next-buffer) +(global-set-key (kbd "C-c b p") #'previous-buffer) +(global-set-key (kbd "C-c b s") #'save-buffer) + +;; Files +(global-set-key (kbd "C-c f f") #'find-file) +(global-set-key (kbd "C-c f d") #'dirvish) +(global-set-key (kbd "C-c f j") #'dirvish-side) +(global-set-key (kbd "C-c f r") #'consult-recent-file) +(global-set-key (kbd "C-c f s") #'save-buffer) + +;; Open external browser +(global-set-key (kbd "C-c o b") #'my/open-browser) + +;; Windows — h/t/n/s mirrors your Dvorak movement +(global-set-key (kbd "C-c w h") #'windmove-left) +(global-set-key (kbd "C-c w t") #'windmove-down) +(global-set-key (kbd "C-c w n") #'windmove-up) +(global-set-key (kbd "C-c w s") #'windmove-right) +(global-set-key (kbd "C-c w d") #'delete-window) +(global-set-key (kbd "C-c w v") #'split-window-right) +(global-set-key (kbd "C-c w -") #'split-window-below) +(global-set-key (kbd "C-c w H") #'shrink-window-horizontally) +(global-set-key (kbd "C-c w S") #'enlarge-window-horizontally) +(global-set-key (kbd "C-c w N") #'enlarge-window) +(global-set-key (kbd "C-c w T") #'shrink-window) + +;; Project +(global-set-key (kbd "C-c p p") #'project-switch-project) +(global-set-key (kbd "C-c p f") #'project-find-file) +(global-set-key (kbd "C-c p g") #'consult-ripgrep) +(global-set-key (kbd "C-c p b") #'consult-project-buffer) + +;; Git +(global-set-key (kbd "C-c g g") #'magit-status) +(global-set-key (kbd "C-c g b") #'magit-blame) +(global-set-key (kbd "C-c g l") #'magit-log-current) +(global-set-key (kbd "C-c g d") #'magit-diff-buffer-file) + +;; LSP / Code +(global-set-key (kbd "C-c c a") #'eglot-code-actions) +(global-set-key (kbd "C-c c r") #'eglot-rename) +(global-set-key (kbd "C-c c f") #'eglot-format-buffer) +(global-set-key (kbd "C-c c d") #'xref-find-definitions) +(global-set-key (kbd "C-c c D") #'xref-find-references) +(global-set-key (kbd "C-c c i") #'consult-imenu) + +;; Typst +(global-set-key (kbd "C-c y c") #'typst-ts-compile) +(global-set-key (kbd "C-c y w") #'typst-ts-watch-mode) +(global-set-key (kbd "C-c y p") #'typst-ts-preview) + +;; Errors +(global-set-key (kbd "C-c e n") #'flymake-goto-next-error) +(global-set-key (kbd "C-c e p") #'flymake-goto-prev-error) +(global-set-key (kbd "C-c e l") #'flymake-show-buffer-diagnostics) + +;; Search +(global-set-key (kbd "C-c s s") #'consult-line) +(global-set-key (kbd "C-c s g") #'consult-ripgrep) +(global-set-key (kbd "C-c s i") #'consult-imenu) + +;; Toggles +(global-set-key (kbd "C-c t s") #'flyspell-mode) +(global-set-key (kbd "C-c t w") #'whitespace-mode) + +;; Spell language +;; C-c l is already used by xkbmap-switcher, so use C-c d for dictionaries. +(global-set-key (kbd "C-c d e") #'my/ispell-en) +(global-set-key (kbd "C-c d c") #'my/ispell-cs) + +;; Quit +(global-set-key (kbd "C-c q q") #'save-buffers-kill-emacs) + +;; ------------------------- +;; Eglot (LSP) +;; ------------------------- +(use-package eglot + :ensure nil + :hook ((c-mode . eglot-ensure) + (c++-mode . eglot-ensure) + (c-ts-mode . eglot-ensure) + (c++-ts-mode . eglot-ensure) + (nasm-mode . eglot-ensure) + (python-mode . eglot-ensure) + (python-ts-mode . eglot-ensure) + (typst-ts-mode . eglot-ensure)) + :custom + (eglot-autoshutdown t) + (eglot-confirm-server-initiated-edits nil) + :config + (defun my/eglot-format-on-save () + (add-hook 'before-save-hook #'eglot-format-buffer nil t)) + + (add-hook 'c-mode-hook #'my/eglot-format-on-save) + (add-hook 'c++-mode-hook #'my/eglot-format-on-save) + (add-hook 'c-ts-mode-hook #'my/eglot-format-on-save) + (add-hook 'c++-ts-mode-hook #'my/eglot-format-on-save) + + (setq c-ts-mode-indent-offset 8 + c-ts-mode-indent-style 'bsd) + + (add-to-list 'eglot-server-programs + '((c-mode c++-mode c-ts-mode c++-ts-mode) + . ("clangd" + "--background-index" + "--clang-tidy" + "--completion-style=detailed" + "--header-insertion=never" + "--fallback-style=none"))) + + (add-to-list 'eglot-server-programs + '(python-mode . ("pylsp"))) + + (add-to-list 'eglot-server-programs + '(typst-ts-mode . ("tinymist")))) + +;; ------------------------- +;; Flymake (used by Eglot) +;; ------------------------- +(use-package flymake + :ensure nil + :custom + (flymake-no-changes-timeout 1.0) + :bind (:map flymake-mode-map + ("M-n" . flymake-goto-next-error) + ("M-p" . flymake-goto-prev-error))) + +;; ------------------------- +;; NASM mode +;; ------------------------- +(use-package nasm-mode + :hook (asm-mode . nasm-mode)) + +;; ------------------------- +;; EXWM +;; ------------------------- +(use-package exwm + :config + (setq exwm-workspace-number 5) + + (add-hook 'exwm-update-title-hook + (lambda () + (exwm-workspace-rename-buffer + (format "%s: %s" exwm-class-name exwm-title)))) + + (setq mouse-autoselect-window t + focus-follows-mouse t) + + (setq exwm-layout-show-all-buffers t + exwm-workspace-show-all-buffers t) + + (defvar my/terminal "kitty") + + (setq exwm-input-global-keys + `((,(kbd "s-r") . exwm-reset) + + (,(kbd "s-") + . (lambda () + (interactive) + (start-process-shell-command "term" nil my/terminal))) + + (,(kbd "s-b") . my/open-browser) + + (,(kbd "s-d") . dirvish) + + (,(kbd "s-x") + . (lambda () + (interactive) + (start-process-shell-command "lock" nil "betterscreenlock -l"))) + + (,(kbd "s-q") . kill-buffer) + (,(kbd "s-f") . exwm-floating-toggle-floating) + + ;; Window resize — uppercase H/T/N/S mirrors movement + (,(kbd "s-H") . shrink-window-horizontally) + (,(kbd "s-S") . enlarge-window-horizontally) + (,(kbd "s-N") . enlarge-window) + (,(kbd "s-T") . shrink-window) + + ;; Window focus — lowercase mirrors movement + (,(kbd "s-h") . windmove-left) + (,(kbd "s-s") . windmove-right) + (,(kbd "s-n") . windmove-up) + (,(kbd "s-t") . windmove-down) + + ,@(mapcar + (lambda (i) + `(,(kbd (format "s-%d" i)) + . (lambda () + (interactive) + (exwm-workspace-switch ,i)))) + (number-sequence 0 4)) + + (,(kbd "s-") . exwm-workspace-switch-next) + (,(kbd "s-S-") . exwm-workspace-switch-previous) + (,(kbd "s-w") . exwm-workspace-move-window) + + (,(kbd "s-&") + . (lambda (command) + (interactive (list (read-shell-command "$ "))) + (start-process-shell-command command nil command))))) + + (setq exwm-manage-configurations + '(((string-match-p "dialog" exwm-class-name) floating t) + ((string-match-p "confirm" exwm-title) floating t))) + + ;; NOTE: Your monitor names differ here from the xrandr command below. + ;; Keep whichever names are correct on your machine. + (setq exwm-randr-workspace-monitor-plist + '(0 "DP-4" + 1 "HDMI-0")) + + (add-hook 'exwm-randr-screen-change-hook + (lambda () + (start-process-shell-command + "xrandr" nil + "xrandr --output DP-3 --primary --auto \ + --output HDMI-1 --auto --left-of DP-3"))) + + (exwm-randr-mode 1) + + (defun my/exwm-init () + (start-process-shell-command "pipewire" nil "pipewire") + (start-process-shell-command "pipewire-pulse" nil "pipewire-pulse") + (start-process-shell-command "wireplumber" nil "wireplumber")) + + (add-hook 'exwm-init-hook #'my/exwm-init) + + (when (and (getenv "DISPLAY") + (not (getenv "EXWM_SKIP"))) + (exwm-enable))) + +;; ------------------------- +;; Audio keys +;; ------------------------- +(global-set-key (kbd "") + (lambda () + (interactive) + (start-process-shell-command + "vol-up" nil "amixer set Master 5%+"))) + +(global-set-key (kbd "") + (lambda () + (interactive) + (start-process-shell-command + "vol-down" nil "amixer set Master 5%-"))) + +(global-set-key (kbd "") + (lambda () + (interactive) + (start-process-shell-command + "mute" nil "amixer set Master toggle"))) + +;; ------------------------- +;; Navidrome +;; ------------------------- +(use-package naviel + :load-path "~/Code/naviel/" + :config + (setq naviel-url "http://navi.cdatgoose.org") + (setq naviel-eq-backend 'lavfi)) + +(use-package tasker + :load-path "~/Code/tasker" + :commands (tasker-list tasker-new tasker-open tasker-add-code-link) + :bind (:map tasker-list-mode-map + ("q" . quit-window))) + + + + +;; ------------------------- +;; EAF +;; ------------------------- +;; EAF is kept available, but it no longer overrides `browse-url'. +;; Use `eaf-open-browser' manually when you specifically want EAF. +(use-package eaf + :load-path "~/.emacs.d/site-lisp/emacs-application-framework" + :custom + (eaf-browser-continue-where-left-off t) + (eaf-browser-enable-adblocker t) + :config + (defalias 'browse-web #'eaf-open-browser)) + +;; ------------------------- +;; XKB switcher (Dvorak-friendly) +;; ------------------------- +(use-package xkbmap-switcher + :load-path "~/Code/keyswtch/" + :config + (global-set-key (kbd "C-c l") #'xkbmap-switcher-cycle) + (global-set-key (kbd "C-c L") #'xkbmap-switcher-set)) + +;; ------------------------- +;; Spell checking (Hunspell EN + CZ) +;; ------------------------- +(use-package flyspell + :ensure nil + :hook ((text-mode . flyspell-mode) + (prog-mode . flyspell-prog-mode)) + :init + (setq ispell-program-name "hunspell") + (setq ispell-dictionary "en_US") + (setq ispell-local-dictionary-alist + '(("en_US" + "[A-Za-z]" "[^A-Za-z]" "[']" nil + ("-d" "en_US") nil utf-8) + ("cs_CZ" + "[A-Za-zÁ-ž]" "[^A-Za-zÁ-ž]" "[']" nil + ("-d" "cs_CZ") nil utf-8)))) + +;; ------------------------- +;; Quick language switching +;; ------------------------- +(defun my/ispell-en () + "Switch spell check to English." + (interactive) + (ispell-change-dictionary "en_US") + (message "Switched to English (en_US)")) + +(defun my/ispell-cs () + "Switch spell check to Czech." + (interactive) + (ispell-change-dictionary "cs_CZ") + (message "Switched to Czech (cs_CZ)")) + +(global-set-key (kbd "C-c s e") #'my/ispell-en) +(global-set-key (kbd "C-c s c") #'my/ispell-cs) + +;; ------------------------- +;; Custom +;; ------------------------- +(custom-set-variables + ;; custom-set-variables was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + '(org-safe-remote-resources + '("\\`/ssh:apps@5\\.189\\.176\\.18:/home/apps/portfolio/blog/org/thoughts\\.org\\'" + "\\`/ssh:apps@cdatgoose\\.org:/home/apps/portfolio/blog/org/thoughts\\.org\\'" + "\\`https://fniessen\\.github\\.io/org-html-themes/org/html-theme-readtheorg\\.setup\\'" + "\\`https://fniessen\\.github\\.io/org-html-themes/org/html-theme-bigblow\\.setup\\'")) + '(package-selected-packages + '(avy cape corfu diff-hl dirvish embark-consult empv exwm gptel + gruvbox-theme ibrowse magit marginalia nasm-mode naviel + nerd-icons-completion orderless tidal typst-ts-mode vertico + websocket xkbmap-switcher xkcd))) + +(custom-set-faces + ;; custom-set-faces was added by Custom. + ;; If you edit it by hand, you could mess it up, so be careful. + ;; Your init file should contain only one such instance. + ;; If there is more than one, they won't work right. + ) + +;;; init.el ends here diff --git a/dunst/dmenu.sh b/dunst/dmenu.sh new file mode 100755 index 0000000..ec42048 --- /dev/null +++ b/dunst/dmenu.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +exec /usr/bin/dmenu \ + -p "dunst:" \ + -fn "FiraCode Nerd Font-12" \ + -nb "#282828" \ + -nf "#ebdbb2" \ + -sb "#d79921" \ + -sf "#282828" \ + -i diff --git a/dunst/dunstrc b/dunst/dunstrc new file mode 100644 index 0000000..5bbac66 --- /dev/null +++ b/dunst/dunstrc @@ -0,0 +1,107 @@ +[global] + monitor = 0 + follow = keyboard + + width = 360 + height = (80, 260) + origin = top-right + offset = (12, 42) + scale = 0 + notification_limit = 5 + + progress_bar = true + progress_bar_height = 8 + progress_bar_frame_width = 0 + progress_bar_min_width = 160 + progress_bar_max_width = 340 + progress_bar_corner_radius = 3 + + icon_corner_radius = 4 + indicate_hidden = yes + transparency = 4 + separator_height = 0 + padding = 10 + horizontal_padding = 12 + text_icon_padding = 10 + frame_width = 2 + frame_color = "#d79921" + gap_size = 8 + separator_color = frame + sort = urgency_descending + + font = FiraCode Nerd Font 10 + line_height = 2 + markup = full + format = "%s\n%b" + alignment = left + vertical_alignment = center + show_age_threshold = 60 + ellipsize = end + ignore_newline = no + stack_duplicates = true + hide_duplicate_count = false + show_indicators = yes + enable_posix_regex = true + + enable_recursive_icon_lookup = true + icon_theme = Adwaita + icon_position = left + min_icon_size = 32 + max_icon_size = 64 + + sticky_history = yes + history_length = 30 + + dmenu = /home/aag/.config/dunst/dmenu.sh + browser = /usr/bin/xdg-open + always_run_script = true + title = Dunst + class = Dunst + corner_radius = 5 + corners = all + ignore_dbusclose = false + force_xwayland = false + force_xinerama = false + + mouse_left_click = do_action, close_current + mouse_middle_click = context + mouse_right_click = close_all + +[experimental] + per_monitor_dpi = false + pause_on_mouse_over = false + +[urgency_low] + background = "#282828" + foreground = "#928374" + frame_color = "#504945" + highlight = "#689d6a" + timeout = 4 + default_icon = dialog-information + +[urgency_normal] + background = "#282828" + foreground = "#ebdbb2" + frame_color = "#d79921" + highlight = "#d79921" + timeout = 6 + default_icon = dialog-information + +[urgency_critical] + background = "#3c3836" + foreground = "#ebdbb2" + frame_color = "#cc241d" + highlight = "#cc241d" + timeout = 0 + default_icon = dialog-warning + +[fullscreen_delay] + fullscreen = pushback + +[fullscreen_show_critical] + msg_urgency = critical + fullscreen = show + +[transient_history_ignore] + match_transient = yes + history_ignore = yes diff --git a/kitty/kitty.conf b/kitty/kitty.conf new file mode 100644 index 0000000..bfd1500 --- /dev/null +++ b/kitty/kitty.conf @@ -0,0 +1,190 @@ +# ============================================================ +# ~/.config/kitty/kitty.conf — gruvbox dark +# pairs with .tmux.conf +# ============================================================ + + +# ------------------------------------------------------------ +# font +# ------------------------------------------------------------ +font_family JetBrains Mono Regular +bold_font JetBrains Mono Bold +italic_font JetBrains Mono Italic +bold_italic_font JetBrains Mono Bold Italic + +font_size 13.0 + +# fine-tune spacing +adjust_line_height 2 +adjust_column_width 0 +disable_ligatures never + + +# ------------------------------------------------------------ +# gruvbox dark colour scheme +# ------------------------------------------------------------ + +# background / foreground +background #282828 +foreground #ebdbb2 +cursor #ebdbb2 +background_opacity 0.88 +# selection +selection_background #d79921 +selection_foreground #282828 + +# url underline +url_color #83a598 + +# black +color0 #282828 +color8 #928374 + +# red +color1 #cc241d +color9 #fb4934 + +# green +color2 #98971a +color10 #b8bb26 + +# yellow +color3 #d79921 +color11 #fabd2f + +# blue +color4 #458588 +color12 #83a598 + +# purple +color5 #b16286 +color13 #d3869b + +# cyan / aqua +color6 #689d6a +color14 #8ec07c + +# white +color7 #a89984 +color15 #ebdbb2 + + +# ------------------------------------------------------------ +# cursor +# ------------------------------------------------------------ +cursor_shape block +cursor_blink_interval 0 + + +# ------------------------------------------------------------ +# scrollback +# ------------------------------------------------------------ +scrollback_lines 10000 +scrollback_pager less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER + + +# ------------------------------------------------------------ +# window / padding +# ------------------------------------------------------------ +window_padding_width 8 +placement_strategy center +hide_window_decorations no +remember_window_size yes +initial_window_width 220c +initial_window_height 50c + + +# ------------------------------------------------------------ +# tab bar — matches tmux status style +# ------------------------------------------------------------ +tab_bar_edge bottom +tab_bar_style powerline +tab_powerline_style slanted +tab_bar_background #3c3836 +tab_bar_margin_color #3c3836 + +tab_title_template "{index}:{title}" +active_tab_title_template "{index}:{title}" + +active_tab_foreground #282828 +active_tab_background #83a598 +active_tab_font_style bold + +inactive_tab_foreground #a89984 +inactive_tab_background #3c3836 +inactive_tab_font_style normal + + +# ------------------------------------------------------------ +# bell +# ------------------------------------------------------------ +enable_audio_bell no +visual_bell_duration 0.0 + + +# ------------------------------------------------------------ +# performance +# ------------------------------------------------------------ +repaint_delay 10 +input_delay 3 +sync_to_monitor yes +background_blur 19 + +# ------------------------------------------------------------ +# mouse +# ------------------------------------------------------------ +mouse_hide_wait 3.0 +focus_follows_mouse no +copy_on_select clipboard + + +# ------------------------------------------------------------ +# keyboard shortcuts +# ------------------------------------------------------------ +# clear defaults +clear_all_shortcuts no + +# copy / paste +map ctrl+shift+c copy_to_clipboard +map ctrl+shift+v paste_from_clipboard + +# font size +map ctrl+shift+equal change_font_size all +1.0 +map ctrl+shift+minus change_font_size all -1.0 +map ctrl+shift+0 change_font_size all 0 + +# tabs +map ctrl+shift+t new_tab_with_cwd +map ctrl+shift+w close_tab +map ctrl+shift+right next_tab +map ctrl+shift+left previous_tab +map ctrl+shift+1 goto_tab 1 +map ctrl+shift+2 goto_tab 2 +map ctrl+shift+3 goto_tab 3 +map ctrl+shift+4 goto_tab 4 +map ctrl+shift+5 goto_tab 5 + +# windows (kitty splits — complement to tmux panes) +map ctrl+shift+enter new_window_with_cwd +map ctrl+shift+] next_window +map ctrl+shift+[ previous_window + +# scrollback +map ctrl+shift+up scroll_line_up +map ctrl+shift+down scroll_line_down +map ctrl+shift+page_up scroll_page_up +map ctrl+shift+page_down scroll_page_down +map ctrl+shift+home scroll_home +map ctrl+shift+end scroll_end + +# reload config +map ctrl+shift+F5 load_config_file + +map ctrl+shift+s launch --title="ssh picker" --hold bash ~/.config/kitty/ssh_picker.sh + + + +# ------------------------------------------------------------ +# shell integration +# ------------------------------------------------------------ +shell_integration enabled diff --git a/kitty/ssh_picker.sh b/kitty/ssh_picker.sh new file mode 100755 index 0000000..6ad6e3a --- /dev/null +++ b/kitty/ssh_picker.sh @@ -0,0 +1,202 @@ +#!/usr/bin/env bash +# ~/.config/kitty/ssh_picker.sh +# Launched by kitty as an overlay window. +# Reads servers from ~/.ssh/config (Host entries) and lets +# you fuzzy-filter with arrow keys, then opens kitten ssh. +# +# Dependencies: none beyond bash + standard coreutils. +# Optional: fzf — if present it takes over the picker UI. + +set -euo pipefail + +# ------------------------------------------------------------ +# config — edit these or let them fall through to ~/.ssh/config +# ------------------------------------------------------------ +SSH_CONFIG="${HOME}/.ssh/config" +TERM_FALLBACK="xterm-256color" + +# ------------------------------------------------------------ +# gruvbox dark ANSI helpers +# ------------------------------------------------------------ +RESET=$'\e[0m' +BOLD=$'\e[1m' +DIM=$'\e[2m' + +GB_BG=$'\e[48;2;40;40;40m' # #282828 +GB_BG1=$'\e[48;2;60;56;54m' # #3c3836 +GB_BG2=$'\e[48;2;80;73;69m' # #504945 +GB_YELLOW=$'\e[38;2;250;189;47m' # #fabd2f +GB_BLUE=$'\e[38;2;131;165;152m' # #83a598 +GB_GREEN=$'\e[38;2;184;187;38m' # #b8bb26 +GB_RED=$'\e[38;2;251;73;52m' # #fb4934 +GB_ORANGE=$'\e[38;2;254;128;25m' # #fe8019 +GB_FG=$'\e[38;2;235;219;178m' # #ebdbb2 +GB_DIM=$'\e[38;2;168;153;132m' # #a89984 + +# ------------------------------------------------------------ +# parse servers +# ------------------------------------------------------------ +get_servers() { + local servers=() + + # pull Host entries from ~/.ssh/config (skip wildcards) + if [[ -f "$SSH_CONFIG" ]]; then + while IFS= read -r line; do + if [[ "$line" =~ ^[Hh]ost[[:space:]]+([^*?]+)$ ]]; then + local host="${BASH_REMATCH[1]// /}" + [[ -n "$host" ]] && servers+=("$host") + fi + done < "$SSH_CONFIG" + fi + + # fallback examples if config is empty + if [[ ${#servers[@]} -eq 0 ]]; then + servers=( + "contabo server" + ) + fi + + printf '%s\n' "${servers[@]}" +} + +# ------------------------------------------------------------ +# fzf path (preferred) +# ------------------------------------------------------------ +if command -v fzf &>/dev/null; then + selected=$(get_servers | fzf \ + --prompt=" ssh " \ + --pointer=">" \ + --marker="*" \ + --height=40% \ + --layout=reverse \ + --border=rounded \ + --info=inline \ + --color="dark,\ +bg:#282828,\ +bg+:#3c3836,\ +fg:#ebdbb2,\ +fg+:#fbf1c7,\ +hl:#fabd2f,\ +hl+:#fabd2f,\ +border:#504945,\ +prompt:#83a598,\ +pointer:#fe8019,\ +marker:#b8bb26,\ +spinner:#d3869b,\ +header:#a89984,\ +info:#a89984" \ + --header=" ctrl+c to cancel" \ + --preview="echo {} | xargs -I% sh -c 'grep -A10 \"^Host %$\" ~/.ssh/config 2>/dev/null || echo \"no config entry\"'" \ + --preview-window="right:40%:wrap" \ + 2>/dev/null) || exit 0 + + [[ -z "$selected" ]] && exit 0 + TERM="$TERM_FALLBACK" kitten ssh "$selected" + exit 0 +fi + +# ------------------------------------------------------------ +# built-in picker (no fzf) +# ------------------------------------------------------------ +mapfile -t SERVERS < <(get_servers) +TOTAL=${#SERVERS[@]} +SELECTED=0 +QUERY="" +FILTERED=("${SERVERS[@]}") + +draw() { + clear + printf '%s' "${GB_BG}" + printf '\n' + printf ' %s%s ssh picker%s\n' "${GB_YELLOW}${BOLD}" "" "${RESET}" + printf ' %s%s%s\n\n' "${GB_DIM}" "arrow keys to navigate, enter to connect, esc to quit" "${RESET}" + + # search bar + printf ' %ssearch:%s %s%s%s\n\n' \ + "${GB_DIM}" "${RESET}" \ + "${GB_FG}${BOLD}" "${QUERY}" "${RESET}" + + # server list + local i=0 + for srv in "${FILTERED[@]}"; do + if [[ $i -eq $SELECTED ]]; then + printf ' %s%s > %s%s\n' "${GB_BG1}${GB_ORANGE}${BOLD}" "" "$srv" "${RESET}" + else + printf ' %s %s%s\n' "${GB_DIM}" "$srv" "${RESET}" + fi + (( i++ )) + done + + printf '\n %s%s hosts found • install fzf for preview support%s\n' \ + "${GB_DIM}" "${#FILTERED[@]}" "${RESET}" +} + +filter() { + FILTERED=() + SELECTED=0 + for srv in "${SERVERS[@]}"; do + if [[ -z "$QUERY" || "${srv,,}" == *"${QUERY,,}"* ]]; then + FILTERED+=("$srv") + fi + done +} + +# hide cursor +tput civis +trap 'tput cnorm; clear' EXIT + +while true; do + draw + + # read a single keypress (handles escape sequences) + IFS= read -r -s -N1 key + + # check for escape sequences (arrow keys) + if [[ "$key" == $'\e' ]]; then + IFS= read -r -s -N1 -t 0.1 k2 || true + if [[ "$k2" == "[" ]]; then + IFS= read -r -s -N1 -t 0.1 k3 || true + case "$k3" in + A) # up + (( SELECTED > 0 )) && (( SELECTED-- )) + ;; + B) # down + (( SELECTED < ${#FILTERED[@]} - 1 )) && (( SELECTED++ )) + ;; + esac + else + # plain escape — quit + exit 0 + fi + continue + fi + + case "$key" in + $'\n'|$'\r') + [[ ${#FILTERED[@]} -eq 0 ]] && continue + tput cnorm + clear + host="${FILTERED[$SELECTED]}" + printf '%sconnecting to %s%s%s...\n\n' \ + "${GB_DIM}" "${GB_BLUE}${BOLD}" "$host" "${RESET}" + TERM="$TERM_FALLBACK" kitten ssh "$host" + exit 0 + ;; + $'\x7f'|$'\b') + # backspace + QUERY="${QUERY%?}" + filter + ;; + $'\x03') + # ctrl+c + exit 0 + ;; + *) + # printable character — append to query + if [[ "$key" =~ ^[[:print:]]$ ]]; then + QUERY+="$key" + filter + fi + ;; + esac +done diff --git a/polybar/config.ini b/polybar/config.ini new file mode 100644 index 0000000..649d289 --- /dev/null +++ b/polybar/config.ini @@ -0,0 +1,456 @@ +[colors] +bg = #282828 +bg1 = #3c3836 +bg2 = #504945 +bg3 = #665c54 +bg4 = #7c6f64 +fg = #ebdbb2 +fg1 = #d5c4a1 +fg2 = #bdae93 +red = #cc241d +green = #98971a +yellow = #d79921 +orange = #d65d0e +purple = #b16286 +aqua = #689d6a +gray = #928374 + +[bar/main] +width = 100% +height = 30 +monitor = ${env:MONITOR:} +background = ${colors.bg} +foreground = ${colors.fg} + +line-size = 2 +line-color = ${colors.yellow} +border-bottom-size = 2 +border-color = ${colors.bg1} + +padding-left = 1 +padding-right = 2 +module-margin = 1 + +font-0 = "FiraCode Nerd Font:style=Regular:size=11;3" +font-1 = "FiraCode Nerd Font:style=Bold:size=11;3" + +modules-left = i3 +modules-center = date +modules-right = ${env:POLYBAR_MODULES_RIGHT:bar-mode dunst awake pulseaudio keyboard wireguard net-speed network temp cpu memory filesystem tray} + +separator = "│" +separator-foreground = ${colors.bg3} + +cursor-click = pointer + +fixed-center = true +wm-restack = i3 +enable-ipc = true + + +;; ── i3 ───────────────────────────────────────── + +[module/i3] +type = internal/i3 +index-sort = true + +format = + +label-mode = %mode% +label-mode-background = ${colors.orange} +label-mode-foreground = ${colors.bg} +label-mode-padding = 2 + +label-focused = %index% +label-focused-foreground = ${colors.bg} +label-focused-background = ${colors.yellow} +label-focused-padding = 2 + +label-unfocused = %index% +label-unfocused-foreground = ${colors.fg2} +label-unfocused-padding = 2 + +label-visible = %index% +label-visible-foreground = ${colors.fg1} +label-visible-padding = 2 + +label-urgent = %index% +label-urgent-background = ${colors.red} +label-urgent-foreground = ${colors.bg} +label-urgent-padding = 2 + +pin-workspaces = false +scroll-up = i3wm-wsnext +scroll-down = i3wm-wsprev + +label-empty = %index% +label-empty-foreground = ${colors.bg3} +label-empty-padding = 2 + +;; ── Date ─────────────────────────────────────── + +[module/date] +type = internal/date +interval = 5 + +date = %a %d %b +time = %H:%M + +format-prefix = "date " +format-prefix-foreground = ${colors.aqua} + +label = %date% %time% + + +;; ── Dunst ───────────────────────────────────── + +[module/dunst] +type = custom/script +exec = /home/aag/.config/polybar/scripts/dunst-status.sh +interval = 2 + +format-prefix = "ntf " +format-prefix-foreground = ${colors.yellow} + +label = %output% + +click-left = dunstctl set-paused toggle +click-middle = dunstctl history-pop +click-right = dunstctl close-all + + +;; ── Bar Mode ────────────────────────────────── + +[module/bar-mode] +type = custom/script +exec = /home/aag/.config/polybar/scripts/bar-mode-status.sh +interval = 2 + +format-prefix = "bar " +format-prefix-foreground = ${colors.yellow} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/bar-mode-toggle.sh +click-right = /home/aag/.config/polybar/scripts/bar-mode-toggle.sh compact +click-middle = /home/aag/.config/polybar/scripts/bar-mode-toggle.sh full + + +;; ── Force Awake ─────────────────────────────── + +[module/awake] +type = custom/script +exec = /home/aag/.config/polybar/scripts/awake-status.sh +interval = 2 + +format-prefix = "wake " +format-prefix-foreground = ${colors.orange} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/awake-toggle.sh +click-middle = /home/aag/.config/polybar/scripts/awake-toggle.sh on +click-right = /home/aag/.config/polybar/scripts/awake-toggle.sh off + + +;; ── Updates ─────────────────────────────────── + +[module/updates] +type = custom/script +exec = /home/aag/.config/polybar/scripts/updates-status.sh +interval = 900 + +format-prefix = "upd " +format-prefix-foreground = ${colors.yellow} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/updates-action.sh + + +;; ── Screenshot ──────────────────────────────── + +[module/screenshot] +type = custom/script +exec = /usr/bin/printf menu +interval = 3600 + +format-prefix = "shot " +format-prefix-foreground = ${colors.aqua} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/screenshot-menu.sh + + +;; ── Power Menu ──────────────────────────────── + +[module/power] +type = custom/script +exec = /usr/bin/printf menu +interval = 3600 + +format-prefix = "pwr " +format-prefix-foreground = ${colors.red} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/power-menu.sh + + +;; ── Clipboard ───────────────────────────────── + +[module/clipboard] +type = custom/script +exec = /usr/bin/printf menu +interval = 3600 + +format-prefix = "clip " +format-prefix-foreground = ${colors.purple} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/clipboard-menu.sh + + +;; ── Display ─────────────────────────────────── + +[module/display] +type = custom/script +exec = /home/aag/.config/polybar/scripts/display-status.sh +interval = 2 + +format-prefix = "disp " +format-prefix-foreground = ${colors.orange} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/display-toggle.sh +scroll-up = /home/aag/.config/polybar/scripts/display-brightness.sh up +scroll-down = /home/aag/.config/polybar/scripts/display-brightness.sh down + + +;; ── Microphone ──────────────────────────────── + +[module/mic] +type = custom/script +exec = /home/aag/.config/polybar/scripts/mic-status.sh +interval = 2 + +format-prefix = "mic " +format-prefix-foreground = ${colors.purple} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/mic-toggle.sh + + +;; ── PipeWire (via PulseAudio API) ───────────── + +[module/pulseaudio] +type = internal/pulseaudio + +use-ui-max = true +interval = 2 + +format-volume = +format-volume-prefix = "vol " +format-volume-prefix-foreground = ${colors.green} +format-muted = +format-muted-prefix = "vol " +format-muted-prefix-foreground = ${colors.gray} + +label-volume = %percentage%% +label-muted = muted + +label-muted-foreground = ${colors.gray} + +click-left = pavucontrol +click-right = pavucontrol +click-middle = pactl set-sink-mute @DEFAULT_SINK@ toggle +scroll-up = pactl set-sink-volume @DEFAULT_SINK@ +5% +scroll-down = pactl set-sink-volume @DEFAULT_SINK@ -5% + + +;; ── Music ───────────────────────────────────── + +[module/music] +type = custom/script +exec = /home/aag/.config/polybar/scripts/music-status.sh +interval = 2 + +format-prefix = "mus " +format-prefix-foreground = ${colors.green} + +label = %output% + +click-left = playerctl play-pause +click-middle = playerctl previous +click-right = playerctl next + + +;; ── Keyboard ────────────────────────────────── + + +[module/keyboard] +type = custom/script +exec = /home/aag/.config/polybar/scripts/kb-status.sh +interval = 2 + +format-prefix = "kbd " +format-prefix-foreground = ${colors.purple} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/kb-switch.sh + + +;; ── WireGuard ───────────────────────────────── + +[module/wireguard] +type = custom/script +exec = /home/aag/.config/polybar/scripts/wg-pc-status.sh +interval = 2 + +format-prefix = "wg " +format-prefix-foreground = ${colors.aqua} + +label = %output% + +click-left = /home/aag/.config/polybar/scripts/wg-pc-toggle.sh +click-right = /home/aag/.config/polybar/scripts/wg-pc-toggle.sh down +click-middle = /home/aag/.config/polybar/scripts/wg-pc-toggle.sh up + + +;; ── Network ─────────────────────────────────── + +[module/network] +type = internal/network +interface = ${env:POLYBAR_NETWORK:eth0} +interval = 5 + +format-connected = +format-connected-prefix = "net " +format-connected-prefix-foreground = ${colors.green} + +format-disconnected = +format-disconnected-prefix = "net " +format-disconnected-prefix-foreground = ${colors.red} + +label-connected = %local_ip% +label-disconnected = offline + + +;; ── Network Speed ───────────────────────────── + +[module/net-speed] +type = custom/script +exec = /home/aag/.config/polybar/scripts/net-speed.sh +interval = 2 + +format-prefix = "spd " +format-prefix-foreground = ${colors.green} + +label = %output% + + +;; ── Temperature ─────────────────────────────── + +[module/temp] +type = custom/script +exec = /home/aag/.config/polybar/scripts/temp-status.sh +interval = 5 + +format-prefix = "tmp " +format-prefix-foreground = ${colors.orange} + +label = %output% + + +;; ── CPU ─────────────────────────────────────── + +[module/cpu] +type = internal/cpu +interval = 2 +warn-percentage = 85 + +format-prefix = "cpu " +format-prefix-foreground = ${colors.orange} +format-warn = +format-warn-prefix = "cpu " +format-warn-prefix-foreground = ${colors.red} + +label = %percentage%% +label-warn = %percentage%% +label-warn-foreground = ${colors.red} + + +;; ── Memory ──────────────────────────────────── + +[module/memory] +type = internal/memory +interval = 3 +warn-percentage = 80 + +format-prefix = "ram " +format-prefix-foreground = ${colors.purple} +format-warn = +format-warn-prefix = "ram " +format-warn-prefix-foreground = ${colors.red} + +label = %percentage_used%% +label-warn = %percentage_used%% +label-warn-foreground = ${colors.red} + + +;; ── Filesystem ──────────────────────────────── + +[module/filesystem] +type = internal/fs +interval = 30 +warn-percentage = 85 + +mount-0 = / + +format-mounted-prefix = "disk " +format-mounted-prefix-foreground = ${colors.aqua} +format-warn = +format-warn-prefix = "disk " +format-warn-prefix-foreground = ${colors.red} + +label-mounted = %percentage_used%% +label-warn = %percentage_used%% +label-warn-foreground = ${colors.red} +label-unmounted = down +label-unmounted-foreground = ${colors.red} + + +;; ── Scratchpad ──────────────────────────────── + +[module/scratchpad] +type = custom/script +exec = /home/aag/.config/polybar/scripts/scratchpad-status.sh +interval = 2 + +format-prefix = "scratch " +format-prefix-foreground = ${colors.yellow} + +label = %output% + +click-left = i3-msg scratchpad show +click-middle = i3-msg move scratchpad + + +;; ── Tray ────────────────────────────────────── + +[module/tray] +type = internal/tray + +format = +format-margin = 0 +tray-spacing = 4pt + + +[settings] +screenchange-reload = true +pseudo-transparency = false diff --git a/polybar/config.ini~ b/polybar/config.ini~ new file mode 100644 index 0000000..18dfcaa --- /dev/null +++ b/polybar/config.ini~ @@ -0,0 +1,168 @@ +[colors] +bg = #282828 +bg1 = #3c3836 +bg2 = #504945 +bg3 = #665c54 +bg4 = #7c6f64 +fg = #ebdbb2 +fg1 = #d5c4a1 +fg2 = #bdae93 +red = #cc241d +green = #98971a +yellow = #d79921 +orange = #d65d0e +purple = #b16286 +aqua = #689d6a +gray = #928374 + +[bar/main] +width = 100% +height = 28 +monitor = ${env:MONITOR:} +background = ${colors.bg} +foreground = ${colors.fg} + +line-size = 2 +line-color = ${colors.yellow} + +padding-left = 1 +padding-right = 2 +module-margin = 1 + +font-0 = "FiraCode Nerd Font:style=Regular:size=11;3" +font-1 = "FiraCode Nerd Font:style=Bold:size=11;3" + +modules-left = i3 +modules-center = date +modules-right = xkeyboard cpu memory filesystem + +separator = "│" +separator-foreground = ${colors.bg3} + +cursor-click = pointer +cursor-scroll = ns-resize + +enable-ipc = true + + +;; ── i3 ───────────────────────────────────────── + +[module/i3] +type = internal/i3 +index-sort = true + +format = + +label-focused = %index% +label-focused-foreground = ${colors.bg} +label-focused-background = ${colors.yellow} +label-focused-padding = 2 + +label-unfocused = %index% +label-unfocused-foreground = ${colors.fg2} +label-unfocused-padding = 2 + +label-visible = %index% +label-visible-foreground = ${colors.fg1} +label-visible-padding = 2 + +label-urgent = %index% +label-urgent-background = ${colors.red} +label-urgent-foreground = ${colors.bg} +label-urgent-padding = 2 + + +;; ── Date ─────────────────────────────────────── + +[module/date] +type = internal/date +interval = 5 + +date = %a %d %b +time = %H:%M + +format-prefix = "time " +format-prefix-foreground = ${colors.aqua} + +label = %date% %time% + + +;; ── PipeWire (via PulseAudio API) ───────────── + +[module/pulseaudio] +type = internal/pulseaudio + +use-ui-max = true +interval = 2 + +format-volume = +format-muted = + +label-volume = %percentage%% +label-muted = muted + +label-muted-foreground = ${colors.gray} + +ramp-volume-0 = vol-low +ramp-volume-1 = vol-mid +ramp-volume-2 = vol-high +ramp-volume-foreground = ${colors.green} + +click-right = pavucontrol + + +;; ── Keyboard ────────────────────────────────── + + +[module/xkeyboard] +type = internal/xkeyboard + +blacklist-0 = num lock +blacklist-1 = scroll lock + +format-prefix = "kbd " +format-prefix-foreground = ${colors.purple} + +label-layout = %layout% + +click-left = /usr/bin/env bash ~/.config/polybar/scripts/kb-switch.sh + +;; ── CPU ─────────────────────────────────────── + +[module/cpu] +type = internal/cpu +interval = 2 + +format-prefix = "cpu " +format-prefix-foreground = ${colors.orange} + +label = %percentage%% + + +;; ── Memory ──────────────────────────────────── + +[module/memory] +type = internal/memory +interval = 3 + +format-prefix = "ram " +format-prefix-foreground = ${colors.purple} + +label = %percentage_used%% + + +;; ── Filesystem ──────────────────────────────── + +[module/filesystem] +type = internal/fs +interval = 30 + +mount-0 = / + +format-mounted-prefix = "disk " +format-mounted-prefix-foreground = ${colors.aqua} + +label-mounted = %percentage_used%% +label-unmounted = down +label-unmounted-foreground = ${colors.red} + diff --git a/polybar/launch.sh b/polybar/launch.sh new file mode 100755 index 0000000..5b7e018 --- /dev/null +++ b/polybar/launch.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/polybar/config.ini" +state_dir="${XDG_RUNTIME_DIR:-/tmp}" + +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" + mkdir -p "$state_dir" 2>/dev/null || true +fi + +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi + +mode_file="$state_dir/polybar-mode.state" +compact_modules="bar-mode dunst awake pulseaudio keyboard wireguard net-speed network temp cpu memory filesystem tray" +full_modules="bar-mode dunst awake updates screenshot power clipboard display mic pulseaudio music keyboard wireguard net-speed network temp cpu memory filesystem scratchpad tray" + +if [ -r "$mode_file" ] && [ "$(cat "$mode_file")" = "full" ]; then + export POLYBAR_MODULES_RIGHT="$full_modules" +else + export POLYBAR_MODULES_RIGHT="$compact_modules" +fi + +# Kill any running instances before i3 reloads the bar. +killall -q polybar || true +while pgrep -u "$UID" -x polybar >/dev/null; do + sleep 0.1 +done + +mapfile -t monitors < <(polybar --list-monitors 2>/dev/null | cut -d: -f1 || true) + +if [ "${#monitors[@]}" -eq 0 ]; then + polybar --reload main -c "$CONFIG" & + exit 0 +fi + +for monitor in "${monitors[@]}"; do + MONITOR="$monitor" polybar --reload main -c "$CONFIG" & +done diff --git a/polybar/launch.sh~ b/polybar/launch.sh~ new file mode 100755 index 0000000..7490090 --- /dev/null +++ b/polybar/launch.sh~ @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Kill any running instances +killall -q polybar +while pgrep -u $UID -x polybar > /dev/null; do sleep 0.1; done + +# If you have multiple monitors, launch one bar per output: +# for m in $(polybar --list-monitors | cut -d":" -f1); do +# MONITOR=$m polybar main & +# done + +polybar main 2>&1 | tee -a /tmp/polybar.log & disown diff --git a/polybar/scripts/awake-status.sh b/polybar/scripts/awake-status.sh new file mode 100755 index 0000000..98948f2 --- /dev/null +++ b/polybar/scripts/awake-status.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi +state_file="$state_dir/polybar-awake.state" + +if [ -f "$state_file" ]; then + echo "%{F#98971a}on%{F-}" +else + echo "%{F#928374}off%{F-}" +fi diff --git a/polybar/scripts/awake-toggle.sh b/polybar/scripts/awake-toggle.sh new file mode 100755 index 0000000..608bbc2 --- /dev/null +++ b/polybar/scripts/awake-toggle.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +set -euo pipefail + +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" + mkdir -p "$state_dir" +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi +state_file="$state_dir/polybar-awake.state" +action="${1:-toggle}" + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +notify() { + if command -v dunstify >/dev/null 2>&1; then + dunstify -a polybar -u low "Force awake" "$1" + fi +} + +save_current_state() { + local query + query="$(xset q)" + + { + printf 'blanking=%s\n' "$(printf '%s\n' "$query" | awk '/prefer blanking:/ { print $3; exit }')" + printf 'exposures=%s\n' "$(printf '%s\n' "$query" | awk '/allow exposures:/ { print $6; exit }')" + printf 'timeout=%s\n' "$(printf '%s\n' "$query" | awk '/timeout:/ { print $2; exit }')" + printf 'cycle=%s\n' "$(printf '%s\n' "$query" | awk '/timeout:/ { print $4; exit }')" + printf 'standby=%s\n' "$(printf '%s\n' "$query" | awk '/Standby:/ { print $2; exit }')" + printf 'suspend=%s\n' "$(printf '%s\n' "$query" | awk '/Standby:/ { print $4; exit }')" + printf 'off=%s\n' "$(printf '%s\n' "$query" | awk '/Standby:/ { print $6; exit }')" + printf 'dpms=%s\n' "$(printf '%s\n' "$query" | awk '/DPMS is/ { print $3; exit }')" + } >"$state_file" +} + +enable_awake() { + if [ ! -f "$state_file" ]; then + save_current_state + fi + + xset s noblank + xset s off + xset -dpms + notify "enabled" +} + +restore_value() { + local name="$1" + local fallback="$2" + local value + + value="$(awk -F= -v key="$name" '$1 == key { print $2; exit }' "$state_file" 2>/dev/null || true)" + printf '%s' "${value:-$fallback}" +} + +disable_awake() { + if [ -f "$state_file" ]; then + local blanking exposures timeout cycle standby suspend off dpms + blanking="$(restore_value blanking yes)" + exposures="$(restore_value exposures yes)" + timeout="$(restore_value timeout 600)" + cycle="$(restore_value cycle 600)" + standby="$(restore_value standby 600)" + suspend="$(restore_value suspend 600)" + off="$(restore_value off 600)" + dpms="$(restore_value dpms Enabled)" + + if [ "$blanking" = "yes" ]; then + xset s blank + else + xset s noblank + fi + + if [ "$exposures" = "yes" ]; then + xset s expose + else + xset s noexpose + fi + + xset s "$timeout" "$cycle" + xset dpms "$standby" "$suspend" "$off" + + if [ "$dpms" = "Enabled" ]; then + xset +dpms + else + xset -dpms + fi + + rm -f "$state_file" + else + xset s on + xset +dpms + fi + + notify "disabled" +} + +case "$action" in + on) + enable_awake + ;; + off) + disable_awake + ;; + toggle) + if [ -f "$state_file" ]; then + disable_awake + else + enable_awake + fi + ;; + *) + notify "unknown action: $action" + exit 2 + ;; +esac diff --git a/polybar/scripts/bar-mode-status.sh b/polybar/scripts/bar-mode-status.sh new file mode 100755 index 0000000..7e6d787 --- /dev/null +++ b/polybar/scripts/bar-mode-status.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi + +mode_file="$state_dir/polybar-mode.state" + +if [ -r "$mode_file" ] && [ "$(cat "$mode_file")" = "full" ]; then + echo "%{F#d79921}less%{F-}" +else + echo "%{F#98971a}more%{F-}" +fi diff --git a/polybar/scripts/bar-mode-toggle.sh b/polybar/scripts/bar-mode-toggle.sh new file mode 100755 index 0000000..d943df1 --- /dev/null +++ b/polybar/scripts/bar-mode-toggle.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +requested="${1:-toggle}" +state_dir="${XDG_RUNTIME_DIR:-/tmp}" + +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" + mkdir -p "$state_dir" 2>/dev/null || true +fi + +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi + +mode_file="$state_dir/polybar-mode.state" + +current="compact" +if [ -r "$mode_file" ]; then + current="$(cat "$mode_file")" +fi + +case "$requested" in + full|expand|expanded) + next="full" + ;; + compact|minimize|minimized) + next="compact" + ;; + toggle) + if [ "$current" = "full" ]; then + next="compact" + else + next="full" + fi + ;; + *) + exit 2 + ;; +esac + +printf '%s\n' "$next" >"$mode_file" + +if command -v dunstify >/dev/null 2>&1; then + dunstify -a polybar -u low "Polybar" "$next mode" +fi + +/home/aag/.config/polybar/launch.sh >/tmp/polybar-mode-toggle.log 2>&1 & diff --git a/polybar/scripts/clipboard-menu.sh b/polybar/scripts/clipboard-menu.sh new file mode 100755 index 0000000..10fe648 --- /dev/null +++ b/polybar/scripts/clipboard-menu.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" +mkdir -p "$state_dir" 2>/dev/null || true +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi +history_file="$state_dir/polybar-clipboard-history" + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +current="" +if command -v xclip >/dev/null 2>&1; then + current="$(xclip -selection clipboard -o 2>/dev/null || true)" +elif command -v xsel >/dev/null 2>&1; then + current="$(xsel --clipboard --output 2>/dev/null || true)" +fi + +if [ -n "$current" ]; then + one_line="$(printf '%s' "$current" | tr '\n' ' ' | cut -c 1-180)" + if ! grep -Fxq "$one_line" "$history_file" 2>/dev/null; then + tmp="${history_file}.tmp" + { printf '%s\n' "$one_line"; cat "$history_file" 2>/dev/null; } | awk 'NF && !seen[$0]++' | head -n 50 >"$tmp" + mv "$tmp" "$history_file" + fi +fi + +choice="$( + dmenu -i -p clipboard \ + -fn "FiraCode Nerd Font-14" \ + -nb "#282828" -nf "#ebdbb2" \ + -sb "#d79921" -sf "#282828" <"$history_file" +)" + +[ -n "$choice" ] || exit 0 + +if command -v xclip >/dev/null 2>&1; then + printf '%s' "$choice" | xclip -selection clipboard +elif command -v xsel >/dev/null 2>&1; then + printf '%s' "$choice" | xsel --clipboard --input +fi diff --git a/polybar/scripts/display-brightness.sh b/polybar/scripts/display-brightness.sh new file mode 100755 index 0000000..ea3d04c --- /dev/null +++ b/polybar/scripts/display-brightness.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +direction="${1:-up}" +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" + mkdir -p "$state_dir" +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi +state_file="$state_dir/polybar-display-brightness.state" + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +output="$( + xrandr --query 2>/dev/null | + awk '/ connected primary/ { print $1; exit } / connected/ && !out { out = $1 } END { if (out) print out }' +)" + +[ -n "$output" ] || exit 0 + +brightness="$(cat "$state_file" 2>/dev/null || printf '1.0')" + +case "$direction" in + up) + brightness="$(awk -v v="$brightness" 'BEGIN { v += 0.05; if (v > 1.00) v = 1.00; printf "%.2f", v }')" + ;; + down) + brightness="$(awk -v v="$brightness" 'BEGIN { v -= 0.05; if (v < 0.30) v = 0.30; printf "%.2f", v }')" + ;; +esac + +printf '%s\n' "$brightness" >"$state_file" +xrandr --output "$output" --brightness "$brightness" diff --git a/polybar/scripts/display-status.sh b/polybar/scripts/display-status.sh new file mode 100755 index 0000000..620e3da --- /dev/null +++ b/polybar/scripts/display-status.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi +state_file="$state_dir/polybar-display-night.state" + +if [ -f "$state_file" ]; then + echo "%{F#d65d0e}night%{F-}" +else + echo "day" +fi diff --git a/polybar/scripts/display-toggle.sh b/polybar/scripts/display-toggle.sh new file mode 100755 index 0000000..df810e8 --- /dev/null +++ b/polybar/scripts/display-toggle.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" + mkdir -p "$state_dir" +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi +state_file="$state_dir/polybar-display-night.state" + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +output="$( + xrandr --query 2>/dev/null | + awk '/ connected primary/ { print $1; exit } / connected/ && !out { out = $1 } END { if (out) print out }' +)" + +[ -n "$output" ] || exit 0 + +notify() { + if command -v dunstify >/dev/null 2>&1; then + dunstify -a polybar -u low "Display" "$1" + fi +} + +if [ -f "$state_file" ]; then + xrandr --output "$output" --gamma 1:1:1 + rm -f "$state_file" + notify "day mode" +else + xrandr --output "$output" --gamma 1:0.88:0.72 + : >"$state_file" + notify "night mode" +fi diff --git a/polybar/scripts/dunst-status.sh b/polybar/scripts/dunst-status.sh new file mode 100755 index 0000000..da23ddd --- /dev/null +++ b/polybar/scripts/dunst-status.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v dunstctl >/dev/null 2>&1; then + echo "n/a" + exit 0 +fi + +paused="$(dunstctl is-paused 2>/dev/null || echo unavailable)" +waiting="$(dunstctl count waiting 2>/dev/null || echo 0)" + +case "$paused" in + true) + if [ "$waiting" -gt 0 ] 2>/dev/null; then + echo "dnd $waiting" + else + echo "dnd" + fi + ;; + false) + echo "on" + ;; + *) + echo "off" + ;; +esac diff --git a/polybar/scripts/kb-status.sh b/polybar/scripts/kb-status.sh new file mode 100755 index 0000000..551e587 --- /dev/null +++ b/polybar/scripts/kb-status.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +query="$(setxkbmap -query 2>/dev/null || true)" +layout="$(printf '%s\n' "$query" | awk '/^layout:/ { print $2; exit }')" +variant="$(printf '%s\n' "$query" | awk '/^variant:/ { print $2; exit }')" + +case "${layout:-unknown}:${variant:-}" in + us:dvorak) + echo "us dv" + ;; + cz:*|cz:) + echo "cz" + ;; + *:) + echo "${layout:-unknown}" + ;; + *) + echo "${layout:-unknown} ${variant}" + ;; +esac diff --git a/polybar/scripts/kb-switch.sh b/polybar/scripts/kb-switch.sh new file mode 100755 index 0000000..af231f0 --- /dev/null +++ b/polybar/scripts/kb-switch.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +DMENU="${DMENU:-/usr/bin/dmenu}" +SETXKBMAP="${SETXKBMAP:-/usr/bin/setxkbmap}" +LOG_FILE="${XDG_RUNTIME_DIR:-/tmp}/polybar-kb-switch.log" + +exec 2>>"$LOG_FILE" + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +choice="$( + printf '%s\n' "us" "us dvorak" "cz" | + "$DMENU" -i -p "Keyboard" \ + -b \ + -fn "FiraCode Nerd Font-12" \ + -nb "#282828" \ + -nf "#ebdbb2" \ + -sb "#d79921" \ + -sf "#282828" +)" || exit 0 + +case "$choice" in + "us") + "$SETXKBMAP" us + ;; + "us dvorak") + "$SETXKBMAP" us -variant dvorak + ;; + "cz") + "$SETXKBMAP" cz + ;; +esac diff --git a/polybar/scripts/kb-switch.sh~ b/polybar/scripts/kb-switch.sh~ new file mode 100755 index 0000000..69ff036 --- /dev/null +++ b/polybar/scripts/kb-switch.sh~ @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +options="us\nus dvorak\ncs" + +choice=$(echo -e "$options" | dmenu -i -p "Keyboard layout") + +case "$choice" in + "us") + setxkbmap us + ;; + "us dvorak") + setxkbmap us -variant dvorak + ;; + "cs") + setxkbmap cz + ;; +esac diff --git a/polybar/scripts/mic-status.sh b/polybar/scripts/mic-status.sh new file mode 100755 index 0000000..21c44ce --- /dev/null +++ b/polybar/scripts/mic-status.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v pactl >/dev/null 2>&1; then + echo "n/a" + exit 0 +fi + +mute="$(pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{ print $2 }' || true)" + +case "$mute" in + yes) + echo "%{F#cc241d}muted%{F-}" + ;; + no) + echo "%{F#98971a}on%{F-}" + ;; + *) + echo "n/a" + ;; +esac diff --git a/polybar/scripts/mic-toggle.sh b/polybar/scripts/mic-toggle.sh new file mode 100755 index 0000000..de93cf6 --- /dev/null +++ b/polybar/scripts/mic-toggle.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v pactl >/dev/null 2>&1; then + exit 0 +fi + +pactl set-source-mute @DEFAULT_SOURCE@ toggle + +if command -v dunstify >/dev/null 2>&1; then + status="$(pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{ print $2 }' || true)" + dunstify -a polybar -u low "Microphone" "${status:-toggled}" +fi diff --git a/polybar/scripts/music-status.sh b/polybar/scripts/music-status.sh new file mode 100755 index 0000000..229e058 --- /dev/null +++ b/polybar/scripts/music-status.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v playerctl >/dev/null 2>&1; then + echo "n/a" + exit 0 +fi + +status="$(playerctl status 2>/dev/null || true)" +if [ -z "$status" ]; then + echo "off" + exit 0 +fi + +artist="$(playerctl metadata artist 2>/dev/null || true)" +title="$(playerctl metadata title 2>/dev/null || true)" + +if [ -n "$artist" ] && [ -n "$title" ]; then + text="$artist - $title" +elif [ -n "$title" ]; then + text="$title" +else + text="$status" +fi + +case "$status" in + Playing) + prefix="%{F#98971a}>%{F-}" + ;; + Paused) + prefix="%{F#d79921}=%{F-}" + ;; + *) + prefix="-" + ;; +esac + +printf '%s %s\n' "$prefix" "$(printf '%s' "$text" | cut -c 1-32)" diff --git a/polybar/scripts/net-speed.sh b/polybar/scripts/net-speed.sh new file mode 100755 index 0000000..ead10fd --- /dev/null +++ b/polybar/scripts/net-speed.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +iface="${POLYBAR_NETWORK:-eth0}" +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" + mkdir -p "$state_dir" +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi +state_file="$state_dir/polybar-net-${iface}.state" +rx_file="/sys/class/net/$iface/statistics/rx_bytes" +tx_file="/sys/class/net/$iface/statistics/tx_bytes" + +human() { + local bytes="$1" + + if [ "$bytes" -ge 1048576 ]; then + awk -v v="$bytes" 'BEGIN { printf "%.1fM", v / 1048576 }' + elif [ "$bytes" -ge 1024 ]; then + awk -v v="$bytes" 'BEGIN { printf "%.0fK", v / 1024 }' + else + printf '%sB' "$bytes" + fi +} + +if [ ! -r "$rx_file" ] || [ ! -r "$tx_file" ]; then + echo "n/a" + exit 0 +fi + +now="$(date +%s)" +rx="$(cat "$rx_file")" +tx="$(cat "$tx_file")" + +if [ -r "$state_file" ]; then + read -r old_now old_rx old_tx <"$state_file" || true +else + old_now="$now" + old_rx="$rx" + old_tx="$tx" +fi + +printf '%s %s %s\n' "$now" "$rx" "$tx" >"$state_file" + +delta=$((now - old_now)) +if [ "$delta" -le 0 ]; then + delta=1 +fi + +down=$(((rx - old_rx) / delta)) +up=$(((tx - old_tx) / delta)) + +if [ "$down" -lt 0 ]; then + down=0 +fi + +if [ "$up" -lt 0 ]; then + up=0 +fi + +printf '%s/%s\n' "$(human "$down")" "$(human "$up")" diff --git a/polybar/scripts/polkit-agent.sh b/polybar/scripts/polkit-agent.sh new file mode 100755 index 0000000..ed86a5f --- /dev/null +++ b/polybar/scripts/polkit-agent.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +if pgrep -u "$UID" -f 'polkit-gnome-authentication-agent-1|polkit-kde-authentication-agent-1|lxqt-policykit-agent|polkit-mate-authentication-agent-1|xfce-polkit' >/dev/null 2>&1; then + exit 0 +fi + +agents=( + /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 + /usr/lib/polkit-kde-authentication-agent-1 + /usr/lib/lxqt-policykit/lxqt-policykit-agent + /usr/bin/lxqt-policykit-agent + /usr/lib/mate-polkit/polkit-mate-authentication-agent-1 + /usr/lib/xfce-polkit/xfce-polkit +) + +for agent in "${agents[@]}"; do + if [ -x "$agent" ]; then + exec "$agent" + fi +done + +echo "No supported polkit authentication agent found" >&2 +exit 1 diff --git a/polybar/scripts/power-menu.sh b/polybar/scripts/power-menu.sh new file mode 100755 index 0000000..ab59352 --- /dev/null +++ b/polybar/scripts/power-menu.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +choice="$( + printf '%s\n' lock logout suspend reboot shutdown | + dmenu -i -p power \ + -fn "FiraCode Nerd Font-14" \ + -nb "#282828" -nf "#ebdbb2" \ + -sb "#d79921" -sf "#282828" +)" + +case "$choice" in + lock) + i3lock + ;; + logout) + i3-msg exit + ;; + suspend) + loginctl suspend + ;; + reboot) + loginctl reboot + ;; + shutdown) + loginctl poweroff + ;; +esac diff --git a/polybar/scripts/scratchpad-status.sh b/polybar/scripts/scratchpad-status.sh new file mode 100755 index 0000000..426d3b2 --- /dev/null +++ b/polybar/scripts/scratchpad-status.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v i3-msg >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then + echo "n/a" + exit 0 +fi + +count="$( + i3-msg -t get_tree 2>/dev/null | + jq '[.. | objects | select(.name == "__i3_scratch") | .floating_nodes[]?] | length' 2>/dev/null || printf '0' +)" + +if [ "$count" -gt 0 ] 2>/dev/null; then + echo "%{F#d79921}$count%{F-}" +else + echo "0" +fi diff --git a/polybar/scripts/screenshot-menu.sh b/polybar/scripts/screenshot-menu.sh new file mode 100755 index 0000000..b3064a9 --- /dev/null +++ b/polybar/scripts/screenshot-menu.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -euo pipefail + +dir="${XDG_PICTURES_DIR:-$HOME/Pictures}/Screenshots" +mkdir -p "$dir" 2>/dev/null || true +if [ ! -w "$dir" ]; then + dir="/tmp" +fi + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +notify() { + if command -v dunstify >/dev/null 2>&1; then + dunstify -a polybar -u low "Screenshot" "$1" + fi +} + +menu() { + printf '%s\n' full selection window | + dmenu -i -p screenshot \ + -fn "FiraCode Nerd Font-14" \ + -nb "#282828" -nf "#ebdbb2" \ + -sb "#d79921" -sf "#282828" +} + +file="$dir/shot-$(date +%Y%m%d-%H%M%S).png" +choice="$(menu)" + +case "$choice" in + full) + if command -v scrot >/dev/null 2>&1; then + scrot "$file" + elif command -v import >/dev/null 2>&1; then + import -window root "$file" + else + notify "No screenshot tool found" + exit 1 + fi + ;; + selection) + if command -v flameshot >/dev/null 2>&1; then + flameshot gui -p "$dir" + exit 0 + elif command -v maim >/dev/null 2>&1; then + maim -s "$file" + elif command -v scrot >/dev/null 2>&1; then + scrot -s "$file" + elif command -v import >/dev/null 2>&1; then + import "$file" + else + notify "No selection screenshot tool found" + exit 1 + fi + ;; + window) + if command -v scrot >/dev/null 2>&1; then + scrot -u "$file" + elif command -v import >/dev/null 2>&1; then + import "$file" + else + notify "No window screenshot tool found" + exit 1 + fi + ;; + *) + exit 0 + ;; +esac + +notify "$file" diff --git a/polybar/scripts/temp-status.sh b/polybar/scripts/temp-status.sh new file mode 100755 index 0000000..d1f7665 --- /dev/null +++ b/polybar/scripts/temp-status.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -euo pipefail + +temp="" + +if command -v sensors >/dev/null 2>&1; then + temp="$( + sensors 2>/dev/null | + awk ' + /Package id 0:/ { gsub(/[^0-9.-]/, "", $4); print int($4); exit } + /Tctl:/ { gsub(/[^0-9.-]/, "", $2); print int($2); exit } + /temp1:/ && $2 ~ /^\+/ { gsub(/[^0-9.-]/, "", $2); print int($2); exit } + ' + )" +fi + +if [ -z "$temp" ]; then + for zone in /sys/class/thermal/thermal_zone*/temp; do + [ -r "$zone" ] || continue + value="$(cat "$zone")" + if [ "$value" -gt 0 ] 2>/dev/null; then + temp=$((value / 1000)) + break + fi + done +fi + +if [ -z "$temp" ]; then + echo "n/a" +elif [ "$temp" -ge 80 ]; then + echo "%{F#cc241d}${temp}C%{F-}" +elif [ "$temp" -ge 65 ]; then + echo "%{F#d79921}${temp}C%{F-}" +else + echo "${temp}C" +fi diff --git a/polybar/scripts/updates-action.sh b/polybar/scripts/updates-action.sh new file mode 100755 index 0000000..a22a747 --- /dev/null +++ b/polybar/scripts/updates-action.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +cmd='sudo pacman -Syu; echo; read -r -p "Press Enter to close..."' + +if command -v kitty >/dev/null 2>&1; then + exec kitty --title "System update" /usr/bin/env bash -lc "$cmd" +elif command -v foot >/dev/null 2>&1; then + exec foot --title "System update" /usr/bin/env bash -lc "$cmd" +elif command -v xterm >/dev/null 2>&1; then + exec xterm -T "System update" -e /usr/bin/env bash -lc "$cmd" +fi + +if command -v dunstify >/dev/null 2>&1; then + dunstify -a polybar -u normal "Updates" "No terminal found" +fi diff --git a/polybar/scripts/updates-status.sh b/polybar/scripts/updates-status.sh new file mode 100755 index 0000000..75568ad --- /dev/null +++ b/polybar/scripts/updates-status.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v checkupdates >/dev/null 2>&1; then + count="$( (checkupdates 2>/dev/null || true) | wc -l)" +elif command -v pacman >/dev/null 2>&1; then + count="$( (pacman -Qu 2>/dev/null || true) | wc -l)" +else + echo "n/a" + exit 0 +fi + +if [ "$count" -gt 0 ]; then + echo "%{F#d79921}$count%{F-}" +else + echo "0" +fi diff --git a/polybar/scripts/wg-pc-status.sh b/polybar/scripts/wg-pc-status.sh new file mode 100755 index 0000000..b708695 --- /dev/null +++ b/polybar/scripts/wg-pc-status.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +iface="${WG_QUICK_PROFILE:-PC}" + +age_label() { + local latest now age + + latest="$(wg show "$iface" latest-handshakes 2>/dev/null | awk 'NF { if ($2 > newest) newest = $2 } END { print newest + 0 }')" + if [ -z "$latest" ] || [ "$latest" -le 0 ] 2>/dev/null; then + return 0 + fi + + now="$(date +%s)" + age=$((now - latest)) + + if [ "$age" -lt 60 ]; then + printf ' %ss' "$age" + elif [ "$age" -lt 3600 ]; then + printf ' %sm' "$((age / 60))" + elif [ "$age" -lt 86400 ]; then + printf ' %sh' "$((age / 3600))" + else + printf ' %sd' "$((age / 86400))" + fi +} + +if [ -d "/sys/class/net/$iface" ]; then + echo "%{F#98971a}up$(age_label)%{F-}" +else + echo "%{F#cc241d}down%{F-}" +fi diff --git a/polybar/scripts/wg-pc-toggle.sh b/polybar/scripts/wg-pc-toggle.sh new file mode 100755 index 0000000..5cd102f --- /dev/null +++ b/polybar/scripts/wg-pc-toggle.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +profile="${WG_QUICK_PROFILE:-PC}" +iface="$profile" +requested="${1:-toggle}" + +export DISPLAY="${DISPLAY:-:0}" +if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" +fi + +case "$profile" in + *[!A-Za-z0-9_.@-]*|"") + echo "Invalid WireGuard profile: $profile" >&2 + exit 2 + ;; +esac + +log_file="${XDG_RUNTIME_DIR:-/tmp}/polybar-wg-$profile.log" + +notify() { + if command -v dunstify >/dev/null 2>&1; then + dunstify -a polybar -u low "WireGuard" "$1" + fi +} + +polkit_agent_running() { + pgrep -u "$UID" -f 'polkit-gnome-authentication-agent-1|polkit-kde-authentication-agent-1|lxqt-policykit-agent|polkit-mate-authentication-agent-1|xfce-polkit' >/dev/null 2>&1 +} + +ensure_polkit_agent() { + if polkit_agent_running; then + return 0 + fi + + if [ -x /home/aag/.config/polybar/scripts/polkit-agent.sh ]; then + /usr/bin/setsid -f /home/aag/.config/polybar/scripts/polkit-agent.sh >>"$log_file" 2>&1 + sleep 0.5 + fi + + polkit_agent_running +} + +case "$requested" in + up|down) + action="$requested" + ;; + toggle) + if [ -d "/sys/class/net/$iface" ]; then + action="down" + else + action="up" + fi + ;; + *) + notify "Unknown action: $requested" + exit 2 + ;; +esac + +if [ "$(id -u)" -eq 0 ]; then + auth=() +elif command -v pkexec >/dev/null 2>&1; then + if ! ensure_polkit_agent; then + notify "No polkit agent running" + echo "No polkit authentication agent is running." >"$log_file" + echo "Install/start polkit-gnome, then restart i3 or run ~/.config/polybar/scripts/polkit-agent.sh." >>"$log_file" + exit 1 + fi + auth=(pkexec --disable-internal-agent) +else + notify "pkexec not found" + exit 1 +fi + +if "${auth[@]}" /usr/bin/wg-quick "$action" "$profile" >"$log_file" 2>&1; then + notify "$profile $action complete" +else + notify "$profile $action failed: $log_file" + exit 1 +fi diff --git a/quickshell/gruvbar/README.md b/quickshell/gruvbar/README.md new file mode 100644 index 0000000..3c759ec --- /dev/null +++ b/quickshell/gruvbar/README.md @@ -0,0 +1,34 @@ +# Gruvbar Quickshell + +This is a Quickshell replacement bar for the current i3/Polybar setup. It keeps the Gruvbox palette and FiraCode Nerd Font look, but uses QML widgets instead of Polybar text modules. + +## Run + +```sh +sudo pacman -S quickshell +~/.config/quickshell/gruvbar/launch.sh +``` + +The config is a named Quickshell config, so this also works: + +```sh +quickshell -c gruvbar +``` + +## Switch i3 From Polybar + +After testing the bar manually, change the i3 startup line from Polybar to Gruvbar: + +```i3 +# exec_always --no-startup-id ~/.config/polybar/launch.sh +exec_always --no-startup-id /home/aag/.config/quickshell/gruvbar/launch.sh +``` + +## Widgets + +- Workspaces with click and scroll switching. +- Expand/minimize button for compact and full modes. +- Clickable notification, force-awake, volume, keyboard, WireGuard PC, display, mic, music, scratchpad, screenshot, clipboard, update, and power controls. +- Live CPU, memory, disk, network, network speed, temperature, and clock status. + +Most actions reuse the existing Polybar scripts so the behavior stays aligned while the UI moves to Quickshell. diff --git a/quickshell/gruvbar/launch.sh b/quickshell/gruvbar/launch.sh new file mode 100755 index 0000000..a591a40 --- /dev/null +++ b/quickshell/gruvbar/launch.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v quickshell >/dev/null 2>&1; then + echo "quickshell is not installed. Install it with: sudo pacman -S quickshell" >&2 + exit 1 +fi + +pkill -f "quickshell.*-c gruvbar" 2>/dev/null || true +exec quickshell -c gruvbar diff --git a/quickshell/gruvbar/scripts/action.sh b/quickshell/gruvbar/scripts/action.sh new file mode 100755 index 0000000..80d26d3 --- /dev/null +++ b/quickshell/gruvbar/scripts/action.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +set -euo pipefail + +action="${1:-}" +arg="${2:-}" +polybar_scripts="/home/aag/.config/polybar/scripts" + +run_existing() { + local script="$1" + shift + + if [ -x "$polybar_scripts/$script" ]; then + exec "$polybar_scripts/$script" "$@" + fi +} + +case "$action" in + awake) + run_existing awake-toggle.sh "$arg" + ;; + clipboard) + run_existing clipboard-menu.sh + ;; + display) + run_existing display-toggle.sh + ;; + display-brightness) + run_existing display-brightness.sh "${arg:-up}" + ;; + dunst) + command -v dunstctl >/dev/null 2>&1 || exit 0 + case "$arg" in + history) dunstctl history-pop ;; + close) dunstctl close-all ;; + *) dunstctl set-paused toggle ;; + esac + ;; + keyboard) + run_existing kb-switch.sh + ;; + mic) + run_existing mic-toggle.sh + ;; + music) + command -v playerctl >/dev/null 2>&1 || exit 0 + case "$arg" in + prev) playerctl previous ;; + next) playerctl next ;; + *) playerctl play-pause ;; + esac + ;; + power) + run_existing power-menu.sh + ;; + screenshot) + run_existing screenshot-menu.sh + ;; + scratch) + command -v i3-msg >/dev/null 2>&1 || exit 0 + case "$arg" in + move) i3-msg move scratchpad ;; + *) i3-msg scratchpad show ;; + esac + ;; + updates) + run_existing updates-action.sh + ;; + volume) + command -v pactl >/dev/null 2>&1 || { + [ "$arg" = "" ] && command -v pavucontrol >/dev/null 2>&1 && exec pavucontrol + exit 0 + } + case "$arg" in + up) pactl set-sink-volume @DEFAULT_SINK@ +5% ;; + down) pactl set-sink-volume @DEFAULT_SINK@ -5% ;; + mute) pactl set-sink-mute @DEFAULT_SINK@ toggle ;; + *) command -v pavucontrol >/dev/null 2>&1 && pavucontrol ;; + esac + ;; + wg) + run_existing wg-pc-toggle.sh "$arg" + ;; +esac diff --git a/quickshell/gruvbar/scripts/status.sh b/quickshell/gruvbar/scripts/status.sh new file mode 100755 index 0000000..f6d4402 --- /dev/null +++ b/quickshell/gruvbar/scripts/status.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +set -euo pipefail + +kind="${1:-}" +iface="${POLYBAR_NETWORK:-eth0}" +profile="${WG_QUICK_PROFILE:-PC}" + +state_dir="${XDG_RUNTIME_DIR:-/tmp}" +if [ ! -w "$state_dir" ]; then + state_dir="${XDG_CACHE_HOME:-$HOME/.cache}" + mkdir -p "$state_dir" 2>/dev/null || true +fi +if [ ! -w "$state_dir" ]; then + state_dir="/tmp" +fi + +human_bytes() { + local bytes="$1" + + if [ "$bytes" -ge 1048576 ]; then + awk -v v="$bytes" 'BEGIN { printf "%.1fM", v / 1048576 }' + elif [ "$bytes" -ge 1024 ]; then + awk -v v="$bytes" 'BEGIN { printf "%.0fK", v / 1024 }' + else + printf '%sB' "$bytes" + fi +} + +case "$kind" in + awake) + [ -f "$state_dir/polybar-awake.state" ] && echo on || echo off + ;; + cpu) + state_file="$state_dir/quickshell-cpu.state" + read -r cpu user nice system idle iowait irq softirq steal _ "$state_file" + diff_total=$((total - old_total)) + diff_idle=$((idle_all - old_idle)) + + if [ "$diff_total" -le 0 ]; then + echo 0% + else + echo "$(((100 * (diff_total - diff_idle)) / diff_total))%" + fi + ;; + date) + date '+%a %d %b %H:%M' + ;; + disk) + df -P / | awk 'NR == 2 { print $5 }' + ;; + display) + [ -f "$state_dir/polybar-display-night.state" ] && echo night || echo day + ;; + dunst) + if ! command -v dunstctl >/dev/null 2>&1; then + echo off + elif [ "$(dunstctl is-paused 2>/dev/null || echo false)" = true ]; then + waiting="$(dunstctl count waiting 2>/dev/null || echo 0)" + [ "$waiting" -gt 0 ] 2>/dev/null && echo "dnd $waiting" || echo dnd + else + echo on + fi + ;; + keyboard) + export DISPLAY="${DISPLAY:-:0}" + if [ -z "${XAUTHORITY:-}" ] && [ -r "$HOME/.Xauthority" ]; then + export XAUTHORITY="$HOME/.Xauthority" + fi + query="$(setxkbmap -query 2>/dev/null || true)" + layout="$(printf '%s\n' "$query" | awk '/^layout:/ { print $2; exit }')" + variant="$(printf '%s\n' "$query" | awk '/^variant:/ { print $2; exit }')" + case "${layout:-unknown}:${variant:-}" in + us:dvorak) echo "us dv" ;; + cz:*|cz:) echo cz ;; + *:) echo "${layout:-unknown}" ;; + *) echo "${layout:-unknown} ${variant}" ;; + esac + ;; + mem) + awk ' + /MemTotal:/ { total = $2 } + /MemAvailable:/ { avail = $2 } + END { if (total > 0) printf "%d%%\n", ((total - avail) * 100) / total; else print "n/a" } + ' /proc/meminfo + ;; + mic) + if ! command -v pactl >/dev/null 2>&1; then + echo n/a + else + mute="$(pactl get-source-mute @DEFAULT_SOURCE@ 2>/dev/null | awk '{ print $2 }' || true)" + [ "$mute" = yes ] && echo muted || echo on + fi + ;; + music) + if ! command -v playerctl >/dev/null 2>&1; then + echo n/a + exit 0 + fi + status="$(playerctl status 2>/dev/null || true)" + [ -n "$status" ] || { echo off; exit 0; } + artist="$(playerctl metadata artist 2>/dev/null || true)" + title="$(playerctl metadata title 2>/dev/null || true)" + if [ -n "$artist" ] && [ -n "$title" ]; then + printf '%s - %s\n' "$artist" "$title" | cut -c 1-34 + elif [ -n "$title" ]; then + printf '%s\n' "$title" | cut -c 1-34 + else + echo "$status" + fi + ;; + net) + ip -o -4 addr show "$iface" 2>/dev/null | awk '{ split($4, ip, "/"); print ip[1]; found = 1; exit } END { if (!found) print "offline" }' + ;; + netspeed) + rx_file="/sys/class/net/$iface/statistics/rx_bytes" + tx_file="/sys/class/net/$iface/statistics/tx_bytes" + state_file="$state_dir/quickshell-net-${iface}.state" + + if [ ! -r "$rx_file" ] || [ ! -r "$tx_file" ]; then + echo n/a + exit 0 + fi + + now="$(date +%s)" + rx="$(cat "$rx_file")" + tx="$(cat "$tx_file")" + + if [ -r "$state_file" ]; then + read -r old_now old_rx old_tx <"$state_file" || true + else + old_now="$now" + old_rx="$rx" + old_tx="$tx" + fi + + printf '%s %s %s\n' "$now" "$rx" "$tx" >"$state_file" + delta=$((now - old_now)) + [ "$delta" -gt 0 ] || delta=1 + down=$(((rx - old_rx) / delta)) + up=$(((tx - old_tx) / delta)) + [ "$down" -ge 0 ] || down=0 + [ "$up" -ge 0 ] || up=0 + printf '%s/%s\n' "$(human_bytes "$down")" "$(human_bytes "$up")" + ;; + scratch) + if command -v i3-msg >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then + i3-msg -t get_tree 2>/dev/null | + jq '[.. | objects | select(.name == "__i3_scratch") | .floating_nodes[]?] | length' 2>/dev/null || echo 0 + else + echo n/a + fi + ;; + temp) + if command -v sensors >/dev/null 2>&1; then + temp="$( + sensors 2>/dev/null | + awk ' + /Package id 0:/ { gsub(/[^0-9.-]/, "", $4); print int($4); exit } + /Tctl:/ { gsub(/[^0-9.-]/, "", $2); print int($2); exit } + /temp1:/ && $2 ~ /^\+/ { gsub(/[^0-9.-]/, "", $2); print int($2); exit } + ' + )" + else + temp="" + fi + if [ -z "$temp" ]; then + for zone in /sys/class/thermal/thermal_zone*/temp; do + [ -r "$zone" ] || continue + value="$(cat "$zone")" + if [ "$value" -gt 0 ] 2>/dev/null; then + temp=$((value / 1000)) + break + fi + done + fi + [ -n "${temp:-}" ] && echo "${temp}C" || echo n/a + ;; + updates) + if command -v checkupdates >/dev/null 2>&1; then + (checkupdates 2>/dev/null || true) | wc -l + elif command -v pacman >/dev/null 2>&1; then + (pacman -Qu 2>/dev/null || true) | wc -l + else + echo n/a + fi + ;; + volume) + if ! command -v pactl >/dev/null 2>&1; then + echo n/a + exit 0 + fi + mute="$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | awk '{ print $2 }' || true)" + vol="$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | awk 'NR == 1 { print $5; exit }' || true)" + [ "$mute" = yes ] && echo muted || echo "${vol:-n/a}" + ;; + wg) + if [ ! -d "/sys/class/net/$profile" ]; then + echo down + exit 0 + fi + if ! command -v wg >/dev/null 2>&1; then + echo up + exit 0 + fi + latest="$(wg show "$profile" latest-handshakes 2>/dev/null | awk 'NF { if ($2 > newest) newest = $2 } END { print newest + 0 }' || true)" + if [ -z "$latest" ] || [ "$latest" -le 0 ] 2>/dev/null; then + echo up + exit 0 + fi + age=$(($(date +%s) - latest)) + if [ "$age" -lt 60 ]; then suffix="${age}s"; elif [ "$age" -lt 3600 ]; then suffix="$((age / 60))m"; elif [ "$age" -lt 86400 ]; then suffix="$((age / 3600))h"; else suffix="$((age / 86400))d"; fi + echo "up $suffix" + ;; + *) + echo n/a + exit 1 + ;; +esac diff --git a/quickshell/gruvbar/shell.qml b/quickshell/gruvbar/shell.qml new file mode 100644 index 0000000..0d03f70 --- /dev/null +++ b/quickshell/gruvbar/shell.qml @@ -0,0 +1,631 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.I3 +import Quickshell.Io + +ShellRoot { + id: root + + property bool expanded: false + property int pendingWorkspace: 0 + property int barHeight: 38 + property int drawerHeight: 118 + property string scriptDir: Quickshell.shellPath("scripts") + property string fontFamily: "FiraCode Nerd Font" + property string bg: "#282828" + property string bg1: "#3c3836" + property string bg2: "#504945" + property string bg3: "#665c54" + property string fg: "#ebdbb2" + property string fg1: "#d5c4a1" + property string red: "#cc241d" + property string green: "#98971a" + property string yellow: "#d79921" + property string orange: "#d65d0e" + property string purple: "#b16286" + property string aqua: "#689d6a" + property string gray: "#928374" + property string clockText: Qt.formatDateTime(new Date(), "hh:mm dd MMM") + + signal refreshRequested(string actionName) + + function statusCommand(name) { + return [root.scriptDir + "/status.sh", name]; + } + + function requestStatusRefresh(actionName) { + root.refreshRequested(actionName); + quickStatusRefresh.actionName = actionName; + settleStatusRefresh.actionName = actionName; + lateStatusRefresh.actionName = actionName; + quickStatusRefresh.restart(); + settleStatusRefresh.restart(); + lateStatusRefresh.restart(); + } + + function runAction(name, arg) { + var cmd = [root.scriptDir + "/action.sh", name]; + if (arg !== undefined && arg !== "") + cmd.push(arg); + + Quickshell.execDetached(cmd); + root.requestStatusRefresh(name); + } + + function switchWorkspace(workspaceNumber) { + root.pendingWorkspace = workspaceNumber; + pendingWorkspaceReset.restart(); + I3.dispatch("workspace number " + workspaceNumber); + } + + function textColor(value, fallback) { + if (value === "n/a") + return root.gray; + + if (value.indexOf("down") === 0 || value.indexOf("muted") === 0) + return root.red; + + if (value.indexOf("dnd") === 0 || value.indexOf("night") === 0) + return root.orange; + + if (value === "on" || value.indexOf("up") === 0 || value === "day") + return root.green; + + return fallback; + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: root.clockText = Qt.formatDateTime(new Date(), "hh:mm dd MMM") + } + + Timer { + id: quickStatusRefresh + + property string actionName: "" + + interval: 120 + onTriggered: root.refreshRequested(actionName) + } + + Timer { + id: settleStatusRefresh + + property string actionName: "" + + interval: 500 + onTriggered: root.refreshRequested(actionName) + } + + Timer { + id: lateStatusRefresh + + property string actionName: "" + + interval: 1600 + onTriggered: root.refreshRequested(actionName) + } + + Timer { + id: pendingWorkspaceReset + + interval: 700 + onTriggered: root.pendingWorkspace = 0 + } + + ListModel { + id: workspaceModel + + ListElement { + ws: 1 + } + + ListElement { + ws: 2 + } + + ListElement { + ws: 3 + } + + ListElement { + ws: 4 + } + + ListElement { + ws: 5 + } + + ListElement { + ws: 6 + } + + ListElement { + ws: 7 + } + + ListElement { + ws: 8 + } + + ListElement { + ws: 9 + } + + ListElement { + ws: 10 + } + + } + + Variants { + model: Quickshell.screens + + PanelWindow { + id: bar + + required property var modelData + readonly property bool canExpand: modelData.name === Quickshell.screens[0].name + + screen: modelData + implicitHeight: root.barHeight + exclusiveZone: root.barHeight + aboveWindows: true + + anchors { + top: true + left: true + right: true + } + + Rectangle { + anchors.fill: parent + color: root.bg + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 1 + color: root.bg2 + } + + Row { + id: taskStrip + + anchors.centerIn: parent + height: 30 + spacing: 6 + + ToggleButton { + visible: bar.canExpand + } + + Repeater { + model: workspaceModel + + delegate: WorkspaceButton { + required property int ws + + workspaceNumber: ws + } + + } + + } + + Row { + id: tray + + anchors.right: parent.right + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + height: 28 + spacing: 5 + + StatusPill { + title: "ntf" + command: root.statusCommand("dunst") + actionName: "dunst" + accent: root.yellow + } + + StatusPill { + title: "wake" + command: root.statusCommand("awake") + actionName: "awake" + accent: root.orange + } + + StatusPill { + title: "wg" + command: root.statusCommand("wg") + actionName: "wg" + accent: root.aqua + } + + StatusPill { + title: "vol" + command: root.statusCommand("volume") + actionName: "volume" + actionArg: "mute" + wheelAction: "volume" + wheelUpArg: "up" + wheelDownArg: "down" + accent: root.green + } + + StatusPill { + title: "kbd" + command: root.statusCommand("keyboard") + actionName: "keyboard" + accent: root.purple + } + + ClockPill { + } + + } + + } + + PopupWindow { + id: drawer + + visible: root.expanded && bar.canExpand + grabFocus: false + anchor.window: bar + anchor.rect.x: Math.max(8, Math.round((bar.width - implicitWidth) / 2)) + anchor.rect.y: root.barHeight + 6 + implicitWidth: Math.max(320, Math.min(760, bar.width - 24)) + implicitHeight: root.drawerHeight + color: "transparent" + + Rectangle { + anchors.fill: parent + radius: 7 + color: root.bg1 + border.width: 1 + border.color: root.bg3 + + RowLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + Flow { + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 7 + + StatusPill { + title: "net" + command: root.statusCommand("net") + accent: root.green + } + + StatusPill { + title: "spd" + command: root.statusCommand("netspeed") + accent: root.green + } + + StatusPill { + title: "tmp" + command: root.statusCommand("temp") + accent: root.orange + } + + StatusPill { + title: "cpu" + command: root.statusCommand("cpu") + accent: root.orange + } + + StatusPill { + title: "ram" + command: root.statusCommand("mem") + accent: root.purple + } + + StatusPill { + title: "disk" + command: root.statusCommand("disk") + accent: root.aqua + } + + StatusPill { + title: "upd" + command: root.statusCommand("updates") + actionName: "updates" + interval: 900000 + accent: root.yellow + } + + StatusPill { + title: "disp" + command: root.statusCommand("display") + actionName: "display" + wheelAction: "display-brightness" + wheelUpArg: "up" + wheelDownArg: "down" + accent: root.orange + } + + StatusPill { + title: "mic" + command: root.statusCommand("mic") + actionName: "mic" + accent: root.purple + } + + StatusPill { + title: "mus" + command: root.statusCommand("music") + actionName: "music" + interval: 2000 + accent: root.green + } + + StatusPill { + title: "scratch" + command: root.statusCommand("scratch") + actionName: "scratch" + accent: root.yellow + } + + } + + ColumnLayout { + Layout.preferredWidth: 84 + Layout.fillHeight: true + spacing: 7 + + ActionButton { + label: "shot" + actionName: "screenshot" + accent: root.aqua + } + + ActionButton { + label: "clip" + actionName: "clipboard" + accent: root.purple + } + + ActionButton { + label: "power" + actionName: "power" + accent: root.red + } + + } + + } + + } + + } + + } + + } + + component WorkspaceButton: Rectangle { + id: wsButton + + required property int workspaceNumber + readonly property bool focused: I3.focusedWorkspace !== null && I3.focusedWorkspace.number === workspaceNumber + readonly property bool active: focused || root.pendingWorkspace === workspaceNumber + + Layout.preferredWidth: 30 + Layout.preferredHeight: 28 + width: 30 + height: 28 + radius: 4 + color: active ? root.yellow : (mouse.containsMouse ? root.bg2 : root.bg1) + border.width: 1 + border.color: active ? root.yellow : root.bg2 + + Text { + anchors.centerIn: parent + text: String(wsButton.workspaceNumber) + color: wsButton.active ? root.bg : root.fg1 + font.family: root.fontFamily + font.pixelSize: 13 + font.bold: wsButton.active + } + + MouseArea { + id: mouse + + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onClicked: root.switchWorkspace(wsButton.workspaceNumber) + onWheel: wheel.angleDelta.y > 0 ? I3.dispatch("workspace next") : I3.dispatch("workspace prev") + } + + } + + component ToggleButton: Rectangle { + Layout.preferredHeight: 28 + Layout.preferredWidth: 68 + width: 68 + height: 28 + radius: 5 + color: root.expanded ? root.bg2 : root.bg1 + border.width: 1 + border.color: root.yellow + + Text { + anchors.centerIn: parent + text: root.expanded ? "close" : "start" + color: root.yellow + font.family: root.fontFamily + font.pixelSize: 12 + font.bold: true + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.expanded = !root.expanded + } + + } + + component ClockPill: Rectangle { + width: 122 + height: 28 + radius: 5 + color: root.bg1 + border.width: 1 + border.color: root.bg2 + + Text { + anchors.centerIn: parent + text: root.clockText + color: root.fg + font.family: root.fontFamily + font.pixelSize: 12 + font.bold: true + } + + } + + component ActionButton: Rectangle { + id: button + + property string label: "" + property string actionName: "" + property string actionArg: "" + property string accent: root.fg1 + + Layout.preferredWidth: 76 + Layout.preferredHeight: 28 + width: 76 + height: 28 + radius: 5 + color: mouse.pressed ? root.bg3 : (mouse.containsMouse ? root.bg2 : root.bg) + border.width: 1 + border.color: accent + + Text { + id: labelText + + anchors.centerIn: parent + text: button.label + color: button.accent + font.family: root.fontFamily + font.pixelSize: 12 + font.bold: true + } + + MouseArea { + id: mouse + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.runAction(button.actionName, button.actionArg) + } + + } + + component StatusPill: Rectangle { + id: pill + + property string title: "" + property var command: [] + property int interval: 2000 + property string value: "..." + property string actionName: "" + property string actionArg: "" + property string wheelAction: "" + property string wheelUpArg: "" + property string wheelDownArg: "" + property string accent: root.fg1 + + function refresh() { + if (pill.command.length > 0) + proc.exec(pill.command); + + } + + function matchesRefresh(actionName) { + return actionName === "" || actionName === pill.actionName || actionName === pill.wheelAction; + } + + Layout.preferredHeight: 28 + Layout.preferredWidth: Math.max(56, label.implicitWidth + 18) + width: Math.max(56, label.implicitWidth + 18) + height: 28 + radius: 5 + color: mouse.pressed ? root.bg3 : (mouse.containsMouse ? root.bg2 : root.bg) + border.width: 1 + border.color: root.bg2 + + Text { + id: label + + anchors.centerIn: parent + text: pill.title + " " + pill.value + color: root.textColor(pill.value, pill.accent) + font.family: root.fontFamily + font.pixelSize: 12 + } + + Process { + id: proc + + command: pill.command + running: true + + stdout: StdioCollector { + onStreamFinished: pill.value = this.text.trim() + } + + } + + Connections { + function onRefreshRequested(actionName) { + if (pill.matchesRefresh(actionName)) + pill.refresh(); + + } + + target: root + } + + Timer { + interval: pill.interval + running: true + repeat: true + onTriggered: pill.refresh() + } + + MouseArea { + id: mouse + + anchors.fill: parent + hoverEnabled: true + cursorShape: pill.actionName !== "" ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + if (pill.actionName !== "") + root.runAction(pill.actionName, pill.actionArg); + + } + onWheel: { + if (pill.wheelAction !== "") + root.runAction(pill.wheelAction, wheel.angleDelta.y > 0 ? pill.wheelUpArg : pill.wheelDownArg); + + } + } + + } + +} diff --git a/sddm/10-gruvbox-void.conf b/sddm/10-gruvbox-void.conf new file mode 100644 index 0000000..28dd1dd --- /dev/null +++ b/sddm/10-gruvbox-void.conf @@ -0,0 +1,4 @@ +[Theme] +Current=gruvbox-void +ThemeDir=/usr/share/sddm/themes +Font=FiraCode Nerd Font diff --git a/sddm/themes/gruvbox-void/Main.qml b/sddm/themes/gruvbox-void/Main.qml new file mode 100644 index 0000000..88fc0cf --- /dev/null +++ b/sddm/themes/gruvbox-void/Main.qml @@ -0,0 +1,331 @@ +import QtQuick 2.0 +import SddmComponents 2.0 + +Rectangle { + id: root + + property int sessionIndex: session.index + property string fontFamily: config.font ? config.font : "FiraCode Nerd Font" + property color bg: "#282828" + property color bg1: "#3c3836" + property color bg2: "#504945" + property color bg3: "#665c54" + property color fg: "#ebdbb2" + property color fg1: "#d5c4a1" + property color red: "#cc241d" + property color green: "#98971a" + property color yellow: "#d79921" + property color orange: "#d65d0e" + property color purple: "#b16286" + property color aqua: "#689d6a" + property string clockText: Qt.formatDateTime(new Date(), "hh:mm ddd dd MMM") + + width: 1920 + height: 1080 + color: bg + Component.onCompleted: { + if (name.text == "") + name.focus = true; + else + password.focus = true; + } + + TextConstants { + id: textConstants + } + + Connections { + function onLoginSucceeded() { + promptMessage.text = textConstants.loginSucceeded; + promptMessage.color = green; + } + + function onLoginFailed() { + password.text = ""; + promptMessage.text = textConstants.loginFailed; + promptMessage.color = red; + } + + function onInformationMessage(message) { + promptMessage.text = message; + promptMessage.color = orange; + } + + target: sddm + } + + Timer { + interval: 1000 + running: true + repeat: true + onTriggered: root.clockText = Qt.formatDateTime(new Date(), "hh:mm ddd dd MMM") + } + + Background { + anchors.fill: parent + source: config.background + fillMode: Image.PreserveAspectCrop + } + + Rectangle { + anchors.fill: parent + color: "#99282828" + } + + Rectangle { + id: topbar + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 42 + color: bg + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 1 + color: bg2 + } + + Text { + anchors.left: parent.left + anchors.leftMargin: 16 + anchors.verticalCenter: parent.verticalCenter + text: "void / i3" + color: yellow + font.family: fontFamily + font.pixelSize: 14 + font.bold: true + } + + Text { + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.verticalCenter: parent.verticalCenter + text: root.clockText + color: fg + font.family: fontFamily + font.pixelSize: 13 + font.bold: true + } + + } + + Rectangle { + id: panel + + width: Math.min(430, parent.width - 80) + height: 356 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + radius: 6 + color: "#e6282828" + border.width: 1 + border.color: bg3 + + Column { + anchors.fill: parent + anchors.margins: 22 + spacing: 12 + + Text { + width: parent.width + text: textConstants.welcomeText.arg(sddm.hostName) + color: fg + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + font.family: fontFamily + font.pixelSize: 18 + font.bold: true + } + + Text { + width: parent.width + text: textConstants.userName + color: fg1 + font.family: fontFamily + font.pixelSize: 12 + font.bold: true + } + + TextBox { + id: name + + width: parent.width + height: 34 + text: userModel.lastUser + color: bg1 + borderColor: bg3 + focusColor: yellow + hoverColor: aqua + textColor: fg + radius: 4 + font.family: fontFamily + font.pixelSize: 14 + KeyNavigation.backtab: rebootButton + KeyNavigation.tab: password + Keys.onPressed: { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + sddm.login(name.text, password.text, sessionIndex); + event.accepted = true; + } + } + } + + Text { + width: parent.width + text: textConstants.password + color: fg1 + font.family: fontFamily + font.pixelSize: 12 + font.bold: true + } + + PasswordBox { + id: password + + width: parent.width + height: 34 + color: bg1 + borderColor: bg3 + focusColor: yellow + hoverColor: aqua + textColor: fg + radius: 4 + font.family: fontFamily + font.pixelSize: 14 + tooltipFG: fg + tooltipBG: bg1 + KeyNavigation.backtab: name + KeyNavigation.tab: session + Keys.onPressed: { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + sddm.login(name.text, password.text, sessionIndex); + event.accepted = true; + } + } + } + + Row { + width: parent.width + height: 34 + spacing: 10 + + ComboBox { + id: session + + width: Math.floor((parent.width - parent.spacing) * 0.62) + height: parent.height + model: sessionModel + index: sessionModel.lastIndex + color: bg1 + menuColor: bg1 + borderColor: bg3 + focusColor: yellow + hoverColor: aqua + textColor: fg + arrowColor: bg2 + font.family: fontFamily + font.pixelSize: 13 + KeyNavigation.backtab: password + KeyNavigation.tab: layoutBox + } + + LayoutBox { + id: layoutBox + + width: parent.width - session.width - parent.spacing + height: parent.height + color: bg1 + menuColor: bg1 + borderColor: bg3 + focusColor: yellow + hoverColor: aqua + textColor: fg + arrowColor: bg2 + font.family: fontFamily + font.pixelSize: 13 + KeyNavigation.backtab: session + KeyNavigation.tab: loginButton + } + + } + + Text { + id: promptMessage + + width: parent.width + height: 18 + text: textConstants.prompt + color: fg1 + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + font.family: fontFamily + font.pixelSize: 11 + } + + Row { + width: parent.width + height: 34 + spacing: 8 + + Button { + id: loginButton + + width: Math.floor((parent.width - (parent.spacing * 2)) / 3) + height: parent.height + text: textConstants.login + color: yellow + activeColor: orange + pressedColor: green + textColor: bg + font.family: fontFamily + font.pixelSize: 13 + onClicked: sddm.login(name.text, password.text, sessionIndex) + KeyNavigation.backtab: layoutBox + KeyNavigation.tab: shutdownButton + } + + Button { + id: shutdownButton + + width: loginButton.width + height: parent.height + text: textConstants.shutdown + color: bg2 + activeColor: red + pressedColor: red + textColor: fg + font.family: fontFamily + font.pixelSize: 13 + onClicked: sddm.powerOff() + KeyNavigation.backtab: loginButton + KeyNavigation.tab: rebootButton + } + + Button { + id: rebootButton + + width: parent.width - loginButton.width - shutdownButton.width - (parent.spacing * 2) + height: parent.height + text: textConstants.reboot + color: bg2 + activeColor: orange + pressedColor: orange + textColor: fg + font.family: fontFamily + font.pixelSize: 13 + onClicked: sddm.reboot() + KeyNavigation.backtab: shutdownButton + KeyNavigation.tab: name + } + + } + + } + + } + +} diff --git a/sddm/themes/gruvbox-void/metadata.desktop b/sddm/themes/gruvbox-void/metadata.desktop new file mode 100644 index 0000000..26d1fa7 --- /dev/null +++ b/sddm/themes/gruvbox-void/metadata.desktop @@ -0,0 +1,16 @@ +[SddmGreeterTheme] +Name=Gruvbox Void +Description=Gruvbox SDDM theme matching the local i3 setup +Author=aag +Copyright=2026 +License=MIT +Type=sddm-theme +Version=1.0 +Website= +Screenshot= +MainScript=Main.qml +ConfigFile=theme.conf +TranslationsDirectory=translations +Email= +Theme-Id=gruvbox-void +Theme-API=2.0 diff --git a/sddm/themes/gruvbox-void/theme.conf b/sddm/themes/gruvbox-void/theme.conf new file mode 100644 index 0000000..f1a1ab6 --- /dev/null +++ b/sddm/themes/gruvbox-void/theme.conf @@ -0,0 +1,3 @@ +[General] +background=void.png +font=FiraCode Nerd Font -- cgit v1.2.3