Reading view

There are new articles available, click to refresh the page.

How PlugInKit enables app extensions

App extensions or appexes perform a wide range of tasks, from providing support for file systems like ExFAT to generating thumbnails for QuickLook and enabling Spotlight to index the contents of files. Although they’re relatively old, macOS made major changes in their management in Ventura, and they’ve become popular in many third-party apps. Despite that, there’s remarkably little information about how appexes are managed. As a result, when they play up it’s not clear what you should do. This article tries to disperse that cloud of unknowing.

There are four relatively unknown services responsible for managing apps, their extensions and related services:

  • LaunchServices, which plays a prominent part in managing app launch, and maintains a large and comprehensive database of information about known apps and their extensions;
  • RunningBoard, which manages resources such as memory and GPU access for some apps and their extensions;
  • PlugInKit, which manages appexes for QuickLook, Spotlight, and many other services;
  • Duet Activity Scheduler (DAS), which dispatches and manages background activities.

At the heart of PlugInKit is its management daemon pkd, in /usr/libexec, which relies on working files and folders buried in a locked directory deep in /var/folders. It maintains its own registry of appexes, to which it adds annotations such as when they were last managed, and whether they have been ‘elected’. You can follow activities in the log using the subsystem com.apple.PlugInKit, and exchanges with com.apple.launchservices and PlugInKit’s client services such as com.apple.quicklook.ThumbnailsAgent, responsible for the generation of QuickLook thumbnails for files.

Discovery

Of the four services, only LaunchServices appears to maintain a database that persists across restarts, although it’s rumoured to be stored over more than one database. RunningBoard maintains a list of running processes, DAS maintains a list of activities that it’s scheduled to dispatch, and PlugInKit keeps a registry of appexes, but each of those appears to be built from scratch during startup.

Shortly after user login, PlugInKit is initialised and starts populating its registry by a process of discovery, apparently deriving its information from the LaunchServices database, where each installed appex is detailed. Among the information stored there for appexes is their PlugInKit dictionary (PKDict) with the NSExtensionPointIdentifier that sets what type of appex it is, and their SDK data, repeating that type information as the NSExtensionPointName.

PlugInKit looks up batches of appexes to register, grouped according to their NSExtensionPointName. The first of these is com.apple.textinputmethod-services, providing text input methods such as Vietnamese and Traditional Chinese. After those come com.apple.FinderSync and com.apple.fileprovider-nonui, and so on through a set sequence until ending with com.apple.widgetkit-extension.

Log entries for this discovery phase are remarkable as, provided appexes haven’t changed in the LaunchServices database, entries by PlugInKit are almost identical during every startup. This stability is particularly helpful over appex UUIDs: so long as the appex remains the same, its UUID and order in discovery will be the same as well.

Coverage and versions

Particularly in recent versions of macOS, LaunchServices appears to cast its net widely, adding apps to its database from almost any accessible source. This results in some of the features it supports listing multiple versions of apps, some of which aren’t installed in traditional Applications folders.

PlugInKit’s coverage appears more focussed, and its rules over which appex to use ensure that old versions are excluded. Although old versions are registered during discovery, PlugInKit normally only offers the most recent version and, if there are multiple copies of that, the last registered by its timestamp in the registry. It’s also more conservative about which appexes it recognises: while LaunchServices will happily add apps that aren’t stored in an Applications folder and have never been opened on that Mac, PlugInKit appears more cautious in those it registers.

This is best illustrated in a VM with the host Applications folder shared. Initially, before that folder has been opened in the VM, apps and appexes inside it aren’t offered in the Finder’s Open With menu, and appexes such as a QuickLook thumbnail previewer can’t be used. When that shared folder has been opened in the Finder, LaunchServices makes its apps available through Open With. But the thumbnail previewer is only registered with PlugInKit when its shared parent app has been run in the VM.

Continuous discovery

In addition to startup discovery, PlugInKit has automatic continuous discovery during normal running. This can be seen best when an app containing an appex is installed and launched. LaunchServices adds the new app and its appex to its database, in what its log entries refer to as seeding. This is reported to PlugInKit, which then performs re-discovery for all appexes with the same NSExtensionPointName, so adding the new appex to its registry. During this, PlugInKit informs the service using that type of appex, triggering deployment of the capabilities of the new appex.

