pyhook и wxpython - использование вместе вызывает зависание - PullRequest
0 голосов
/ 10 июля 2019

Мое приложение python перестает отвечать на запросы, когда я несколько раз что-то делаю "wx" ... будь то нажатия кнопок, меню, внутри текстовой области и т. Д.

Кажется, это проблема с моимиспользование оконных хуков в других местах программы.Когда они используются вместе, дела идут плохо.

Хотя приведенный ниже пример может не иметь большого смысла вне контекста реального приложения, это то, что я придумал в качестве минимального примера.Я взял все, что мог, чтобы попытаться диагностировать проблему с минимальными дополнительными сложностями.

Похоже, что второй насос сообщений Windows, вызванный из рабочего потока, является фактором, который делает это возможным.Если я это прокомментирую, приложение не зависнет ... или не сделает что-нибудь полезное ... Хуксам требуется насос, и я не могу поместить его в свой поток пользовательского интерфейса, иначе он перестанет зависать, что привело меня кво-первых, это POC ...

Если вы запустите это и нажмете кнопку мыши около десяти раз, вы заметите, что текстовые блоки искажены, а затем приложение просто зависает.Кто-нибудь знает, почему?

Я использую

  • python 2.7.16 32-битный
  • pyHook 1.5.1
  • wxPython3.0.2.0 32 бита для python 2.7

(обновление) Я также пытался с теми же результатами

  • python 2.7.16 32 бита
  • pyHook1.5.1
  • wxPython 4.0.6 32 бит для python 2.7

Мы используем их в реальном приложении, потому что мои старшие сотрудники хотят продолжать поддерживать Windows XP (в целомдругой разговор)

main.py

import wx
from twisted.internet import wxreactor

from windows_hooks import WindowsHooksWrapper
from main_window import MainWindow


def main():
    hook_wrapper = WindowsHooksWrapper()
    hook_wrapper.start()

    app = wx.App(False)
    frame = MainWindow(None, 'Hooks Testing', hook_wrapper)

    from twisted.internet import reactor
    reactor.registerWxApp(app)
    reactor.run()

    hook_wrapper.stop()


if __name__ == "__main__":
    wxreactor.install()
    main()

windows_hooks.py

import pyHook
import threading
import pythoncom


class WindowsHooksWrapper(object):
    def __init__(self):
        self.hook_manager = None
        self.started = False
        self.thread = threading.Thread(target=self.thread_proc)
        self.window_to_publish_to = None

        print "HookWrapper created on Id {}".format(threading.current_thread().ident)

    def __del__(self):
        self.stop()

    def start(self):
        if self.started:
            self.stop()

        self.started = True
        self.thread.start()

    def stop(self):
        if not self.started:
            return

        self.started = False
        self.thread.join()

    def on_mouse_event(self, event):
        """
        Called back from pyHooks library on a mouse event
        :param event: event passed from pyHooks
        :return: True if we are to pass the event on to other hooks and the process it was intended
         for. False to consume the event.
        """

        if self.window_to_publish_to:
            from twisted.internet import reactor
            reactor.callFromThread(self.window_to_publish_to.print_to_text_box, event)
        return True

    def thread_proc(self):
        print "Thread started with Id {}".format(threading.current_thread().ident)

        # Evidently, the hook must be registered on the same thread with the windows msg pump or
        #     it will not work and no indication of error is seen
        # Also note that for exception safety, when the hook manager goes out of scope, the
        #     documentation says that it unregisters all outstanding hooks
        self.hook_manager = pyHook.HookManager()
        self.hook_manager.MouseAll = self.on_mouse_event
        self.hook_manager.HookMouse()

        while self.started:
            pythoncom.PumpMessages()

        print "Thread exiting..."

        self.hook_manager.UnhookMouse()
        self.hook_manager = None

main_window.py

import threading
import wx


class MainWindow(wx.Frame):
    def __init__(self, parent, title, hook_manager):
        wx.Frame.__init__(self, parent, title=title, size=(800, 600))
        self.hook_manager = hook_manager

        self.CreateStatusBar()

        menu_file = wx.Menu()
        menu_item_exit = menu_file.Append(wx.ID_EXIT, "E&xit", " Terminate the program")

        menu_help = wx.Menu()
        menu_item_about = menu_help.Append(wx.ID_ABOUT, "&About", " Information about this program")

        menu_bar = wx.MenuBar()
        menu_bar.Append(menu_file, "&File")
        menu_bar.Append(menu_help, "&Help")
        self.SetMenuBar(menu_bar)

        self.panel = MainPanel(self, hook_manager)

        self.Bind(wx.EVT_MENU, self.on_about, menu_item_about)
        self.Bind(wx.EVT_MENU, self.on_exit, menu_item_exit)

        self.Show(True)

    def on_about(self, e):
        dlg = wx.MessageDialog(self, "A window to test Windows Hooks", "About Test Windows Hooks",
                               wx.OK)
        dlg.ShowModal()
        dlg.Destroy()

    def on_exit(self, e):
        self.Close(True)


class MainPanel(wx.Panel):
    def __init__(self, parent, hook_manager):
        self.hook_manager = hook_manager
        hook_manager.window_to_publish_to = self

        self.consuming = False

        wx.Panel.__init__(self, parent)
        self.textbox = wx.TextCtrl(self, style=wx.TE_MULTILINE | wx.TE_READONLY)

        self.horizontal = wx.BoxSizer()
        self.horizontal.Add(self.textbox, proportion=1, flag=wx.EXPAND)

        self.sizer_vertical = wx.BoxSizer(wx.VERTICAL)
        self.sizer_vertical.Add(self.horizontal, proportion=1, flag=wx.EXPAND)
        self.SetSizerAndFit(self.sizer_vertical)
        self.called_back_count = 0

    def print_to_text_box(self, event):
        self.called_back_count += 1
        print "Printing message {} on Thread with Id {}".format(self.called_back_count,
                                                                threading.current_thread().ident)
        self.textbox.AppendText('MessageName: {}\n'.format(event.MessageName))
        self.textbox.AppendText('Message: {}\n'.format(event.Message))
        self.textbox.AppendText('Time: {}\n'.format(event.Time))
        self.textbox.AppendText('Window: {}\n'.format(event.Window))
        self.textbox.AppendText('WindowName: {}\n'.format(event.WindowName))
        self.textbox.AppendText('Position: {}\n'.format(event.Position))
        self.textbox.AppendText('Wheel: {}\n'.format(event.Wheel))
        self.textbox.AppendText('Injected: {}\n'.format(event.Injected))
        self.textbox.AppendText('---\n')

Я также пробовалверсия без Twisted и использовала wxPostEvent с пользовательским событием вместо этого, но мы подозревали, что это может быть проблемой, поэтому я изменил ее на использование Twisted, и это все равно не годится.

Я опубликую отредактированный листинг сэто немного.

...