AppleScript to eject mounted local volumes

AppleScript to eject mounted attached volumes/disks.

I have a MBP which I use with a Thunderbolt dock to connect to various USB and thunderbolt hard drives, SSDs, and optical drives. This script lets me eject and unmount anything physically connected before disconnecting my Thunderbolt dock.

Yah, sure, OWC makes a utility you can install that’ll do this, but it installs a kernel extension which I wasn’t keen on installing.

-- AppleScript I wrote to eject mounted USB disks before I disconnect my USB-C hub.
-- Notes: 
-- this will halt a time machine backup to a locally mounted 
--   drive when it tries to eject it.
--   will also halt a time machine backup to a network destination.
-- tested in macOS 11.6 (2021.11.19)
-- License:  MIT

tell application "System Events"
  set ds to get disks
  
  -- for debugging
  if false then
    log "list of disks: "
    repeat with d in ds
      log (get the URL of d)
      log (get the name of d)
    end repeat
  end if
  
  log ("Ejecting local ejectable volumes")
  repeat with d in ds
    -- before we try to do anything with this disk    
    -- try to access the disk and catch any errors 
    -- as a way to test if the disk is still present.
    -- set a flag that we can test below.
    set diskAvailable to true
    try
      set trashme to (get the name of d)
    on error errormsg number errorno
      if errorno is -1728 then set diskAvailable to false
    end try
    
    -- Notes:
    -- ejectable excludes sparse volume mounts for time 
    -- machine, but also means other ejectables aren't unmounted…
    -- ...decided to remove this in the if clause below and let it halt 
    -- time machine backups even for network volumes also
    -- since I'm generally calling this script when I'm disconnecting
    -- the laptop and heading out.
    --         d is not ejectable and ¬
    
    -- starts with "Data@" excludes time machine local snapshot
    --    mounts during backups
    -- starts with file:///Volumes excludes file:///System/Volumes/... 
    --  system volumes.
    -- local volume excludes network mounts
    
    
    if diskAvailable and ¬
      d is local volume and ¬
      the URL of d starts with "file:///Volumes/" and ¬
      the displayed name of d does not start with "Data@" then
      log ("........ ejecting:  " & (get the name of d))
      tell application "Finder" to eject (get the name of d)
    else if diskAvailable then
      log ("   Skipping: " & (the URL of d))
    else
      log ("  Skipping disk that is no longer available")
    end if
  end repeat
  log (" all ejectable local volumes ejected")
end tell

Posted as a gist here for easier copying and using.

Faster to write a script than to deal with so many browser windows? Well, certainly more fun :)

AppleScript to move Safari windows up as much as the window closest to the top of the screen will allow.

There’s a bug in Safari 15.0 where it sometimes re-positions open windows when waking from sleep by moving all the windows down an amount such that the bottom of the lowest one is at the bottom of the monitor (or something like that? They all move down a lot).

If happen to place your Safari windows up enough so that another app’s window can be visible on-screen under them (chat in my case), and you have a few too many windows open in Safari (ahem!), then you might be very annoyed to have to over a hundred windows all back up again.

Now, you could go through your heap of open Safari windows, but if you’re a programmer than you might (likely incorrectly ;)) think it’d be faster to write an AppleScript to move them all back up again. Not that I’d do such a thing, you understand, but, purely for educational purposes, here’s the kind of script one might write (or the second version if the first one took 6 minutes process the > 100 Safari windows you had open) :). This one moves all Safari windows up the same amount to keep their relative positioning and moves them up as much as possible while still keeping the window positioned highest on the screen below the menu bar.

Disclaimer: I write zero to two AppleScripts a year and never really learned AppleScript. There’s almost certainly better ways to do this. But it works for me and fixes my windows after Safari munges them again. So GoodEnough™ 🙂

-- Put in the public domain with zero warranty of any kind
--
-- only tested on macOS 11.6, with Safari 13.0; ymmv

use scripting additions

tell application "Safari"
  set windlist to windows
  
  log "Examining " & length of windlist & " windows..."
  
  set lowestYWindow to first item of windlist
  set lowestYValue to get second item of (get bounds of lowestYWindow)
  
  repeat with wind in windlist
    set curValue to second item of (get bounds of wind)
    if curValue < lowestYValue then
      copy wind to lowestYWindow
      set lowestYValue to curValue
    end if
  end repeat
  
  -- subtract out the title bar height (hard coded - bad dog!)
  set yOffset to lowestYValue - 25
  
  if yOffset > 0 then
    log "moving " & length of windlist & " windows up " & yOffset & " pixels"
    repeat with aWind in windlist
      set aBounds to (get the bounds of aWind)
      set the second item of aBounds to ((get the second item of aBounds as integer) - yOffset)
      set the fourth item of aBounds to ((get the fourth item of aBounds as integer) - yOffset)
      set the bounds of aWind to aBounds
    end repeat
  else
    display alert ("The highest window already at the top!  Not moving any windows!")
  end if
  log "done"
end tell

Here it is in a gist for easier copy/edit, etc.

Blast from the past: “Bedtime conversations tend to be a little goofy when Dad’s doing lights out….”

Cleaning up an old family website (pre blog) for sharing stories with out-of-town family and found this bit of goofy-dad nonsense that made me laugh so I thought I’d share it:

—-

Bedtime conversations tend to be a little goofy when dad’s doing lights out….

A discussion of wavelengths of energy and radio astronomy leads to the following: 

Geek: How do you see things with xrays and radio waves?

Dad: Well you could look at the signals bouncing back. Where there is nothing you get no bounce-back, and where there’s a planets or moon or something you get a bounce back. How long it takes to bounce back tells you how far away it is. Longer wave lenghts show less detail than shorter wave lengths (long discussion of why).

One time there were these astronomers trying to discover new planets and they decided to create a gamma-wave telescope.

Geek: Did they find any?

Dad: Well, they thought they had, but things were kind of strange…. 

So then they sent gamma rays towards this one spot and they got ultraviolet waves back!

“That’s funny” the astronomers said to each other, “let’s try xrays.”

So they shot some xrays towards the same location… this time they got back radio waves! 

Geek: “They did?!”

Dad: “Yep!”

After trying to understand what kind of astronomical body could turn their xrays into radio waves of the frequency and waveforms they’d received back, someone got the idea to play the radio waves through a speaker. Boy were they surprised to hear “HEY! No Peeking!” coming out of the speaker!

(Geek breaks out in laughing!) 

“WHAT!??? said the astronomers. 

They sent back a radio signal on the same frequency saying, “Sorry, we didn’t know anyone was there!” 

They got a message back saying, “You didn’t? Didn’t you see my sign?”.

“What sign?” the astronomers sent back. 

“Check the ultraviolet signal we returned when you sent all those gamma rays,” was the reply. 

So the astronomers took the ultraviolet signal they’d gotten back and figured out it was a sign saying “Bob’s Spaceship Repair Shop.”

—-

The next several nights we’d hear “HEY! No Peeking!” coming from Geek’s room followed by lots of laughter so I guess it was a lasting laughable ™. 🙂

Aug. 2004. (Geek was 8)

Two books that changed my relationship with money

Two books that changed my relationship with money for the better:


 The Millionaire Next Door by Thomas J. Stanley and William D. Danko.

An older book, but the mental model of the people profiled is useful to understand and consider adopting elements of.

Your Money or Your Life by Vicki Robin & Joe Dominguez

I wouldn’t, and haven’t, gone full frugal as outlined in this book, but it’s useful to understand how far that axis of existence goes and then adopt the elements of it that work for you (we have adopted a lot of useful stuff from this book).

The links above are to Amazon (sorry) where you can get paper or Kindle versions, and here are Audible versions of both of these in case audio books work better for you:

The Millionaire Next Door

Your Money or Your Life

NOTE: Of course, both of these books would say “there is no reason to BUY these books! Get them from your library, or via inter-library loan if your library doesn’t have them.  If truly not available that way, buy them used.”   🙂

