Display Manager

Display Manager

Display Manager's rotate feature from the command line

Introduction


Display Manager is a tool that allows users to programmatically manage Mac display settings like resolution, refresh rate, rotation, brightness, screen mirroring, and HDMI underscan. It’s primarily intended for Mac sys admins and developers who need to control displays in specific, predictable ways. Display Manager works in a 2-part package: a library that actually controls Mac displays (display_manager_lib.py), and a script that allows access via the command line, and executes commands in a non-interfering way (display_manager.py).

This blog post outlines my process in rebuilding and updating Display Manager’s codebase. Below, I’ll discuss how I found which of Apple’s frameworks to manipulate to manage Mac displays, how I used Hopper Disassembler to explore how others used those frameworks, and how I’ve utilized exploratory testing throughout the project.

Background


This is my first project as a Junior System Administrator in the University of Utah Mac Group, and I’ve learned a great deal about Apple’s display-related frameworks in the course of completing it. I started with Pierce Darragh’s “Display Manager” v0.10.0 (Display Manager v0.10.0), and was tasked with updating several of its deprecated features, along with adding several of my own. I began by reorganizing the features that “worked” into display_manager_lib.py, and educating myself about Apple’s Quartz Display Services (a branch of CoreGraphics) and IOKit frameworks, along with Objective-C (the language they’re written in). Once I’d explored and tested my way to a sufficient understanding of these frameworks (and how the old Display Manager harnessed them), I began fleshing out the Display Manager Library, extending the built-in Quartz and CoreFoundation Python modules with PyObjC (which can reference Objective-C functions and variables directly from the files they “live in”).

Example


My process for implementing the rotation feature into Display Manager was very much a microcosm of the whole project. I began with rather little to go on — while Quartz does have a way to check a display’s rotation, it cannot change it; in IOKit’s case, there is no widely circulated public documentation (that I could find, at least) that details rotation functionality. At wit’s end, I searched GitHub for many combinations of the keywords “rotate”, “iokit”, “quartz”, and “coregraphics”. Eventually, I stumbled across fb-rotate, which uses an IOKit function called IOServiceRequestProbe to rotate displays. I played around with this library for a bit, and after a little experimentation, I found ways to do simple things like flip a display’s screen upside down (though issues persisted like the mouse continuing to function right-side-up) with IOServiceRequestProbe. Thus began my journey to discover what this function actually did, how it did so, and how I could manipulate it.

To cut a long, arduous adventure short, between Apple’s IOKit documentation and various pages in their open-source documentation, I was able to piece together enough information to reliably perform certain rotation operations consistently (albeit only by introducing many other bugs interfering with display configurations I’d already nailed down, particularly screen mirroring). I still didn’t know what I was passing IOServiceRequestProbe to actually rotate the display, only that copying other examples in a certain way resulted in a marginal degree of success. It was at this point that I was first introduced to Hopper, a “reverse engineering tool that lets you disassemble, decompile and debug” existing binaries. I’d already come across the compiled versions of a few successful rotation applications, so I decided to take Hopper out for a spin.

After some enormously helpful tutorials (see: here and here) I learned to use Hopper’s “pseudocode” functionality to cut through a dense jungle of assembly to some references to IOGraphicsTypes.h and IOGraphicsTypesPrivate.h, which contained some very important variables: kIOScaleRotate0, kIOScaleRotate90, kIOScaleRotate180, kIOScaleRotate270, and kIOFBSetTransform. By tracing my way back through the binaries I’d loaded into Hopper, I found that the second parameter of IOServiceRequestProbe should be a bitwise-or between any of the kIOScaleRotates, bitwise-shifted up 16 bits, and kIOFBSetTransform. Finally, it all fell into place: performing this operation myself yielded exactly the desired result, and no longer interfered directly with mirroring!

A screenshot of Hopper disassembler's pseudocode feature

Hopper pseudocode feature in action

 

Takeaway


The above typifies my experience in (re)building Display Manager: between sparse, piecemeal documentation, niche GitHub repos, and some creative “disassembly”, anything is possible. My general process looked something like this:

      1. Determine how to isolate desired features by reading, bookmarking, and cross-referencing all relevant documentation within various Apple frameworks and existing usage examples, should I happen to find them.

      2. Perform heavy exploratory testing on anything and everything related to the feature at hand. Often, I’d note multiple ways to accomplish the same goal, and come back to them whenever features began interfering or a particular implementation fell short. Keeping the big picture in mind was essential — I found that a) being able to perform a desired action in a script, and b) being able to pull all the pieces together into a library in a way that both made sense and satisfied all use and corner cases required radically different mindsets.

        The general structure of the Exploratory Testing process

        The general structure of the Exploratory Testing process



      3. Build interfaces (or, in Python’s case, abstract base classes) and unit tests, nearly all the way up to integration tests, before trying to implement much of anything. As noted above in (2), knowing how the whole project fit together on the macro-level helped me solve problems on the micro-level that I’m not sure I could have any other way. In other words, it’s hard to explore the “ins and outs” of (what at least felt like) uncharted waters without a map; specifying the desired
        Spaghetti APIs

        Spaghetti APIs can be difficult to work with

        end structure of my project made building the smaller pieces much more achievable.

      4. Implement the most abstruse pieces first. It’s much easier to build around the more “involved” methods than it is to code them into existing “obvious” structures; as intuitive of software as you might be able to write from scratch, when you’re tacking together appalling chunks of recondite “spaghetti APIs” others have provided across multiple languages, you might have to make some structural sacrifices. When push comes to shove, it’s best to avoid reinventing the wheel whenever possible; building a simple, abstract interface on top of a hulking monstrosity allows you to carry on writing software as if the whole project were that straightforward.

      I hope Display Manager suits your needs. I’ve done my best to create an intuitive, transparent tool to resolve a complex, practical class of problems, and I’ll do my best to maintain it into the future. You can download it here.

No Comments

Leave a Reply