from PySide6 import QtWidgets, QtGui, QtCore
import os
from datetime import datetime
[docs]
class ScreenCapture(QtWidgets.QDialog):
"""A dialog for capturing a selected area of the screen."""
def __init__(self, parent=None, path_object=None, output=None):
super(ScreenCapture, self).__init__(parent)
self.path_object = path_object
self.click_position = None
# Determine output path
file_name = datetime.now().strftime("screen_grab_%Y-%m-%d_at_%H.%M.%S.png")
self.output_path = self._determine_output_path(output, file_name)
# Ensure output directory exists
os.makedirs(os.path.dirname(self.output_path), exist_ok=True)
# Initialize UI properties
self.rectangle = QtCore.QRect()
self._configure_window()
self.set_screen_area()
self._connect_screen_signals()
def _determine_output_path(self, output, file_name):
"""Determines the output path for the screenshot."""
if output:
return output
if self.path_object:
return self.path_object.get_preview_path(ext=".png")
return os.path.expanduser(f"~/Desktop/{file_name}").replace("\\", "/")
def _configure_window(self):
"""Configures the window properties."""
self.setWindowFlags(
QtCore.Qt.FramelessWindowHint
| QtCore.Qt.WindowStaysOnTopHint
| QtCore.Qt.CustomizeWindowHint
| QtCore.Qt.Tool
)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.setCursor(QtCore.Qt.CrossCursor)
self.setMouseTracking(True)
def _connect_screen_signals(self):
"""Connects signals for screen changes to update geometry."""
screens = QtGui.QGuiApplication.screens()
for screen in screens:
screen.geometryChanged.connect(self.set_screen_area)
primary_screen = QtGui.QGuiApplication.primaryScreen()
if primary_screen:
primary_screen.virtualGeometryChanged.connect(self.set_screen_area)
[docs]
def set_screen_area(self):
"""Sets the screen area to the total desktop size."""
screens = QtGui.QGuiApplication.screens()
total_desktop = QtCore.QRect()
for screen in screens:
total_desktop = total_desktop.united(screen.geometry())
self.setGeometry(total_desktop)
[docs]
@classmethod
def grab_window(cls):
"""Launches the ScreenCapture dialog."""
temp = ScreenCapture()
temp.exec()
[docs]
def get_rectangle(self):
"""Returns the selected rectangle area."""
return self.rectangle
[docs]
def mousePressEvent(self, event):
"""Handles the mouse press event to start area selection."""
self.click_position = (
event.globalPosition().toPoint()
) # โ
Fix deprecated method
print(f"๐ฑ๏ธ Mouse pressed at: {self.click_position}")
[docs]
def mouseReleaseEvent(self, event):
"""Handles the mouse release event to capture the selected area."""
self.rectangle = QtCore.QRect(
self.click_position, event.globalPosition().toPoint()
).normalized()
self.click_position = None
if self.rectangle.width() == 0 or self.rectangle.height() == 0:
print("โ Invalid selection! Capture area too small.")
self.close()
return
print(f"๐ธ Selected region: {self.rectangle}")
# ๐ Hide the overlay before capture
self.hide()
QtWidgets.QApplication.processEvents() # Ensure UI updates before capture
# ๐ธ Capture the selected area
pix = capture_area(self.rectangle, self.output_path)
# ๐ Close the dialog after capturing
self.close()
return pix
[docs]
def mouseMoveEvent(self, event):
"""Repaints while moving the mouse."""
self.repaint()
[docs]
def paintEvent(self, event):
"""Custom paint event for drawing selection overlay."""
mouse_pos = self.mapFromGlobal(QtGui.QCursor.pos())
click_pos = (
self.mapFromGlobal(self.click_position) if self.click_position else None
)
qp = QtGui.QPainter(self)
qp.setBrush(QtGui.QColor(0, 0, 0, 150)) # Darken overlay
qp.setPen(QtCore.Qt.NoPen)
qp.drawRect(event.rect()) # Darken the whole screen
if click_pos:
self.rectangle = QtCore.QRect(click_pos, mouse_pos)
qp.setCompositionMode(QtGui.QPainter.CompositionMode_Clear)
qp.drawRect(self.rectangle)
qp.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
# Draw cropping markers
if click_pos:
pen = QtGui.QPen(QtGui.QColor("white"), 3, QtCore.Qt.SolidLine)
qp.setPen(pen)
qp.drawLine(
mouse_pos.x(), click_pos.y(), mouse_pos.x(), mouse_pos.y()
) # Left
qp.drawLine(
click_pos.x(), click_pos.y(), mouse_pos.x(), click_pos.y()
) # Top
qp.drawLine(
click_pos.x(), click_pos.y(), click_pos.x(), mouse_pos.y()
) # Right
qp.drawLine(
click_pos.x(), mouse_pos.y(), mouse_pos.x(), mouse_pos.y()
) # Bottom
[docs]
def capture_area(rect, output_path):
"""
Captures the selected screen area, ensuring it works on multiple monitors.
Args:
rect (QRect): The selected area to capture.
output_path (str): File path to save the captured image_plane.
Returns:
QPixmap: The captured screen area as a QPixmap.
"""
screens = QtGui.QGuiApplication.screens()
# Identify the screen that contains the selection
selected_screen = None
for screen in screens:
if screen.geometry().contains(
rect.center()
): # Check if center of rect is in this screen
selected_screen = screen
break
if not selected_screen:
print("โ No screen detected for the selected area!")
return None
# Convert coordinates to screen-relative space
screen_geometry = selected_screen.geometry()
adjusted_rect = rect.translated(
-screen_geometry.topLeft()
) # Adjust for screen offset
# Ensure valid size
if adjusted_rect.width() == 0 or adjusted_rect.height() == 0:
print("โ Invalid capture area. Selection might be too small.")
return None
# Hide overlay before capture
QtCore.QThread.msleep(100)
QtWidgets.QApplication.processEvents()
# Capture the adjusted area from the correct screen
pixmap = selected_screen.grabWindow(
0,
adjusted_rect.x(),
adjusted_rect.y(),
adjusted_rect.width(),
adjusted_rect.height(),
)
# Debugging Output
print(
f"๐ธ Capturing region: {rect}, Adjusted: {adjusted_rect}, from screen: {selected_screen.name()}, Output path: {output_path}"
)
# Save the captured image_plane
if not pixmap.isNull():
pixmap.save(output_path, "PNG")
print(f"โ
Screenshot saved at {output_path}")
else:
print("โ Failed to capture screenshot. Image is null.")
return pixmap
[docs]
def run(path_object=None, output="thumb", parent=None):
"""
Runs the screen capture tool.
Args:
path_object (object, optional): Path object.
output (str, optional): Output file path.
parent (QWidget, optional): Parent widget.
Returns:
str: The path to the saved screenshot.
"""
temp = ScreenCapture(parent=parent, path_object=path_object, output=output)
temp.exec()
print(f"๐ธ Screen capture saved to: {temp.output_path}")
return temp.output_path
if __name__ == "__main__":
app = QtWidgets.QApplication([])
ScreenCapture.grab_window()