23
23
24
24
"""
25
25
26
+ from __future__ import print_function
27
+
26
28
import locale
27
29
import logging
28
30
from subprocess import Popen , PIPE
31
+ import sys
29
32
30
33
import psutil
31
34
from six import binary_type
@@ -39,6 +42,9 @@ class X11Window(BaseWindow):
39
42
The Window class is an interface to the window control and
40
43
placement APIs for X11.
41
44
45
+ Window control methods such as :meth:`close` will return ``True``
46
+ if successful.
47
+
42
48
"""
43
49
44
50
_log = logging .getLogger ("window" )
@@ -51,23 +57,31 @@ class X11Window(BaseWindow):
51
57
xprop = "xprop"
52
58
53
59
@classmethod
54
- def _run_xdotool_command (cls , arguments , error_on_failure = True ):
55
- return cls ._run_command (cls .xdotool , arguments , error_on_failure )
60
+ def _run_xdotool_command (cls , arguments ):
61
+ return cls ._run_command (cls .xdotool , arguments )
62
+
63
+ @classmethod
64
+ def _run_xdotool_command_simple (cls , arguments ):
65
+ # Run the command and return whether or not it succeeded based on
66
+ # the return code.
67
+ stdout , return_code = cls ._run_command (cls .xdotool , arguments )
68
+ if stdout : print (stdout )
69
+ return return_code == 0
56
70
57
71
@classmethod
58
- def _run_xprop_command (cls , arguments , error_on_failure = True ):
59
- return cls ._run_command (cls .xprop , arguments , error_on_failure )
72
+ def _run_xprop_command (cls , arguments ):
73
+ return cls ._run_command (cls .xprop , arguments )
60
74
61
75
@classmethod
62
- def _run_command (cls , command , arguments , error_on_failure = True ):
76
+ def _run_command (cls , command , arguments ):
63
77
"""
64
78
Run a command with arguments and return the result.
65
79
66
80
:param command: command to run
67
81
:type command: str
68
82
:param arguments: arguments to append
69
83
:type arguments: list
70
- :returns: stdout, stderr, return_code
84
+ :returns: stdout, return_code
71
85
:rtype: tuple
72
86
"""
73
87
arguments = [str (arg ) for arg in arguments ]
@@ -85,16 +99,13 @@ def _run_command(cls, command, arguments, error_on_failure=True):
85
99
if isinstance (stderr , binary_type ):
86
100
stderr = stderr .decode (encoding )
87
101
88
- # Handle non-zero return codes.
89
- if p .wait () > 0 and error_on_failure :
90
- print (stdout )
91
- print (stderr )
92
- raise RuntimeError ("%s command exited with non-zero return"
93
- " code %d" % (command , p .returncode ))
102
+ stderr = stderr .rstrip ()
103
+ if stderr :
104
+ print (stderr , file = sys .stderr )
94
105
95
106
# Return the process output and return code.
96
- return stdout .rstrip (), stderr . rstrip (), p .returncode
97
- except Exception as e :
107
+ return stdout .rstrip (), p .returncode
108
+ except OSError as e :
98
109
cls ._log .error ("Failed to execute command '%s': %s. Is "
99
110
"%s installed?" ,
100
111
full_readable_command , e , command )
@@ -105,16 +116,27 @@ def _run_command(cls, command, arguments, error_on_failure=True):
105
116
106
117
@classmethod
107
118
def get_foreground (cls ):
108
- window_id , _ , _ = cls ._run_xdotool_command (["getactivewindow" ])
109
- return cls .get_window (int (window_id ))
119
+ window_id , return_code = cls ._run_xdotool_command ([
120
+ "getactivewindow"
121
+ ])
122
+ if return_code == 0 :
123
+ return cls .get_window (int (window_id ))
124
+ else :
125
+ return cls .get_window (0 ) # return an invalid window
110
126
111
127
@classmethod
112
128
def get_all_windows (cls ):
113
129
# Get all window IDs using 'xdotool search'.
114
- output , _ , _ = cls ._run_xdotool_command (['search' , '--onlyvisible' ,
115
- '--name' , '' ])
116
- lines = [line for line in output .split ('\n ' ) if line ]
117
- windows = [cls .get_window (int (line )) for line in lines ]
130
+ stdout , return_code = cls ._run_xdotool_command ([
131
+ 'search' , '--onlyvisible' , '--name' , ''
132
+ ])
133
+ if return_code == 0 :
134
+ lines = [line for line in stdout .split ('\n ' ) if line ]
135
+ windows = [cls .get_window (int (line )) for line in lines ]
136
+ else :
137
+ # Return any windows found previously.
138
+ if stdout : print (stdout )
139
+ return list (cls ._windows_by_id .values ())
118
140
119
141
# Exclude window IDs that have no associated process ID.
120
142
result = []
@@ -140,9 +162,15 @@ def get_matching_windows(cls, executable=None, title=None):
140
162
args .append (title )
141
163
else :
142
164
args .append ('' )
143
- output , _ , _ = cls ._run_xdotool_command (args , False )
144
- lines = [line for line in output .split ('\n ' ) if line ]
145
- windows = [cls .get_window (int (line )) for line in lines ]
165
+ stdout , return_code = cls ._run_xdotool_command (args )
166
+ if return_code == 0 :
167
+ lines = [line for line in stdout .split ('\n ' ) if line ]
168
+ windows = [cls .get_window (int (line )) for line in lines ]
169
+ else :
170
+ # Use windows found previously.
171
+ if stdout : print (stdout )
172
+ windows = list (cls ._windows_by_id .values ())
173
+
146
174
matching = []
147
175
for window in windows :
148
176
if executable :
@@ -174,11 +202,12 @@ def _get_properties_from_xprop(self, *properties):
174
202
# This method retrieves windows properties by shelling out to xprop.
175
203
result = {}
176
204
args = ['-id' , self .id ] + list (properties )
177
- output , _ , return_code = self ._run_xprop_command (args , False )
205
+ stdout , return_code = self ._run_xprop_command (args )
178
206
if return_code > 0 :
207
+ if stdout : print (stdout )
179
208
return {}
180
209
181
- for line in output .split ('\n ' ):
210
+ for line in stdout .split ('\n ' ):
182
211
line = line .split (' =' , 1 )
183
212
if len (line ) != 2 :
184
213
continue
@@ -208,7 +237,12 @@ def _get_properties_from_xprop(self, *properties):
208
237
def _get_window_text (self ):
209
238
# Get the title text.
210
239
args = ['getwindowname' , self .id ]
211
- return self ._run_xdotool_command (args )[0 ]
240
+ stdout , return_code = self ._run_xdotool_command (args )
241
+ if return_code == 0 :
242
+ return stdout
243
+ else :
244
+ if stdout : print (stdout )
245
+ return ""
212
246
213
247
def _get_class_name (self ):
214
248
return (self ._get_properties_from_xprop ("WM_CLASS" )
@@ -364,9 +398,13 @@ def is_focused(self):
364
398
# Methods related to window geometry.
365
399
366
400
def get_position (self ):
367
- output , _ , _ = self ._run_xdotool_command (
401
+ stdout , return_code = self ._run_xdotool_command (
368
402
['getwindowgeometry' , '--shell' , self .id ])
369
- geometry = output .strip ().split ('\n ' )
403
+ if return_code > 0 :
404
+ if stdout : print (stdout )
405
+ return Rectangle (0 , 0 , 0 , 0 )
406
+
407
+ geometry = stdout .strip ().split ('\n ' )
370
408
geo = dict ([val .lower ()
371
409
for val in line .split ('=' )]
372
410
for line in geometry )
@@ -376,44 +414,58 @@ def get_position(self):
376
414
def set_position (self , rectangle ):
377
415
l , t , w , h = rectangle .ltwh
378
416
id = self .id
379
- self ._run_xdotool_command (
417
+ return self ._run_xdotool_command_simple (
380
418
['windowmove' , id , l , t ,
381
- 'windowsize' , id , w , h ])
419
+ 'windowsize' , id , w , h ]
420
+ )
382
421
383
422
#-----------------------------------------------------------------------
384
423
# Methods for miscellaneous window control.
385
424
386
425
def minimize (self ):
387
- self ._run_xdotool_command (['windowminimize' , self .id ])
426
+ # Attempt to minimize the window. Return the command's success.
427
+ return self ._run_xdotool_command_simple (['windowminimize' ,
428
+ self .id ])
388
429
389
430
def _toggle_maximize (self ):
390
431
# Doesn't seem possible with xdotool. We'll try pressing a-f10
391
- # with the window focused.
392
- self .set_foreground ()
393
- self ._run_xdotool_command (['keydown' , 'Alt_L' , 'key' , 'F10' ,
394
- 'keyup' , 'Alt_L' ])
432
+ # with the window focused. Only maximize if set_foreground()
433
+ # succeeded.
434
+ return self .set_foreground () and self ._run_xdotool_command_simple ([
435
+ 'keydown' , 'Alt_L' , 'key' , 'F10' , 'keyup' , 'Alt_L'
436
+ ])
395
437
396
438
def maximize (self ):
397
439
if not self .is_maximized :
398
- self ._toggle_maximize ()
440
+ return self ._toggle_maximize ()
441
+ return True # already maximized.
399
442
400
443
def restore (self ):
401
444
state = self .state
402
445
if self ._is_minimized (state ):
403
- self ._run_xdotool_command (['windowactivate' , self .id ])
446
+ return self ._run_xdotool_command_simple ([
447
+ 'windowactivate' , self .id
448
+ ])
404
449
elif self ._is_maximized (state ):
405
- self ._toggle_maximize ()
450
+ return self ._toggle_maximize ()
451
+ else :
452
+ # True if already restored or False if no _NET_WM_STATE.
453
+ return state is not None
406
454
407
455
def close (self ):
408
- self ._run_xdotool_command (['windowclose' , self .id ])
456
+ return self ._run_xdotool_command_simple (['windowclose' , self .id ])
409
457
410
458
def set_foreground (self ):
411
- if self .is_minimized :
412
- self .restore ()
459
+ # Restore if minimized.
460
+ if self .is_minimized and not self .restore ():
461
+ return False # restore() failed
413
462
if not self .is_focused :
414
463
id = '%i' % self .id
415
- self ._run_xdotool_command (['windowactivate' , id ,
416
- 'windowfocus' , id ])
464
+ return self ._run_xdotool_command_simple ([
465
+ 'windowactivate' , id , 'windowfocus' , id
466
+ ])
467
+
468
+ return True
417
469
418
470
def set_focus (self ):
419
471
"""
@@ -422,4 +474,6 @@ def set_focus(self):
422
474
This method will set the input focus, but will not necessarily bring
423
475
the window to the front.
424
476
"""
425
- self ._run_xdotool_command (['windowfocus' , self .id ])
477
+ return self ._run_xdotool_command_simple ([
478
+ 'windowfocus' , self .id
479
+ ])
0 commit comments