Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: http://akrabat.com/feed/

  1. <?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
  2. xmlns:content="http://purl.org/rss/1.0/modules/content/"
  3. xmlns:wfw="http://wellformedweb.org/CommentAPI/"
  4. xmlns:dc="http://purl.org/dc/elements/1.1/"
  5. xmlns:atom="http://www.w3.org/2005/Atom"
  6. xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
  7. xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  8. >
  9.  
  10. <channel>
  11. <title>Rob Allen</title>
  12. <atom:link href="https://akrabat.com/feed/" rel="self" type="application/rss+xml" />
  13. <link>https://akrabat.com</link>
  14. <description>Pragmatism in today&#039;s world</description>
  15. <lastBuildDate>Sat, 09 Aug 2025 11:54:23 +0000</lastBuildDate>
  16. <language>en-US</language>
  17. <sy:updatePeriod>
  18. hourly </sy:updatePeriod>
  19. <sy:updateFrequency>
  20. 1 </sy:updateFrequency>
  21. <generator>https://wordpress.org/?v=6.8.2</generator>
  22. <item>
  23. <title>Saving the current URL to a Note</title>
  24. <link>https://akrabat.com/saving-the-current-url-to-a-note/</link>
  25. <comments>https://akrabat.com/saving-the-current-url-to-a-note/#respond</comments>
  26. <dc:creator><![CDATA[Rob]]></dc:creator>
  27. <pubDate>Tue, 19 Aug 2025 10:00:00 +0000</pubDate>
  28. <category><![CDATA[Mac]]></category>
  29. <category><![CDATA[Shortcuts]]></category>
  30. <guid isPermaLink="false">https://akrabat.com/?p=7467</guid>
  31.  
  32. <description><![CDATA[Inspired by John Gruber mentioning on the Cortex podcast that he has a shortcut that saves links to a note in Tot, I thought I'd do something similar for saving to a note in Apple Notes. I want to store as a bullet item containing the name of the page, the link and the date. Something like this: (Funny that the spellchecker doesn't know that Thu is the short form for Thursday) The Save Links… <a href="https://akrabat.com/saving-the-current-url-to-a-note/">continue reading</a>.]]></description>
  33. <content:encoded><![CDATA[<p>Inspired by John Gruber mentioning on the <a href="https://www.relay.fm/cortex/169">Cortex podcast</a> that he has a shortcut that saves links to a note in <a href="https://tot.rocks">Tot</a>, I thought I'd do something similar for saving to a note in Apple Notes.</p>
  34. <p>I want to store as a bullet item containing the name of the page, the link and the date. Something like this:</p>
  35. <picture><source  
  36. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-dark.png"
  37.        media="(prefers-color-scheme: dark)"
  38.    /><source
  39. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-light.png"
  40.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  41.    /><br />
  42.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025saved-link-text-light.png" loading="lazy" alt="Saved link text light." class="border" width="408"/>
  43. </picture>
  44. <p>(Funny that the spellchecker doesn't know that Thu is the short form for Thursday)</p>
  45. <h2>The <em>Save Links to Notes</em> Shortcut</h2>
  46. <p>This is the shortcut that I created to do it:
  47. <picture><source  
  48. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-dark.png"
  49.        media="(prefers-color-scheme: dark)"
  50.    /><source
  51. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-light.png"
  52.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  53.    /><br />
  54.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-shortcut-light.png" loading="lazy" alt="Save links to notes shortcut light." class="noborder" width="558"/>
  55. </picture>
  56. <p>You can download it here:<br />
  57. <a href="https://www.icloud.com/shortcuts/6c12e888f9da431d9977985b229d636b">https://www.icloud.com/shortcuts/6c12e888f9da431d9977985b229d636b</a></p>
  58. <h2>Breaking down the actions</h2>
  59. <p>To get the URL into the shortcut, we want:</p>
  60. <ul>
  61. <li><em>Show in Share Sheet</em> so that it's available on iOS/iPadOS</li>
  62. <li><em>Receive What's Onscreen</em> so that when a browser is focussed on Mac, it finds the URL</li>
  63. <li><em>Use as a Quick Action</em> so that we can assign a keyboard shortcut (<tt style="font-family: sans-serif">⌃⌥⌘U</tt> in case)</li>
  64. </ul>
  65. <p>We can then use <em>Get Contents of web page</em> along with <em>Get Details of Safari Web Page</em> to get the pages's title which Shortcuts calls <em>Name</em> for some reason.</p>
  66. <p>There's an action for <em>Current Date</em>, so we add that to get the variable.</p>
  67. <p>Creating formatted text in a note is a little involved. Firstly we use a <em>Text</em> action to set out the Markdown that we want. I used the date format <tt>EEE, dd MMM yyyy</tt> as it's short and clear to me.</p>
  68. <p>There's a <em>Make Rich Text from Markdown</em> action which processes the Markdown for us, but if you just append it to the note, it doesn't work. The workaround is to add it to a <em>List</em> action and then append the list to the note. </p>
  69. <h2>That's it</h2>
  70. <p>All we need to do now is show a notification including the <tt>Shortcut Input</tt> variable as that's the URL that we've just saved.
  71. <picture><source  
  72. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-dark.png"
  73.        media="(prefers-color-scheme: dark)"
  74.    /><source
  75. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-light.png"
  76.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  77.    /><br />
  78.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025save-links-to-notes-notification-light.png" loading="lazy" alt="Save links to notes notification light." class="noborder" width="376"/>
  79. </picture>
  80. <p>With this shortcut, I can add a new entry to my note from both my Mac, iPad and iPhone with minimal effort. </p>
  81. <p>I like it.</p>
  82. ]]></content:encoded>
  83. <wfw:commentRss>https://akrabat.com/saving-the-current-url-to-a-note/feed/</wfw:commentRss>
  84. <slash:comments>0</slash:comments>
  85. </item>
  86. <item>
  87. <title>Accessing Longplay info for SwiftBar</title>
  88. <link>https://akrabat.com/accessing-longplay-info-for-swiftbar/</link>
  89. <comments>https://akrabat.com/accessing-longplay-info-for-swiftbar/#respond</comments>
  90. <dc:creator><![CDATA[Rob]]></dc:creator>
  91. <pubDate>Tue, 12 Aug 2025 10:00:00 +0000</pubDate>
  92. <category><![CDATA[Mac]]></category>
  93. <guid isPermaLink="false">https://akrabat.com/?p=7447</guid>
  94.  
  95. <description><![CDATA[One app that I find incredibly useful is SwiftBar and one use I have is to display track info for the currently playing song in Apple Music. SwiftBar plugins work as shell scripts that execute on a timer and echo specially formatted text which SwiftBar then turns into an item on the menu bar with an attached menu I use a heavily modified Now Playing plugin that was originally written by Adam Kenyon, so all… <a href="https://akrabat.com/accessing-longplay-info-for-swiftbar/">continue reading</a>.]]></description>
  96. <content:encoded><![CDATA[<p>One app that I find incredibly useful is <a href="https://github.com/swiftbar/SwiftBar">SwiftBar</a> and one use I have is to display track info for the currently playing song in Apple Music.</p>
  97. <p>SwiftBar plugins work as shell scripts that execute on a timer and echo specially formatted text which SwiftBar then turns into an item on the menu bar with an attached menu</p>
  98. <p>I use a heavily modified <a href="https://github.com/matryer/xbar-plugins/blob/main/Music/nowplaying.5s.sh">Now Playing plugin</a> that was originally written by Adam Kenyon, so all the hard work was done by them.</p>
  99. <p>Recently, I've been using <a href="https://longplay.rocks">Longplay</a> to play albums and wanted the same functionality.</p>
  100. <p>Now Playing uses AppleScript to determine if a music player is playing and what the track info is:</p>
  101. <pre>
  102. app_playing=$(osascript -e "tell application \"$i\" to player state as string")
  103. </pre>
  104. <p>And</p>
  105. <pre>
  106. track=$(osascript -e "tell application \"$app\" to name of current track")
  107. artist=$(osascript -e "tell application \"$app\" to artist of current track")
  108. </pre>
  109. <p>When looking at adding Longplay, I was pleased to discover that it has AppleScript support, but perusing the Dictionary, I discovered that it doesn't support the features I need here.</p>
  110. <p>Upon emailing the developer, they very helpfully pointed out that Longplay also has Shortcuts support and that I could probably use that instead. They were right.</p>
  111. <p>I knocked up a couple of shortcuts:</p>
  112. <picture><source  
  113. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-dark.png"
  114.        media="(prefers-color-scheme: dark)"
  115.    /><source
  116. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-light.png"
  117.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  118.    /><br />
  119.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-status-light.png" loading="lazy" alt="Longplay status Apple Shortcut" class="noborder" width="431"/>
  120. </picture>
  121. <p>and</p>
  122. <picture><source  
  123. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-dark.png"
  124.        media="(prefers-color-scheme: dark)"
  125.    /><source
  126. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-light.png"
  127.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  128.    /><br />
  129.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025longplay-now-playing-light.png" loading="lazy" alt="Longplay now playing light." class="noborder" width="431"/>
  130. </picture>
  131. <p>With these set-up, I can now run them from the command line using <tt>shortcuts</tt>:</p>
  132. <pre>
  133. app_playing=$(shortcuts run "Longplay status");
  134. </pre>
  135. <p>This will set <tt>$app_playing</tt> to either "Yes" or "No" as strings as it is defined as boolean in Shortcuts.</p>
  136. <pre>
  137. track=$(shortcuts run "Longplay now playing");
  138. </pre>
  139. <p>This simply sets <tt>$track</tt> to the string of the currently playing track.</p>
  140. <h2>Updated Now Playing script</h2>
  141. <p>With the ability to get the info I needed from the command line, I <s>hacked</s> updated my copy of the Now Playing script and all is good.</p>
  142. <p>I've updated it a bit over the years, so I've uploaded my version to Gist: <a href="https://gist.github.com/akrabat/8bcfac9dfef5fd4e9b67ac5bb504ea7a">nowplaying.5s.sh</a>.</p>
  143. ]]></content:encoded>
  144. <wfw:commentRss>https://akrabat.com/accessing-longplay-info-for-swiftbar/feed/</wfw:commentRss>
  145. <slash:comments>0</slash:comments>
  146. </item>
  147. <item>
  148. <title>Responding to StreamDeck buttons with Keyboard Maestro</title>
  149. <link>https://akrabat.com/controlling-the-streamdeck-via-keyboard-maestro/</link>
  150. <comments>https://akrabat.com/controlling-the-streamdeck-via-keyboard-maestro/#respond</comments>
  151. <dc:creator><![CDATA[Rob]]></dc:creator>
  152. <pubDate>Tue, 05 Aug 2025 10:00:00 +0000</pubDate>
  153. <category><![CDATA[AppleScript]]></category>
  154. <category><![CDATA[Keyboard Maestro]]></category>
  155. <category><![CDATA[Mac]]></category>
  156. <guid isPermaLink="false">https://akrabat.com/?p=7440</guid>
  157.  
  158. <description><![CDATA[I run Apple Music on my Mac desktop and send the output to my HomePod minis. To control the volume, you need to manipulate the Apple Music volume slider rather than the global volume controls for the Mac. It's easier to press buttons than use a mouse, so I used Keyboard Maestro to respond to two buttons on my Stream Deck instead. This is possible because Keyboard Maestro has a Stream Deck Plugin, so you… <a href="https://akrabat.com/controlling-the-streamdeck-via-keyboard-maestro/">continue reading</a>.]]></description>
  159. <content:encoded><![CDATA[<p>I run Apple Music on my Mac desktop and send the output to my HomePod minis. To control the volume, you need to manipulate the Apple Music volume slider rather than the global volume controls for the Mac.</p>
  160. <p>It's easier to press buttons than use a mouse, so I used <a href="https://www.keyboardmaestro.com/">Keyboard Maestro</a> to respond to two buttons on my <a href="https://www.elgato.com/uk/en/p/stream-deck">Stream Deck</a> instead.</p>
  161. <p>This is possible because Keyboard Maestro has a <a href="https://marketplace.elgato.com/product/keyboard-maestro-35c7590b-b7fb-4be0-9e5d-9fd4b4c0f013">Stream Deck Plugin</a>, so you need to install that first.</p>
  162. <h2>Setting up the Stream Deck button</h2>
  163. <p>You can now assign Keyboard Maestro to a button in the Stream Deck software:</p>
  164. <p><img fetchpriority="high" decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025streamdeck-km-button.png" alt="Keyboard Maestro automation configuration interface showing a button setup with a speaker/volume icon. The interface displays fields for Title (empty), Button ID (R3C1), Virtual Row (3), and Virtual Column (1). The left side shows a black square button with white speaker and minus icons." title="streamdeck-km-button.png" border="0" width="500" height="239" /></p>
  165. <p>This is the configuration for my volume down button, as you can tell by the icon I chose. The Button ID defaults to the row and column number of where you have placed it on the Stream Deck.</p>
  166. <h2>Responding to the button in Keyboard Maestro</h2>
  167. <p>On the Keyboard Maestro side, we need a macro that is trigged by the Stream Deck button. This is easy to do as it looks like a USB device key and you can press the button the Stream Deck and Keyboard Maestro will recognise it and fill in the correct details.</p>
  168. <p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025streamdeck-km-control.png" alt="Screenshot of an Keyboard Maestro automation interface showing a "Music volume down" macro. The trigger is a Stream Deck R3C1 button press with modifiers. The action executes AppleScript code that decreases the Music app's volume by 1, with a minimum volume of 0." title="streamdeck-km-control.png" border="0" width="500" height="529" /></p>
  169. <p>Upon clicking the button, we simply run some AppleScript to control the Music app's volume.</p>
  170. <h2>That's it</h2>
  171. <p>That's all there is to responding to a button on the Stream Deck on a Mac. In this case, I'm using AppleScript, but Keyboard Maestro lets you do practically anything on the computer!</p>
  172. ]]></content:encoded>
  173. <wfw:commentRss>https://akrabat.com/controlling-the-streamdeck-via-keyboard-maestro/feed/</wfw:commentRss>
  174. <slash:comments>0</slash:comments>
  175. </item>
  176. <item>
  177. <title>Step-debugging Docker Compose NestJS services</title>
  178. <link>https://akrabat.com/step-debugging-docker-compose-nestjs-services/</link>
  179. <comments>https://akrabat.com/step-debugging-docker-compose-nestjs-services/#respond</comments>
  180. <dc:creator><![CDATA[Rob]]></dc:creator>
  181. <pubDate>Tue, 29 Jul 2025 10:00:00 +0000</pubDate>
  182. <category><![CDATA[NodeJS]]></category>
  183. <category><![CDATA[TypeScript]]></category>
  184. <guid isPermaLink="false">https://akrabat.com/?p=7454</guid>
  185.  
  186. <description><![CDATA[I'm working on a NestJS project that uses monorepo mode. It consists of a number of separate microservice applications that each have their own Docker container that are managed in development using Docker Compose. I like step-debugging in my IDE and so needed to set it up for this application. This is what I did. The application setup Each service in our project has its own container in docker-compose.yaml and we use a reverse proxy to… <a href="https://akrabat.com/step-debugging-docker-compose-nestjs-services/">continue reading</a>.]]></description>
  187. <content:encoded><![CDATA[<p>I'm working on a <a href="https://nestjs.com">NestJS</a> project that uses <a href="https://docs.nestjs.com/cli/monorepo#monorepo-mode">monorepo mode</a>. It consists of a number of separate microservice applications that each have their own <a href="https://www.docker.com">Docker</a> container that are managed in development using <a href="https://docs.docker.com/compose/">Docker Compose</a>.</p>
  188. <p>I like step-debugging in my IDE and so needed to set it up for this application. This is what I did.</p>
  189. <h2>The application setup</h2>
  190. <p>Each service in our project has its own container in docker-compose.yaml and we use a reverse proxy to have a single endpoint that routes requests to the correct microservice. Each custom <tt>Dockerfile</tt> runs <tt>ppm run start:dev:{service name}></tt> which is defined in <tt>package.json</tt> like this:</p>
  191. <pre>
  192. "start:dev:service1": "nest start service1 --watch --tsc --watchOptions.poll=1000 --preserveWatchOutput",
  193. </pre>
  194. <p>Using <tt>--watchOptions.poll=1000</tt> is just more reliable when running in Docker with volumes mounted into the container. We also set the <tt>--preserveWatchOutput</tt> flag to ensure that the service doesn't take control of the terminal as this is unhelpful when you have multiple services in play.</p>
  195. <h2>Set the apps up for debugging</h2>
  196. <p>We need to make some modification for step debugging. Firstly, I created a set of <tt>start:debug:{service name}</tt> scripts in <tt>package.json</tt> that look like this:</p>
  197. <pre>
  198. "start:debug:service1": "nest start service1 --debug 0.0.0.0:9229 --watch --tsc --watchOptions.poll=1000 --preserveWatchOutput",
  199. </pre>
  200. <p>We enabled the <tt>--debug</tt> flag to enable node's <tt>--inspect</tt> flag so that port <tt>9229</tt> is available to the debugger. However, buy default this is bound to <tt>127.0.0.1</tt> which is not useful in a container, so we bind to <tt>0.0.0.0:9229</tt> so that it's available outside the container.</p>
  201. <p>Next, we need to our new <tt>start:debug:{service name}</tt> scripts and expose port 92229 to our local environment for each service. We do this in <tt>docker-compose.override.yaml</tt>:</p>
  202. <pre lang="yaml">
  203. services:
  204.  service1:
  205.    ports:
  206.      - "9230:9229"
  207.    command: pnpm run start:debug:service1
  208.  
  209.  service2:
  210.    ports:
  211.      - "9231:9229"
  212.    command: pnpm run start:debug:service2
  213. </pre>
  214. <p>I don't tend to like binding to the default port as that invariably confuses me when I run some test thing locally, so I've picked ports starting from <tt>9230</tt> onwards for my services.</p>
  215. <p>Running <tt>docker compose up</tt> will now start the containers.</p>
  216. <h2>Debugging in WebStorm</h2>
  217. <p>To set up <a href="https://www.jetbrains.com/webstorm/">WebStorm</a> for step debugging, create a <em>Run/Debug configuration</em> entry of type <tt>Attach to Node.js/Chrome</tt> for each container.</p>
  218. <p>The settings for service1 are:</p>
  219. <ul>
  220. <li>Name: Debug service1</li>
  221. <li>Host: localhost</li>
  222. <li>Port: 9230</li>
  223. </ul>
  224. <p>For the other services, change the name and port.</p>
  225. <picture><source  
  226. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-dark.png"
  227.        media="(prefers-color-scheme: dark)"
  228.    /><source
  229. srcset="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-light.png"
  230.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  231.    /><br />
  232.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025webstorm-nodejs-debug-config-light.png" loading="lazy" alt="Webstorm nodejs debug config light." class="noborder" width="600"/>
  233. </picture>
  234. <p>From the Debugging dropdown at in the title bar select the service and press the green "bug" button. You'll see a "Debugger attached." message in the Docker logs.</p>
  235. <p>Attach a breakpoint and access the endpoint using <tt>curl</tt> or another HTTP client and the IDE should stop execution at the breakpoint.</p>
  236. <h2>Debugging in VS Code</h2>
  237. <p>To set up <a href="https://code.visualstudio.com">VS Code</a> for step debugging, create a <tt>.vscode/launch.json</tt> file in your project. It should look like this:</p>
  238. <pre lang="json">
  239. {
  240.  "version": "0.2.0",
  241.  "configurations": [
  242.    {
  243.      "name": "Debug service1",
  244.      "type": "node",
  245.      "request": "attach",
  246.      "port": 9230,
  247.      "address": "localhost",
  248.      "localRoot": "${workspaceFolder}",
  249.      "remoteRoot": "/usr/src/app",
  250.      "restart": true
  251.    },
  252.    {
  253.      "name": "Debug service2",
  254.      "type": "node",
  255.      "request": "attach",
  256.      "port": 9231,
  257.      "address": "localhost",
  258.      "localRoot": "${workspaceFolder}",
  259.      "remoteRoot": "/usr/src/app",
  260.      "restart": true
  261.    }
  262.  ]
  263. }
  264. </pre>
  265. <p>Set <tt>remoteRoot</tt> to the directory within the Docker container where the project is mounted.</p>
  266. <p>Start debugging by selecting the <em>Run and Debug</em> pane in the left hand toolbar and choose the service from the dropdown at the top. Then press the green <em>Start debugging</em> button (or press F5).You'll see a "Debugger attached." message in the Docker logs.</p>
  267. <p>Attach a breakpoint and access the endpoint using <tt>curl</tt> or another HTTP client and the IDE should stop execution at the breakpoint.</p>
  268. <h2>That's it</h2>
  269. <p>Having done this, I can now enjoy step debugging this new-to-me codebase and understand what it does!</p>
  270. ]]></content:encoded>
  271. <wfw:commentRss>https://akrabat.com/step-debugging-docker-compose-nestjs-services/feed/</wfw:commentRss>
  272. <slash:comments>0</slash:comments>
  273. </item>
  274. <item>
  275. <title>QuickSS: Screenshot the active window on Mac</title>
  276. <link>https://akrabat.com/quickss-screenshot-the-active-window-on-mac/</link>
  277. <comments>https://akrabat.com/quickss-screenshot-the-active-window-on-mac/#respond</comments>
  278. <dc:creator><![CDATA[Rob]]></dc:creator>
  279. <pubDate>Tue, 22 Jul 2025 09:00:00 +0000</pubDate>
  280. <category><![CDATA[Mac]]></category>
  281. <category><![CDATA[Software]]></category>
  282. <guid isPermaLink="false">https://akrabat.com/?p=7418</guid>
  283.  
  284. <description><![CDATA[Back in 2016, I wrote about using QuickGrab to take a screenshot of the active window via a single key press with no mouse use required. It's now 2025 and I'm still using this and Apple has announced that Rosetta 2 will be phased out in a couple of years. As QuickGrab is one of the few Intel-only apps I still use, I thought I'd recompile for Apple Silicon and it was then that I… <a href="https://akrabat.com/quickss-screenshot-the-active-window-on-mac/">continue reading</a>.]]></description>
  285. <content:encoded><![CDATA[<p>Back in 2016, I wrote about using QuickGrab to take a screenshot of the active window via a single key press with no mouse use required.</p>
  286. <p>It's now 2025 and I'm still using this and Apple has announced that Rosetta 2 will be phased out in a couple of years. As QuickGrab is one of the few Intel-only apps I still use, I thought I'd recompile for Apple Silicon and it was then that I ran into trouble:</p>
  287. <pre>
  288. $ gcc -framework cocoa -x objective-c -o quickgrab quickgrab.m
  289. quickgrab.m:92:26: error: 'CGWindowListCreateImage' is unavailable:
  290. obsoleted in macOS 15.0 - Please use ScreenCaptureKit instead.
  291. </pre>
  292. <p><em>Obsoleted in macOS 15.0</em> isn't something you want to see! The APIs that QuickGrab use are no longer part of the SDK and so it cannot be compiled. Hence I decided to replace it with a Swift version and also take the opportunity to add additional features that I wanted.</p>
  293. <p>While on holiday, I wrote <a href="https://github.com/akrabat/QuickSS">QuickSS</a>. I spent some time playing around with ScreenCaptureKit, but couldn't get it to replicate the window shadows that the standard screenshot tool on <tt>shift+cmd+4</tt> does. To solve this I decided to use <tt>screencapture</tt> to take the screenshot which is provided by macOS. </p>
  294. <h2>Screenshot to file</h2>
  295. <p>To save a screenshot of the active window directly to a file, just run <tt>quickss</tt>. You probably want to select cmd+tab to a different window, so prefix with <tt>sleep</tt>:</p>
  296. <pre>
  297. rob@Caledonia QuickGrab (master *) $ sleep 3; quickss
  298. Capturing window ID: 5940 [Safari: 'Rob Allen – Pragmatism in the real world']
  299. Screenshot saved to: /Users/rob/Downloads/Screenshot 2025-07-22 at 11.00.00.png
  300. </pre>
  301. <p>As it's my app, I made some changes to match the way I work with it. Firstly it defaults to saving the file to the Downloads folder and names it the same as the default screenshot utility. You can use <tt>--file</tt> to override this should you need to and <tt>--quiet</tt> will output just the filename which is useful for onward scripting, or displaying in notifications.</p>
  302. <h2>Screenshot directly to clipboard</h2>
  303. <p>I also added the ability to put the screenshot directly onto the clipboard with <tt>--clipboard</tt></p>
  304. <pre>
  305. $ sleep 3; quickss --clipboard
  306. Capturing window ID: 5940 [Safari: 'Rob Allen – Pragmatism in the real world']
  307. Screenshot copied to clipboard
  308. </pre>
  309. <p>I can now paste directly into Slack/Discord/Messages/Messenger/WhatsApp/Signal/etc. (Yes, there are far too many of these services nowadays!)</p>
  310. <h2>Keyboard shortcut</h2>
  311. <p>Obviously, the best way to run this is to use global keyboard shortcuts. My preference is to use <tt style="font-family: sans-serif">⌃⌥⌘4</tt> to put the screenshot onto the clipboard and <tt style="font-family: sans-serif">⇧⌃⌥⌘4</tt> to save it to file.</p>
  312. <h3>Using Shortcuts.app</h3>
  313. <p>You can use the Shortcuts app for this and create a shortcut for copying to clipboard like this:</p>
  314. <p><img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025quickss-take-screenshot-to-cliboard.png" alt="Quickss take screenshot to cliboard." title="quickss-take-screenshot-to-cliboard.png" border="0" width="700" height="511" /></p>
  315. <p>The equivalent for saving to file is the same, except that you don't need the <tt>--clipboard</tt> parameter.</p>
  316. <h3>Using Alfred</h3>
  317. <p>I use <a href="https://www.alfredapp.com/">Alfred</a> for these sort of things, so I wrote a Workflow to do this based on the previous one:</p>
  318. <p><img loading="lazy" decoding="async" src="https://akrabat.com/wp-content/uploads/2025/07/2025quickss-alfred-workflow.png" alt="Quickss alfred workflow." title="quickss-alfred-workflow.png" border="0" width="700" height="363" /></p>
  319. <p>It's downloadable from the <a href="https://github.com/akrabat/QuickSS/releases/latest">QuickSS latest release</a> page.</p>
  320. <h2>That's it</h2>
  321. <p>Over the years, I've found that having a global hotkey to screenshot the current active window is really helpful. I like it even more now that it goes directly to the clipboard.</p>
  322. ]]></content:encoded>
  323. <wfw:commentRss>https://akrabat.com/quickss-screenshot-the-active-window-on-mac/feed/</wfw:commentRss>
  324. <slash:comments>0</slash:comments>
  325. </item>
  326. <item>
  327. <title>Notarising a macOS standalone binary</title>
  328. <link>https://akrabat.com/notarising-a-macos-standalone-binary/</link>
  329. <comments>https://akrabat.com/notarising-a-macos-standalone-binary/#respond</comments>
  330. <dc:creator><![CDATA[Rob]]></dc:creator>
  331. <pubDate>Tue, 15 Jul 2025 09:00:00 +0000</pubDate>
  332. <category><![CDATA[Command Line]]></category>
  333. <category><![CDATA[Development]]></category>
  334. <category><![CDATA[Shell Scripting]]></category>
  335. <guid isPermaLink="false">https://akrabat.com/?p=7413</guid>
  336.  
  337. <description><![CDATA[I've been writing a simple Swift command line tool called QuickSS. It's a single file swift file, that I compile to a standalone binaryusing: swiftc quickss.swift -o quickss To distribute it on modern Macs, I need to sign it and then get Apple to notarise it. Signing the binary To sign the binary, you need a "Developer ID Application" certificate from your paid developer account. If you don't have one there already create a new… <a href="https://akrabat.com/notarising-a-macos-standalone-binary/">continue reading</a>.]]></description>
  338. <content:encoded><![CDATA[<p>I've been writing a simple Swift command line tool called QuickSS. It's a single file swift file, that I compile to a standalone binaryusing:</p>
  339. <pre>
  340. swiftc quickss.swift -o quickss
  341. </pre>
  342. <p>To distribute it on modern Macs, I need to sign it and then get Apple to notarise it.</p>
  343. <h2>Signing the binary</h2>
  344. <p>To sign the binary, you need a "Developer ID Application" certificate from your paid <a href="https://developer.apple.com/account/resources/certificates/list">developer account</a>. If you don't have one there already create a new one, which requires a CSR from Keychain. Fortunately, the <a href="https://developer.apple.com/help/account/certificates/create-a-certificate-signing-request">info on what to do</a> is clear.</p>
  345. <p>Download your Developer ID Application certificate and add to Keychain Access.</p>
  346. <p>Next you need its name. This is done using <tt>security find-identity -v -p codesigning</tt> which gives you a list of certs. You want the text between the <tt>"</tt> that starts "Developer ID Application:". The easiest way to get this if you are scripting it is:</p>
  347. <pre>
  348. IDENTITY=$(security find-identity -v -p codesigning | grep "Developer ID Application" | grep -m 1 -oE '"[^"]+"' | tr -d '"')
  349. </pre>
  350. <p>To actually sign the binary, use the <tt>codesign</tt> utility:</p>
  351. <pre>
  352. codesign --timestamp --options runtime --sign "$IDENTITY" quickss
  353. </pre>
  354. <h2>Notarising</h2>
  355. <p>To notarise the binary, you use the <tt>notarytool</tt> utility within <tt>xcrun</tt>. This expects a zip or pkg file, so zip up the binary first. You also need to know your Apple developer account's Team ID, your Apple ID and you need an app-specific password from <a href="https://account.apple.com">account.apple.com</a>.</p>
  356. <p>Submit your app for notarising with:</p>
  357. <pre>
  358. zip -q quickss.zip quickss
  359.  
  360. xcrun notarytool submit  --wait --no-progress -f json \
  361.    --team-id "$TEAM_ID" \
  362.    --apple-id "$APPLE_ID" \
  363.    --password "$APP_SPECIFIC_PASSWORD" \
  364.    quickss.zip
  365.  
  366. rm quickss.zip
  367. </pre>
  368. <p>If it fails, use the submission ID to query the log:</p>
  369. <pre>
  370. xcrun notarytool log \
  371.    --team-id "$TEAM_ID" \
  372.    --apple-id "$APPLE_ID" \
  373.    --password "$APP_SPECIFIC_PASSWORD" \
  374.    "&lt;submission ID&gt;"
  375. </pre>
  376. <p>Apple has now notarised your app.</p>
  377. <h2>Note: No need to staple</h2>
  378. <p>For a standard Mac applications (<tt>.app</tt> bundles), you can "staple" the notarisation into it. This makes things more efficient for the GateKeeper technology. However, for a standalone binary, <em>this cannot be done</em> as there is no directory into which to store the notarisation file.</p>
  379. <p>If you try to staple, you'll get <tt>The staple and validate action failed! Error 73</tt> with no further information which is not entirely helpful.</p>
  380. <p>For standalone binaries GateKeeper checks directly with Apple's servers the first time they are run, so stapling is unnecessary.</p>
  381. <h2>That's it</h2>
  382. <p>That's it. You now have a notarised standalone binary that can be easily distributed.</p>
  383. ]]></content:encoded>
  384. <wfw:commentRss>https://akrabat.com/notarising-a-macos-standalone-binary/feed/</wfw:commentRss>
  385. <slash:comments>0</slash:comments>
  386. </item>
  387. <item>
  388. <title>Using the 1Password CLI in a script</title>
  389. <link>https://akrabat.com/using-the-1password-cli-in-a-script/</link>
  390. <comments>https://akrabat.com/using-the-1password-cli-in-a-script/#respond</comments>
  391. <dc:creator><![CDATA[Rob]]></dc:creator>
  392. <pubDate>Tue, 08 Jul 2025 09:00:00 +0000</pubDate>
  393. <category><![CDATA[Command Line]]></category>
  394. <category><![CDATA[Development]]></category>
  395. <category><![CDATA[Shell Scripting]]></category>
  396. <guid isPermaLink="false">https://akrabat.com/?p=7407</guid>
  397.  
  398. <description><![CDATA[I'm currently writing a script that notarises a macOS CLI app which needs to access a password. Rather than put it in an environment variable, I thought I'd use the 1Password CLI. This is the first time I've used it, so these are my notes. The 1Password CLI tool is call op. I installed it via Homebrew with: brew install 1password-cli Sign in You need to sign in. op signin As I have multiple accounts… <a href="https://akrabat.com/using-the-1password-cli-in-a-script/">continue reading</a>.]]></description>
  399. <content:encoded><![CDATA[<p>I'm currently writing a script that notarises a macOS CLI app which needs to access a password. Rather than put it in an environment variable, I thought I'd use the 1Password CLI. This is the first time I've used it, so these are my notes.</p>
  400. <p>The 1Password CLI tool is call <tt>op</tt>. I installed it via Homebrew with:</p>
  401. <pre>brew install 1password-cli</pre>
  402. <h2>Sign in</h2>
  403. <p>You need to sign in. </p>
  404. <pre>op signin</pre>
  405. <p>As I have multiple accounts as various clients have shared access to specific vaults, it asks me which account I want to sign in. To save this step, you can set the <tt>OP_ACCOUNT</tt> environment variable:</p>
  406. <pre>export OP_ACCOUNT=my.1password.com</pre>
  407. <p>Alternatively, use the <tt>--account</tt> parameter.</p>
  408. <p>You get a dialog box where for me, I use TouchID to sign in.</p>
  409. <p><tt>op signin</tt> is idempotent so is a no-op if the Terminal is already signed in.</p>
  410. <h2>Access data</h2>
  411. <p>There are multiple ways to retrieve the data from a 1Password item.</p>
  412. <h3><tt style="color:inherit">op item get</tt></h3>
  413. <p>Use <tt>op item get "&lt;item&gt;" --field "&lt;fieldname&gt;"</tt> to get a specific field. e.g</p>
  414. <pre>op item get "Apple App Notarisation" --field "username"</pre>
  415. <p>The <tt>&lt;item&gt;</tt> can be the name of the item or its id. e.g. something like <tt>dajka2z5l57m4p43s6bapd3eo4</tt></p>
  416. <p>Note, that for a password, you also need to pass in <tt>--reveal</tt>.</p>
  417. <p>As I'm writing a script, I assign to a variable:</p>
  418. <pre>
  419. APPLE_ID=$(op item get "Apple App Notarisation" --field username)
  420. APP_SPECIFIC_PASSWORD=$(op item get "Apple App Notarisation" --field password --reveal)
  421. TEAM_ID=$(op item get "Apple App Notarisation" --field team_id)
  422. </pre>
  423. <p>Alternatively, you can get back multiple fields in one go by providing a list of comma separated fields:</p>
  424. <pre>
  425. FIELDS=$(op item get "Apple App Notarisation" --fields username,password,team_id --reveal)
  426. IFS=',' read -r APPLE_ID APP_SPECIFIC_PASSWORD TEAM_ID <<< "$FIELDS"
  427. </pre>
  428. <h3><tt style="color:inherit">op read</tt></h3>
  429. <p>You can also use the <tt>read parameter</tt> which takes a URL-style path:</p>
  430. <pre>
  431. op read op://&lt;vault&gt;/&lt;item&gt;/&lt;field&gt;
  432. </pre>
  433. <p>Use <tt>op vault list</tt> to view the list of vault names and you don't need <tt>--reveal</tt> for passwords. </p>
  434. <p>For my case, I can use:</p>
  435. <pre>
  436. APPLE_ID=$(read "op://Private/Apple App Notarisation/username")
  437. APP_SPECIFIC_PASSWORD=$(op read "op://Private/Apple App Notarisation/password")
  438. TEAM_ID=$(op read "op://Private/Apple App Notarisation/team_id")
  439. </pre>
  440. <h3>Format as JSON</h3>
  441. <p>You can also get the entire item in JSON using:</p>
  442. <pre>op item get "Apple App Notarisation" --format json</pre>
  443. <p>Then use <a href="https://jqlang.org/"><tt>jq</tt></a> to extract what you need. e.g to print the username and password you could do:</p>
  444. <pre>op item get "Apple App Notarisation" --format json | jq -r '
  445.  .fields[] | select(.label=="username" or .label=="password") | "\(.label): \(.value)"
  446. '</pre>
  447. <h3>That's it</h3>
  448. <p>That's it. Very simple to put into a script and keep my password secure.</p>
  449. ]]></content:encoded>
  450. <wfw:commentRss>https://akrabat.com/using-the-1password-cli-in-a-script/feed/</wfw:commentRss>
  451. <slash:comments>0</slash:comments>
  452. </item>
  453. <item>
  454. <title>Accessing my printer&#039;s web app remotely via Tailscale</title>
  455. <link>https://akrabat.com/accessing-my-printers-web-app-remotely-via-tailscale/</link>
  456. <comments>https://akrabat.com/accessing-my-printers-web-app-remotely-via-tailscale/#respond</comments>
  457. <dc:creator><![CDATA[Rob]]></dc:creator>
  458. <pubDate>Tue, 01 Jul 2025 09:00:00 +0000</pubDate>
  459. <category><![CDATA[Computing]]></category>
  460. <guid isPermaLink="false">https://akrabat.com/?p=7404</guid>
  461.  
  462. <description><![CDATA[We have an HP all-in-one scanner and printer that is on our local network. Recently, I was away from home and needed to reconfigure the scanning settings for unimportant reasons. Usually, when I'm not in the office, I use Tailscale to connect back to machines as required, but the printer isn't running Tailscale, so its built-in web app isn't directly available. To solve this problem, I set up Tailscale subnet route on the Linux box… <a href="https://akrabat.com/accessing-my-printers-web-app-remotely-via-tailscale/">continue reading</a>.]]></description>
  463. <content:encoded><![CDATA[<p>We have an HP all-in-one scanner and printer that is on our local network. Recently, I was away from home and needed to reconfigure the scanning settings for unimportant reasons.</p>
  464. <p>Usually, when I'm not in the office, I use <a href="https://tailscale.com/">Tailscale</a> to connect back to machines as required, but the printer isn't running Tailscale, so its built-in web app isn't directly available. To solve this problem, I set up Tailscale subnet route on the Linux box I have in the office.</p>
  465. <p>As this is Linux box, it was easy enough to SSH into it and I ran:</p>
  466. <pre>
  467. sudo tailscale set --advertise-routes=192.168.220./24
  468. </pre>
  469. <p>I can't remember why our local network uses the 192.168.220.0 range; I suspect it's related to a client's VPN config being overbearing, before I started jailing such clients in a VM.</p>
  470. <p>The Linux box now knows about the subnet route. To enable it, head over the <a href="https://login.tailscale.com/admin/machines">machines tab</a> of the Tailscale admin, find the machine in question and click the "&#8230;" button to find the "Edit route settings" item to authorise it.</p>
  471. <p>Once this was done, Chrome on my remote Mac could access the printer's web app on https://192.168.220.24 and I did the admin required remotely.</p>
  472. <p>I'm unclear if there'll be any issues leaving this route enabled all the time, as <a href="https://github.com/tailscale/tailscale/issues/1227">issue 1227</a> implies that everything local might get routed via my Linux box, so I disabled it after use and will enable it as an when I need it.</p>
  473. ]]></content:encoded>
  474. <wfw:commentRss>https://akrabat.com/accessing-my-printers-web-app-remotely-via-tailscale/feed/</wfw:commentRss>
  475. <slash:comments>0</slash:comments>
  476. </item>
  477. <item>
  478. <title>FIxing Linux Tailscale exit node routing</title>
  479. <link>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/</link>
  480. <comments>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/#comments</comments>
  481. <dc:creator><![CDATA[Rob]]></dc:creator>
  482. <pubDate>Tue, 24 Jun 2025 09:00:00 +0000</pubDate>
  483. <category><![CDATA[Computing]]></category>
  484. <guid isPermaLink="false">https://akrabat.com/?p=7401</guid>
  485.  
  486. <description><![CDATA[I run a Tailscale network so that remote computers can access local services. I also have a Linux box at home on that network that advertises itself as an exit node and recently noticed that it wasn't working. I had some time recently to sit down and work out what was going on. My initial suspicion was that it was DNS related as a cursory search brought up lots of results related to DNS. However,… <a href="https://akrabat.com/fixing-linux-tailscale-exit-node-routing/">continue reading</a>.]]></description>
  487. <content:encoded><![CDATA[<p>I run a <a href="https://tailscale.com/">Tailscale</a> network so that remote computers can access local services. I also have a Linux box at home on that network that advertises itself as an <a href="https://tailscale.com/kb/1103/exit-nodes">exit node</a> and recently noticed that it wasn't working.</p>
  488. <p>I had some time recently to sit down and work out what was going on. My initial suspicion was that it was DNS related as a cursory search brought up lots of results related to DNS. However, some quick tests with <tt>nslookup</tt> and <tt>dig</tt> showed that DNS was correctly resolving, so it seemed to be a routing issue.</p>
  489. <p>Further searching led me to realise that my Linux box needs to masquerade the traffic. This can be done using:</p>
  490. <pre>
  491. sudo iptables -t nat -A POSTROUTING -o {network interface} -j MASQUERADE
  492. </pre>
  493. <p>I used <tt>ifconfig</tt> to look up my network interface which was <tt>enp3s0</tt> and then all was well. </p>
  494. <p>I connected my Mac to the exit node from a remote location and could browse the web with my remote IP address correctly set to my home's IP address.</p>
  495. <p>Given that this was working, I'm unclear what has changed such that this setting needed configuring. I have found <a href="https://github.com/tailscale/tailscale/issues/15708">issue 15708</a> which may be related so potentially a future Tailscale update will solve this. I don't rebook this box often, so maybe I set this flag before and forgot?</p>
  496. <p>I've written it up here now though, so I can find it again if I need it!</p>
  497. ]]></content:encoded>
  498. <wfw:commentRss>https://akrabat.com/fixing-linux-tailscale-exit-node-routing/feed/</wfw:commentRss>
  499. <slash:comments>1</slash:comments>
  500. </item>
  501. <item>
  502. <title>Renaming files with Hazel</title>
  503. <link>https://akrabat.com/renaming-files-with-hazel/</link>
  504. <comments>https://akrabat.com/renaming-files-with-hazel/#respond</comments>
  505. <dc:creator><![CDATA[Rob]]></dc:creator>
  506. <pubDate>Tue, 17 Jun 2025 10:00:00 +0000</pubDate>
  507. <category><![CDATA[Mac]]></category>
  508. <guid isPermaLink="false">https://akrabat.com/?p=7390</guid>
  509.  
  510. <description><![CDATA[I'm a member of a number of groups that publish a magazine, either paper-based or PDF. I prefer the PDF version, so download from the website and then move to the relevant directory. Recently, I realised that I could use Hazel to do this for me. To take one example, the filename of the PDF that I download is of the format PE-{issue number}-web{some random characters}.pdf, for example: PE-123-web9j45s3gd.pdf. There's sometimes a hyphen after web,… <a href="https://akrabat.com/renaming-files-with-hazel/">continue reading</a>.]]></description>
  511. <content:encoded><![CDATA[<p>I'm a member of a number of groups that publish a magazine, either paper-based or PDF. I prefer the PDF version, so download from the website and then move to the relevant directory.</p>
  512. <p>Recently, I realised that I could use Hazel to do this for me.</p>
  513. <p>To take one example, the filename of the PDF that I download is of the format <tt>PE-{issue number}-web{some random characters}.pdf</tt>, for example: <tt>PE-123-web9j45s3gd.pdf</tt>. There's sometimes a hyphen after <tt>web</tt>, but not always.</p>
  514. <p>I want the filename to <tt>PE Magazine {issue number}</tt></p>
  515. <p>The rule in Hazel looks like this:</p>
  516. <picture><source  
  517. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-dark.png"
  518.        media="(prefers-color-scheme: dark)"
  519.    /><source
  520. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-light.png"
  521.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  522.    /><br />
  523.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-rename-pe-mag-light.png" loading="lazy" alt="Screenshot of Hazel rename rule" width="500"/>
  524. </picture>
  525. <h2>Matching the name</h2>
  526. <p>Breaking it down, we detect that this is a file of interest with a <em>Name matches</em> rule. We can start with the literal string <tt>PE-</tt> as that's constant, but then we need to match the number and also give it a name so that we can refer to it later.</p>
  527. <p>This is done using the "Custom Text" element. You can then click on it to edit its attributes where I set its name to "issue" and the pattern to "Number" which matches sequential digits.</p>
  528. <picture><source  
  529. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-dark.png"
  530.        media="(prefers-color-scheme: dark)"
  531.    /><source
  532. srcset="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-light.png"
  533.        media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)"
  534.    /><br />
  535.    <img decoding="async" src="https://akrabat.com/wp-content/uploads/2025/06/2025hazel-match-attributes-light.png" loading="lazy" alt="Screenshot of Hazel match attributes screen" class="border" width="300"/>
  536. </picture>
  537. <p>The rest of the name match is a hyphen followed by an "Anything" element as we don't care about any other characters.</p>
  538. <p><tt>Renaming</tt></p>
  539. <p>Now that we have we can use the <em>Rename with pattern</em> action with the literal "<tt>PE Magazine - </tt>" followed by our "issue" custom element that we created in the match rule, followed by the extension.</p>
  540. <h2>Finishing it off</h2>
  541. <p>Once the file has been renamed, there's a "Move to folder" action to move it the right place and then the folder is opened, so that I can grab the file if I need to.</p>
  542. <p>That's it. Automating the rename and move means that I don't have to think about it again.</p>
  543. ]]></content:encoded>
  544. <wfw:commentRss>https://akrabat.com/renaming-files-with-hazel/feed/</wfw:commentRss>
  545. <slash:comments>0</slash:comments>
  546. </item>
  547. </channel>
  548. </rss>
  549.  

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=http%3A//akrabat.com/feed/

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda