Last active
August 16, 2022 00:04
-
-
Save Onefabis/db58ce5cf0dad81536c460832cd8e904 to your computer and use it in GitHub Desktop.
qmk_color_widget
This file contains hidden or 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 time | |
from PyQt5.QtCore import * | |
from PyQt5.QtGui import * | |
from PyQt5.QtWidgets import * | |
import sys | |
import pywinusb.hid as hid | |
from functools import partial | |
import base64 | |
import os | |
import re | |
import win32gui, win32process | |
import psutil | |
import ast | |
class Device(object): | |
def __init__(self): | |
pass | |
def sample_handler(self, data): | |
print("Raw data: {0}".format(data)) | |
def hid_devices(self): | |
all_hids = hid.find_all_hid_devices() # Get a list of HID objects | |
# Convert to a dictionary of Names:Objects | |
hids_dict = {} | |
for device in all_hids: | |
device_name = str( | |
"{0.vendor_name} {0.product_name}" | |
"(vID=0x{1:04x}, pID=0x{2:04x})" | |
"".format(device, device.vendor_id, device.product_id) | |
) | |
hids_dict[device_name] = device | |
return hids_dict | |
def hid_read(self, hids_dict, menu_item): | |
device = hids_dict[menu_item] # Match the selection to the HID object | |
device.open() # Open the HID device for communication | |
device.set_raw_data_handler(self.sample_handler) # Set raw data callback | |
return device # Return the HID deviceh | |
class QMKColorWidget(QWidget): | |
def __init__(self, parent=None): | |
super(QMKColorWidget, self).__init__(parent) | |
self.isMove = False | |
self.isResize = False | |
self.new_menu_pos = 0 | |
self.new_pos = None | |
self.new_size = None | |
self.device_sel = None | |
self.thread_raw = None | |
self.thread_app = None | |
self.selected_item = None | |
self.active_apps = [] | |
self.apps_blacklist = [] | |
self.colors = ["blue", "red", "orange", "purple", "green", "yellow"] | |
self.roundness = 3 | |
self.opacity = 0.5 | |
self.init_pos = [60, 60] | |
self.init_size = [600, 15] | |
self.read_settings() | |
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SplashScreen) | |
self.setWindowModality(Qt.WindowModal | Qt.ApplicationModal) | |
self.setStyleSheet("""QMKColorWidget {border-radius: %s px; border: 3px solid red}""" %self.roundness) | |
self.setWindowOpacity(self.opacity) | |
ly = QVBoxLayout(self) | |
ly.setContentsMargins(0, 0, 0, 0) | |
self.toolbar = QLabel() | |
self.toolbar_ly = QHBoxLayout(self.toolbar) | |
self.toolbar_ly.setContentsMargins(0, 0, 0, 0) | |
self.toolbar_ly.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding, QSizePolicy.Minimum)) | |
ly.addWidget(self.toolbar) | |
self.toolbar.setAttribute(Qt.WA_StyledBackground, True) | |
self.setContextMenuPolicy(Qt.CustomContextMenu) | |
self.customContextMenuRequested.connect(self.on_context_menu) | |
self.currPos = self.startPos = 0 | |
self.curSize = self.size() | |
self.setGeometry(self.init_pos[0], self.init_pos[1], self.init_size[0], self.init_size[1]) | |
base64_image = "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyVpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMS1jMDAwIDc5LmIwZjhiZTkwLCAyMDIxLzEyLzE1LTIxOjI1OjE1ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjMuMiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RThCMzEwMTkxQTVCMTFFRDlBNTBCMkJGRUU1NDAxQjkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RThCMzEwMUExQTVCMTFFRDlBNTBCMkJGRUU1NDAxQjkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFOEIzMTAxNzFBNUIxMUVEOUE1MEIyQkZFRTU0MDFCOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFOEIzMTAxODFBNUIxMUVEOUE1MEIyQkZFRTU0MDFCOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pi9hm8EAAAA5SURBVHjafNAxDgAwCAJA5f9/pnUw0SiykHAbTtJU3HGYkTjsNw7buNjgbp2HFd4sWVhIzPqZJ8AAcWoWD0ghUMEAAAAASUVORK5CYII=" | |
img = base64.b64decode(base64_image) | |
c_img = QPixmap() | |
c_img.loadFromData(img) | |
self.palette = QPalette() | |
self.palette.setBrush(self.backgroundRole(), QBrush(c_img)) | |
self.setPalette(self.palette) | |
self.createMenu() | |
def resizeEvent(self, event): | |
bmp = QBitmap(self.size()) | |
bmp.clear() | |
painter = QPainter(bmp) | |
painter.setRenderHint(QPainter.Antialiasing, True) | |
painter.setPen(Qt.NoPen) | |
painter.setBrush(Qt.NoBrush) | |
path = QPainterPath() | |
path.addRoundedRect(0, 0, self.geometry().width(), self.geometry().height(), self.roundness, self.roundness) | |
painter.fillPath(path, Qt.red) | |
painter.end() | |
self.setMask(bmp) | |
def on_context_menu(self, point): | |
# show context menu | |
if self.new_menu_pos != 0: | |
new_pos = QPoint(self.new_menu_pos.x() + point.x(), self.new_menu_pos.y() + point.y()) | |
point = new_pos | |
self.popMenu.exec_(point) | |
def closeEvent(self, event: QCloseEvent) -> None: | |
try: | |
self.stop_device() | |
except: | |
pass | |
self.save_settings() | |
qApp.quit() | |
def mouse_moved(self, event): | |
if self.isMove: | |
new = event.globalPos() | |
self.new_pos = new + self.currPos - self.startPos | |
self.new_menu_pos = self.new_pos | |
self.move(self.new_pos.x(), self.new_pos.y()) | |
elif self.isResize: | |
self.new_pos = event.globalPos() - self.startPos | |
self.new_size = self.curSize + QSize(self.new_pos.x(), self.new_pos.y()) | |
self.resize(self.new_size.width(), self.new_size.height()) | |
def mousePressEvent(self, event): | |
if event.button() == 1: | |
self.isMove = True | |
self.startPos = event.globalPos() | |
self.currPos = self.pos() | |
elif event.button() == 4: | |
self.isResize = True | |
self.curSize = self.size() | |
self.startPos = event.globalPos() | |
else: | |
self.isMove = False | |
self.isResize = False | |
QWidget.mousePressEvent(self, event) | |
def mouseMoveEvent(self, event): | |
self.mouse_moved(event) | |
def mouseReleaseEvent(self, event): | |
if event.button() == Qt.MiddleButton: | |
self.isResize = False | |
if event.button() == Qt.LeftButton: | |
self.isMove = False | |
QWidget.mouseReleaseEvent(self, event) | |
def createMenu(self): | |
self.popMenu = QMenu(self) | |
self.popMenu.setStyleSheet(""" | |
QMenu{background-color: white;color: black;} | |
QMenu::item:selected{background-color: rgb(220, 220, 220);color: black;} | |
QMenu::item:default { color: rgb(20, 20, 20); } | |
""") | |
self.devices = Device() | |
self.hids_dict = self.devices.hid_devices() # Get a dictionary of HID devices | |
device_names = list(self.hids_dict.keys()) | |
for s in range(len(device_names)): | |
action = self.popMenu.addAction(device_names[s]) | |
if self.selected_item is not None and self.selected_item == s: | |
self.popMenu.setDefaultAction(action) | |
action.triggered.connect(partial(self.start_device, s)) | |
action = self.popMenu.addMenu("Прозрачность") | |
for m in range(10): | |
opacity = action.addAction("%d" %(m*10+10)) | |
opacity.triggered.connect(partial(self.set_opacity, m)) | |
action = self.popMenu.addMenu("Скругление") | |
for r in range(10): | |
opacity = action.addAction("%d пикс." % (r)) | |
opacity.triggered.connect(partial(self.set_roundhess, r)) | |
if QSysInfo().productType() == "windows": | |
win32gui.EnumWindows(self.winEnumHandler, None ) | |
if len(self.active_apps) > 0: | |
action = self.popMenu.addMenu("Исключить") | |
active_apps = list(set(self.active_apps)) | |
for s in range(len(active_apps)): | |
if active_apps[s] not in self.apps_blacklist: | |
app = action.addAction(active_apps[s].rsplit("\\", 1)[-1].rsplit(".")[0]) | |
app.triggered.connect(partial(self.add_to_blacklist, active_apps[s])) | |
if len(self.apps_blacklist) > 0: | |
action = self.popMenu.addMenu("Чёрный список") | |
for s in range(len(self.apps_blacklist)): | |
blacklist_app = action.addAction(self.apps_blacklist[s].rsplit("\\", 1)[-1].rsplit(".")[0]) | |
blacklist_app.triggered.connect(partial(self.remove_from_blacklist, self.apps_blacklist[s])) | |
action = self.popMenu.addAction("Пауза") | |
action.triggered.connect(lambda: self.stop_device()) | |
action = self.popMenu.addAction("Закрыть") | |
action.triggered.connect(lambda: self.close()) | |
def winEnumHandler(self, hwnd, ctx): | |
if win32gui.IsWindowVisible(hwnd): | |
_, pid = win32process.GetWindowThreadProcessId(hwnd) | |
self.active_apps.append(psutil.Process(pid).exe()) | |
def set_opacity(self, o): | |
self.setWindowOpacity((o+1)*0.1) | |
self.opacity = (o+1)*0.1 | |
def set_roundhess(self, r): | |
self.roundness = r | |
size = self.geometry() | |
self.setGeometry(QRect(size.x(), size.y(), size.width(), size.height()+1)) | |
self.setGeometry(size) | |
def add_to_blacklist(self, b): | |
self.apps_blacklist.append(b) | |
self.apps_blacklist = list(set(self.apps_blacklist)) | |
self.createMenu() | |
def remove_from_blacklist(self, b): | |
self.apps_blacklist.remove(b) | |
self.createMenu() | |
def start_device(self, data, part): | |
self.selected_item = data | |
try: | |
self.popMenu.deleteLater() | |
self.createMenu() | |
self.start_thread() | |
except: | |
pass | |
def start_thread(self): | |
if self.thread_raw is not None: | |
self.getRawDataHandler.stop() | |
self.thread_raw.quit() | |
self.thread_raw.wait() | |
if self.thread_app is not None: | |
self.getActiveAppHandler.stop() | |
self.thread_app.quit() | |
self.thread_app.wait() | |
self.thread_raw = QThread() | |
self.getRawDataHandler = getRawDataHandler(self.selected_item) | |
self.getRawDataHandler.moveToThread(self.thread_raw) | |
self.thread_raw.started.connect(self.getRawDataHandler.run) | |
self.getRawDataHandler.newLayer.connect(self.edit_color) | |
self.thread_raw.start() | |
self.thread_app = QThread() | |
self.getActiveAppHandler = getActiveAppHandler(self.apps_blacklist) | |
self.getActiveAppHandler.moveToThread(self.thread_app) | |
self.thread_app.started.connect(self.getActiveAppHandler.run) | |
self.getActiveAppHandler.visible.connect(self.get_visible) | |
self.thread_app.start() | |
def stop_device(self): | |
self.setStyleSheet("background-color: none;") | |
self.setPalette(self.palette) | |
self.getRawDataHandler.stop() | |
self.thread_raw.quit() | |
self.thread_raw.wait() | |
self.thread_raw = None | |
self.getActiveAppHandler.stop() | |
self.thread_app.quit() | |
self.thread_app.wait() | |
self.thread_app = None | |
def save_settings(self): | |
script_path = os.path.realpath(__file__) | |
settings_path = (script_path.rsplit("\\", 1)[0] + "\\settings.txt") | |
data_to_save = [self.roundness, self.opacity] | |
data_to_save.extend([self.new_pos.x(), self.new_pos.y()]) if self.new_pos else data_to_save.extend([self.init_pos[0], self.init_pos[1]]) | |
data_to_save.extend([self.new_size.width(), self.new_size.height()]) if self.new_size else data_to_save.extend([self.init_size[0], self.init_size[1]]) | |
text_to_save = " ".join([str(x) for x in data_to_save]) | |
f = open(settings_path, 'w+') | |
f.write("Colors:\n" + "\n".join(self.colors) + "\n\n") | |
f.write("Window:\n" + text_to_save + "\n\n") | |
f.write("Device:\n" + "\n\n") if self.selected_item is None else f.write("Device:\n" + str(self.selected_item) + "\n\n") | |
f.write("Blacklist:\n" + str(self.apps_blacklist)) | |
f.close() | |
def read_settings(self): | |
script_path = os.path.realpath(__file__) | |
settings_path = (script_path.rsplit("\\", 1)[0] + "\\settings.txt") | |
if os.path.isfile(settings_path): | |
try: | |
delimiters = "Colors:", "Window:", "Device:", "Blacklist:" | |
regexPattern = '|'.join(map(re.escape, delimiters)) | |
f = open(settings_path, 'r') | |
lines = f.read() | |
data_to_parse = re.split(regexPattern, lines) | |
self.colors = list(filter(None, data_to_parse[1].split("\n"))) | |
window_data = list(filter(None, data_to_parse[2].split("\n")))[0].split(" ") | |
window_data = [self.int_or_float(x) for x in window_data] | |
self.roundness = window_data[0] | |
self.opacity = window_data[1] | |
self.init_pos = [window_data[2], window_data[3]] | |
self.init_size = [window_data[4], window_data[5]] | |
self.new_menu_pos = QPoint(self.init_pos[0], self.init_pos[1]) | |
device_idx = list(filter(None, data_to_parse[3].split("\n"))) | |
if len(device_idx)>0: | |
self.selected_item = device_idx[0] | |
blacklist = list(filter(None, data_to_parse[4].split("\n"))) | |
if len(blacklist): | |
self.apps_blacklist.extend(ast.literal_eval(blacklist[0])) | |
finally: | |
print("close") | |
f.close() | |
def int_or_float(self, s): | |
try: | |
return int(s) | |
except ValueError: | |
return float(s) | |
@pyqtSlot(int) | |
def edit_color(self, layer): | |
if layer < len(self.colors): | |
self.setStyleSheet("background-color: %s;" % str(self.colors[layer])) | |
@pyqtSlot(int) | |
def get_visible(self, visible): | |
if visible == 0: | |
self.hide() | |
else: | |
self.show() | |
class getActiveAppHandler(QObject): | |
visible = pyqtSignal(int) | |
# newTextAndColor = pyqtSignal(str, object) | |
def __init__(self, blacklist, parent=None): | |
QThread.__init__(self, parent) | |
self.blacklist = blacklist | |
self._isRunning = True | |
self.old_active_app = None | |
def run(self): | |
while True: | |
QThread.msleep(100) | |
if QSysInfo().productType() == "windows": | |
hwnd = win32gui.GetForegroundWindow() | |
if hwnd: | |
_, pid = win32process.GetWindowThreadProcessId(hwnd) | |
if pid: | |
path = psutil.Process(pid).exe() | |
QThread.msleep(100) | |
if path != self.old_active_app: | |
if path in self.blacklist: | |
self.visible.emit(0) | |
else: | |
self.visible.emit(1) | |
self.old_active_app = path | |
if self._isRunning == False: | |
break | |
return | |
def stop(self): | |
self._isRunning = False | |
print("stop") | |
class getRawDataHandler(QObject): | |
newLayer = pyqtSignal(int) | |
# newTextAndColor = pyqtSignal(str, object) | |
def __init__(self, id, parent=None): | |
QThread.__init__(self, parent) | |
self.id = id | |
self._isRunning = True | |
def sample_handler(self, data): | |
self.newLayer.emit(int(data[1])) | |
def run(self): | |
self.device = hid.find_all_hid_devices()[self.id] | |
self.device.open() | |
self.device.set_raw_data_handler(self.sample_handler) | |
try: | |
while self.device.is_plugged(): | |
QThread.msleep(300) | |
if self._isRunning == False: | |
break | |
# self.device.close() | |
return | |
finally: | |
print("device close") | |
self.device.close() | |
def stop(self): | |
self._isRunning = False | |
print("stop") | |
qApp = QApplication(sys.argv) | |
qApp.setQuitOnLastWindowClosed(True) | |
window = QMKColorWidget() | |
window.show() | |
qApp.exec() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment