Using Raycast to Move My Mac

An AOC Ultrawide (49″) dominates my desk. Stretching 47″ from wingtip to wingtip, it serves as:

  • The only display for the Linux tower that hides between my desk and the wall.
  • A supplemental (primary) display for my MacBooks.
  • The KVM switch to swap

I have two MacBooks:

  • A 13″ MacBook Pro for work
  • A 13″ MacBook Air for personal use

During the work day, I typically have my work MacBook to the left of my monitor, plugged in to the USB-C cable. My MacBook Air sits against the wall, closed and dormant. The desk space in front of me is open and available, so I can use paper and pen to take notes, manage my to-do list, etc. That setup looks like this:

            ┌─────────────────────────────────────────────┐
            │                                             │
            │                                             │
   ┌────────│                   Monitor                   │
   │        │                                             │
   │        │                                             │
USB-C       └─────────────────────────────────────────────┘
   │  ┌──────────┐
   │  │   Work   │
   └──│ MacBook  │
      └──────────┘

During the work day, I’ll sometimes grab my personal MacBook, open it up, and set it on my desk. Maybe I’m accessing some personal files, or wanting to follow some Hacker News link that hasn’t yet been categorized in BlueCoat so is blocked at the work proxy. Here’s that setup:

            ┌─────────────────────────────────────────────┐
            │                                             │
            │                                             │
   ┌────────│                   Monitor                   │
   │        │                                             │
   │        │                                             │
USB-C       └─────────────────────────────────────────────┘
   │  ┌──────────┐           ┌──────────┐
   │  │   Work   │           │ Personal │
   └──│ MacBook  │           │ MacBook  │
      └──────────┘           └──────────┘

Sometimes I want to display my personal MacBook on the big monitor. I swap the USB-C cable, like this:

            ┌─────────────────────────────────────────────┐
            │                                             │
            │                                             │
            │                   Monitor                   │
            │                                             │
            │                                             │
            └────────────┬────────────────────────────────┘
      ┌──────────┐       │   ┌──────────┐
      │   Work   │    USB-C  │ Personal │
      │ MacBook  │       └───┤ MacBook  │
      └──────────┘           └──────────┘

Any time I have both laptops on my desk, I lose my desk space for my notebook. Or for the book I’m reading. Or for my breakfast or lunch or dinner. It’s a compromise I live with, until evenings or weekends. Then, I lean my work laptop against the wall, move my personal laptop to the left, and do personal projects. That setup looks like this:

            ┌─────────────────────────────────────────────┐
            │                                             │
            │                                             │
   ┌────────│                   Monitor                   │
   │        │                                             │
   │        │                                             │
USB-C       └─────────────────────────────────────────────┘
   │  ┌──────────┐
   │  │ Personal │
   └──│ MacBook  │
      └──────────┘

Shifting my laptop from center to left requires little effort, but macOS doesn’t know I’ve moved the laptop. To make moving my mouse pointer across screens spatially coherent, I have to:

  • Open Displays in System Settings (using Raycast, of course)
  • Navigate my mouse pointer to the System Settings window, which can be tricky depending on which screen the window and the pointer are on
  • Click the Arrange… button
  • Move the virtual laptop screen to the left
  • Click the Done button

Then the weekend ends, or it’s tomorrow, and work has begun anew. The work laptop takes over the left and the personal one leans against the wall. When the moment comes that I drag the personal one out and set it up in the middle of my desk, I have to retrace the steps outlined above, only this time I move the virtual laptop screen to the center.

I’ve done this for ages. Me, a software developer, not using automation. Who, even, am I? Last night, the idiocy of my manual steps hit me. Time to automate.

My first thought: Automator. I always think I’m going to use Automator for something, and then I never do. This is one of those cases. I fired up Automator and hit the New Document button while thinking, “Am I even doing this right?” I selected Application, figuring that would be easier to launch from Raycast. Then, I saw a search bar and typed “Display.” Nothing seemed relevant. I typed “Arrange.” No results. So I used Raycast to launch a Kagi search. What I found:

  • Automator doesn’t seem to support what I’m trying to do.
  • A utility called displayplacer does seem to support this use case. Thank you, Jake Hilborn!

I installed displayplacer (brew install displayplacer) and skimmed the instructions. I’m 55 years old, and as illogical as this is, I feel like I’ve done my time reading the f**kin’ manual, and all that reading should count going forward. So I skimmed the README and saw: “Show current screen info and possible resolutions: displayplacer list.” So I ran that command, expecting to see current screen info, and saw this:

Persistent screen id: 37D8832A-2D66-02CA-B9F7-8F30A301B230
Contextual screen id: 1
Serial screen id: s4251086178
Type: MacBook built in screen
Resolution: 1470x956
Hertz: 60
Color Depth: 8
Scaling: on
Origin: (0,0) - main display
Rotation: 0 - rotate internal screen example (may crash computer, but will be rotated after rebooting): `displayplacer "id:37D8832A-2D66-02CA-B9F7-8F30A301B230 degree:90"`
Enabled: true
Resolutions for rotation 0:
  mode 0: res:960x600 hz:60 color_depth:8 scaling:on
  mode 1: res:1024x640 hz:60 color_depth:8 scaling:on
  mode 2: res:1024x666 hz:60 color_depth:8 scaling:on
  mode 3: res:1280x800 hz:60 color_depth:8 scaling:on
  mode 4: res:1280x832 hz:60 color_depth:8 scaling:on
  mode 5: res:1470x918 hz:60 color_depth:8 scaling:on
  mode 6: res:1470x956 hz:60 color_depth:8 scaling:on <-- current mode
  mode 7: res:1710x1068 hz:60 color_depth:8 scaling:on
  mode 8: res:1710x1112 hz:60 color_depth:8 scaling:on
  mode 9: res:1920x1200 hz:60 color_depth:8
  mode 10: res:2048x1280 hz:60 color_depth:8
  mode 11: res:2048x1332 hz:60 color_depth:8
  mode 12: res:2560x1600 hz:60 color_depth:8
  mode 13: res:2560x1664 hz:60 color_depth:8

