SDL icon indicating copy to clipboard operation
SDL copied to clipboard

Add click-through functionality for windows

Open AQtun81 opened this issue 3 weeks ago • 2 comments

Description

This PR adds a SDL_SetWindowMousePassthrough function which allows users to create windows that don't capture input allowing them to create overlay-style apps.

This implementation has only been tested on Windows 11, other platforms require validation. (testing used SDL window's HWND with contents of WIN_SetWindowMousePassthrough directly)

TODO: SDL_video.h is missing docummentation comment explaining the functionality DYNAPI is possibly misconfigured/missing information (the new function is not present in build)

Existing Issue(s)

#12683

AQtun81 avatar Dec 01 '25 10:12 AQtun81

For dynapi, go ahead and revert your changes in that directory and run src/dynapi/gendynapi.py instead.

Also, in general, please use the SDL coding style, e.g.

if (x) {
} else {
}

slouken avatar Dec 01 '25 15:12 slouken

I have made so many small errors and commits fixing them, so I went ahead and squashed the commits, it should make the changes more readable.

Regarding the API, should SDL_SetWindowMousePassthrough return a bool? Since as far as I know some platforms don't allow for mouse passthrough for bordered windows, this function could enforce that it can only be used on borderless windows ensuring consistent cross-platform behavior. On the other hand not everyone might care about cross-platform as long as their platform/s supports it.

This is the program I'm using to test the feature, it renders a red square on a transparent window and toggles mouse passthrough when mouse cursor hovers over it or leaves the area.

#include <SDL3/SDL.h>

int main()
{
    const SDL_FRect testRect = {100, 100, 200, 200};
    bool running = true;
    bool mousePassthrough = false;
    SDL_Event event;

    if (!SDL_Init(SDL_INIT_VIDEO)) {
        SDL_Log("SDL_Init failed: %s", SDL_GetError());
        return 1;
    }

    SDL_Window* window = SDL_CreateWindow("Mouse Passthrough", 800, 600,
        SDL_WINDOW_TRANSPARENT | SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALWAYS_ON_TOP);
    if (!window) {
        SDL_Log("SDL_CreateWindow failed: %s", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    SDL_Renderer* renderer = SDL_CreateRenderer(window, NULL);
    if (!renderer) {
        SDL_Log("SDL_CreateRenderer failed: %s", SDL_GetError());
        SDL_DestroyWindow(window);
        SDL_Quit();
        return 1;
    }
    SDL_SetRenderVSync(renderer, 1);
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

    while (running) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_EVENT_QUIT) {
                running = false;
            }
            if (event.type == SDL_EVENT_KEY_DOWN) {
                if (event.key.key == SDLK_ESCAPE) {
                    running = false;
                }
            }
        }

        // toggle mouse passthrough
        float x, y;
        int xOffset, yOffset;
        SDL_GetGlobalMouseState(&x, &y);
        SDL_GetWindowPosition(window, &xOffset, &yOffset);
        x -= (float)xOffset;
        y -= (float)yOffset;
        bool hovered = x >= testRect.x && x < testRect.x + testRect.w &&
                       y >= testRect.y && y < testRect.y + testRect.h;
        if (!hovered != mousePassthrough) {
            mousePassthrough = !hovered;
            SDL_SetWindowMousePassthrough(window, mousePassthrough);
            SDL_Log(mousePassthrough ? "passthrough enabled\n" : "passthrough disabled\n");
        }

        // render
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
        SDL_RenderClear(renderer);
        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 128);
        SDL_RenderFillRect(renderer, &testRect);
        SDL_RenderPresent(renderer);
    }

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

AQtun81 avatar Dec 03 '25 00:12 AQtun81

Yes, it should return a bool.

slouken avatar Dec 17 '25 19:12 slouken