src/eric7/Snapshot/SnapshotWaylandGrabber.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9413
80c06d472826
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
15 from PyQt6.QtGui import QPixmap, QCursor 15 from PyQt6.QtGui import QPixmap, QCursor
16 from PyQt6.QtWidgets import QApplication 16 from PyQt6.QtWidgets import QApplication
17 17
18 try: 18 try:
19 from PyQt6.QtDBus import QDBusInterface, QDBusMessage 19 from PyQt6.QtDBus import QDBusInterface, QDBusMessage
20
20 DBusAvailable = True 21 DBusAvailable = True
21 except ImportError: 22 except ImportError:
22 DBusAvailable = False 23 DBusAvailable = False
23 24
24 from EricWidgets import EricMessageBox 25 from EricWidgets import EricMessageBox
29 30
30 31
31 class SnapshotWaylandGrabber(QObject): 32 class SnapshotWaylandGrabber(QObject):
32 """ 33 """
33 Class implementing a grabber object for non-Wayland desktops. 34 Class implementing a grabber object for non-Wayland desktops.
34 35
35 @signal grabbed(QPixmap) emitted after the grab operation is finished 36 @signal grabbed(QPixmap) emitted after the grab operation is finished
36 """ 37 """
38
37 grabbed = pyqtSignal(QPixmap) 39 grabbed = pyqtSignal(QPixmap)
38 40
39 def __init__(self, parent=None): 41 def __init__(self, parent=None):
40 """ 42 """
41 Constructor 43 Constructor
42 44
43 @param parent reference to the parent object 45 @param parent reference to the parent object
44 @type QObject 46 @type QObject
45 """ 47 """
46 super().__init__(parent) 48 super().__init__(parent)
47 49
48 from .SnapshotTimer import SnapshotTimer 50 from .SnapshotTimer import SnapshotTimer
51
49 self.__grabTimer = SnapshotTimer() 52 self.__grabTimer = SnapshotTimer()
50 self.__grabTimer.timeout.connect(self.__performGrab) 53 self.__grabTimer.timeout.connect(self.__performGrab)
51 54
52 def supportedModes(self): 55 def supportedModes(self):
53 """ 56 """
54 Public method to get the supported screenshot modes. 57 Public method to get the supported screenshot modes.
55 58
56 @return tuple of supported screenshot modes 59 @return tuple of supported screenshot modes
57 @rtype tuple of SnapshotModes 60 @rtype tuple of SnapshotModes
58 """ 61 """
59 if DBusAvailable and Globals.isKdeDesktop(): 62 if DBusAvailable and Globals.isKdeDesktop():
60 # __IGNORE_WARNING_Y114__ 63 # __IGNORE_WARNING_Y114__
70 SnapshotModes.SELECTEDWINDOW, 73 SnapshotModes.SELECTEDWINDOW,
71 SnapshotModes.RECTANGLE, 74 SnapshotModes.RECTANGLE,
72 ) 75 )
73 else: 76 else:
74 return () 77 return ()
75 78
76 def grab(self, mode, delay=0, captureCursor=False, 79 def grab(self, mode, delay=0, captureCursor=False, captureDecorations=False):
77 captureDecorations=False):
78 """ 80 """
79 Public method to perform a grab operation potentially after a delay. 81 Public method to perform a grab operation potentially after a delay.
80 82
81 @param mode screenshot mode 83 @param mode screenshot mode
82 @type ScreenshotModes 84 @type ScreenshotModes
83 @param delay delay in seconds 85 @param delay delay in seconds
84 @type int 86 @type int
85 @param captureCursor flag indicating to include the mouse cursor 87 @param captureCursor flag indicating to include the mouse cursor
90 """ 92 """
91 if not DBusAvailable: 93 if not DBusAvailable:
92 # just to play it safe 94 # just to play it safe
93 self.grabbed.emit(QPixmap()) 95 self.grabbed.emit(QPixmap())
94 return 96 return
95 97
96 self.__mode = mode 98 self.__mode = mode
97 self.__captureCursor = captureCursor 99 self.__captureCursor = captureCursor
98 self.__captureDecorations = captureDecorations 100 self.__captureDecorations = captureDecorations
99 if delay: 101 if delay:
100 self.__grabTimer.start(delay) 102 self.__grabTimer.start(delay)
101 else: 103 else:
102 QTimer.singleShot(200, self.__performGrab) 104 QTimer.singleShot(200, self.__performGrab)
103 105
104 def __performGrab(self): 106 def __performGrab(self):
105 """ 107 """
106 Private method to perform the grab operations. 108 Private method to perform the grab operations.
107 109
108 @exception RuntimeError raised to indicate an unsupported grab mode 110 @exception RuntimeError raised to indicate an unsupported grab mode
109 """ 111 """
110 if self.__mode not in ( 112 if self.__mode not in (
111 SnapshotModes.FULLSCREEN, SnapshotModes.SELECTEDSCREEN, 113 SnapshotModes.FULLSCREEN,
112 SnapshotModes.SELECTEDWINDOW, SnapshotModes.RECTANGLE, 114 SnapshotModes.SELECTEDSCREEN,
115 SnapshotModes.SELECTEDWINDOW,
116 SnapshotModes.RECTANGLE,
113 ): 117 ):
114 raise RuntimeError("unsupported grab mode given") 118 raise RuntimeError("unsupported grab mode given")
115 119
116 if self.__mode == SnapshotModes.FULLSCREEN: 120 if self.__mode == SnapshotModes.FULLSCREEN:
117 self.__grabFullscreen() 121 self.__grabFullscreen()
118 elif self.__mode == SnapshotModes.SELECTEDSCREEN: 122 elif self.__mode == SnapshotModes.SELECTEDSCREEN:
119 self.__grabSelectedScreen() 123 self.__grabSelectedScreen()
120 elif self.__mode == SnapshotModes.SELECTEDWINDOW: 124 elif self.__mode == SnapshotModes.SELECTEDWINDOW:
121 self.__grabSelectedWindow() 125 self.__grabSelectedWindow()
122 else: 126 else:
123 self.__grabRectangle() 127 self.__grabRectangle()
124 128
125 def __grabFullscreen(self): 129 def __grabFullscreen(self):
126 """ 130 """
127 Private method to grab the complete desktop. 131 Private method to grab the complete desktop.
128 """ 132 """
129 snapshot = QPixmap() 133 snapshot = QPixmap()
130 134
131 if Globals.isKdeDesktop(): 135 if Globals.isKdeDesktop():
132 interface = QDBusInterface( 136 interface = QDBusInterface(
133 "org.kde.KWin", 137 "org.kde.KWin", "/Screenshot", "org.kde.kwin.Screenshot"
134 "/Screenshot", 138 )
135 "org.kde.kwin.Screenshot" 139 reply = interface.call("screenshotFullscreen", self.__captureCursor)
136 )
137 reply = interface.call(
138 "screenshotFullscreen",
139 self.__captureCursor
140 )
141 if self.__checkReply(reply, 1): 140 if self.__checkReply(reply, 1):
142 filename = reply.arguments()[0] 141 filename = reply.arguments()[0]
143 if filename: 142 if filename:
144 snapshot = QPixmap(filename) 143 snapshot = QPixmap(filename)
145 with contextlib.suppress(OSError): 144 with contextlib.suppress(OSError):
147 elif Globals.isGnomeDesktop(): 146 elif Globals.isGnomeDesktop():
148 path = self.__temporaryFilename() 147 path = self.__temporaryFilename()
149 interface = QDBusInterface( 148 interface = QDBusInterface(
150 "org.gnome.Shell", 149 "org.gnome.Shell",
151 "/org/gnome/Shell/Screenshot", 150 "/org/gnome/Shell/Screenshot",
152 "org.gnome.Shell.Screenshot" 151 "org.gnome.Shell.Screenshot",
153 ) 152 )
154 reply = interface.call( 153 reply = interface.call("Screenshot", self.__captureCursor, False, path)
155 "Screenshot",
156 self.__captureCursor,
157 False,
158 path
159 )
160 if self.__checkReply(reply, 2): 154 if self.__checkReply(reply, 2):
161 filename = reply.arguments()[1] 155 filename = reply.arguments()[1]
162 if filename: 156 if filename:
163 snapshot = QPixmap(filename) 157 snapshot = QPixmap(filename)
164 with contextlib.suppress(OSError): 158 with contextlib.suppress(OSError):
165 os.remove(filename) 159 os.remove(filename)
166 160
167 self.grabbed.emit(snapshot) 161 self.grabbed.emit(snapshot)
168 162
169 def __grabSelectedScreen(self): 163 def __grabSelectedScreen(self):
170 """ 164 """
171 Private method to grab a selected screen. 165 Private method to grab a selected screen.
172 """ 166 """
173 snapshot = QPixmap() 167 snapshot = QPixmap()
174 168
175 if Globals.isKdeDesktop(): 169 if Globals.isKdeDesktop():
176 screen = QApplication.screenAt(QCursor.pos()) 170 screen = QApplication.screenAt(QCursor.pos())
177 try: 171 try:
178 screenId = QApplication.screens().index(screen) 172 screenId = QApplication.screens().index(screen)
179 except ValueError: 173 except ValueError:
180 # default to screen 0 174 # default to screen 0
181 screenId = 0 175 screenId = 0
182 176
183 # Step 2: grab the screen 177 # Step 2: grab the screen
184 interface = QDBusInterface( 178 interface = QDBusInterface(
185 "org.kde.KWin", 179 "org.kde.KWin", "/Screenshot", "org.kde.kwin.Screenshot"
186 "/Screenshot", 180 )
187 "org.kde.kwin.Screenshot" 181 reply = interface.call("screenshotScreen", screenId, self.__captureCursor)
188 )
189 reply = interface.call(
190 "screenshotScreen",
191 screenId,
192 self.__captureCursor
193 )
194 if self.__checkReply(reply, 1): 182 if self.__checkReply(reply, 1):
195 filename = reply.arguments()[0] 183 filename = reply.arguments()[0]
196 if filename: 184 if filename:
197 snapshot = QPixmap(filename) 185 snapshot = QPixmap(filename)
198 with contextlib.suppress(OSError): 186 with contextlib.suppress(OSError):
201 # Step 1: grab entire desktop 189 # Step 1: grab entire desktop
202 path = self.__temporaryFilename() 190 path = self.__temporaryFilename()
203 interface = QDBusInterface( 191 interface = QDBusInterface(
204 "org.gnome.Shell", 192 "org.gnome.Shell",
205 "/org/gnome/Shell/Screenshot", 193 "/org/gnome/Shell/Screenshot",
206 "org.gnome.Shell.Screenshot" 194 "org.gnome.Shell.Screenshot",
207 ) 195 )
208 reply = interface.call( 196 reply = interface.call(
209 "ScreenshotWindow", 197 "ScreenshotWindow",
210 self.__captureDecorations, 198 self.__captureDecorations,
211 self.__captureCursor, 199 self.__captureCursor,
212 False, 200 False,
213 path 201 path,
214 ) 202 )
215 if self.__checkReply(reply, 2): 203 if self.__checkReply(reply, 2):
216 filename = reply.arguments()[1] 204 filename = reply.arguments()[1]
217 if filename: 205 if filename:
218 snapshot = QPixmap(filename) 206 snapshot = QPixmap(filename)
219 with contextlib.suppress(OSError): 207 with contextlib.suppress(OSError):
220 os.remove(filename) 208 os.remove(filename)
221 209
222 # Step 2: extract the area of the screen containing 210 # Step 2: extract the area of the screen containing
223 # the cursor 211 # the cursor
224 if not snapshot.isNull(): 212 if not snapshot.isNull():
225 screen = QApplication.screenAt(QCursor.pos()) 213 screen = QApplication.screenAt(QCursor.pos())
226 geom = screen.geometry() 214 geom = screen.geometry()
227 snapshot = snapshot.copy(geom) 215 snapshot = snapshot.copy(geom)
228 216
229 self.grabbed.emit(snapshot) 217 self.grabbed.emit(snapshot)
230 218
231 def __grabSelectedWindow(self): 219 def __grabSelectedWindow(self):
232 """ 220 """
233 Private method to grab a selected window. 221 Private method to grab a selected window.
234 """ 222 """
235 snapshot = QPixmap() 223 snapshot = QPixmap()
236 224
237 if Globals.isKdeDesktop(): 225 if Globals.isKdeDesktop():
238 mask = 0 226 mask = 0
239 if self.__captureDecorations: 227 if self.__captureDecorations:
240 mask |= 1 228 mask |= 1
241 if self.__captureCursor: 229 if self.__captureCursor:
242 mask |= 2 230 mask |= 2
243 interface = QDBusInterface( 231 interface = QDBusInterface(
244 "org.kde.KWin", 232 "org.kde.KWin", "/Screenshot", "org.kde.kwin.Screenshot"
245 "/Screenshot", 233 )
246 "org.kde.kwin.Screenshot" 234 reply = interface.call("interactive", mask)
247 )
248 reply = interface.call(
249 "interactive",
250 mask
251 )
252 if self.__checkReply(reply, 1): 235 if self.__checkReply(reply, 1):
253 filename = reply.arguments()[0] 236 filename = reply.arguments()[0]
254 if filename: 237 if filename:
255 snapshot = QPixmap(filename) 238 snapshot = QPixmap(filename)
256 with contextlib.suppress(OSError): 239 with contextlib.suppress(OSError):
258 elif Globals.isGnomeDesktop(): 241 elif Globals.isGnomeDesktop():
259 path = self.__temporaryFilename() 242 path = self.__temporaryFilename()
260 interface = QDBusInterface( 243 interface = QDBusInterface(
261 "org.gnome.Shell", 244 "org.gnome.Shell",
262 "/org/gnome/Shell/Screenshot", 245 "/org/gnome/Shell/Screenshot",
263 "org.gnome.Shell.Screenshot" 246 "org.gnome.Shell.Screenshot",
264 ) 247 )
265 reply = interface.call( 248 reply = interface.call(
266 "ScreenshotWindow", 249 "ScreenshotWindow",
267 self.__captureDecorations, 250 self.__captureDecorations,
268 self.__captureCursor, 251 self.__captureCursor,
269 False, 252 False,
270 path 253 path,
271 ) 254 )
272 if self.__checkReply(reply, 2): 255 if self.__checkReply(reply, 2):
273 filename = reply.arguments()[1] 256 filename = reply.arguments()[1]
274 if filename: 257 if filename:
275 snapshot = QPixmap(filename) 258 snapshot = QPixmap(filename)
276 with contextlib.suppress(OSError): 259 with contextlib.suppress(OSError):
277 os.remove(filename) 260 os.remove(filename)
278 261
279 self.grabbed.emit(snapshot) 262 self.grabbed.emit(snapshot)
280 263
281 def __grabRectangle(self): 264 def __grabRectangle(self):
282 """ 265 """
283 Private method to grab a rectangular desktop area. 266 Private method to grab a rectangular desktop area.
284 """ 267 """
285 snapshot = QPixmap() 268 snapshot = QPixmap()
286 269
287 if Globals.isGnomeDesktop(): 270 if Globals.isGnomeDesktop():
288 # Step 1: let the user select the area 271 # Step 1: let the user select the area
289 interface = QDBusInterface( 272 interface = QDBusInterface(
290 "org.gnome.Shell", 273 "org.gnome.Shell",
291 "/org/gnome/Shell/Screenshot", 274 "/org/gnome/Shell/Screenshot",
292 "org.gnome.Shell.Screenshot" 275 "org.gnome.Shell.Screenshot",
293 ) 276 )
294 reply = interface.call("SelectArea") 277 reply = interface.call("SelectArea")
295 if self.__checkReply(reply, 4): 278 if self.__checkReply(reply, 4):
296 x, y, width, height = reply.arguments()[:4] 279 x, y, width, height = reply.arguments()[:4]
297 280
298 # Step 2: grab the selected area 281 # Step 2: grab the selected area
299 path = self.__temporaryFilename() 282 path = self.__temporaryFilename()
300 reply = interface.call( 283 reply = interface.call(
301 "ScreenshotArea", 284 "ScreenshotArea", x, y, width, height, False, path
302 x, y, width, height,
303 False,
304 path
305 ) 285 )
306 if self.__checkReply(reply, 2): 286 if self.__checkReply(reply, 2):
307 filename = reply.arguments()[1] 287 filename = reply.arguments()[1]
308 if filename: 288 if filename:
309 snapshot = QPixmap(filename) 289 snapshot = QPixmap(filename)
310 with contextlib.suppress(OSError): 290 with contextlib.suppress(OSError):
311 os.remove(filename) 291 os.remove(filename)
312 292
313 self.grabbed.emit(snapshot) 293 self.grabbed.emit(snapshot)
314 294
315 def __checkReply(self, reply, argumentsCount): 295 def __checkReply(self, reply, argumentsCount):
316 """ 296 """
317 Private method to check, if a reply is valid. 297 Private method to check, if a reply is valid.
318 298
319 @param reply reference to the reply message 299 @param reply reference to the reply message
320 @type QDBusMessage 300 @type QDBusMessage
321 @param argumentsCount number of expected arguments 301 @param argumentsCount number of expected arguments
322 @type int 302 @type int
323 @return flag indicating validity 303 @return flag indicating validity
324 @rtype bool 304 @rtype bool
325 """ 305 """
326 if reply.type() == QDBusMessage.MessageType.ReplyMessage: 306 if reply.type() == QDBusMessage.MessageType.ReplyMessage:
327 if len(reply.arguments()) == argumentsCount: 307 if len(reply.arguments()) == argumentsCount:
328 return True 308 return True
329 309
330 EricMessageBox.warning( 310 EricMessageBox.warning(
331 None, 311 None,
332 self.tr("Screenshot Error"), 312 self.tr("Screenshot Error"),
333 self.tr("<p>Received an unexpected number of reply arguments." 313 self.tr(
334 " Expected {0} but got {1}</p>").format( 314 "<p>Received an unexpected number of reply arguments."
315 " Expected {0} but got {1}</p>"
316 ).format(
335 argumentsCount, 317 argumentsCount,
336 len(reply.arguments()), 318 len(reply.arguments()),
337 )) 319 ),
338 320 )
321
339 elif reply.type() == QDBusMessage.MessageType.ErrorMessage: 322 elif reply.type() == QDBusMessage.MessageType.ErrorMessage:
340 EricMessageBox.warning( 323 EricMessageBox.warning(
341 None, 324 None,
342 self.tr("Screenshot Error"), 325 self.tr("Screenshot Error"),
343 self.tr("<p>Received error <b>{0}</b> from DBus while" 326 self.tr(
344 " performing screenshot.</p><p>{1}</p>").format( 327 "<p>Received error <b>{0}</b> from DBus while"
328 " performing screenshot.</p><p>{1}</p>"
329 ).format(
345 reply.errorName(), 330 reply.errorName(),
346 reply.errorMessage(), 331 reply.errorMessage(),
347 )) 332 ),
348 333 )
334
349 elif reply.type() == QDBusMessage.MessageType.InvalidMessage: 335 elif reply.type() == QDBusMessage.MessageType.InvalidMessage:
350 EricMessageBox.warning( 336 EricMessageBox.warning(
351 None, 337 None, self.tr("Screenshot Error"), self.tr("Received an invalid reply.")
352 self.tr("Screenshot Error"), 338 )
353 self.tr("Received an invalid reply.")) 339
354
355 else: 340 else:
356 EricMessageBox.warning( 341 EricMessageBox.warning(
357 None, 342 None,
358 self.tr("Screenshot Error"), 343 self.tr("Screenshot Error"),
359 self.tr("Received an unexpected reply.")) 344 self.tr("Received an unexpected reply."),
360 345 )
346
361 return False 347 return False
362 348
363 def __temporaryFilename(self): 349 def __temporaryFilename(self):
364 """ 350 """
365 Private method to generate a temporary filename. 351 Private method to generate a temporary filename.
366 352
367 @return path name for a unique, temporary file 353 @return path name for a unique, temporary file
368 @rtype str 354 @rtype str
369 """ 355 """
370 return "/tmp/eric-snap-{0}.png".format(uuid.uuid4().hex) # secok 356 return "/tmp/eric-snap-{0}.png".format(uuid.uuid4().hex) # secok

eric ide

mercurial