Persistent screen id: 5560ED61-A63E-45E0-83E4-1BBF803B08E4
Contextual screen id: 3
Serial screen id: s497
Type: 49 inch external screen
Resolution: 5120x1440
Hertz: 120
Color Depth: 8
Scaling: off
Origin: (-1878,-1440)
Rotation: 0
Enabled: true
Resolutions for rotation 0:
  mode 0: res:800x600 hz:120 color_depth:8
  mode 1: res:800x600 hz:120 color_depth:8 scaling:on
  mode 2: res:800x600 hz:75 color_depth:8 scaling:on
  mode 3: res:800x600 hz:75 color_depth:8
  mode 4: res:800x600 hz:72 color_depth:8
  mode 5: res:800x600 hz:70 color_depth:8 scaling:on
  mode 6: res:800x600 hz:60 color_depth:8 scaling:on
  mode 7: res:800x600 hz:60 color_depth:8
  mode 8: res:800x600 hz:56 color_depth:8
  mode 9: res:960x540 hz:120 color_depth:8 scaling:on
  mode 10: res:960x540 hz:60 color_depth:8 scaling:on
  mode 11: res:960x540 hz:50 color_depth:8 scaling:on
  mode 12: res:1024x768 hz:120 color_depth:8
  mode 13: res:1024x768 hz:75 color_depth:8
  mode 14: res:1024x768 hz:70 color_depth:8
  mode 15: res:1024x768 hz:60 color_depth:8
  mode 16: res:1280x720 hz:120 color_depth:8 scaling:on
  mode 17: res:1280x720 hz:60 color_depth:8
  mode 18: res:1280x720 hz:60 color_depth:8 scaling:on
  mode 19: res:1280x720 hz:50 color_depth:8
  mode 20: res:1280x960 hz:120 color_depth:8
  mode 21: res:1280x960 hz:75 color_depth:8
  mode 22: res:1280x960 hz:70 color_depth:8
  mode 23: res:1280x960 hz:60 color_depth:8
  mode 24: res:1280x1024 hz:75 color_depth:8
  mode 25: res:1280x1024 hz:60 color_depth:8
  mode 26: res:1344x1008 hz:120 color_depth:8
  mode 27: res:1344x1008 hz:75 color_depth:8
  mode 28: res:1344x1008 hz:70 color_depth:8
  mode 29: res:1344x1008 hz:60 color_depth:8
  mode 30: res:1600x1200 hz:120 color_depth:8
  mode 31: res:1600x1200 hz:75 color_depth:8
  mode 32: res:1600x1200 hz:70 color_depth:8
  mode 33: res:1600x1200 hz:60 color_depth:8
  mode 34: res:1920x540 hz:120 color_depth:8
  mode 35: res:1920x540 hz:120 color_depth:8 scaling:on
  mode 36: res:1920x540 hz:75 color_depth:8 scaling:on
  mode 37: res:1920x540 hz:75 color_depth:8
  mode 38: res:1920x540 hz:70 color_depth:8 scaling:on
  mode 39: res:1920x540 hz:70 color_depth:8
  mode 40: res:1920x540 hz:60 color_depth:8 scaling:on
  mode 41: res:1920x540 hz:60 color_depth:8
  mode 42: res:1920x1080 hz:120 color_depth:8
  mode 43: res:1920x1080 hz:60 color_depth:8
  mode 44: res:1920x1080 hz:50 color_depth:8
  mode 45: res:2048x576 hz:120 color_depth:8 scaling:on
  mode 46: res:2048x576 hz:120 color_depth:8
  mode 47: res:2048x576 hz:75 color_depth:8 scaling:on
  mode 48: res:2048x576 hz:75 color_depth:8
  mode 49: res:2048x576 hz:70 color_depth:8
  mode 50: res:2048x576 hz:70 color_depth:8 scaling:on
  mode 51: res:2048x576 hz:60 color_depth:8
  mode 52: res:2048x576 hz:60 color_depth:8 scaling:on
  mode 53: res:2304x648 hz:120 color_depth:8
  mode 54: res:2304x648 hz:120 color_depth:8 scaling:on
  mode 55: res:2304x648 hz:75 color_depth:8
  mode 56: res:2304x648 hz:75 color_depth:8 scaling:on
  mode 57: res:2304x648 hz:70 color_depth:8
  mode 58: res:2304x648 hz:70 color_depth:8 scaling:on
  mode 59: res:2304x648 hz:60 color_depth:8
  mode 60: res:2304x648 hz:60 color_depth:8 scaling:on
  mode 61: res:2560x720 hz:120 color_depth:8
  mode 62: res:2560x720 hz:120 color_depth:8 scaling:on
  mode 63: res:2560x720 hz:75 color_depth:8
  mode 64: res:2560x720 hz:75 color_depth:8 scaling:on
  mode 65: res:2560x720 hz:70 color_depth:8
  mode 66: res:2560x720 hz:70 color_depth:8 scaling:on
  mode 67: res:2560x720 hz:60 color_depth:8
  mode 68: res:2560x720 hz:60 color_depth:8 scaling:on
  mode 69: res:2560x1440 hz:120 color_depth:8
  mode 70: res:2560x1440 hz:60 color_depth:8
  mode 71: res:3008x846 hz:120 color_depth:8 scaling:on
  mode 72: res:3008x846 hz:120 color_depth:8
  mode 73: res:3008x846 hz:75 color_depth:8 scaling:on
  mode 74: res:3008x846 hz:75 color_depth:8
  mode 75: res:3008x846 hz:70 color_depth:8 scaling:on
  mode 76: res:3008x846 hz:70 color_depth:8
  mode 77: res:3008x846 hz:60 color_depth:8 scaling:on
  mode 78: res:3008x846 hz:60 color_depth:8
  mode 79: res:3200x900 hz:120 color_depth:8
  mode 80: res:3200x900 hz:75 color_depth:8
  mode 81: res:3200x900 hz:70 color_depth:8
  mode 82: res:3200x900 hz:60 color_depth:8
  mode 83: res:3360x946 hz:120 color_depth:8
  mode 84: res:3360x946 hz:75 color_depth:8
  mode 85: res:3360x946 hz:70 color_depth:8
  mode 86: res:3360x946 hz:60 color_depth:8
  mode 87: res:3840x1080 hz:120 color_depth:8
  mode 88: res:3840x1080 hz:75 color_depth:8
  mode 89: res:3840x1080 hz:70 color_depth:8
  mode 90: res:3840x1080 hz:60 color_depth:8
  mode 91: res:4096x1152 hz:120 color_depth:8
  mode 92: res:4096x1152 hz:75 color_depth:8
  mode 93: res:4096x1152 hz:70 color_depth:8
  mode 94: res:4096x1152 hz:60 color_depth:8
  mode 95: res:4608x1296 hz:120 color_depth:8
  mode 96: res:4608x1296 hz:75 color_depth:8
  mode 97: res:4608x1296 hz:70 color_depth:8
  mode 98: res:4608x1296 hz:60 color_depth:8
  mode 99: res:5120x1440 hz:120 color_depth:8 <-- current mode
  mode 100: res:5120x1440 hz:75 color_depth:8
  mode 101: res:5120x1440 hz:70 color_depth:8
  mode 102: res:5120x1440 hz:60 color_depth:8
  mode 103: res:1024x768 hz:120 color_depth:8 scaling:on
  mode 104: res:1024x768 hz:75 color_depth:8 scaling:on
  mode 105: res:1024x768 hz:70 color_depth:8 scaling:on
  mode 106: res:1024x768 hz:60 color_depth:8 scaling:on
  mode 107: res:1280x960 hz:120 color_depth:8 scaling:on
  mode 108: res:1280x960 hz:75 color_depth:8 scaling:on
  mode 109: res:1280x960 hz:70 color_depth:8 scaling:on
  mode 110: res:1280x960 hz:60 color_depth:8 scaling:on
  mode 111: res:1344x1008 hz:120 color_depth:8 scaling:on
  mode 112: res:1344x1008 hz:75 color_depth:8 scaling:on
  mode 113: res:1344x1008 hz:70 color_depth:8 scaling:on
  mode 114: res:1344x1008 hz:60 color_depth:8 scaling:on
  mode 115: res:1600x1200 hz:120 color_depth:8 scaling:on
  mode 116: res:1600x1200 hz:75 color_depth:8 scaling:on
  mode 117: res:1600x1200 hz:70 color_depth:8 scaling:on
  mode 118: res:1600x1200 hz:60 color_depth:8 scaling:on
  mode 119: res:400x300 hz:120 color_depth:8 scaling:on
  mode 120: res:400x300 hz:75 color_depth:8 scaling:on
  mode 121: res:400x300 hz:72 color_depth:8 scaling:on
  mode 122: res:400x300 hz:60 color_depth:8 scaling:on
  mode 123: res:400x300 hz:56 color_depth:8 scaling:on
  mode 124: res:512x384 hz:120 color_depth:8 scaling:on
  mode 125: res:512x384 hz:75 color_depth:8 scaling:on
  mode 126: res:512x384 hz:70 color_depth:8 scaling:on
  mode 127: res:512x384 hz:60 color_depth:8 scaling:on
  mode 128: res:640x360 hz:60 color_depth:8 scaling:on
  mode 129: res:640x360 hz:50 color_depth:8 scaling:on
  mode 130: res:640x480 hz:120 color_depth:8 scaling:on
  mode 131: res:640x480 hz:75 color_depth:8 scaling:on
  mode 132: res:640x480 hz:70 color_depth:8 scaling:on
  mode 133: res:640x480 hz:60 color_depth:8 scaling:on
  mode 134: res:640x480 hz:75 color_depth:8
  mode 135: res:640x480 hz:72 color_depth:8
  mode 136: res:640x480 hz:60 color_depth:8
  mode 137: res:640x512 hz:75 color_depth:8 scaling:on
  mode 138: res:640x512 hz:60 color_depth:8 scaling:on
  mode 139: res:672x504 hz:120 color_depth:8 scaling:on
  mode 140: res:672x504 hz:75 color_depth:8 scaling:on
  mode 141: res:672x504 hz:70 color_depth:8 scaling:on
  mode 142: res:672x504 hz:60 color_depth:8 scaling:on
  mode 143: res:720x480 hz:60 color_depth:8
  mode 144: res:720x576 hz:50 color_depth:8
  mode 145: res:1152x324 hz:120 color_depth:8 scaling:on
  mode 146: res:1152x324 hz:75 color_depth:8 scaling:on
  mode 147: res:1152x324 hz:70 color_depth:8 scaling:on
  mode 148: res:1152x324 hz:60 color_depth:8 scaling:on
  mode 149: res:1280x360 hz:120 color_depth:8 scaling:on
  mode 150: res:1280x360 hz:75 color_depth:8 scaling:on
  mode 151: res:1280x360 hz:70 color_depth:8 scaling:on
  mode 152: res:1280x360 hz:60 color_depth:8 scaling:on
  mode 153: res:1504x423 hz:120 color_depth:8 scaling:on
  mode 154: res:1504x423 hz:75 color_depth:8 scaling:on
  mode 155: res:1504x423 hz:70 color_depth:8 scaling:on
  mode 156: res:1504x423 hz:60 color_depth:8 scaling:on
  mode 157: res:1600x450 hz:120 color_depth:8 scaling:on
  mode 158: res:1600x450 hz:75 color_depth:8 scaling:on
  mode 159: res:1600x450 hz:70 color_depth:8 scaling:on
  mode 160: res:1600x450 hz:60 color_depth:8 scaling:on
  mode 161: res:1680x473 hz:120 color_depth:8 scaling:on
  mode 162: res:1680x473 hz:75 color_depth:8 scaling:on
  mode 163: res:1680x473 hz:70 color_depth:8 scaling:on
  mode 164: res:1680x473 hz:60 color_depth:8 scaling:on
  mode 165: res:1920x540 hz:120 color_depth:8 scaling:on
  mode 166: res:1920x540 hz:60 color_depth:8 scaling:on
  mode 167: res:3840x1080 hz:120 color_depth:8
  mode 168: res:3840x1080 hz:60 color_depth:8

