Created
December 21, 2023 11:25
-
-
Save UDPSendToFailed/71ba1c4def88e2b9edf5950b3ed77922 to your computer and use it in GitHub Desktop.
Corsair LCD Tool v0.0.1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
import math | |
import os | |
import platform | |
import queue | |
import sys | |
import threading | |
import time | |
from dataclasses import dataclass | |
import cv2 | |
import hid | |
import numpy as np | |
import yaml | |
from PyQt6.QtCore import Qt, QTimer, QPointF, pyqtSignal, QThread, pyqtSlot, QEvent, QRectF | |
from PyQt6.QtGui import QPixmap, QPainter, QMovie, QIcon, QTransform, QImage, QAction, QPalette, QColor | |
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QFileDialog, QSlider, QWidget, QGraphicsScene, \ | |
QGraphicsView, QGraphicsPixmapItem, QSystemTrayIcon, QMenu, QStyleFactory, QGraphicsItem, QCheckBox | |
from openrgb import OpenRGBClient | |
from openrgb.utils import RGBColor | |
if os.name == 'nt': # Windows platform | |
import winshell | |
elif os.name == 'posix': # Unix/Linux platform | |
import subprocess | |
# Set up logging | |
logging.basicConfig(level=logging.DEBUG) # Adjust the logging level as needed | |
VID = 0x1b1c # Corsair | |
PID = 0x0c39 # Corsair LCD Cap for Elite Capellix coolers | |
@dataclass | |
class CorsairCommand: | |
"""Corsair Command""" | |
opcode: int # 0x02 | |
unknown1: int # 0x05 | |
unknown2: int # 0x40 | |
is_end: bool # 0x00 or 0x01 | |
part_num: int # 0x0000 - 0xffff, little endian | |
datalen: int # 0x0000 - 0xffff, little endian | |
data: bytes # datalen bytes + padding up to packet size | |
HEADER_SIZE = 8 | |
@classmethod | |
def from_bytes(cls, data): | |
try: | |
opcode = data[0] | |
unknown1 = data[1] | |
unknown2 = data[2] | |
is_end = data[3] == 0x01 | |
part_num = int.from_bytes(data[4:6], byteorder='little') | |
datalen = int.from_bytes(data[6:8], byteorder='little') | |
payload = data[cls.HEADER_SIZE:cls.HEADER_SIZE + datalen] | |
return cls( | |
opcode=opcode, | |
unknown1=unknown1, | |
unknown2=unknown2, | |
is_end=is_end, | |
part_num=part_num, | |
datalen=datalen, | |
data=payload | |
) | |
except Exception as e: | |
logging.error(f"Error parsing CorsairCommand: {e}") | |
raise | |
def to_bytes(self): | |
return bytes([ | |
self.opcode, | |
self.unknown1, | |
self.unknown2, | |
0x01 if self.is_end else 0x00, | |
]) + \ | |
self.part_num.to_bytes(2, byteorder='little') + \ | |
self.datalen.to_bytes(2, byteorder='little') + \ | |
self.data | |
@property | |
def is_start(self): | |
return self.part_num == 0 | |
@property | |
def header_size(self): | |
return self.HEADER_SIZE | |
@property | |
def size(self): | |
return self.header_size + self.datalen | |
def make_commands(data, opcode=0x02, max_len=1024): | |
"""Splits data into Corsair commands of max_len bytes""" | |
real_max_len = max_len - CorsairCommand.HEADER_SIZE | |
part_num = 0 | |
while data: | |
if len(data) < real_max_len: | |
padded_data = data + b'\x00' * (real_max_len - len(data)) | |
else: | |
padded_data = data[:real_max_len] | |
datalen = min(real_max_len, len(data)) | |
data = data[real_max_len:] | |
try: | |
yield CorsairCommand( | |
opcode=opcode, | |
unknown1=0x05, | |
unknown2=0x40, | |
is_end=not bool(data), | |
part_num=part_num, | |
datalen=datalen, | |
data=padded_data, | |
) | |
except Exception as e: | |
logging.error(f"Error creating CorsairCommand: {e}") | |
raise | |
part_num += 1 | |
class UpdateDeviceThread(QThread): | |
captureSignal: pyqtSignal = pyqtSignal() | |
def __init__(self, container, image_previewer): | |
super().__init__() | |
self.container = container | |
self.image_previewer = image_previewer | |
self.device = hid.device() | |
try: | |
self.device.open(VID, PID) | |
except Exception as e: | |
logging.error(f"Error opening device: {e}") | |
raise | |
QTimer.singleShot(0, self.start_timer) | |
def start_timer(self): | |
self.updateLCD_timer = QTimer() | |
self.updateLCD_timer.timeout.connect(self.updateLCD) | |
self.updateLCD_timer.start(int(1000 / 30)) | |
def run(self): | |
self.exec() | |
@pyqtSlot() | |
def updateLCD(self): | |
try: | |
image = self.image_previewer.captureContainer() | |
width = image.width() | |
height = image.height() | |
ptr = image.bits() | |
ptr.setsize(height * width * 4) | |
arr = np.frombuffer(ptr, np.uint8).reshape((height, width, 4)) | |
arr = cv2.resize(arr, (480, 480)) | |
image_data = cv2.imencode('.jpg', arr)[1].tobytes() | |
self.write_command(image_data) | |
except Exception as e: | |
logging.error(f"Error updating LCD: {e}") | |
def write_command(self, data): | |
try: | |
commands = make_commands(data) | |
for command in commands: | |
self.device.write(command.to_bytes()) | |
except Exception as e: | |
logging.error(f"Error writing command to device: {e}") | |
class NoScrollGraphicsView(QGraphicsView): | |
def __init__(self, scene, parent=None): | |
super().__init__(scene, parent) | |
self.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.FullViewportUpdate) | |
def scrollContentsBy(self, dx, dy): | |
# Override the function and do nothing | |
pass | |
def wheelEvent(self, event): | |
logging.debug("Wheel event detected, but ignored") | |
pass # Ignore wheel events | |
class LEDController(QThread): | |
captureSignal: pyqtSignal = pyqtSignal() | |
def __init__(self, image_previewer): | |
super().__init__() | |
self.image_previewer = image_previewer | |
self.client = OpenRGBClient() | |
self.last_rgb_colors = [RGBColor(0, 0, 0)] * 24 | |
QTimer.singleShot(0, self.start_timer) | |
def start_timer(self): | |
self.updateLED_timer = QTimer() | |
self.updateLED_timer.timeout.connect(self.analyze_and_set_colors) | |
self.updateLED_timer.start(33) # Adjusted for ~30 fps | |
@pyqtSlot() | |
def analyze_and_set_colors(self): | |
try: | |
image = self.image_previewer.captureContainer() | |
width = image.width() | |
height = image.height() | |
radius = min(width, height) // 2 | |
for i in range(24): | |
angle = 2 * math.pi * (23 - i) / 24 | |
x = width - int(width / 2 + radius * math.cos(angle)) | |
y = int(height / 2 + radius * math.sin(angle)) | |
color = QColor(image.pixel(x, y)) | |
rgb_color = RGBColor(color.red(), color.green(), color.blue()) | |
self.last_rgb_colors[i] = rgb_color # Directly assign the new color | |
self.send_color_to_openrgb() # Send color data to OpenRGB | |
except Exception as e: | |
logging.error(f"Error updating LED: {e}") | |
def send_color_to_openrgb(self): | |
for device in self.client.devices: | |
if device.name == "Corsair Commander Core": | |
self.set_device_colors(device) | |
def set_device_colors(self, device): | |
try: | |
for i, rgb_color in enumerate(self.last_rgb_colors): | |
device.leds[i].set_color(rgb_color) | |
except Exception as e: | |
logging.error(f"Error setting color for device {device.name}: {e}") | |
class ImagePreviewer(QMainWindow): | |
def __init__(self): | |
super().__init__() | |
logging.debug("Initializing ImagePreviewer") | |
self.pixmapItem = QGraphicsPixmapItem() | |
self.pixmapItem.setPos(QPointF(0, 0)) | |
self.trayIcon = QSystemTrayIcon(self) | |
self.trayIcon.setIcon(QIcon('icon.ico')) | |
self.windowStateHandler = WindowStateHandler(self) | |
self.controller = LEDController(self) | |
self.controller.analyze_and_set_colors() | |
self.currentImagePath = None | |
self.lastPixmapPos = None | |
self.lastTransform = None | |
self.lastSliderValue = None | |
self.lastSceneRect = None | |
self.lastScrollBarPos = None | |
self.setWindowTitle('Corsair LCD Tool') | |
self.setFixedSize(600, 650) # Extend the window size | |
self.container = QWidget(self) | |
self.container.setGeometry(60, 20, 480, 480) | |
self.container.setStyleSheet("background-color: #282c34; border: 0px") | |
self.scene = QGraphicsScene(self.container) | |
self.view = QGraphicsView(self.scene, self.container) | |
self.view.setGeometry(0, 0, 480, 480) | |
self.view.setRenderHint(QPainter.RenderHint.Antialiasing) | |
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) | |
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) | |
self.view.setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse) | |
self.openButton = QPushButton("Open Image", self) | |
self.openButton.setGeometry(60, 540, 120, 30) | |
self.openButton.clicked.connect(self.openImage) | |
self.trayButton = QPushButton("Minimize to Tray", self) | |
self.trayButton.setGeometry(240, 540, 120, 30) | |
self.trayButton.clicked.connect(self.windowStateHandler.minimizeWindow) | |
self.resetButton = QPushButton("Reset View", self) | |
self.resetButton.setGeometry(420, 540, 120, 30) | |
self.resetButton.clicked.connect(self.resetImage) | |
self.script_path = os.path.join(os.getcwd(), 'asd2.py') | |
# Get the path to the Python interpreter | |
self.python_path = sys.executable | |
if platform.system() == "Windows": | |
import winshell | |
# Get the path to the startup folder | |
self.startup_folder = winshell.startup() | |
# Get the path to the shortcut | |
self.shortcut_path = os.path.join(self.startup_folder, 'asd2.lnk') | |
elif platform.system() == "Linux": | |
# In Linux, there's no equivalent to the startup folder in Windows. | |
# You can use crontab or systemd to run scripts at startup. | |
# Here, we'll use crontab as an example. | |
# The crontab command to add or remove the script from startup | |
self.crontab_command = f'@reboot {self.python_path} {self.script_path} &' | |
self.startupCheckbox = QCheckBox("Run at startup", self) # Add a checkbox | |
self.startupCheckbox.setGeometry(60, 580, 120, 30) # Position it below the buttons | |
self.startupCheckbox.stateChanged.connect(self.update_startup) | |
self.slider = QSlider(Qt.Orientation.Horizontal, self) | |
self.slider.setGeometry(60, 510, 480, 20) | |
self.slider.setMinimum(20) | |
self.slider.setMaximum(180) | |
self.slider.setValue(100) | |
self.slider.setDisabled(True) | |
self.slider.valueChanged.connect(self.scaleImage) | |
restoreAction = QAction("Restore", self) | |
restoreAction.triggered.connect(self.showNormal) | |
quitAction = QAction("Quit", self) | |
quitAction.triggered.connect(QApplication.quit) | |
self.trayMenu = QMenu() | |
self.trayMenu.addAction(restoreAction) | |
self.trayMenu.addAction(quitAction) | |
self.trayIcon.setContextMenu(self.trayMenu) | |
self.setWindowIcon(QIcon('icon.ico')) | |
self.movie = None | |
logging.debug("ImagePreviewer initialized") | |
try: | |
self.updateDeviceThread = UpdateDeviceThread(self.container, self) | |
self.updateDeviceThread.start() | |
self.frames = [] | |
except Exception as e: | |
logging.error(f"Error initializing ImagePreviewer: {e}") | |
raise | |
self.saveStateHandler = saveStateHandler(self) | |
self.show() | |
# Set the style for the application | |
QApplication.instance().setStyle(QStyleFactory.create('Fusion')) | |
# Set the palette for the application | |
palette = QPalette() | |
palette.setColor(QPalette.ColorRole.Window, QColor('#2c313a')) | |
palette.setColor(QPalette.ColorRole.WindowText, QColor('white')) | |
palette.setColor(QPalette.ColorRole.Base, QColor('#2c313a')) | |
palette.setColor(QPalette.ColorRole.AlternateBase, QColor('gray')) | |
palette.setColor(QPalette.ColorRole.ToolTipBase, QColor('#2c313a')) | |
palette.setColor(QPalette.ColorRole.ToolTipText, QColor('white')) | |
palette.setColor(QPalette.ColorRole.Text, QColor('white')) | |
palette.setColor(QPalette.ColorRole.Button, QColor('#2c313a')) | |
palette.setColor(QPalette.ColorRole.ButtonText, QColor('white')) | |
palette.setColor(QPalette.ColorRole.BrightText, QColor('red')) | |
palette.setColor(QPalette.ColorRole.Link, QColor('#0069c0')) | |
palette.setColor(QPalette.ColorRole.Highlight, QColor('#0069c0')) | |
palette.setColor(QPalette.ColorRole.HighlightedText, QColor('black')) | |
QApplication.instance().setPalette(palette) | |
self.saveStateHandler.loadImageState() | |
logging.debug("UI Initialized") | |
def update_startup(self, state): | |
print(f"Checkbox state: {state}") | |
print(f"Script path: {self.script_path}") | |
if platform.system() == "Windows": | |
print(f"Shortcut path: {self.shortcut_path}") | |
if state == 2: # Checkbox is checked | |
# Create a shortcut in the startup folder | |
with winshell.shortcut(self.shortcut_path) as shortcut: | |
shortcut.path = self.script_path | |
shortcut.description = 'My Python script' | |
print("Shortcut created.") | |
else: # Checkbox is unchecked | |
# Remove the shortcut from the startup folder | |
if os.path.exists(self.shortcut_path): | |
os.remove(self.shortcut_path) | |
print("Shortcut removed.") | |
elif platform.system() == "Linux": | |
service_content = f"""[Unit] | |
Description=My Python script | |
[Service] | |
ExecStart={self.python_path} {self.script_path} | |
[Install] | |
WantedBy=multi-user.target | |
""" | |
service_path = f"/etc/systemd/system/{os.path.basename(self.script_path)}.service" | |
if state == 2: # Checkbox is checked | |
# Create a systemd service | |
with open(service_path, 'w') as f: | |
f.write(service_content) | |
subprocess.run(["systemctl", "daemon-reload"], check=True) | |
subprocess.run(["systemctl", "enable", os.path.basename(self.script_path)], check=True) | |
print("Systemd service created and enabled.") | |
else: # Checkbox is unchecked | |
# Remove the systemd service | |
if os.path.exists(service_path): | |
os.remove(service_path) | |
subprocess.run(["systemctl", "daemon-reload"], check=True) | |
print("Systemd service removed.") | |
def openImage(self): | |
logging.debug("Entering openImage function") | |
fileName, _ = QFileDialog.getOpenFileName(self, "Open Image", "", | |
"Images (*.png *.xpm *.jpg *.bmp *.gif)") | |
if fileName: | |
logging.debug(f"Opening image file: {fileName}") | |
self.loadNewImage(fileName) | |
self.currentImagePath = fileName | |
self.saveStateHandler.restartsaveImageStateTimer() | |
def loadNewImage(self, fileName): | |
logging.debug(f"Loading a new image: {fileName}") | |
# Clear prior image or gif | |
self.scene.clear() | |
# Check if the file is a gif | |
if fileName.lower().endswith('.gif'): | |
self.loadNewGif(fileName) | |
else: | |
# Stop and delete the old movie if it exists | |
if self.movie is not None: | |
self.movie.stop() | |
self.movie.deleteLater() | |
self.movie = None | |
# Load the image as pixmap | |
pixmap = QPixmap(fileName) | |
# Create a pixmap item | |
self.pixmapItem = QGraphicsPixmapItem(pixmap) | |
# Set pixmap item to be movable | |
self.pixmapItem.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) | |
# Add pixmap item to the scene | |
self.scene.addItem(self.pixmapItem) | |
# Enable the slider | |
self.slider.setEnabled(True) | |
def loadNewGif(self, fileName): | |
# Stop and delete the old movie if it exists | |
if self.movie is not None: | |
self.movie.stop() | |
self.movie.deleteLater() | |
# Create a new QMovie object | |
self.movie = QMovie(fileName) | |
# Create a pixmap item | |
self.pixmapItem = QGraphicsPixmapItem() | |
# Set pixmap item to be movable | |
self.pixmapItem.setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable) | |
# Add pixmap item to the scene | |
self.scene.addItem(self.pixmapItem) | |
# Connect frameChanged signal to set each frame of gif | |
self.movie.frameChanged.connect(lambda: self.pixmapItem.setPixmap(QPixmap.fromImage(self.movie.currentImage()))) | |
# Start the gif | |
self.movie.start() | |
# Enable the slider | |
self.slider.setEnabled(True) | |
def resetImage(self): | |
if self.currentImagePath is not None: | |
self.loadNewImage(self.currentImagePath) | |
def scaleImage(self, value): | |
if self.pixmapItem is not None: | |
# Reset any existing transformations | |
self.pixmapItem.resetTransform() | |
# Calculate the scale factor. The slider's range is 20 to 180, | |
# so we'll divide by 100 to get a range of 0.2 to 1.8 | |
scale_factor = value / 100.0 | |
# Set the pixmap item's transformation origin point to its center | |
self.pixmapItem.setTransformOriginPoint(self.pixmapItem.boundingRect().center()) | |
# Scale the pixmap item | |
self.pixmapItem.setScale(scale_factor) | |
self.saveStateHandler.restartsaveImageStateTimer() | |
def captureContainer(self): | |
try: | |
pixmap = QPixmap(self.container.size()) | |
self.container.render(pixmap) | |
return pixmap.toImage() | |
except Exception as e: | |
logging.error(f"Error in captureContainer: {e}") | |
return QImage() # Return an empty QImage in case of an error | |
def changeEvent(self, event): | |
self.windowStateHandler.handleWindowStateChange(event) | |
class saveStateHandler: | |
def __init__(self, image_previewer): | |
self.oldPos = None | |
self.main = image_previewer | |
self.isFirstLoad = True | |
self.checkStateTimer = QTimer() | |
self.checkStateTimer.setInterval(1000) | |
self.checkStateTimer.timeout.connect(self.checkState) | |
self.saveImageStateTimer = QTimer() | |
self.saveImageStateTimer.setInterval(2000) | |
self.saveImageStateTimer.timeout.connect(self.handleSaveImageStateTimeout) | |
self.saveImageStateFlag = False | |
self.oldTransform = None | |
def loadImageState(self): | |
logging.debug("Entering loadImageState function") | |
try: | |
logging.debug(f"self.isFirstLoad value: {self.isFirstLoad}") | |
if not self.isFirstLoad: | |
logging.debug("State has already been loaded. Skipping load.") | |
return | |
state_file = 'state.yaml' | |
if os.path.exists(state_file): | |
try: | |
with open(state_file, 'r') as f: | |
state = yaml.safe_load(f) | |
logging.debug(f"Loaded state from file: {state}") | |
if state is not None: | |
image_path = state.get('lastImagePath', '') | |
if image_path and os.path.exists(image_path): | |
self.main.loadNewImage(image_path) | |
self.main.currentImagePath = image_path | |
self.main.view.resetTransform() | |
self.main.scene.setSceneRect(QRectF(*state.get('lastSceneRect'))) | |
self.main.pixmapItem.setPos(QPointF(*state.get('lastPixmapPos'))) | |
self.main.view.setTransform(QTransform().scale(*state.get('lastTransform'))) | |
self.main.slider.setValue(state.get('lastSliderValue')) | |
self.main.view.horizontalScrollBar().setValue(state.get('lastScrollBarPos')[0]) | |
self.main.view.verticalScrollBar().setValue(state.get('lastScrollBarPos')[1]) | |
self.main.show() | |
print(f"Loaded state: {state}") | |
else: | |
logging.warning( | |
"State file is empty or image path does not exist. Loading with default settings.") | |
else: | |
logging.warning("No state file found. Loading with default settings.") | |
except yaml.YAMLError as e: | |
logging.error( | |
f"Error loading state: Invalid YAML file. Loading with default settings. Error details: {e}") | |
except Exception as e: | |
logging.error(f"Error loading state: {e}") | |
self.isFirstLoad = False | |
self.checkStateTimer.start() | |
print('checkStateTimer started') | |
def saveImageState(self): | |
try: | |
# Check if state loading is complete before saving the state | |
if self.isFirstLoad: | |
logging.debug("State loading is not complete. Skipping saveImageState.") | |
return | |
else: | |
logging.debug("Saving current state to disk.") | |
state = { | |
'lastImagePath': self.main.currentImagePath, | |
'lastPixmapPos': [self.main.pixmapItem.pos().x(), self.main.pixmapItem.pos().y()], | |
'lastTransform': [self.main.view.transform().m11(), self.main.view.transform().m22()], | |
'lastSliderValue': self.main.slider.value(), | |
'lastSceneRect': [self.main.scene.sceneRect().x(), self.main.scene.sceneRect().y(), | |
self.main.scene.sceneRect().width(), self.main.scene.sceneRect().height()], | |
'lastScrollBarPos': [self.main.view.horizontalScrollBar().value(), | |
self.main.view.verticalScrollBar().value()] | |
} | |
with open('state.yaml', 'w') as file: | |
yaml.dump(state, file) | |
logging.debug(f"Saved state to disk: {state}") | |
except Exception as e: | |
logging.error(f"An error occurred while saving the image state: {e}") | |
def checkState(self): | |
try: | |
newPos = self.main.pixmapItem.pos() | |
newTransform = self.main.view.transform() | |
if newPos is not None and newTransform is not None: | |
if newPos != self.oldPos or newTransform != self.oldTransform: | |
if not self.isFirstLoad: | |
self.restartsaveImageStateTimer() | |
self.oldPos = newPos | |
self.oldTransform = newTransform | |
else: | |
logging.warning("Either pixmap position or view transform is None.") | |
except Exception as e: | |
logging.error(f"Error in checkState: {e}") | |
def handleSaveImageStateTimeout(self): | |
try: | |
if self.saveImageStateFlag: | |
self.saveImageState() | |
self.saveImageStateFlag = False | |
except Exception as e: | |
logging.error(f"Error handling save state timeout: {e}") | |
def restartsaveImageStateTimer(self): | |
try: | |
if self.isFirstLoad: | |
logging.debug("restartsaveImageStateTimer skipped because isFirstLoad is True.") | |
return | |
if self.saveImageStateTimer.isActive(): | |
self.saveImageStateTimer.stop() | |
self.saveImageStateTimer.start() | |
self.saveImageStateFlag = True | |
logging.debug(f"restartsaveImageStateTimer called") | |
except Exception as e: | |
logging.error(f"Error in restartsaveImageStateTimer: {e}") | |
class WindowStateHandler: | |
def __init__(self, image_previewer): | |
self.main = image_previewer | |
self.main.trayIcon.activated.connect(self.handleTrayActivation) | |
def handleWindowStateChange(self, event): | |
if event.type() == QEvent.Type.WindowStateChange: | |
if self.main.windowState() == Qt.WindowState.WindowMinimized: | |
logging.debug("Window minimized to taskbar. Saving current state.") | |
self.main.lastPixmapPos = self.main.pixmapItem.pos() | |
self.main.lastTransform = self.main.view.transform() | |
self.main.lastSliderValue = self.main.slider.value() | |
self.main.lastSceneRect = self.main.scene.sceneRect() | |
self.main.lastScrollBarPos = ( | |
self.main.view.horizontalScrollBar().value(), self.main.view.verticalScrollBar().value()) | |
print( | |
f"Saved state: Pixmap Position ({self.main.lastPixmapPos.x()}, {self.main.lastPixmapPos.y()}), Transform ({self.main.lastTransform.m11()}, {self.main.lastTransform.m22()}), Slider Value {self.main.lastSliderValue}, Scene Rect {self.main.lastSceneRect.x()}, {self.main.lastSceneRect.y()}, {self.main.lastSceneRect.width()}, {self.main.lastSceneRect.height()}, ScrollBar Position {self.main.lastScrollBarPos}") | |
elif event.oldState() == Qt.WindowState.WindowMinimized and self.main.lastPixmapPos is not None and self.main.lastTransform is not None and self.main.lastSliderValue is not None and self.main.lastSceneRect is not None and self.main.lastScrollBarPos is not None: | |
logging.debug("Window restored from taskbar.") | |
self.main.view.resetTransform() | |
self.main.scene.setSceneRect(self.main.lastSceneRect) | |
self.main.pixmapItem.setPos(self.main.lastPixmapPos) | |
self.main.view.setTransform(self.main.lastTransform) | |
self.main.slider.setValue(self.main.lastSliderValue) | |
self.main.view.horizontalScrollBar().setValue(self.main.lastScrollBarPos[0]) | |
self.main.view.verticalScrollBar().setValue(self.main.lastScrollBarPos[1]) | |
self.main.show() | |
print( | |
f"Restored state: Pixmap Position ({self.main.lastPixmapPos.x()}, {self.main.lastPixmapPos.y()}), Transform ({self.main.lastTransform.m11()}, {self.main.lastTransform.m22()}), Slider Value {self.main.lastSliderValue}, Scene Rect {self.main.lastSceneRect.x()}, {self.main.lastSceneRect.y()}, {self.main.lastSceneRect.width()}, {self.main.lastSceneRect.height()}, ScrollBar Position {self.main.lastScrollBarPos}") | |
def handleTrayActivation(self, reason): | |
if reason == QSystemTrayIcon.ActivationReason.Trigger: | |
logging.debug("System tray icon activated. Restoring window.") | |
self.restoreWindow() | |
def restoreWindow(self): | |
try: | |
self.main.show() | |
self.main.view.resetTransform() | |
self.main.scene.setSceneRect(self.main.lastSceneRect) | |
self.main.pixmapItem.setPos(self.main.lastPixmapPos) | |
self.main.view.setTransform(self.main.lastTransform) | |
self.main.slider.setValue(self.main.lastSliderValue) | |
self.main.view.horizontalScrollBar().setValue(self.main.lastScrollBarPos[0]) | |
self.main.view.verticalScrollBar().setValue(self.main.lastScrollBarPos[1]) | |
print( | |
f"Restored state: Pixmap Position ({self.main.lastPixmapPos.x()}, {self.main.lastPixmapPos.y()}), Transform ({self.main.lastTransform.m11()}, {self.main.lastTransform.m22()}), Slider Value {self.main.lastSliderValue}, Scene Rect {self.main.lastSceneRect.x()}, {self.main.lastSceneRect.y()}, {self.main.lastSceneRect.width()}, {self.main.lastSceneRect.height()}, ScrollBar Position {self.main.lastScrollBarPos}") | |
self.main.trayIcon.hide() | |
except Exception as e: | |
logging.error(f"Error in restoreWindow: {e}") | |
def minimizeWindow(self): | |
try: | |
logging.debug("Minimizing window to system tray. Saving current state.") | |
self.main.lastPixmapPos = self.main.pixmapItem.pos() | |
self.main.lastTransform = self.main.view.transform() | |
self.main.lastSliderValue = self.main.slider.value() | |
self.main.lastSceneRect = self.main.scene.sceneRect() | |
self.main.lastScrollBarPos = ( | |
self.main.view.horizontalScrollBar().value(), self.main.view.verticalScrollBar().value()) | |
print( | |
f"Saved state: Pixmap Position ({self.main.lastPixmapPos.x()}, {self.main.lastPixmapPos.y()}), Transform ({self.main.lastTransform.m11()}, {self.main.lastTransform.m22()}), Slider Value {self.main.lastSliderValue}, Scene Rect {self.main.lastSceneRect.x()}, {self.main.lastSceneRect.y()}, {self.main.lastSceneRect.width()}, {self.main.lastSceneRect.height()}, ScrollBar Position {self.main.lastScrollBarPos}") | |
self.main.hide() | |
self.main.trayIcon.show() | |
except Exception as e: | |
logging.error(f"Error in minimizeWindow: {e}") | |
if __name__ == "__main__": | |
try: | |
app = QApplication(sys.argv) | |
window = ImagePreviewer() | |
window.show() | |
sys.exit(app.exec()) | |
except Exception as e: | |
logging.error(f"Error in main: {e}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment