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.

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.

Tiny Hints: Swift Playground – landscape size for SwiftUI preview

Assuming you have a ContentView defined and want to see it in the Swift playground preview with a landscape aspect ratio, this code snippet seems to work:

let view = UIHostingController(rootView: ContentView())
// pick whatever size you want (this is iPhone 8 div 2):
view.preferredContentSize = CGSize(width: 667, height: 375)
PlaygroundPage.current.liveView = view

Might be a better way, but when I didn’t find a menu item in Xcode 11.3 I just coded this in to move forward. Seems to work to check layout, though it’s not strictly “landscape orientation” from a device standpoint ¯\_(ツ)_/¯:

Improving on the common AnyCancellable .store(…) pattern.

If you’ve been reading any Combine documentation or code, you’ve probably seen code like this before:

let newPhotos = photos.selectedPhotos
newPhotos
  .map { [unowned self] newImage in
    return self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)
  .store(in: &subscriptions)

Pretty standard: call something that returns a publisher, map the results to whatever type you need, assign that value to an instance variable, and finally, store the resulting AnyCancellable into a Set named subscriptions which is an instance variable or property in the enclosing scope. In this case we’re in a UIViewController subclass where it’s defined as:

private var subscriptions = Set<AnyCancellable>()

This code is from the popular book on Combine.

The problem with this pattern in this particular case, and other similar situations, is that the AnyCancellable is never removed from the subscriptions Set. Sometimes this doesn’t matter – when the enclosing scope is going to go away and the code is only called once relatively soon before exiting scope cleans up memory. But sometimes, as in the sample project this code is from, the user can do the action invoking this code (adding photos in this case) repeatedly. Each time this code is invoked the .store(...) call adds another AnyCancellable to the Set. Since this view controller is the root view controller of the app it is never closed and this memory is never cleaned up during the lifetime of the app.


An easy solution is to add an instance variable to your class (a UIViewController in this case):

private var newPhotosSubscription: AnyCancellable?

and then change the code above to something like this so the AnyCancellable is assigned to the instance variable and the .store(..) part is removed:

self.newPhotosSubscription = newPhotos
  .map { [unowned self] newImage in
    self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)

When this assignment happens it frees the previous value, if any, and thus there will be at most a single AnyCancellable kept around.

Ok, that works. But I’m a perfectionist and that single AnyCancellable hanging around bugged me. That’s fixable, but as I went and fixed this issue in more places in the project there was a proliferation of instance variables as I had to add one for each place this came up.

So here’s a solution I came up with to avoid that.

First, add an extension on AnyCancellable:

extension AnyCancellable {
  func store(in dictionary: inout [UInt64: AnyCancellable], 
             for key: UInt64) {
    dictionary[key] = self
  }
}

Then change the subscriptions instance variable to be a matching Dictionary (and remove the individual instance variables you added for each case previously, if you did that already):

private var subscriptions = Dictionary<UInt64, AnyCancellable>()

In our original function which created the newPhotosSubscription and let the user choose photos, change the code to:

let key = DispatchTime.now().uptimeNanoseconds
 
newPhotos
  .handleEvents(receiveCompletion: { [unowned self] _ in
    self.subscriptions.removeValue(forKey: key)
  })
  .map { [unowned self] newImage in
    self.images.value + [newImage]
  }
  .assign(to: \.value, on: images)
  .store(in: &subscriptions, for: key)

So above we created a Dictionary instead of a Set for our subscriptions instance variable that holds on to the AnyCancellable so it remains allocated while needed. The dictionary allows us to store the AnyCancellable under a unique key, which in this case is a UInt64. On the first line above we create that key and assign it the number of nanoseconds since the device rebooted.

Then we add a .handleEvents operator to the subscription pipeline. Once the publisher has sent the .finished or .failure completion event we no longer need to keep the AnyCancellable around. Our receiveCompletion closure code removes the AnyCancellable stored under the captured key from our subscriptions instance variable.

(Note: if you preferred, you could replace UInt64 with UUID in the dictionary declaration and the AnyCancellable extension. Then instead of setting the key value to DispatchTime.now().uptimeNanoseconds you could generate a new UUID with UUID(). All that matters is that the is a unique value conforming to Hashable and doesn’t cost too much to generate).


I’m relatively new to Combine, so if you know a better way to do this, I’d love to hear about it!


Addendum

1) Be aware that Swift closures capture variables by reference by default (unlike for Objective C). So don’t re-use the key variable like I did in my project 

2) I also ran into a case where the pattern above did odd things for a Combine pipeline subscribed to a Future publisher – the store seems to happen after the pipeline has finished executing (huh?!) and thus the clean up in the completion handler is called before the store. I haven’t dug into that to understand why, but thought I’d mention it in case you are seeing things not get cleaned up when you expect like I was.

-> I later learned that this is because in Combine Future is “eager” and will complete immediately. One option is to wrap it in a Deferred publisher and another is to capture the AnyCancellable in a local variable and only save it to the dictionary if the completion block hasn’t been called yet.