For example, launching a new app containing an appex with the NSExtensionPointName of com.apple.quicklook.thumbnail results in LaunchServices adding that app and its appex to its database, then PlugInKit performs another discovery of all appexes with that NSExtensionPointName. When the new appex is added to its registry, PlugInKit informs com.apple.quicklook.ThumbnailsAgent of the new QuickLook thumbnail previewer, so it can be made available for creating thumbnails almost immediately.

Discovery is readily traced in the log, but even simpler to follow by opening successive windows in AppexIndexer, and following the UUIDs given there.

Removal of an app with an enclosed appex is also quickly reflected in PlugInKit’s registry, and the appex’s service is informed immediately to ensure that no attempt is made to run the deleted extension.

Practical consequences

  • Features of appexes are made available by the subsystem they act in.
  • PlugInKit informs an appex’s subsystem of its availability during discovery.
  • PlugInKit’s discovery relies on updates to the LaunchServices database.
  • Damage to or dysfunction of the LaunchServices database can therefore block or impair PlugInKit registration, in turn preventing correct function of the appex.
  • Resetting the LaunchServices database will inevitably delay PlugInKit’s discovery, and could lead to malfunction of appexes, such as failure to generate QuickLook thumbnails.
  • Controlling appexes is currently only available in System Settings > General > Login Items & Extensions.
  • Controlling appexes using the pluginkit command tool is only temporary, and any changes made there will be reverted in subsequent discovery.
  • Investigating appex problems by examining com.apple.PlugInKit subsystem entries in the log is clear and straightforward, and individual appexes can readily be traced using their UUID.

Last Week on My Mac: Discovering discovery

Apps are getting ever more flexible, and in doing so they’re also becoming increasingly complex. Gone are the days when they mostly opened files, did things to them, and saved them again. We now expect to be able to use our favourite image editor from inside the Photos app, and to share documents between multiple apps, using features as services in reusable components.

For those of us who used OpenDoc back in the 1990s this is all familiar territory. Intended as Apple’s response to Microsoft’s OLE (Object Linking and Embedding), OpenDoc broke apps down to single-task components that worked together. This was best exemplified in the suite of Internet tools provided collectively as Cyberdog for a brief period in 1996-97. Those included a web browser, FTP and email clients, and a newsreader, that could be embedded in other apps that supported OpenDoc’s Bento format.

When Steve Jobs killed OpenDoc in 1997, few could have envisaged what was to come later in app extensions, or appexes, nor how extensively they have become used by macOS. From speech synthesisers and Blu-ray encoders to wallpapers and widgets, appexes have proliferated far beyond the wildest dreams of the OpenDoc designers, but so little is known about they’re managed by macOS.

macOS Sequoia keeps extensive registries of apps and appexes. The most detailed is the grand database maintained by LaunchServices, only visible through its hidden lsregister command tool. Appexes are the preserve of the PlugInKit registry, which can be dumped using the pluginkit tool. Resource management is performed by RunningBoard and appears inaccessible, as do the activity schedules managed by Duet Activity Scheduler (DAS).

All four registries appear to be constructed afresh during startup, in the case of LaunchServices and PlugInKit by a process of discovery, something I’ll be looking at in more detail in the near future. For PlugInKit, it’s discovery that determines which appex services are offered, whether they’re generators of QuickLook thumbnails or previews, Safari extensions, or file systems such as ExFAT or MS-DOS. The latter are refugees from their former existence as kernel extensions, a route now being followed by macFuse 5.0.

Although the user has limited control over those in System Settings and, in the case of Safari extensions, in Safari’s settings, the PlugInKit registry is designed to operate automatically. If the user does try making changes using pluginkit those are likely to be undone when the registry is next updated, and in any case following reboot.

There are some differences obvious between LaunchServices’ database and PlugInKit’s registry. While LaunchServices comfortably accommodates as many versions of apps that it can find, and offers them as choices for opening documents in the Finder’s contextual menu, PlugInKit makes the user’s life simpler by only offering the latest version of each appex. Given that appexes now include replacements for QuickLook’s qlgenerators, and Spotlight importers, that’s hardly surprising, and the prospect of being offered multiple versions in the Share menu would be overwhelming for any user.

