-
-
Save aspotton/1888298869c8adf59a577f2fe9d32fc8 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python | |
# Python screenshot tool (fullscreen/area selection) | |
import sys | |
import os | |
from io import BytesIO | |
from PyQt5 import QtCore, QtGui | |
from PyQt5.QtGui import QPixmap, QScreen | |
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QSizePolicy, QGroupBox, QSpinBox, QCheckBox, QGridLayout, QPushButton, QHBoxLayout, QVBoxLayout, QFileDialog | |
from subprocess import getoutput | |
from io import StringIO | |
from Xlib import X, display, Xutil | |
# Documentation for python-xlib here: | |
# http://python-xlib.sourceforge.net/doc/html/index.html | |
class XSelect: | |
def __init__(self, display): | |
# X display | |
self.d = display | |
# Screen | |
self.screen = self.d.screen() | |
# Draw on the root window (desktop surface) | |
self.window = self.screen.root | |
# If only I could get this working... | |
#cursor = xobject.cursor.Cursor(self.d, Xcursorfont.crosshair) | |
#cursor = self.d.create_resource_object('cursor', Xcursorfont.X_cursor) | |
self.cursor = X.NONE | |
colormap = self.screen.default_colormap | |
color = colormap.alloc_color(0, 0, 0) | |
# Xor it because we'll draw with X.GXxor function | |
xor_color = color.pixel ^ 0xffffff | |
self.gc = self.window.create_gc( | |
line_width = 1, | |
line_style = X.LineSolid, | |
fill_style = X.FillOpaqueStippled, | |
fill_rule = X.WindingRule, | |
cap_style = X.CapButt, | |
join_style = X.JoinMiter, | |
foreground = xor_color, | |
background = self.screen.black_pixel, | |
function = X.GXxor, | |
graphics_exposures = False, | |
subwindow_mode = X.IncludeInferiors, | |
) | |
def get_mouse_selection(self): | |
started = False | |
start = dict(x=0, y=0) | |
end = dict(x=0, y=0) | |
last = None | |
drawlimit = 10 | |
i = 0 | |
self.window.grab_pointer(self.d, X.PointerMotionMask|X.ButtonReleaseMask|X.ButtonPressMask, | |
X.GrabModeAsync, X.GrabModeAsync, X.NONE, self.cursor, X.CurrentTime) | |
self.window.grab_keyboard(self.d, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) | |
while True: | |
e = self.d.next_event() | |
# Window has been destroyed, quit | |
if e.type == X.DestroyNotify: | |
break | |
# Mouse button press | |
elif e.type == X.ButtonPress: | |
# Left mouse button? | |
if e.detail == 1: | |
start = dict(x=e.root_x, y=e.root_y) | |
started = True | |
# Right mouse button? | |
elif e.detail == 3: | |
return | |
# Mouse button release | |
elif e.type == X.ButtonRelease: | |
end = dict(x=e.root_x, y=e.root_y) | |
if last: | |
self.draw_rectangle(start, last) | |
break | |
# Mouse movement | |
elif e.type == X.MotionNotify and started: | |
i = i + 1 | |
if i % drawlimit != 0: | |
continue | |
if last: | |
self.draw_rectangle(start, last) | |
last = None | |
last = dict(x=e.root_x, y=e.root_y) | |
self.draw_rectangle(start, last) | |
self.d.ungrab_keyboard(X.CurrentTime) | |
self.d.ungrab_pointer(X.CurrentTime) | |
self.d.sync() | |
coords = self.get_coords(start, end) | |
if coords['width'] <= 1 or coords['height'] <= 1: | |
return | |
return [coords['start']['x'], coords['start']['y'], coords['width'], coords['height']] | |
def get_coords(self, start, end): | |
safe_start = dict(x=0, y=0) | |
safe_end = dict(x=0, y=0) | |
if start['x'] > end['x']: | |
safe_start['x'] = end['x'] | |
safe_end['x'] = start['x'] | |
else: | |
safe_start['x'] = start['x'] | |
safe_end['x'] = end['x'] | |
if start['y'] > end['y']: | |
safe_start['y'] = end['y'] | |
safe_end['y'] = start['y'] | |
else: | |
safe_start['y'] = start['y'] | |
safe_end['y'] = end['y'] | |
return { | |
'start': { | |
'x': safe_start['x'], | |
'y': safe_start['y'], | |
}, | |
'end': { | |
'x': safe_end['x'], | |
'y': safe_end['y'], | |
}, | |
'width' : safe_end['x'] - safe_start['x'], | |
'height': safe_end['y'] - safe_start['y'], | |
} | |
def draw_rectangle(self, start, end): | |
coords = self.get_coords(start, end) | |
self.window.rectangle(self.gc, | |
coords['start']['x'], | |
coords['start']['y'], | |
coords['end']['x'] - coords['start']['x'], | |
coords['end']['y'] - coords['start']['y'] | |
) | |
class Screenshot(QWidget): | |
def __init__(self): | |
super(Screenshot, self).__init__() | |
self.screenshotLabel = QLabel() | |
self.screenshotLabel.setSizePolicy(QSizePolicy.Expanding, | |
QSizePolicy.Expanding) | |
self.screenshotLabel.setAlignment(QtCore.Qt.AlignCenter) | |
self.screenshotLabel.setMinimumSize(240, 160) | |
self.createOptionsGroupBox() | |
self.createButtonsLayout() | |
mainLayout = QVBoxLayout() | |
mainLayout.addWidget(self.screenshotLabel) | |
mainLayout.addWidget(self.optionsGroupBox) | |
mainLayout.addLayout(self.buttonsLayout) | |
self.setLayout(mainLayout) | |
self.area = None | |
self.shootScreen() | |
self.delaySpinBox.setValue(1) | |
self.setWindowTitle("Screenshot") | |
self.resize(300, 200) | |
def resizeEvent(self, event): | |
scaledSize = self.originalPixmap.size() | |
scaledSize.scale(self.screenshotLabel.size(), QtCore.Qt.KeepAspectRatio) | |
if not self.screenshotLabel.pixmap() or scaledSize != self.screenshotLabel.pixmap().size(): | |
self.updateScreenshotLabel() | |
def selectArea(self): | |
self.hide() | |
xs = XSelect(display.Display()) | |
self.area = xs.get_mouse_selection() | |
if self.area: | |
xo, yo, x, y = self.area | |
self.areaLabel.setText("Area: x%s y%s to x%s y%s" % (xo, yo, x, y)) | |
self.shootScreen() | |
else: | |
self.areaLabel.setText("Area: fullscreen") | |
self.shootScreen() | |
self.show() | |
def newScreenshot(self): | |
if self.hideThisWindowCheckBox.isChecked(): | |
self.hide() | |
self.newScreenshotButton.setDisabled(True) | |
QtCore.QTimer.singleShot(self.delaySpinBox.value() * 1000, self.shootScreen) | |
def saveScreenshot(self): | |
format = 'png' | |
initialPath = QtCore.QDir.currentPath() + "/untitled." + format | |
fileName, fileSuffixSelection = QFileDialog.getSaveFileName( | |
self, | |
"Save As", | |
initialPath, | |
"{} Files (*.{});;All Files (*)".format(format.upper(), format)) | |
if fileName: | |
self.originalPixmap.save(str(fileName), format) | |
def copyToClipboard(self): | |
if not self.originalPixmap: | |
return | |
qi = self.originalPixmap.toImage() | |
QApplication.clipboard().setImage(qi) | |
def shootScreen(self): | |
if self.delaySpinBox.value() != 0: | |
QApplication.beep() | |
# Garbage collect any existing image first. | |
self.originalPixmap = None | |
screen = QApplication.primaryScreen() | |
self.originalPixmap = screen.grabWindow(QApplication.desktop().winId()) | |
if self.area is not None: | |
qi = self.originalPixmap.toImage() | |
qi = qi.copy(int(self.area[0]), int(self.area[1]), int(self.area[2]), int(self.area[3])) | |
self.originalPixmap = None | |
self.originalPixmap = QPixmap.fromImage(qi) | |
self.updateScreenshotLabel() | |
self.newScreenshotButton.setDisabled(False) | |
if self.hideThisWindowCheckBox.isChecked(): | |
self.show() | |
def updateCheckBox(self): | |
if self.delaySpinBox.value() == 0: | |
self.hideThisWindowCheckBox.setDisabled(True) | |
else: | |
self.hideThisWindowCheckBox.setDisabled(False) | |
def createOptionsGroupBox(self): | |
self.optionsGroupBox = QGroupBox("Options") | |
self.delaySpinBox = QSpinBox() | |
self.delaySpinBox.setSuffix(" s") | |
self.delaySpinBox.setMaximum(60) | |
self.delaySpinBox.valueChanged.connect(self.updateCheckBox) | |
self.delaySpinBoxLabel = QLabel("Screenshot Delay:") | |
self.hideThisWindowCheckBox = QCheckBox("Hide This Window") | |
self.hideThisWindowCheckBox.setChecked(True) | |
self.areaLabel = QLabel("Area: fullscreen") | |
optionsGroupBoxLayout = QGridLayout() | |
optionsGroupBoxLayout.addWidget(self.delaySpinBoxLabel, 0, 0) | |
optionsGroupBoxLayout.addWidget(self.delaySpinBox, 0, 1) | |
optionsGroupBoxLayout.addWidget(self.hideThisWindowCheckBox, 1, 0) | |
optionsGroupBoxLayout.addWidget(self.areaLabel, 1, 1) | |
self.optionsGroupBox.setLayout(optionsGroupBoxLayout) | |
def createButtonsLayout(self): | |
self.selectAreaButton = self.createButton( | |
"Select Area", | |
self.selectArea | |
) | |
self.newScreenshotButton = self.createButton( | |
"New Screenshot", | |
self.newScreenshot | |
) | |
self.copyScreenshotButton = self.createButton( | |
"Copy to Clipboard", | |
self.copyToClipboard | |
) | |
self.saveScreenshotButton = self.createButton( | |
"Save Screenshot", | |
self.saveScreenshot | |
) | |
self.quitScreenshotButton = self.createButton( | |
"Quit", | |
self.close | |
) | |
self.buttonsLayout = QHBoxLayout() | |
self.buttonsLayout.addStretch() | |
self.buttonsLayout.addWidget(self.selectAreaButton) | |
self.buttonsLayout.addWidget(self.newScreenshotButton) | |
self.buttonsLayout.addWidget(self.copyScreenshotButton) | |
self.buttonsLayout.addWidget(self.saveScreenshotButton) | |
self.buttonsLayout.addWidget(self.quitScreenshotButton) | |
def createButton(self, text, member): | |
button = QPushButton(text) | |
button.clicked.connect(member) | |
return button | |
def updateScreenshotLabel(self): | |
self.screenshotLabel.setPixmap(self.originalPixmap.scaled( | |
self.screenshotLabel.size(), QtCore.Qt.KeepAspectRatio, | |
QtCore.Qt.SmoothTransformation)) | |
if __name__ == '__main__': | |
app = QApplication(sys.argv) | |
screenshot = Screenshot() | |
screenshot.show() | |
sys.exit(app.exec_()) |
What platforms does this work on? I saw a comment on the code you forked from suggesting it didn't work on Windows.
What platforms does this work on? I saw a comment on the code you forked from suggesting it didn't work on Windows.
AFAIK it doesn't work on Windows due to how it uses the X server to get the mouse position, but I haven't tried it on anything other than Linux/Ubuntu.
Hi Adam,
I was looking for a piece of python code to take screenshots and I found yours.
When I saw the comment "If only I could get this working..." about changing the cursor when grabbing an area, I went looking for a solution and found one here.
After, I added these few lines of code instead of self.cursor = X.None.
from Xlib import X, display, Xcursorfont
and
`# Create font cursor
font = display.open_font('cursor')
self.cursor = font.create_glyph_cursor(font, Xcursorfont.crosshair, Xcursorfont.crosshair+1, (65535, 65535, 65535), (0, 0, 0))`
then in get_mouse_selection function
self.window.grab_pointer(self.d, X.PointerMotionMask|X.ButtonReleaseMask|X.ButtonPressMask, X.GrabModeAsync, X.GrabModeAsync, X.NONE, self.cursor, X.CurrentTime)
I hope this will help someone.
Updates