phoenix
phoenix copied to clipboard
App#windows() does not include windows that are not in the current space
- Version: 2.3.49
- macOS: 10.11.3
Let's say that I am in laptop only mode, with two spaces open, and an application having a window in each space.
I don't believe this applies in this case, but note that in Mission Control, I have the following settings:

if i run the following code:
keys.push(
new Key('3', altButton, function() {
var apps = App.all()
for (var i = 0; i < apps.length; i++) {
var app = apps[i]
if (app.name() == "Sublime Text") {
Phoenix.log("app.windows() = " + app.windows())
for (var j = 0; j < app.windows().length; j++) {
var w = app.windows()[j]
Phoenix.log("windows[" + j + "].title() = " + w.title())
Phoenix.log("windows[" + j + "].screen().hash() = " + w.screen().hash())
Phoenix.log("windows[" + j + "].spaces() = " + w.spaces())
for (var k = 0; k < w.spaces().length; k++) {
var s = w.spaces()[k]
Phoenix.log("windows[" + j + "].space[" + k + "].hash() = " + s.hash())
}
}
}
}
})
);
For the following cases I get the following output in Console:
A. The app has both windows (visible) in the first (currently active) space
8/13/16 4:14:20.032 PM Phoenix[8074]: app.windows() = [object PHWindow],[object PHWindow]
8/13/16 4:14:20.035 PM Phoenix[8074]: windows[0].title() = artifacts.xml
8/13/16 4:14:20.035 PM Phoenix[8074]: windows[0].screen().hash() = 2077752381
8/13/16 4:14:20.036 PM Phoenix[8074]: windows[0].spaces() = [object PHSpace]
8/13/16 4:14:20.036 PM Phoenix[8074]: windows[0].space[0].hash() = 3
8/13/16 4:14:20.040 PM Phoenix[8074]: windows[1].title() = phoenix.js
8/13/16 4:14:20.040 PM Phoenix[8074]: windows[1].screen().hash() = 2077752381
8/13/16 4:14:20.040 PM Phoenix[8074]: windows[1].spaces() = [object PHSpace]
8/13/16 4:14:20.041 PM Phoenix[8074]: windows[1].space[0].hash() = 3
B. The app has one window in the first space (which is currently active), and a second window in the second (non-visible) space:
8/13/16 4:14:43.246 PM Phoenix[8074]: app.windows() = [object PHWindow]
8/13/16 4:14:43.249 PM Phoenix[8074]: windows[0].title() = phoenix.js
8/13/16 4:14:43.249 PM Phoenix[8074]: windows[0].screen().hash() = 2077752381
8/13/16 4:14:43.250 PM Phoenix[8074]: windows[0].spaces() = [object PHSpace]
8/13/16 4:14:43.251 PM Phoenix[8074]: windows[0].space[0].hash() = 3
C. Same as B, but If switch to the second space, and rerun:
8/13/16 4:14:56.452 PM Phoenix[8074]: app.windows() = [object PHWindow]
8/13/16 4:14:56.454 PM Phoenix[8074]: windows[0].title() = artifacts.xml
8/13/16 4:14:56.454 PM Phoenix[8074]: windows[0].screen().hash() = 2077752381
8/13/16 4:14:56.455 PM Phoenix[8074]: windows[0].spaces() = [object PHSpace]
8/13/16 4:14:56.455 PM Phoenix[8074]: windows[0].space[0].hash() = 4
Hmm, interesting. App#windows() asks the Accessibility API to return all windows. No tricks. I wonder if this is considered by Apple as the desired result.
Oh, and a quick hint. App.get(appName) might be handy.
I think this is again caused by the “Displays have separate Spaces” option being disabled. If that is the case, I’m not sure what we can do about it. Could you again test this theory?
Yeah, that is what i thought that it might be too, but I double checked it and verified that the behavior is the same when the option is both enabled & disabled. Hard to believe, right? Does it do the same for you?
Did you log out between enabling / disabling the separate spaces option? It's required for it to take fully effect I believe.
On Sun, 14 Aug 2016, 11:58 p.m. Robert Lasko, [email protected] wrote:
Yeah, that is what i thought that it might be too, but I double checked it and verified that the behavior is the same when the option is both enabled & disabled. Hard to believe, right? Does it do the same for you?
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/kasper/phoenix/issues/131#issuecomment-239696292, or mute the thread https://github.com/notifications/unsubscribe-auth/AAI_0ffERWelp5D8pfNQjgUPN0rWY4Xaks5qf4FWgaJpZM4JjwOj .
Yes, i did logout. I've triple verified that it isn't just some wacky user (me) error. Am I the only person that is experiencing this?
this should be really easy to replicate. here is the code i am using to indicate the problem in Console:
keys.push(
new Key('3', ["alt"], function() {
var app = App.get("APP_NAME_GOES_HERE")
Phoenix.log("app.windows() = " + app.windows())
for (var j = 0; j < app.windows().length; j++) {
var w = app.windows()[j]
Phoenix.log("windows[" + j + "].title() = " + w.title())
Phoenix.log("windows[" + j + "].screen().hash() = " + w.screen().hash())
Phoenix.log("windows[" + j + "].spaces() = " + w.spaces())
for (var k = 0; k < w.spaces().length; k++) {
var s = w.spaces()[k]
Phoenix.log("windows[" + j + "].space[" + k + "].hash() = " + s.hash())
}
}
})
);
open up your app with two windows in separate spaces, and then Alt+3
@rjlasko I haven’t encountered this before and definitely have been able to get windows from different Spaces. (For example Space#windows() wouldn’t work otherwise. Including many other APIs.)
Could this be a behaviour related to a certain app? Have you verified this behaviour with multiple apps?
@rjlasko Ok, sorry I retract my previous comment — I probably have only tried fetching windows from different but active spaces. I actually can reproduce this and guess what, unfortunately this is how Apple seems to have implemented it! You only get the windows for the active space. I wonder why this hasn’t come up before!
Other window managers seem to hack this by listening to window events (open and close). 👎
Sorry for the delayed reply... So there are actually two related issues here.
- App#windows() not returning windows in other non-active spaces.
- Space#windows() not returning windows if not an active space.
That is a bummer, however I guess that not all is lost. My final goal was to be able to reorganize all active apps onto different predefined spaces on a single-display system. Maybe there is still a workaround available for this involving navigating to all spaces, and performing operations while the active space has been altered. I will report my success after I have tested it out.
Actually, i don't think that a workaround is possible, is it? I was thinking about moving a window to a different space and then focusing on it to transition, but that is not enough if i cannot add/delete a space.
There is no API that allows the creation or deletion of a space, correct? If so, then spaces support is ultimately limited to what is already within the set of active (visible) spaces. =(
Please prove me wrong.
@rjlasko Well the Spaces support is minimal, because Apple simply doesn’t give public APIs for the feature. To limit issues, we only use the private APIs that make sense. Creating and removing spaces with the private APIs require you to kill the Dock with every change. Not really practical. So you need to preset the Spaces with the UI.
You can however switch to a Space easily by focusing to an app/window that is located there. Obviously this is made harder by the fact that you can’t get windows from different Spaces if they are there to start with (but if you move a window to a different Space, you already have the reference to it). App#windows() and Space#windows() rely on Window#all(). Since Window#all() is not returning windows from other Spaces than the current one (but still from all current Spaces for different screens), the whole chain breaks in this regard. This seems to be the case with Hammerspoon as well. I still am baffled on why I haven’t noticed before (since I’ve manually tested these when they were implemented).
To improve this, I guess we would need to start with the windows returned by the Accessibility API and then listen to events to add windows created and deleted in other Spaces than the current (you could try this yourself). Hammerspoon seems to do this in the filter extension. Not sure if this would work, since you could already have windows in different Spaces. I’m confused why Apple has implemented this how it is. I would expect if I ask the Accessibility API to “return me all windows”, it would return all windows. Since it returns windows from different screens anyway (and screens can have different Spaces).
I guess the main idea is that you cannot create windows in other Spaces than the current one (for every screen). Not even from the UI, new windows open to the current Space for the active screen.
You could have your Spaces preset in the way you prefer. Open the windows in the current Space and then add/move them to an other one. This should work just fine.
FYI, App#mainWindow() will return a window even if it is on a different space. It's not ideal, but it's better than nothing.
A quick update on this: I noted that App#windows() will return windows on a different space if those windows are minimized. If you're trying to move all of an app's windows from an inactive space to the current active space, you can do something hacky like the following to get it to work:
function moveAppToCurrentSpace(name) {
let app = App.get(name)
if (!app) return
const space = Space.active()
while(app.mainWindow() !== undefined) {
app.mainWindow().minimize()
}
space.moveWindows(app.windows())
app.windows().forEach(w => w.unminimize())
app.focus()
}
This isn't without some wonkiness. The minimize animation, for instance, will occur on the new space. Some apps (WezTerm, for instance) seem to destroy their windows rather than minimize them. I don't actually recommend using the snippet above, but it does point to something smelly, in my opinion.