PlugInKit and appexes aren’t recent, and probably date back to OS X 10.9 Mavericks, with their NSExtension property list definitions appearing a year later in Yosemite. In macOS 13 Ventura, Apple augmented that with ExtensionKit and ExtensionFoundations both for creating extensions and the extension points offered by host apps. Appexes now cover many different domains, and have become increasingly popular in third-party products, with some like Eternal Storms’ Yoink relying on them for their tight integration with macOS.

Improving our understanding of appexes and their management by PlugInKit isn’t an academic exercise. Host apps and their extensions don’t always work in perfect harmony. Whether you’re developing either of them, or just trying to cope with their disagreements, insight can be important. Now that Sequoia requires QuickLook thumbnail and preview generation to occur in appexes rather than qlgenerators, the qlmanage command tool is of limited value, and you have to rely on PlugInKit instead.

Once upon a time, Apple used to provide extensive and well-written conceptual documentation, where it explained how Mac OS worked, so that when we came to tackle problems we could fall back on understanding. Now we’re largely left to fend for ourselves, so armed with a forthcoming new version of AppexIndexer, I’m off to discover PlugInKit discovery.

Last Week on My Mac: Bring back the magic

One of the magic tricks that characterised the Mac was its association between documents and their apps. No longer did a user have to type in both the name of the app and the document they wanted it to edit. All they needed to do was double-click the document, and it magically opened in the right app.

In Classic Mac OS, that was accomplished by hidden Desktop databases and type and creator codes. For example, a text document might have the type TEXT and a creator code of ttxt. When you double-clicked on that, the Finder looked up which app had the creator code ttxt, which turned out to be the SimpleText editor, and opened that document using that app.

Although those ancient type and creator codes still live on today in modern macOS, they no longer fulfil that role. Instead, each file has what used to be a Uniform Type Indicator (UTI), now wrapped into a UTType, such as public.plain-text, normally determined by the extension to its name, .txt or .text. When you double-click on a file, LaunchServices looks up that UTType in its registry, discovers which app is set as the default to open documents of that type, then launches that app with an AppleEvent to open the document you picked.

Recognising that we often want to open a document using a different app rather than the default, the Finder’s contextual menu offers a list of suitable apps in its Open With command. That list is built and maintained by LaunchServices, and has changed in recent versions of macOS. Whereas those lists used to consist of apps installed in the traditional Application folders, LaunchServices now scours every accessible volume and folder using Spotlight’s indexes to build the biggest lists possible. If you happen to have an old copy of an app tucked away in a dusty corner, LaunchServices will find it and proudly display it alongside those in everyday use, like a game dog triumphantly presenting not one dead pheasant but every one from miles around.

For those with lean systems, this gives them the flexibility to open a large text document using BBEdit rather than TextEdit, or to select which image editor to use for a JPEG. But for those of us with lots of apps lurking in storage, the result is absurd and almost unusable. It’s bad enough working through the 33 apps that LaunchServices lists as PNG editors, but being offered 70 text editors is beyond a joke.

Unfortunately, there’s no lasting way to block unwanted apps from being added to the list LaunchServices builds for this Open With feature. You can gain temporary relief by excluding them from Spotlight search, but should you ever open the folder they’re in using the Finder, those are all added back. This also afflicts apps in folders shared with a Virtual Machine, where the list includes App Store apps that can’t even be run from within that VM.

There are, of course, alternatives. I could drag and drop the document from its Finder window towards the top of my 27-inch display to the app’s icon in the Dock at the foot, which is marginally less awkward than negotiating my way through that list of 70 apps.

But there are better solutions: why not empower me to determine which of those 70 apps should be offered in the Open With list? This is such a radical idea that it used to be possible with the lsregister command that has become progressively impotent, as LaunchServices has cast its net further in quest of more apps to flood me with. Or maybe use a little machine learning to include only those text editors I use most frequently to open documents? Apple could even brand that LaunchServices Intelligence, although that’s a little overstated.

I can’t help but think of what those magicians from forty years ago would have done, but I’m certain they wouldn’t have offered me that list of 70 apps to choose from.

Controlling LaunchServices in macOS Sequoia

As its name suggests, LaunchServices is responsible for key features integrating the launch of apps and document types. Together with the more recent RunningBoard subsystem, it handles much of the work involved in launching apps, as I explored yesterday for Sequoia. This article looks in more detail at LaunchServices and what you can do to address problems, such as ensuring that only the apps you want are listed in the Finder’s Open With… contextual command.

