• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

How to get the Window Manager's WindowList in a multitasking environment?

Crutch

Well-known member
Here's a pretty weedy question but figured somebody might know: @cheesestraws @Dog Cow

@PB170 pointed out that there's no "Exposé" like feature available in a vintage Mac extension that I know of, so I was planning to try writing one.

Then I realized I don't know how to get the Window Manger's full WindowList representing all open applications' windows in a multitasking environment. (Under System 7 or MultiFinder, the global variable WindowList gets swapped out on a major switch and so only points to the start of the linked list of your current application's windows.) This doesn't seem to be documented in any obvious place. It's possibly described in the Programmers' Guide to MultiFinder which I've never been able to find anywhere.

Does anyone know how, under System 7 or MultiFinder, one can get the list of all open windows in every application? The Window Manager must of course be storing this somewhere.
 

BacioiuC

Well-known member
I remember reading an article on MacTech about something similar and talking about how GetWindowList returns a pointer to the first window and a call such as GetNextWindow and passing the current window as an argument to it. Ended up being something like this (from memory):

Code:
currentWindow = GetNextWindow(currentWindow);
.

Not sure if it was OS8 exclusive (since it was an article on some OS8 api's) but maybe that's worth a shot to check for?
 

cheesestraws

Well-known member
I'm actually getting close to an answer for this but it's System 7 only and slightly terrifying. But I have hopes it will work.
 

Crutch

Well-known member
I look forward to hearing what you are thinking!

I tried to brute-force an answer by opening an application (SimpleText) with just one window, getting its WindowPtr from Macsbug (by just typing 'theport') then switching to another application. Then I used Macsbug to do a RAM search ('RamFP') for the pointer.

I found just one reference to the SimpleText window, in the Process Manager heap. Which, well, of course it's there! But how to access it programmatically I have no idea, yet. (I know that under System 7 I could use GetProcessInformation to get each application's zone, but I don't think that app's copy of the system globals like WindowList actually live in the application zones.)
 

cheesestraws

Well-known member
OK. I started from the Basilisk II rootless patch by zydeco, and followed the thread of logic back through the leaked ROM code. Some of the source code I've been reading and will link to here is technically naughty to read, and if any reader thereof ends up in a complicated intellectual property lawsuit because of this post (but how?) that's not my fault. Officially. Here endeth the disclaimer.

A further disclaimer: this involves walking through highly undocumented gubbins. It is mentioned in the Apple release notes for 8.5 and 8.6 that applications that use the unsupported layer manager API to create floating windows will break. We are not using the layer manager API to create floating windows, but we are about to spelunk into some of its data structures. So whether this will work on 8.5 or above is anyone's guess. There is also at least one personal post from someone at Apple complaining about people using the layer manager without permission because it makes their life harder. So it goes.

Whether it will work at all is anyone's guess. Consider this working notes.

I don't know if you've ever come across the Layer Manager. The Layer Manager is the thing that manages layers of windows. A layer of windows is pretty much, visually, exactly what it sounds like; an application by default has a layer, and that layer contains its windows. When the application is brought to the front, its layer is brought to the front. Applications can have multiple layers.

This is why an application doesn't see any windows but its own in the window list. This isn't so much to keep the knowledge from the application, but from bits of the Window Manager itself. Programmatically, then, a Layer mostly consists of a virtualised set of Window Manager globals that can be swapped in and out to keep the ROM window manager believing it's running in a single-tasking way.

Multifinder is a hell of a hack.

When a layer is activated, the WindowList global is set to the subWindows field of the layer record. So, to get the list of all windows open, one simply ("simply") needs to get the list of all layers and iterate through their subwindows lists. Great.

There doesn't, unfortunately, seem to be any master list of layers. Or, at least, if there is any, I can't find it. Each process (wait for it!) has its own root layer, which is created when InitWindows is called. So we can get the list of all layers by querying the list of all processes then walking the tree of layers.

