summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/auth-config.el82
-rw-r--r--modules/config-utilities.el12
-rw-r--r--modules/dirvish-config.el17
-rw-r--r--modules/flycheck-config.el54
-rw-r--r--modules/modeline-config.el3
-rw-r--r--modules/org-gcal-config.el5
-rw-r--r--modules/system-utils.el13
-rw-r--r--modules/transcription-config.el100
-rw-r--r--modules/video-audio-recording.el12
-rw-r--r--modules/weather-config.el25
10 files changed, 244 insertions, 79 deletions
diff --git a/modules/auth-config.el b/modules/auth-config.el
index 6b8a8ddb..2b52087e 100644
--- a/modules/auth-config.el
+++ b/modules/auth-config.el
@@ -24,9 +24,11 @@
:ensure nil ;; built in
:demand t ;; load this package immediately
:config
- (setenv "GPG_AGENT_INFO" nil) ;; disassociate with external gpg agent
- (setq auth-sources `(,authinfo-file)) ;; use authinfo.gpg (see user-constants.el)
- (setq auth-source-debug t)) ;; echo debug info to Messages
+ ;; USE gpg-agent for passphrase caching (400-day cache from gpg-agent.conf)
+ ;; (setenv "GPG_AGENT_INFO" nil) ;; DISABLED: was preventing gpg-agent cache
+ (setq auth-sources `(,authinfo-file)) ;; use authinfo.gpg (see user-constants.el)
+ (setq auth-source-debug t) ;; echo debug info to Messages
+ (setq auth-source-cache-expiry 86400)) ;; cache decrypted credentials for 24 hours
;; ----------------------------- Easy PG Assistant -----------------------------
;; Key management, cryptographic operations on regions and files, dired
@@ -40,5 +42,79 @@
;; (setq epa-pinentry-mode 'loopback) ;; emacs request passwords in minibuffer
(setq epg-gpg-program "gpg2")) ;; force use gpg2 (not gpg v.1)
+;; ---------------------------------- Plstore ----------------------------------
+;; Encrypted storage used by oauth2-auto for Google Calendar tokens.
+;; CRITICAL: Enable passphrase caching to prevent password prompts every 10 min.
+
+(use-package plstore
+ :ensure nil ;; built-in
+ :demand t
+ :config
+ ;; Cache passphrase indefinitely (relies on gpg-agent for actual caching)
+ (setq plstore-cache-passphrase-for-symmetric-encryption t)
+ ;; Allow gpg-agent to cache the passphrase (400 days per gpg-agent.conf)
+ (setq plstore-encrypt-to nil)) ;; Use symmetric encryption, not key-based
+
+;; ------------------------ Authentication Reset Utility -----------------------
+
+(defun cj/reset-auth-cache (&optional include-gpg-agent)
+ "Reset authentication caches when wrong password was entered.
+
+By default, only clears Emacs-side caches (auth-source, EPA file
+handler) and leaves gpg-agent's long-term cache intact. This preserves
+your 400-day cache for GPG and SSH passphrases.
+
+With prefix argument INCLUDE-GPG-AGENT (\\[universal-argument]), also
+clears gpg-agent's password cache. Use this when gpg-agent itself has
+cached an incorrect password.
+
+Clears:
+1. auth-source cache (Emacs-level credential cache)
+2. EPA file handler cache (encrypted file cache)
+3. gpg-agent cache (only if INCLUDE-GPG-AGENT is non-nil)
+
+Use this when you see errors like:
+ - \"Bad session key\"
+ - \"Decryption failed\"
+ - GPG repeatedly using wrong cached password"
+ (interactive "P")
+ (message "Resetting authentication caches...")
+
+ ;; Clear auth-source cache (Emacs credential cache)
+ (auth-source-forget-all-cached)
+
+ ;; Clear EPA file handler cache
+ (when (fboundp 'epa-file-clear-cache)
+ (epa-file-clear-cache))
+
+ ;; Only clear gpg-agent cache if explicitly requested
+ (if include-gpg-agent
+ (let ((result (shell-command "echo RELOADAGENT | gpg-connect-agent")))
+ (if (zerop result)
+ (message "✓ Emacs and gpg-agent caches cleared. Next access will prompt for password.")
+ (message "⚠ Warning: Failed to clear gpg-agent cache")))
+ (message "✓ Emacs caches cleared. GPG/SSH passphrases preserved for session.")))
+
+(defun cj/kill-gpg-agent ()
+ "Force kill gpg-agent (it will restart automatically on next use).
+
+This is a more aggressive reset than `cj/reset-auth-cache'. Use this
+when gpg-agent is stuck or behaving incorrectly.
+
+The gpg-agent will automatically restart on the next GPG operation."
+ (interactive)
+ (let ((result (shell-command "gpgconf --kill gpg-agent")))
+ (if (zerop result)
+ (message "✓ gpg-agent killed. It will restart automatically on next use.")
+ (message "⚠ Warning: Failed to kill gpg-agent"))))
+
+;; Keybindings
+(with-eval-after-load 'keybindings
+ (keymap-set cj/custom-keymap "A" #'cj/reset-auth-cache))
+
+(with-eval-after-load 'which-key
+ (which-key-add-key-based-replacements
+ "C-; A" "reset auth cache"))
+
(provide 'auth-config)
;;; auth-config.el ends here.
diff --git a/modules/config-utilities.el b/modules/config-utilities.el
index 32018371..2af3effa 100644
--- a/modules/config-utilities.el
+++ b/modules/config-utilities.el
@@ -33,8 +33,7 @@
"C-c d i b" "info build"
"C-c d i p" "info packages"
"C-c d i f" "info features"
- "C-c d r" "reload init"
- "C-c d a" "reset auth cache"))
+ "C-c d r" "reload init"))
;;; --------------------------------- Profiling ---------------------------------
@@ -283,15 +282,6 @@ Recompile natively when supported, otherwise fall back to byte compilation."
(load-file user-init-file))
(keymap-set cj/debug-config-keymap "r" 'cj/reload-init-file)
-;; ----------------------------- Reset-Auth-Sources ----------------------------
-
-(defun cj/reset-auth-cache ()
- "Clear Emacs auth-source cache."
- (interactive)
- (auth-source-forget-all-cached)
- (message "Emacs auth-source cache cleared."))
-(keymap-set cj/debug-config-keymap "a" 'cj/reset-auth-cache)
-
;; ------------------------ Validate Org Agenda Entries ------------------------
(defun cj/validate-org-agenda-timestamps ()
diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el
index 441ff61b..b10c97f0 100644
--- a/modules/dirvish-config.el
+++ b/modules/dirvish-config.el
@@ -8,15 +8,15 @@
;; ediff, playlist creation, path copying, and external file manager integration.
;;
;; Key Bindings:
-;; - d: Duplicate file at point (adds "-copy" before extension)
-;; - D: Delete marked files immediately (dired-do-delete)
+;; - d: Delete marked files (dired-do-delete)
+;; - D: Duplicate file at point (adds "-copy" before extension)
;; - g: Quick access menu (jump to predefined directories)
;; - G: Search with deadgrep in current directory
;; - f: Open system file manager in current directory
;; - o/O: Open file with xdg-open/custom command
;; - l: Copy file path (project-relative or home-relative)
;; - L: Copy absolute file path
-;; - P: Create M3U playlist from marked audio files
+;; - P: Copy file path (same as 'l', replaces dired-do-print)
;; - M-D: DWIM menu (context actions for files)
;; - TAB: Toggle subtree expansion
;; - F11: Toggle sidebar view
@@ -120,9 +120,9 @@ Filters for audio files, prompts for the playlist name, and saves the resulting
(setq dired-listing-switches "-l --almost-all --human-readable --group-directories-first")
(setq dired-dwim-target t)
(setq dired-clean-up-buffers-too t) ;; offer to kill buffers associated deleted files and dirs
- (setq dired-clean-confirm-killing-deleted-buffers t) ;; don't ask; just kill buffers associated with deleted files
- (setq dired-recursive-copies (quote always)) ;; “always” means no asking
- (setq dired-recursive-deletes (quote top))) ;; “top” means ask once
+ (setq dired-clean-confirm-killing-deleted-buffers nil) ;; don't ask; just kill buffers associated with deleted files
+ (setq dired-recursive-copies (quote always)) ;; "always" means no asking
+ (setq dired-recursive-deletes (quote top))) ;; "top" means ask once
;; note: disabled as it prevents marking and moving files to another directory
;; (setq dired-kill-when-opening-new-dired-buffer t) ;; don't litter by leaving buffers when navigating directories
@@ -322,13 +322,14 @@ regardless of what file or subdirectory the point is on."
("M-p" . dirvish-peek-toggle)
("M-s" . dirvish-setup-menu)
("TAB" . dirvish-subtree-toggle)
- ("d" . cj/dirvish-duplicate-file)
+ ("d" . dired-do-delete)
+ ("D" . cj/dirvish-duplicate-file)
("f" . cj/dirvish-open-file-manager-here)
("g" . dirvish-quick-access)
("o" . cj/xdg-open)
("O" . cj/open-file-with-command) ; Prompts for command to run
("r" . dirvish-rsync)
- ("P" . cj/dired-create-playlist-from-marked)
+ ("P" . cj/dired-copy-path-as-kill)
("s" . dirvish-quicksort)
("v" . dirvish-vc-menu)
("y" . dirvish-yank-menu)))
diff --git a/modules/flycheck-config.el b/modules/flycheck-config.el
index ea19f08f..e2e8abe9 100644
--- a/modules/flycheck-config.el
+++ b/modules/flycheck-config.el
@@ -6,30 +6,30 @@
;; This file configures Flycheck for on-demand syntax and grammar checking.
;; - Flycheck starts automatically only in sh-mode and emacs-lisp-mode
-;; - This binds a custom helper (=cj/flycheck-list-errors=) to “C-; ?”
+;; - This binds a custom helper (=cj/flycheck-list-errors=) to "C-; ?"
;; for popping up Flycheck's error list in another window.
-;; - It also customizes Checkdoc to suppress only the “sentence-end-double-space”
-;; and “warn-escape” warnings.
+;; - It also customizes Checkdoc to suppress only the "sentence-end-double-space"
+;; and "warn-escape" warnings.
-;; - It registers a Proselint checker for prose files
-;; (text-mode, markdown-mode, gfm-mode).
+;; - It registers LanguageTool for comprehensive grammar checking of prose files
+;; (text-mode, markdown-mode, gfm-mode, org-mode).
-;; Note: I do use proselint quite a bit in emails and org-mode files. However, some
-;; org-files can be large and running proselint on them will slow Emacs to a crawl.
-;; Therefore, hitting "C-; ?" also runs cj/flycheck-prose-on-demand if in an org buffer.
+;; Note: Grammar checking is on-demand only to avoid performance issues.
+;; Hitting "C-; ?" runs cj/flycheck-prose-on-demand if in an org buffer.
-;;
;; The cj/flycheck-prose-on-demand function:
;; - Turns on flycheck for the local buffer
-;; - ensures proselint is added
-;; - triggers an immediate check
-;;
-;; Since this is called within cj/flycheck-list-errors, flycheck's error list will still
-;; display and the focus transferred to that buffer.
+;; - Enables LanguageTool checker
+;; - Triggers an immediate check
+;; - Displays errors in the *Flycheck errors* buffer
-;; OS Dependencies:
-;; proselint (in the Arch AUR)
+;; Installation:
+;; On Arch Linux:
+;; sudo pacman -S languagetool
+;;
+;; The wrapper script at scripts/languagetool-flycheck formats LanguageTool's
+;; JSON output into flycheck-compatible format. It requires Python 3.
;;; Code:
@@ -62,20 +62,20 @@
;; use the load-path of the currently running Emacs instance
(setq flycheck-emacs-lisp-load-path 'inherit)
- ;; Define the prose checker (installed separately via OS).
- (flycheck-define-checker proselint
- "A linter for prose."
- :command ("proselint" source-inplace)
+ ;; Define LanguageTool checker for comprehensive grammar checking
+ (flycheck-define-checker languagetool
+ "A grammar checker using LanguageTool.
+Uses a wrapper script to format output for flycheck."
+ :command ("~/.emacs.d/scripts/languagetool-flycheck"
+ source-inplace)
:error-patterns
((warning line-start (file-name) ":" line ":" column ": "
- (id (one-or-more (not (any " "))))
(message) line-end))
:modes (text-mode markdown-mode gfm-mode org-mode))
- (add-to-list 'flycheck-checkers 'proselint)
+ (add-to-list 'flycheck-checkers 'languagetool)
(defun cj/flycheck-list-errors ()
"Display flycheck's error list and switch to its buffer.
-
Runs flycheck-prose-on-demand if in an org-buffer."
(interactive)
(when (derived-mode-p 'org-mode)
@@ -85,12 +85,14 @@ Runs flycheck-prose-on-demand if in an org-buffer."
(switch-to-buffer-other-window "*Flycheck errors*"))
(defun cj/flycheck-prose-on-demand ()
- "Enable Flycheck+Proselint in this buffer, run it, and show errors."
+ "Enable Flycheck with LanguageTool in this buffer, run it, and show errors."
(interactive)
;; turn on Flycheck locally
(flycheck-mode 1)
- ;; ensure proselint is valid for org/text
- (flycheck-add-mode 'proselint major-mode)
+ ;; ensure LanguageTool is valid for current mode
+ (flycheck-add-mode 'languagetool major-mode)
+ ;; select LanguageTool as the checker
+ (setq-local flycheck-checker 'languagetool)
;; trigger immediate check
(flycheck-buffer)))
diff --git a/modules/modeline-config.el b/modules/modeline-config.el
index 140d21cd..b1403539 100644
--- a/modules/modeline-config.el
+++ b/modules/modeline-config.el
@@ -156,7 +156,8 @@ Shows only in active window.")
mode-line-format-right-align
cj/modeline-vc-branch
" "
- cj/modeline-misc-info))
+ cj/modeline-misc-info
+ " "))
;; Mark all segments as risky-local-variable (required for :eval forms)
(dolist (construct '(cj/modeline-buffer-name
diff --git a/modules/org-gcal-config.el b/modules/org-gcal-config.el
index 28cc1933..97e8446a 100644
--- a/modules/org-gcal-config.el
+++ b/modules/org-gcal-config.el
@@ -165,9 +165,8 @@ Useful after changing `cj/org-gcal-sync-interval-minutes'."
(setq org-gcal-managed-update-existing-mode "gcal") ;; GCal wins on conflicts
:config
- ;; Enable plstore passphrase caching after org-gcal loads
- (require 'plstore)
- (setq plstore-cache-passphrase-for-symmetric-encryption t)
+ ;; Plstore caching is now configured globally in auth-config.el
+ ;; to ensure it loads before org-gcal needs it
;; set org-gcal timezone based on system timezone
(setq org-gcal-local-timezone (cj/detect-system-timezone))
diff --git a/modules/system-utils.el b/modules/system-utils.el
index 2b39d862..e9686777 100644
--- a/modules/system-utils.el
+++ b/modules/system-utils.el
@@ -186,13 +186,22 @@ Logs output and exit code to buffer *external-open.log*."
;;; -------------------------- Scratch Buffer Happiness -------------------------
(defvar scratch-emacs-version-and-system
- (concat ";; Emacs " emacs-version
+ (concat "# Emacs " emacs-version
" on " system-configuration ".\n"))
(defvar scratch-greet
- (concat ";; Emacs ♥ you, " user-login-name ". Happy Hacking!\n\n"))
+ (concat "# Emacs ♥ you, " user-login-name ". Happy Hacking!\n\n"))
(setopt initial-scratch-message
(concat scratch-emacs-version-and-system scratch-greet))
+;; Set scratch buffer to org-mode
+(setopt initial-major-mode 'org-mode)
+
+;; Move cursor to end of scratch buffer on startup
+(add-hook 'emacs-startup-hook
+ (lambda ()
+ (when (string= (buffer-name) "*scratch*")
+ (goto-char (point-max)))))
+
;;; --------------------------------- Dictionary --------------------------------
(use-package quick-sdcv
diff --git a/modules/transcription-config.el b/modules/transcription-config.el
index 4c7d4943..fd2f4aaa 100644
--- a/modules/transcription-config.el
+++ b/modules/transcription-config.el
@@ -5,19 +5,23 @@
;;; Commentary:
;;
-;; Audio transcription workflow using OpenAI Whisper (API or local).
+;; Audio transcription workflow with multiple backend options.
;;
;; USAGE:
;; In dired: Press `T` on an audio file to transcribe
;; Anywhere: M-x cj/transcribe-audio
;; View active: M-x cj/transcriptions-buffer
+;; Switch backend: C-; T b (or M-x cj/transcription-switch-backend)
;;
;; OUTPUT FILES:
;; audio.m4a → audio.txt (transcript)
;; → audio.log (process logs, conditionally kept)
;;
;; BACKENDS:
-;; - 'openai-api: Fast cloud transcription (requires OPENAI_API_KEY)
+;; - 'openai-api: Fast cloud transcription
+;; API key retrieved from authinfo.gpg (machine api.openai.com)
+;; - 'assemblyai: Cloud transcription with speaker diarization
+;; API key retrieved from authinfo.gpg (machine api.assemblyai.com)
;; - 'local-whisper: Local transcription (requires whisper installed)
;;
;; NOTIFICATIONS:
@@ -33,12 +37,14 @@
(require 'dired)
(require 'notifications)
+(require 'auth-source)
;; ----------------------------- Configuration ---------------------------------
-(defvar cj/transcribe-backend 'local-whisper
+(defvar cj/transcribe-backend 'assemblyai
"Transcription backend to use.
- `openai-api': Fast cloud transcription via OpenAI API
+- `assemblyai': Cloud transcription with speaker diarization via AssemblyAI
- `local-whisper': Local transcription using installed Whisper")
(defvar cj/transcription-keep-log-when-done nil
@@ -83,9 +89,36 @@ SUCCESS-P indicates whether transcription succeeded."
"Return absolute path to transcription script based on backend."
(let ((script-name (pcase cj/transcribe-backend
('openai-api "oai-transcribe")
+ ('assemblyai "assemblyai-transcribe")
('local-whisper "local-whisper"))))
(expand-file-name (concat "scripts/" script-name) user-emacs-directory)))
+(defun cj/--get-openai-api-key ()
+ "Retrieve OpenAI API key from authinfo.gpg.
+Expects entry in authinfo.gpg:
+ machine api.openai.com login api password sk-...
+Returns the API key string, or nil if not found."
+ (when-let* ((auth-info (car (auth-source-search
+ :host "api.openai.com"
+ :require '(:secret))))
+ (secret (plist-get auth-info :secret)))
+ (if (functionp secret)
+ (funcall secret)
+ secret)))
+
+(defun cj/--get-assemblyai-api-key ()
+ "Retrieve AssemblyAI API key from authinfo.gpg.
+Expects entry in authinfo.gpg:
+ machine api.assemblyai.com login api password <key>
+Returns the API key string, or nil if not found."
+ (when-let* ((auth-info (car (auth-source-search
+ :host "api.assemblyai.com"
+ :require '(:secret))))
+ (secret (plist-get auth-info :secret)))
+ (if (functionp secret)
+ (funcall secret)
+ secret)))
+
;; ---------------------------- Process Management -----------------------------
(defun cj/--notify (title message &optional urgency)
@@ -125,14 +158,28 @@ Returns the process object."
(format "Audio file: %s\n" audio-file)
(format "Script: %s\n\n" script)))
- ;; Start process
- (let ((process (make-process
- :name process-name
- :buffer (get-buffer-create buffer-name)
- :command (list script audio-file)
- :sentinel (lambda (proc event)
- (cj/--transcription-sentinel proc event audio-file txt-file log-file))
- :stderr log-file)))
+ ;; Start process with environment
+ (let* ((process-environment
+ ;; Add API key to environment based on backend
+ (pcase cj/transcribe-backend
+ ('openai-api
+ (if-let ((api-key (cj/--get-openai-api-key)))
+ (cons (format "OPENAI_API_KEY=%s" api-key)
+ process-environment)
+ (user-error "OpenAI API key not found in authinfo.gpg for host api.openai.com")))
+ ('assemblyai
+ (if-let ((api-key (cj/--get-assemblyai-api-key)))
+ (cons (format "ASSEMBLYAI_API_KEY=%s" api-key)
+ process-environment)
+ (user-error "AssemblyAI API key not found in authinfo.gpg for host api.assemblyai.com")))
+ (_ process-environment)))
+ (process (make-process
+ :name process-name
+ :buffer (get-buffer-create buffer-name)
+ :command (list script audio-file)
+ :sentinel (lambda (proc event)
+ (cj/--transcription-sentinel proc event audio-file txt-file log-file))
+ :stderr log-file)))
;; Track transcription
(push (list process audio-file (current-time) 'running) cj/transcriptions-list)
@@ -298,6 +345,21 @@ Uses backend specified by `cj/transcribe-backend'."
(kill-process process)
(message "Killed transcription process")))
+;;;###autoload
+(defun cj/transcription-switch-backend ()
+ "Switch transcription backend.
+Prompts with completing-read to select from available backends."
+ (interactive)
+ (let* ((backends '(("assemblyai" . assemblyai)
+ ("openai-api" . openai-api)
+ ("local-whisper" . local-whisper)))
+ (current (symbol-name cj/transcribe-backend))
+ (prompt (format "Transcription backend (current: %s): " current))
+ (choice (completing-read prompt backends nil t))
+ (new-backend (alist-get choice backends nil nil #'string=)))
+ (setq cj/transcribe-backend new-backend)
+ (message "Transcription backend: %s" choice)))
+
;; ------------------------------- Dired Integration ---------------------------
(with-eval-after-load 'dired
@@ -310,17 +372,19 @@ Uses backend specified by `cj/transcribe-backend'."
;; Transcription keymap
(defvar-keymap cj/transcribe-map
:doc "Keymap for transcription operations"
- "t" #'cj/transcribe-audio
- "b" #'cj/transcriptions-buffer
+ "a" #'cj/transcribe-audio
+ "b" #'cj/transcription-switch-backend
+ "v" #'cj/transcriptions-buffer
"k" #'cj/transcription-kill)
-(keymap-set cj/custom-keymap "t" cj/transcribe-map)
+(keymap-set cj/custom-keymap "T" cj/transcribe-map)
(with-eval-after-load 'which-key
(which-key-add-key-based-replacements
- "C-; t" "transcription menu"
- "C-; t t" "transcribe audio"
- "C-; t b" "show transcriptions buffer"
- "C-; t k" "kill transcription"))
+ "C-; T" "transcription menu"
+ "C-; T a" "transcribe audio"
+ "C-; T b" "switch backend"
+ "C-; T v" "view transcriptions"
+ "C-; T k" "kill transcription"))
(provide 'transcription-config)
;;; transcription-config.el ends here
diff --git a/modules/video-audio-recording.el b/modules/video-audio-recording.el
index c714a0a6..45bab267 100644
--- a/modules/video-audio-recording.el
+++ b/modules/video-audio-recording.el
@@ -4,7 +4,7 @@
;;; Commentary:
;; Use ffmpeg to record desktop video or just audio.
;; with audio from mic and audio from default audio sink
-;; Also supports audio-only recording in Opus format.
+;; Audio recordings use M4A/AAC format for best compatibility.
;;
;; Note: video-recordings-dir and audio-recordings-dir are defined
;; (and directory created) in user-constants.el
@@ -311,16 +311,16 @@ Otherwise use the default location in `audio-recordings-dir'."
(system-device (cdr devices))
(location (expand-file-name directory))
(name (format-time-string "%Y-%m-%d-%H-%M-%S"))
- (filename (expand-file-name (concat name ".opus") location))
+ (filename (expand-file-name (concat name ".m4a") location))
(ffmpeg-command
(format (concat "ffmpeg "
"-f pulse -i %s "
"-ac 1 "
"-f pulse -i %s "
- "-ac 2 "
- "-filter_complex \"[0:a]volume=%.1f[mic];[1:a]volume=%.1f[sys];[mic][sys]amerge=inputs=2\" "
- "-c:a libopus "
- "-b:a 96k "
+ "-ac 1 "
+ "-filter_complex \"[0:a]volume=%.1f[mic];[1:a]volume=%.1f[sys];[mic][sys]amerge=inputs=2[out];[out]pan=mono|c0=0.5*c0+0.5*c1\" "
+ "-c:a aac "
+ "-b:a 64k "
"%s")
mic-device
system-device
diff --git a/modules/weather-config.el b/modules/weather-config.el
index 31fb1b70..3a30aa17 100644
--- a/modules/weather-config.el
+++ b/modules/weather-config.el
@@ -10,8 +10,15 @@
;; ----------------------------------- Wttrin ----------------------------------
+;; Load wttrin from local development directory
+(add-to-list 'load-path "/home/cjennings/code/wttrin")
+
+;; Set debug flag BEFORE loading wttrin (checked at load time)
+(setq wttrin-debug nil)
+
(use-package wttrin
- :vc (:url "https://github.com/cjennings/emacs-wttrin" :rev :newest)
+ ;; Uncomment the next line to use vc-install instead of local directory:
+ ;; :vc (:url "https://github.com/cjennings/emacs-wttrin" :rev :newest)
:defer t
:preface
;; dependency for wttrin
@@ -21,6 +28,22 @@
("M-W" . wttrin)
:custom
(wttrin-unit-system "u")
+ (wttrin-mode-line-favorite-location "New Orleans, LA")
+ (wttrin-mode-line-refresh-interval 900) ; 15 minutes
+ :init
+ ;; Explicitly autoload the mode function (needed for local dev directory)
+ (autoload 'wttrin-mode-line-mode "wttrin" "Toggle weather display in mode-line." t)
+ ;; Enable mode-line widget AFTER Emacs finishes initializing
+ ;; (url-retrieve async needs full init to work without buffer errors)
+ (if (daemonp)
+ ;; Daemon mode: wait for first client to connect
+ (add-hook 'server-after-make-frame-hook
+ (lambda () (wttrin-mode-line-mode 1))
+ t) ; append to end of hook
+ ;; Normal Emacs: wait for startup to complete
+ (add-hook 'after-init-hook
+ (lambda () (wttrin-mode-line-mode 1))
+ t)) ; append to end of hook
:config
(setq wttrin-default-locations '(
"New Orleans, LA"