casement 0.4.0__py3-none-any.whl
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.
- casement/__init__.py +2 -0
- casement/__main__.py +16 -0
- casement/app_id.py +88 -0
- casement/cli.py +95 -0
- casement/enums.py +22 -0
- casement/env_var.py +154 -0
- casement/registry.py +272 -0
- casement/shortcut.py +794 -0
- casement/utils.py +26 -0
- casement/version.py +34 -0
- casement-0.4.0.dist-info/METADATA +141 -0
- casement-0.4.0.dist-info/RECORD +16 -0
- casement-0.4.0.dist-info/WHEEL +5 -0
- casement-0.4.0.dist-info/entry_points.txt +2 -0
- casement-0.4.0.dist-info/licenses/LICENSE +165 -0
- casement-0.4.0.dist-info/top_level.txt +1 -0
casement/__init__.py
ADDED
casement/__main__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
""" Enables support for calling the casement cli using `python -m casement`
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
from __future__ import absolute_import
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import casement.cli
|
|
9
|
+
|
|
10
|
+
if __name__ == '__main__':
|
|
11
|
+
# TODO: move casement cli to click
|
|
12
|
+
# prog_name prevents __main__.py from being shown as the command name in the help
|
|
13
|
+
# text. We don't know the exact command the user passed so we provide a generic
|
|
14
|
+
# `python -m casement` command.
|
|
15
|
+
# sys.exit(casement.cli.main(prog_name="python -m casement"))
|
|
16
|
+
sys.exit(casement.cli.main())
|
casement/app_id.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import os
|
|
3
|
+
from ctypes import wintypes
|
|
4
|
+
|
|
5
|
+
import pythoncom
|
|
6
|
+
from win32com.propsys import propsys
|
|
7
|
+
from win32com.shell import shellcon
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AppId(object):
|
|
11
|
+
@staticmethod
|
|
12
|
+
def for_shortcut(shortcut):
|
|
13
|
+
"""Gets the AppUserModel.ID for the given shortcut.
|
|
14
|
+
|
|
15
|
+
This will allow windows to group windows with the same app id on a shortcut
|
|
16
|
+
pinned to the taskbar. Use :py:meth:`AppId.for_application` to set the
|
|
17
|
+
app id for a running application.
|
|
18
|
+
"""
|
|
19
|
+
if os.path.exists(shortcut):
|
|
20
|
+
# These imports won't work inside python 2 DCC's
|
|
21
|
+
from win32com.propsys import propsys
|
|
22
|
+
|
|
23
|
+
# Original info from https://stackoverflow.com/a/61714895
|
|
24
|
+
key = propsys.PSGetPropertyKeyFromName("System.AppUserModel.ID")
|
|
25
|
+
store = propsys.SHGetPropertyStoreFromParsingName(shortcut)
|
|
26
|
+
return store.GetValue(key).GetValue()
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def set_for_shortcut(shortcut, app_id):
|
|
30
|
+
"""Sets AppUserModel.ID info for a windows shortcut.
|
|
31
|
+
|
|
32
|
+
Note: This doesn't seem to work on a pinned taskbar shortcut. Set it on
|
|
33
|
+
a desktop shortcut then pin that shortcut.
|
|
34
|
+
|
|
35
|
+
This will allow windows to group windows with the same app id on a shortcut
|
|
36
|
+
pinned to the taskbar. Use :py:meth:`AppId.set_for_application` to set
|
|
37
|
+
the app id for a running application.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
shortcut (str): The .lnk filename to set the app id on.
|
|
41
|
+
app_id (str): The app id to set on the shortcut
|
|
42
|
+
"""
|
|
43
|
+
if os.path.exists(shortcut):
|
|
44
|
+
# Original info from https://stackoverflow.com/a/61714895
|
|
45
|
+
key = propsys.PSGetPropertyKeyFromName("System.AppUserModel.ID")
|
|
46
|
+
store = propsys.SHGetPropertyStoreFromParsingName(
|
|
47
|
+
shortcut, None, shellcon.GPS_READWRITE, propsys.IID_IPropertyStore
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
new_value = propsys.PROPVARIANTType(app_id, pythoncom.VT_BSTR)
|
|
51
|
+
store.SetValue(key, new_value)
|
|
52
|
+
store.Commit()
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def for_application():
|
|
56
|
+
"""Returns the current Windows 7+ app user model id used for taskbar
|
|
57
|
+
grouping."""
|
|
58
|
+
|
|
59
|
+
lp_buffer = wintypes.LPWSTR()
|
|
60
|
+
app_user_model_id = (
|
|
61
|
+
ctypes.windll.shell32.GetCurrentProcessExplicitAppUserModelID
|
|
62
|
+
)
|
|
63
|
+
app_user_model_id(ctypes.cast(ctypes.byref(lp_buffer), wintypes.LPWSTR))
|
|
64
|
+
appid = lp_buffer.value
|
|
65
|
+
ctypes.windll.kernel32.LocalFree(lp_buffer)
|
|
66
|
+
return appid
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def set_for_application(appid, prefix=None):
|
|
70
|
+
"""Controls Windows taskbar grouping.
|
|
71
|
+
|
|
72
|
+
Specifies a Explicit App User Model ID that Windows 7+ uses to control
|
|
73
|
+
grouping of windows on the taskbar. This must be set before any ui is
|
|
74
|
+
displayed. The best place to call it is in the first widget to be displayed
|
|
75
|
+
__init__ method.
|
|
76
|
+
|
|
77
|
+
See :py:meth:`AppId.set_for_shortcut` to set the app id on a windows shortcut.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
appid (str): The id of the application. Should use full camel-case.
|
|
81
|
+
http://msdn.microsoft.com/en-us/library/dd378459%28v=vs.85%29.aspx#how
|
|
82
|
+
prefix (str, optional): The prefix attached to the id.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
# https://stackoverflow.com/a/27872625
|
|
86
|
+
if prefix:
|
|
87
|
+
appid = u'%s.%s' % (prefix, appid)
|
|
88
|
+
return not ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
|
casement/cli.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from argparse import ArgumentParser
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CasementParser(object):
|
|
8
|
+
'''
|
|
9
|
+
To implement a cli for casement, create a submodule with the name of your
|
|
10
|
+
cli command. For example to add a ``casement test echo`` command, you would
|
|
11
|
+
create casement\test.py with the following code::
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
from argparse import ArgumentParser
|
|
15
|
+
|
|
16
|
+
class _CLI(object):
|
|
17
|
+
def __init__(self, args):
|
|
18
|
+
# Parse the command and create the parser casement will use
|
|
19
|
+
parser = ArgumentParser()
|
|
20
|
+
parser.add_argument('command')
|
|
21
|
+
args = parser.parse_args(args)
|
|
22
|
+
# This will be added to args passed to the run command
|
|
23
|
+
self.command = args.command
|
|
24
|
+
# Create self.parser by calling the requested function
|
|
25
|
+
getattr(self, args.command)()
|
|
26
|
+
|
|
27
|
+
def echo(self):
|
|
28
|
+
"""Creates a ArgumentParser to handle the echo command as self.parser"""
|
|
29
|
+
self.parser = ArgumentParser(
|
|
30
|
+
usage='casement shortcut test [-h] [-v] text',
|
|
31
|
+
)
|
|
32
|
+
self.parser.add_argument('text', nargs=argparse.REMAINDER)
|
|
33
|
+
|
|
34
|
+
def run(self, args):
|
|
35
|
+
"""Do something with the arguments parsed from self.parser"""
|
|
36
|
+
if args.command == 'echo':
|
|
37
|
+
print('ECHO: {}'.format(' '.join(args.text)))
|
|
38
|
+
'''
|
|
39
|
+
|
|
40
|
+
def __init__(self):
|
|
41
|
+
self.target_cli = None
|
|
42
|
+
self._base_parser = ArgumentParser(
|
|
43
|
+
description='Windows management tool',
|
|
44
|
+
usage="casement <command> [<args>]\n\n"
|
|
45
|
+
"Valid commands are:\n"
|
|
46
|
+
" shortcut or sc: Manage windows shortcuts.",
|
|
47
|
+
)
|
|
48
|
+
self._base_parser.add_argument('command', help='Command to run')
|
|
49
|
+
args = self._base_parser.parse_args(sys.argv[1:2])
|
|
50
|
+
|
|
51
|
+
# Convert short command names to full command names
|
|
52
|
+
short_names = {
|
|
53
|
+
'sc': 'shortcut',
|
|
54
|
+
}
|
|
55
|
+
self.command = short_names.get(args.command, args.command)
|
|
56
|
+
# use dispatch pattern to invoke method with same name
|
|
57
|
+
try:
|
|
58
|
+
module = importlib.import_module(
|
|
59
|
+
'.{}'.format(self.command), package='casement'
|
|
60
|
+
)
|
|
61
|
+
except ImportError:
|
|
62
|
+
self._base_parser.print_help()
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
# parse_args defaults to [1:] for args, but we need to remove
|
|
65
|
+
# the 2 levels of commands for proper validation
|
|
66
|
+
self.args_list = sys.argv[3:]
|
|
67
|
+
|
|
68
|
+
self.target_cli = module._CLI(sys.argv[2:3])
|
|
69
|
+
for action in self.target_cli.parser._actions:
|
|
70
|
+
if action.dest == 'verbose':
|
|
71
|
+
break
|
|
72
|
+
else:
|
|
73
|
+
self.target_cli.parser.add_argument(
|
|
74
|
+
'-v', '--verbose', action='store_true', help='Give more output.'
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def parse_args(self):
|
|
78
|
+
"""Parse sub-command arguments adding global options."""
|
|
79
|
+
args = self.target_cli.parser.parse_args(self.args_list)
|
|
80
|
+
# Add command to the args from the target_cli
|
|
81
|
+
try:
|
|
82
|
+
args.command = self.target_cli.command
|
|
83
|
+
except AttributeError:
|
|
84
|
+
args.command = None
|
|
85
|
+
return args
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def main():
|
|
89
|
+
casement = CasementParser()
|
|
90
|
+
args = casement.parse_args()
|
|
91
|
+
logging.basicConfig(
|
|
92
|
+
format='%(message)s', level=logging.DEBUG if args.verbose else logging.INFO
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
casement.target_cli.run(args)
|
casement/enums.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FlashTimes(Enum):
|
|
5
|
+
"""Windows flags for casement.utils.flashWindow().
|
|
6
|
+
|
|
7
|
+
https://docs.microsoft.com/en-us/windows/desktop/api/winuser/ns-winuser-flashwinfo
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
"'Stop flashing. The system restores the window to its original state.'"
|
|
11
|
+
FLASHW_STOP = 0
|
|
12
|
+
"Flash the window caption."
|
|
13
|
+
FLASHW_CAPTION = 0x00000001
|
|
14
|
+
"Flash the taskbar button."
|
|
15
|
+
FLASHW_TRAY = 0x00000002
|
|
16
|
+
"""Flash both the window caption and taskbar button. This is equivalent
|
|
17
|
+
to setting the ``FLASHW_CAPTION | FLASHW_TRAY flags``."""
|
|
18
|
+
FLASHW_ALL = 0x00000003
|
|
19
|
+
"Flash continuously, until the ``FLASHW_STOP`` flag is set."
|
|
20
|
+
FLASHW_TIMER = 0x00000004
|
|
21
|
+
"Flash continuously until the window comes to the foreground."
|
|
22
|
+
FLASHW_TIMERNOFG = 0x0000000C
|
casement/env_var.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
import win32api
|
|
6
|
+
import win32con
|
|
7
|
+
import win32file
|
|
8
|
+
import win32gui
|
|
9
|
+
from six.moves import winreg
|
|
10
|
+
from six.moves.collections_abc import MutableMapping
|
|
11
|
+
|
|
12
|
+
from .registry import REG_LOCATIONS, RegKey
|
|
13
|
+
|
|
14
|
+
_logger_modify = logging.getLogger(__name__ + ".modify")
|
|
15
|
+
_logger_broadcast = logging.getLogger(__name__ + ".broadcast")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EnvVar(MutableMapping):
|
|
19
|
+
"""A dictionary like mapping of environment variables as defined in the
|
|
20
|
+
registry. Stored environment variables are not expanded by default. You can
|
|
21
|
+
use `normalize_path` for various path manipulation like expanding variables
|
|
22
|
+
and dealing with short/long file paths.
|
|
23
|
+
|
|
24
|
+
Arguments:
|
|
25
|
+
system (bool, optional): Controls if this instance modifies system or
|
|
26
|
+
user environment variables.
|
|
27
|
+
|
|
28
|
+
Every time you set or delete a environment variable it will send the
|
|
29
|
+
`WM_SETTINGCHANGE` message notifying other applications that system-wide
|
|
30
|
+
settings have changed.
|
|
31
|
+
See: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-settingchange
|
|
32
|
+
|
|
33
|
+
You can prevent this message from being sent every time you modify env vars
|
|
34
|
+
by using the `EnvVar.delayed_broadcast()` with context:
|
|
35
|
+
|
|
36
|
+
user_env = EnvVar(system=False)
|
|
37
|
+
with EnvVar.delayed_broadcast():
|
|
38
|
+
user_env['VAR_B'] = 'value b'
|
|
39
|
+
user_env['VAR_C'] = 'value c'
|
|
40
|
+
del user_env['VAR_A']
|
|
41
|
+
|
|
42
|
+
By using `EnvVar.delayed_broadcast()` broadcast will only be called once,
|
|
43
|
+
instead of three times.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# These class variables are used to control if broadcast sends its message
|
|
47
|
+
_broadcast_enabled = True
|
|
48
|
+
_broadcast_required = False
|
|
49
|
+
|
|
50
|
+
def __init__(self, system=False):
|
|
51
|
+
key, sub_key = REG_LOCATIONS["system" if system else "user"]['env_var']
|
|
52
|
+
self.__reg__ = RegKey(key, sub_key)
|
|
53
|
+
|
|
54
|
+
def __delitem__(self, key):
|
|
55
|
+
entry = self.__reg__.entry(key)
|
|
56
|
+
with self.delayed_broadcast():
|
|
57
|
+
_logger_modify.debug('Deleting env var: "{}"'.format(key))
|
|
58
|
+
entry.delete()
|
|
59
|
+
type(self)._broadcast_required = True
|
|
60
|
+
|
|
61
|
+
def __getitem__(self, key):
|
|
62
|
+
entry = self.__reg__.entry(key)
|
|
63
|
+
try:
|
|
64
|
+
return entry.value()
|
|
65
|
+
except OSError:
|
|
66
|
+
raise KeyError(key)
|
|
67
|
+
|
|
68
|
+
def __iter__(self):
|
|
69
|
+
return iter(self.__reg__.entry_names())
|
|
70
|
+
|
|
71
|
+
def __len__(self):
|
|
72
|
+
reg_key = self.__reg__._key()
|
|
73
|
+
if not reg_key:
|
|
74
|
+
return 0
|
|
75
|
+
|
|
76
|
+
_, count, _ = winreg.QueryInfoKey(reg_key)
|
|
77
|
+
return count
|
|
78
|
+
|
|
79
|
+
def __setitem__(self, key, value, value_type=winreg.REG_EXPAND_SZ):
|
|
80
|
+
entry = self.__reg__.entry(key)
|
|
81
|
+
with self.delayed_broadcast():
|
|
82
|
+
_logger_modify.debug('Setting env var: "{}" to "{}"'.format(key, value))
|
|
83
|
+
entry.set(value, value_type=value_type)
|
|
84
|
+
type(self)._broadcast_required = True
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def broadcast(cls):
|
|
88
|
+
"""Notify the system about the changes. Uses SendMessageTimeout to avoid
|
|
89
|
+
hanging if applications fail to respond to the broadcast.
|
|
90
|
+
"""
|
|
91
|
+
if cls._broadcast_enabled is False:
|
|
92
|
+
_logger_broadcast.debug(
|
|
93
|
+
'Skipping broadcasting that the environment was changed'
|
|
94
|
+
)
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
_logger_broadcast.debug('Broadcasting that the environment was changed')
|
|
98
|
+
win32gui.SendMessageTimeout(
|
|
99
|
+
win32con.HWND_BROADCAST,
|
|
100
|
+
win32con.WM_SETTINGCHANGE,
|
|
101
|
+
0,
|
|
102
|
+
'Environment',
|
|
103
|
+
win32con.SMTO_ABORTIFHUNG,
|
|
104
|
+
1000,
|
|
105
|
+
)
|
|
106
|
+
# Reset the needs broadcast variable now that we have broadcast
|
|
107
|
+
cls._broadcast_required = False
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
@contextmanager
|
|
111
|
+
def delayed_broadcast(cls):
|
|
112
|
+
"""Context manager that disables broadcast calls for each set/delete of
|
|
113
|
+
environment variables. Only if any set/delete calls were made inside the
|
|
114
|
+
with context, will it call broadcast.
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
current = cls._broadcast_enabled
|
|
118
|
+
cls._broadcast_enabled = False
|
|
119
|
+
yield
|
|
120
|
+
finally:
|
|
121
|
+
cls._broadcast_enabled = current
|
|
122
|
+
if cls._broadcast_required:
|
|
123
|
+
cls.broadcast()
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def normalize_path(cls, path, expandvars=True, tilde=None, normpath=True):
|
|
127
|
+
"""Normalize a path handling short/long names, env vars and normpath.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
path (str): Input path to expand.
|
|
131
|
+
expandvars (bool, optional): Expand environment variables.
|
|
132
|
+
tilde (bool, optional): Converts windows short paths(`C:\\PROGRA~1`).
|
|
133
|
+
If True, short paths are expanded to full file paths. If False
|
|
134
|
+
file paths are converted to short paths. Nothing is done if None.
|
|
135
|
+
normpath (bool, optional): If True calls os.path.normpath.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
str: Input path with any tilde-shortenings expanded.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
pywintypes.error: Raised if path doesn't exist when tilde is not None.
|
|
142
|
+
"""
|
|
143
|
+
if tilde:
|
|
144
|
+
path = win32api.GetShortPathName(path)
|
|
145
|
+
elif tilde is False:
|
|
146
|
+
path = win32file.GetLongPathName(path)
|
|
147
|
+
|
|
148
|
+
if expandvars:
|
|
149
|
+
path = os.path.expandvars(path)
|
|
150
|
+
|
|
151
|
+
if normpath:
|
|
152
|
+
path = os.path.normpath(path)
|
|
153
|
+
|
|
154
|
+
return path
|
casement/registry.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""
|
|
2
|
+
key: Nodes in tree
|
|
3
|
+
Each node in the tree is called a key.
|
|
4
|
+
Each key can contain both subkeys and data entries called values.
|
|
5
|
+
"""
|
|
6
|
+
import six
|
|
7
|
+
from six.moves import winreg
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _generate_key_map():
|
|
11
|
+
"""Creates a map of strings to winreg.HKEY_* objects."""
|
|
12
|
+
key_map = {}
|
|
13
|
+
|
|
14
|
+
for key in dir(winreg):
|
|
15
|
+
if key.startswith('HKEY_'):
|
|
16
|
+
# Add the long names
|
|
17
|
+
key_map[key] = getattr(winreg, key)
|
|
18
|
+
# Add the winreg value so we can lookup nice names for keys
|
|
19
|
+
key_map[getattr(winreg, key)] = key
|
|
20
|
+
# Generate shortnames
|
|
21
|
+
short = 'HK' + ''.join([s[0] for s in key.split('_')[1:]])
|
|
22
|
+
if short not in key_map:
|
|
23
|
+
key_map[short] = key_map[key]
|
|
24
|
+
return key_map
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
key_map = _generate_key_map()
|
|
28
|
+
|
|
29
|
+
"""Stores commonly looked up locations in the registry."""
|
|
30
|
+
REG_LOCATIONS = {
|
|
31
|
+
"system": {
|
|
32
|
+
"classes": (winreg.HKEY_LOCAL_MACHINE, 'Software\\Classes'),
|
|
33
|
+
"env_var": (
|
|
34
|
+
winreg.HKEY_LOCAL_MACHINE,
|
|
35
|
+
'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment',
|
|
36
|
+
),
|
|
37
|
+
"uninstall": (
|
|
38
|
+
winreg.HKEY_LOCAL_MACHINE,
|
|
39
|
+
'Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
"user": {
|
|
43
|
+
"classes": (winreg.HKEY_CURRENT_USER, 'Software\\Classes'),
|
|
44
|
+
"env_var": (winreg.HKEY_CURRENT_USER, 'Environment'),
|
|
45
|
+
"regedit": (
|
|
46
|
+
winreg.HKEY_CURRENT_USER,
|
|
47
|
+
'Software\\Microsoft\\Windows\\CurrentVersion\\Applets\\Regedit',
|
|
48
|
+
),
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RegKey(object):
|
|
54
|
+
"""Object for accessing a specific registry key.
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
|
|
58
|
+
import winreg
|
|
59
|
+
from casement.registry import RegKey, REG_LOCATIONS
|
|
60
|
+
|
|
61
|
+
# Examples of different ways to get access to the same registry key.
|
|
62
|
+
classes_system = RegKey(*REG_LOCATIONS['system']['classes'])
|
|
63
|
+
classes_system = RegKey('HKEY_LOCAL_MACHINE\\Software\\Classes')
|
|
64
|
+
classes_system = RegKey('HKEY_LOCAL_MACHINE', 'Software\\Classes')
|
|
65
|
+
classes_system = RegKey(winreg.HKEY_LOCAL_MACHINE, 'Software\\Classes')
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
key: The path to the registry key as a string, or a winreg HKEY_* constant.
|
|
69
|
+
If a string is passed it needs to start with one of keys stored in
|
|
70
|
+
`key_map`(Example: HKEY_CLASSES_ROOT, HKCR). Alternatively you can
|
|
71
|
+
pass a winreg HKEY_* constant (Example: `winreg.HKEY_CURRENT_USER`).
|
|
72
|
+
sub_key (str, optional): A string to access sub-keys of key. If None and
|
|
73
|
+
key is a string it will split key into key and sub-key.
|
|
74
|
+
computer_name (str, optional): Name of another computer to connect to it's
|
|
75
|
+
registry instead of the current one.
|
|
76
|
+
architecture (int, optional): Access this key in the the 32 or 64 bit registry.
|
|
77
|
+
Valid values are 32 or 64.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __init__(self, key, sub_key=None, computer_name=None, architecture=64):
|
|
81
|
+
# If sub_key is not passed, it must be part of key
|
|
82
|
+
if sub_key is None:
|
|
83
|
+
key, sub_key = key.split('\\', 1)
|
|
84
|
+
|
|
85
|
+
# Convert key strings into winreg keys
|
|
86
|
+
if isinstance(key, six.string_types):
|
|
87
|
+
key = key.upper()
|
|
88
|
+
key = key_map[key]
|
|
89
|
+
|
|
90
|
+
self.architecture = architecture
|
|
91
|
+
self.computer_name = computer_name
|
|
92
|
+
self.key = key
|
|
93
|
+
self.sub_key = sub_key
|
|
94
|
+
|
|
95
|
+
def __str__(self):
|
|
96
|
+
return 'RegKey({!r}, {!r}, {!r}, {})'.format(
|
|
97
|
+
key_map.get(self.key, self.key),
|
|
98
|
+
self.sub_key,
|
|
99
|
+
self.computer_name,
|
|
100
|
+
self.architecture,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _key(self, write=False, create=False, delete=False):
|
|
104
|
+
"""Returns a winreg.OpenKeyEx or None.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
A winreg handle object
|
|
108
|
+
"""
|
|
109
|
+
connection = winreg.ConnectRegistry(self.computer_name, self.key)
|
|
110
|
+
sam = self._sam(self.architecture)
|
|
111
|
+
access = winreg.KEY_READ
|
|
112
|
+
if write:
|
|
113
|
+
access = winreg.KEY_WRITE
|
|
114
|
+
|
|
115
|
+
if create:
|
|
116
|
+
func = winreg.CreateKeyEx
|
|
117
|
+
else:
|
|
118
|
+
func = winreg.OpenKeyEx
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
return func(connection, self.sub_key, 0, access | sam)
|
|
122
|
+
except OSError:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def _sam(cls, architecture):
|
|
127
|
+
"""Return the correct wow64 key for the requested architecture."""
|
|
128
|
+
if architecture == 32:
|
|
129
|
+
return winreg.KEY_WOW64_32KEY
|
|
130
|
+
elif architecture == 64:
|
|
131
|
+
return winreg.KEY_WOW64_64KEY
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
def child(self, name):
|
|
135
|
+
"""Returns a RegKey for a sub_key with the given name.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
name (str): The name of the child key to return.
|
|
139
|
+
"""
|
|
140
|
+
cls = type(self)
|
|
141
|
+
sub_key = '{}\\{}'.format(self.sub_key, name)
|
|
142
|
+
return cls(
|
|
143
|
+
self.key,
|
|
144
|
+
sub_key=sub_key,
|
|
145
|
+
computer_name=self.computer_name,
|
|
146
|
+
architecture=self.architecture,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def child_names(self):
|
|
150
|
+
"""Generator returning the name for all sub_keys of this key."""
|
|
151
|
+
reg_key = self._key()
|
|
152
|
+
if not reg_key:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
index = 0
|
|
156
|
+
while True:
|
|
157
|
+
try:
|
|
158
|
+
yield winreg.EnumKey(reg_key, index)
|
|
159
|
+
index += 1
|
|
160
|
+
except OSError:
|
|
161
|
+
break
|
|
162
|
+
|
|
163
|
+
def create(self):
|
|
164
|
+
self._key(create=True)
|
|
165
|
+
|
|
166
|
+
def delete(self):
|
|
167
|
+
"""Delete the entire registry key. Raises a RuntimeError if the key has
|
|
168
|
+
child keys."""
|
|
169
|
+
# If the key doesn't exist, there is nothing to do
|
|
170
|
+
if not self.exists():
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
# TODO: add recursive delete?
|
|
174
|
+
for child in self.child_names():
|
|
175
|
+
# We can't delete this key if it has sub-keys. Using for loop instead
|
|
176
|
+
# of using next() so this can be used inside a contextmanager
|
|
177
|
+
if child:
|
|
178
|
+
msg = "Unable to delete key, it has sub-keys. {}".format(self)
|
|
179
|
+
raise RuntimeError(msg)
|
|
180
|
+
|
|
181
|
+
sam = self._sam(self.architecture)
|
|
182
|
+
winreg.DeleteKeyEx(self.key, self.sub_key, sam)
|
|
183
|
+
return True
|
|
184
|
+
|
|
185
|
+
def entry(self, name=None):
|
|
186
|
+
"""Returns a RegEntry for the given name. To access the (Default) entry
|
|
187
|
+
pass an empty string or None.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
name (str, optional): The name of the entry to return.
|
|
191
|
+
"""
|
|
192
|
+
return RegEntry(self, name)
|
|
193
|
+
|
|
194
|
+
def entry_names(self):
|
|
195
|
+
"""Generator returning the name for all entry's stored on this key."""
|
|
196
|
+
reg_key = self._key()
|
|
197
|
+
if not reg_key:
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
_, count, _ = winreg.QueryInfoKey(reg_key)
|
|
201
|
+
for index in range(count):
|
|
202
|
+
name, _, _ = winreg.EnumValue(reg_key, index)
|
|
203
|
+
yield name
|
|
204
|
+
|
|
205
|
+
def exists(self):
|
|
206
|
+
"""Returns True if the key exists in the registry."""
|
|
207
|
+
key = self._key()
|
|
208
|
+
return key is not None
|
|
209
|
+
|
|
210
|
+
def regedit(self, multiple=True):
|
|
211
|
+
"""Opens regedit to this registry key."""
|
|
212
|
+
if not self.exists():
|
|
213
|
+
raise RuntimeError("Can't open regedit to a key that doesn't exist.")
|
|
214
|
+
|
|
215
|
+
regedit_key = REG_LOCATIONS['user']['regedit']
|
|
216
|
+
reg = RegKey(*regedit_key)
|
|
217
|
+
if not reg.exists():
|
|
218
|
+
raise RuntimeError("Unable to find regedit preference {}".format(reg))
|
|
219
|
+
|
|
220
|
+
# Update the registry prefs storing the last key it had open.
|
|
221
|
+
last_key = reg.entry('LastKey')
|
|
222
|
+
computer = self.computer_name if self.computer_name else "Computer"
|
|
223
|
+
new = '\\'.join((computer, key_map.get(self.key, self.key), self.sub_key))
|
|
224
|
+
last_key.set(new, winreg.REG_SZ)
|
|
225
|
+
|
|
226
|
+
# Launch regedit
|
|
227
|
+
import subprocess
|
|
228
|
+
|
|
229
|
+
cmd = ['regedit']
|
|
230
|
+
if multiple:
|
|
231
|
+
cmd.append('/m')
|
|
232
|
+
subprocess.Popen(cmd)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class RegEntry(object):
|
|
236
|
+
def __init__(self, key, name):
|
|
237
|
+
self.key = key
|
|
238
|
+
self.name = name
|
|
239
|
+
|
|
240
|
+
def delete(self):
|
|
241
|
+
key = self.key._key(write=True)
|
|
242
|
+
winreg.DeleteValue(key, self.name)
|
|
243
|
+
winreg.CloseKey(key)
|
|
244
|
+
|
|
245
|
+
def type(self):
|
|
246
|
+
"""Returns the winreg value type."""
|
|
247
|
+
return self.value_info()[1]
|
|
248
|
+
|
|
249
|
+
def set(self, value, value_type=None):
|
|
250
|
+
key = self.key._key(write=True, create=True)
|
|
251
|
+
|
|
252
|
+
if isinstance(value_type, six.string_types):
|
|
253
|
+
value_type = getattr(winreg, value_type)
|
|
254
|
+
|
|
255
|
+
winreg.SetValueEx(key, self.name, 0, value_type, value)
|
|
256
|
+
winreg.CloseKey(key)
|
|
257
|
+
# TODO: add notify, or keep that in EnvVar?
|
|
258
|
+
|
|
259
|
+
def value(self):
|
|
260
|
+
"""Returns the data value."""
|
|
261
|
+
value, _ = self.value_info()
|
|
262
|
+
# We may need to do some conversion based on type in the future.
|
|
263
|
+
return value
|
|
264
|
+
|
|
265
|
+
def value_info(self):
|
|
266
|
+
"""Returns the entry value and value type.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
object: Value stored in key
|
|
270
|
+
int: registry type for value. See winreg's Value Types
|
|
271
|
+
"""
|
|
272
|
+
return winreg.QueryValueEx(self.key._key(), self.name)
|