dvui icon indicating copy to clipboard operation
dvui copied to clipboard

SDL3 hidden window hangs if no animation is playing

Open knightpp opened this issue 2 months ago • 11 comments

In #597 I accidentally fixed another issue, where hidden window stopped receiving events (or something).

Replacing animated spinner with some static widget results in a hang on window hide action.

Steps:

  1. Run the app
  2. You should see tray entry, click on Toggle visibility entry
  3. You can no longer unhide the window
pub fn appFrame() !dvui.App.Result {
    var vbox = dvui.box(
        @src(),
        .{ .dir = .vertical },
        .{ .rect = .cast(dvui.windowRect()), .background = true },
    );
    defer vbox.deinit();
	
	// dvui.spinner(@src, .{});
    dvui.label(@src(), "test", .{}, .{});
    return .ok;
}

updated repro repo

full main.zig
const std = @import("std");
const dvui = @import("dvui");
const c = @import("backend").c;

pub const dvui_app: dvui.App = .{
    .config = .{
        .options = .{
            .size = .{ .w = 250.0, .h = 350.0 },
            .min_size = .{ .w = 250.0, .h = 350.0 },
            .title = "dvui sdl3 tray repro",
            // .hidden = true,
        },
    },
    .frameFn = appFrame,
    .initFn = appInit,
    .deinitFn = appDeinit,
};
pub const main = dvui.App.main;
pub const panic = dvui.App.panic;
pub const std_options: std.Options = .{
    .logFn = dvui.App.logFn,
    .log_level = .debug,
};

var debug_allocator = std.heap.DebugAllocator(.{}).init;
const gpa = debug_allocator.allocator();

fn trayCallback(user_data: ?*anyopaque, entry: ?*c.SDL_TrayEntry) callconv(.c) void {
    const window: *c.SDL_Window = @ptrCast(@alignCast(user_data));
    std.debug.print("tray callback {p}\n", .{window});

    if (window_hidden) {
        if (!c.SDL_ShowWindow(window)) {
            std.log.err("show window failed: {s}", .{c.SDL_GetError()});
            return;
        }
        if (!c.SDL_RaiseWindow(window)) {
            std.log.err("raise window failed: {s}", .{c.SDL_GetError()});
            return;
        }

        c.SDL_SetTrayEntryChecked(entry, true);
        std.debug.print("show and raise window\n", .{});
        window_hidden = false;
    } else {
        if (!c.SDL_HideWindow(window)) {
            std.log.err("hide window failed: {s}", .{c.SDL_GetError()});
            return;
        }
        c.SDL_SetTrayEntryChecked(entry, false);
        window_hidden = true;
        std.debug.print("hide window\n", .{});
    }
}

var tray: ?*c.SDL_Tray = null;
var window_hidden: bool = false;

pub fn appInit(win: *dvui.Window) !void {
    std.debug.print("app init called\n", .{});
    defer std.debug.print("app init exit\n", .{});

    tray = c.SDL_CreateTray(null, "Toggle window visibility") orelse {
        std.log.err("SDL_CreateTray: {s}", .{c.SDL_GetError()});
        return error.Tray;
    };
    errdefer c.SDL_DestroyTray(tray);

    const menu = c.SDL_CreateTrayMenu(tray) orelse {
        std.log.err("SDL_CreateTrayMenu: {s}", .{c.SDL_GetError()});
        return error.TrayMenu;
    };
    const entry = c.SDL_InsertTrayEntryAt(
        menu,
        -1,
        "Toggle visibility",
        c.SDL_TRAYENTRY_BUTTON | c.SDL_TRAYENTRY_CHECKBOX | c.SDL_TRAYENTRY_CHECKED,
    ) orelse {
        std.log.err("SDL_InsertTrayEntryAt: {s}", .{c.SDL_GetError()});
        return error.TrayEntry;
    };
    c.SDL_SetTrayEntryCallback(entry, trayCallback, win.backend.impl.window);

    win.theme = switch (win.backend.preferredColorScheme() orelse .light) {
        .light => dvui.Theme.builtin.adwaita_light,
        .dark => dvui.Theme.builtin.adwaita_dark,
    };
}

pub fn appDeinit() void {
    std.debug.print("app deinit called\n", .{});
    defer std.debug.print("app deinit exit\n", .{});

    c.SDL_DestroyTray(tray);
    _ = debug_allocator.deinit();
}

pub fn appFrame() !dvui.App.Result {
    var vbox = dvui.box(
        @src(),
        .{ .dir = .vertical },
        .{ .rect = .cast(dvui.windowRect()), .background = true },
    );
    defer vbox.deinit();

    dvui.label(@src(), "test", .{}, .{});
    return .ok;
}

