How to detect a click or other mouse event in win32gui’s Notifyicon?

Asked

Viewed 226 times

1

I’m trying to modify the library win10toast 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’ve mastered as much as I can 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 code of the received message. What I would like to happen is that by clicking or interacting with the notification, that callback gets the interaction message, since the window is connected to the notification.

The documentation of 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 from the window itself, and not the notification, are coming:

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 of WM_NCDESTROY and 144 don’t know, but it happens even when the notification is not created, so it doesn’t seem to have any relation to it.

How do I receive events related to notification?

1 answer

2


The Win32 GUI API is controlled through messages, which are sent to a queue of process messages and wait there until they are processed.

For a callback to be called, you need to consume the incoming messages from the queue 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 doesn’t have a message loop. So although click events are being lined up, they are never being consumed and are never getting to your callback. When you destroy the window (DestroyWindow), the queue is emptied and you lose all events.

The win32gui has a method, called PumpMessages, that loops messages until you receive the message WM_QUIT. Of documentation:

Pumpmessages: Pumps all messages for the Current thread until a WM_QUIT message.

Adding the PumpMessages before the time.sleep will cause your callback to receive messages from the tooltip. 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 callback (1044 is WM_USER + 20):

Recebido evento de janela, mensagem: 1044
Recebido evento de janela, mensagem: 799
Recebido evento de janela, mensagem: 1044
  • Perfect! The lparam of the message 1044 is 1028 if the notification expires without clicking, and 1029 expires because of the click. Thank you very much!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.