Switch to Previous Desktop Space

I did. And I got an answer.

His technique seems doable in KBM (but not by me, not any time soon anyway).

2 Likes

Does anyone have any ideas for how to create something like Spencer's "little invisible app windows"? That seems a steep learning curve for me.

<brainstorm on>
I've been thinking that I could create TextEdit windows where the doc title is the name given to the desktop workspace, all with a common prefix, so they show up together in lists of active TE windows. Either they are as tiny as they can be made and put off in a corner, or even outside the visible area, or they have the name and an icon or symbol, a background color, etc. so they become an identifier flag to let you know which desktop you are working in and maybe to identify the thumbnails that get displayed by Mission Control.

TextEdit is one of the few apps where open windows are restored after a reboot in their original desktops. Most apps reopen a single window and those that do reopen multiple windows often move them all to the first desktop. TE would thus, at least at first look, fill the requirement of changing desktops for you when you bring the appropriate window to the foreground.
<brainstorm off>

Comments?

Sorry, I can't help—that's beyond my pay grade!

But in another forum post I saw the following and noticed the mention of spaces. Maybe it's worth checking out.

Contexts - Radically simpler & faster window switcher for Mac

Thanks Jim,

That looks really interesting! Unfortunately, it’s for Catalina and Big Sur and I am on Mojave.

August

Hi @_jims, I've been making some progress.

I have been unable to find that WhichSpace.app has any kind of API, programmatic access by AppleScript, JavaScript, KBM, or whatever, to read the desktop number or get notification when the desktop changes. Do you know of any?

Meanwhile, it turns out that CurrentKey will call a specific AppleScript script every time the Desktop changes, no matter what method you use to change.

This is documented for CK at https://spencerdailey.com/2021/03/19/setup-banner-notifications-that-will-tell-you-the-name-of-the-space-you-just-moved-into/

So here is my modification of Spencer's script to report not only the current desktop, but also the previous.

As you can see from the debugging comments in the script, I tripped over the two different path syntaxes for AppleScript and the shell. The path hardcoded into the script is also the directory where ck.scpt lives, the default location in CK for the script.

Here's what I get as a Notification at the upper right of my screen, when I change from one desktop to another:
Screen Shot 2022-02-07 at 11.11.33 AM

As a Proof of Concept, at this point I have the name of the previous desktop name (Room name in CK terms) available when this AppleScript is triggered.

My next step is to modify the script to save the previous name so that I can read it when I need it, i.e. when triggered by my Previous Desktop hotkey.

One think I really like about CK is the naming of the Rooms/Desktops with meaning, and the other, magic for me, is that when I manually rearrange the order of the desktops, the names follow along, which gives me the freedom to rearrange the spaces in whatever order works for on my current task and the names and contents stay the same.

Yes, I use it throughout the macros that can be downloaded at Desktop Spaces • Macros for Navigation and Window Management.

tell application "System Events" ¬
to tell process "WhichSpace" ¬
to set temp to (title of menu bar items of menu bar 1)
return item 1 of temp

In my macros, that's all I use and need from WhichSpace, however...

If you really want to dig into this, you might find the following interesting:
[feature request] command line app · Issue #25 · gechr/WhichSpace

In that thread, you’ll see reference to davidpurnell/SpaceInfo: A command line app which returns MacOS spaces & display info

1 Like

Hi August

In a totally unrelated question, I am wondering how your friend is doing?

Did he finish the books? Is there a link to purchase?

I am also curious about his context switching as it relates to productivity for both you and him

Can you share how the experience has been?

To my knowledge, Rob is still working on the books.

As for me, it is incredibly useful to be able to have separate desktops on my Mac for different contexts. Right now I'm on my KBM desktop, for general use, forumm browsing, brainstorming, etc. When I'm deep into a macro that is taking some AppleScript or JavaScript testing and research, etc., I may dedicate a couple more desktops to those functions as well. I have a desktop devoted to Facebook, sometimes with six or more windows, another for healthcare and insurance, again with several ToDo lists and browser windows.

Since I was a teenager I've had a fantasy of a well equipped shop with woodworking tools, metalworking tools, and lots of different workbenches for various projects that could in various stages of completion.These days I'd add a couple of 3D printer stations, etc. In a way, my multiple desktops are form of that. I don't have to put everything away that I'm actually in the middle of to be able to take care of something else that comes up.

image

2 Likes

Getting back to the OP question about switching to the previous desktop space.

@_jims's macros are very useful and seem to be the current state of the art for KM Desktop switching. But as @_jims will tell you, you have to switch windows using his macros for the system to keep track of where you are so that "previous" in the system matches what you think it ought to be.

Unfortunately, there are many ways to change Desktop Spaces, intentionally and unintentionally, without Jim's system, and in those cases, his Previous macro or menu choice won't be what you want it to be. For a simple example, you could be in Acrobat Reader and click on a link inside the PDF. Your default browser will open up a new tab in the most recently used browser window, which may or may not be in that same Desktop Space, and without jumping through hoops before you click on that link, you cannot tell. Suddenly finding yourself in another Desktop Space, of course you will want to return to where you were, and Jim's Previous macro will not be of any help, and if you don't happen to remember, then you have to hunt for it.

That's why I asked the question in the first place. seven months ago, and why, even though Jim's macros are very useful, I haven't market this issue as Solved.

I've come to the conclusion that any working system will require two pieces of information:

  • The ID of the current desktop, recorded somewhere to be the "previous" location after a move, recorded at every move.
  • A "trigger" to notify you when anything, user, browser, Finder, Terminal process, or whatever, changes the current Desktop Space.

There's good progress on the first of those above, from @_jims, and over in the thread How to test for a Space (Mission Control)?.

Ideal for the second would be a Keyboard Maestro trigger that the Desktop Space has changed, as I asked about in the OP. We don't have that (yet), but it sure would be nice, wouldn't it?

What if there was a way to create a "virtual" trigger? I think I have one, but I'm not the programmer (yet) to implement it.

I was recently given an "Amulet of Revealing" by the developer of the CurrentKey app. He said:

My swift code listens to this specific MacOS api, here is the line:

NSWorkspace.shared.notificationCenter.addObserver(forName: 
    NSWorkspace.activeSpaceDidChangeNotification, 
        object: nil, queue: nil, using: 
            <the function you want to handle this event> )

I'm not a Swift programmer. That's the first line of Swift code I've actually had an interest in using. I don't even know if I added linebreaks to the above in the correct syntax. What I did would not work for AppleScript.

So I need some help from the Swift programmers on this forum. I'm tagging @CJK, @ccstone, @DanThomas, @ComplexPoint, @MitchellModel, @peternlewis, and @_jims, as folks who I remember participating on this topic in various threads. My apologies to anyone I forgot and to anyone I tagged who isn't interested.

Please help me correct my misunderstandings:

The first part ends in .addObserver so that says to me that this line only needs to be run once and an Observer will be created that hangs around. Is that right?

Does the Observer terminate when the current code stops running, so the code has to stay open, or what? Is that Observer permanent until reboot? I really don't know how this works.

When the Observer is running, it's watching for this notification called NSWorkspace.activeSpaceDidChangeNotification to turn true. When that happens, because of what we're doing here there's no "object" involved (whatever that would be) and no "queue" involved (again, whatever that is), but it will use the function name that I provide to it, "to handle this event".

Can that function be an AppleScript function, or an AppleScript function inside a Swift wrapper, so that the AppleScript function can call a KM function?

If that Observer can indirectly call a KM macro as the "handler", then it seems that's getting most of the way there to having a virtual KM trigger of the Desktop Space being changed.

Am I right? What am I not understanding? What needs to be done to make this work so that KM can start this Observer, if it's not already running, and then get a macro triggered every time anything changes the current Desktop Space?

Thanks!

1 Like

Yes.

The code would have to run and continue running and monitor that and trigger a macro when it happened.

It could probably be done in JavaScript for Automation. If not, it would require a small application.

I will add it down as a potential trigger for a future version. I'm glad to see there is at least some Spaces API that is public.

3 Likes

Thanks Peter.

So I would need a small utility (perhaps run from a Terminal window) called something like WatchDesktop or KMDSObserver that would start this Observer and just stay there. From a terminal window, it could be run in the background. Would that be enough?

Are you suggesting that the function that it calls should be in JXA? Or that JXA be used to run this Swift code? Or that this API ought to be available in JXA if it's in Swift?

Since it's a high priority for me, and I know you don't like to make promises of what functions will be in the next update until you've gotten them working, I'll continue banging on this. If you get it working next week, please let me know!

