• Development,  Tech Tips

    Using a Game Controller to Speed Up Tedious Mac Tasks

    FULL DISCLOSURE: I personally prefer using Keyboard Maestro over Hammerspoon for handling the “actions” in this tutorial. That said, the Hammerspoon approach seemed worth mentioning, even if I don’t have much confidence in my Lua code — so here it is, just in case it’s useful!

    If you spend a lot of time performing repetitive tasks on your Mac, using a game controller as an input device can make things much more efficient—and more comfortable. Whether you’re organizing files, editing video, or culling photos, a controller lets you execute common actions with minimal effort.

    In this post, I’ll walk through how I use a game controller to speed up photo culling—the process of selecting the best images from a shoot. While this tutorial focuses on photo review, the same approach can be applied to many other workflows.

    Culling is an essential but tedious part of a photographer’s workflow. I’ve spent countless hours sorting through tens of thousands of images in ExcireFoto 2025 and Adobe Lightroom, and I quickly realized that using just a keyboard and trackpad was slowing me down. That’s when I started experimenting with using a USB/Bluetooth game controller for culling. Instead of hunching over a keyboard, I can sit back and control everything with a lightweight, ergonomic device in my hands.

    Here’s why it works so well:

    1. Ergonomic – Game controllers are made to fit your hands. It’s natural to hold them for hours at a time.
    2. Quicker actions with muscle memory – Pressing controller buttons becomes second nature, making repetitive tasks much faster.
    3. Keeps your eyes on the screen – No need to glance at your keyboard; your fingers always know where to go.
    4. Better posture and comfort – You can position your monitor optimally without worrying about your keyboard.
    5. Simple to program – Setting up button mappings takes only minutes.
    6. Free software – You don’t need expensive tools to get this working.
    7. Gamifies tedious work – Making a dull task feel more interactive can increase efficiency.
    8. More control over navigation – Assign multiple actions to different buttons or combinations.
    9. Works for more than just culling – This method applies to video editing, file organization, and more.

    I’ve done various iterations of this over time. Today I realized I’ve never blogged about it. So, here goes!

  • Development,  Tech Tips

    Double-tap Modifier Hotkeys in Any Application

    PHPStorm has this great feature where you can double-tap the shift key to open the “Search Everywhere” utility (or at least that’s how I’ve configured my PHPStorm). It’s wonderful and I use it all day. I wanted the same behavior in some of my other applications but quickly realized it’s not achievable out-of-the-box, even with Keyboard Maestro. I tried to use the double-tap usb device key as a trigger but it didn’t work. I ended up solving the problem using Karabiner Elements.

    The idea is simple: Use Karabiner Elements to turn a double-tapped left shift into a hotkey I’d not realistically have set up in any application, then use that hotkey as the hotkey in Keyboard Maestro. I chose <cmd-shift-opt-ctrl-f>.

    First, here is the code, which you can create as a Complex Modification in Karabiner Elements:

  • Development

    Laravel + Filament + Private Disk on Cloudways

    Introduction

    After a long, frustrating debugging exercise, I have discovered a quick tip for my Cloudways + Laravel friends. The “why” is still a touch fuzzy for me at the moment, but I’ll at least explain the symptoms and the fix.

    The goal: Add a private file upload to a Filament resource form, ensuring anonymous users cannot download/view the file.

    The issue: Everything was working great in my ddev environment. I could preview and open the files if I was logged in, and I would see a 403 if I was logged out. When I moved this to a Cloudways server I was getting automatically logged-out every time I hit a private file URL. Weird, right?

    Code

    Here’s the final code (the Cloudways “fix” is further down this page):

  • Development

    Writing Tests for Drush Commands

    There are plenty of examples of these in the wild, but I figured I’d show a stripped down version of an automated Kernel test that successfully tests a drush command. The trick here is making sure you set up a logger and that you stub a few methods (if you happen to use $this->logger()  and dt()  in your Drush commands). Also featured in this example is the use of Faker to generate realistic test data.

    I learned this via the migrate_tools project here.

  • Development

    Using Data Providers in PHPUnit Tests

    This is the before code, where a single test is run, and each scenario I’m testing could be influenced by the previous scenario (not a good thing, unless that was my goal, which it was not).

    This is the after code, where three tests are run. Unfortunately this takes 3x longer to execute. The upside is that each scenario cannot affect the others and it’s perhaps more readable and easier to add additional scenarios.

     

  • Development

    Skip Empty Values During Concatenation in a Drupal 10 Migration

    UPDATE. You can do the same as below without the need for a custom module function. (source)


    This quick example shows one method to ignore empty values in a Drupal 10 migration concatenate process.

    Code

    Migrate code:

    Module code:

    Result

    Example data:

    Resulting field_address_full plain text value

     

  • Development,  Tech Tips

    Using lnav to View Live DDEV Logs

    https://lnav.org/ is a fantastic tool to work with log files. The SQL query functionality is a lot like https://github.com/multiprocessio/dsq, which also impresses.

    My quick tip is that lnav can read from stdin. This means you can pipe content into it. Here’s how I monitor my ddev environment in realtime. Hooray for colors!

    If you haven’t explored lnav you’ll want to dive into the documentation a bit. It does a lot more than just give pretty colors to your log files / output. https://docs.lnav.org/en/v0.11.2/hotkeys.html is certainly worth bookmarking.

  • Development

    Rendering null boolean values with Grid4PHP

    I’m a big fan of Grid4PHP for adding a quick CRUD user interface to a MySQL database.

    I have a lot of boolean columns, like “is_telehealth” that need to start as NULL until we make a determination about the field’s value. Grid4PHP renders null booleans as “0” in the table/edit form. This is misleading, given it’s not actually a 0 value. If you add the ‘isnull’ attribute to the column it lets you save the null value, but it still renders as a “0”.

    Using a custom formatter I was able to render an empty string (nothing) instead of “0” for these null booleans:

  • Development

    Drupal Migrate API skip_on_multiple Process Plugin

    Here’s a Migrate API process plugin I wrote in Drupal 9 that skips a property or row when more than one value exists. My use case:

    1. My source data has locations; each location has multiple associated organizations
    2. My new Drupal site has locations; each location has a parent organization
    3. I want to only populate the field_parent_org field (an entity reference) if there is a single organization value in the source data for the location
    4. I’m using a custom module called “pdms_migrate”

    I’ve stripped out all of the noise from the examples below… hopefully it’s enough to help you understand the example:

  • Development,  Tech Tips

    Handling Daylight Saving Time in Cron Jobs

    • Our business is located in Maine (same timezone as New York; we set clocks back one hour in the Fall and ahead one hour in the Spring)
    • Server A uses ET (automatically adjusts to UTC-4 in the summer, and UTC-5 in the winter)
    • Server B uses UTC (not affected by Daylight Saving Time) – we cannot control the timezone of this server

    We want to ensure both servers are always referencing, figuratively, the same clock on the wall in Maine.

    Here’s what we can do to make sure the timing of cron jobs on Server B (UTC) match the changing times of Server A (ET).

    Only one of the php artisan scrub-db  commands will execute, depending on the time of year.