Execute the command below to set your screens to the current arrangement. If screen ids are switching, please run `displayplacer --help` for info on using contextual or serial ids instead of persistent ids.

displayplacer "id:37D8832A-2D66-02CA-B9F7-8F30A301B230 res:1470x956 hz:60 color_depth:8 enabled:true scaling:on origin:(0,0) degree:0" "id:5560ED61-A63E-45E0-83E4-1BBF803B08E4 res:5120x1440 hz:120 color_depth:8 enabled:true scaling:off origin:(-1878,-1440) degree:0"

Oh no oh no oh no I’m back in XWindows and I have to get things exactly right or my display won’t come up at all — or worse, my monitor will explode. Visions of installing Red Hat from CD during the Clinton era raced through my head, back when I was still doing my time, RTFMing. Just as my eyes began to glaze, I saw the last part of the output:

Execute the command below to set your screens to the current arrangement. If screen ids are switching, please run `displayplacer --help` for info on using contextual or serial ids instead of persistent ids.

displayplacer "id:37D8832A-2D66-02CA-B9F7-8F30A301B230 res:1470x956 hz:60 color_depth:8 enabled:true scaling:on origin:(0,0) degree:0" "id:5560ED61-A63E-45E0-83E4-1BBF803B08E4 res:5120x1440 hz:120 color_depth:8 enabled:true scaling:off origin:(-1878,-1440) degree:0"