Also, setting initial window state to hidden = true stops showing the tray entry, but appInit is called and exits.

pub const dvui_app: dvui.App = .{
    .config = .{
        .options = .{
			// ...
            .hidden = true,
        },
    },
    // ...
};

knightpp avatar Oct 11 '25 07:10 knightpp

I cannot reproduce this issue on Windows 11. After adding a log to the frame callback, I get two frames when hiding the window through the tray icon. Unhiding works without issue. Setting .hidden = true correctly hides the window and unhiding still works (need to click it twice because the window_hidden bool still defaults to false.

A weird interaction I did see is that if I close the application by doing Ctrl+c in the terminal I don't get the deinit call until I hover over the tray icon. At that point the deinit logs print to the terminal, printing over any other running terminal application. Closing by pressing the normal close button on the window works. It seems like sdls appQuit callback isn't called until the next window event, in this case coming from hovering the tray icon, which then calls our App.deinitFn.

RedPhoenixQ avatar Oct 11 '25 11:10 RedPhoenixQ

I'm seeing the same behavior as @RedPhoenixQ (also on Windows 11). The ctrl-c thing I also have been seeing in the normal sdl3-app example on both windows and mac, but haven't looked into it.

@knightpp Just to confirm:

  • what OS are you on?
  • can you try after blowing away .zig-cache ?

david-vanderson avatar Oct 11 '25 12:10 david-vanderson

Oh - unrelated but if you want vbox to take up the whole window, you can pass .expand = .both rather than explicitly passing .rect.

david-vanderson avatar Oct 11 '25 12:10 david-vanderson

@knightpp Just to confirm:

* what OS are you on?

Linux NixOS info(SDLBackend): version: 3.2.20 no callbacks

* can you try after blowing away .zig-cache ?
rm -rf zig-out .zig-cache/ ~/.cache/zig/

Did not help.

The ctrl-c thing

After I type ctrl-c to stop the app, it prints a log for each my click in the hanged state.

I'll try to reproduce it in a VM with some other distro.

knightpp avatar Oct 11 '25 13:10 knightpp

I can reproduce it on Ubuntu 25.10, but not on Ubuntu 24.04.

The issue still happens with --fsys=sdl3 --fsys=freetype flags.

knightpp avatar Oct 11 '25 14:10 knightpp

I should have guessed given the "shell.nix" file. I can reproduce the problem on Linux Mint 22.1 and am investigating!

david-vanderson avatar Oct 11 '25 15:10 david-vanderson

So far I've found that somehow interacting with the tray does not interrupt SDL_WaitEventTimeout.

david-vanderson avatar Oct 11 '25 16:10 david-vanderson

Maybe it would be enough to call dvui.refresh manually in the tray callback (passing in the dvui.Window pointer explicitly because we might be outside the main thread)

RedPhoenixQ avatar Oct 11 '25 16:10 RedPhoenixQ

That's a good idea, but it looks like the tray callback is not called until SDL_WaitEventTimeout returns.

david-vanderson avatar Oct 11 '25 17:10 david-vanderson

I compiled SDL3 main and confirmed that it is fixed there. Probably by this commit: https://github.com/libsdl-org/SDL/commit/ce4af658ba9991a3e19e25383a6051cb101d1460

So this will be fixed as soon as that commit is included in an SDL3 release and then picked up by https://github.com/castholm/SDL and then we have to update. So probably a few months? We'll keep this open until then.

In the meantime you should be able to build SDL from source and use --fsys=sdl3 to work around it. Let me know if that works for you or you need a different solution.

david-vanderson avatar Oct 12 '25 01:10 david-vanderson

Thanks, updating sdl3 to master fixed the issue.

shell.nix with latest sdl3 commit
{
  pkgs ? import <nixpkgs> { },
}:
pkgs.mkShell {
  packages = [
    pkgs.pkg-config
    (pkgs.sdl3.overrideAttrs {
      version = "unstable-5dab2c7";

      src = pkgs.fetchFromGitHub {
        owner = "libsdl-org";
        repo = "SDL";
        rev = "5dab2c73f0537bd121d2311b6949a654c5692af9";
        hash = "sha256-jiTTKxJgjn+TGRP2cCuxs0d9zkrjpcpComsV4MT58tg=";
      };

      postPatch = "";
    })
    pkgs.freetype
  ];
}

knightpp avatar Oct 12 '25 08:10 knightpp