2

I'm trying to implement IVirtualDesktopManager interface (and hopefully IVirtualDesktopManagerInternal afterward) in Python based on this SO answer, but I'm stuck.


Edit: I want a working example in Python that will at least lead me to the implementation of IsWindowOnCurrentVirtualDesktop method - it should return a Boolean when a hwnd is passed to it.


Using pywin32 I'm able to get to some point in the process, but I'm not sure neither if I even can do that in pure Python nor how to continue:

# bypythoncom.py
import pythoncom
import pywintypes

"""IServiceProvider* pServiceProvider = NULL;
HRESULT hr = ::CoCreateInstance(
    CLSID_ImmersiveShell, NULL, CLSCTX_LOCAL_SERVER,
    __uuidof(IServiceProvider), (PVOID*)&pServiceProvider);"""

CLSID_ImmersiveShell = pywintypes.IID("{C2F03A33-21F5-47FA-B4BB-156362A2F239}")

provider = pythoncom.CoCreateInstance(
    CLSID_ImmersiveShell,
    None,
    pythoncom.CLSCTX_LOCAL_SERVER,
    pythoncom.IID_IServiceProvider,
)
print(provider)

"""if (SUCCEEDED(hr))
{
    IVirtualDesktopManager *pDesktopManager = NULL;
    hr = pServiceProvider->QueryService(__uuidof(IVirtualDesktopManager), &pDesktopManager);

    if (SUCCEEDED(hr))
    {
        BOOL bIsOnCurrentDesktop = FALSE;
        hr = pDesktopManager->IsWindowOnCurrentVirtualDesktop(hWnd, &bIsOnCurrentDesktop);

        if (SUCCEEDED(hr))
        {
            // use bIsOnCurrentDesktop as needed...
        }

        pDesktopManager->Release();
    }

    pServiceProvider->Release();
}
"""

manager = provider.QueryService(
    pywintypes.IID("{a5cd92ff-29be-454c-8d04-d82879fb3f1b}"),
    pythoncom.IID_IUnknown,
)
print(manager)

# C:\dev\examples\desktops>python bypythoncom.py
# <PyIServiceProvider at 0x002A7170 with obj at 0x001C256C>
# <PyIUnknown at 0x002A7188 with obj at 0x002AB2F4>

Using comtypes I don't know what to use as interface argument to QueryService:

# bycomtypes.py
import comtypes
import comtypes.client
from comtypes.GUID import GUID

clsid = GUID("{C2F03A33-21F5-47FA-B4BB-156362A2F239}")  # CLSID_ImmersiveShell

service_provider = comtypes.client.CreateObject(clsid, interface=comtypes.IServiceProvider)
print(service_provider)

iid = GUID("{A5CD92FF-29BE-454C-8D04-D82879FB3F1B}") # IID_IVirtualDesktopManager

manager = service_provider.QueryService(
    iid, interface
)

# C:\dev\examples\desktops>python bycomtypes.py
# <POINTER(IServiceProvider) ptr=0xbd8d04 at 30c8e90>

I'm not really sure what I should do after getting provider, IUnknown has only QueryInterface method exposed, here's another ctypes and comtypes try:

import ctypes
from comtypes import IUnknown, CLSCTX_LOCAL_SERVER
from comtypes.GUID import GUID

ole32 = ctypes.windll.ole32
# ole32.CoInitialize(None)

CLSID_ImmersiveShell = GUID("{C2F03A33-21F5-47FA-B4BB-156362A2F239}")
IID_IServiceProvider = GUID("{6D5140C1-7436-11CE-8034-00AA006009FA}")

provider = ctypes.POINTER(IUnknown)()
ole32.CoCreateInstance(
    ctypes.byref(CLSID_ImmersiveShell),
    None,
    CLSCTX_LOCAL_SERVER,
    ctypes.byref(IID_IServiceProvider),
    ctypes.byref(provider),
)
print(provider)

IID_IVirtualDesktopManager = GUID("{A5CD92FF-29BE-454C-8D04-D82879FB3F1B}")
instance = provider.QueryInterface(IUnknown, IID_IVirtualDesktopManager)

