Merge lp:~blackdaemon/enso/win32-selection-fixes into lp:~communityenso/enso/community-enso

Proposed by blackdaemon
Status: Needs review
Proposed branch: lp:~blackdaemon/enso/win32-selection-fixes
Merge into: lp:~communityenso/enso/community-enso
Diff against target: 548 lines (+218/-90)
4 files modified
enso/platform/win32/selection/FileSelection.py (+16/-11)
enso/platform/win32/selection/TextSelection.py (+43/-20)
enso/platform/win32/selection/_ContextUtils.py (+158/-58)
enso/platform/win32/selection/__init__.py (+1/-1)
To merge this branch: bzr merge lp:~blackdaemon/enso/win32-selection-fixes
Reviewer Review Type Date Requested Status
Community Enso Team Pending
Review via email: mp+27830@code.launchpad.net

Description of the change

Handling of copy/cut/paste for Vim and gVim
Handling of copy/paste for console windows

To post a comment you must log in.
148. By blackdaemon

Small fixes

Unmerged revisions

148. By blackdaemon

Small fixes

147. By blackdaemon

Handling Vim and gVim copy&paste

146. By blackdaemon

Handle copy&paste for console windows using "Alt-space e y" and "Alt-space e p"

145. By blackdaemon

Ignore pylint errors for pywintypes.error

144. By blackdaemon

Added getWindowProcessName()

143. By blackdaemon

Added handling of some special keys ([, ])
Fixed sending of extended keys (Ins, Del, Home, [, ], etc)
Added typeShiftKey
Added possibility to send key as string or virtual-code

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'enso/platform/win32/selection/FileSelection.py'
2--- enso/platform/win32/selection/FileSelection.py 2008-04-27 11:12:38 +0000
3+++ enso/platform/win32/selection/FileSelection.py 2010-07-30 13:46:41 +0000
4@@ -44,8 +44,10 @@
5 # Imports
6 # ----------------------------------------------------------------------------
7
8+import os
9 import win32con
10 import win32clipboard
11+import win32gui
12 import pywintypes
13 import logging
14
15@@ -144,9 +146,8 @@
16 value = win32clipboard.GetClipboardData(
17 win32con.CF_HDROP
18 )
19- except pywintypes.error, e:
20- logging.warn( "Error getting CF_HDROP from clipboard: %s" \
21- % ( str(e) ) )
22+ except pywintypes.error, e: #IGNORE:E1101
23+ logging.warn( "Error getting CF_HDROP from clipboard: %s", str(e) )
24 value = None
25 else:
26 logging.info( "Clipboard type CF_HDROP not in clipboard." )
27@@ -167,13 +168,17 @@
28 subclass which is appropriate to the currently active application.
29 """
30
31- windowClass = ContextUtils.getForegroundClassNameUnicode()
32-
33- if windowClass == u"ConsoleWindowClass":
34- fsContext = NullFileSelectionContext()
35- elif windowClass == u"Emacs":
36- fsContext = NullFileSelectionContext()
37- else:
38- fsContext = DefaultFileSelectionContext()
39+ hwnd = win32gui.GetForegroundWindow()
40+ className = ContextUtils.getWindowClassName(hwnd)
41+ processName = ContextUtils.getWindowProcessName(hwnd)
42+
43+ fsContext = DefaultFileSelectionContext()
44+
45+ if className == u"Emacs":
46+ fsContext = NullFileSelectionContext()
47+ elif className == u"ConsoleWindowClass":
48+ fsContext = NullFileSelectionContext()
49+ elif className == u"Vim" and os.path.basename(processName).lower() == "gvim.exe":
50+ fsContext = NullFileSelectionContext()
51
52 return fsContext
53
54=== modified file 'enso/platform/win32/selection/TextSelection.py'
55--- enso/platform/win32/selection/TextSelection.py 2009-02-20 00:39:06 +0000
56+++ enso/platform/win32/selection/TextSelection.py 2010-07-30 13:46:41 +0000
57@@ -42,6 +42,8 @@
58 # Imports
59 # ----------------------------------------------------------------------------
60
61+import os
62+import win32gui
63 import win32clipboard
64 import win32con
65 import logging
66@@ -103,6 +105,7 @@
67 newDict[ "text" ] = newPlainText
68 if len( newHtml ) > 0:
69 newDict[ "html" ] = newHtml
70+ return newDict
71
72 def _textDictToAscii( textDict ):
73 text = textDict.get( "text", u"" )
74@@ -407,7 +410,7 @@
75 class NonReplacingTextSelection( DefaultTextSelection ):
76 """
77 In some applications, notably MoonEdit and Emacs, a paste
78- does not replace selected tet: it inserts
79+ does not replace selected text: it inserts
80 at the cursor. This is the selection context to use for
81 those applications.
82 """
83@@ -473,20 +476,6 @@
84 these functions are no-ops.
85 """
86
87- def getSelection( self ):
88- """
89- Always blank.
90- """
91- return {}
92-
93- def replaceSelection( self, textDict ):
94- """
95- Text in the command prompt window is immutable, so
96- the replacement behavior is impossible to achieve.
97- Return False to indicate failure.
98- """
99- return False
100-
101 def simulatePasteKeystroke( self ):
102 # Alt-space pops up the window menu (the thing you get
103 # by clicking in the upper-left corner). Then typing
104@@ -495,7 +484,11 @@
105 ContextUtils.typeSequence( "e p" )
106
107 def simulateCopyKeystroke( self ):
108- pass
109+ # Alt-space pops up the window menu (the thing you get
110+ # by clicking in the upper-left corner). Then typing
111+ # e and then y selects edit->copy.
112+ ContextUtils.typeAltKey( " " )
113+ ContextUtils.typeSequence( "e y" )
114
115 def simulateCutKeystroke( self ):
116 pass
117@@ -509,6 +502,28 @@
118 return self._pasteText( textDict )
119
120
121+class VimTextSelection( DefaultTextSelection ):
122+ """
123+ Returned if the currently active application is a Windows Vim/gVim.
124+ """
125+
126+ def simulatePasteKeystroke( self ):
127+ # Shift-Insert should work by default in Windows version of Vim/gVim
128+ ContextUtils.typeShiftKey(win32con.VK_INSERT)
129+
130+ def simulateCopyKeystroke( self ):
131+ # Ctrl-Insert should work by default in Windows version of Vim/gVim
132+ # But yanking the selection will also clear it. So the sequence is:
133+ # copy text to clipboard: Ctrl-Insert
134+ # get to normal mode 2x: Ctrl-[ Ctrl-[
135+ # return to last selection: gv
136+ ContextUtils.typeSequence('CD INS [ [ CU g v')
137+
138+ def simulateCutKeystroke( self ):
139+ # Ctrl-Del should work by default in Windows version of Vim/gVim
140+ ContextUtils.typeCommandKey(win32con.VK_DELETE)
141+
142+
143 # ----------------------------------------------------------------------------
144 # Public Function
145 # ----------------------------------------------------------------------------
146@@ -521,16 +536,24 @@
147 If no text is selected, this must return a TextSelection object with
148 no text in it -- NOT a None.
149 """
150+ hwnd = win32gui.GetForegroundWindow()
151+ className = ContextUtils.getWindowClassName(hwnd)
152
153- className = ContextUtils.getForegroundClassNameUnicode()
154+ tsContext = DefaultTextSelection()
155
156 if className == u"Emacs":
157 tsContext = EmacsTextSelection()
158 elif className == u"MoonEdit":
159 tsContext = NonReplacingTextSelection()
160 elif className == u"ConsoleWindowClass":
161- tsContext = CommandPromptTextSelection()
162- else:
163- tsContext = DefaultTextSelection()
164+ processName = ContextUtils.getWindowProcessName(hwnd)
165+ if os.path.basename(processName).lower() == "vim.exe":
166+ tsContext = VimTextSelection()
167+ else:
168+ tsContext = CommandPromptTextSelection()
169+ elif className == u"Vim":
170+ processName = ContextUtils.getWindowProcessName(hwnd)
171+ if os.path.basename(processName).lower() == "gvim.exe":
172+ tsContext = VimTextSelection()
173
174 return tsContext
175
176=== modified file 'enso/platform/win32/selection/_ContextUtils.py'
177--- enso/platform/win32/selection/_ContextUtils.py 2008-04-27 11:12:38 +0000
178+++ enso/platform/win32/selection/_ContextUtils.py 2010-07-30 13:46:41 +0000
179@@ -1,6 +1,6 @@
180 # Copyright (c) 2008, Humanized, Inc.
181 # All rights reserved.
182-#
183+#
184 # Redistribution and use in source and binary forms, with or without
185 # modification, are permitted provided that the following conditions are met:
186 #
187@@ -14,7 +14,7 @@
188 # 3. Neither the name of Enso nor the names of its contributors may
189 # be used to endorse or promote products derived from this
190 # software without specific prior written permission.
191-#
192+#
193 # THIS SOFTWARE IS PROVIDED BY Humanized, Inc. ``AS IS'' AND ANY
194 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
195 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
196@@ -37,10 +37,12 @@
197 # Imports
198 # ----------------------------------------------------------------------------
199
200+import os
201 import win32api
202 import win32con
203 import win32gui
204 import win32clipboard
205+import win32process
206 import ctypes
207 import pywintypes
208 import time
209@@ -95,6 +97,55 @@
210 # key is being depressed."
211 KEYEVENTF_KEYDOWN = 0
212 KEYEVENTF_KEYUP = win32con.KEYEVENTF_KEYUP
213+KEYEVENTF_EXTENDEDKEY = win32con.KEYEVENTF_EXTENDEDKEY
214+
215+VK_LBRACKET = 0xDB
216+VK_RBRACKET = 0xDD
217+
218+KEY_MAPPING = { "F1" : win32con.VK_F1,
219+ "F2" : win32con.VK_F2,
220+ "F3" : win32con.VK_F3,
221+ "F4" : win32con.VK_F4,
222+ "F5" : win32con.VK_F5,
223+ "F6" : win32con.VK_F6,
224+ "F7" : win32con.VK_F7,
225+ "F8" : win32con.VK_F8,
226+ "F9" : win32con.VK_F9,
227+ "F10": win32con.VK_F10,
228+ "F11": win32con.VK_F11,
229+ "F12": win32con.VK_F12,
230+ "CD" : win32con.VK_LCONTROL,
231+ "CU" : win32con.VK_LCONTROL,
232+ "SD" : win32con.VK_LSHIFT,
233+ "SU" : win32con.VK_LSHIFT,
234+ "AD" : win32con.VK_MENU,
235+ "AU" : win32con.VK_MENU,
236+ "ID" : win32con.VK_LWIN,
237+ "IU" : win32con.VK_LWIN,
238+ "LA": win32con.VK_LEFT,
239+ "RA": win32con.VK_RIGHT,
240+ "ESC": win32con.VK_ESCAPE,
241+ "INS": win32con.VK_INSERT,
242+ "DEL": win32con.VK_DELETE,
243+ "[": VK_LBRACKET,
244+ "]": VK_RBRACKET,
245+ "LBRACKET": VK_LBRACKET,
246+ "RBRACKET": VK_RBRACKET
247+ }
248+
249+# List of keys that need to have the win32con.KEYEVENTF_EXTENDEDKEY flag set
250+KEYS_EXTENDED = [
251+ win32con.VK_UP,
252+ win32con.VK_DOWN,
253+ win32con.VK_LEFT,
254+ win32con.VK_RIGHT,
255+ win32con.VK_HOME,
256+ win32con.VK_END,
257+ win32con.VK_PRIOR, # PgUp
258+ win32con.VK_NEXT, # PgDn
259+ win32con.VK_INSERT,
260+ win32con.VK_DELETE
261+]
262
263 # ----------------------------------------------------------------------------
264 # Private Module Variables
265@@ -125,10 +176,14 @@
266 # win32all does not provide access to MapVirtualKey, so we have to use
267 # ctypes to access the DLL directly, and have to append "A" to the name
268 # since the function is implemented in Unicode and ANSI versions.
269-
270+
271 # This gives a hardware scancode for the virtual key.
272 scanCode = ctypes.windll.user32.MapVirtualKeyA( vkCode, 0 )
273
274+ # Some keys needs the 'extended-key' attribute
275+ if vkCode in KEYS_EXTENDED:
276+ eventType |= KEYEVENTF_EXTENDEDKEY
277+
278 # This creates the keyboard event (this function is the one called
279 # by keyboard driver interupt handlers, so it's as low-level as it gets)
280 win32api.keybd_event( vkCode, scanCode, eventType, 0 )
281@@ -157,7 +212,7 @@
282 try:
283 win32clipboard.OpenClipboard( 0 )
284 success = True
285- except pywintypes.error:
286+ except pywintypes.error: #IGNORE:E1101
287 if totalTime < CLIPBOARD_OPEN_WAIT_AMOUNT:
288 sleepForMs( CLIPBOARD_OPEN_WAIT_INTERVAL )
289 totalTime += CLIPBOARD_OPEN_WAIT_INTERVAL
290@@ -186,10 +241,10 @@
291
292 # Postconditions:
293 assert( _hasTheClipboardOpen() )
294-
295+
296 try:
297 win32clipboard.CloseClipboard()
298- except pywintypes.error:
299+ except pywintypes.error: #IGNORE:E1101
300 logging.warn( "Attempted to close clipboard when not open." )
301 global _contextUtilsHasTheClipboardOpen
302 _contextUtilsHasTheClipboardOpen = False
303@@ -205,7 +260,7 @@
304 function, then closes it when the wrapped function is done,
305 whether or not the wrapped function throws an exception.
306 """
307-
308+
309 def wrapperFunc( *args, **kwargs ):
310 # If safeOpenClipboard() raises an exception, this function will do
311 # nothing but allow it to be raised. (We shouldn't attempt to close
312@@ -231,7 +286,7 @@
313 the CF_CLIPBOARD_VIEWER_IGNORE format so that clipboard viewers
314 will ignore this alteration of the clipboard.
315 """
316-
317+
318 win32clipboard.EmptyClipboard()
319 setClipboardDataViewerIgnore()
320
321@@ -264,7 +319,7 @@
322 """
323 LONGTERM TODO: This is kept around for debugging but can be deleted from
324 production code.
325-
326+
327 Given a format code (of the kind returned from the windows clipboard
328 functions), returns a string describing the meaning of that
329 format code.
330@@ -333,8 +388,18 @@
331 """
332
333 _keyboardEvent( win32con.VK_CONTROL, KEYEVENTF_KEYDOWN )
334- _keyboardEvent( ord(key.upper()), KEYEVENTF_KEYDOWN )
335- _keyboardEvent( ord(key.upper()), KEYEVENTF_KEYUP )
336+
337+ if isinstance(key, basestring):
338+ if key in KEY_MAPPING:
339+ key_code = KEY_MAPPING[key]
340+ else:
341+ key_code = ord(key.upper())
342+ else:
343+ key_code = key
344+
345+ _keyboardEvent( key_code, KEYEVENTF_KEYDOWN )
346+ _keyboardEvent( key_code, KEYEVENTF_KEYUP )
347+
348 _keyboardEvent( win32con.VK_CONTROL, KEYEVENTF_KEYUP )
349 logging.info( "I am in typeCommandKey and I just typed " + key )
350
351@@ -344,18 +409,50 @@
352 Given a character literal, simulates holding the Alt key down
353 and typing that character.
354 """
355-
356+
357 _keyboardEvent( win32con.VK_MENU, KEYEVENTF_KEYDOWN )
358- _keyboardEvent( ord(key.upper()), KEYEVENTF_KEYDOWN )
359- _keyboardEvent( ord(key.upper()), KEYEVENTF_KEYUP )
360+
361+ if isinstance(key, basestring):
362+ if key in KEY_MAPPING:
363+ key_code = KEY_MAPPING[key]
364+ else:
365+ key_code = ord(key.upper())
366+ else:
367+ key_code = key
368+
369+ _keyboardEvent( key_code, KEYEVENTF_KEYDOWN )
370+ _keyboardEvent( key_code, KEYEVENTF_KEYUP )
371+
372 _keyboardEvent( win32con.VK_MENU, KEYEVENTF_KEYUP )
373
374
375+def typeShiftKey( key ):
376+ """
377+ Given a character literal, simulates holding the Shift key down
378+ and typing that character.
379+ """
380+
381+ _keyboardEvent( win32con.VK_LSHIFT, KEYEVENTF_KEYDOWN )
382+
383+ if isinstance(key, basestring):
384+ if key in KEY_MAPPING:
385+ key_code = KEY_MAPPING[key]
386+ else:
387+ key_code = ord(key.upper())
388+ else:
389+ key_code = key
390+
391+ _keyboardEvent( key_code, KEYEVENTF_KEYDOWN )
392+ _keyboardEvent( key_code, KEYEVENTF_KEYUP )
393+
394+ _keyboardEvent( win32con.VK_LSHIFT, KEYEVENTF_KEYUP )
395+
396+
397 def tapKey( keyCode ):
398 """
399 Given a virtual key code, simulates tapping that key.
400 """
401-
402+
403 _keyboardEvent( keyCode, KEYEVENTF_KEYDOWN )
404 _keyboardEvent( keyCode, KEYEVENTF_KEYUP )
405
406@@ -384,43 +481,10 @@
407 # keydown, keyup, keypress, and pauses.
408
409 keys = keys.split( " " )
410- mapping = { "F1" : win32con.VK_F1,
411- "F2" : win32con.VK_F2,
412- "F3" : win32con.VK_F3,
413- "F4" : win32con.VK_F4,
414- "F5" : win32con.VK_F5,
415- "F6" : win32con.VK_F6,
416- "F7" : win32con.VK_F7,
417- "F8" : win32con.VK_F8,
418- "F9" : win32con.VK_F9,
419- "F10": win32con.VK_F10,
420- "F11": win32con.VK_F11,
421- "F12": win32con.VK_F12,
422- "CD" : win32con.VK_LCONTROL,
423- "CU" : win32con.VK_LCONTROL,
424- "SD" : win32con.VK_LSHIFT,
425- "SU" : win32con.VK_LSHIFT,
426- "AD" : win32con.VK_MENU,
427- "AU" : win32con.VK_MENU,
428- "ID" : win32con.VK_LWIN,
429- "IU" : win32con.VK_LWIN,
430- "LA": win32con.VK_LEFT,
431- "RA": win32con.VK_RIGHT,
432- "ESC": win32con.VK_ESCAPE,
433- "INS": win32con.VK_INSERT,
434- "DEL": win32con.VK_DELETE
435- }
436
437 for key in keys:
438 key = key.upper()
439-
440- # Any one-character code means tap and release that literal key.
441- if len(key) == 1:
442- key_code = ord( key )
443- _keyboardEvent( key_code, KEYEVENTF_KEYDOWN )
444- _keyboardEvent( key_code, KEYEVENTF_KEYUP )
445- continue
446-
447+
448 # "W##" means wait
449 if key[0] == "W":
450 time.sleep( float(key[1:]) )
451@@ -430,26 +494,30 @@
452 # keybd_event function, and therefore don't use our keyboard
453 # event wrapper.
454 if key in ["SD", "AD", "ID", "CD"]:
455- win32api.keybd_event( mapping[key], 0, KEYEVENTF_KEYDOWN, 0 )
456+ win32api.keybd_event( KEY_MAPPING[key], 0, KEYEVENTF_KEYDOWN, 0 )
457 continue
458 if key in ["SU", "AU", "IU", "CU"]:
459- win32api.keybd_event( mapping[key], 0, KEYEVENTF_KEYUP, 0 )
460+ win32api.keybd_event( KEY_MAPPING[key], 0, KEYEVENTF_KEYUP, 0 )
461 continue
462
463- # Any other multi-character code means look up the code
464- # in the table above, and tap the key.
465- key_code = mapping[key]
466+ # Any one-character code means tap and release that literal key.
467+ if key in KEY_MAPPING:
468+ key_code = KEY_MAPPING[key]
469+ else:
470+ key_code = ord(key.upper())
471 _keyboardEvent( key_code, KEYEVENTF_KEYDOWN )
472 _keyboardEvent( key_code, KEYEVENTF_KEYUP )
473
474
475-def getForegroundClassNameUnicode():
476+def getForegroundClassNameUnicode(hwnd = None):
477 """
478- Returns a unicode string containing the class name of the frontmost
479+ Returns a unicode string containing the class name of the specified
480 application window.
481+ If hwnd parameter is None, frontmost window will be queried.
482 """
483
484- hwnd = win32gui.GetForegroundWindow()
485+ if hwnd is None:
486+ hwnd = win32gui.GetForegroundWindow()
487
488 # Maximum number of chars we'll accept for the class name; the
489 # rest will be truncated if it's longer than this.
490@@ -466,6 +534,38 @@
491 return classNameBuf.value
492
493
494+def getWindowClassName(hwnd = None):
495+ """
496+ Returns a unicode string containing the class name of the specified
497+ application window.
498+ If hwnd parameter is None, frontmost window will be queried.
499+ """
500+ return getForegroundClassNameUnicode(hwnd)
501+
502+
503+def getWindowProcessName(hwnd = None):
504+ """
505+ Returns a unicode string containing the process name of the specified
506+ application window (executable path).
507+ If hwnd parameter is None, frontmost window will be queried.
508+ """
509+ if hwnd is None:
510+ hwnd = win32gui.GetForegroundWindow()
511+ # Get PID so we can get process name
512+ _, process_id = win32process.GetWindowThreadProcessId(hwnd)
513+ # Get process name
514+ phandle = win32api.OpenProcess(
515+ win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ,
516+ False,
517+ process_id)
518+ if phandle:
519+ pexe = win32process.GetModuleFileNameEx(phandle, 0)
520+ pexe = os.path.normcase(os.path.normpath(pexe))
521+ return pexe
522+ else:
523+ return None
524+
525+
526 # ----------------------------------------------------------------------------
527 # Exception
528 # ----------------------------------------------------------------------------
529@@ -475,5 +575,5 @@
530 Exception raised if the clipboard was unable to be opened after
531 multiple attempts.
532 """
533-
534+
535 pass
536
537=== modified file 'enso/platform/win32/selection/__init__.py'
538--- enso/platform/win32/selection/__init__.py 2008-04-27 11:12:38 +0000
539+++ enso/platform/win32/selection/__init__.py 2010-07-30 13:46:41 +0000
540@@ -70,7 +70,7 @@
541 textSelContext = TextSelection.get()
542
543 # Trying to "set" a file selection doesn't do anything.
544- textSelContext.replaceSelection( sel_dict )
545+ return textSelContext.replaceSelection( sel_dict )
546
547
548 # ----------------------------------------------------------------------------

Subscribers

People subscribed via source and target branches

to status/vote changes: