pyvda 0.3.2__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyvda-0.3.2 → pyvda-0.4.0}/PKG-INFO +2 -2
- {pyvda-0.3.2 → pyvda-0.4.0}/README.md +1 -1
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda/__init__.py +2 -2
- pyvda-0.4.0/pyvda/_version.py +1 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda/com_defns.py +61 -6
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda.egg-info/PKG-INFO +2 -2
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda.egg-info/SOURCES.txt +3 -1
- {pyvda-0.3.2 → pyvda-0.4.0}/setup.py +7 -3
- pyvda-0.4.0/tests/test_desktop_functions.py +131 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/LICENSE.txt +0 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda/pyvda.py +0 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda/utils.py +0 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda/winstring.py +0 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda.egg-info/dependency_links.txt +0 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda.egg-info/requires.txt +0 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/pyvda.egg-info/top_level.txt +0 -0
- {pyvda-0.3.2 → pyvda-0.4.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyvda
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python implementation of the VirtualDesktopAccessor for manipulating Windows 10 virtual desktops.
|
|
5
5
|
Home-page: https://github.com/mrob95/py-VirtualDesktopAccessor
|
|
6
6
|
Author: Mike Roberts
|
|
@@ -14,7 +14,7 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
License-File: LICENSE.txt
|
|
15
15
|
|
|
16
16
|
# Python Virtual Desktop Accessor
|
|
17
|
-
Python module providing programmatic access to most of the settings accessed through the [Windows
|
|
17
|
+
Python module providing programmatic access to most of the settings accessed through the [Windows task view](https://en.wikipedia.org/wiki/Task_View).
|
|
18
18
|
Including switching virtual desktops, moving windows between virtual desktops, pinning windows and listing the windows on a desktop.
|
|
19
19
|
|
|
20
20
|
Originally based on https://github.com/Ciantic/VirtualDesktopAccessor.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Python Virtual Desktop Accessor
|
|
2
|
-
Python module providing programmatic access to most of the settings accessed through the [Windows
|
|
2
|
+
Python module providing programmatic access to most of the settings accessed through the [Windows task view](https://en.wikipedia.org/wiki/Task_View).
|
|
3
3
|
Including switching virtual desktops, moving windows between virtual desktops, pinning windows and listing the windows on a desktop.
|
|
4
4
|
|
|
5
5
|
Originally based on https://github.com/Ciantic/VirtualDesktopAccessor.
|
|
@@ -34,8 +34,6 @@ Example
|
|
|
34
34
|
AppView.current().pin()
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "0.3.2"
|
|
38
|
-
|
|
39
37
|
import platform
|
|
40
38
|
import os
|
|
41
39
|
|
|
@@ -62,3 +60,5 @@ from .pyvda import (
|
|
|
62
60
|
get_virtual_desktops,
|
|
63
61
|
set_wallpaper_for_all_desktops,
|
|
64
62
|
)
|
|
63
|
+
|
|
64
|
+
from ._version import __version__
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -34,18 +34,45 @@ from comtypes import (
|
|
|
34
34
|
)
|
|
35
35
|
from .winstring import HSTRING
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
def get_windows_build() -> int:
|
|
39
|
+
"""From cpython source:
|
|
40
|
+
|
|
41
|
+
The members are named: major, minor, build, platform, service_pack,
|
|
42
|
+
service_pack_major, service_pack_minor, suite_mask, product_type and
|
|
43
|
+
platform_version. For backward compatibility, only the first 5 items
|
|
44
|
+
are available by indexing. All elements are numbers, except
|
|
45
|
+
service_pack and platform_type which are strings, and platform_version
|
|
46
|
+
which is a 3-tuple. Platform is always 2. Product_type may be 1 for a
|
|
47
|
+
workstation, 2 for a domain controller, 3 for a server.
|
|
48
|
+
Platform_version is a 3-tuple containing a version number that is
|
|
49
|
+
intended for identifying the OS rather than feature detection.
|
|
50
|
+
|
|
51
|
+
In https://github.com/mrob95/pyvda/issues/11 we switched to using
|
|
52
|
+
`platform_version` for feature detection, but this is not reliable
|
|
53
|
+
on new versions of Windows 11.
|
|
54
|
+
"""
|
|
38
55
|
winver = sys.getwindowsversion()
|
|
39
|
-
|
|
56
|
+
build = winver.build
|
|
57
|
+
# dodgy workaround for https://github.com/mrob95/pyvda/issues/11
|
|
58
|
+
if build < 10000 and winver.platform_version[2] > 10000:
|
|
59
|
+
build = winver.platform_version[2]
|
|
60
|
+
return build
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if not os.getenv("READTHEDOCS"):
|
|
64
|
+
build = get_windows_build()
|
|
40
65
|
BUILD_OVER_20231 = build >= 20231
|
|
41
66
|
BUILD_OVER_21313 = build >= 21313
|
|
42
67
|
BUILD_OVER_22449 = build >= 22449
|
|
43
68
|
BUILD_OVER_22621 = build >= 22621
|
|
69
|
+
BUILD_OVER_22631 = build >= 22631
|
|
44
70
|
else:
|
|
45
71
|
BUILD_OVER_20231 = False
|
|
46
72
|
BUILD_OVER_21313 = False
|
|
47
73
|
BUILD_OVER_22449 = False
|
|
48
74
|
BUILD_OVER_22621 = False
|
|
75
|
+
BUILD_OVER_22631 = False
|
|
49
76
|
|
|
50
77
|
|
|
51
78
|
CLSID_ImmersiveShell = GUID("{C2F03A33-21F5-47FA-B4BB-156362A2F239}")
|
|
@@ -165,8 +192,9 @@ IApplicationView._methods_ = [
|
|
|
165
192
|
STDMETHOD(HRESULT, "GetPersistingStateName", (POINTER(PWSTR),)),
|
|
166
193
|
]
|
|
167
194
|
|
|
168
|
-
|
|
169
|
-
|
|
195
|
+
if BUILD_OVER_22631:
|
|
196
|
+
GUID_IVirtualDesktop = GUID("{3F07F4BE-B107-441A-AF0F-39D82529072C}")
|
|
197
|
+
elif BUILD_OVER_22621:
|
|
170
198
|
GUID_IVirtualDesktop = GUID("{3F07F4BE-B107-441A-AF0F-39D82529072C}")
|
|
171
199
|
elif BUILD_OVER_21313:
|
|
172
200
|
GUID_IVirtualDesktop = GUID("{536D3495-B208-4CC9-AE26-DE8111275BF8}")
|
|
@@ -211,8 +239,9 @@ class IVirtualDesktop2(IUnknown):
|
|
|
211
239
|
]
|
|
212
240
|
|
|
213
241
|
|
|
214
|
-
|
|
215
|
-
|
|
242
|
+
if BUILD_OVER_22631:
|
|
243
|
+
GUID_IVirtualDesktopManagerInternal = GUID("{4970BA3D-FD4E-4647-BEA3-D89076EF4B9C}")
|
|
244
|
+
elif BUILD_OVER_22621:
|
|
216
245
|
GUID_IVirtualDesktopManagerInternal = GUID("{A3175F2D-239C-4BD2-8AA0-EEBA8B0B138E}")
|
|
217
246
|
elif BUILD_OVER_21313:
|
|
218
247
|
GUID_IVirtualDesktopManagerInternal = GUID("{B2F925B9-5A0F-4D2E-9F4D-2B1507593C10}")
|
|
@@ -224,6 +253,32 @@ else:
|
|
|
224
253
|
# HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{F31574D6-B682-4CDC-BD56-1827860ABEC6}
|
|
225
254
|
class IVirtualDesktopManagerInternal(IUnknown):
|
|
226
255
|
_iid_ = GUID_IVirtualDesktopManagerInternal
|
|
256
|
+
if BUILD_OVER_22631:
|
|
257
|
+
_methods_ = [
|
|
258
|
+
COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(UINT), "pCount"),),
|
|
259
|
+
STDMETHOD(HRESULT, "MoveViewToDesktop", (POINTER(IApplicationView), POINTER(IVirtualDesktop))),
|
|
260
|
+
STDMETHOD(HRESULT, "CanViewMoveDesktops", (POINTER(IApplicationView), POINTER(UINT))),
|
|
261
|
+
COMMETHOD([], HRESULT, "GetCurrentDesktop", (["out"], POINTER(POINTER(IVirtualDesktop)), "pDesktop"),),
|
|
262
|
+
COMMETHOD([], HRESULT, "GetDesktops", (["out"], POINTER(POINTER(IObjectArray)), "array")),
|
|
263
|
+
STDMETHOD(HRESULT, "GetAdjacentDesktop", (POINTER(IVirtualDesktop), AdjacentDesktop, POINTER(POINTER(IVirtualDesktop)),)),
|
|
264
|
+
STDMETHOD(HRESULT, "SwitchDesktop", (POINTER(IVirtualDesktop),)),
|
|
265
|
+
STDMETHOD(HRESULT, "Unknown1", (POINTER(IVirtualDesktop),)),
|
|
266
|
+
COMMETHOD([], HRESULT, "CreateDesktopW", (["out"], POINTER(POINTER(IVirtualDesktop)), "pDesktop"),),
|
|
267
|
+
STDMETHOD(HRESULT, "MoveDesktop", (POINTER(IVirtualDesktop), HWND, INT)),
|
|
268
|
+
COMMETHOD([], HRESULT, "RemoveDesktop", (["in"], POINTER(IVirtualDesktop), "destroyDesktop"), (["in"], POINTER(IVirtualDesktop), "fallbackDesktop")),
|
|
269
|
+
COMMETHOD([], HRESULT, "FindDesktop", (["in"], POINTER(GUID), "pGuid"), (["out"], POINTER(POINTER(IVirtualDesktop)), "pDesktop")),
|
|
270
|
+
STDMETHOD(HRESULT, "Unknown2", (POINTER(IVirtualDesktop), POINTER(POINTER(IObjectArray)), POINTER(POINTER(IObjectArray)))),
|
|
271
|
+
COMMETHOD([], HRESULT, "SetName", (["in"], POINTER(IVirtualDesktop), "pDesktop"), (["in"], HSTRING, "name")),
|
|
272
|
+
COMMETHOD([], HRESULT, "SetWallpaper", (["in"], POINTER(IVirtualDesktop), "pDesktop"), (["in"], HSTRING, "path")),
|
|
273
|
+
COMMETHOD([], HRESULT, "SetWallpaperForAllDesktops", (["in"], HSTRING, "path")),
|
|
274
|
+
COMMETHOD([], HRESULT, "CopyDesktopState", (["in"], POINTER(IApplicationView), "pView0"), (["in"], POINTER(IApplicationView), "pView0")),
|
|
275
|
+
|
|
276
|
+
COMMETHOD([], HRESULT, "Unknown3", (["in"], HSTRING, "a1"), (["out"], POINTER(POINTER(IVirtualDesktop)), "out")),
|
|
277
|
+
STDMETHOD(HRESULT, "pDesktop", (POINTER(IVirtualDesktop),)),
|
|
278
|
+
STDMETHOD(HRESULT, "Unknown5", (POINTER(IVirtualDesktop),)),
|
|
279
|
+
COMMETHOD([], HRESULT, "Unknown6", (["out"], POINTER(POINTER(IVirtualDesktop)), "pDesktop"),),
|
|
280
|
+
STDMETHOD(HRESULT, "Unknown7"),
|
|
281
|
+
]
|
|
227
282
|
if BUILD_OVER_22621:
|
|
228
283
|
_methods_ = [
|
|
229
284
|
COMMETHOD([], HRESULT, "GetCount", (["out"], POINTER(UINT), "pCount"),),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyvda
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Python implementation of the VirtualDesktopAccessor for manipulating Windows 10 virtual desktops.
|
|
5
5
|
Home-page: https://github.com/mrob95/py-VirtualDesktopAccessor
|
|
6
6
|
Author: Mike Roberts
|
|
@@ -14,7 +14,7 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
License-File: LICENSE.txt
|
|
15
15
|
|
|
16
16
|
# Python Virtual Desktop Accessor
|
|
17
|
-
Python module providing programmatic access to most of the settings accessed through the [Windows
|
|
17
|
+
Python module providing programmatic access to most of the settings accessed through the [Windows task view](https://en.wikipedia.org/wiki/Task_View).
|
|
18
18
|
Including switching virtual desktops, moving windows between virtual desktops, pinning windows and listing the windows on a desktop.
|
|
19
19
|
|
|
20
20
|
Originally based on https://github.com/Ciantic/VirtualDesktopAccessor.
|
|
@@ -2,6 +2,7 @@ LICENSE.txt
|
|
|
2
2
|
README.md
|
|
3
3
|
setup.py
|
|
4
4
|
pyvda/__init__.py
|
|
5
|
+
pyvda/_version.py
|
|
5
6
|
pyvda/com_defns.py
|
|
6
7
|
pyvda/pyvda.py
|
|
7
8
|
pyvda/utils.py
|
|
@@ -10,4 +11,5 @@ pyvda.egg-info/PKG-INFO
|
|
|
10
11
|
pyvda.egg-info/SOURCES.txt
|
|
11
12
|
pyvda.egg-info/dependency_links.txt
|
|
12
13
|
pyvda.egg-info/requires.txt
|
|
13
|
-
pyvda.egg-info/top_level.txt
|
|
14
|
+
pyvda.egg-info/top_level.txt
|
|
15
|
+
tests/test_desktop_functions.py
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
from setuptools import setup, find_packages
|
|
2
2
|
import os
|
|
3
|
-
import pyvda
|
|
4
3
|
|
|
5
4
|
def read(*names):
|
|
6
5
|
return open(os.path.join(os.path.dirname(__file__), *names)).read()
|
|
7
6
|
|
|
7
|
+
# done this way to avoid importing the package during setup
|
|
8
|
+
def get_version():
|
|
9
|
+
contents = read("pyvda", "_version.py").strip()
|
|
10
|
+
version = contents.rpartition(" ")[2].strip('"')
|
|
11
|
+
return version
|
|
12
|
+
|
|
8
13
|
setup(
|
|
9
14
|
name="pyvda",
|
|
10
|
-
version=
|
|
15
|
+
version=get_version(),
|
|
11
16
|
description="Python implementation of the VirtualDesktopAccessor for manipulating Windows 10 virtual desktops.",
|
|
12
17
|
author="Mike Roberts",
|
|
13
18
|
author_email="mike.roberts.2k10@googlemail.com",
|
|
@@ -24,4 +29,3 @@ setup(
|
|
|
24
29
|
"Programming Language :: Python :: 3.7",
|
|
25
30
|
],
|
|
26
31
|
)
|
|
27
|
-
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from comtypes import CoInitializeEx, COINIT_MULTITHREADED
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from pyvda import (
|
|
4
|
+
AppView,
|
|
5
|
+
VirtualDesktop,
|
|
6
|
+
get_virtual_desktops,
|
|
7
|
+
get_apps_by_z_order,
|
|
8
|
+
)
|
|
9
|
+
import time
|
|
10
|
+
import win32gui
|
|
11
|
+
import threading
|
|
12
|
+
|
|
13
|
+
current_window = AppView.current()
|
|
14
|
+
current_desktop = VirtualDesktop.current()
|
|
15
|
+
|
|
16
|
+
def test_move_and_go():
|
|
17
|
+
current_window.move(VirtualDesktop(1))
|
|
18
|
+
|
|
19
|
+
VirtualDesktop(1).go()
|
|
20
|
+
current_desktop_number = VirtualDesktop.current().number
|
|
21
|
+
assert current_desktop_number == 1, f"Wanted 1, got {current_desktop_number}"
|
|
22
|
+
|
|
23
|
+
current_window_desktop_number = current_window.desktop.number
|
|
24
|
+
assert current_window_desktop_number == 1, f"Wanted 1, got {current_window_desktop_number}"
|
|
25
|
+
|
|
26
|
+
current_window.move(current_desktop)
|
|
27
|
+
current_desktop.go()
|
|
28
|
+
|
|
29
|
+
def test_pin_app():
|
|
30
|
+
assert not current_window.is_app_pinned()
|
|
31
|
+
current_window.pin_app()
|
|
32
|
+
assert current_window.is_app_pinned()
|
|
33
|
+
current_window.unpin_app()
|
|
34
|
+
assert not current_window.is_app_pinned()
|
|
35
|
+
|
|
36
|
+
def test_pin_window():
|
|
37
|
+
assert not current_window.is_pinned()
|
|
38
|
+
current_window.pin()
|
|
39
|
+
assert current_window.is_pinned()
|
|
40
|
+
current_window.unpin()
|
|
41
|
+
assert not current_window.is_pinned()
|
|
42
|
+
|
|
43
|
+
def test_z_order():
|
|
44
|
+
apps = get_apps_by_z_order()
|
|
45
|
+
assert apps[0] == AppView.current()
|
|
46
|
+
|
|
47
|
+
assert len(get_apps_by_z_order(False, False)) > len(apps)
|
|
48
|
+
|
|
49
|
+
def test_switch_focus():
|
|
50
|
+
apps = get_apps_by_z_order()
|
|
51
|
+
if len(apps) == 1:
|
|
52
|
+
raise Exception("For testing purposes, open another window!")
|
|
53
|
+
|
|
54
|
+
ts = apps[0].get_activation_timestamp()
|
|
55
|
+
|
|
56
|
+
apps[1].set_focus()
|
|
57
|
+
time.sleep(1)
|
|
58
|
+
assert AppView.current() == apps[1]
|
|
59
|
+
apps[0].switch_to()
|
|
60
|
+
time.sleep(1)
|
|
61
|
+
assert AppView.current() == apps[0]
|
|
62
|
+
|
|
63
|
+
assert apps[0].get_activation_timestamp() > ts
|
|
64
|
+
|
|
65
|
+
def test_visibility():
|
|
66
|
+
cur = AppView.current()
|
|
67
|
+
assert cur.is_shown_in_switchers()
|
|
68
|
+
assert cur.is_visible()
|
|
69
|
+
|
|
70
|
+
def test_current():
|
|
71
|
+
count = len(get_virtual_desktops())
|
|
72
|
+
current = VirtualDesktop.current().number
|
|
73
|
+
assert 0 < current <= count, f"Current desktop number {current} is outside of expected range 0-{count}"
|
|
74
|
+
|
|
75
|
+
hwnd = win32gui.GetForegroundWindow()
|
|
76
|
+
assert AppView(hwnd) == AppView.current()
|
|
77
|
+
assert AppView(hwnd).is_on_current_desktop()
|
|
78
|
+
|
|
79
|
+
def test_create_and_remove_desktop():
|
|
80
|
+
old_count = len(get_virtual_desktops())
|
|
81
|
+
new = VirtualDesktop.create()
|
|
82
|
+
new_count = len(get_virtual_desktops())
|
|
83
|
+
expected_count = old_count + 1
|
|
84
|
+
assert new_count == expected_count, f"Wanted {expected_count}, got {new_count}"
|
|
85
|
+
new.go()
|
|
86
|
+
|
|
87
|
+
new.remove(fallback=VirtualDesktop(1))
|
|
88
|
+
new_count = len(get_virtual_desktops())
|
|
89
|
+
assert new_count == old_count, f"Wanted {new_count}, got {new_count}"
|
|
90
|
+
fellback = VirtualDesktop.current().number
|
|
91
|
+
assert fellback == 1, f"Wanted 1, got {fellback}"
|
|
92
|
+
|
|
93
|
+
time.sleep(1) # Got to wait for the animation before we can return
|
|
94
|
+
current_desktop.go()
|
|
95
|
+
|
|
96
|
+
def test_desktop_names():
|
|
97
|
+
current_name = current_desktop.name
|
|
98
|
+
test_name = "pyvda testing"
|
|
99
|
+
current_desktop.rename(test_name)
|
|
100
|
+
assert current_desktop.name == test_name, f"Wanted '{test_name}', got '{current_desktop.name}'"
|
|
101
|
+
current_desktop.rename(current_name)
|
|
102
|
+
assert current_desktop.name == current_name, f"Wanted '{current_name}', got '{current_desktop.name}'"
|
|
103
|
+
|
|
104
|
+
def test_move_and_go_threads():
|
|
105
|
+
error: Optional[Exception] = None
|
|
106
|
+
def f():
|
|
107
|
+
nonlocal error
|
|
108
|
+
try:
|
|
109
|
+
test_move_and_go()
|
|
110
|
+
except Exception as e:
|
|
111
|
+
error = e
|
|
112
|
+
t = threading.Thread(target=f)
|
|
113
|
+
t.start()
|
|
114
|
+
t.join()
|
|
115
|
+
if error is not None:
|
|
116
|
+
raise error
|
|
117
|
+
|
|
118
|
+
def test_initialisation_with_com_mta():
|
|
119
|
+
error: Optional[Exception] = None
|
|
120
|
+
def f():
|
|
121
|
+
nonlocal error
|
|
122
|
+
try:
|
|
123
|
+
CoInitializeEx(COINIT_MULTITHREADED)
|
|
124
|
+
test_move_and_go()
|
|
125
|
+
except Exception as e:
|
|
126
|
+
error = e
|
|
127
|
+
t = threading.Thread(target=f)
|
|
128
|
+
t.start()
|
|
129
|
+
t.join()
|
|
130
|
+
if error is not None:
|
|
131
|
+
raise error
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|