mrob95/pyvda

Windows 11 22H2 - Exception when calling get_virtual_desktops

Closed this issue · 28 comments

Attempting to call get_virtual_desktops() on my Windows 11 22H2 (build 22621.1) machine throws this error:
_ctypes.COMError: (-2147023116, 'A null reference pointer was passed to the stub.', (None, None, None, 0, None))

Thanks for reporting this. I don't currently have access to a windows 11 PC to test, but am going to try downloading a developer VM to see if I can repro it this weekend.

image

The latest version I can get hold of from microsoft is 22000, which doesn't seem to have this issue.

If you or anyone else would like this fixed before I next buy a laptop you can help out by building https://github.com/tyranid/oleviewdotnet and looking up the interface definition for IVirtualDesktopManagerInternal:

  1. Click Registry -> Interfaces By Name
  2. Search for IVirtualDesktopManagerInternal
  3. right click -> View Proxy Definition
  4. Post the interface definition, including GUID, here
    image

It's an Insider build, so I'm not surprised there's no standalone download. I mean, you could maybe try to update your VM to Insider by logging into it with a Microsoft account that owns Windows (I'm... actually not sure if that's a requirement for the Insider program. It probably is?)

ANYWAY, I'm gonna go look into the interface definition now. Just gotta wait for VS to finish updating...

Well that didn't go well. I couldn't even find IVirtualDesktopManagerInternal, and when I tried to view the proxy definition of IVirtualDisplayManager (the closest thing I could find), well...
Damn null pointer exceptions.

New discoveries about IVirtualDesktopManagerInternal:

  • GUID is unchanged!
  • Name is now B2F925B9-5A0F-4D2E-9F4D-2B1507593C10 (same as GUID), apparently - explains why I couldn't find them by name, I guess.
  • New method: GetAllCurrentDesktops(IObjectArray**) (from here)
  • "View Proxy Definition" is just borked on my machine...

Thanks, that is very useful info and should be enough to fix the issue.

Out of interest based on this comment:

they are in the registry but the interface names have been replaced by the ids so the function that searches by name fails.

Are you able to find the proxy definition if you search for B2F925B9 as the name?

Are you able to find the proxy definition if you search for B2F925B9 as the name?

Yes.
image

Does "view proxy definition" work there or is it borked for them all?

Nope, it's broken for everyone.

Is there a setting in win11 to control whether you have one "stack" of virtual desktops per monitor or one stack across all monitors?

Doesn't seem like it. In fact, looks like opening Task View in one monitor opens it in all monitors.

Okay, could you give this PR a try and see if that fixes it?

#16

I'm still a bit confused by the naming of "GetAllCurrentDesktops" - what does it mean for a desktop to not be current?

Running the test suite with #16 seems to fix everything but test_create_and_remove_desktop, which fails on the assertion that new_count == (old_count + 1).

It does successfully add a new desktop, but then fails to detect it...?

I've pushed an update which just adds some better error messages to the tests. Would be interested to know what it thinks new_count and old_count are - 0 or 1 for both maybe?

Before the update, both old_count and new_count were 1 - get_all_desktops wasn't returning the desktop that was just created, apparently.

>       assert new_count == expected_count, f"Wanted {expected_count}, got {new_count}"
E       AssertionError: Wanted 2, got 1
E       assert 1 == 2

Hmm. My theory is that Microsoft are planning to release a feature at some point which allows you to use a different stack of virtual desktops for each monitor. That would explain why they have added all of these new integer parameters - you have to pass in the id of the monitor you want the desktops for. It would also explain the GetDesktopPerMonitor and SetDesktopPerMonitor functions, which imply that such a setting exists, or will exist in the future.

Given this I don't think GetAllCurrentDesktops is the call we want. It's only going to give us an array of the desktops which are currently active - so if you had three monitors it would give back three desktops. That's why it's currently only giving back one - because we only have one stack of desktops.

What we want is to figure out what we need to pass into GetDesktops to get it to give us back all of the desktops on the one stack. Setting it to 0 worked in previous builds but now it is complaining about a null pointer.

I've pushed another update passing in 1, just in case they are using 1-indexed monitor IDs. I don't have high hopes that that will work though.

...why did that work.

Congratulations, you've apparently thinking on the same track as Microsoft.

