Skip to content

Instantly share code, notes, and snippets.

@Onefabis
Last active August 16, 2022 00:04
Show Gist options
  • Save Onefabis/db58ce5cf0dad81536c460832cd8e904 to your computer and use it in GitHub Desktop.
Save Onefabis/db58ce5cf0dad81536c460832cd8e904 to your computer and use it in GitHub Desktop.
qmk_color_widget
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