From bfff2fbfb57a638f8381ee41ee13421f5cbea321 Mon Sep 17 00:00:00 2001 From: Ian Keane Date: Sat, 8 Aug 2026 14:31:15 -0400 Subject: [PATCH] Touchpad, mpv bindings, floating window --- config/fish/config.fish | 5 + config/profile | 5 + config/stumpwm/config.lisp | 295 ++++++++++++++++++++-------- config/xorg.conf.d/70-touchpad.conf | 8 + link-root.sh | 57 ++++++ link.sh | 2 + scripts/mpv-url.sh | 5 + scripts/yt-download.sh | 5 + 8 files changed, 303 insertions(+), 79 deletions(-) create mode 100644 config/xorg.conf.d/70-touchpad.conf create mode 100755 link-root.sh create mode 100755 scripts/mpv-url.sh create mode 100755 scripts/yt-download.sh diff --git a/config/fish/config.fish b/config/fish/config.fish index bc4abfc..bee77d9 100644 --- a/config/fish/config.fish +++ b/config/fish/config.fish @@ -17,3 +17,8 @@ set -gx LANG en_US.UTF-8 if status is-interactive set -gx GPG_TTY (tty) end + +# Added by LM Studio CLI (lms) +set -gx PATH $PATH /home/green/.lmstudio/bin +# End of LM Studio CLI section + diff --git a/config/profile b/config/profile index 7ad9510..7780006 100644 --- a/config/profile +++ b/config/profile @@ -11,3 +11,8 @@ export SHELL=/usr/bin/fish export TERMINAL=wezterm export PATH="$HOME/.dotfiles/scripts:$PATH" export PATH="/usr/local/bin:$PATH" + +# Added by LM Studio CLI (lms) +export PATH="$PATH:/home/green/.lmstudio/bin" +# End of LM Studio CLI section + diff --git a/config/stumpwm/config.lisp b/config/stumpwm/config.lisp index 141a130..77d392b 100644 --- a/config/stumpwm/config.lisp +++ b/config/stumpwm/config.lisp @@ -1,6 +1,5 @@ (in-package :stumpwm) -;; change the prefix key to something else (set-prefix-key (kbd "C-t")) ;; prompt the user for an interactive command. The first arg is an @@ -10,7 +9,6 @@ (when cmd (eval-command cmd t)))) - ;; Read some doc (define-key *root-map* (kbd "d") "exec gv") ;; Browse somewhere @@ -26,129 +24,268 @@ (nsubstitute #\+ #\Space search) (run-shell-command (concatenate 'string ,prefix search)))) -(make-web-jump "duckduckgo" "firefox https://duckduckgo.com/?q=") -(make-web-jump "imdb" "firefox http://www.imdb.com/find?q=") - -;; C-t M-s is a terrble binding, but you get the idea. -(define-key *root-map* (kbd "M-s") "duckduckgo") -(define-key *root-map* (kbd "i") "imdb") - -;; Message/help window styling +;;; Appearance (set-fg-color "#d8dee9") (set-bg-color "#1b2b34") (set-border-color "#6699cc") (setf *normal-border-width* 2) (setf *window-border-style* :thin) (setf *ignore-wm-inc-hints* t) +(set-font "-b&h-luxi mono-medium-r-normal--20-*-*-*-m-0-iso10646-1") +(setf *message-window-padding* 16) +(setf *message-window-gravity* :center) +(setf *input-window-gravity* :center) + +;;; Modules (set-module-dir "~/.stumpwm.d/modules/util") (load-module "swm-gaps") (setf swm-gaps:*inner-gaps-size* 8) (setf swm-gaps:*outer-gaps-size* 16) +;;; Frame behaviour +;; Prevent new frames from sucking windows from other frames on creation +(setf *new-frame-action* :empty) +(setf *suppress-frame-indicator* t) + +;;; Window placement policy +(define-frame-preference "Default" + (0 t nil :class "Konqueror" :role "...konqueror-mainwindow") + (1 t nil :class "XTerm") + (:float t t :class "alsamixer-scratch")) + +(define-frame-preference "Emacs" + (1 t t :restore "emacs-editing-dump" :title "...xdvi") + (0 t t :create "emacs-dump" :class "Emacs")) + +;;; Commands + +(defcommand kill-window-or-frame () () + "Kill the current window, or remove the frame if empty." + (if (current-window) + (delete-window) + (remove-split))) + +(defcommand toggle-fullscreen () () + "Toggle fullscreen for the current window." + (fullscreen)) + +(defcommand focus-left () () + "Focus the frame to the left." + (move-focus :left)) + +(defcommand focus-right () () + "Focus the frame to the right." + (move-focus :right)) + +(defcommand focus-up () () + "Focus the frame above." + (move-focus :up)) + +(defcommand focus-down () () + "Focus the frame below." + (move-focus :down)) + +(defcommand split-horizontal () () + "Split the current frame horizontally." + (split-frame-in-dir :column)) + +(defcommand split-vertical () () + "Split the current frame vertically." + (split-frame-in-dir :row)) + +(defcommand cycle-next () () + "Cycle to the next window in the current frame." + (next-in-frame)) + +(defcommand cycle-prev () () + "Cycle to the previous window in the current frame." + (prev-in-frame)) + +(defcommand rofi-run () () + "Open rofi run launcher." + (run-shell-command "rofi -show run")) + +(defcommand rofi-pull-window () () + "Pull a window from the group into the current frame using rofi." + (let* ((windows (remove (current-window) (group-windows (current-group)))) + (names (format nil "~{~a~%~}" (mapcar #'window-name windows))) + (selection (run-prog-collect-output + "/bin/sh" "-c" + (format nil "echo '~a' | rofi -i -dmenu -p 'window'" names)))) + (when (and selection (not (string= selection ""))) + (let ((win (find (string-trim '(#\Newline) selection) + windows :key #'window-name :test #'string=))) + (when win (pull-window win)))))) + (defcommand gaps-off () () + "Disable gaps." (swm-gaps:toggle-gaps-off)) (defcommand gaps-small () () + "Set small gaps." (setf swm-gaps:*inner-gaps-size* 8 swm-gaps:*outer-gaps-size* 8) (swm-gaps:toggle-gaps-on)) (defcommand gaps-medium () () + "Set medium gaps." (setf swm-gaps:*inner-gaps-size* 16 swm-gaps:*outer-gaps-size* 16) (swm-gaps:toggle-gaps-on)) (defcommand gaps-large () () + "Set large gaps." (setf swm-gaps:*inner-gaps-size* 32 swm-gaps:*outer-gaps-size* 32) (swm-gaps:toggle-gaps-on)) +(defcommand mpv-pick () () + "Pick a video from ~/Videos and play it in mpv." + (run-shell-command "mpv-pick.sh")) + +(defcommand mpv-url () () + "Play the URL in the clipboard with mpv." + (run-shell-command "mpv-url.sh")) + +(defcommand yt-download () () + "Download the URL in the clipboard to a chosen subdirectory of ~/Videos with yt-dlp." + (let* ((url (run-prog-collect-output "/usr/bin/xclip" "-o" "-selection" "clipboard")) + (url (string-trim '(#\Newline) url))) + (if (string= url "") + (message "Clipboard is empty!") + (let* ((dir (run-prog-collect-output + "/bin/sh" "-c" + "{ echo '.'; find $HOME/Videos -maxdepth 1 -mindepth 1 -type d -exec basename {} \\; | sort; } | rofi -i -dmenu -p 'download to'" )) + (dir (string-trim '(#\Newline) dir))) + (when (not (string= dir "")) + (let ((dest (format nil "~a/Videos/~a" (getenv "HOME") dir))) + (run-shell-command (format nil "cd ~s && yt-dlp ~s" dest url)) + (message "Downloading to ~a..." dest))))))) + +;;; Brightness commands +(defcommand brightness-10 () () + "Set brightness to 10%." + (run-shell-command "brightnessctl set 10%")) +(defcommand brightness-20 () () + "Set brightness to 20%." + (run-shell-command "brightnessctl set 20%")) +(defcommand brightness-30 () () + "Set brightness to 30%." + (run-shell-command "brightnessctl set 30%")) +(defcommand brightness-40 () () + "Set brightness to 40%." + (run-shell-command "brightnessctl set 40%")) +(defcommand brightness-50 () () + "Set brightness to 50%." + (run-shell-command "brightnessctl set 50%")) +(defcommand brightness-60 () () + "Set brightness to 60%." + (run-shell-command "brightnessctl set 60%")) +(defcommand brightness-70 () () + "Set brightness to 70%." + (run-shell-command "brightnessctl set 70%")) +(defcommand brightness-80 () () + "Set brightness to 80%." + (run-shell-command "brightnessctl set 80%")) +(defcommand brightness-90 () () + "Set brightness to 90%." + (run-shell-command "brightnessctl set 90%")) +(defcommand brightness-100 () () + "Set brightness to 100%." + (run-shell-command "brightnessctl set 100%")) + +;;; Float terminal scratchpad system +;; +;; StumpWM's define-frame-preference supports :float as the frame number, which +;; causes the window to be floated *during* group-add-window, before tiling +;; logic runs. This is the only reliable way to float a window from config — +;; attempting to call float-window from *new-window-hook* is too late, as +;; StumpWM has already tiled it. The hook IS still useful though, for +;; positioning after the float is established. +;; +;; To add a new float terminal: +;; 1. Add (:float t t :class "my-class") to the "Default" define-frame-preference +;; 2. Call (register-float-term "my-class" "command" x y width height) +;; 3. Define a command that calls (spawn-or-focus "my-class" "command") + +(defvar *float-term-rules* '()) + +(defun register-float-term (class command x y width height) + "Register a floating terminal. Add a matching define-frame-preference rule too." + (setf *float-term-rules* + (cons (list class command x y width height) + (remove class *float-term-rules* :key #'car :test #'string=)))) + +(add-hook *new-window-hook* + (lambda (win) + (let ((rule (assoc (window-class win) *float-term-rules* :test #'string=))) + (when rule + (destructuring-bind (class command x y width height) rule + (declare (ignore class command)) + (stumpwm::float-window-move-resize win :x x :y y :width width :height height)))))) + +(defun spawn-or-focus (class command) + "Focus existing window with CLASS or spawn COMMAND." + (let ((win (find-if (lambda (w) (string= (window-class w) class)) + (screen-windows (current-screen))))) + (if win (focus-window win) (run-shell-command command)))) + +;; Registrations +(register-float-term "alsamixer-scratch" + "wezterm start --class alsamixer-scratch -- alsamixer" + 228 150 400 800) + +(defcommand alsamixer-float () () + "Open alsamixer in a floating wezterm window." + (spawn-or-focus "alsamixer-scratch" "wezterm start --class alsamixer-scratch -- alsamixer")) + +;;; Config keymap +(defvar *config-map* (make-sparse-keymap)) +(define-key *config-map* (kbd "a") "alsamixer-float") +(define-key *config-map* (kbd "t") "alsamixer-float") +(defvar *brightness-map* (make-sparse-keymap)) +(define-key *brightness-map* (kbd "1") "brightness-10") +(define-key *brightness-map* (kbd "2") "brightness-20") +(define-key *brightness-map* (kbd "3") "brightness-30") +(define-key *brightness-map* (kbd "4") "brightness-40") +(define-key *brightness-map* (kbd "5") "brightness-50") +(define-key *brightness-map* (kbd "6") "brightness-60") +(define-key *brightness-map* (kbd "7") "brightness-70") +(define-key *brightness-map* (kbd "8") "brightness-80") +(define-key *brightness-map* (kbd "9") "brightness-90") +(define-key *brightness-map* (kbd "0") "brightness-100") +(define-key *config-map* (kbd "b") *brightness-map*) + +;;; Keymaps + (defvar *gaps-map* (make-sparse-keymap)) (define-key *gaps-map* (kbd "0") "gaps-off") (define-key *gaps-map* (kbd "1") "gaps-small") (define-key *gaps-map* (kbd "2") "gaps-medium") (define-key *gaps-map* (kbd "3") "gaps-large") -(set-font "-b&h-luxi mono-medium-r-normal--20-*-*-*-m-0-iso10646-1") -(setf *message-window-padding* 16) -(setf *message-window-gravity* :center) -(setf *input-window-gravity* :center) - - - -;;; Define window placement policy... - -;; Clear rules -(clear-window-placement-rules) - -;; Last rule to match takes precedence! -;; TIP: if the argument to :title or :role begins with an ellipsis, a substring -;; match is performed. -;; TIP: if the :create flag is set then a missing group will be created and -;; restored from *data-dir*/create file. -;; TIP: if the :restore flag is set then group dump is restored even for an -;; existing group using *data-dir*/restore file. -(define-frame-preference "Default" - ;; frame raise lock (lock AND raise == jumpto) - (0 t nil :class "Konqueror" :role "...konqueror-mainwindow") - (1 t nil :class "XTerm")) - -(define-frame-preference "Ardour" - (0 t t :instance "ardour_editor" :type :normal) - (0 t t :title "Ardour - Session Control") - (0 nil nil :class "XTerm") - (1 t nil :type :normal) - (1 t t :instance "ardour_mixer") - (2 t t :instance "jvmetro") - (1 t t :instance "qjackctl") - (3 t t :instance "qjackctl" :role "qjackctlMainForm")) - -(define-frame-preference "Shareland" - (0 t nil :class "XTerm") - (1 nil t :class "aMule")) - -(define-frame-preference "Emacs" - (1 t t :restore "emacs-editing-dump" :title "...xdvi") - (0 t t :create "emacs-dump" :class "Emacs")) - -(defcommand kill-window-or-frame () () - (if (current-window) - (delete-window) - (remove-split))) - -;; Don't send a random window to a new split -(setf *new-frame-action* :empty) -(setf *suppress-frame-indicator* t) - -(defcommand rofi-pull-window () () - (let* ((windows (remove (current-window) (group-windows (current-group)))) - (names (format nil "~{~a~%~}" (mapcar #'window-name windows))) - (selection (run-prog-collect-output - "/bin/sh" "-c" - (format nil "echo '~a' | rofi -dmenu -p 'window'" names)))) - (when (and selection (not (string= selection ""))) - (let ((win (find (string-trim '(#\Newline) selection) - windows :key #'window-name :test #'string=))) - (when win (pull-window win)))))) (defvar *window-map* (make-sparse-keymap)) -(define-key *top-map* (kbd "s-W") *window-map*) (define-key *window-map* (kbd "r") "iresize") (define-key *window-map* (kbd "g") *gaps-map*) (defvar *media-map* (make-sparse-keymap)) -(define-key *top-map* (kbd "s-m") *media-map*) -(define-key *media-map* (kbd "v") - "exec mpv-pick.sh") +(define-key *media-map* (kbd "v") "mpv-pick") +(define-key *media-map* (kbd "m") "mpv-url") +(define-key *media-map* (kbd "d") "yt-download") -(define-key *top-map* (kbd "s-d") "exec rofi -show run") +;;; Top-level bindings +(define-key *top-map* (kbd "s-d") "rofi-run") +(define-key *top-map* (kbd "s-w") "rofi-pull-window") (define-key *top-map* (kbd "s-h") "move-focus left") (define-key *top-map* (kbd "s-j") "move-focus down") (define-key *top-map* (kbd "s-k") "move-focus up") (define-key *top-map* (kbd "s-l") "move-focus right") -(define-key *top-map* (kbd "s-w") "rofi-pull-window") (define-key *top-map* (kbd "s-p") "next-in-frame") (define-key *top-map* (kbd "s-n") "prev-in-frame") (define-key *top-map* (kbd "s-q") "kill-window-or-frame") +(define-key *top-map* (kbd "s-f") "toggle-fullscreen") +(define-key *top-map* (kbd "s-c") *config-map*) (define-key *top-map* (kbd "s--") "vsplit") -(define-key *top-map* (kbd "s-\|") "hsplit") - +(define-key *top-map* (kbd "s-\\") "hsplit") +(define-key *top-map* (kbd "s-W") *window-map*) +(define-key *top-map* (kbd "s-m") *media-map*) diff --git a/config/xorg.conf.d/70-touchpad.conf b/config/xorg.conf.d/70-touchpad.conf new file mode 100644 index 0000000..abc5441 --- /dev/null +++ b/config/xorg.conf.d/70-touchpad.conf @@ -0,0 +1,8 @@ +Section "InputClass" + Identifier "touchpad" + MatchIsTouchpad "on" + Driver "libinput" + Option "DisableWhileTyping" "true" + Option "Tapping" "on" + Option "NaturalScrolling" "false" +EndSection diff --git a/link-root.sh b/link-root.sh new file mode 100755 index 0000000..37b32a9 --- /dev/null +++ b/link-root.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Run this as root to install system-level config from dotfiles. +# Usage: sudo ./install-system.sh + +set -e + +DOTFILES="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [ "$EUID" -ne 0 ]; then + echo "please run as root" + exit 1 +fi + +backup_and_link() { + local src="$1" + local dst="$2" + + mkdir -p "$(dirname "$dst")" + + if [ -L "$dst" ]; then + echo "already a symlink, skipping: $dst" + return + fi + + if [ -e "$dst" ]; then + echo "backing up: $dst -> $dst.bak" + mv "$dst" "$dst.bak" + fi + + echo "linking: $dst -> $src" + ln -s "$src" "$dst" +} + +# Touchpad palm detection +backup_and_link "$DOTFILES/config/xorg.conf.d/70-touchpad.conf" "/etc/X11/xorg.conf.d/70-touchpad.conf" + +# Kanata key remapper +backup_and_link "$DOTFILES/config/kanata/rc.kanata" "/etc/rc.d/rc.kanata" +chmod +x /etc/rc.d/rc.kanata + +# NetworkManager wifi power saving fix +backup_and_link "$DOTFILES/config/NetworkManager/wifi-powersave-off.conf" "/etc/NetworkManager/conf.d/wifi-powersave-off.conf" + +if ! grep -q "rc.kanata" /etc/rc.d/rc.local; then + echo "" >> /etc/rc.d/rc.local + echo "# Start kanata key remapper" >> /etc/rc.d/rc.local + echo "if [ -x /etc/rc.d/rc.kanata ]; then" >> /etc/rc.d/rc.local + echo " /etc/rc.d/rc.kanata start" >> /etc/rc.d/rc.local + echo "fi" >> /etc/rc.d/rc.local + echo "added kanata to rc.local" +else + echo "rc.local already has kanata entry, skipping" +fi + +echo "" +echo "done. reboot or restart X for xorg changes to take effect." diff --git a/link.sh b/link.sh index c05dec2..94cbe83 100755 --- a/link.sh +++ b/link.sh @@ -58,6 +58,8 @@ backup_and_link "$DOTFILES/config/gnupg/gpg-agent.conf" "$HOME/.gnupg/gpg-agent. # pinentry-smart: TUI-aware wrapper (curses normally, tty inside senpai/mutt/etc.) mkdir -p "$HOME/.local/bin" backup_and_link "$DOTFILES/scripts/mpv-pick.sh" "$HOME/.local/bin/mpv-pick.sh" +backup_and_link "$DOTFILES/scripts/mpv-url.sh" "$HOME/.local/bin/mpv-url.sh" +backup_and_link "$DOTFILES/scripts/yt-download.sh" "$HOME/.local/bin/yt-download.sh" chmod +x "$DOTFILES/scripts/pinentry-smart" # GTK themes diff --git a/scripts/mpv-url.sh b/scripts/mpv-url.sh new file mode 100755 index 0000000..af4d0ec --- /dev/null +++ b/scripts/mpv-url.sh @@ -0,0 +1,5 @@ +#!/bin/bash +url=$(xclip -o -selection clipboard) +if [ -n "$url" ]; then + mpv "$url" +fi diff --git a/scripts/yt-download.sh b/scripts/yt-download.sh new file mode 100755 index 0000000..f4740e8 --- /dev/null +++ b/scripts/yt-download.sh @@ -0,0 +1,5 @@ +#!/bin/bash +url=$(xclip -o -selection clipboard) +if [ -n "$url" ]; then + cd $HOME/Videos && yt-dlp "$url" +fi