1
# Copyright (C) 2005-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Win32-specific helper functions
19
Only one dependency: ctypes should be installed.
30
from breezy.i18n import gettext
32
has_ctypes_win32 = False
33
if sys.platform == 'win32':
37
has_ctypes_win32 = False
40
# Special Win32 API constants
41
# Handles of std streams
42
WIN32_STDIN_HANDLE = -10
43
WIN32_STDOUT_HANDLE = -11
44
WIN32_STDERR_HANDLE = -12
46
# CSIDL constants (from MSDN 2003)
47
CSIDL_APPDATA = 0x001A # Application Data folder
48
# <user name>\Local Settings\Application Data (non roaming)
49
CSIDL_LOCAL_APPDATA = 0x001c
50
CSIDL_PERSONAL = 0x0005 # My Documents folder
52
# from winapi C headers
55
MAX_COMPUTERNAME_LENGTH = 31
57
# Registry data type ids
62
def debug_memory_win32api(message='', short=True):
63
"""Use trace.note() to dump the running memory info."""
64
from breezy import trace
66
class PROCESS_MEMORY_COUNTERS_EX(ctypes.Structure):
67
"""Used by GetProcessMemoryInfo"""
68
_fields_ = [('cb', ctypes.c_ulong),
69
('PageFaultCount', ctypes.c_ulong),
70
('PeakWorkingSetSize', ctypes.c_size_t),
71
('WorkingSetSize', ctypes.c_size_t),
72
('QuotaPeakPagedPoolUsage', ctypes.c_size_t),
73
('QuotaPagedPoolUsage', ctypes.c_size_t),
74
('QuotaPeakNonPagedPoolUsage', ctypes.c_size_t),
75
('QuotaNonPagedPoolUsage', ctypes.c_size_t),
76
('PagefileUsage', ctypes.c_size_t),
77
('PeakPagefileUsage', ctypes.c_size_t),
78
('PrivateUsage', ctypes.c_size_t),
80
cur_process = ctypes.windll.kernel32.GetCurrentProcess()
81
mem_struct = PROCESS_MEMORY_COUNTERS_EX()
82
ret = ctypes.windll.psapi.GetProcessMemoryInfo(
83
cur_process, ctypes.byref(mem_struct), ctypes.sizeof(mem_struct))
85
trace.note(gettext('Failed to GetProcessMemoryInfo()'))
87
info = {'PageFaultCount': mem_struct.PageFaultCount,
88
'PeakWorkingSetSize': mem_struct.PeakWorkingSetSize,
89
'WorkingSetSize': mem_struct.WorkingSetSize,
90
'QuotaPeakPagedPoolUsage': mem_struct.QuotaPeakPagedPoolUsage,
91
'QuotaPagedPoolUsage': mem_struct.QuotaPagedPoolUsage,
92
'QuotaPeakNonPagedPoolUsage':
93
mem_struct.QuotaPeakNonPagedPoolUsage,
94
'QuotaNonPagedPoolUsage': mem_struct.QuotaNonPagedPoolUsage,
95
'PagefileUsage': mem_struct.PagefileUsage,
96
'PeakPagefileUsage': mem_struct.PeakPagefileUsage,
97
'PrivateUsage': mem_struct.PrivateUsage,
100
trace.note(gettext('Cannot debug memory on win32 without ctypes'
104
# using base-2 units (see HACKING.txt).
105
trace.note(gettext('WorkingSize {0:>7}KiB'
106
'\tPeakWorking {1:>7}KiB\t{2}').format(
107
info['WorkingSetSize'] / 1024,
108
info['PeakWorkingSetSize'] / 1024,
112
trace.note('%s', message)
113
trace.note(gettext('WorkingSize %8d KiB'),
114
info['WorkingSetSize'] / 1024)
115
trace.note(gettext('PeakWorking %8d KiB'),
116
info['PeakWorkingSetSize'] / 1024)
117
trace.note(gettext('PagefileUsage %8d KiB'),
118
info.get('PagefileUsage', 0) / 1024)
119
trace.note(gettext('PeakPagefileUsage %8d KiB'),
120
info.get('PeakPagefileUsage', 0) / 1024)
121
trace.note(gettext('PrivateUsage %8d KiB'),
122
info.get('PrivateUsage', 0) / 1024)
123
trace.note(gettext('PageFaultCount %8d'), info.get('PageFaultCount', 0))
126
def get_console_size(defaultx=80, defaulty=25):
127
"""Return size of current console.
129
This function try to determine actual size of current working
130
console window and return tuple (sizex, sizey) if success,
131
or default size (defaultx, defaulty) otherwise.
133
if not has_ctypes_win32:
135
return (defaultx, defaulty)
137
# To avoid problem with redirecting output via pipe
138
# we need to use stderr instead of stdout
139
h = ctypes.windll.kernel32.GetStdHandle(WIN32_STDERR_HANDLE)
140
csbi = ctypes.create_string_buffer(22)
141
res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
144
(bufx, bufy, curx, cury, wattr,
145
left, top, right, bottom, maxx, maxy) = struct.unpack(
146
"hhhhHhhhhhh", csbi.raw)
147
sizex = right - left + 1
148
sizey = bottom - top + 1
149
return (sizex, sizey)
151
return (defaultx, defaulty)
154
def _get_sh_special_folder_path(csidl):
155
"""Call SHGetSpecialFolderPathW if available, or return None.
157
Result is always unicode (or None).
161
SHGetSpecialFolderPath = \
162
ctypes.windll.shell32.SHGetSpecialFolderPathW
163
except AttributeError:
166
buf = ctypes.create_unicode_buffer(MAX_PATH)
167
if SHGetSpecialFolderPath(None, buf, csidl, 0):
172
def get_appdata_location():
173
"""Return Application Data location.
174
Return None if we cannot obtain location.
176
Windows defines two 'Application Data' folders per user - a 'roaming'
177
one that moves with the user as they logon to different machines, and
178
a 'local' one that stays local to the machine. This returns the 'roaming'
179
directory, and thus is suitable for storing user-preferences, etc.
181
appdata = _get_sh_special_folder_path(CSIDL_APPDATA)
184
# Use APPDATA if defined, will return None if not
185
return get_environ_unicode('APPDATA')
188
def get_local_appdata_location():
189
"""Return Local Application Data location.
190
Return the same as get_appdata_location() if we cannot obtain location.
192
Windows defines two 'Application Data' folders per user - a 'roaming'
193
one that moves with the user as they logon to different machines, and
194
a 'local' one that stays local to the machine. This returns the 'local'
195
directory, and thus is suitable for caches, temp files and other things
196
which don't need to move with the user.
198
local = _get_sh_special_folder_path(CSIDL_LOCAL_APPDATA)
201
# Vista supplies LOCALAPPDATA, but XP and earlier do not.
202
local = get_environ_unicode('LOCALAPPDATA')
205
return get_appdata_location()
208
def get_home_location():
209
"""Return user's home location.
210
Assume on win32 it's the <My Documents> folder.
211
If location cannot be obtained return system drive root,
214
home = _get_sh_special_folder_path(CSIDL_PERSONAL)
217
home = get_environ_unicode('HOME')
220
homepath = get_environ_unicode('HOMEPATH')
221
if homepath is not None:
222
return os.path.join(get_environ_unicode('HOMEDIR', ''), home)
223
# at least return windows root directory
224
windir = get_environ_unicode('WINDIR')
226
return os.path.splitdrive(windir)[0] + '/'
227
# otherwise C:\ is good enough for 98% users
232
"""Return user name as login name.
233
If name cannot be obtained return None.
237
advapi32 = ctypes.windll.advapi32
238
GetUserName = getattr(advapi32, 'GetUserNameW')
239
except AttributeError:
242
buf = ctypes.create_unicode_buffer(UNLEN + 1)
243
n = ctypes.c_int(UNLEN + 1)
244
if GetUserName(buf, ctypes.byref(n)):
246
# otherwise try env variables
247
return get_environ_unicode('USERNAME')
250
# 1 == ComputerNameDnsHostname, which returns "The DNS host name of the local
251
# computer or the cluster associated with the local computer."
252
_WIN32_ComputerNameDnsHostname = 1
256
"""Return host machine name.
257
If name cannot be obtained return None.
259
:return: A unicode string representing the host name.
263
kernel32 = ctypes.windll.kernel32
264
except AttributeError:
265
pass # Missing the module we need
267
buf = ctypes.create_unicode_buffer(MAX_COMPUTERNAME_LENGTH + 1)
268
n = ctypes.c_int(MAX_COMPUTERNAME_LENGTH + 1)
270
# Try GetComputerNameEx which gives a proper Unicode hostname
271
GetComputerNameEx = getattr(kernel32, 'GetComputerNameExW', None)
272
if (GetComputerNameEx is not None
273
and GetComputerNameEx(_WIN32_ComputerNameDnsHostname,
274
buf, ctypes.byref(n))):
276
return get_environ_unicode('COMPUTERNAME')
279
def _ensure_with_dir(path):
280
if (not os.path.split(path)[0] or path.startswith(u'*')
281
or path.startswith(u'?')):
282
return u'./' + path, True
287
def _undo_ensure_with_dir(path, corrected):
294
def glob_one(possible_glob):
295
"""Same as glob.glob().
297
work around bugs in glob.glob()
298
- Python bug #1001604 ("glob doesn't return unicode with ...")
299
- failing expansion for */* with non-iso-8859-* chars
301
corrected_glob, corrected = _ensure_with_dir(possible_glob)
302
glob_files = glob.glob(corrected_glob)
305
# special case to let the normal code path handle
306
# files that do not exist, etc.
307
glob_files = [possible_glob]
309
glob_files = [_undo_ensure_with_dir(elem, corrected)
310
for elem in glob_files]
311
return [elem.replace(u'\\', u'/') for elem in glob_files]
314
def glob_expand(file_list):
315
"""Replacement for glob expansion by the shell.
317
Win32's cmd.exe does not do glob expansion (eg ``*.py``), so we do our own
320
:param file_list: A list of filenames which may include shell globs.
321
:return: An expanded list of filenames.
323
Introduced in breezy 0.18.
327
expanded_file_list = []
328
for possible_glob in file_list:
329
expanded_file_list.extend(glob_one(possible_glob))
330
return expanded_file_list
333
def get_app_path(appname):
334
r"""Look up in Windows registry for full path to application executable.
335
Typically, applications create subkey with their basename
336
in HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\
338
:param appname: name of application (if no filename extension
339
is specified, .exe used)
340
:return: full path to aplication executable from registry,
341
or appname itself if nothing found.
346
if not os.path.splitext(basename)[1]:
347
basename = appname + '.exe'
350
hkey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE,
351
'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\' +
353
except EnvironmentError:
358
path, type_id = _winreg.QueryValueEx(hkey, '')
362
_winreg.CloseKey(hkey)
364
if type_id == REG_SZ:
366
if type_id == REG_EXPAND_SZ and has_win32api:
367
fullpath = win32api.ExpandEnvironmentStrings(path)
368
if len(fullpath) > 1 and fullpath[0] == '"' and fullpath[-1] == '"':
369
fullpath = fullpath[1:-1] # remove quotes around value
374
def set_file_attr_hidden(path):
375
"""Set file attributes to hidden if possible"""
376
if not has_ctypes_win32:
378
from ctypes.wintypes import BOOL, DWORD, LPCWSTR
379
_kernel32 = ctypes.windll.kernel32
380
# <https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-setfileattributesw>
381
_SetFileAttributesW = ctypes.WINFUNCTYPE(BOOL, LPCWSTR, DWORD)(
382
("SetFileAttributesW", _kernel32))
383
FILE_ATTRIBUTE_HIDDEN = 2
384
if not SetFileAttributes(path, FILE_ATTRIBUTE_HIDDEN):
385
e = ctypes.WinError()
387
trace.mutter('Unable to set hidden attribute on %r: %s', path, e)
390
def _command_line_to_argv(command_line, argv, single_quotes_allowed=False):
391
"""Convert a Unicode command line into a list of argv arguments.
393
It performs wildcard expansion to make wildcards act closer to how they
394
work in posix shells, versus how they work by default on Windows. Quoted
395
arguments are left untouched.
397
:param command_line: The unicode string to split into an arg list.
398
:param single_quotes_allowed: Whether single quotes are accepted as quoting
399
characters like double quotes. False by
401
:return: A list of unicode strings.
403
# First, split the command line
404
s = cmdline.Splitter(
405
command_line, single_quotes_allowed=single_quotes_allowed)
407
# Bug #587868 Now make sure that the length of s agrees with sys.argv
408
# we do this by simply counting the number of arguments in each. The counts should
409
# agree no matter what encoding sys.argv is in (AFAIK)
410
# len(arguments) < len(sys.argv) should be an impossibility since python gets
411
# args from the very same PEB as does GetCommandLineW
414
# Now shorten the command line we get from GetCommandLineW to match sys.argv
415
if len(arguments) < len(argv):
416
raise AssertionError("Split command line can't be shorter than argv")
417
arguments = arguments[len(arguments) - len(argv):]
419
# Carry on to process globs (metachars) in the command line
420
# expand globs if necessary
421
# TODO: Use 'globbing' instead of 'glob.glob', this gives us stuff like
424
for is_quoted, arg in arguments:
425
if is_quoted or not glob.has_magic(arg):
428
args.extend(glob_one(arg))
433
def get_unicode_argv():
434
prototype = ctypes.WINFUNCTYPE(ctypes.c_wchar_p)
435
GetCommandLineW = prototype(("GetCommandLineW",
436
ctypes.windll.kernel32))
437
command_line = GetCommandLineW()
438
if command_line is None:
439
raise ctypes.WinError()
440
# Skip the first argument, since we only care about parameters
441
argv = _command_line_to_argv(command_line, sys.argv)[1:]
444
def get_environ_unicode(key, default=None):
445
"""Get `key` from environment as unicode or `default` if unset
447
The environment is natively unicode on modern windows versions but
448
Python 2 only accesses it through the legacy bytestring api.
450
Environmental variable names are case insenstive on Windows.
452
A large enough buffer will be allocated to retrieve the value, though
453
it may take two calls to the underlying library function.
455
cfunc = getattr(get_environ_unicode, "_c_function", None)
457
from ctypes.wintypes import DWORD, LPCWSTR, LPWSTR
458
cfunc = ctypes.WINFUNCTYPE(DWORD, LPCWSTR, LPWSTR, DWORD)(
459
("GetEnvironmentVariableW", ctypes.windll.kernel32))
460
get_environ_unicode._c_function = cfunc
461
buffer_size = 256 # heuristic, 256 characters often enough
463
buf = ctypes.create_unicode_buffer(buffer_size)
464
length = cfunc(key, buf, buffer_size)
466
code = ctypes.GetLastError()
467
if code == 203: # ERROR_ENVVAR_NOT_FOUND
469
raise ctypes.WinError(code)
470
if buffer_size > length:
476
from ctypes.wintypes import BOOL, DWORD, HANDLE
477
_kernel32 = ctypes.windll.kernel32
478
_CloseHandle = ctypes.WINFUNCTYPE(BOOL, HANDLE)(
479
("CloseHandle", _kernel32))
480
_OpenProcess = ctypes.WINFUNCTYPE(HANDLE, DWORD, BOOL, DWORD)(
481
("OpenProcess", _kernel32))
483
def _ctypes_is_local_pid_dead(pid):
484
"""True if pid doesn't correspond to live process on this machine"""
485
handle = _OpenProcess(1, False, pid) # PROCESS_TERMINATE
487
errorcode = ctypes.GetLastError()
488
if errorcode == 5: # ERROR_ACCESS_DENIED
489
# Probably something alive we're not allowed to kill
491
elif errorcode == 87: # ERROR_INVALID_PARAMETER
493
raise ctypes.WinError(errorcode)
497
is_local_pid_dead = _ctypes_is_local_pid_dead