Skip to content

wayland/webos: send the cursor visibility only when it changes - #17

Open
mgonzalez01 wants to merge 1 commit into
webosbrew:webOS-3.4.xfrom
mgonzalez01:fix/screensaver-cursor-visibility
Open

mgonzalez01 wants to merge 1 commit into
webosbrew:webOS-3.4.xfrom
mgonzalez01:fix/screensaver-cursor-visibility

Conversation

@mgonzalez01

@mgonzalez01 mgonzalez01 commented Oct 12, 2026 •

Copy link
Copy Markdown

On an LG TV, an SDL3 app that allows the screensaver (SDL_HINT_VIDEO_ALLOW_SCREENSAVER) never sees LG's screensaver stay up: it starts after the TV's idle timer and ends again within a second, with no input. On an OLED it shows as a short black flash every few minutes.

Wayland_ShowCursor() calls WaylandWebOS_SetCursorVisibility(mouse->cursor_visible) on every cursor redraw, and SDL redraws the cursor on every mouse focus change. When the screensaver comes up, the app's window loses mouse focus, SDL asks the compositor to show the pointer again, and the screensaver ends. Apps that block the screensaver, SDL's default, never see this, which is probably why it hasn't come up with games.

This adds WaylandWebOS_UpdateCursorVisibility(), which sends the request only when it differs from what was last sent (the existing cursor_hidden state), as SDL2-webOS did, and uses it in Wayland_ShowCursor(). SDL_webOSCursorVisibility() still sends every request it's given.

Tested

LG OLED55G2, webOS 10.3.1 (Rockhopper release 10.3.1-3001), with the minimal app below: an SDL3 window that allows the screensaver and clears itself 10 times a second. The same binary and package each run; only libSDL3.so.0 differs. Lines from the TV's /var/log/messages, whose timestamps are seconds since boot.

release-3.4.16-webos.5 as is: the screensaver lasts 43 ms.

[16511.013748100] user.info sam [] SAM NL_APP_LAUNCH_BEGIN {"app_id":"com.webos.app.screensaver","caller_id":"com.webos.service.tvpower","mode":"normal"}
[16511.674318820] user.info surface-manager [] LSM NL_VSC {"app_id":"com.webos.app.screensaver","visible":true,"window_type":"_WEBOS_WINDOW_TYPE_SCREENSAVER"}
[16511.692066700] user.info tvpowerd [] tvpowerd NL_POWER_STATE {"old":"Active","new":"Screen Saver"}
[16511.734683620] user.info tvpowerd [] tvpowerd NL_POWER_STATE {"old":"Screen Saver","new":"Active"}
[16512.015280780] user.info surface-manager [] LSM NL_VSC {"app_id":"com.webos.app.screensaver","visible":false,"window_type":"_WEBOS_WINDOW_TYPE_SCREENSAVER"}

The app logged SDL_EVENT_WINDOW_MOUSE_LEAVE and SDL_EVENT_WINDOW_FOCUS_LOST at 16511.67, then SDL_EVENT_WINDOW_FOCUS_GAINED and SDL_EVENT_WINDOW_MOUSE_ENTER at 16512.03.

With this commit (the library reports SDL-3.4.16-release-3.4.16-webos.5-1-gb33e293): the screensaver stays.

[18796.012495100] user.info sam [] SAM NL_APP_LAUNCH_BEGIN {"app_id":"com.webos.app.screensaver","caller_id":"com.webos.service.tvpower","mode":"normal"}
[18796.649880600] user.info surface-manager [] LSM NL_VSC {"app_id":"com.webos.app.screensaver","visible":true,"window_type":"_WEBOS_WINDOW_TYPE_SCREENSAVER"}
[18796.672761240] user.info tvpowerd [] tvpowerd NL_POWER_STATE {"old":"Active","new":"Screen Saver"}

tvpower, polled twice a second, still said Screen Saver 119 s later, when the run ended. An earlier version of the change held the same way, and so does a larger app that showed the flash every few minutes before. One more difference: with the release, the window receives a mouse enter right at startup, and with this commit it doesn't, since nothing tells the compositor "visible" at init any more. The magic remote's pointer still appears over the window as soon as the remote moves.