Hi @August. Actually with almost all situations, including the one you describe, the macros I provided will properly track the Current and Previous Desktop Space. This is done using the macro Go to Previous Desktop with the trigger: The focused window: The focused window changes

Notice that I said almost all situations. I recommend using the macros Go Left a Desktop, Go Right a Desktop, etc. when doing explicit space changes because this will guarantee that the Current and Previous Desktop Spaces are properly tracked within the logic of the macro set.

From a practical standpoint, I've found that the rare situations that invisibly change the Desktop Space (from the standpoint of the macro set) cause minimal inconvenience. (I use these macros hundreds of times during the typical week.)


With the above said, my macros could be greatly simplified if @peternlewis was able to add a Space Change trigger to Keyboard Maestro.


If you are looking to roll your own space change detector, I suggest you consider digging into the WhichSpace code. Obviously the developer, George Christou, has determined a method.

1 Like

Thanks for the clarification, Jim (@_jims),

Sorry I misrepresented your macros. That was my recollection of the difficulties that I initially encountered. Maybe I exaggerated in my memory or maybe I wasn't patient enough to learn new habits that worked with those macros. However it happened, I'm very glad to know that they aren't as problematic as I had been painting them to myself. Sorry for the complaints.

However, if simple Go Left and Go Right worked for me, I wouldn't be chasing this. If I had three, five, or nine Desktops, that would probably suffice. And yes, sometimes I can rearrange the order of the Desktops so that the Spaces I am using most are next to each other. That's often the case when I'm doing any kind of programming where I have my working edits in the middle, doc in the Desktop to the left of that and testing in the Desktop to the right. I use Go Left and Go Right a LOT when I'm working on something like that. But for me that's a limited case with 3 desktops out of 20. I have several projects that are in ideation, research, and collecting mode. That's one group of four Desktop Spaces, currently. I have three Desktops that are for ToDo lists, status tracking, and filing/archiving, plust a couple more because I'm testing out different apps in parallel on different projects. I have two for email, one for FaceBook, and sometimes those aren't enough. So usually I'm jumping from one group to another group, and having to know that there's five steps in between is an annoyance, not a help.

Thanks,
August

Hi @August.

I use 16 Desktops and most of the space changes (and window movements) are to non-adjacent Desktop Spaces.


My macro/subroutine set can be used two main ways:

1. As is with or without a Conflict Palette.

Here's the CP that summarizes the capabilities provided:

Conflict Palette-image

In the palette you'll see that there are actually 40 different actions that can be completed using the stock features of the macros (40 = 2*16 + 8).

I sometimes use the conflict palette, but it's mostly designed for beginners or occasional users. I typically use the configurable hot keys assigned to the 40 actions... or, more often, the second method.

2. Creating and using additional simple macros that leverage the subroutines provided in the macro/sub-routine set.

This enables a context sensitive use of desktop spaces.

The best way to understand this method would be to read a post I submitted elsewhere: Workspaces in Mission Control Desktop Spaces

But this Group Palette might give you a general idea:

Group Palette-image


@August, elsewhere you have mentioned that you use more than 16 Desktop Spaces. The macro/subroutine set is limited to 16 because macOS does not provide shortcut keys for more than 16.

With said, if you'd like to try applying these macros and subroutines to your situation (e.g., let's say you have 25), I see at least one possibility...

Applications that are not necessary assigned to a space could be used in one or more of the first 16. (When I use these desktop spaces I do not assign applications to specific space even though the default use of many applications is normally on one of them. (See the Group Palette image above). In contrast, there are two applications that I've chosen to appear on all Desktop Spaces: Keyboard Maestro and Tot. When I don't want to see them, I simply use Hide (⌘H).)

Desktop Spaces 17 to 25 could then be used for applications that you use in an isolated fashion and would be assigned to one of these eight using the normal macOS setting (i.e., Right-click the application in the dock>Options>Assign To: This Desktop). You could then switch to these spaces by opening (or activating) any of these eight applications when needed. Once using one of these spaces, you could return to any of the first 16 using the macros in the set.

@August, if are interested in working within these constraints, let me know. I would need to modify and test one subroutine: sub—Put DesktopNo and/or DesktopNo_prev

My apologies for the slow response. I didn't see the notification that you had tagged me.

Yup.

It'll exist for as long as the application running the code continues to run.

For someone who doesn't know, you're a very good guesser.