Jake, you are an angel. I copied that displayplacer line into a shell script called middle. Then, I arranged the virtual display to the left, reran displayplacer list, and copied that output line to a shell script called left. I put both shell scripts in a directory in my path: ~/bin. I ran them a few times and verified in the Displays app that they, indeed, virtually moved the laptop screen to left and to center.

Next step: incorporate into Raycast. I opened Raycast Settings / Extensions / Scripts, and fumbled my way into adding the ~/bin directory by pushing Add Directories. I hoped my scripts would automatically display, but they didn’t. I clicked the + button, thinking I could point to an existing script, but I could only create a new one. Somewhat exasperated, I created a new script and verified that yes, it displayed in Raycast Settings / Extensions / Scripts / Script Commands. I looked at the script to see what witchcraft it contained to appear in Raycast, but this is where the hubris of age caught me: I ignored the comments in the script. Comments don’t count, I figured. Rookie mistake by a graybeard. “Hmm,” I thought, “Raycast must have some internal registry that contains only scripts created through its UI.” Because clearly the complicated answer is the correct one. I almost succumbed to the ease of recreating my two scripts through Raycast’s UI, but I turned to Kagi first to find out how to trick this internal registry. I started RTFMing here: https://www.raycast.com/blog/getting-started-with-script-commands. This line leapt off the screen:

All scripts have parameters to instruct Raycast on how to process and output your request.

Well, duh. The line # Required parameters: should have been sufficient. But now I got it. I updated my scripts with the required comments, and they both popped into view:

Here’s the left script; the middle script is similar enough that you can figure it out:

#!/bin/bash

# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title MacBook Air on Left
# @raycast.mode silent

# Optional parameters:
# @raycast.icon 🤖
# @raycast.packageName Display Arrangements

# Documentation:
# @raycast.description Run this when the laptop is to the left of the main display
# @raycast.author rob_warner
# @raycast.authorURL https://raycast.com/rob_warner

displayplacer "id:37D8832A-2D66-02CA-B9F7-8F30A301B230 res:1470x956 hz:60 color_depth:8 enabled:true scaling:on origin:(0,0) degree:0" "id:5560ED61-A63E-45E0-83E4-1BBF803B08E4 res:5120x1440 hz:120 color_depth:8 enabled:true scaling:off origin:(696,-1440) degree:0"

After some Raycast testing, I declared the scripts flawless!

P.S. I made those textual graphics (monitor and laptop(s)) using Monodraw. Well worth the $10.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.