# C:\dev\examples\desktops>python byctypes.py
# <POINTER(IUnknown) ptr=0x8b294 at 8a26c0>
# Traceback (most recent call last):
#   File "another.py", line 22, in <module>
#     instance = provider.QueryInterface(IUnknown, IID_IVirtualDesktopManager)
#   File "C:\dev\venvs\project\lib\site-packages\comtypes\__init__.py", line 1158, in QueryInterface
#     self.__com_QueryInterface(byref(iid), byref(p))
# _ctypes.COMError: (-2147467262, 'No such interface supported', (None, None, None, 0, None))

Edit2: I changed the last example so it reveals what @Baget points at. An excerpt from the actual implementation in Python of comtypes.IUnknown's QueryInterface is:

def QueryInterface(self, interface, iid=None):
    "QueryInterface(interface) -> instance"
    p = POINTER(interface)()
    if iid is None:
        iid = interface._iid_

I would be grateful if someone gives a hint on how to do this or links some implementation I may follow to do the job.

ipaleka
  • 3,745
  • 2
  • 13
  • 33
  • You should also add to your question, what you are trying to achieve as a end goal – Tarun Lalwani Jul 25 '19 at 07:32
  • The first sentence explains, I want to be able to run those three method from the interface in Python, linked SO answer in C++ deals with one of those methods and that's totally fine, the other two (and Internals interface probably) should be easy toimplement. – ipaleka Jul 25 '19 at 10:27
  • 1
    See if this helps? https://github.com/DanEdens/Virtual_Desktops_Plugin/ – Tarun Lalwani Jul 25 '19 at 14:41
  • Wow, that's it! :) **Please make an answer** from this comment, so I may accept it. There are some issues I've got (`pDesktopManagerInternal.GetDesktops(ctypes.byref(pObjectArray)) TypeError: call takes exactly 1 arguments (2 given)`, but that "problem" is what we call in Croatia "a p**sy smoke", kind of meaning "piece of cake to solve". :) – ipaleka Jul 25 '19 at 16:14
  • 1
    Cool, glad could point you in the right direction :-) – Tarun Lalwani Jul 25 '19 at 17:18

2 Answers2

4

You can take inspiration from below code to write your own

All credits to the original author on https://github.com/DanEdens/Virtual_Desktops_Plugin/

# -*- coding: utf-8 -*-
#
# This file is a plugin for EventGhost.
# Copyright © 2005-2019 EventGhost Project <http://www.eventghost.net/>
#
# EventGhost is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# EventGhost is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with EventGhost. If not, see <http://www.gnu.org/licenses/>.

u"""
    Name: VirtualDesktops
    Author: Kgschlosser
    Version: 0.1
    Description: Creates events based on Virtual desktop interactions.
    GUID: {5DFFBD61-7582-4D6F-8EA9-9CB36284C9CF}
    URL: http://eventghost.net/forum/viewtopic.php?f=10&p=53389#p53389
"""
import eg

eg.RegisterPlugin(
    name = "Virtual Desktops",
    author = "Kgschlosser",
    version = "0.0.004",
    guid = "{C2F03A33-21F5-47FA-B4BB-156362A2F239}",
    canMultiLoad = False,
    url = "http://eventghost.net/forum/viewtopic.php?f=10&p=53389#p53389",
    description = "Creates events based on Virtual desktop interactions.",

)


from ctypes.wintypes import HRESULT, HWND, BOOL, POINTER, DWORD, INT, UINT, LPVOID, ULONG

import comtypes
import ctypes
from comtypes import helpstring, COMMETHOD
from comtypes.GUID import GUID

REFGUID = POINTER(GUID)
REFIID = REFGUID
ENUM = INT
IID = GUID
INT32 = ctypes.c_int32
INT64 = ctypes.c_int64

CLSID_ImmersiveShell = GUID(
    '{C2F03A33-21F5-47FA-B4BB-156362A2F239}'
)

CLSID_IVirtualNotificationService = GUID(
    '{A501FDEC-4A09-464C-AE4E-1B9C21B84918}'
)


class HSTRING__(ctypes.Structure):
    _fields_ = [
        ('unused', INT),
    ]


