How to detect a click or other mouse event in NotifyIcon from win32gui?

1

I'm trying to modify the win10toast library so that I can pass a callback that runs when the user clicks on the Windows 10 notification that I show.

The "meat" of the library, which I condested as much as I could to create a minimal and functional example, is here:

from win32api import GetModuleHandle
from win32api import PostQuitMessage
from win32con import CW_USEDEFAULT
from win32con import WM_DESTROY
from win32con import WM_USER
from win32con import WS_OVERLAPPED
from win32con import WS_SYSMENU
from win32gui import CreateWindow
from win32gui import DestroyWindow
from win32gui import NIF_ICON
from win32gui import NIF_INFO
from win32gui import NIF_MESSAGE
from win32gui import NIF_TIP
from win32gui import NIM_ADD
from win32gui import NIM_DELETE
from win32gui import NIM_MODIFY
from win32gui import RegisterClass
from win32gui import UnregisterClass
from win32gui import Shell_NotifyIcon
from win32gui import UpdateWindow
from win32gui import WNDCLASS
import time


# Callback de evento da janela
def on_proc(hwnd, msg, wparam, lparam):
    print(f'Recebido evento de janela, mensagem: {msg}')
    if msg == WM_DESTROY:
        nid = (hwnd, 0)
        # Destruir a notificação quando a janela for destruída
        Shell_NotifyIcon(NIM_DELETE, nid)
        PostQuitMessage(0)

    return None

# Criar janela à qual a notificação pertence (0x0)
wc = WNDCLASS()
hinst = wc.hInstance = GetModuleHandle(None)
wc.lpszClassName = str("PythonTaskbar")  # must be a string
wc.lpfnWndProc = on_proc

classAtom = RegisterClass(wc)
style = WS_OVERLAPPED | WS_SYSMENU
hwnd = CreateWindow(classAtom, "Taskbar", style,
                         0, 0, CW_USEDEFAULT,
                         CW_USEDEFAULT,
                         0, 0, hinst, None)
UpdateWindow(hwnd)

# Criar a notificação
flags = NIF_ICON | NIF_MESSAGE | NIF_TIP
nid = (hwnd, 0, flags, WM_USER + 20, None, "Tooltip")
Shell_NotifyIcon(NIM_ADD, nid)

# Mostrar a notificação
Shell_NotifyIcon(NIM_MODIFY, (hwnd, 0, NIF_INFO,
                              WM_USER + 20,
                              None, "Balloon Tooltip", "Hello, SO PT", 200, "Título"))

# Esperar 3 segundos e destruir a janela
time.sleep(3)
DestroyWindow(hwnd)
UnregisterClass(wc.lpszClassName, None)

When the window receives an event message, such as the destroyed window, the callback on_proc is called and displays the received message code. What I would like to happen is that by clicking or interacting with the notification, this callback would receive the interaction message, since the window is linked to the notification.

The documentation for Official Windows API seems to indicate that in fact, the window associated with the notification should receive its events:

  

Type: HWND

     

A handle to the window that receives notifications associated with an icon in the notification area.

Unfortunately, the program output shows that only the messages in the window itself, not the notification, are arriving:

Recebido evento de janela, mensagem: 144
Recebido evento de janela, mensagem: 2
Recebido evento de janela, mensagem: 130

Here, code 2 warns of the event WM_DESTROY , the 130 WM_NCDESTROY and the 144 do not know, but it happens even when the notification is not created, so it does not seem to be related to it.

How do I receive notification-related events?

    
asked by anonymous 30.06.2018 / 23:59

1 answer

2

The win32 GUI API is controlled through messages, which are sent to a process message queue and are waiting there for processing.

For a callback to be called, you need to queue the incoming messages and dispatch them. This is consumed through a message loop. In C ++ the code is more or less the following:

while (1) {
    GetMessage(msg);
    DispatchMessage(msg);
}

Why is this important? Because your code does not have a message loop. So, even though click events are being queued, they are never being consumed and they are never reaching your callback. When you destroy the window ( DestroyWindow ), the queue is emptied and you lose all events.

win32gui has a method, called PumpMessages , that makes the message loop until it receives the message WM_QUIT . From the documentation:

  

PumpMessages: Pumps all messages for the current thread until a WM_QUIT message.

Adding PumpMessages before time.sleep will cause your callback to receive the tooltip messages. The relevant part of the modified code would be:

# Esperar 3 segundos e destruir a janela
win32gui.PumpMessages()
time.sleep(3)

In my tests, other events were printed by the callback (1044 is WM_USER + 20):

Recebido evento de janela, mensagem: 1044
Recebido evento de janela, mensagem: 799
Recebido evento de janela, mensagem: 1044
    
06.07.2018 / 15:08