Archive for November 2, 2021

Tuesday, November 2, 2021 [Tweets] [Favorites]

Please Shrink the Trackpad

Lukas Mathis:

After years of struggling with ghost clicks, randomly dragged icons, poor palm rejection, and generally ever worsening MacBook trackpads, I didn’t want to deal with software features trying to compensate for a trackpad’s lack of physical features.


The reason this [Lenovo] trackpad just works is that its form follows its function. It’s built to move the cursor without getting into your way when you do anything other than moving the cursor, and it’s built to click when you actually want to click, not when you accidentally touch the trackpad wrong.


Or perhaps the «large trackpad» trend is similar to the «glossy screen» effect. Just like those nice, shiny screens, big trackpads look enticing. The fact that they mostly get in the way is not apparent at the time of purchase.

Apple used to have the best trackpads, but now I constantly get accidental and misclassified clicks on my 16-inch MacBook Pro. Its keyboard is the same width, but its trackpad is much wider, so there’s less safe area to rest my palms. For my next notebook, I’m seriously considering getting a smaller display than I’d like. That’s the only way to get a smaller trackpad, and even the one on the MacBook Air is wider than I’d prefer. I never had problems running out of space using the older trackpads going back to the Wallstreet PowerBook, whose trackpad was considerably narrower than the Spacebar. It even had a real button to click.


Update (2021-11-12): Jonathan Deutsch:

I had plenty of issues with the original giant 2016 15" MBP trackpad, and after returning it chose 13" sizes from then on to avoid most palm issues. I heard the software got better at some point. When I got the 2019 16" MBP I didn’t experience too many problems.

So, apparently, the 2019 version that’s causing trouble for me is the improved version.

See also: Hacker News.

Lightroom Classic 11


Reimagine local adjustments with the brand new Masking panel. Now use the Brush, Linear Gradient, and Radial Gradient tools with greater precision and organization. You can also access the Color Range and Luminance Range tools that help you select and adjust specific colors or brightness levels in a photo. Depth Range control is also available for photos containing depth information. You can choose to combine multiple masks in a single photo to make complex local adjustments and easily access them in an organized Masking panel.


Backed by Artificial Intelligence, the Masking panel offers two new options - Select Subject and Select Sky!


When multiple images are selected, you can now choose to view metadata for the active image or across all selected images.


Click the Customize button to select the metadata fields that you want to display in the Metadata Panel.


The way that the Lightroom Classic database is stored on disk has been optimized and a new file will be created. This new file, with an extension of .lrcat-data, will now be found in the same folder as your catalog file. This file contains important information about your photos and edits and should be included in any backup regimes you may have.


The progress bar of saving an XMP will be displayed in the Activity Center.


Keyboard Maestro 10

Stairways Software (tweet, press release):

Keyboard Maestro 10 expands on the powerful base of previous versions, improving the editor, adding many new actions and triggers, Paste by Name, status menu display, subroutines, and more.


Added Paste by Name action giving Spotlight-like search of clipboard history.


Dangerous NSLog() Calls in Swift

Daniel Jalkut (tweet):

NSLog("Failed to get modification date at URL: \(url) - \(error)")


In the scenario of my crash, the interpolation of “url” and “error” results in surprise template placeholders, because of the presence of spaces in the path that the URL represents. In my tests, a file called “Open Program Ideas File” exists and is used to test some file manipulations. When the path to this file is encoded as a URL, the name of the file becomes “Open%20Program%20Ideas%20File”. Since the error’s failure message reiterates the name of the file it couldn’t find, we end up with another copy of the percent-escaped string in the parameter to NSLog. Each instance of “%20” followed by a letter is liable to be interpreted by NSLog as a printf-style format specifier. So as far as our filename’s string is concerned, we have “%20P”, “%20I”, and “%20F” threatening to cause trouble.


If this were Objective-C, we wouldn’t run into the problem because the parameters would need to be passed as variadic arguments[…] But if we try that in Swift, we run into trouble.

Sebastian Celis:

That Swift compiler error is very unhelpful, but you should still be able to use variadic arguments — you just need to do it a bit differently. I tend to pass them all in as strings. For example:

NSLog("Oops! %@ - %@", "\(url)", "\(error)")

Norbert Heger:

As a safety measure you could add this to your project to let the compiler catch the dangerous cases[…]

BJ Homer:

I prefer this format, because I can still keep all the string interpolation “in-line”:

NSLog("%@", "Oops! \(url) - \(error)")

It’s cool—and sometimes very useful—to be able to directly interpolate Swift strings using the \() syntax. However, for logging I prefer the Python-style approach that Swift’s print() function uses:

print("Oops!", url, "-", error)

I find it more readable, and you can easily add line breaks after commas to wrap long lines or to line up pairs of arguments that go together.

Of course, print() doesn’t output to the same places as NSLog(), but you can write a wrapper function. I think that’s a good idea, anyway, as it lets you switch whether logging is enabled, log to a file if necessary, include the source location, etc. At various times, NSLog()’s output hasn’t show up at all for me or it has been duplicated. Having this more under my control has been helpful in working around such hiccups.

Here’s some code that I’ve been using to make logging more ergonomic:

func log(_ i0: @autoclosure() -> Any?,
         _ i1: @autoclosure() -> Any?,
         _ i2: @autoclosure() -> Any?,
         file: String = #file,
         line: UInt = #line,
         function: String = #function) {
    log([i0(), i1(), i2()], file: file, line: line, function: function)

The parameters are type Any? to avoid the compiler error about implicitly converting an Optional to Any. Unfortunately, we need a separate version for each number of parameters because @autoclosure doesn’t work with variadic parameters. These all funnel into:

func log(_ items: @autoclosure() -> [Any?],
         file: String = #file,
         line: UInt = #line,
         function: String = #function) {
    log(String(mjtPrintingOptionals: items()), file: file, line: line, function: function)

And to convert the array to a string:

public extension String {
    init(mjtPrinting items: [Any], separator: String = " ") {
        // @SwiftIssue: No print() that takes an Array, and no way to splat, so
        // we have to print individually.
        var result = ""
        var prefix = ""
        for item in items {
            print(item, terminator: "", to: &result)
            prefix = separator
        self = result

    init(mjtPrintingOptionals items: [Any?], separator: String = " ") {
        self = String(mjtPrinting: { $0 ?? "nil" }, separator: separator)

This last function unwraps the optionals before printing so that we don’t have Optional(...) cluttering the log.