HSTRING = POINTER(HSTRING__)


class EventRegistrationToken(ctypes.Structure):
    _fields_ = [
        ('value', INT64)
    ]


class AdjacentDesktop(ENUM):
    LeftDirection = 3
    RightDirection = 4


class ApplicationViewOrientation(ENUM):
    ApplicationViewOrientation_Landscape = 0
    ApplicationViewOrientation_Portrait = 1


class TrustLevel(ENUM):
    BaseTrust = 0
    PartialTrust = BaseTrust + 1
    FullTrust = PartialTrust + 1


IID_IInspectable = GUID(
    '{AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90}'
)


class IInspectable(comtypes.IUnknown):
    _case_insensitive_ = True
    _idlflags_ = []
    _iid_ = IID_IInspectable
    _methods_ = [
        COMMETHOD(
            [helpstring('Method GetIids')],
            HRESULT,
            'GetIids',
            (['out'], POINTER(ULONG), 'iidCount'),
            (['out'], POINTER(POINTER(IID)), 'iids'),
        ),
        COMMETHOD(
            [helpstring('Method GetRuntimeClassName')],
            HRESULT,
            'GetRuntimeClassName',
            (['out'], POINTER(HSTRING), 'className'),
        ),
        COMMETHOD(
            [helpstring('Method GetTrustLevel')],
            HRESULT,
            'GetTrustLevel',
            (['out'], POINTER(TrustLevel), 'trustLevel'),
        ),
    ]


IID_IApplicationViewConsolidatedEventArgs = GUID(
    '{514449EC-7EA2-4DE7-A6A6-7DFBAAEBB6FB}'
)


class IApplicationViewConsolidatedEventArgs(IInspectable):
    _case_insensitive_ = True
    _iid_ = IID_IApplicationViewConsolidatedEventArgs
    _idlflags_ = []
    _methods_ = [
        COMMETHOD(
            [helpstring('Method get_IsUserInitiated')],
            HRESULT,
            'get_IsUserInitiated',
            (['retval', 'out'], POINTER(BOOL), 'value'),
        ),
    ]


IID_IApplicationView = GUID(
    '{D222D519-4361-451E-96C4-60F4F9742DB0}'
)


class IApplicationView(IInspectable):
    _case_insensitive_ = True
    _iid_ = IID_IApplicationView
    _idlflags_ = []
    _methods_ = [
        COMMETHOD(
            [helpstring('Method get_Orientation')],
            HRESULT,
            'get_Orientation',
            (['retval', 'out'], POINTER(ApplicationViewOrientation), 'value'),
        ),
        COMMETHOD(
            [helpstring('Method get_AdjacentToLeftDisplayEdge')],
            HRESULT,
            'get_AdjacentToLeftDisplayEdge',
            (['retval', 'out'], POINTER(BOOL), 'value'),
        ),
        COMMETHOD(
            [helpstring('Method get_AdjacentToRightDisplayEdge')],
            HRESULT,
            'get_AdjacentToRightDisplayEdge',
            (['retval', 'out'], POINTER(BOOL), 'value'),
        ),
        COMMETHOD(
            [helpstring('Method get_IsFullScreen')],
            HRESULT,
            'get_IsFullScreen',
            (['retval', 'out'], POINTER(BOOL), 'value'),
        ),
        COMMETHOD(
            [helpstring('Method get_IsOnLockScreen')],
            HRESULT,
            'get_IsOnLockScreen',
            (['retval', 'out'], POINTER(BOOL), 'value'),
        ),
        COMMETHOD(
            [helpstring('Method get_IsScreenCaptureEnabled')],
            HRESULT,
            'get_IsScreenCaptureEnabled',
            (['retval', 'out'], POINTER(BOOL), 'value'),
        ),
        COMMETHOD(
            [helpstring('Method put_IsScreenCaptureEnabled')],
            HRESULT,
            'put_IsScreenCaptureEnabled',
            (['in'], BOOL, 'value'),
        ),
        COMMETHOD(
            [helpstring('Method put_Title')],
            HRESULT,
            'put_Title',
            (['in'], HSTRING, 'value'),
        ),
        COMMETHOD(
            [helpstring('Method get_Title')],
            HRESULT,
            'get_Title',
            (['retval', 'out'], POINTER(HSTRING), 'value'),
        ),
        COMMETHOD(
            [helpstring('Method get_Id')],
            HRESULT,
            'get_Id',
            (['retval', 'out'], POINTER(INT32), 'value'),
        ),
        COMMETHOD(
            [helpstring('Method add_Consolidated')],
            HRESULT,
            'add_Consolidated',
            (['in'], POINTER(IApplicationViewConsolidatedEventArgs), 'handler'),
            (['retval', 'out'], POINTER(EventRegistrationToken), 'token'),
        ),
        COMMETHOD(
            [helpstring('Method remove_Consolidated')],
            HRESULT,
            'remove_Consolidated',
            (['in', ], EventRegistrationToken, 'EventRegistrationToken'),
        ),
    ]