Regular Expressions for unicode string matching in Swift are pretty cool!

I had to do some validation on a username while coding an iOS app today and so dove into regular expression matching. The specification said “Usernames can include upper and lower case letters, numbers, periods, and underscores” and I’m figuring they’re thinking ASCII only. RegEx of [a-zA-Z0-9_.]+ kind of thing. Thinking of my twitter friend @krzyżanowskim and his posts about not being able to include the proper ż in so many places, I wanted to do better.

So I dove into the world of unicode regular expressions and found this very useful resource. I think I’ve figured out what might be a decent answer (please let me know here or on Twitter @GeekAndDad if you have advice to offer :)).

To include all letters, accented letters, symbols, numbers, an underscore or period (.), but exclude all other punctuation or spaces, I came up with this bit of Swift code:

func validateUsername( username: String) -> Bool { 
  // length tests go here and return false on failure 

  let expr = #"^[\p{L}\p{M}\p{N}_.\p{Me}\p{S}]+$"#
  let matchesRange = username.range(of: expr, options: .regularExpression)
  if matchesRange == nil {
    return false
  }

  // additional validation step here

  return true
}

Note that I don’t have to check matchesRange‘s lowerBound and upperBound against the string’s startIndex or endIndex because I’ve included the ^ and $ to indicate that everything in the string must match the expression.

Seems to work so far. I’m impressed and grateful that Swift’s regular expressions support these unicode specifiers (!!!).

Quick AppleScript to get a Reminders list to TextEdit for printing

UPDATE: iOS 14.5.1 and macOS 11.3.1 have added the ability to print a list! Hallelujah!

––––––––––––––––––––––––––––––––––––

Damn cannot print a Reminders list for the store. Who doesn’t think to add printing?

@steveriggins

I need this capability for the same reason (shared food shopping list is in a shared Reminders list and because of COVID-19 will eventually go shopping without wanting to touch my phone while there). So I hacked together a quick AppleScript to grab all the not-yet-completed items from a list in Reminders and put the name of each reminder prefixed with “[ ] ” as a single line in a new TextEdit document (adding the notes for a reminder is left as an easy exercise for the reader 😉 ).

Here it is as a gist for easy use. And here’s a Feedback issue number if you’d like to report it and add a vote for Apple to fix this: FB7646521

tell application "Reminders"
  set listNames to {}
  repeat with aList in lists
    copy name of aList to end of listNames
  end repeat
  set listName to choose from list listNames with prompt "Select the list to print:"
  
  -- now find list object for the choosen list
  set listToPrint to ""
  repeat with aList in lists
    if name of aList as string is equal to listName as string then
      set listToPrint to aList
      exit repeat
    end if
  end repeat
  
  -- log name of listToPrint as string
  
  -- get a list of the names of all the reminders
  set listItems to reminders of listToPrint
  set reminderStrings to {}
  repeat with aReminder in listItems
    if aReminder is not completed then
      set reminderText to name of aReminder as string
      copy ("[ ]  " & reminderText) to end of reminderStrings
    end if
  end repeat
  
  -- make a single string out of the list of reminders
  set TID to AppleScript's text item delimiters
  set AppleScript's text item delimiters to "
"
  set listText to reminderStrings as text
  set AppleScript's text item delimiters to TID
  log listText
  
  set listText to ((name of listToPrint as string) & return & return & listText)
  
  -- make a new text edit document to print
  tell application "TextEdit"
    make new document
    set text of front document to listText
    -- prints to your default printer
    -- commented out since you may want to set formatting first.
    -- print front document
  end tell
end tell

Safari – Send Window To Back Command

I often end up with a Safari window in front that I don’t want to close or minimize, but that I do want to send behind all other windows. Alas, Safari doesn’t have this command in the Window menu. I’ve requested that it be added (FB7642150) and you can also 😉

In the meantime, I don’t much like AppleScript so I don’t know it well, but this need finally hit the level where I was willing to suffer through the pain of AppleScript and hack something together (there’s probably a better way to write this). I put this into a simple Alfred workflow and it works great (yay!):

on alfred_script(q)
  tell application "Safari"
    local lastVisibleWindow
    repeat with n from 1 to count of windows
      if window n is visible then
        set lastVisibleWindow to window n
      else
        exit repeat
       end if
     end repeat
    set index of front window to index of lastVisibleWindow
  end tell
end alfred_script

You can also use this script from a scripts menu, or other utility able to invoke applescripts, by removing the first and last lines that make it a handler for Alfred.

Tested and works under Safari 13.0.5 on macOS 10.15.3

This script handles the case where you have minimized windows. The “set index of the frontmost window to (count of windows)” type script you’ll find elsewhere online doesn’t work if you have minimized windows.

cheers.

SwiftUI tiny bits: Little view extension to log to the console

I wanted to log to the console in the middle of a SwiftUI chain but you can’t put “print” statements in SwiftUI code in most places (“Everything must be a View!” ;)). Since I was in a playground I couldn’t set a breakpoint that logged to the console (my standard strategy when in an application context). So I wrote this little thing:

extension View {
  func printMessage(_ msg: Any...,
              separator: String = " ",
              terminator: String = "\n")
            -> some View {
    // Print them out as if not
    // converted to an array.
    for m in msg {
      print(m,
            separator,
            terminator: "")
    }
    print()
    return self
  }
}

Use it like so:

import Cocoa
import PlaygroundSupport
import SwiftUI

extension View {
  func printMessage(_ msg: Any...,
        separator: String = " ",
        terminator: String = "\n")
      -> some View {
  // Print them out as if not
  // converted to an array.
  for m in msg {
    print(m,
      separator,
      terminator: "")
  }
  print()
  return self
  }
}

struct ContentView: View {
  var body: some View {
    VStack {
      Text("Hello")
      Text("World!")
        .padding()
        .background(
          GeometryReader { proxy in
            Color.clear
              .printMessage("Info: ", proxy.size)
        })
    }.padding()
  }
}


struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

PlaygroundPage.current.liveView = NSHostingView(rootView: ContentView())

Output is:

Info:   (72.0, 48.0)
Info:   (72.0, 48.0)  
Info:   (72.0, 48.0)  

Could well be a better way or issues with this, but it’s just something I put together quickly to solve a problem (“what is the value of proxy.size?”) and it seems to work, so thought I’d share it. Improvements or better ways to get the same capabilities are welcome 🙂

Tiny Hints: a couple of Xcode 11.3 SwiftUI Preview tips

Xcode 11.3 & SwiftUI Previews: Dark Mode preview bug work-around, compact previews, multi-previews for ColorSchemes and Dynamic Text sizes.

Few small tips for previewing with SwiftUI that I just figured out.

Updated for Xcode 13 thanks to a tip from a reader

Here’s the simple test ContentView.swift file to use in a standard single view iOS SwiftUI app template project in Xcode 11.3 that we’ll use to explore these tips:

import SwiftUI

struct ContentView: View {
  var body: some View {
    HStack {
      Image(systemName: "paperplane")
        .font(.largeTitle)
      VStack {
        Text("Title Text")
          .font(.title)
        Text("Subtitle goes here")
          .font(.subheadline)
      }
    }
  }
}

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

Standard Preview looks like this:

So, the first thing is WOW that takes a lot of space for just a small horizontal cell I’m working on (like for list row or whatever). It’s especially annoying if you’re trying to make sure it works well in dark mode, at different dynamic type sizes, etc. First tip is how to make that work better.

1 – How to preview a horizontal or smaller view in less space in the Xcode Canvas

Context: you’re working on a custom horizontal control, or a row in a list and you want to do multiple previews for light & dark mode, or different text size classes.

By default each preview gets an entire iPhone worth of chrome around it which means you have to scroll to check all your variations out – tiresome! Avoid this by using the .previewLayout modifier with a size that’s sufficient for your view. For example:

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
  }
}

