Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/win32.py @ 13375:f1fa8f481c7c
port win32.py to using the Python ctypes library
The pywin32 package is no longer needed.
ctypes is now required for running Mercurial on Windows.
ctypes is included in Python since version 2.5. For Python 2.4, ctypes is
available as an extra installer package for Windows.
Moved spawndetached() from windows.py to win32.py and fixed it, using
ctypes as well. spawndetached was defunct with Python 2.6.6 because Python
removed their undocumented subprocess.CreateProcess. This fixes
'hg serve -d' on Windows.
author | Adrian Buehlmann <adrian@cadifra.com> |
---|---|
date | Mon, 14 Feb 2011 11:12:26 +0100 |
parents | 1c613c1ae43d |
children | 60b5c6c3fd12 |
comparison
equal
deleted
inserted
replaced
13374:1c613c1ae43d | 13375:f1fa8f481c7c |
---|---|
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others | 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others |
4 # | 4 # |
5 # This software may be used and distributed according to the terms of the | 5 # This software may be used and distributed according to the terms of the |
6 # GNU General Public License version 2 or any later version. | 6 # GNU General Public License version 2 or any later version. |
7 | 7 |
8 """Utility functions that use win32 API. | |
9 | |
10 Mark Hammond's win32all package allows better functionality on | |
11 Windows. This module overrides definitions in util.py. If not | |
12 available, import of this module will fail, and generic code will be | |
13 used. | |
14 """ | |
15 | |
16 import win32api | |
17 | |
18 import errno, os, sys, pywintypes, win32con, win32file, win32process | |
19 import winerror, win32gui, win32console | |
20 import osutil, encoding | 8 import osutil, encoding |
21 from win32com.shell import shell, shellcon | 9 import ctypes, errno, os, struct, subprocess |
10 | |
11 _kernel32 = ctypes.windll.kernel32 | |
12 | |
13 _BOOL = ctypes.c_long | |
14 _WORD = ctypes.c_ushort | |
15 _DWORD = ctypes.c_ulong | |
16 _LPCSTR = _LPSTR = ctypes.c_char_p | |
17 _HANDLE = ctypes.c_void_p | |
18 _HWND = _HANDLE | |
19 | |
20 _INVALID_HANDLE_VALUE = -1 | |
21 | |
22 # GetLastError | |
23 _ERROR_SUCCESS = 0 | |
24 _ERROR_INVALID_PARAMETER = 87 | |
25 _ERROR_INSUFFICIENT_BUFFER = 122 | |
26 | |
27 # WPARAM is defined as UINT_PTR (unsigned type) | |
28 # LPARAM is defined as LONG_PTR (signed type) | |
29 if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p): | |
30 _WPARAM = ctypes.c_ulong | |
31 _LPARAM = ctypes.c_long | |
32 elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p): | |
33 _WPARAM = ctypes.c_ulonglong | |
34 _LPARAM = ctypes.c_longlong | |
35 | |
36 class _FILETIME(ctypes.Structure): | |
37 _fields_ = [('dwLowDateTime', _DWORD), | |
38 ('dwHighDateTime', _DWORD)] | |
39 | |
40 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure): | |
41 _fields_ = [('dwFileAttributes', _DWORD), | |
42 ('ftCreationTime', _FILETIME), | |
43 ('ftLastAccessTime', _FILETIME), | |
44 ('ftLastWriteTime', _FILETIME), | |
45 ('dwVolumeSerialNumber', _DWORD), | |
46 ('nFileSizeHigh', _DWORD), | |
47 ('nFileSizeLow', _DWORD), | |
48 ('nNumberOfLinks', _DWORD), | |
49 ('nFileIndexHigh', _DWORD), | |
50 ('nFileIndexLow', _DWORD)] | |
51 | |
52 # CreateFile | |
53 _FILE_SHARE_READ = 0x00000001 | |
54 _FILE_SHARE_WRITE = 0x00000002 | |
55 _FILE_SHARE_DELETE = 0x00000004 | |
56 | |
57 _OPEN_EXISTING = 3 | |
58 | |
59 # Process Security and Access Rights | |
60 _PROCESS_QUERY_INFORMATION = 0x0400 | |
61 | |
62 # GetExitCodeProcess | |
63 _STILL_ACTIVE = 259 | |
64 | |
65 # registry | |
66 _HKEY_CURRENT_USER = 0x80000001L | |
67 _HKEY_LOCAL_MACHINE = 0x80000002L | |
68 _KEY_READ = 0x20019 | |
69 _REG_SZ = 1 | |
70 _REG_DWORD = 4 | |
71 | |
72 class _STARTUPINFO(ctypes.Structure): | |
73 _fields_ = [('cb', _DWORD), | |
74 ('lpReserved', _LPSTR), | |
75 ('lpDesktop', _LPSTR), | |
76 ('lpTitle', _LPSTR), | |
77 ('dwX', _DWORD), | |
78 ('dwY', _DWORD), | |
79 ('dwXSize', _DWORD), | |
80 ('dwYSize', _DWORD), | |
81 ('dwXCountChars', _DWORD), | |
82 ('dwYCountChars', _DWORD), | |
83 ('dwFillAttribute', _DWORD), | |
84 ('dwFlags', _DWORD), | |
85 ('wShowWindow', _WORD), | |
86 ('cbReserved2', _WORD), | |
87 ('lpReserved2', ctypes.c_char_p), | |
88 ('hStdInput', _HANDLE), | |
89 ('hStdOutput', _HANDLE), | |
90 ('hStdError', _HANDLE)] | |
91 | |
92 class _PROCESS_INFORMATION(ctypes.Structure): | |
93 _fields_ = [('hProcess', _HANDLE), | |
94 ('hThread', _HANDLE), | |
95 ('dwProcessId', _DWORD), | |
96 ('dwThreadId', _DWORD)] | |
97 | |
98 _DETACHED_PROCESS = 0x00000008 | |
99 _STARTF_USESHOWWINDOW = 0x00000001 | |
100 _SW_HIDE = 0 | |
101 | |
102 class _COORD(ctypes.Structure): | |
103 _fields_ = [('X', ctypes.c_short), | |
104 ('Y', ctypes.c_short)] | |
105 | |
106 class _SMALL_RECT(ctypes.Structure): | |
107 _fields_ = [('Left', ctypes.c_short), | |
108 ('Top', ctypes.c_short), | |
109 ('Right', ctypes.c_short), | |
110 ('Bottom', ctypes.c_short)] | |
111 | |
112 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): | |
113 _fields_ = [('dwSize', _COORD), | |
114 ('dwCursorPosition', _COORD), | |
115 ('wAttributes', _WORD), | |
116 ('srWindow', _SMALL_RECT), | |
117 ('dwMaximumWindowSize', _COORD)] | |
118 | |
119 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12 | |
120 | |
121 def _raiseoserror(name): | |
122 err = ctypes.WinError() | |
123 raise OSError(err.errno, '%s: %s' % (name, err.strerror)) | |
124 | |
125 def _getfileinfo(name): | |
126 fh = _kernel32.CreateFileA(name, 0, | |
127 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE, | |
128 None, _OPEN_EXISTING, 0, None) | |
129 if fh == _INVALID_HANDLE_VALUE: | |
130 _raiseoserror(name) | |
131 try: | |
132 fi = _BY_HANDLE_FILE_INFORMATION() | |
133 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)): | |
134 _raiseoserror(name) | |
135 return fi | |
136 finally: | |
137 _kernel32.CloseHandle(fh) | |
22 | 138 |
23 def os_link(src, dst): | 139 def os_link(src, dst): |
24 try: | 140 if not _kernel32.CreateHardLinkA(dst, src, None): |
25 win32file.CreateHardLink(dst, src) | 141 _raiseoserror(src) |
26 except pywintypes.error: | 142 |
27 raise OSError(errno.EINVAL, 'target implements hardlinks improperly') | 143 def nlinks(name): |
28 except NotImplementedError: # Another fake error win Win98 | 144 '''return number of hardlinks for the given file''' |
29 raise OSError(errno.EINVAL, 'Hardlinking not supported') | 145 return _getfileinfo(name).nNumberOfLinks |
30 | |
31 def _getfileinfo(pathname): | |
32 """Return number of hardlinks for the given file.""" | |
33 try: | |
34 fh = win32file.CreateFile(pathname, 0, | |
35 win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE | | |
36 win32file.FILE_SHARE_DELETE, | |
37 None, win32file.OPEN_EXISTING, 0, None) | |
38 except pywintypes.error: | |
39 raise OSError(errno.ENOENT, 'The system cannot find the file specified') | |
40 try: | |
41 return win32file.GetFileInformationByHandle(fh) | |
42 finally: | |
43 fh.Close() | |
44 | |
45 def nlinks(pathname): | |
46 """Return number of hardlinks for the given file.""" | |
47 return _getfileinfo(pathname)[7] | |
48 | 146 |
49 def samefile(fpath1, fpath2): | 147 def samefile(fpath1, fpath2): |
50 """Returns whether fpath1 and fpath2 refer to the same file. This is only | 148 '''Returns whether fpath1 and fpath2 refer to the same file. This is only |
51 guaranteed to work for files, not directories.""" | 149 guaranteed to work for files, not directories.''' |
52 res1 = _getfileinfo(fpath1) | 150 res1 = _getfileinfo(fpath1) |
53 res2 = _getfileinfo(fpath2) | 151 res2 = _getfileinfo(fpath2) |
54 # Index 4 is the volume serial number, and 8 and 9 contain the file ID | 152 return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber |
55 return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9] | 153 and res1.nFileIndexHigh == res2.nFileIndexHigh |
154 and res1.nFileIndexLow == res2.nFileIndexLow) | |
56 | 155 |
57 def samedevice(fpath1, fpath2): | 156 def samedevice(fpath1, fpath2): |
58 """Returns whether fpath1 and fpath2 are on the same device. This is only | 157 '''Returns whether fpath1 and fpath2 are on the same device. This is only |
59 guaranteed to work for files, not directories.""" | 158 guaranteed to work for files, not directories.''' |
60 res1 = _getfileinfo(fpath1) | 159 res1 = _getfileinfo(fpath1) |
61 res2 = _getfileinfo(fpath2) | 160 res2 = _getfileinfo(fpath2) |
62 return res1[4] == res2[4] | 161 return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber |
63 | 162 |
64 def testpid(pid): | 163 def testpid(pid): |
65 '''return True if pid is still running or unable to | 164 '''return True if pid is still running or unable to |
66 determine, False otherwise''' | 165 determine, False otherwise''' |
67 try: | 166 h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid) |
68 handle = win32api.OpenProcess( | 167 if h: |
69 win32con.PROCESS_QUERY_INFORMATION, False, pid) | 168 try: |
70 if handle: | 169 status = _DWORD() |
71 status = win32process.GetExitCodeProcess(handle) | 170 if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)): |
72 return status == win32con.STILL_ACTIVE | 171 return status.value == _STILL_ACTIVE |
73 except pywintypes.error, details: | 172 finally: |
74 return details[0] != winerror.ERROR_INVALID_PARAMETER | 173 _kernel32.CloseHandle(h) |
75 return True | 174 return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER |
76 | 175 |
77 def lookup_reg(key, valname=None, scope=None): | 176 def lookup_reg(key, valname=None, scope=None): |
78 ''' Look up a key/value name in the Windows registry. | 177 ''' Look up a key/value name in the Windows registry. |
79 | 178 |
80 valname: value name. If unspecified, the default value for the key | 179 valname: value name. If unspecified, the default value for the key |
81 is used. | 180 is used. |
82 scope: optionally specify scope for registry lookup, this can be | 181 scope: optionally specify scope for registry lookup, this can be |
83 a sequence of scopes to look up in order. Default (CURRENT_USER, | 182 a sequence of scopes to look up in order. Default (CURRENT_USER, |
84 LOCAL_MACHINE). | 183 LOCAL_MACHINE). |
85 ''' | 184 ''' |
86 try: | 185 adv = ctypes.windll.advapi32 |
87 from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \ | 186 byref = ctypes.byref |
88 QueryValueEx, OpenKey | |
89 except ImportError: | |
90 return None | |
91 | |
92 if scope is None: | 187 if scope is None: |
93 scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE) | 188 scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE) |
94 elif not isinstance(scope, (list, tuple)): | 189 elif not isinstance(scope, (list, tuple)): |
95 scope = (scope,) | 190 scope = (scope,) |
96 for s in scope: | 191 for s in scope: |
192 kh = _HANDLE() | |
193 res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh)) | |
194 if res != _ERROR_SUCCESS: | |
195 continue | |
97 try: | 196 try: |
98 val = QueryValueEx(OpenKey(s, key), valname)[0] | 197 size = _DWORD(600) |
99 # never let a Unicode string escape into the wild | 198 type = _DWORD() |
100 return encoding.tolocal(val.encode('UTF-8')) | 199 buf = ctypes.create_string_buffer(size.value + 1) |
101 except EnvironmentError: | 200 res = adv.RegQueryValueExA(kh.value, valname, None, |
102 pass | 201 byref(type), buf, byref(size)) |
202 if res != _ERROR_SUCCESS: | |
203 continue | |
204 if type.value == _REG_SZ: | |
205 # never let a Unicode string escape into the wild | |
206 return encoding.tolocal(buf.value.encode('UTF-8')) | |
207 elif type.value == _REG_DWORD: | |
208 fmt = '<L' | |
209 s = ctypes.string_at(byref(buf), struct.calcsize(fmt)) | |
210 return struct.unpack(fmt, s)[0] | |
211 finally: | |
212 adv.RegCloseKey(kh.value) | |
103 | 213 |
104 def system_rcpath_win32(): | 214 def system_rcpath_win32(): |
105 '''return default os-specific hgrc search path''' | 215 '''return default os-specific hgrc search path''' |
106 filename = win32api.GetModuleFileName(0) | 216 rcpath = [] |
217 size = 600 | |
218 buf = ctypes.create_string_buffer(size + 1) | |
219 len = _kernel32.GetModuleFileNameA(None, ctypes.byref(buf), size) | |
220 if len == 0: | |
221 raise ctypes.WinError() | |
222 elif len == size: | |
223 raise ctypes.WinError(_ERROR_INSUFFICIENT_BUFFER) | |
224 filename = buf.value | |
107 # Use mercurial.ini found in directory with hg.exe | 225 # Use mercurial.ini found in directory with hg.exe |
108 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini') | 226 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini') |
109 if os.path.isfile(progrc): | 227 if os.path.isfile(progrc): |
110 return [progrc] | 228 rcpath.append(progrc) |
229 return rcpath | |
111 # Use hgrc.d found in directory with hg.exe | 230 # Use hgrc.d found in directory with hg.exe |
112 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d') | 231 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d') |
113 if os.path.isdir(progrcd): | 232 if os.path.isdir(progrcd): |
114 rcpath = [] | |
115 for f, kind in osutil.listdir(progrcd): | 233 for f, kind in osutil.listdir(progrcd): |
116 if f.endswith('.rc'): | 234 if f.endswith('.rc'): |
117 rcpath.append(os.path.join(progrcd, f)) | 235 rcpath.append(os.path.join(progrcd, f)) |
118 return rcpath | 236 return rcpath |
119 # else look for a system rcpath in the registry | 237 # else look for a system rcpath in the registry |
120 try: | 238 value = lookup_reg('SOFTWARE\\Mercurial', None, _HKEY_LOCAL_MACHINE) |
121 value = win32api.RegQueryValue( | 239 if not isinstance(value, str) or not value: |
122 win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\\Mercurial') | |
123 rcpath = [] | |
124 for p in value.split(os.pathsep): | |
125 if p.lower().endswith('mercurial.ini'): | |
126 rcpath.append(p) | |
127 elif os.path.isdir(p): | |
128 for f, kind in osutil.listdir(p): | |
129 if f.endswith('.rc'): | |
130 rcpath.append(os.path.join(p, f)) | |
131 return rcpath | 240 return rcpath |
132 except pywintypes.error: | 241 value = value.replace('/', os.sep) |
133 return [] | 242 for p in value.split(os.pathsep): |
243 if p.lower().endswith('mercurial.ini'): | |
244 rcpath.append(p) | |
245 elif os.path.isdir(p): | |
246 for f, kind in osutil.listdir(p): | |
247 if f.endswith('.rc'): | |
248 rcpath.append(os.path.join(p, f)) | |
249 return rcpath | |
134 | 250 |
135 def user_rcpath_win32(): | 251 def user_rcpath_win32(): |
136 '''return os-specific hgrc search path to the user dir''' | 252 '''return os-specific hgrc search path to the user dir''' |
137 userdir = os.path.expanduser('~') | 253 userdir = os.path.expanduser('~') |
138 if sys.getwindowsversion()[3] != 2 and userdir == '~': | |
139 # We are on win < nt: fetch the APPDATA directory location and use | |
140 # the parent directory as the user home dir. | |
141 appdir = shell.SHGetPathFromIDList( | |
142 shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA)) | |
143 userdir = os.path.dirname(appdir) | |
144 return [os.path.join(userdir, 'mercurial.ini'), | 254 return [os.path.join(userdir, 'mercurial.ini'), |
145 os.path.join(userdir, '.hgrc')] | 255 os.path.join(userdir, '.hgrc')] |
146 | 256 |
147 def getuser(): | 257 def getuser(): |
148 '''return name of current user''' | 258 '''return name of current user''' |
149 return win32api.GetUserName() | 259 adv = ctypes.windll.advapi32 |
150 | 260 size = _DWORD(300) |
151 def set_signal_handler_win32(): | 261 buf = ctypes.create_string_buffer(size.value + 1) |
152 """Register a termination handler for console events including | 262 if not adv.GetUserNameA(ctypes.byref(buf), ctypes.byref(size)): |
263 raise ctypes.WinError() | |
264 return buf.value | |
265 | |
266 _SIGNAL_HANDLER = ctypes.WINFUNCTYPE(_BOOL, _DWORD) | |
267 _signal_handler = [] | |
268 | |
269 def set_signal_handler(): | |
270 '''Register a termination handler for console events including | |
153 CTRL+C. python signal handlers do not work well with socket | 271 CTRL+C. python signal handlers do not work well with socket |
154 operations. | 272 operations. |
155 """ | 273 ''' |
156 def handler(event): | 274 def handler(event): |
157 win32process.ExitProcess(1) | 275 _kernel32.ExitProcess(1) |
158 win32api.SetConsoleCtrlHandler(handler) | 276 |
277 if _signal_handler: | |
278 return # already registered | |
279 h = _SIGNAL_HANDLER(handler) | |
280 _signal_handler.append(h) # needed to prevent garbage collection | |
281 if not _kernel32.SetConsoleCtrlHandler(h, True): | |
282 raise ctypes.WinError() | |
283 | |
284 _WNDENUMPROC = ctypes.WINFUNCTYPE(_BOOL, _HWND, _LPARAM) | |
159 | 285 |
160 def hidewindow(): | 286 def hidewindow(): |
161 def callback(*args, **kwargs): | 287 user32 = ctypes.windll.user32 |
162 hwnd, pid = args | 288 |
163 wpid = win32process.GetWindowThreadProcessId(hwnd)[1] | 289 def callback(hwnd, pid): |
164 if pid == wpid: | 290 wpid = _DWORD() |
165 win32gui.ShowWindow(hwnd, win32con.SW_HIDE) | 291 user32.GetWindowThreadProcessId(hwnd, ctypes.byref(wpid)) |
166 | 292 if pid == wpid.value: |
167 pid = win32process.GetCurrentProcessId() | 293 user32.ShowWindow(hwnd, _SW_HIDE) |
168 win32gui.EnumWindows(callback, pid) | 294 return False # stop enumerating windows |
295 return True | |
296 | |
297 pid = _kernel32.GetCurrentProcessId() | |
298 user32.EnumWindows(_WNDENUMPROC(callback), pid) | |
169 | 299 |
170 def termwidth(): | 300 def termwidth(): |
171 try: | 301 # cmd.exe does not handle CR like a unix console, the CR is |
172 # Query stderr to avoid problems with redirections | 302 # counted in the line length. On 80 columns consoles, if 80 |
173 screenbuf = win32console.GetStdHandle(win32console.STD_ERROR_HANDLE) | 303 # characters are written, the following CR won't apply on the |
174 if screenbuf is None: | 304 # current line but on the new one. Keep room for it. |
175 return 79 | 305 width = 79 |
176 try: | 306 # Query stderr to avoid problems with redirections |
177 window = screenbuf.GetConsoleScreenBufferInfo()['Window'] | 307 screenbuf = _kernel32.GetStdHandle( |
178 width = window.Right - window.Left | 308 _STD_ERROR_HANDLE) # don't close the handle returned |
179 return width | 309 if screenbuf is None or screenbuf == _INVALID_HANDLE_VALUE: |
180 finally: | 310 return width |
181 screenbuf.Detach() | 311 csbi = _CONSOLE_SCREEN_BUFFER_INFO() |
182 except pywintypes.error: | 312 if not _kernel32.GetConsoleScreenBufferInfo( |
183 return 79 | 313 screenbuf, ctypes.byref(csbi)): |
314 return width | |
315 width = csbi.srWindow.Right - csbi.srWindow.Left | |
316 return width | |
317 | |
318 def spawndetached(args): | |
319 # No standard library function really spawns a fully detached | |
320 # process under win32 because they allocate pipes or other objects | |
321 # to handle standard streams communications. Passing these objects | |
322 # to the child process requires handle inheritance to be enabled | |
323 # which makes really detached processes impossible. | |
324 si = _STARTUPINFO() | |
325 si.cb = ctypes.sizeof(_STARTUPINFO) | |
326 si.dwFlags = _STARTF_USESHOWWINDOW | |
327 si.wShowWindow = _SW_HIDE | |
328 | |
329 pi = _PROCESS_INFORMATION() | |
330 | |
331 env = '' | |
332 for k in os.environ: | |
333 env += "%s=%s\0" % (k, os.environ[k]) | |
334 if not env: | |
335 env = '\0' | |
336 env += '\0' | |
337 | |
338 args = subprocess.list2cmdline(args) | |
339 # Not running the command in shell mode makes python26 hang when | |
340 # writing to hgweb output socket. | |
341 comspec = os.environ.get("COMSPEC", "cmd.exe") | |
342 args = comspec + " /c " + args | |
343 | |
344 res = _kernel32.CreateProcessA( | |
345 None, args, None, None, False, _DETACHED_PROCESS, | |
346 env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi)) | |
347 if not res: | |
348 raise ctypes.WinError() | |
349 | |
350 return pi.dwProcessId |