IID_IServiceProvider = GUID(
    '{6D5140C1-7436-11CE-8034-00AA006009FA}'
)


class IServiceProvider(comtypes.IUnknown):
    _case_insensitive_ = True
    _idlflags_ = []
    _iid_ = IID_IServiceProvider
    _methods_ = [
        COMMETHOD(
            [helpstring('Method QueryService'), 'local', 'in'],
            HRESULT,
            'QueryService',
            (['in'], REFGUID, 'guidService'),
            (['in'], REFIID, 'riid'),
            (['out'], POINTER(LPVOID), 'ppvObject'),
        ),
    ]


IID_IObjectArray = GUID(
    "{92CA9DCD-5622-4BBA-A805-5E9F541BD8C9}"
)


class IObjectArray(comtypes.IUnknown):
    """
    Unknown Object Array
    """
    _case_insensitive_ = True
    _idlflags_ = []
    _iid_ = None

    _methods_ = [
        COMMETHOD(
            [helpstring('Method GetCount')],
            HRESULT,
            'GetCount',
            (['out'], POINTER(UINT), 'pcObjects'),
        ),
        COMMETHOD(
            [helpstring('Method GetAt')],
            HRESULT,
            'GetAt',
            (['in'], UINT, 'uiIndex'),
            (['in'], REFIID, 'riid'),
            (['out', 'iid_is'], POINTER(LPVOID), 'ppv'),
        ),
    ]


IID_IVirtualDesktop = GUID(
    '{FF72FFDD-BE7E-43FC-9C03-AD81681E88E4}'
)


class IVirtualDesktop(comtypes.IUnknown):
    _case_insensitive_ = True
    _iid_ = IID_IVirtualDesktop
    _idlflags_ = []
    _methods_ = [
        COMMETHOD(
            [helpstring('Method IsViewVisible')],
            HRESULT,
            'IsViewVisible',
            (['out'], POINTER(IApplicationView), 'pView'),
            (['out'], POINTER(INT), 'pfVisible'),
        ),
        COMMETHOD(
            [helpstring('Method GetID')],
            HRESULT,
            'GetID',
            (['out'], POINTER(GUID), 'pGuid'),
        )
    ]


IID_IVirtualDesktopManager = GUID(
    '{A5CD92FF-29BE-454C-8D04-D82879FB3F1B}'
)


class IVirtualDesktopManager(comtypes.IUnknown):
    _case_insensitive_ = True
    _iid_ = IID_IVirtualDesktopManager
    _idlflags_ = []
    _methods_ = [
        COMMETHOD(
            [helpstring('Method IsWindowOnCurrentVirtualDesktop')],
            HRESULT,
            'IsWindowOnCurrentVirtualDesktop',
            (['in'], HWND, 'topLevelWindow'),
            (['out'], POINTER(BOOL), 'onCurrentDesktop'),
        ),
        COMMETHOD(
            [helpstring('Method GetWindowDesktopId')],
            HRESULT,
            'GetWindowDesktopId',
            (['in'], HWND, 'topLevelWindow'),
            (['out'], POINTER(GUID), 'desktopId'),
        ),
        COMMETHOD(
            [helpstring('Method MoveWindowToDesktop')],
            HRESULT,
            'MoveWindowToDesktop',
            (['in'], HWND, 'topLevelWindow'),
            (['in'], REFGUID, 'desktopId'),
        ),
    ]