RunningBoard is concerned with the control of resources such as memory, GPU access, CPU limits and the process lifecycle. Events are handled as assertions, and for apps that it manages those can result in reallocations and changes of state. Each running app has a lengthy and detailed job description created during launch but that doesn’t persist once an app has shut down.

In contrast, LaunchServices compiles a large registry database of apps and their associations with and capabilities for handling different document types. Its records determine which app opens a document when you double-click on its icon in the Finder, and most prominently which are listed when you open the Open With… item in the Finder’s contextual menu. Apps are registered there automatically, and their details are updated each time they’re run. Although the user can’t interact directly with LaunchServices, there is a command tool that offers control over it, lsregister, although it’s buried deep in the system frameworks, doesn’t have a man page, and now works differently.

lsregister

This command tool can be found at /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister, and typing that in with the -h option will show its usage information, as the closest you’ll come to documentation. If you’re going to use it much, you’ll want to create an alias for it, or add it to your PATH with
PATH=/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support:"$PATH"
to use the command as lsregister, as I’ll do here.

Useful lsregister commands follow one of two forms:
lsregister [options] [path]
to register or unregister an item (usually an app) specified by the path, and
lsregister [options] [-apps domainlist] [-libs domainlist] [-all domainlist]
to act on the LaunchServices database for the given types (apps, libs, all) and domains. Domains are usually specified as a list of letters:
u,s,l,n
is the complete set, covering user (your Home folder), system, local and network.

Registration

In the past, apps used to populate the LaunchServices registry were those located in the traditional Applications folders, but recent versions of macOS have extended that to cover almost any accessible folder. This has been explored by Jeff Johnson, who has shown that excluding folders and volumes from Spotlight indexing, by adding them to the list in Search Privacy… in Spotlight settings, will exclude those apps from LaunchServices’ list. Alternatively, you can hide the folder they’re in by adding a dot to the start of its name. However, that can still fail at times, for example if you open that excluded location in the Finder, resulting in those hidden apps being added back into that list.

You can try to remove an app from the LaunchServices registry using the command
lsregister -R -f -u pathname
where pathname is the path and name of the app. In Sequoia, that invariably returns an error that lsregister “failed to scan [path]: -10814 from spotlight,” where the path given is that to the app. That error code comes from LaunchServices, and its name reveals the cause: kLSApplicationNotFoundErr, even when the pathname given to lsregister is correct. Despite that error, if that app is hidden from Spotlight search, this should prove effective until something undoes it again.

This over-enthusiasm to register apps can be even more than a nuisance when running a lightweight macOS Virtual Machine on Apple silicon. If you make the host’s Applications folder a shared folder with the VM, then open that shared folder within the VM, all the apps within it are promptly added to the Open With… list in the guest, a behaviour likely to be unwanted.

Given the current state of LaunchServices discovery, you’re unlikely to want to add an app to the database, as it’s most probably already there, whether wanted or not.

Resetting the registry

In the past, one last-ditch method of addressing LaunchServices problems has been to reset its registry. You can perform that using either
lsregister -kill -r -v -apps u
to affect just the user domain, or
lsregister -kill -r -v -apps u,s,l
to widen its coverage to other domains.

Running either of those in recent versions of macOS including Sequoia is likely to wreak havoc, though. While this appears to be effective with the Open With… list, its effects on System Settings can be catastrophic. This can remove its entire contents, and even blow the wallpaper away. Normal function should start to return after restarting the Mac, but even then problems can persist.

Dumping the registry

Even on a minimal Mac, LaunchServices’ registry is very large. If you want to inspect its contents, use a command like
lsregister -dump > ~/Documents/lsregisterDump.text
to write its contents to the file lsregisterDump.text. Browsing that should keep you occupied for many hours or days.

Summary

  • LaunchServices is responsible for making associations between apps and documents, and maintains a large registry of all apps and document types.
  • Its registry is used to populate the list for the Open With… item in the Finder’s contextual menu.
  • LaunchServices now tries to include all apps in accessible volumes and folders.
  • You can control (to a limited degree) its registry using the hidden lsregister command tool.
  • You can exclude apps only if their location is excluded from Spotlight search by adding it to Search Privacy… in Spotlight settings.
  • LaunchServices in a VM will also try to include all apps in shared folders on the host.
  • Resetting the registry using lsregistry -kill can wreak havoc with System Settings and should be avoided.
  • Dump the registry to text using lsregister -dump if you enjoy a long read.

How macOS Sequoia launches an app

Each new version of macOS has increased the complexity of launching apps, from the basics of launchd, the addition of LaunchServices, to security checks on notarization and XProtect. This article steps through the major landmarks seen when launching a notarized app that has already passed its first-run checks and is known to macOS Sequoia 15.3, on an Apple silicon Mac.

Rather than trying to provide a blow-by-blow account of what’s written in the log over the course of thousands of entries, I’ve extracted landmarks that demonstrate when each subsystem gets involved and its salient actions. These have been gleaned from several similar app launches, and are ultimately timed and taken from one complete record of one of my simpler notarized apps that has no entitlements and uses only basic AppKit features. The app hadn’t been through quarantine as it had been built and notarized on the same Mac, and had been run previously but not in that session since the previous boot. It had thus been previously registered with LaunchServices and other subsystems. The host was a Mac mini M4 Pro, so timings should be briefer than on many other Macs, it was run from the main Applications folder on the internal SSD, and AI was enabled.

LaunchServices and RunningBoard

LaunchServices has been around for many years, and handles many of the tasks exposed in the Finder, including mapping of document types to app capabilities, Recent Items and Open Recent lists, making it the backbone of app launching. RunningBoard was introduced in Catalina and has steadily assumed responsibility for managing resources used by apps, including memory and access to the GPU. Although the test app doesn’t have any of its resources managed by RunningBoard, LaunchServices launched it through RunningBoard.

RunningBoard’s first task is to create a job description, which it helpfully writes to the log as a dictionary. This is a mine of useful information, and has replaced the copious information compiled by LaunchServices in the past. This includes:

  • a dictionary of Mach services
  • whether Pressured Exit is enabled
  • a full listing of environment variables, such as TMPDIR, SHELL, PATH
  • RunningBoard properties including another TMPDIR
  • whether to materialise dataless files.

Once that job description has been constructed for the app, RunningBoard tracks the app and its assertions, providing a detailed running commentary through the rest of the app’s life. LaunchServices still performs its traditional tasks, including creating an LSApplication object and sending an oapp AppleEvent to mark the opening of the app, and launchd still reports that it’s uncorking exec source upfront.

When the app is running, its preferences are loaded from the user CFPrefsD, and its pasteboard is created. Almost 0.1 second later (0.3 seconds after the start of launch) there’s a sustained flurry of log entries concerning Biome, and signs of AI involvement (Apple silicon only). The latter include a check for the availability of generative models and WritingTools. There are also entries referring to the loading of synapse observers.

LaunchServices log entries are readily accessed through its subsystem com.apple.launchservices, and RunningBoard through com.apple.runningboard.

Security and privacy

The first serious engagement in security is the verification of the app’s signature and its evaluation by Apple Mobile File Integrity (AMFI, using amfid). Shortly after that comes the standard Gatekeeper (GK) assessment, with its XProtect scan, starting less than 0.1 second after the start of launch. Immediately after the start of that scan, XProtect should report which set of data files it’s using. In Sequoia those should be at /var/protected/xprotect/XProtect.bundle/Contents/Resources/XProtect.yara. That scan took just over 0.1 second.

While XProtect is busy, syspolicyd checks the app’s notarization ticket online, through a CloudKit connection with the CKTicketStore. That’s obvious from log entries recording the network connections involved, and the complete check takes around 0.05 second. Once that and the XProtect scan are complete, syspolicyd reports the GK scan is complete, and evaluates its result.

At about the same time that the Gatekeeper checks are completing, privacy management by TCC (Transparency Consent and Control, in tccd) is starting up. Its initialisation includes establishing the Attribution Chain for any Mach-O binaries run by the app, so that TCC knows where to look for any required entitlements. Following that, TCC writes bursts of entries as different components such as the Open and Save Panel service are set up for the app.

The final phases of security initialisation come in provenance tracking, which first appeared in macOS Ventura. This may be associated with presence of the extended attribute com.apple.provenance, but details are currently sketchy.

Following syspolicy in the log is best through its subsystem com.apple.syspolicy, you can watch XProtect using com.apple.xprotect, and TCC is com.apple.TCC.

Overall

Downloadable PDF: applaunch153

Main landmarks with elapsed time in seconds:

  • 0.000 Finder sendAction
  • 0.023 LaunchServices, launch through RunningBoard
  • 0.029 RunningBoard launch request
  • 0.043 AMFI evaluate
  • 0.066 Gatekeeper assessment
  • 0.080 XProtect scan
  • 0.085 check notarization ticket
  • 0.187 TCC checks
  • 0.204 launched

Previous article

Launching apps in Sonoma 14.6.1: Conclusions

Browse your Mac’s log with LogUI

If you ever need to understand what’s going on in your Mac, reading its log is essential. However much you might stare at Activity Monitor, read source code or disassemble components of macOS, what you can see in the log tells you what has really happened. Whether it’s a bug or an unexpected event, it’s the only way to look back and discover what went on.

For most, Console isn’t the right tool. It only offers the options of viewing its live stream, or making an archive of the whole log and wading through that when you need to look at an event in the past. Although my log browser Ulbow gives much better access, for many it’s still a daunting task. I’ve now switched almost entirely to using my new lightweight log browser, LogUI, and here explain how you can use it. Although it’s currently an early release with limited features, you should find it ideal for getting started.

LogUI 1.0 build 27 is available from here: logui127

This comes as a Zip archive, so unZip it, and move the LogUI app to another folder before running it for the first time; your main Applications folder is fine, and almost anywhere will do nicely. Alongside it is a copy of the PDF Help file that’s also inside the app, so you can refer to it when you’re not running LogUI. As the two are identical, you can trash the separate copy if you’re happy to use that inside the app.

Before opening LogUI, generate an event that you can examine in the log. One starter might be launching an app. Try to do this when your Mac is otherwise quiet and unoccupied: if Activity Monitor shows it’s busy with lots of background tasks, then the log could be filling with noise, when what you want most is peace. For the sake of simplicity, time this on an even minute, and make a mental note of that time to the second.

As soon as you’ve done that, open LogUI and you’ll be greeted by its window, with its toolbar ready for you to set up and get your first log extract. That should be set as follows:

  • Start to the current date, hour and minutes. As those are set to the time you open the window, they should already be close to the time of the event. Just after the stepper control is the seconds setting. If the event occurred a moment after the seconds changed to 00, that makes a convenient time to start your log extract.
  • Period set to around 5 seconds. This value can be floating point decimal, so might be 2.5 seconds for a smaller log extract.
  • Max entries set to 1000 to start with.
  • Show Signposts not ticked.
  • Full Fields ticked.
  • Subsystem empty.

Then click on Get Log to see the log extract for that period.

In my case, a five second period overfilled the 1000 I had set in Max entries. Scrolling to the foot of the extract showed that had been reached in less than two seconds, so I increased Max entries to 10000 and clicked Get Log again.

There are now over 5,000 entries in the extract, but they scroll smoothly enough to let me look around them.

The best way of reducing the number of entries to make them more understandable is to add a Subsystem as a filter. In this case, as I’ve launched an app, I can get most useful information from LaunchServices, by entering
com.apple.launchservices
into Subsystem. I also reduce the number of fields shown by unticking the Full Fields box, letting me concentrate on the messages. You can toggle Full Fields without having to get log entries again, as it only affects the fields within those entries that are shown in the window. Then click on Get Log again.

Scrolling down through the 358 entries remaining, I quickly reach one with a message field reading
LAUNCH: translocate to <private> from <private>
Although we could do without the privacy censorship here, it’s easy to work out that the app I just launched underwent app translocation, and that’s confirmed a little further down when LaunchServices gives its full path.

If you want a copy of the text from one or more selected entries, use the Copy command (Command-C), then paste it into any text editor. That doesn’t copy all the fields, only those you’re most likely to need elsewhere. For a full copy of that log extract, with colours indicating the fields, click on the Save as RTF button. As with LogUI’s window title, saved files are given default file names containing the start time of that log extract to help you keep them in good order.

This is the same log extract seen in my rich text editor DelightEd.

Finally, open LogUI’s Settings to enter the defaults you want its windows to open with. These match controls in its toolbar. The last of those, Light Mode, enables you to run the whole app in Light Mode if you prefer. For that to work, your Mac will also need to be in Light Mode, and you should tick that box, close the Settings window and quit the app. When you next open it, it should operate in Light Mode. Other changes made to Settings don’t need you to quit the app for them to take effect.

Enjoy!

❌