MP

The Privilege to Ignore

This has been a rough year, and we’re not even halfway through. I’m tired of reading the news because each day brings another depressing event. I’ve started quickly scrolling past to the happier posts, but many people can’t do the same. They can’t escape from their sick loved ones, or the color of their skin, or their gender, or their sexuality, or the natural disasters that have upended their lives.

I can, at least for now, and it’s likely you can too. I can work from home and pay to have my groceries delivered, reducing my chances of getting sick. I can turn off the news and ignore the protests that are occurring half a world away. I can turn on my TV and watch movies and play games, worrying about what to buy next instead of how to afford my mortgage.

It’s important to recognize what a luxury that is. Someone is out there delivering my food so they can pay for their own. Someone is out protesting in the street, fighting for their life, in the US, and Hong Kong, and India. Someone is trying to recover from a fire that took everything from them.

Give back to the people who aren’t as privileged every chance you get. You have plenty of causes to pick from this year, and there are a lot of people who could use your help with a fresh start.

A Quick Rundown on Notarization

I get periodically asked about Apple’s notarization requirements, so I thought I’d amass my knowledge in one place and link people to when they have questions. This is not an extensive overview, but I’ve tried to strike a balance between technical and understandable. Hopefully you’ll find it useful, especially if I’ve just sent you this link.

What is notarization?

Starting with macOS Catalina (10.15), applications that are not distributed through the Mac App Store must be notarized by Apple. In short, after building an application, you need to upload the build to Apple, have them sign it with their certificate, and then download a receipt from them which gets stapled to the application to show that it is notarized. During this process, Apple verifies the application does not contain malware.

Importantly, you can only staple notarization receipts to packaged applications, which are basically fancy folders containing the application’s files. It is not currently possible to staple a receipt to a standalone executable since there is nowhere to store it. The only workaround is to convert the executable into a packaged application.

When most applications are run (see below for exceptions), Apple will verify the stapled receipt. If a receipt wasn’t stapled to the application, macOS will check with Apple’s servers to see if the application is notarized. If a receipt exists, or the server responds positively, then the application will run normally. If both checks fail, macOS will display an error and refuse to run the application.

Screenshot of the alert that appears when an application isn't notarized

How does notarization work?

Notarization is built on top of a technology called Gatekeeper, which is built on top of extended file attributes. Starting with OS X 10.5, files downloaded via certain methods, such as a web browser, receive a quarantine flag. When users launch an application, Gatekeeper checks for the quarantine flag. In macOS 10.15, if the quarantine flag is present, Gatekeeper will verify the application is notarized and prevent it from running if it is not. Previous versions of macOS would only display a warning.

The current implementation introduces a loophole: the quarantine flag is not set for all downloaded files. For example, games downloaded by Steam do not currently have a quarantine flag, so the notarization check is not run for those games. This loophole extends to basically all applications downloaded or updated by custom mechanisms.

What will notarization look like in the future?

In a session from WWDC 2019, an Apple employee recommended that all software be notarized (emphasis mine):

First, sign and notarize all the software that you distribute, even if it doesn’t get quarantined today.

We don’t know if the loophole for Steam and other downloads is intentional and will remain forever, but their wording leaves the possibility of an expanded quarantine in the future, and Apple is the type of company that chooses their words carefully.

We should hopefully receive more information during WWDC20, but it’s probably a safe bet that we’ll see more changes to notarization in the next version of macOS.

How to Programmatically Add Folders to the Spotlight Ignore List

Automating additions to the Spotlight ignore list used to be as simple as using defaults write, but the release of macOS 10.15 Catalina brought some changes that make it slightly more complicated:

  • The VolumeConfiguration.plist file has moved. After upgrading to Catalina, you now have multiple volumes even though Finder shows the volumes combined. The config’s actual location is now: /System/Volumes/Data/.Spotlight-V100/VolumeConfiguration.plist.
  • defaults no longer seems to work on VolumeConfiguration.plist. Instead, it now complains the domain does not exist.

PlistBuddy does still work, although it’s less elegant. Thankfully, there are some straightforward examples in the man pages that we can copy:

sudo /usr/libexec/PlistBuddy -c "Add :Exclusions: string /path/to/folder/" /System/Volumes/Data/.Spotlight-V100/VolumeConfiguration.plist

Once you’re done editing the ignore list, a restart of your computer should trigger Spotlight to detect the changes.1 You may also be able to trigger it by opening the Spotlight Privacy preferences, or just waiting around for the next index.

But… why?

In practice, I’m using this to automatically ignore dependency folders like node_modules in my dotfiles. Quite often, one package I’m working on depends on another, and it’s annoying for Spotlight and Alfred to display a packaged version instead of my development copy. Automating them away on all my machines makes life just a little bit easier.

  1. In previous versions of macOS, you could manually stop and start the “mds” process to trigger the changes, but that silently fails now that mds is considered a protected process. 

Stop Safari from Forcing HTTPS on Localhost

At my previous job, we ran one of our frontend services through an HTTPS proxy to ensure our development environment was as close to production as possible (cookie policies, content security warnings, etc). We also used an HSTS policy to direct browsers to only use the HTTPS version of our site. Unfortunately, when Safari picks up on this, it insists on redirecting all localhost requests to HTTPS.

Ideally, Safari would ignore those directives for special domains such as “localhost” or take the port number into account, but until it does, you can reset the HSTS settings by running these lines in Terminal:

sudo killall nsurlstoraged
rm -f ~/Library/Cookies/HSTS.plist
sudo launchctl start com.apple.nsurlstoraged.plist

Note that this shortcut will reset the HSTS settings for all websites. If you’re using a lot of open networks, where people could monitor your traffic, feel free to edit the file manually.

Creating a Background Gradient with SwiftUI

Creating a gradient background in SwiftUI follows a similar process to UIKit: create a set of colors, define a start and endpoint, create a view from that gradient, and then assign it as the background of another view. Thankfully, SwiftUI handles the view creation for you, so the resulting code ends up a lot neater.

For example, in UIKit:

let gradient = CAGradientLayer()
gradient.frame = view.bounds
gradient.colors = [UIColor.white.cgColor, UIColor.blue.cgColor]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1.0, y: 1.0)

let gradientView = UIView()
gradientView.layer.insertSublayer(gradient, at: 0)
view.backgroundView = gradientView

And in SwiftUI:

.background(StaticMember(
    LinearGradient(
        gradient: Gradient(colors: [Color.white, Color.blue]),
        startPoint: .topLeading,
        endPoint: .bottomTrailing
    )
))

Quickly Open the Current Tmuxinator Project

I got tired of manually typing the name of Tmuxinator projects every time I opened a folder, so I made a quick mux function that opens, edits, or deletes the Tmuxinator project named after the current folder.

It’s not the smartest function—it won’t work great if your folders are named the same—but hopefully it helps you out anyway.

function mux() {
  project=$(pwd | xargs basename)

  if [ "$*" = "" ]; then
    tmuxinator start "$project"
  elif [ "$*" = "edit" ]; then
    tmuxinator edit "$project"
  elif [ "$*" = "delete" ]; then
    tmuxinator delete "$project"
  else
    tmuxinator "$*"
  fi
}

Usage

  • mux — starts the project
  • mux edit — creates/edits the project
  • mux delete — deletes the project

All other commands fall through to Tmuxinator.

UIRefreshControl Can Interrupt Scrolling

While implementing infinite scrolling in Get Seated, I ran into a strange side-effect of calling endRefreshing() on a UIRefreshControl that isn’t animating: if the user is scrolling the view, any velocity from the user’s “flick” will be lost and the view will immediately stop scrolling.

It’s easy to hit this glitch if you call endRefreshing() every time new data finishes downloading from your server. Thankfully, UIRefreshControl has a property to detect if it’s currently refreshing. Just verify it returns true before you try to end refreshing.

Swift 3

if self.refreshControl?.isRefreshing == true {
    self.refreshControl?.endRefreshing()
}

Swift 2

if self.refreshControl?.refreshing == true {
    self.refreshControl?.endRefreshing()
}

Experiencing Productivity

Shawn Blanc:

Is the stay-at-home dad who spends most of his day changing diapers and cleaning up messes any less productive than his wife who is the CEO of a charity organization?

Sometimes I feel the most productive when I’m doing non-work errands. If you’re trying to break a cycle of non-productivity, don’t forget there are plenty of productive things you can do that may not be directly related to your occupation.

Doing simple household chores can be a great warm-up to addressing bigger challenges. If that’s what gives you motivation and confidence, don’t feel guilty.

How to Create a Hotkey That Selects the Current Word (OS X)

One of my favorite features in Sublime Text is the Cmd+D keyboard shortcut that selects the word nearest to your text cursor. Multiple presses select other instances of the word allowing you to edit them simultaneously, but I mostly use it to select the word I just typed.1 A similarly helpful feature has come to the iPad and iPhone 6S in iOS 9.2

Since most of my day is spent in Sublime Text, pressing Cmd+D quickly made its way into my muscle memory, and I started attempting to use it in other programs to no avail. A little bit of searching led me to the discovery that OS X has a similar feature built-in, but it’s not easily assigned to a hotkey.

You’ll need to create ~/Library/Keybindings/DefaultKeyBinding.dict and add the following lines to it, then restart any open applications you want to use the hotkey in:

{
  // ^ for Control, ~ for Option, $ for Shift, @ for Command
  "^w" = (selectWord:);
}

The example above uses Ctrl+W, but you can customize it to your liking. For instance, swapping ^w for @d would match Sublime Text’s key combination.3

Any application that uses native text fields will now allow you to quickly select the current word. Have fun editing!

  1. If you’re interested in editing every instance of the current word, there’s a better way: Cmd+Ctrl+G

  2. iPad: Tap the keyboard with two fingers.
    iPhone 6S: Press down hard on the keyboard. 

  3. Be careful. Unfortunately Cmd+D is set to other actions in a lot of applications, and this won’t override it. For instance, Safari uses it to create a bookmark. You might want to pick something else. 

Zsh PATH Issues in OS X 10.11, El Capitan

I immediately ran into problems with my PATH after upgrading to El Capitan. Instead of using my custom version of Ruby, El Capitan was using the system version. It was easy to spot why: OS X was reordering $PATH.

If you’re having the same problem, I suspect we have similar setups: I set $PATH in ~/.zshenv so Pow and GoSublime have access to it; I configure aliases and functions in ~/.zshrc since it’s more efficient to only load them for interactive sessions.

As it happens, El Capitan introduced a global Zsh profile at /etc/zprofile that calls /usr/libexec/path_helper, a utility which adds system directories to $PATH, reorders it, and then removes duplicates.1 Our customizations are being overwritten because Zsh calls /etc/zprofile after ~/.zshenv.

Solution 1: Disable Loading Global Profiles

If you don’t want to load /etc/zprofile or other global settings, there’s a command to disable them. All you do is add this line somewhere in your ~/.zshenv file:

setopt no_global_rcs

There are two things to be aware of with this fix although I find it highly unlike either will affect you:

  1. Future OS versions may modify these files with changes you need.
  2. If you’re on a multiuser system, your system administrator could be using the global files to configure settings and functions related to your job.

Since I find it unlikely OS X will change these files and since I am my own system administrator, this is the solution I’ve personally enacted.

Solution 2: Don’t Set $PATH in Zshenv

The biggest reason I set my PATH in zshenv is so Pow and GoSublime will have access to it, but most programs have multiple ways to set their search locations. For instance, Pow checks ~/.powconfig for exported variables. Likewise, GoSublime has a settings file where you can set $PATH and $GOPATH. Just move your PATH setup to ~/.zshrc or ~/.zprofile and configure your tools using their custom methods.

For instance, a Pow config for use with rbenv might look like this:

export PATH=/Users/$(whoami)/.rbenv/shims:$PATH

The downside is you will need to update multiple files if you ever change your Ruby, Go, or similar setup.

  1. OS X has always done this for Bash (in /etc/profile) and Csh (in /etc/csh.login). While an annoying change, it appears to have been made for consistency.