Compare commits
7 commits
05e0fbae40
...
038ec85e0e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
038ec85e0e | ||
|
|
3a639902a0 | ||
|
|
82ecae035c | ||
|
|
4de1b825f7 | ||
|
|
a3c3458140 | ||
|
|
468f74086c | ||
|
|
40555ea625 |
6 changed files with 329 additions and 21 deletions
|
|
@ -1048,13 +1048,13 @@ And some shortcut keys from the =general= project:
|
|||
I want to quickly jump, by the number shown on the tab, to that grouping. The following two functions create leader sequences with the name of the tab group:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-tab-bar-update-names ()
|
||||
(defun ha-tab-bar-update-names (&optional changed-tab)
|
||||
"Create normal-mode keybindings for the tab groupings.
|
||||
This creates `SPC TAB 1' to jump to the first tab, etc."
|
||||
(interactive)
|
||||
;; Remove all previously created keybindings:
|
||||
(ignore-errors
|
||||
(dolist (indx (number-sequence 1 9))
|
||||
(dolist (indx (number-sequence 0 9))
|
||||
(general-nmap :prefix "SPC" (format "<tab> %d" indx) nil)))
|
||||
|
||||
;; Loop through the existing tabs, create keys for each:
|
||||
|
|
@ -1063,11 +1063,14 @@ I want to quickly jump, by the number shown on the tab, to that grouping. The fo
|
|||
(defun ha-tab-bar-update-tab-keybinding (tab-deets indx)
|
||||
"Create a keybinding to jump to tab described by TAB-DEETS.
|
||||
The key sequence, `SPC' `TAB' then INDX."
|
||||
(let ((name (alist-get 'name tab-deets)))
|
||||
(general-nmap :prefix "SPC"
|
||||
(format "<tab> %d" (1+ indx))
|
||||
`(,name .
|
||||
(lambda () (interactive) (tab-bar-select-tab ,(1+ indx)))))))
|
||||
(when (< indx 10)
|
||||
(let ((name (alist-get 'name tab-deets)))
|
||||
(general-nmap :prefix "SPC"
|
||||
;; As indx is starts with 0, we need to create keybindings to
|
||||
;; match the tab labels by incrementing it by one ... unless,
|
||||
;; we are at 10, where we use 0 instead:
|
||||
(format "<tab> %d" (if (= indx 9) 0 (1+ indx)))
|
||||
`(,name . (lambda () (interactive) (tab-bar-select-tab ,(1+ indx))))))))
|
||||
#+END_SRC
|
||||
|
||||
Any time I create or delete a new tab, we can call =ha-tab-bar-update-names=:
|
||||
|
|
|
|||
28
ha-org.org
28
ha-org.org
|
|
@ -3,7 +3,7 @@
|
|||
#+date: 2020-09-18
|
||||
#+tags: emacs org
|
||||
#+startup: inlineimages
|
||||
#+lastmod: [2025-07-01 Tue]
|
||||
#+lastmod: [2025-12-02 Tue]
|
||||
|
||||
A literate programming file for configuring org-mode and those files.
|
||||
|
||||
|
|
@ -245,6 +245,32 @@ And I would like to have cute little icons for those states:
|
|||
("^ +\\([-*]\\) "
|
||||
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•")))))))
|
||||
#+end_src
|
||||
|
||||
What was
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun display-latest-clocked-task ()
|
||||
"Display the name of the latest task that was clocked in."
|
||||
(interactive)
|
||||
(let ((latest-task (car (org-clock-get-clock-string))))
|
||||
(if latest-task
|
||||
(message "Latest clocked task: %s" latest-task)
|
||||
(message "No task is currently clocked in."))))
|
||||
|
||||
#+END_SRC
|
||||
|
||||
Need to be able to clock out of a task even if no /clock-in/ has occurred.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-clock-out ()
|
||||
"Safely clock out of the current task.
|
||||
This will not throw an error if not active clock exists."
|
||||
(interactive)
|
||||
(ignore-errors
|
||||
(org-clock-out)
|
||||
(message "Clocked out.")))
|
||||
#+END_SRC
|
||||
|
||||
** Meetings
|
||||
I've notice that while showing a screen while taking meeting notes, I don't always like showing other windows, so I created this function to remove distractions during a meeting.
|
||||
|
||||
|
|
|
|||
|
|
@ -292,7 +292,7 @@ For the sake of my demonstrations, I use =ha-shell= to start a terminal with a p
|
|||
default-directory)))
|
||||
(buf-name (format "*%s*" win-name)))
|
||||
(setq ha-latest-ssh-window-name buf-name)
|
||||
(ha-make-term win-name ha-shell))) ; Lisp-2 FTW!?
|
||||
(ha-make-term win-name))) ; Lisp-2 FTW!?
|
||||
#+end_src
|
||||
|
||||
Now that Emacs can /host/ a Terminal shell, I would like to /programmatically/ send commands to the running terminal, e.g. =(ha-shell-send "ls *.py")= I would really like to be able to send and execute a command in a terminal from a script.
|
||||
|
|
@ -317,8 +317,6 @@ Now that Emacs can /host/ a Terminal shell, I would like to /programmatically/ s
|
|||
(t (progn
|
||||
(insert command)
|
||||
(term-send-input))))))
|
||||
|
||||
(ha-shell-send "exit")
|
||||
#+end_src
|
||||
|
||||
Let's have a quick way to bugger out of the terminal:
|
||||
|
|
|
|||
267
hammerspoon.org
Normal file
267
hammerspoon.org
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
#+title: Hammerspoon Configuration
|
||||
#+author: Howard X. Abrams
|
||||
#+date: 2025-11-24
|
||||
#+filetags: emacs hamacs
|
||||
#+lastmod: [2025-12-02 Tue]
|
||||
|
||||
A literate programming file for configuring Hammerspoon.
|
||||
|
||||
* Introduction
|
||||
Ever since I got a Mac, I’ve used various tools to /script/ it. With my limited number of applications, my UI needs are simple (I mean, what more do you need once Emacs is in full screen). That said, I’ve been using [[https://hammerspoon.org][Hammerspoon]] for those few needs. While I refer to the [[https://www.hammerspoon.org/docs/index.html][standard documentation]], I often steal snippets of code from others.
|
||||
|
||||
Simple to use. Use =hs.execute= to run a script, =ha.alert.show= to print a small message on the screen, and for something longer:
|
||||
#+BEGIN_SRC lua :tangle no
|
||||
hs.notify.new({title="Hammerspoon", informativeText="Hello World"}):send()
|
||||
#+END_SRC
|
||||
|
||||
* Shortcut Keybindings
|
||||
I’ve created left and right ~Meh~ keys on my Moonlanders:
|
||||
- Left Meh Key: ~C-M-S-s~ (Control Option/Meta Command Shift)
|
||||
- Right Meh Key: ~C-M-S~ (Control Option/Meta Shift)
|
||||
|
||||
To create special key bindings, I can:
|
||||
#+BEGIN_SRC lua
|
||||
----------------------------------------------------------------------
|
||||
-- Launcher replaces iCanHazShortcuts
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "T", function()
|
||||
hs.application.launchOrFocus("iTerm")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "S", function()
|
||||
hs.application.launchOrFocus("Slack")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "W", function()
|
||||
hs.application.launchOrFocus("Spotify")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "F", function()
|
||||
hs.application.launchOrFocus("Firefox")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "C", function()
|
||||
-- hs.osascript.applescriptFromFile("~/bin/chrome.scr")
|
||||
hs.execute("~/bin/chrome.scr")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "B", function()
|
||||
hs.application.launchOrFocus("Microsoft Outlook")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "Z", function()
|
||||
hs.application.launchOrFocus("zoom.us")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "A", function()
|
||||
hs.application.launchOrFocus("Cursor")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "Q", function()
|
||||
hs.application.launchOrFocus("KeepassXC")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "E", function()
|
||||
hs.execute("FOR_WORK=yes open -a /Applications/Emacs.app")
|
||||
end)
|
||||
|
||||
-- Special Emacs Guys
|
||||
-- Right Meh key:
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "X", function()
|
||||
hs.execute("~/bin/emacs-capture")
|
||||
end)
|
||||
|
||||
-- Left Meh key:
|
||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "X", function()
|
||||
hs.execute("~/bin/emacs-capture-clock")
|
||||
end)
|
||||
|
||||
hs.hotkey.bind({"alt", "ctrl", "shift"}, "M", function()
|
||||
hs.execute("~/bin/emacs-capture-meeting")
|
||||
end)
|
||||
#+END_SRC
|
||||
* Zoom
|
||||
Library extensions to Hammerspoon are called /spoons/, and most of these are a simple Lua script, =init.lua= stored in the =Spoons= subdirectory. We grab these with a =git clone=, typically. To use the Zoom spoon, clone it:
|
||||
|
||||
#+BEGIN_SRC sh :dir ~/.hammerspoon/Spoons :tangle no :results silent
|
||||
git clone https://github.com/jpf/Zoom.spoon.git
|
||||
#+END_SRC
|
||||
|
||||
I noticed that it does its works by calling Zoom’s menus, and with the latest version of Zoom, they changed the /case/ of the menu, meaning that I needed to create a pull request.
|
||||
|
||||
From this [[https://developer.okta.com/blog/2020/10/22/set-up-a-mute-indicator-light-for-zoom-with-hammerspoon][nice essay]], we create a menu bar item that shows the status, as well as allowing me to click it to toggle:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
zoomStatusMenuBarItem = hs.menubar.new(true)
|
||||
zoomStatusMenuBarItem:setClickCallback(function()
|
||||
spoon.Zoom:toggleMute()
|
||||
end)
|
||||
|
||||
updateZoomStatus = function(event)
|
||||
hs.printf("updateZoomStatus(%s)", event)
|
||||
if (event == "from-running-to-meeting") then
|
||||
zoomStatusMenuBarItem:returnToMenuBar()
|
||||
elseif (event == "muted") then
|
||||
zoomStatusMenuBarItem:setTitle("🔴")
|
||||
elseif (event == "unmuted") then
|
||||
zoomStatusMenuBarItem:setTitle("🟢")
|
||||
elseif (event == "from-meeting-to-running") then
|
||||
zoomStatusMenuBarItem:removeFromMenuBar()
|
||||
end
|
||||
end
|
||||
#+END_SRC
|
||||
|
||||
Now we can load, instantiate it, as well as create a callback loop to call =updateZoomStatus=:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
hs.loadSpoon("Zoom")
|
||||
spoon.Zoom:setStatusCallback(updateZoomStatus)
|
||||
spoon.Zoom:start()
|
||||
#+END_SRC
|
||||
|
||||
And bind a key to the mute ability that works good for both keyboards:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "M", spoon.Zoom:toggleMute)
|
||||
|
||||
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "M", function()
|
||||
spoon.Zoom:toggleMute()
|
||||
end)
|
||||
#+END_SRC
|
||||
|
||||
Lovely bit of code and shows the true power of Hammerspoon to fix the various app issues.
|
||||
* Automatically Adjust Volume
|
||||
Leaving my home office and hopping on the train can be socially awkward when my laptop suddenly screams, so if I leave my home, I just readjust the volume and turn it off:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
----------------------------------------------------------------------
|
||||
-- When leaving house, turn off the volume:
|
||||
|
||||
wifiWatcher = nil
|
||||
homeSSID = "Intertubes"
|
||||
workSSID = "workdaysecure"
|
||||
hotspotSSID = "Pixie Dust"
|
||||
lastSSID = hs.wifi.currentNetwork()
|
||||
|
||||
function ssidChangedCallback()
|
||||
newSSID = hs.wifi.currentNetwork()
|
||||
|
||||
if newSSID == homeSSID and lastSSID ~= homeSSID then
|
||||
-- joined our home WiFi network
|
||||
hs.audiodevice.defaultOutputDevice():setVolume(25)
|
||||
elseif newSSID == workSSID and lastSSID ~= workSSID then
|
||||
-- joined our work WiFi network
|
||||
hs.audiodevice.defaultOutputDevice():setVolume(0)
|
||||
elseif newSSID == hotspotSSID and lastSSID ~= hotspotSSID then
|
||||
-- joined our hotspot WiFi network
|
||||
hs.audiodevice.defaultOutputDevice():setVolume(0)
|
||||
elseif newSSID ~= homeSSID and lastSSID == homeSSID then
|
||||
-- departed our home WiFi network
|
||||
hs.audiodevice.defaultOutputDevice():setVolume(0)
|
||||
end
|
||||
|
||||
lastSSID = newSSID
|
||||
end
|
||||
|
||||
hs.wifi.watcher.new(ssidChangedCallback):start()
|
||||
#+END_SRC
|
||||
|
||||
Why yes, I’m /looking for features/ to use Hammerspoon.
|
||||
* Clocking out a Task
|
||||
I want to use Org’s task clocking ability, but I often forget to /clock out/. This code allows me to clock out whenever my computer goes to sleep … which works well when closing the laptop lid:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
function sleepWatch(eventType)
|
||||
if (eventType == hs.caffeinate.watcher.systemWillSleep) then
|
||||
if hs.application.find("Emacs") then
|
||||
hs.execute("/opt/homebrew/bin/emacsclient --socket work --eval '(ha-clock-out)'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
hs.caffeinate.watcher.new(sleepWatch):start()
|
||||
#+END_SRC
|
||||
|
||||
Not that we could also do something for Wake:
|
||||
|
||||
#+BEGIN_SRC lua :tangle no
|
||||
if (eventType == hs.caffeinate.watcher.systemDidWake) then
|
||||
...
|
||||
end
|
||||
#+END_SRC
|
||||
|
||||
* Monitors
|
||||
My company gave me a nice monitor … maybe a little too nice, as I don’t care to shift my neck to the extreme sides (serious first-world problem), so here I can /center/ a window Keeping it my field of view:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
----------------------------------------------------------------------
|
||||
-- Centering a window on the large monitors at Work:
|
||||
|
||||
function centerWindow()
|
||||
local win = hs.window.focusedWindow()
|
||||
local app = win:application()
|
||||
local f = win:frame()
|
||||
|
||||
-- Magic numbers figured out by trial and error:
|
||||
f.x = 600
|
||||
f.y = 30
|
||||
f.w = 2200
|
||||
f.h = 1470
|
||||
|
||||
if app then
|
||||
local name = app:name()
|
||||
-- If the application is Slack, adjust the height
|
||||
if name == "Slack" or name == "iTerm2" then
|
||||
f.h = 1200
|
||||
end
|
||||
end
|
||||
|
||||
win:setFrame(f)
|
||||
hs.alert.show("Centered Window")
|
||||
end
|
||||
|
||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "Y", centerWindow)
|
||||
#+END_SRC
|
||||
|
||||
* Auto Reload Configuration
|
||||
Whenever my configuration file is altered (or with a ~Meh-R~ key), I reload the configuration:
|
||||
|
||||
#+BEGIN_SRC lua
|
||||
----------------------------------------------------------------------
|
||||
-- Automatically reload the Hammerspoon configuration
|
||||
|
||||
hs.hotkey.bind({"cmd", "alt", "ctrl", "shift"}, "R", function()
|
||||
hs.reload()
|
||||
hs.alert.show("Reloaded Hammerspoon Config")
|
||||
end)
|
||||
|
||||
function reloadConfig(files)
|
||||
doReload = false
|
||||
for _,file in pairs(files) do
|
||||
if file:sub(-4) == ".lua" then
|
||||
doReload = true
|
||||
end
|
||||
end
|
||||
if doReload then
|
||||
hs.reload()
|
||||
end
|
||||
end
|
||||
|
||||
hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", reloadConfig):start()
|
||||
#+END_SRC
|
||||
|
||||
* Technical Artifacts :noexport:
|
||||
#+BEGIN_SRC lua
|
||||
hs.alert.show("Hammerspoon Configuration")
|
||||
#+END_SRC
|
||||
|
||||
|
||||
#+DESCRIPTION: Literate Hammerspoon configuration
|
||||
|
||||
#+PROPERTY: header-args:sh :tangle no
|
||||
#+PROPERTY: header-args:lua :tangle ~/.hammerspoon/init.lua
|
||||
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
||||
|
||||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
||||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
||||
|
|
@ -3,6 +3,10 @@
|
|||
;; modifier, so holding down `d` and hitting `p` results in `P` being
|
||||
;; entered.
|
||||
|
||||
(deflocalkeys-macos
|
||||
ì 13
|
||||
)
|
||||
|
||||
;; Also includes a symbol layer accessible while holding down either
|
||||
;; the `h' or `g' keys.
|
||||
|
||||
|
|
@ -13,11 +17,12 @@
|
|||
;; )
|
||||
)
|
||||
(defsrc
|
||||
esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
|
||||
` 1 2 3 4 5 6 7 8 9 0 - = bspc
|
||||
tab q w e r t y u i o p [ ] \
|
||||
caps a s d f g h j k l ; ' ret
|
||||
lsft z x c v b n m , . / rsft
|
||||
lctl lalt lmet spc rmet ralt
|
||||
fn lctl lalt lmet spc rmet ralt
|
||||
)
|
||||
|
||||
(defvar
|
||||
|
|
@ -40,17 +45,19 @@
|
|||
)
|
||||
|
||||
(deflayer base
|
||||
esc F1 F2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
|
||||
` 1 2 3 4 5 6 7 8 9 0 - = bspc
|
||||
tab q w e r t y u i o p [ ] \
|
||||
@caps @a @s @d @f @g @h @j @k @l @; ' ret
|
||||
lsft z x c v b n m , . / rsft
|
||||
lctl lalt lmet spc rmet ralt
|
||||
@h lctl lalt lmet spc rmet ralt
|
||||
)
|
||||
|
||||
(deflayer SYMBOLS
|
||||
_ F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 _ _ _
|
||||
esc 🔅 🔆 F3 F4 f5 f6 ◀◀ ▶⏸ ▶▶ 🔇 🔉 🔊
|
||||
_ f11 f12 f13 f14 f15 f16 f17 f18 f19 f20 _ _ _
|
||||
_ S-1 S-2 { } S-\ F16 F17 F18 F19 pgup _ _ _
|
||||
_ S-3 S-4 S-9 S-0 ` left down up right pgdn _ _
|
||||
_ S-5 S-6 [ ] S-` F11 F12 F13 F14 F15 _
|
||||
_ _ _ _ _ _
|
||||
_ _ _ _ _ _ _
|
||||
)
|
||||
|
|
|
|||
19
zshell.org
19
zshell.org
|
|
@ -218,6 +218,13 @@ The [[https://github.com/zsh-users/zsh-syntax-highlighting][ZShell Syntax Highli
|
|||
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
|
||||
#+END_SRC
|
||||
|
||||
Using the =colorize= plugin (see [[Plugins][plugins section below]]), we customize with this variable setting:
|
||||
|
||||
#+BEGIN_SRC zsh
|
||||
export ZSH_COLORIZE_STYLE="coffee"
|
||||
#+END_SRC
|
||||
|
||||
|
||||
** Language Support
|
||||
Anything special for particular languages.
|
||||
*** Python
|
||||
|
|
@ -446,7 +453,7 @@ Where be the =emacsclient=? It should, at this point, be in our path.
|
|||
And how should we call it?
|
||||
|
||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
||||
export EMACS="emacsclient --socket-name personal"
|
||||
export EMACS_SOCKET_NAME=personal
|
||||
#+END_SRC
|
||||
|
||||
Which needs to be overwritten on my Work computer:
|
||||
|
|
@ -454,15 +461,15 @@ Which needs to be overwritten on my Work computer:
|
|||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
||||
if hostname | grep AL33 >/dev/null
|
||||
then
|
||||
export EMACS="emacsclient --socket-name work"
|
||||
export EMACS_SOCKET_NAME=work
|
||||
fi
|
||||
#+END_SRC
|
||||
|
||||
The =EDITOR= variable that some programs use to edit files from the command line:
|
||||
|
||||
#+BEGIN_SRC zsh :tangle ~/.zshenv
|
||||
export EDITOR="$EMACS --tty"
|
||||
export VISUAL="$EMACS --create-frame"
|
||||
export EDITOR="emacsclient --tty"
|
||||
export VISUAL="emacsclient --create-frame"
|
||||
#+END_SRC
|
||||
|
||||
With these variables defined, we can create simple aliases:
|
||||
|
|
@ -470,8 +477,8 @@ With these variables defined, we can create simple aliases:
|
|||
#+BEGIN_SRC zsh
|
||||
alias e="$EDITOR"
|
||||
alias te="$EDITOR"
|
||||
alias ee="$EMACS --create-frame"
|
||||
alias eee="$EMACS --create-frame --no-wait"
|
||||
alias ee="emacsclient --create-frame"
|
||||
alias eee="emacsclient --create-frame --no-wait"
|
||||
#+END_SRC
|
||||
|
||||
** Vterm
|
||||
|
|
|
|||
Loading…
Reference in a new issue