Compare commits

...
2 Commits
6 changed files with 320 additions and 56 deletions
+1 -11
View File
@@ -1,15 +1,5 @@
config/emacs/backups/
config/emacs/eln-cache/
config/emacs/elpa/
config/emacs/auto-save/
config/emacs/auto-save-list/
config/emacs/custom.el
config/emacs/places.eld
config/emacs/projects.eld
config/emacs/recentf.eld
config/emacs/.ignore config/emacs/.ignore
config/emacs/session* config/emacs/var/
config/emacs/history
config/fish/conf.d config/fish/conf.d
config/fish/functions config/fish/functions
config/fish/fish_variables config/fish/fish_variables
+3
View File
@@ -21,6 +21,9 @@
;;; Code: ;;; Code:
(startup-redirect-eln-cache (expand-file-name "var/eln-cache/" user-emacs-directory))
(setq package-user-dir (expand-file-name "var/elpa/" user-emacs-directory))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Garbage Collection ;; Garbage Collection
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+35 -45
View File
@@ -5,16 +5,31 @@
;; to the window on the left. ;; to the window on the left.
;;; Code: ;;; Code:
;;;; 1. Basics ;;;; 1. Emac setup, files, packages and paths
(let ((bin (expand-file-name "~/.local/bin"))) (let ((bin (expand-file-name "~/.local/bin")))
(add-to-list 'exec-path bin) (add-to-list 'exec-path bin)
(setenv "PATH" (concat bin path-separator (getenv "PATH")))) (setenv "PATH" (concat bin path-separator (getenv "PATH"))))
;; Custom's output goes to its own file, not here. ;; all the emacs runtime files live in var/.
(setq custom-file (expand-file-name "custom.el" user-emacs-directory)) ;; early-init.el redirects eln-cache and elpa.
(defvar my/var (locate-user-emacs-file "var/"))
(make-directory (concat my/var "transient/") t)
(setq savehist-file (concat my/var "history")
save-place-file (concat my/var "places.eld")
recentf-save-file (concat my/var "recentf.eld")
project-list-file (concat my/var "projects.eld")
transient-history-file (concat my/var "transient/history.el")
transient-levels-file (concat my/var "transient/levels.el")
transient-values-file (concat my/var "transient/values.el"))
;; Custom output goes to its own file, not here.
(setq custom-file (expand-file-name "var/custom.el" user-emacs-directory))
(load custom-file 'noerror 'nomessage) (load custom-file 'noerror 'nomessage)
;; Package archives: GNU and NonGNU are built in, MELPA is added at lowest priority. ;; No session manager save: stops the empty session.* files.
(add-hook 'emacs-save-session-functions #'always)
;; Package GNU, NonGNU are built in melpa additional lowest priority.
(require 'package) (require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)
(setq package-archive-priorities '(("gnu" . 3) ("nongnu" . 2) ("melpa" . 1))) (setq package-archive-priorities '(("gnu" . 3) ("nongnu" . 2) ("melpa" . 1)))
@@ -23,7 +38,8 @@
(require 'use-package) (require 'use-package)
(setq use-package-always-ensure t) (setq use-package-always-ensure t)
;;;; 2. UI ;;;; 2. Ui settings, theme setup and fonts
(add-to-list 'default-frame-alist '(fullscreen . maximized))
(menu-bar-mode -1) (menu-bar-mode -1)
(tool-bar-mode -1) (tool-bar-mode -1)
(scroll-bar-mode -1) (scroll-bar-mode -1)
@@ -46,43 +62,29 @@
(electric-pair-mode 1) (electric-pair-mode 1)
(which-key-mode 1) (which-key-mode 1)
;; Jellybeans theme.
(add-to-list 'warning-suppress-types '(files)) (add-to-list 'warning-suppress-types '(files))
(use-package jbeans-theme (use-package jbeans-theme
:config :config
(load-theme 'jbeans t)) (load-theme 'jbeans t))
;; Font, on a darker background than the theme's. (set-face-attribute 'default nil :family "JetBrainsMono Nerd Font" :height 120)
(set-face-attribute 'default nil (setq-default line-spacing '(4 . 4))
:family "JetBrainsMono Nerd Font"
:height 120
:background "#131313")
;; Mode line at the top of each window: the header line takes over its ;; set mode line at the top of each window
;; format and look, on the editor background.
(setq-default header-line-format mode-line-format mode-line-format nil) (setq-default header-line-format mode-line-format mode-line-format nil)
(set-face-attribute 'header-line nil :inherit 'mode-line :background "#131313") (set-face-attribute 'header-line nil :inherit 'mode-line :background "#131313")
(setq-default line-spacing '(4 . 4))
;; Command launcher. ;; Command launcher.
(keymap-global-set "C-S-p" #'execute-extended-command) (keymap-global-set "C-S-p" #'execute-extended-command)
;; Escape cancels prompts. keyboard-quit can't leave the minibuffer, so there ;; Escape behaves like C-g
;; Escape runs abort-minibuffers, as C-g does.
(keymap-global-set "<escape>" #'keyboard-quit) (keymap-global-set "<escape>" #'keyboard-quit)
(keymap-set minibuffer-local-map "<escape>" #'abort-minibuffers) (keymap-set minibuffer-local-map "<escape>" #'abort-minibuffers)
(add-to-list 'default-frame-alist '(fullscreen . maximized))
;;;; 3. Files ;;;; 3. Files
(dolist (d '("backups/" "auto-save/")) (setq make-backup-files nil
(make-directory (locate-user-emacs-file d) t)) auto-save-default nil
auto-save-list-file-prefix nil
(setq backup-directory-alist
`(("." . ,(locate-user-emacs-file "backups/")))
auto-save-file-name-transforms
`((".*" ,(locate-user-emacs-file "auto-save/") t))
create-lockfiles nil create-lockfiles nil
confirm-kill-processes nil confirm-kill-processes nil
vc-follow-symlinks t) vc-follow-symlinks t)
@@ -93,8 +95,7 @@
(global-auto-revert-mode 1) (global-auto-revert-mode 1)
;;;; 4. Completion ;;;; 4. Completion
;; C-n/C-p move through candidates: Vertico remaps next-line/previous-line, ;; C-n/C-p navigates up and down, section 5 stops evil from overrriding
;; and section 5 stops Evil from taking those keys in the minibuffer.
(use-package vertico (use-package vertico
:init :init
(vertico-mode 1) (vertico-mode 1)
@@ -117,7 +118,6 @@
(vertico-posframe-mode 1)) (vertico-posframe-mode 1))
;;;; 5. Evil ;;;; 5. Evil
;; Evil 1.15.0 on Emacs 31 reads this legacy variable, which Emacs no longer defines.
(defvar evil-mode-buffers nil) (defvar evil-mode-buffers nil)
(defun my/insert-tab () (defun my/insert-tab ()
@@ -127,8 +127,6 @@
(use-package evil (use-package evil
:init :init
;; evil-want-keybinding: Evil Collection (below) supplies mode bindings instead
;; of Evil's small built-in set. It has to be set before Evil loads.
(setq evil-want-C-u-scroll t (setq evil-want-C-u-scroll t
evil-undo-system 'undo-redo evil-undo-system 'undo-redo
evil-want-keybinding nil) evil-want-keybinding nil)
@@ -137,21 +135,16 @@
(evil-mode 1) (evil-mode 1)
(evil-define-key 'insert 'global (evil-define-key 'insert 'global
;; Literal tab. <tab> as well, or a mode's own <tab> binding wins in GUI frames.
(kbd "TAB") #'my/insert-tab (kbd "TAB") #'my/insert-tab
(kbd "<tab>") #'my/insert-tab (kbd "<tab>") #'my/insert-tab
;; complete using the buffer's completion sources.
;; C-SPC: complete using the buffer's completion sources.
(kbd "C-SPC") #'completion-at-point (kbd "C-SPC") #'completion-at-point
(kbd "C-@") #'completion-at-point) (kbd "C-@") #'completion-at-point)
;; Global navigation.
;; C-p, C-f and C-b are assigned to Consult below. ;; C-p, C-f and C-b are assigned to Consult below.
(evil-define-key '(normal motion) 'global (evil-define-key '(normal motion) 'global
(kbd "C-e") #'dired-jump)) (kbd "C-e") #'dired-jump))
;; Evil Collection: vim-style bindings for other packages, one mode at a time.
;; Info here (F1 i); Magit, Magit sections and commit messages in git.el.
(use-package evil-collection (use-package evil-collection
:after evil :after evil
:custom :custom
@@ -172,13 +165,12 @@
(kbd "<escape>") #'abort-minibuffers) (kbd "<escape>") #'abort-minibuffers)
;; Window navigation (normal/motion state): Ctrl+hjkl move, Ctrl+` closes. ;; Window navigation (normal/motion state): Ctrl+hjkl move, Ctrl+` closes.
;; GUI only for C-`: terminals send it as C-@.
(evil-define-key '(normal motion) 'global (evil-define-key '(normal motion) 'global
(kbd "C-h") #'windmove-left (kbd "C-h") #'windmove-left
(kbd "C-j") #'windmove-down (kbd "C-j") #'windmove-down
(kbd "C-k") #'windmove-up (kbd "C-k") #'windmove-up
(kbd "C-l") #'windmove-right (kbd "C-l") #'windmove-right
(kbd "C-`") #'delete-window) (kbd "C-q") #'delete-window)
;; Result buffers: hjkl motions and RET to jump to a result. ;; Result buffers: hjkl motions and RET to jump to a result.
(dolist (m '(occur-mode grep-mode xref--xref-buffer-mode)) (dolist (m '(occur-mode grep-mode xref--xref-buffer-mode))
@@ -269,12 +261,10 @@
(when-let* ((win (get-buffer-window (process-buffer proc)))) (when-let* ((win (get-buffer-window (process-buffer proc))))
(select-window win)))) (select-window win))))
;;;; 9. Languages ;;;; 9. Modules - additional functionality outside of the core init.el
;; Magit and the :git command live in modules/git.el.
(load (locate-user-emacs-file "modules/git.el") nil 'nomessage)
;; Setup for dev languages, modes, lsp and lsp keybinds. ;; Setup for dev languages, modes, lsp and lsp keybinds.
(load (locate-user-emacs-file "development.el") nil 'nomessage) (load (locate-user-emacs-file "modules/development.el") nil 'nomessage)
;;;; 10. Git
;; Magit and the :git command live in git.el, next to this file.
(load (locate-user-emacs-file "git.el") nil 'nomessage)
;;; init.el ends here ;;; init.el ends here
+281
View File
@@ -0,0 +1,281 @@
#!/usr/bin/env bash
# focus - small window helpers for xfwm4
# focus next|prev move focus right/left through windows (stops at the ends)
# focus up|down focus the nearest window directly above/below, if any
# focus tile master/stack tiling: active window left, the rest stacked right
# (a single window is simply maximized)
# Deps: wmctrl xdotool xprop xwininfo
set -u
# ---- config -----------------------------------------------------------------
MASTER_RATIO=50 # master width as % of the usable width
GAP=0 # px between windows and around the screen edge
USE_WORKAREA=1 # 1 = ask xfwm4 for the usable area (already excludes panels)
PANEL_TOP=0 # extra px to keep free on each side; if USE_WORKAREA=0,
PANEL_BOTTOM=0 # put your panel sizes here instead
PANEL_LEFT=0
PANEL_RIGHT=0
SETTLE=0.05 # seconds to let xfwm4 finish un-maximizing before moving
VERIFY=1 # 1 = measure each window afterwards and fix pixel offsets
VERIFY_TRIES=2 # correction passes per window
# ---- generic helpers --------------------------------------------------------
die() { echo "${0##*/}: $*" >&2; exit 1; }
usage() { echo "usage: ${0##*/} next|prev|up|down|tile" >&2; exit 1; }
# window geometry from wmctrl, keyed by id (filled by collect_windows)
declare -A GX GY GW GH
need() {
local c
for c in "$@"; do command -v "$c" >/dev/null || die "missing dependency: $c"; done
}
# Active window id in wmctrl's 0x%08x format (0x00000000 if none)
active_window() {
local d
d=$(xdotool getactivewindow 2>/dev/null) || d=0
printf '0x%08x' "$d"
}
# index_of ID -> prints index in WINS, or -1
index_of() {
local i
for i in "${!WINS[@]}"; do
[[ ${WINS[i]} == "$1" ]] && { echo "$i"; return; }
done
echo -1
}
# collect_windows focus|tile -> fills WINS, ordered left to right on screen
# (then top to bottom), so "next" moves right and "prev" moves left.
# focus: normal windows + dialogs, minimized included (focusing restores them)
# vert: normal windows + dialogs, minimized skipped (they have no real position)
# tile: normal windows only, minimized skipped
collect_windows() {
local mode=$1 cur id desk x y w h props
local -a found=()
cur=$(xdotool get_desktop) || die "cannot read current desktop"
WINS=()
while read -r id desk x y w h _; do
[[ $desk == "$cur" || $desk == -1 ]] || continue # this desktop or sticky
props=$(xprop -id "$id" _NET_WM_WINDOW_TYPE _NET_WM_STATE 2>/dev/null) || continue
if [[ $props == *"_NET_WM_WINDOW_TYPE(ATOM)"* ]]; then
if [[ $mode == tile ]]; then
[[ $props == *_NET_WM_WINDOW_TYPE_NORMAL* ]] || continue
else
[[ $props == *_NET_WM_WINDOW_TYPE_NORMAL* || $props == *_NET_WM_WINDOW_TYPE_DIALOG* ]] || continue
fi
fi
[[ $props == *_NET_WM_STATE_SKIP_TASKBAR* ]] && continue # panels, desktop, notifyd
[[ $mode != focus && $props == *_NET_WM_STATE_HIDDEN* ]] && continue
GX[$id]=$x GY[$id]=$y GW[$id]=$w GH[$id]=$h
found+=("$x $y $id")
done < <(wmctrl -lG)
(( ${#found[@]} )) || return 0
while read -r _ _ id; do WINS+=("$id"); done < <(printf '%s\n' "${found[@]}" | sort -n -k1,1 -k2,2)
}
# ---- focus ------------------------------------------------------------------
focus_cycle() {
local dir=$1 n idx
collect_windows focus
n=${#WINS[@]}
(( n > 0 )) || return 0
idx=$(index_of "$(active_window)")
if (( idx < 0 )); then
# nothing of ours focused (desktop/panel): enter at the matching end
[[ $dir == next ]] && idx=0 || idx=$(( n - 1 ))
elif [[ $dir == next ]]; then
(( idx < n - 1 )) || return 0 # already rightmost: stop, no wrap
(( idx += 1 ))
else
(( idx > 0 )) || return 0 # already leftmost: stop, no wrap
(( idx -= 1 ))
fi
wmctrl -ia "${WINS[idx]}"
}
# focus_vertical up|down -- nearest window straight above/below the active one.
# "Straight above/below" = shares some horizontal span with it. No match: do nothing.
focus_vertical() {
local dir=$1 act id ax1 ax2 acy cy dy best="" best_dy=-1
collect_windows vert
act=$(active_window)
[[ -n ${GX[$act]+x} ]] || return 0
ax1=${GX[$act]}; ax2=$(( ax1 + GW[$act] ))
acy=$(( GY[$act] + GH[$act] / 2 ))
for id in "${WINS[@]}"; do
[[ $id == "$act" ]] && continue
(( GX[$id] < ax2 && GX[$id] + GW[$id] > ax1 )) || continue # no horizontal overlap
cy=$(( GY[$id] + GH[$id] / 2 ))
if [[ $dir == up ]]; then dy=$(( acy - cy )); else dy=$(( cy - acy )); fi
(( dy > 0 )) || continue # wrong side
if (( best_dy < 0 || dy < best_dy )); then best=$id best_dy=$dy; fi
done
[[ -n $best ]] && wmctrl -ia "$best"
return 0
}
# ---- geometry helpers -------------------------------------------------------
# workarea -> WA_X WA_Y WA_W WA_H (usable rectangle on this desktop)
workarea() {
local cur raw i
local -a a
cur=$(xdotool get_desktop)
raw=$(xprop -root _NET_WORKAREA 2>/dev/null)
if (( USE_WORKAREA )) && [[ $raw == *=* ]]; then
IFS=', ' read -r -a a <<< "${raw#*= }"
i=$(( cur * 4 ))
(( i + 3 < ${#a[@]} )) || i=0
WA_X=${a[i]} WA_Y=${a[i+1]} WA_W=${a[i+2]} WA_H=${a[i+3]}
else
read -r WA_W WA_H < <(xdotool getdisplaygeometry)
WA_X=0 WA_Y=0
fi
(( WA_X += PANEL_LEFT, WA_Y += PANEL_TOP,
WA_W -= PANEL_LEFT + PANEL_RIGHT, WA_H -= PANEL_TOP + PANEL_BOTTOM ))
}
# frame_extents ID -> FL FR FT FB (decoration size; 0 if not reported)
frame_extents() {
local raw
FL=0 FR=0 FT=0 FB=0
raw=$(xprop -id "$1" _NET_FRAME_EXTENTS 2>/dev/null) || return 0
[[ $raw == *=* ]] && IFS=', ' read -r FL FR FT FB <<< "${raw#*= }"
}
# client_position ID -> CX CY (real on-screen top-left of the client area)
client_position() {
read -r CX CY < <(xwininfo -id "$1" 2>/dev/null | awk '
/Absolute upper-left X/ {x=$NF} /Absolute upper-left Y/ {y=$NF}
END {print x+0, y+0}')
}
# ---- window actions ---------------------------------------------------------
# Drop states that make xfwm4 ignore move/resize (maximized, xfwm4 edge-tiling, fullscreen, shaded)
reset_state() {
wmctrl -ir "$1" -b remove,maximized_vert,maximized_horz
wmctrl -ir "$1" -b remove,fullscreen,shaded
}
# maximize ID -- clear fullscreen/shaded, then set the WM's maximized state
maximize() {
wmctrl -ir "$1" -b remove,fullscreen,shaded
wmctrl -ir "$1" -b add,maximized_vert,maximized_horz
wmctrl -ia "$1"
}
# place ID X Y W H -- X Y W H is the OUTER rectangle including decorations
place() {
local id=$1 x=$2 y=$3 w=$4 h=$5 cw ch
frame_extents "$id"
cw=$(( w - FL - FR )); (( cw < 1 )) && cw=1
ch=$(( h - FT - FB )); (( ch < 1 )) && ch=1
# gravity 1 (NorthWest): x,y = top-left of the frame, w,h = client size
wmctrl -ir "$id" -e "1,$x,$y,$cw,$ch"
}
# verify ID X Y W H -- measure where the window really landed and nudge it if off
verify() {
local id=$1 x=$2 y=$3 w=$4 h=$5 try dx dy
for (( try = 0; try < VERIFY_TRIES; try++ )); do
frame_extents "$id"
client_position "$id"
dx=$(( CX - (x + FL) ))
dy=$(( CY - (y + FT) ))
(( dx == 0 && dy == 0 )) && return 0
place "$id" $(( x - dx )) $(( y - dy )) "$w" "$h"
sleep "$SETTLE"
done
}
# ---- layout -----------------------------------------------------------------
# layout_master_stack N -> fills RECTS with "x y w h" outer rectangles, index 0 = master
# Integer remainders are handed out so the tiles cover the area exactly, no 1px gaps.
layout_master_stack() {
local n=$1 x0 y0 w0 h0 mw sw sx avail base rem i y hh
RECTS=()
x0=$(( WA_X + GAP )); y0=$(( WA_Y + GAP ))
w0=$(( WA_W - 2 * GAP )); h0=$(( WA_H - 2 * GAP ))
if (( n == 1 )); then
RECTS+=("$x0 $y0 $w0 $h0")
return
fi
mw=$(( (w0 - GAP) * MASTER_RATIO / 100 ))
sw=$(( w0 - GAP - mw ))
sx=$(( x0 + mw + GAP ))
RECTS+=("$x0 $y0 $mw $h0")
(( n -= 1 ))
avail=$(( h0 - (n - 1) * GAP ))
base=$(( avail / n )); rem=$(( avail % n ))
y=$y0
for (( i = 0; i < n; i++ )); do
hh=$(( base + (i < rem ? 1 : 0) ))
RECTS+=("$sx $y $sw $hh")
(( y += hh + GAP ))
done
}
# ---- tile -------------------------------------------------------------------
tile() {
local n idx master i
(( VERIFY )) && need xwininfo
collect_windows tile
n=${#WINS[@]}
(( n > 0 )) || return 0
# only one window: let xfwm4 maximize it (real maximized state, not fullscreen)
if (( n == 1 )); then
maximize "${WINS[0]}"
return 0
fi
# active window becomes master, the rest keep their order in the stack
idx=$(index_of "$(active_window)")
if (( idx > 0 )); then
master=${WINS[idx]}
WINS=("$master" "${WINS[@]:0:idx}" "${WINS[@]:idx+1}")
fi
workarea
layout_master_stack "$n"
for i in "${!WINS[@]}"; do reset_state "${WINS[i]}"; done
sleep "$SETTLE"
for i in "${!WINS[@]}"; do
# shellcheck disable=SC2086
place "${WINS[i]}" ${RECTS[i]}
done
if (( VERIFY )); then
sleep "$SETTLE"
for i in "${!WINS[@]}"; do
# shellcheck disable=SC2086
verify "${WINS[i]}" ${RECTS[i]}
done
fi
wmctrl -ia "${WINS[0]}"
}
# ---- main -------------------------------------------------------------------
need wmctrl xdotool xprop
case ${1:-next} in
next|prev) focus_cycle "${1:-next}" ;;
up|down) focus_vertical "$1" ;;
tile) tile ;;
*) usage ;;
esac