...interesting. What happens if we pass in 2? 🤔

You pass in IntPtr.Zero

Also if you are getting the null pointer error then you are probably missing a proc in the interface

[ComImport]
	[Guid("B2F925B9-5A0F-4D2E-9F4D-2B1507593C10")]
	[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	internal interface IVirtualDesktopManagerInternal
	{
		int GetCount(IntPtr hWndOrMon);

		void MoveViewToDesktop(IApplicationView pView, IVirtualDesktop desktop);

		bool CanViewMoveDesktops(IApplicationView pView);

		IVirtualDesktop GetCurrentDesktop(IntPtr hWndOrMon);

		IObjectArray GetAllCurrentDesktops();

		IObjectArray GetDesktops(IntPtr hWndOrMon);

		IVirtualDesktop GetAdjacentDesktop(IVirtualDesktop pDesktopReference, int uDirection);

		void SwitchDesktop(IntPtr hWndOrMon, IVirtualDesktop desktop);

		IVirtualDesktop CreateDesktop(IntPtr hWndOrMon);

		void MoveDesktop(IVirtualDesktop desktop, IntPtr hWndOrMon, int nIndex);

		void RemoveDesktop(IVirtualDesktop pRemove, IVirtualDesktop pFallbackDesktop);

		IVirtualDesktop FindDesktop(in Guid desktopId);

		void GetDesktopSwitchIncludeExcludeViews(IVirtualDesktop desktop, out IObjectArray o1, out IObjectArray o2);

		void SetDesktopName(IVirtualDesktop desktop, [MarshalAs(UnmanagedType.HString)] string name);

		void SetDesktopWallpaper(IVirtualDesktop desktop, [MarshalAs(UnmanagedType.HString)] string path);

		void UpdateWallpaperPathForAllDesktops([MarshalAs(UnmanagedType.HString)] string path);

		void CopyDesktopState(IApplicationView pView0, IApplicationView pView1);

		bool GetDesktopIsPerMonitor();

		void SetDesktopIsPerMonitor(bool state);

	}

I just reviewed your code and it appears the proc you're missing in IVirtualDesktopManagerInternal is:

IObjectArray GetAllCurrentDesktops();

The unknown one in that interface is:

void GetDesktopSwitchIncludeExcludeViews(IVirtualDesktop desktop, out IObjectArray o1, out IObjectArray o2);

Thanks @mzomparelli - I think it was the GetAllCurrentDesktops prototype that we were missing, which I've now added in #16. The "what do we need to pass into GetDesktops?" question may have been a red herring :)

I'd like to clean up our interface definitions at some point but have a feeling that there are a few more changes coming down the road. Seems obvious that Microsoft are looking to add multi-monitor support as most of the open source WMs (i3, dwm, etc) have.

Updated to 0.2.6, now I get this error:

Traceback (most recent call last):
  File "C:\Users\<USER>\Documents\script.py", line 7, in <module>
    from pyvda import get_virtual_desktops, VirtualDesktop, AppView
  File "C:\Users\<USER>\AppData\Local\Programs\Python\Python310\lib\site-packages\pyvda\__init__.py", line 51, in <module>
    from .pyvda import (
  File "C:\Users\<USER>\AppData\Local\Programs\Python\Python310\lib\site-packages\pyvda\pyvda.py", line 24, in <module>
    managers = Managers()
  File "C:\Users\<USER>\AppData\Local\Programs\Python\Python310\lib\site-packages\pyvda\utils.py", line 52, in __init__
    self.manager_internal2 = get_vd_manager_internal2()
  File "C:\Users\<USER>\AppData\Local\Programs\Python\Python310\lib\site-packages\pyvda\utils.py", line 41, in get_vd_manager_internal2
    return _get_object(IVirtualDesktopManagerInternal2, CLSID_VirtualDesktopManagerInternal)
  File "C:\Users\<USER>\AppData\Local\Programs\Python\Python310\lib\site-packages\pyvda\utils.py", line 27, in _get_object
    pServiceProvider.QueryService(
_ctypes.COMError: (-2147467262, 'No such interface supported', (None, None, None, 0, None))

Whoops. Could you try 0.2.7 @Leo40Git ?

0.2.7 works perfectly!

Nice, thanks for the report and for your help fixing it :)