CLSID_VirtualDesktopManagerInternal = GUID(
    '{C5E0CDCA-7B6E-41B2-9FC4-D93975CC467B}'
)

IID_IVirtualDesktopManagerInternal = GUID(
    '{F31574D6-B682-4CDC-BD56-1827860ABEC6}'
)


# IID_IVirtualDesktopManagerInternal = GUID(
#     '{AF8DA486-95BB-4460-B3B7-6E7A6B2962B5}'
# )

# IID_IVirtualDesktopManagerInternal = GUID(
#     '{EF9F1A6C-D3CC-4358-B712-F84B635BEBE7}'
# )

class IVirtualDesktopManagerInternal(comtypes.IUnknown):
    _case_insensitive_ = True
    _iid_ = IID_IVirtualDesktopManagerInternal
    _idlflags_ = []
    _methods_ = [
        COMMETHOD(
            [helpstring('Method GetCount')],
            HRESULT,
            'GetCount',
            (['out'], POINTER(UINT), 'pCount'),
        ),
        COMMETHOD(
            [helpstring('Method MoveViewToDesktop')],
            HRESULT,
            'MoveViewToDesktop',
            (['out'], POINTER(IApplicationView), 'pView'),
            (['out'], POINTER(IVirtualDesktop), 'pDesktop'),
        ),
        COMMETHOD(
            [helpstring('Method CanViewMoveDesktops')],
            HRESULT,
            'CanViewMoveDesktops',
            (['out'], POINTER(IApplicationView), 'pView'),
            (['out'], POINTER(INT), 'pfCanViewMoveDesktops'),
        ),
        COMMETHOD(
            [helpstring('Method GetCurrentDesktop')],
            HRESULT,
            'GetCurrentDesktop',
            (['out'], POINTER(POINTER(IVirtualDesktop)), 'desktop'),
        ),
        COMMETHOD(
            [helpstring('Method GetDesktops')],
            HRESULT,
            'GetDesktops',
            (['out'], POINTER(POINTER(IObjectArray)), 'ppDesktops'),
        ),
        COMMETHOD(
            [helpstring('Method GetAdjacentDesktop')],
            HRESULT,
            'GetAdjacentDesktop',
            (['out'], POINTER(IVirtualDesktop), 'pDesktopReference'),
            (['in'], AdjacentDesktop, 'uDirection'),
            (['out'], POINTER(POINTER(IVirtualDesktop)), 'ppAdjacentDesktop'),
        ),
        COMMETHOD(
            [helpstring('Method SwitchDesktop')],
            HRESULT,
            'SwitchDesktop',
            (['in'], POINTER(IVirtualDesktop), 'pDesktop'),
        ),
        COMMETHOD(
            [helpstring('Method CreateDesktopW')],
            HRESULT,
            'CreateDesktopW',
            (['out'], POINTER(POINTER(IVirtualDesktop)), 'ppNewDesktop'),
        ),
        COMMETHOD(
            [helpstring('Method RemoveDesktop')],
            HRESULT,
            'RemoveDesktop',
            (['in'], POINTER(IVirtualDesktop), 'pRemove'),
            (['in'], POINTER(IVirtualDesktop), 'pFallbackDesktop'),
        ),
        COMMETHOD(
            [helpstring('Method FindDesktop')],
            HRESULT,
            'FindDesktop',
            (['in'], POINTER(GUID), 'desktopId'),
            (['out'], POINTER(POINTER(IVirtualDesktop)), 'ppDesktop'),
        ),
    ]


IID_IVirtualDesktopNotification = GUID(
    '{C179334C-4295-40D3-BEA1-C654D965605A}'
)