This produces a much more manageable preview like this:

The dimensions you use in .previewLayoutwill be specific to whatever you’re working on, of course, but this comes in really handy when you are previewing multiple versions of a horizontal view like this. Let’s demonstrate that with our next tip by making Dark Mode preview in Xcode 11.3 usable outside a NavigationView.

2 – Dark Mode Preview only seems to really work in NavigationViews

As pointed out by a reader (thanks Shaps!), as of iOS 13 Apple has added a proper view modifier for this so we don’t have to set environment keys (doing so is fine as this is a documented environment key, but, as noted in the documentation, doing it this way doesn’t propagate to the presentation containing the view of the view it’s applied to and the new view modifier does). So instead of .environment(\.colorScheme, .dark) line the code below has was using and the work around for the presenting view not being in dark mode, I’ve changed the approach below to use this new view modifier:

.preferredColorScheme(.dark)

Here’s an example:

struct PreviewsActuallyDarkMode: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
      .preferredColorScheme(.dark)
  }
}

which shows our preview in dark mode as we want:

3 – Combining previews and reducing duplicate code

So now we have dark mode looking good so let’s combine the preceding two tips and look a both light and dark modes on the same screen and without having to scroll:

struct Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
      .previewLayout(.fixed(width: 400, height: 100))
  }
}

struct PreviewsActuallyDarkMode: PreviewProvider {
  static var previews: some View {
    ZStack {
     ContentView()
       .previewLayout(.fixed(width: 400, height: 100))
       .preferredColorScheme(.dark)
  }
}

Which looks like this:

Looking pretty good! While writing them in separate PreviewProviders like that helpfully uses their struct names to label them, it does involve a bit of duplicated code. Here’s an alternate way to do it with a ViewModifier to put shared modifiers in and a Group of views which Xcode makes each their own Preview:

struct MyPreviewModifer: ViewModifier {
  var displayMode: ColorScheme = .light
  var name: String = ""
  
  func body(content: Content) -> some View {
      content
        .previewLayout(.fixed(width: 400, height: 120))
        .previewDisplayName(name)
        .preferredColorScheme(displayMode)
  }
}


struct PreviewsLightDarkDynamicText: PreviewProvider {
  static var previews: some View {
    Group {
      ContentView()
        .modifier(MyPreviewModifer())
      
      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark))

      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark,
                        name: "dark extraSmall"))
        .environment(\.sizeCategory, .extraSmall)

      ContentView()
        .modifier(MyPreviewModifer(displayMode: .dark, 
                        name: "dark extraExtraLarge"))
        .environment(\.sizeCategory, .extraExtraLarge)
    }
  }
}

This gives us four previews and adds the ability to name them since they’re all in the same PreviewProvider struct and so don’t get named individually any longer. Looks like this now:

Well, this “tiny tips” got a little long… but hopefully you found these small tips to be useful.

(Code in gist here for easier copy and paste)

Snippet: SwiftUI View printMessage extension

Just a simple utility to print a string in the middle of a view chain when doing SwiftUI (pardon narrow formatting for blog):

extension View {
  func printMessage(_ msg: Any...,
              separator: String = " ",
              terminator: String = "\n")
            -> some View {
    // Print them out as if not
    // converted to an array.
    for m in msg {
      print(m,
            separator,
            terminator: "")
    }
    print()
    return self
  }
}

Use like this:

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
  var body: some View {
    VStack {
      TestView(label: "hello", idx: 22)
        .font(.title)
      TestView(label: "hello", idx: 24)
        .foregroundColor(Color.red)
      Spacer()
    }
  }
}

struct TestView: View {
  let label: String
  let idx: Int

  var body: some View {
    Text(label)
      .padding(10)
      .printMessage("\(self.label):", "\(self.idx)")
     // or: .printMessage(self.label, self.idx)
  }
}

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())
// In a Swift playground the output is:

hello: 22
hello: 24
hello: 22
hello: 24
hello: 22
hello: 24

Gist here.