But there's a gotcha here: the root layer for an application isn't stored in the public ProcessInfoRec type, but in the internal PEntry type. So we need to find the PEntry for a given process. Fortunately ("fortunately") there's a useful undocumented call here called _PEntryFromProcessSerialNumber (called via _OSDispatch) which returns the internal PEntry for a given process number.

So, to summarise, as far as I can see, one option is:
  • Get list of all processes
  • For each process, get the PEntry with _PEntryFromProcessSerialNumber
  • Walk the layer tree for the process: sublayers of the root layer turn up in the subwindows list of the root layer. Or you can just ignore any child layers because they're probably lies and floating palettes. Add the windows in the subwindows list that aren't sublayers to your window list.
  • Cry
Multifinder is a hell of a hack.
 

Crutch

Well-known member
Excellent! Thank you!

That actually doesn’t sound all that terrible. I assumed walking the list of processes would probably be necessary. I definitely would never have figured the rest out, though, so this is awesome.

I actually tried stepping through _FindWindow which obviously has a way to work through every application’s window list, and was seeing it call _OSDispatch without being able to figure out what it was doing. I didn’t know about _PEntryFromProcessSerialNumber, clearly.

Thanks again! I will try this. I may also try what was going to be my Plan B, which is just patch NewWindow, GetNewWindow, CloseWindow, DisposWindow and keep my own always-current list of all the currently-open documentProc-type windows.
 

cheesestraws

Well-known member
Yeah. I get the impression is that the patching would have been what Apple would have preferred you to do, and feels like it might work better across System versions, but also easier to get out of sync with reality if you miss something. I did start writing a sketch last night but I was too tired to work out how to call _PEntryFromProcessSerialNumber and took that as a sign that it was bedtime.

And yeah, it isn't really so bad, it just feels a bit grotty to be delving into the private bits of the process manager. I sometimes struggle to let go of my "work" distaste for doing questionable hacks, even though as you can probably tell I enjoy it a lot...
 

cheesestraws

Well-known member
OK, yeah, so this turned out far less gross than I was expecting, actually: managed to knock up a proof of concept. Code attached at bottom of post (CodeWarrior, sorry: you'll need to adapt for THINK C).
Screen Shot 2021-10-31 at 10.52.17.png
 

Attachments

  • Layers.sit
    109.5 KB · Views: 3

Crutch

Well-known member
Gorgeous!

I hacked this together based on your links last night before looking at your code (see screenshot).

Now that I HAVE seen your code ... we did exactly the same thing, right down to ignoring all the fields in the PEntry after p_layer. We also may have taken the same shortcut in assuming there are no sublayers in the 'subwindows' list. I assume that what we're supposed to do is use the IsLayer call on each subwindow to confirm that it isn't actually a sublayer. I'll try that later.

In THINK C I just brute forced PEntryFromProcessSerialNumber using inline asm, I like the transparency better than throwing the hex instructions into the code. (Inline asm in THINK C is, by the way, super sleek and integrates seamlessly with local C variables.)

At bottom are a couple lines of code that switch to the first app it finds with an open window whose name starts with 'P' w/ SetFrontProcess and then does SelectWindow on that window. I had assumed doing SelectWindow on a window owned by another process would bomb, but no, it totally works (you don't even have to wait for the context switch after the subsequent WaitNextEvent, I guess why not, the Window Manager just sees that it's getting handed a perfectly valid WindowPtr even though it's in somebody else's heap, fake multitasking is weird in retrospect). So I think if I throw a nice GUI on top of this (which would take time but is conceptually trivial) one could have a System 7 Exposé type thing in a hurry. Of course I'd ideally also save bitmaps for each window at opportune times so I can show little thumbnails and add semitransparent window previews and so much other fluff .......

1635694458399.png
 

cheesestraws

Well-known member
That's a good sign that! I just used the three-word inline by copying another _OSDispatch call out of the universal headers. ASM would have been nicer but I can never remember properly how to use it in CW. Think C's inline ASM is certainly nicer, but I find it as a whole harder to use. Unsure why: just a personal preference.

Looking forward to seeing what you come up with here :-D.
 

Crutch

Well-known member
Next step is going to be to try to keep thumbnail bitmaps around of relatively-recent state of each window from each application. I am messing around with patching _HiliteWindow and/or _BringToFront to see if those are good triggers for stashing away a thumbnail snapshot before the window is obscured by something else.
 

Crutch

Well-known member
Interesting and thanks for the heads up. Ordinarily my interest in the vintage era ends circa 7.5.5 so I will note that as a point of interest but not do anything with the information for now :)
 

PB170

Well-known member
Wow, thanks for getting started with this! :)

I've taken a break from my own programming efforts for a couple of months after getting my control strip modules to work, which was one of my primary goals, but this thread is very interesting to read. Impressive how much you were able to find out and how quickly you got a proof of concept working!

I'll get back with a visual mock-up and a basic algorithm for the implementation I've had in mind as soon as I find the time, so don't get too far ahead :)

By the way, my own primary interest is having this on my PowerBook 170, but a polished extension that works on all versions of the classic MacOS would be really neat. Perhaps you could keep the code flexible :)

Thanks again! It would be absolutely amazing if this could become a reality.
 

Crutch

Well-known member
Thanks for suggesting an interesting project! I happened to have a couple days off this week so made some headway.

I now have an extension running that, when a certain key combination is pressed (I lazily defaulted to Cmd-E but it will be configurable, what’s your preference?), pops up a full-screen previews of up to 8 windows from all running apps. By clicking one, you switch to that window in the appropriate app. It’s pretty cool so far and I think already useful, but needs a ton of work on the visuals (I would like, Expose-style, for the previews to be sized proportionally to then original windows, non-overlapping, allow for a larger number etc.), needs a cdev written to allow configuration, oh and for some reason it runs fine as long as you‘re using the machine but crashes 100% of the time on shutdown. :)

The fun part is that by patching various Window Manager routines (HiliteWindow, BringToFront, and SelectWindow all seem to be necessary so far) I take a “snapshot” of relevant-seeming (documentProc, with a non-empty title, visible, on-screen, etc.) windows any time— and immediately prior to — they get “pushed behind” another window. That way the bitmap preview looks up-to-date even if the window is currently obscured by something else. I think it’s pretty nifty.

I am building it under 7.5.3 in Mini vMac emulating a Mac II. It should work on any machine of that approximate era. It might be slow, though. Anyway if all goes well I’ll be able to share something in a few weeks.
 

cheesestraws

Well-known member
The fun part is that by patching various Window Manager routines (HiliteWindow, BringToFront, and SelectWindow all seem to be necessary so far) I take a “snapshot” of relevant-seeming (documentProc, with a non-empty title, visible, on-screen, etc.) windows any time— and immediately prior to — they get “pushed behind” another window. That way the bitmap preview looks up-to-date even if the window is currently obscured by something else. I think it’s pretty nifty.

That's really nice.
 

PB170

Well-known member
Oh, you're thinking about a different part of Exposé than I am! I was a bit puzzled when you began mentioning previews, but now I understand why :) I've only been thinking about the part that reveals the desktop, since that's the one I use by far the most, and also since I felt the "reveal all windows" one would be difficult to implement in a visually appealing and responsive way on a black and white / pixel based system (as well as it being even more challenging to code). But why not! :) Sounds like you made great progress already!

In case you're not familiar with it, this is the feature I've been referring to (starting at 3:17):
Mac OS X Panther - Expose - YouTube

Hopefully, now that you put the bar high, my idea should be a bit less difficult to implement :giggle:
 

Crutch

Well-known member
Oh yeah sorry, actually I did follow you I think - I was thinking about the desktop-exposing feature at first too but at some point somehow decided to do this part first.

What would you want the trigger to be the un-reveal the desktop? Another keystroke, click somewhere, start dragging something … ?
 
Top