Closed widavies closed 1 year ago
Here's my Windows version:
Hello @widavies,
you are not the first to ask this question. VirtualDesktop is only changing the virtual desktop and does no window activation or change of keyboard focus. And I think there is no satisfying programmable solution for this, but I'm open for ideas.
Greetings
Markus
Got it, I did a search of issues and didn't seem to find it. My bad.
I've done a fair amount of research on this myself and I have a few ideas:
VirtualDesktop.FromIndex(..).MakeVisible()
to switch to a desktop upon detecting a particular keyboard shortcut. Strangely enough, when I run in debug mode, the foreground application is properly restored. However, when I run it in release mode the foreground application does not get restored. My explanation for this at first was that there must be some race condition that is only slowed down by the debugger running, but after some investigation I almost think that the debugger messes with the foreground state during the virtual desktop change and Windows will respond by correctly restoring the foreground application. Not entirely sure though.It seems to be that we can write this off as a Windows bug and that for now we'll need to manually restore the foreground Window. I have two main ideas on how to accomplish this
IApplicationViewCollection
We could use the GetViewsByZOrder()
function to get a listing of all applications by Z-order. My assumption is that IApplicationViewCollection
is for all windows, not just the windows for a desktop, but I could be wrong. It also seems like GetViewInFocus(..)
could be useful.
It then seems that we could loop through the views by z order, filter out any views not on the desktop we want to switch to be checking IVirtualDesktop.IsViewVisible
and then using the SetForegroundWindow function to correct the foreground Window.
I did actually attempt this but GetViewsByZOrder
would segfault for me (Windows 11 22H2)
The other alternative is a little sloppy, but we can always fetch the current foreground Window before switching desktops with GetForegroundWindow.
Basically, we'll just log the foreground Window prior to switching so that when we switch back, we'll restore it. It is a little sticky because if the user switches desktops using the Windows built-in UI, we'll have to hook into that and try to work out the foreground change.
Hello @widavies,
sorry, I should have explained the problems with setting the focus clearer: Setting a window active on a virtual desktop fails when there is a windows active (including the keyboard focus) on another virtual desktop!
I've already tried all possibilities I know to change the focus, They all did not work (some work sometimes, but I could not determine why). I tried (I'm mixing programming languages now):
::SendMessage(WindowHandle, WM_SYSCOMMAND, SC_HOTKEY, (LPARAM) WindowHandle);
::SendMessage(WindowHandle, WM_SYSCOMMAND, SC_RESTORE, (LPARAM) WindowHandle);
::ShowWindow(WindowHandle, SW_SHOW);
::SetForegroundWindow(WindowHandle); //there seems to be only one ForegroundWindow over all virtual desktops
::SetFocus(WindowHandle);
ApplicationView.SetFocus(); // seems to do the same as the "classical" SetFocus()
ApplicationView.SwitchTo(); // seems to do nothing
I even tried to send
::SendMessage(wnd, WM_KILLFOCUS, 0, 0);
to the "old" window that still has the focus before switching.
Or activating the desktop before switching. Nothing works. The main issue seems to be the keyboard focus.
The only way I got it working reliable was programmatically pressing the {WINKEY} twice before switching the virtual desktop. But this has so many side effects that this is not an option.
Greetings
Markus
Btw.: ApplicationView.GetNeediness(int); seems to determine whether a windows has a focus but is not activated.
Going to be a bit before I have time to look into this, but one hackish idea is we could simulate an alt+tab
+ release shortcut to trigger focus of the top window again.
Hey guys, did you try using AttachThreadInput API ?
I encountered a similar problem in the past when I wrote a program that switch to window with fuzzy search (in golang).
But the solution was to use something like this:
private static void ForceForegroundWindow(IntPtr hWnd)
{
uint foreThread = GetWindowThreadProcessId(GetForegroundWindow(),
IntPtr.Zero);
uint appThread = GetCurrentThreadId();
const uint SW_SHOW = 5;
if (foreThread != appThread)
{
AttachThreadInput(foreThread, appThread, true);
BringWindowToTop(hWnd);
ShowWindow(hWnd, SW_SHOW);
AttachThreadInput(foreThread, appThread, false);
}
else
{
BringWindowToTop(hWnd);
ShowWindow(hWnd, SW_SHOW);
}
}
you can read more about it here: https://shlomio.wordpress.com/2012/09/04/solved-setforegroundwindow-win32-api-not-always-works/ https://stackoverflow.com/questions/17370939/set-foreground-window-on-windows-8
**EDIT I tried it myself - every time before I switch to other desktop I save the current HWND and the desktop index. when I switch back I'm taking the last HWND for that desktop index and try to focus the window with a similar function to the above example.
It sometimes works and sometimes not đ
Hello,
I think I have a working solution.
After reading a lot about this, especially in the issue at mzomparelli (who should no longer be supported "openly", as he makes closed software), @FuPeiJiang in combination with @rotemgrim's comment gave me a solution: before switching the desktop, the focus should be placed on a window that is available on each virtual desktop, so that the focus is simply retained when switching! In VD.ahk, the taskbar (window class _ShellTrayWnd) is used for this, but I think the desktop is more useful. The idea to give away the focus with a message to minimize is a great idea (I don't know who had it initially).
With the following code blocks (in C++) it worked for me:
HWND wnd = FindWindow(NULL, "Program Manager");
DWORD DesktopThreadId = GetWindowThreadProcessId(wnd, NULL);
DWORD ForegroundThreadId = GetWindowThreadProcessId(GetForegroundWindow(), NULL);
DWORD CurrentThreadId = GetCurrentThreadId();
if ((DesktopThreadId != 0) && (ForegroundThreadId != 0) && (ForegroundThreadId != CurrentThreadId))
{
AttachThreadInput(DesktopThreadId, CurrentThreadId, true);
AttachThreadInput(ForegroundThreadId, CurrentThreadId, true);
SetForegroundWindow(wnd);
AttachThreadInput(ForegroundThreadId, CurrentThreadId, false);
AttachThreadInput(DesktopThreadId, CurrentThreadId, false);
}
. here is the code for switching the virtual desktop .
HWND wnd = FindWindow(NULL, "Program Manager");
ShowWindow(wnd, SW_MINIMIZE);
I'm not entirely happy with it because it's probably not stable in every situation. I am still testing and experimenting...
Greetings
Markus
If I understand correctly Your method is : WinActivate desktopBackground Switch VD WinMinimize desktopBackground
my old method was : WinActivate taskbar Switch VD WinMinimize taskbar
my current method is : Switch VD WinActivate firstWindow (I think I have a perfect replica of what windows appear in alt+tab list)
I have run into problems but I think Iâve patched them all now
My old method did not have an animation when switching virtual desktop
Itâs better to have a reliable âWinActivate firstWindowâ method because after you move the active window to a different desktop, you have focus on nothing, so you have to focus the next/new first window
my current method is : Show a gui1 so that the active window is owned by my process Switch VD (create gui2, moveVD gui2, winactivate gui2) Loop until/Spin lock until current_VD_num==target_VD_num WinActivate firstWindow If no firstWindow { WinActivate desktopBackground } Destroy gui1 Destroy gui2
It is insanely hacky but I tried the non-hacky way and am more comfortable with this way now
Hello @FuPeiJiang,
thank you very much for your comment.
It's interesting what you have to do to get a clean implementation. Since I only provide a text-oriented tool, the effort to create extra windows just to change the desktop cleanly is too high for me. I will therefore probably leave it at the simpler solution and accept individual error cases.
But I'm still researching.
Greetings
Markus
One idea I have is to act only if a window really has the problem, i.e. flashes. The following code can be used to determine this for a window (here the foreground window), the code uses the declarations of my VirtualDesktop tool:
IntPtr hWnd = GetForegroundWindow();
var view = hWnd.GetApplicationView();
int neediness;
view.GetNeediness(out neediness);
In case the window is flashing neediness has the value 1, if not it has the value 0.
But I'm not really sure how to use it yet. Maybe not at all.
Hello,
I think I have a working solution.
After reading a lot about this, especially in the issue at mzomparelli (who should no longer be supported "openly", as he makes closed software), @FuPeiJiang in combination with @rotemgrim's comment gave me a solution: before switching the desktop, the focus should be placed on a window that is available on each virtual desktop, so that the focus is simply retained when switching! In VD.ahk, the taskbar (window class _ShellTrayWnd) is used for this, but I think the desktop is more useful. The idea to give away the focus with a message to minimize is a great idea (I don't know who had it initially).
With the following code blocks (in C++) it worked for me:
HWND wnd = FindWindow(NULL, "Program Manager"); DWORD DesktopThreadId = GetWindowThreadProcessId(wnd, NULL); DWORD ForegroundThreadId = GetWindowThreadProcessId(GetForegroundWindow(), NULL); DWORD CurrentThreadId = GetCurrentThreadId(); if ((DesktopThreadId != 0) && (ForegroundThreadId != 0) && (ForegroundThreadId != CurrentThreadId)) { AttachThreadInput(DesktopThreadId, CurrentThreadId, true); AttachThreadInput(ForegroundThreadId, CurrentThreadId, true); SetForegroundWindow(wnd); AttachThreadInput(ForegroundThreadId, CurrentThreadId, false); AttachThreadInput(DesktopThreadId, CurrentThreadId, false); }
. here is the code for switching the virtual desktop .
HWND wnd = FindWindow(NULL, "Program Manager"); ShowWindow(wnd, SW_MINIMIZE);
I'm not entirely happy with it because it's probably not stable in every situation. I am still testing and experimenting...
Greetings
Markus
Excellent idea! I've been testing this as well and it actually seems to work well for me without the second part:
HWND wnd = FindWindow(NULL, "Program Manager"); ShowWindow(wnd, SW_MINIMIZE);
Does removing that work for you too?
Hello @widavies,
the code with the "minimise" message is only there to make the shell activate another window on the new desktop. If it's not a problem for you that the desktop has the focus, you don't need the code.
Greetings
Markus
Hello,
further research did not bring further success. Implemented my suggested solution to my companion project PSVirtualDesktop and will implement here soon.
Greetings
Markus
Hello,
released new version 1.13 today that included the change.
Greetings
Markus
@MScholtes Thank you. Is the issue resolved completely? or is it a temporary fix?
Hello @rotemgrim,
this is the permanent solution. However, it may not work in all cases, as it requires a certain cooperation of the displayed windows. I hope, however, it will work in almost all cases.
Greetings
Markus
Issue seems to be solved
To reproduce:
Get-Desktop 0 | Switch-Desktop
to go back to first virtual desktopFocus isn't restored to the notepad window. Let me know if you are able to reduce this, it is somewhat intermittent.