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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyvda
3
- Version: 0.3.2
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 10 task view](https://en.wikipedia.org/wiki/Task_View).
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 10 task view](https://en.wikipedia.org/wiki/Task_View).
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
- if not os.getenv("READTHEDOCS"):
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
- major, minor, build = winver.platform_version or winver[:3]
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
- # No change for 22449
169
- if BUILD_OVER_22621:
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
- # Same GUID for 22449
215
- if BUILD_OVER_22621:
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.2
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 10 task view](https://en.wikipedia.org/wiki/Task_View).
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=pyvda.__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