Skip to content

Instantly share code, notes, and snippets.

@frakman1
Last active December 30, 2022 14:18
Show Gist options
  • Save frakman1/600c597521ed2cdf9e9ecdb5aaaffc31 to your computer and use it in GitHub Desktop.
Save frakman1/600c597521ed2cdf9e9ecdb5aaaffc31 to your computer and use it in GitHub Desktop.
screenshot utility
#!/usr/bin/env python
# Python screenshot tool (fullscreen/area selection)
# Ported from https://gist.github.com/initbrain/6628609
# Tested on Windows 7 64bit using PyQt5 and python 3.7
# area selection NOT working
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from subprocess import getoutput
from io import StringIO as StringIO
from Xlib import X, display, Xutil
import fcntl
# 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)
cursor = X.NONE
self.window.grab_pointer(1, X.PointerMotionMask|X.ButtonReleaseMask|X.ButtonPressMask,
X.GrabModeAsync, X.GrabModeAsync, X.NONE, cursor, X.CurrentTime)
self.window.grab_keyboard(1, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime)
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,
)
done = False
started = False
start = dict(x=0, y=0)
end = dict(x=0, y=0)
last = None
drawlimit = 10
i = 0
while done == False:
e = self.d.next_event()
# Window has been destroyed, quit
if e.type == X.DestroyNotify:
sys.exit(0)
# 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:
sys.exit(0)
# 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)
done = True
pass
# 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)
pass
# Keyboard key
elif e.type == X.KeyPress:
sys.exit(0)
self.d.flush()
coords = self.get_coords(start, end)
if coords['width'] <= 1 or coords['height'] <= 1:
print("Exiting")
sys.exit(0)
else:
print("%d %d %d %d".format((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(QtWidgets.QWidget):
def __init__(self):
super(Screenshot, self).__init__()
self.screenshotLabel = QtWidgets.QLabel()
self.screenshotLabel.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
self.screenshotLabel.setAlignment(QtCore.Qt.AlignCenter)
self.screenshotLabel.setMinimumSize(240, 160)
self.createOptionsGroupBox()
self.createButtonsLayout()
mainLayout = QtWidgets.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()
resArea = getoutput('python %s -A' % sys.argv[0]) #TODO horrible 1/2 !
print("resArea: {}\n\n".format((resArea)))
if resArea and resArea != '0 0 0 0':
self.area = xo, yo, x, y = resArea.split()
self.areaLabel.setText("Area: x%s y%s to x%s y%s" % (xo, yo, x, y))
self.shootScreen()
# print 'OK', self.area #DEBUG
elif self.area is not None:
self.area = None
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 = QtWidgets.QFileDialog.getSaveFileName(self, "Save As", initialPath, "%s Files (*.%s);;All Files (*)" % (format.upper(), format))
img, _ = QtWidgets.QFileDialog.getSaveFileName(self,"Salvar Arquivo", filter="PNG(*.png);; JPEG(*.jpg)")
if img[-3:] == "png":
self.originalPixmap.save(img, "png")
elif img[-3:] == "jpg":
self.originalPixmap.save(img, "jpg")
#if fileName:
# self.originalPixmap.save(fileName, format)
def shootScreen(self):
if self.delaySpinBox.value() != 0:
QtWidgets.qApp.beep()
# Garbage collect any existing image first.
self.originalPixmap = None
self.originalPixmap = QtGui.QScreen.grabWindow(app.primaryScreen(), app.desktop().winId())
if self.area is not None:
qi = self.originalPixmap.toImage()
size = qi.size()
qi = qi.copy(int(self.area[0]), int(self.area[1]), int(self.area[2]), int(self.area[3]))
self.originalPixmap = None
self.originalPixmap = QtGui.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 = QtWidgets.QGroupBox("Options")
self.delaySpinBox = QtWidgets.QSpinBox()
self.delaySpinBox.setSuffix(" s")
self.delaySpinBox.setMaximum(60)
self.delaySpinBox.valueChanged.connect(self.updateCheckBox)
self.delaySpinBoxLabel = QtWidgets.QLabel("Screenshot Delay:")
self.hideThisWindowCheckBox = QtWidgets.QCheckBox("Hide This Window")
self.hideThisWindowCheckBox.setChecked(True)
self.areaLabel = QtWidgets.QLabel("Area: fullscreen")
optionsGroupBoxLayout = QtWidgets.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.saveScreenshotButton = self.createButton("Save Screenshot",
self.saveScreenshot)
self.quitScreenshotButton = self.createButton("Quit", self.close)
self.buttonsLayout = QtWidgets.QHBoxLayout()
self.buttonsLayout.addStretch()
self.buttonsLayout.addWidget(self.selectAreaButton)
self.buttonsLayout.addWidget(self.newScreenshotButton)
self.buttonsLayout.addWidget(self.saveScreenshotButton)
self.buttonsLayout.addWidget(self.quitScreenshotButton)
def createButton(self, text, member):
button = QtWidgets.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__':
import sys
#TODO horrible 2/2 !
if len(sys.argv) == 2 and sys.argv[1] == '-A':
# Grab the display while hiding stdout to work around https://bugs.launchpad.net/listen/+bug/561707
stdout = sys.stdout
sys.stdout = StringIO()
d = display.Display()
sys.stdout = stdout
XSelect(d)
else:
app = QtWidgets.QApplication(sys.argv)
screenshot = Screenshot()
screenshot.show()
sys.exit(app.exec_())
@frakman1
Copy link
Author

Looks like this when run:

image

Currently fails when using the Select Area with this error:

resArea: Traceback (most recent call last):
  File "screenshot.py", line 318, in <module>
    d = display.Display()
  File "C:\Users\frak\AppData\Local\Programs\Python\Python37\lib\site-packages\python_xlib-0.24-py3.7.egg\Xlib\display.py", line 89, in __init__
  File "C:\Users\frak\AppData\Local\Programs\Python\Python37\lib\site-packages\python_xlib-0.24-py3.7.egg\Xlib\display.py", line 71, in __init__
  File "C:\Users\frak\AppData\Local\Programs\Python\Python37\lib\site-packages\python_xlib-0.24-py3.7.egg\Xlib\protocol\display.py", line 85, in __init__
  File "C:\Users\frak\AppData\Local\Programs\Python\Python37\lib\site-packages\python_xlib-0.24-py3.7.egg\Xlib\support\connect.py", line 72, in get_display
  File "C:\Users\frak\AppData\Local\Programs\Python\Python37\lib\site-packages\python_xlib-0.24-py3.7.egg\Xlib\support\connect.py", line 55, in _relative_import
  File "C:\Users\frak\AppData\Local\Programs\Python\Python37\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 668, in _load_unlocked
  File "<frozen importlib._bootstrap>", line 638, in _load_backward_compatible
  File "C:\Users\frak\AppData\Local\Programs\Python\Python37\lib\site-packages\python_xlib-0.24-py3.7.egg\Xlib\support\unix_connect.py", line 31, in <module>
ModuleNotFoundError: No module named 'fcntl'


Traceback (most recent call last):
  File "screenshot.py", line 197, in selectArea
    self.area = xo, yo, x, y = resArea.split()
ValueError: too many values to unpack (expected 4)

@prabhav131
Copy link

Hi @frakman1 @frakman1 I am facing the same problem. Found any way around it yet? I would be really grateful. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment