summaryrefslogtreecommitdiff
path: root/config.org
diff options
context:
space:
mode:
Diffstat (limited to 'config.org')
-rw-r--r--config.org391
1 files changed, 362 insertions, 29 deletions
diff --git a/config.org b/config.org
index 0d1e0d0..a9292d6 100644
--- a/config.org
+++ b/config.org
@@ -289,24 +289,22 @@
* erc
-ERC configuration for connecting to IRC via Soju bouncer. Connects to
-both Libera.Chat and Ergo networks. Perspective integration isolates
-IRC buffers in their own workspace. Consult source for ERC-only buffer
-switching. Evil normal-mode keybindings for channel navigation.
-
#+begin_src emacs-lisp
(use-package erc
:straight nil
:defer t
:custom
+ ;; connection
(erc-nick "cashmere1337")
(erc-user-full-name "cashmere")
+ ;; behaviour
(erc-join-buffer 'buffer)
(erc-kill-buffer-on-part t)
(erc-kill-queries-on-quit t)
(erc-kill-server-buffer-on-quit t)
+ ;; visuals
(erc-fill-function 'erc-fill-wrap)
(erc-timestamp-format "[%H:%M]")
(erc-timestamp-format-left "[%H:%M]")
@@ -316,8 +314,10 @@ switching. Evil normal-mode keybindings for channel navigation.
(erc-track-shorten-start 4)
(erc-track-visibility 'visible)
+ ;; prompt
(erc-prompt (lambda () (concat (buffer-name) ">")))
+ ;; modules
(erc-modules '(autojoin
button
completion
@@ -337,20 +337,26 @@ switching. Evil normal-mode keybindings for channel navigation.
stamp
track))
:config
+ ;; highlight own nick
(setq erc-current-nick-highlight-type 'all)
(setq erc-keywords '("cashmere"))
(setq erc-pals '("cashmere"))
+ ;; nicks module (built-in nick coloring in Emacs 30)
(setq erc-nicks-contrast-range '(40 . 90))
+ ;; scrolltobottom -- keep input always at bottom
(setq erc-scrolltobottom-all t)
(erc-scrolltobottom-mode 1)
+ ;; fill-wrap -- modern chat-like wrapping
(setq erc-fill-wrap-align-prompt nil)
(setq erc-fill-static-center 14)
+ ;; track -- activity in modeline
(setq erc-track-position-in-mode-line t)
+ ;; keep large buffer for soju history replay
(setq erc-max-buffer-size 100000)
(erc-update-modules))
@@ -1056,6 +1062,25 @@ Not necessary but an eyecandy to look at!
#+end_src
+** kitty-graphics
+
+Display inline images in terminal Emacs via the Kitty graphics protocol.
+Only activates when running in a Kitty terminal (not in GUI Emacs).
+
+#+begin_src emacs-lisp
+(use-package kitty-graphics
+ :straight (:local-repo "~/projects/kitty-graphics")
+ :if (and (not (display-graphic-p)) (getenv "KITTY_PID"))
+ :custom
+ (kitty-gfx-max-width 80)
+ (kitty-gfx-max-height 24)
+ :config
+ (kitty-graphics-mode 1)
+ ;; Preview images in dired/dirvish with 'P'
+ (with-eval-after-load 'dired
+ (define-key dired-mode-map (kbd "P") #'kitty-gfx-dired-preview)))
+#+end_src
+
** org-fancy-properties
#+begin_src emacs-lisp
@@ -1112,6 +1137,40 @@ Old config
(makefile . t))))
#+end_src
+** my/snip-upload
+
+Upload buffer, region, or file to snips.sh and copy URL.
+
+#+begin_src emacs-lisp
+(defun my/snip-upload (&optional file)
+ "Upload FILE, region, or current buffer to snips.sh.
+Copies the resulting URL to the kill ring and clipboard."
+ (interactive)
+ (let* ((input (cond
+ (file file)
+ ((use-region-p)
+ (buffer-substring-no-properties (region-beginning) (region-end)))
+ (t (buffer-substring-no-properties (point-min) (point-max)))))
+ (cmd "ssh pb@pb.cashmere.rs 2>/dev/null | sed 's/\\x1b\\[[0-9;]*m//g' | grep -oP 'https://\\S+/f/\\S+'")
+ (url (string-trim
+ (if (and file (file-exists-p file))
+ (shell-command-to-string (format "cat %s | %s" (shell-quote-argument file) cmd))
+ (with-temp-buffer
+ (insert input)
+ (shell-command-on-region (point-min) (point-max) cmd nil t)
+ (buffer-string))))))
+ (if (string-prefix-p "https://" url)
+ (progn
+ (kill-new url)
+ (message "Uploaded: %s" url))
+ (message "Upload failed"))))
+
+(defun my/snip-upload-file ()
+ "Prompt for a file and upload it to snips.sh."
+ (interactive)
+ (my/snip-upload (read-file-name "Upload to snips.sh: ")))
+#+end_src
+
** my/paste-md-to-org
[[http://yummymelon.com/devnull/import-markdown-to-org-with-the-clipboard-in-emacs.html][nfdn: Import Markdown to Org with the Clipboard in Emacs]]
@@ -1842,14 +1901,14 @@ Only inserts when the buffer has just the front-matter (fresh file)."
(<= (count-lines (point-min) (point-max)) 6))
(goto-char (point-max))
(insert
- "\n* 󱎫 \n"
+ "\n* Clockreport \n"
"\n#+BEGIN: clocktable :scope file :maxlevel 3 :emphasize nil :link t\n"
"#+END:\n"
- "\n*  \n"
- "\n** Plan the day\n"
+ "\n* Agenda \n"
+ "\n** NEXT Plan the day\n"
":LOGBOOK:\n"
":END:\n"
- "\n* 󰧑 \n"
+ "\n* Braindump \n"
":PROPERTIES:\n"
":VISIBILITY: folded\n"
":END:\n")))
@@ -2056,6 +2115,25 @@ Some goodies for better integration with org files.
#+end_src
+** denote-merge
+
+#+begin_src emacs-lisp
+(use-package denote-merge
+ :ensure t ; not in any package archive
+ :straight (:host github :repo "protesilaos/denote-merge")
+ ;; You can bind these to keys. They are here so you can learn about them.
+ :commands
+ (denote-merge-file
+ denote-merge-region
+ denote-merge-region-plain
+ denote-merge-region-plain-indented
+ denote-merge-region-org-src
+ denote-merge-region-org-quote
+ denote-merge-region-org-example
+ denote-merge-region-markdown-quote
+ denote-merge-region-markdown-fenced-block))
+#+end_src
+
* which-key
#+begin_src emacs-lisp
@@ -2644,30 +2722,74 @@ ily <3.
Emacs Application Framework -- browser and terminal via Qt widgets.
Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
-#+begin_src emacs-lisp
-(require 'eaf)
-(require 'eaf-browser)
-(require 'eaf-pyqterminal)
-
-(setq browse-url-browser-function 'eaf-open-browser)
+EAF loading is deferred until a graphical frame exists so the daemon
+can start headless without crashing. All requires are wrapped in
+=condition-case= so a broken EAF never takes down the rest of init.
-(setq eaf-pyqterminal-font-size 24
+#+begin_src emacs-lisp
+;; Settings can be declared before EAF loads -- they are just variables.
+(setq eaf-pyqterminal-font-size 24
eaf-pyqterminal-refresh-ms 17
- eaf-pyqterminal-font-family "Maple Mono NF"
- eaf-pyqterminal-cursor-type "bar"
- eaf-pyqterminal-cursor-size 1)
+ eaf-pyqterminal-font-family "GeistMono Nerd Font"
+ ;; eaf-pyqterminal-cursor-type "bar"
+ ;; eaf-pyqterminal-cursor-size 1
+ )
-(with-eval-after-load 'evil
- (require 'eaf-evil)
- (setq eaf-evil-leader-key "C-SPC")
+(defvar my/eaf-loaded nil
+ "Non-nil after EAF has been successfully loaded.")
- (add-hook 'after-init-hook
- (lambda ()
- (let ((leader-km (lookup-key
+(defun my/eaf-init ()
+ "Load EAF and its apps, but only when a graphical display is available.
+Errors are caught so a broken EAF never kills the Emacs session."
+ (when (and (display-graphic-p) (not my/eaf-loaded))
+ (condition-case err
+ (progn
+ (require 'eaf)
+ (require 'eaf-browser)
+ (require 'eaf-pyqterminal)
+ (require 'eaf-auralis)
+
+ ;; Evil leader integration
+ (when (featurep 'evil)
+ (require 'eaf-evil)
+ (setq eaf-evil-leader-key "C-SPC")
+ (let ((leader-km (when (boundp 'general-override-mode-map)
+ (lookup-key
(evil-get-auxiliary-keymap general-override-mode-map 'normal)
- (kbd "SPC"))))
- (when (keymapp leader-km)
- (setq eaf-evil-leader-keymap leader-km))))))
+ (kbd "SPC")))))
+ (when (keymapp leader-km)
+ (setq eaf-evil-leader-keymap leader-km))))
+
+ ;; Process sentinel: clean up buffers when the EAF python
+ ;; process dies unexpectedly instead of leaving zombies.
+ (advice-add 'eaf--start-process :after
+ (lambda (&rest _)
+ (when-let* ((proc (get-process "eaf")))
+ (set-process-sentinel
+ proc
+ (lambda (process event)
+ (when (memq (process-status process) '(exit signal))
+ (message "EAF process terminated: %s" (string-trim event))
+ (dolist (buf (buffer-list))
+ (when (and (buffer-live-p buf)
+ (with-current-buffer buf
+ (derived-mode-p 'eaf-mode)))
+ (with-current-buffer buf
+ (let ((inhibit-read-only t))
+ (erase-buffer)
+ (insert (format "EAF process crashed: %s\nRe-open this buffer to restart."
+ (string-trim event)))))))))))))
+
+ (setq my/eaf-loaded t)
+ (message "EAF loaded successfully."))
+ (error
+ (message "EAF failed to load: %s" (error-message-string err))))))
+
+;; Daemon: wait for the first graphical frame, then load EAF once.
+;; Normal start: load immediately.
+(if (daemonp)
+ (add-hook 'server-after-make-frame-hook #'my/eaf-init)
+ (add-hook 'after-init-hook #'my/eaf-init))
#+end_src
* olivetti
@@ -3112,6 +3234,184 @@ Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
(evil-org-agenda-set-keys))
#+end_src
+* perspective
+
+Workspace isolation via =perspective.el=. Each perspective has its own
+buffer list so switching buffers only shows relevant ones. Per-frame --
+every =emacsclient -c= frame gets its own set of perspectives.
+
+#+begin_src emacs-lisp
+(use-package perspective
+ :ensure t
+ :demand t
+ :custom
+ (persp-sort 'created)
+ (persp-suppress-no-prefix-key-warning t)
+ :init
+ (setq persp-mode-prefix-key (kbd "C-c M-p"))
+ :config
+ (persp-mode)
+
+ ;; ---- consult integration --------------------------------------
+ ;; Make SPC , show only buffers from the current perspective.
+ ;; SPC TAB B gives the unfiltered global list.
+
+ (with-eval-after-load 'consult
+ (consult-customize consult--source-buffer :hidden t :default nil)
+ (add-to-list 'consult-buffer-sources persp-consult-source))
+
+ ;; ---- EAF workspace helpers ------------------------------------
+
+ (defun persp-eaf-open (persp-name eaf-open-fn)
+ "Switch to PERSP-NAME and call EAF-OPEN-FN unless a live EAF buffer exists."
+ (unless my/eaf-loaded
+ (user-error "EAF is not loaded -- open a graphical frame first"))
+ (persp-switch persp-name)
+ (unless (cl-some (lambda (buf)
+ (and (buffer-live-p buf)
+ (with-current-buffer buf
+ (derived-mode-p 'eaf-mode))))
+ (persp-buffers (persp-curr)))
+ (condition-case err
+ (funcall eaf-open-fn)
+ (error
+ (message "EAF open failed in perspective '%s': %s"
+ persp-name (error-message-string err))))))
+
+ (defun persp-eaf-terminal ()
+ "Open pyqterminal in the terminal perspective."
+ (interactive)
+ (persp-eaf-open "terminal" #'eaf-open-pyqterminal))
+
+ (defun persp-eaf-auralis ()
+ "Open the auralis dashboard in the auralis perspective."
+ (interactive)
+ (persp-eaf-open "auralis" #'eaf-open-auralis))
+
+ ;; Reposition Qt widgets in ALL windows after a perspective switch,
+ ;; not just the current buffer. Without this, EAF overlays in
+ ;; non-selected windows become misaligned.
+ (add-hook 'persp-activated-hook
+ (lambda ()
+ (when (fboundp 'eaf-monitor-configuration-change)
+ (walk-windows
+ (lambda (win)
+ (with-current-buffer (window-buffer win)
+ (when (derived-mode-p 'eaf-mode)
+ (eaf-monitor-configuration-change))))
+ nil (selected-frame)))))
+
+ ;; ---- keybindings via general ----------------------------------
+
+ (my-leader
+ "TAB" '(:ignore t :wk "perspective")
+ "TAB TAB" '(persp-switch :wk "switch")
+ "TAB n" '(persp-next :wk "next")
+ "TAB p" '(persp-prev :wk "prev")
+ "TAB d" '(persp-kill :wk "delete")
+ "TAB r" '(persp-rename :wk "rename")
+ "TAB b" '(persp-switch-to-buffer :wk "buffer")
+ "TAB B" '(consult-buffer :wk "global buffer")
+ "TAB a" '(persp-add-buffer :wk "add buffer")
+ "TAB k" '(persp-remove-buffer :wk "remove buffer")
+ "TAB 1" '(persp-eaf-terminal :wk "terminal")
+ "TAB 2" '(persp-eaf-auralis :wk "auralis")))
+#+end_src
+
+* eaf-frames
+
+Open EAF apps in dedicated frames (independent WM windows). Each frame
+is named so it can be found and focused instead of spawning duplicates.
+Useful for tiling EAF apps on separate monitors via mango keybinds.
+
+#+begin_src emacs-lisp
+(defvar eaf-named-frames nil
+ "Alist of (NAME . frame) for dedicated EAF frames.")
+
+(defun eaf-frame-open (name open-fn &optional width height)
+ "Open or focus a dedicated frame NAME running EAF app via OPEN-FN.
+If OPEN-FN signals an error the newly created frame is deleted
+so no empty zombie frames are left behind."
+ (interactive)
+ (unless my/eaf-loaded
+ (user-error "EAF is not loaded -- open a graphical frame first"))
+ (let ((existing (alist-get name eaf-named-frames nil nil #'string=)))
+ (if (and existing (frame-live-p existing))
+ (select-frame-set-input-focus existing)
+ ;; Remove stale entry
+ (setq eaf-named-frames
+ (assoc-delete-all name eaf-named-frames #'string=))
+ (let ((frame (make-frame `((name . ,(format "EAF: %s" name))
+ (width . ,(or width 120))
+ (height . ,(or height 40))))))
+ (select-frame-set-input-focus frame)
+ (condition-case err
+ (progn
+ (push (cons name frame) eaf-named-frames)
+ (funcall open-fn))
+ (error
+ (setq eaf-named-frames
+ (assoc-delete-all name eaf-named-frames #'string=))
+ (delete-frame frame)
+ (message "EAF frame '%s' failed: %s" name
+ (error-message-string err))))))))
+
+(defun eaf-frame-pyqterminal ()
+ "Open pyqterminal in a dedicated frame."
+ (interactive)
+ (eaf-frame-open "terminal" #'eaf-open-pyqterminal))
+
+(defun eaf-frame-auralis ()
+ "Open auralis dashboard in a dedicated frame."
+ (interactive)
+ (eaf-frame-open "auralis" #'eaf-open-auralis))
+
+(defun eaf-frame-browser (url)
+ "Open EAF browser in a dedicated frame."
+ (interactive "sURL: ")
+ (eaf-frame-open "browser" (lambda () (eaf-open-browser url))))
+
+(my-leader
+ "o" '(:ignore t :wk "open frame")
+ "ot" '(eaf-frame-pyqterminal :wk "terminal")
+ "oa" '(eaf-frame-auralis :wk "auralis")
+ "ob" '(eaf-frame-browser :wk "browser"))
+#+end_src
+
+* auralis music controls
+
+Native Emacs keybindings for auralis music player control. These work
+globally (no EAF buffer required) by calling the auralis API directly.
+Includes modeline now-playing indicator.
+
+#+begin_src emacs-lisp
+;; Load auralis unconditionally — it only makes HTTP calls and has no
+;; EAF dependency. Auto-enable the modeline polling timer on load.
+(condition-case err
+ (progn
+ (require 'auralis)
+ (auralis-modeline-enable))
+ (error (message "auralis failed to load: %s" (error-message-string err))))
+
+(my-leader
+ "m" '(:ignore t :wk "music")
+ "m SPC" '(auralis-play-pause :wk "play/pause")
+ "mn" '(auralis-next :wk "next track")
+ "mp" '(auralis-prev :wk "prev track")
+ "ms" '(auralis-search :wk "search library")
+ "mr" '(auralis-play-random :wk "shuffle all")
+ "mi" '(auralis-now-playing :wk "now playing")
+ "m+" '(auralis-volume-up :wk "volume up")
+ "m-" '(auralis-volume-down :wk "volume down")
+ "mS" '(auralis-toggle-shuffle :wk "toggle shuffle")
+ "mR" '(auralis-toggle-repeat :wk "toggle repeat")
+ "mA" '(auralis-toggle-radio :wk "toggle auto-radio")
+ "mq" '(auralis-stop :wk "stop")
+ "mL" '(auralis-scan-library :wk "scan library")
+ "mm" '(auralis-modeline-enable :wk "modeline on")
+ "mM" '(auralis-modeline-disable :wk "modeline off"))
+#+end_src
+
* nerd icons
#+begin_src emacs-lisp
@@ -3609,6 +3909,16 @@ Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
(desc (tmr-mode-line--format-description timer)))
(concat remaining desc)))
+ (maple-modeline-define-segment auralis-now-playing
+ :if (and (boundp 'auralis--modeline-string)
+ (not (string-empty-p auralis--modeline-string)))
+ :format
+ (concat
+ (maple-modeline--concat-or
+ (maple-modeline--icon 'mdicon "nf-md-music" face)
+ "♪")
+ auralis--modeline-string))
+
(maple-modeline-define my-custom-style
:left ((evil :left (bar :left ""))
macro
@@ -3620,6 +3930,7 @@ Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
:right (narrow
org-clock-task
tmr-timer
+ auralis-now-playing
python
my-modeline-time
process
@@ -4341,7 +4652,11 @@ Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
"d" '(:ignore t :wk "denote")
"dj" '(denote-journal-new-or-existing-entry :wk "journal")
"dd" '(denote-menu-list-notes t :wk "List all notes")
+ "dm" '(:ignore t :wk "Merge Notes")
+ "dmr" '(denote-merge-region :wk "Merge Region")
+ "dmf" '(denote-merge-file :wk "Merge File")
"dg" '(consult-denote-grep t :wk "Search")
+ "dl" '(denote-link-or-create t :wk "Link Note")
"dn" '(denote t :wk "Create a new note")
"dr" '(denote-rename-file t :wk "Rename Note")
"dtl" '(tmr-list-timers :wk "list timer")
@@ -4393,6 +4708,8 @@ Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
"gb" '(vc-annotate :wk "blame")
"o" '(:ignore t :wk "open")
+ "os" '(my/snip-upload :wk "snip buffer/region")
+ "oS" '(my/snip-upload-file :wk "snip file")
"h" '(:ignore t :wk "help")
"hm" '(describe-mode :wk "mode")
@@ -4504,6 +4821,7 @@ Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
"m," '(org-priority :wk "priority")
"mI" '(org-clock-in :wk "clock in")
"mO" '(org-clock-out :wk "clock out")
+
"l" '(:ignore :wk "link")
"lc" '(org-cliplink :wk "cliplink")
"li" '(org-download-clipboard :wk "image")
@@ -4722,7 +5040,6 @@ Evil integration uses =C-SPC= to access the leader keymap inside EAF buffers.
((string< a-feed b-feed) t)
((string> a-feed b-feed) nil)
(t (string< a-title b-title)))))))
-
(use-package elfeed-org
:ensure t
:after elfeed
@@ -4881,6 +5198,22 @@ this package provides the ability to display rgb and hex colors.
;; (profiler-stop)
#+end_src
+* ewm
+
+#+begin_src emacs-lisp
+(use-package ewm
+ :straight nil
+ :commands (ewm-start-module ewm-launch-app ewm-toggle-fullscreen)
+ :custom
+ (ewm-mouse-follows-focus t)
+ ;; (ewm-output-config '(("DP-1" :width 2560 :height 1440)))
+ :config
+ (when (and (boundp 'ewm-mode-map) (keymapp ewm-mode-map))
+ ;; Override defaults with your preferred commands if needed
+ ;; (define-key ewm-mode-map (kbd "s-d") #'consult-buffer)
+ ))
+#+end_src
+
* provide init
#+begin_src emacs-lisp