class IVirtualDesktopNotification(comtypes.IUnknown):
    _case_insensitive_ = True
    _iid_ = IID_IVirtualDesktopNotification
    _idlflags_ = []
    _methods_ = [
        COMMETHOD(
            [helpstring('Method VirtualDesktopCreated')],
            HRESULT,
            'VirtualDesktopCreated',
            (['in'], POINTER(IVirtualDesktop), 'pDesktop'),
        ),
        COMMETHOD(
            [helpstring('Method VirtualDesktopDestroyBegin')],
            HRESULT,
            'VirtualDesktopDestroyBegin',
            (['in'], POINTER(IVirtualDesktop), 'pDesktopDestroyed'),
            (['in'], POINTER(IVirtualDesktop), 'pDesktopFallback'),
        ),
        COMMETHOD(
            [helpstring('Method VirtualDesktopDestroyFailed')],
            HRESULT,
            'VirtualDesktopDestroyFailed',
            (['in'], POINTER(IVirtualDesktop), 'pDesktopDestroyed'),
            (['in'], POINTER(IVirtualDesktop), 'pDesktopFallback'),
        ),
        COMMETHOD(
            [helpstring('Method VirtualDesktopDestroyed')],
            HRESULT,
            'VirtualDesktopDestroyed',
            (['in'], POINTER(IVirtualDesktop), 'pDesktopDestroyed'),
            (['in'], POINTER(IVirtualDesktop), 'pDesktopFallback'),
        ),
        COMMETHOD(
            [helpstring('Method ViewVirtualDesktopChanged')],
            HRESULT,
            'ViewVirtualDesktopChanged',
            (['in'], POINTER(IApplicationView), 'pView'),
        ),
        COMMETHOD(
            [helpstring('Method CurrentVirtualDesktopChanged')],
            HRESULT,
            'CurrentVirtualDesktopChanged',
            (['in'], POINTER(IVirtualDesktop), 'pDesktopOld'),
            (['in'], POINTER(IVirtualDesktop), 'pDesktopNew'),
        ),
    ]


IID_IVirtualDesktopNotificationService = GUID('{0CD45E71-D927-4F15-8B0A-8FEF525337BF}')


class IVirtualDesktopNotificationService(comtypes.IUnknown):
    _case_insensitive_ = True
    _iid_ = IID_IVirtualDesktopNotificationService
    _idlflags_ = []
    _methods_ = [
        COMMETHOD(
            [helpstring('Method Register')],
            HRESULT,
            'Register',
            (['in'], POINTER(IVirtualDesktopNotification), 'pNotification'),
            (['out'], POINTER(DWORD), 'pdwCookie'),
        ),

        COMMETHOD(
            [helpstring('Method Unregister')],
            HRESULT,
            'Unregister',
            (['in'], DWORD, 'dwCookie'),
        ),
    ]


comtypes.CoInitialize()

pServiceProvider = comtypes.CoCreateInstance(
    CLSID_ImmersiveShell,
    IServiceProvider,
    comtypes.CLSCTX_LOCAL_SERVER,
)

pDesktopManagerInternal = comtypes.cast(
    pServiceProvider.QueryService(
        CLSID_VirtualDesktopManagerInternal,
        IID_IVirtualDesktopManagerInternal
    ),
    ctypes.POINTER(IVirtualDesktopManagerInternal)
)

pObjectArray = POINTER(IObjectArray)()

pDesktopManagerInternal.GetDesktops(ctypes.byref(pObjectArray))

count = UINT()
pObjectArray.GetCount(ctypes.byref(count))

for i in range(count):
    pDesktop = POINTER(IVirtualDesktop)()
    pObjectArray.GetAt(i, IID_IVirtualDesktop, ctypes.byref(pDesktop))

    id = GUID()
    pDesktop.GetID(ctypes.byref(id))

    print(id)
Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265
  • Thank you very much for this, I've made it work with [some modifications](https://github.com/DanEdens/Virtual_Desktops_Plugin/issues/1). – ipaleka Jul 25 '19 at 17:21
0

QueryInterface is the function that checks if a COM object has the wanted Interface and "cast" it to the new Interface

from the API, you can see that QueryInterface receives a GUID and as an output parameter of the new object pointer.

Baget
  • 3,318
  • 1
  • 24
  • 44
  • I edited my question; am I doing my calls wrong or it's just self-explainable with 'No such interface supported'? – ipaleka Jul 25 '19 at 11:52