Home > Software engineering >  tkinter - wm_protocol dosent handle the WM_HELP message
tkinter - wm_protocol dosent handle the WM_HELP message

Time:10-06

Does tkinter just handle known protocols? If so, it seems useless to me. If not I'm not sure why this code dosent works for me under microsoft.

import tkinter as tk
def lol():
    print('hi')

root = tk.Tk()

menu = tk.Menu(root)
root.config(menu=menu)
filemenu = tk.Menu(menu)
menu.add_cascade(label="File", menu=filemenu)
filemenu.add_command(label="New")

root.wm_protocol('WM_HELP',lol)
root.mainloop()

As I understand this a message should be sent by toggle F1 and the window should respond with the command to this protocol.

CodePudding user response:

X11

Right at the bottom of the X11 implementation of Tk (the library under Tkinter) is this code, which I copy here without the comments:

void
TkWmProtocolEventProc(
    TkWindow *winPtr,
    XEvent *eventPtr)
{
    WmInfo *wmPtr;
    ProtocolHandler *protPtr;
    Atom protocol;
    int result;
    const char *protocolName;
    Tcl_Interp *interp;

    protocol = (Atom) eventPtr->xclient.data.l[0];

    if (protocol == Tk_InternAtom((Tk_Window) winPtr, "_NET_WM_PING")) {
        Window root = XRootWindow(winPtr->display, winPtr->screenNum);

        eventPtr->xclient.window = root;
        (void) XSendEvent(winPtr->display, root, False,
                (SubstructureNotifyMask|SubstructureRedirectMask), eventPtr);
        return;
    }

    wmPtr = winPtr->wmInfoPtr;
    if (wmPtr == NULL) {
        return;
    }

    protocolName = Tk_GetAtomName((Tk_Window) winPtr, protocol);
    for (protPtr = wmPtr->protPtr; protPtr != NULL;
            protPtr = protPtr->nextPtr) {
        if (protocol == protPtr->protocol) {
            Tcl_Preserve(protPtr);
            interp = protPtr->interp;
            Tcl_Preserve(interp);
            result = Tcl_EvalEx(interp, protPtr->command, -1, TCL_EVAL_GLOBAL);
            if (result != TCL_OK) {
                Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
                        "\n    (command for \"%s\" window manager protocol)",
                        protocolName));
                Tcl_BackgroundException(interp, result);
            }
            Tcl_Release(interp);
            Tcl_Release(protPtr);
            return;
        }
    }

    if (protocol == Tk_InternAtom((Tk_Window) winPtr, "WM_DELETE_WINDOW")) {
        Tk_DestroyWindow((Tk_Window) wmPtr->winPtr);
    }
}

Basically, Tk only really handles _NET_WM_PING (which it responds to for you — if you're in this handler, the event loop is running and that's what that protocol is checking for) and WM_DELETE_WINDOW, but it can also handle any protocol that works exactly the same as WM_DELETE_WINDOW at the message level. In particular, you've got no access to the fields of the event (except implicitly), and can't send responses to anything. That means it can handle WM_SAVE_YOURSELF (the old session-management protocol) but can't do WM_TAKE_FOCUS (and doesn't need to; Tk does its own focus management, just like every other application these days). We'd welcome an implementation of _NET_WM_SYNC_REQUEST, but it's not a high priority.

Windows

On Windows, there's some additional processing. In particular, WM_HELP looks like something that should be handled by TkTranslateWinEvent.

int
TkTranslateWinEvent(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam,
    LRESULT *resultPtr)
{
    *resultPtr = 0;
    switch (message) {
    case WM_RENDERFORMAT: {
        TkWindow *winPtr = (TkWindow *) Tk_HWNDToWindow(hwnd);

        if (winPtr) {
            TkWinClipboardRender(winPtr->dispPtr, wParam);
        }
        return 1;
    }

    case WM_RENDERALLFORMATS: {
        TkWindow *winPtr = (TkWindow *) Tk_HWNDToWindow(hwnd);

        if (winPtr && OpenClipboard(hwnd)) {
            if (GetClipboardOwner() == hwnd) {
                TkWinClipboardRender(winPtr->dispPtr, CF_TEXT);
            }
            CloseClipboard();
        }
        return 1;
    }

    case WM_COMMAND:
    case WM_NOTIFY:
    case WM_VSCROLL:
    case WM_HSCROLL: {
        HWND target = (message == WM_NOTIFY)
                ? ((NMHDR*)lParam)->hwndFrom : (HWND) lParam;

        if (target && target != hwnd) {
            *resultPtr = SendMessageW(target, message, wParam, lParam);
            return 1;
        }
        break;
    }

    case WM_LBUTTONDOWN:
    case WM_LBUTTONDBLCLK:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONDBLCLK:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONDBLCLK:
    case WM_XBUTTONDOWN:
    case WM_XBUTTONDBLCLK:
    case WM_LBUTTONUP:
    case WM_MBUTTONUP:
    case WM_RBUTTONUP:
    case WM_XBUTTONUP:
    case WM_MOUSEMOVE:
        TkWinPointerEvent(hwnd, (short) LOWORD(lParam), (short) HIWORD(lParam));
        return 1;

    case WM_SYSKEYDOWN:
    case WM_KEYDOWN:
        if (wParam == VK_PACKET) {
            *resultPtr =
                PostMessageW(hwnd, message, HIWORD(lParam), LOWORD(lParam));
            return 1;
        }
        /* fall through */
    case WM_CLOSE:
    case WM_SETFOCUS:
    case WM_KILLFOCUS:
    case WM_DESTROYCLIPBOARD:
    case WM_UNICHAR:
    case WM_CHAR:
    case WM_SYSKEYUP:
    case WM_KEYUP:
    case WM_MOUSEWHEEL:
    case WM_MOUSEHWHEEL:
        GenerateXEvent(hwnd, message, wParam, lParam);
        return 1;
    case WM_MENUCHAR:
        GenerateXEvent(hwnd, message, wParam, lParam);
        *resultPtr = MAKELONG (0, MNC_CLOSE);
        return 1;
    }
    return 0;
}

As you can see, it doesn't handle it. If it were to, mapping it to generate the <KeyPress-Help> event would probably be the right approach (yes, that's a real event, one that you never see on Windows because of the different standard keyboard layout). Which would also require changes to GenerateXEvent (same file, just below).

I've no idea what the DefWindowProcW does with WM_HELP; that's the function of last resort for an event in Windows, and that's definitely where those events are going at the moment.


Changing any of this is possible, but does require a little bit of C programming at a level of GUI programming that almost nobody works at. Patches can be submitted at https://core.tcl-lang.org/tk/tktnew (I'd recommend developing against the core-8-5-branch in this case, as I think this is a small feature change that can be slipped into patches for old versions).

  • Related