Only tested on this one TV.

The test app

Built with webosbrew's toolchain against SDL-webOS, and packaged with libSDL3.so.0 in lib/ next to bin/ (rpath $ORIGIN/../lib). It writes /tmp/sdlrepro.log. WAYLAND_DEBUG printed nothing on this TV.

/* Minimal reproduction for SDL-webOS: LG's screensaver ends within about a second over an
 * SDL3 window that allows the screensaver. The window does nothing but clear itself 10 times
 * a second. Logs go to /tmp/sdlrepro.log: libwayland's protocol trace (WAYLAND_DEBUG=1), SDL's
 * webOS log lines, and every SDL window event with CLOCK_REALTIME (the trace's clock) and
 * CLOCK_BOOTTIME (/proc/uptime's clock). */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

static void stamp(const char *what, Uint32 type)
{
    struct timespec real, boot;
    clock_gettime(CLOCK_REALTIME, &real);
    clock_gettime(CLOCK_BOOTTIME, &boot);
    /* libwayland prints "[%7u.%03u]": CLOCK_REALTIME in ms, truncated to 32 bits. */
    unsigned long long us = (unsigned long long)real.tv_sec * 1000000ULL + real.tv_nsec / 1000;
    unsigned int ms = (unsigned int)(us / 1000);
    fprintf(stderr, "[%7u.%03u] repro: %s 0x%x (uptime %ld.%02ld)\n", ms, (unsigned int)(us % 1000),
            what, type, (long)boot.tv_sec, boot.tv_nsec / 10000000L);
}

int main(int argc, char **argv)
{
    (void)argc;
    (void)argv;
    if (!freopen("/tmp/sdlrepro.log", "w", stderr)) {
        return 1;
    }
    setvbuf(stderr, NULL, _IOLBF, 0);
    setenv("WAYLAND_DEBUG", "1", 1);
    /* Launchers and media apps allow the screensaver; SDL blocks it by default. */
    SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
    SDL_SetHint("SDL_LOGGING", "app=info,system=info,video=info,*=warn");
    if (!SDL_Init(SDL_INIT_VIDEO)) {
        fprintf(stderr, "SDL_Init: %s\n", SDL_GetError());
        return 1;
    }
    SDL_Window *window = SDL_CreateWindow("SDL screensaver repro", 1920, 1080, 0);
    SDL_Renderer *renderer = window ? SDL_CreateRenderer(window, NULL) : NULL;
    if (!renderer) {
        fprintf(stderr, "window: %s\n", SDL_GetError());
        return 1;
    }
    fprintf(stderr, "repro: %s, screensaver %s\n", SDL_GetRevision(),
            SDL_ScreenSaverEnabled() ? "allowed" : "blocked");
    for (;;) {
        SDL_Event event;
        while (SDL_WaitEventTimeout(&event, 100)) {
            if (event.type == SDL_EVENT_QUIT) {
                SDL_Quit();
                return 0;
            }
            if (event.type >= SDL_EVENT_WINDOW_FIRST && event.type <= SDL_EVENT_WINDOW_LAST) {
                stamp("SDL window event", event.type);
            }
        }
        SDL_SetRenderDrawColor(renderer, 16, 24, 40, 255);
        SDL_RenderClear(renderer);
        SDL_RenderPresent(renderer);
    }
}

Wayland_ShowCursor() runs on every mouse focus change and asked the
compositor to show the pointer each time. When LG's screensaver takes
focus over an app that allows it, that request ends the screensaver
within a second: on an LG OLED55G2 (webOS 25) with an SDL3 window and
SDL_HINT_VIDEO_ALLOW_SCREENSAVER, tvpower went from Screen Saver back
to Active after 43 ms with no input. Sending only changes, as SDL2-webOS
did, keeps the screensaver up. SDL_webOSCursorVisibility() still sends
every request it is given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant