Как отобразить значок в системном трее, отражающий состояние NumLk

Мой компьютер не имеет никакого способа сообщить мне, включен ли мой NumLk или выключен, поэтому я пытаюсь добавить значок в свой системный трей, который будет меняться в зависимости от состояния моего NumLk. Этот .py всегда будет работать, когда мой компьютер включен.

Пока мне удалось смешать 3 кода, и я могу отобразить значок в системном трее, но он не обновляется при изменении состояния NumLk. На самом деле, если я дважды нажимаю NumLk, я все равно получаю тот же значок (on) и получаю эту ошибку:

QCoreApplication::exec: The event loop is already running
  File "\systray_icon_NumLk_on_off.py", line 21, in on_key_press
    main(on)
  File "\systray_icon_NumLk_on_off.py", line 46, in main
    sys.exit(app.exec_())
SystemExit: -1

Мой код может быть не лучшим способом сделать это, поэтому любая альтернатива приветствуется! Вот что я придумал до сих пор:

#####get the state of NumLk key
from win32api import GetKeyState 
from win32con import VK_NUMLOCK
#how to use: print(GetKeyState(VK_NUMLOCK))
#source: http://stackoverflow.com/questions/21160100/python-3-x-getting-the-state-of-caps-lock-num-lock-scroll-lock-on-windows

#####Detect if NumLk is pressed 
import pyglet
from pyglet.window import key
window = pyglet.window.Window()
#source: http://stackoverflow.com/questions/28324372/detecting-a-numlock-capslock-scrlock-keypress-keyup-in-python

on=r'on.png'
off=r'off.png'

@window.event
def on_key_press(symbol, modifiers):
    if symbol == key.NUMLOCK:
        if GetKeyState(VK_NUMLOCK):  
            #print(GetKeyState(VK_NUMLOCK))#should be 0 and 1 but 
            main(on)
        else:
            main(off)
@window.event
def on_draw():
    window.clear()

### display icon in systray
import sys  
from PyQt5 import QtCore, QtGui, QtWidgets
#source: http://stackoverflow.com/questions/893984/pyqt-show-menu-in-a-system-tray-application  - add answer PyQt5
class SystemTrayIcon(QtWidgets.QSystemTrayIcon):

    def __init__(self, icon, parent=None):
        QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
        menu = QtWidgets.QMenu(parent)
        exitAction = menu.addAction("Exit")
        self.setContextMenu(menu)

def main(image):
    app = QtWidgets.QApplication(sys.argv)

    w = QtWidgets.QWidget()
    trayIcon = SystemTrayIcon(QtGui.QIcon(image), w)

    trayIcon.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    pyglet.app.run()

person MagTun    schedule 22.01.2017    source источник
comment
Почему чрезмерное использование библиотек? Qt может проверять нажатия клавиш так же, как и Pyglet. Также может и win32api. Итак, почему спагетти-логика?   -  person Torxed    schedule 23.01.2017


Ответы (1)


Причина QCoreApplication::exec: The event loop is already running на самом деле в том, что вы пытаетесь запустить app.run() дважды. Qt заметит, что экземпляр уже запущен, и выдаст это исключение. Когда вместо этого вы хотите просто поменять значок в уже запущенном экземпляре.

Ваша основная проблема здесь, на самом деле, это сочетание библиотек для решения одной задачи, если вы спросите меня.
Скорее две задачи, но использование Qt5 для графической части вполне приемлемо.

То, как вы используете Pyglet, изначально неверно.
Pyglet задуман как очень мощная и эффективная графическая библиотека, на основе которой вы строите графический движок. Например, если вы делаете игру, видеоплеер или что-то в этом роде.

То, как вы используете win32api, также неверно, потому что вы используете его в графическом окне, которое проверяет значение только при нажатии клавиши внутри этого окна.

Теперь, если вы переместите свой код win32api в поток (точнее, в QtThread), вы можете проверить состояние независимо от того, нажали вы клавишу внутри своего графического окна или нет.

import sys  
import win32api
import win32con
from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread, enumerate
from time import sleep

class SystemTrayIcon(QtWidgets.QSystemTrayIcon):
    def __init__(self, icon, parent=None):
        QtWidgets.QSystemTrayIcon.__init__(self, icon, parent)
        menu = QtWidgets.QMenu(parent)
        exitAction = menu.addAction("Exit")
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(QtWidgets.qApp.quit)
        self.setContextMenu(menu)

class KeyCheck(QtCore.QThread):
    def __init__(self, mainWindow):
        QtCore.QThread.__init__(self)
        self.mainWindow = mainWindow

    def run(self):
        main = None
        for t in enumerate():
            if t.name == 'MainThread':
                main = t
                break

        while main and main.isAlive():
            x = win32api.GetAsyncKeyState(win32con.VK_NUMLOCK)
            ## Now, GetAsyncKeyState returns three values,
            ## 0 == No change since last time
            ## -3000 / 1 == State changed
            ##
            ## Either you use the positive and negative values to figure out which state you're at.
            ## Or you just swap it, but if you just swap it you need to get the startup-state correct.
            if x == 1:
                self.mainWindow.swap()
            elif x < 0:
                self.mainWindow.swap()
            sleep(0.25)

class GUI():
    def __init__(self):
        self.app = QtWidgets.QApplication(sys.argv)

        self.state = True

        w = QtWidgets.QWidget()
        self.modes = {
            True : SystemTrayIcon(QtGui.QIcon('on.png'), w),
            False : SystemTrayIcon(QtGui.QIcon('off.png'), w)
        }

        self.refresh()

        keyChecker = KeyCheck(self)
        keyChecker.start()

        sys.exit(self.app.exec_())

    def swap(self, state=None):
        if state is not None:
            self.state = state
        else:
            if self.state:
                self.state = False
            else:
                self.state = True
        self.refresh()

    def refresh(self):
        for mode in self.modes:
            if self.state == mode:
                self.modes[mode].show()
            else:
                self.modes[mode].hide()

GUI()

Обратите внимание, что я не часто программирую Qt (раз в 4 года или около того).
Так что этот код содержит ошибки в лучшем случае. Вы должны нажать Ctrl+C + Нажмите "Выход" в меню, чтобы это остановить.

Честно говоря, я не хочу тратить больше времени и усилий на изучение того, как управлять потоками в Qt или как правильно выходить из приложения, это не моя область знаний. Но это даст вам грубый рабочий пример того, как вы можете поменять местами значок в нижнем углу вместо того, чтобы пытаться повторно создать цикл main(), который вы сделали.

person Torxed    schedule 23.01.2017
comment
Большое спасибо @Torxed. Это было именно то, что я искал. Спагетти-логика заключалась в том, что я новичок в python и поэтому скопировал коды из несвязанных сообщений. Теперь, когда вы навели порядок, мне просто нужно идти по прямой дороге! Большое спасибо за ваше время! - person MagTun; 23.01.2017
comment
@Энора С удовольствием. Я начал так же, как и вы, просто выхватывал код тут и там и собирал их вместе в один комок спагетти. Это работает как средство обучения, так что продолжайте в том же духе. Желаю удачи в вашем приключении и хорошей работы, чтобы получить первую часть работы. - person Torxed; 23.01.2017
comment
Я опоздал на вечеринку, но если вы удалите Async из win32api.GetAsyncKeyState(win32con.VK_NUMLOCK), вы получите фактическое состояние numlock - person binarynoise; 25.10.2020