A great deal of the AppKit framework is included in the AppleScript-ObjC bridge, which includes the NSWorkspace class and its methods; and the NSNotificationCenter class is part of the Foundation framework. So, yes, the function can be an AppleScript handler. However, not through the specific implementation you've exemplified in the Swift snippet.

There are two NSNotificationCenter class methods for adding observers for notifications. In Swift parlance, these are addObserver(forName:object:queue:using:) (which is the function you were shown); and addObserver(_:selector:name:object:). Of course, Swift APIs can't be called from AppleScript, while the ObjC methods are at our disposal, respectively addObserverForName:object:queue:usingBlock: and addObserver:selector:name:object:. Well, one of them is. Functionally, they're both quite similar, but the first method (which is the equivalent to function used in your Swift snippet) requires your custom function to be passed as a block (like an in-line function for which there is no equivalent AppleScript construct). So instead, you'd create your observer using addObserver:selector:name:object:, where the selector can be an AppleScript handler.

Not quite. KM won't be acting as a trigger, not even indirectly. Remember, the code that instantiates the observer has to continue running for the observer to remain active, and I don't recommend keeping a KM macro running on an infinite loop. Rather, you'd create an AppleScript Stay Open application that stays idling in the background and receives notifications that it's registered to listen for. It contains the handler that will be called whenever a notification is observed, and there's no reason that this handler couldn't then script KM or the KM Engine. But it's this handler that will be your trigger, not KM.

I'll post an example AppleScript implementation.

1 Like

Honestly, I'm a little surprised it worked, let alone as well as it did. It detected every space change. Sadly, it's not forthcoming with information, such as which space you've changed into or come from.

Here's the barebones script:

use framework "AppKit"
use scripting additions

property msgs : "/private/tmp/NSNotifications.txt"

on run
		tell my NSWorkspace's sharedWorkspace's notificationCenter ¬
				to addObserver:me selector:("notify:") ¬
				|name|:(my NSWorkspaceActiveSpaceDidChangeNotification) ¬
				object:(missing value)
		
		close access (open for access msgs)
		set eof of msgs to 0
end run

on idle
		
end idle

on quit
		tell my NSWorkspace's sharedWorkspace's ¬
				notificationCenter to removeObserver:me
		
		continue quit
end quit

to notify:message
		global msgs
		set D to (current date) as «class isot» as string
		write D & linefeed as "utf8" starting at eof to msgs
		display notification "Space changed" with title D
end notify:

This is a Stay Open application, which is one of the options available when choosing which format to save a script as in Script Editor. On running the application, it registers itself as an observer. Then it idles, and does absolutely nothing until you change space. It receives the alert, and the notify: handler is called (there's nothing useful passed to it in the message parameter, but it has to be there; you can name it whatever you want though). All I had it do was log the timestamp at which the space change occurred, and throw up a macOS notification that popped up in the top-right of the screen during changes. As proof:

Have to admit, that was pretty nifty.

NSNotificationObserver.app.zip (55.4 KB)

2 Likes

Me too, very slick indeed! Thanks for sharing, @CJK

I noticed that the notifications you indicated above appear if I run your AppleScript from the Script Editor, but not if I put it in a Execute an AppleScript. Any thoughts on that?

I haven't dug into the code, but as I shared above with @August, developer George Christou has a method to determine and display the Desktop Space number in his WhichSpace app.

Thanks @CJK !

Sorry for the mangled English syntax. I meant that the Observer would call AppleScript which would call a KM macro, so the KM macro would essentially, indirectly, "handle" the Observer's notification. That would make the whole thing a virtual KM trigger, like other KM triggers, in the sense that the specified KM macro would run when the event occurred.

Yes, that's what I was trying to say.

It would, of course, be limited to a single KM macro, but that one macro could execute as many KM macros as you want. That's a bit more convoluted than the way KM allows the macro definition to specify its own triggers, but it would work until Peter is able to find time among all his other priorities to implement and test this.

Thanks @_jims , that's a very interesting idea that I'll chew on. It's different from what I usually do, but it has possibilities.

Before I can say yes, I have to really understand them and how it would affect my workflow as well as how it might affect things down the road.

Wow. This solves some problems I hadn't gotten to yet. I've been working on calling AppleScript from Swift, under the assumption that the Swift observer had to be run from there. But your approach is a whole lot simpler. Thanks! I'll take it!

Here's my KM script that will get called:
image

And here's the macro it calls:
image