+/// Applies display settings immediately (no vid_restart required).
+static void VID_ApplyDisplayMode(const viddef_mode_t *mode)
+{
+ uint32_t fullscreenwanted;
+ int displaywanted = bound(0, mode->display, vid_info_displaycount.integer - 1);
+ SDL_DisplayMode modefinal;
+
+ if (mode->fullscreen)
+ fullscreenwanted = mode->desktopfullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
+ else
+ fullscreenwanted = 0;
+
+ // moving to another display or switching to windowed
+ if (vid.mode.display != displaywanted // SDL seems unable to move any fullscreen window to another display
+ || !fullscreenwanted)
+ {
+ if (SDL_SetWindowFullscreen(window, 0) < 0)
+ {
+ Con_Printf(CON_ERROR "ERROR: can't deactivate fullscreen on display %i because %s\n", vid.mode.display, SDL_GetError());
+ return;
+ }
+ vid.mode.desktopfullscreen = vid.mode.fullscreen = false;
+ Con_DPrintf("Fullscreen deactivated on display %i\n", vid.mode.display);
+ }
+
+ // switching to windowed
+ if (!fullscreenwanted)
+ {
+ int toppx;
+
+ SDL_SetWindowSize(window, vid.mode.width = mode->width, vid.mode.height = mode->height);
+ // resizable and borderless set here cos a separate callback would fail if the cvar is changed when the window is fullscreen
+ SDL_SetWindowResizable(window, vid_resizable.integer ? SDL_TRUE : SDL_FALSE);
+ SDL_SetWindowBordered(window, (SDL_bool)!vid_borderless.integer);
+ SDL_GetWindowBordersSize(window, &toppx, NULL, NULL, NULL);
+ vid_wmborderless = !toppx;
+ if (vid_borderless.integer != vid_wmborderless) // this is not the state we're looking for
+ vid_wmborder_waiting = true;
+ }
+
+ // moving to another display or switching to windowed
+ if (vid.mode.display != displaywanted || !fullscreenwanted)
+ {
+// SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(displaywanted), SDL_WINDOWPOS_CENTERED_DISPLAY(displaywanted));
+// SDL_GetWindowPosition(window, &vid.xPos, &vid.yPos);
+
+ /* bones_was_here BUG: after SDL_DISPLAYEVENT hotplug events, on Xorg + NVIDIA,
+ * SDL_WINDOWPOS_CENTERED_DISPLAY(displaywanted) may place the window somewhere completely invisible.
+ * WORKAROUND: manual positioning seems safer: although SDL_GetDisplayBounds() may return outdated values,
+ * SDL_SetWindowPosition() always placed the window somewhere fully visible, even if it wasn't correct,
+ * when tested with SDL 2.26.5.
+ */
+ SDL_Rect displaybounds;
+ if (SDL_GetDisplayBounds(displaywanted, &displaybounds) < 0)
+ {
+ Con_Printf(CON_ERROR "Error getting bounds of display %i: \"%s\"\n", displaywanted, SDL_GetError());
+ return;
+ }
+ vid.xPos = displaybounds.x + 0.5 * (displaybounds.w - vid.mode.width);
+ vid.yPos = displaybounds.y + 0.5 * (displaybounds.h - vid.mode.height);
+ SDL_SetWindowPosition(window, vid.xPos, vid.yPos);
+
+ vid.mode.display = displaywanted;
+ }
+
+ // switching to a fullscreen mode
+ if (fullscreenwanted)
+ {
+ if (fullscreenwanted == SDL_WINDOW_FULLSCREEN)
+ {
+ // determine if a modeset is needed and if the requested resolution is supported
+ SDL_DisplayMode modewanted, modecurrent;
+
+ modewanted.w = mode->width;
+ modewanted.h = mode->height;
+ modewanted.format = mode->bitsperpixel == 16 ? SDL_PIXELFORMAT_RGB565 : SDL_PIXELFORMAT_RGB888;
+ modewanted.refresh_rate = mode->refreshrate;
+ if (!SDL_GetClosestDisplayMode(displaywanted, &modewanted, &modefinal))
+ {
+ // SDL_GetError() returns a random unrelated error if this fails (in 2.26.5)
+ Con_Printf(CON_ERROR "Error getting closest mode to %ix%i@%ihz for display %i\n", modewanted.w, modewanted.h, modewanted.refresh_rate, vid.mode.display);
+ return;
+ }
+ if (SDL_GetCurrentDisplayMode(displaywanted, &modecurrent) < 0)
+ {
+ Con_Printf(CON_ERROR "Error getting current mode of display %i: \"%s\"\n", vid.mode.display, SDL_GetError());
+ return;
+ }
+ if (memcmp(&modecurrent, &modefinal, sizeof(modecurrent)) != 0)
+ {
+ if (mode->width != modefinal.w || mode->height != modefinal.h)
+ {
+ Con_Printf(CON_WARN "Display %i doesn't support resolution %ix%i\n", vid.mode.display, modewanted.w, modewanted.h);
+ return;
+ }
+ if (SDL_SetWindowDisplayMode(window, &modefinal) < 0)
+ {
+ Con_Printf(CON_ERROR "Error setting mode %ix%i@%ihz for display %i: \"%s\"\n", modefinal.w, modefinal.h, modefinal.refresh_rate, vid.mode.display, SDL_GetError());
+ return;
+ }
+ // HACK to work around SDL BUG when switching from a lower to a higher res:
+ // the display res gets increased but the window size isn't increased
+ // (unless we do this first; switching to windowed mode first also works).
+ SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+ }
+ }
+
+ if (SDL_SetWindowFullscreen(window, fullscreenwanted) < 0)
+ {
+ Con_Printf(CON_ERROR "ERROR: can't activate fullscreen on display %i because %s\n", vid.mode.display, SDL_GetError());
+ return;
+ }
+ // get the real framebuffer size in case the platform's screen coordinates are DPI scaled
+ SDL_GL_GetDrawableSize(window, &vid.mode.width, &vid.mode.height);
+ vid.mode.fullscreen = true;
+ vid.mode.desktopfullscreen = fullscreenwanted == SDL_WINDOW_FULLSCREEN_DESKTOP;
+ Con_DPrintf("Fullscreen activated on display %i\n", vid.mode.display);
+ }
+
+ if (!fullscreenwanted || fullscreenwanted == SDL_WINDOW_FULLSCREEN_DESKTOP)
+ SDL_GetDesktopDisplayMode(displaywanted, &modefinal);
+ else { /* modefinal was set by SDL_GetClosestDisplayMode */ }
+ vid.mode.bitsperpixel = SDL_BITSPERPIXEL(modefinal.format);
+ vid.mode.refreshrate = mode->refreshrate && mode->fullscreen && !mode->desktopfullscreen ? modefinal.refresh_rate : 0;
+ vid.stencil = mode->bitsperpixel > 16;
+}
+
+static void VID_ApplyDisplayMode_c(cvar_t *var)
+{
+ viddef_mode_t mode;
+
+ if (!window)
+ return;
+
+ // Menu designs aren't suitable for instant hardware modesetting
+ // they make players scroll through a list, setting the cvars at each step.
+ if (key_dest == key_menu && !key_consoleactive // in menu, console closed
+ && vid_fullscreen.integer && !vid_desktopfullscreen.integer) // modesetting enabled
+ return;
+
+ Con_DPrintf("%s: applying %s \"%s\"\n", __func__, var->name, var->string);
+
+ mode.display = vid_display.integer;
+ mode.fullscreen = vid_fullscreen.integer;
+ mode.desktopfullscreen = vid_desktopfullscreen.integer;
+ mode.width = vid_width.integer;
+ mode.height = vid_height.integer;
+ mode.bitsperpixel = vid_bitsperpixel.integer;
+ mode.refreshrate = max(0, vid_refreshrate.integer);
+ VID_ApplyDisplayMode(&mode);
+}
+
+static void VID_SetVsync_c(cvar_t *var)
+{
+ signed char vsyncwanted = cls.timedemo ? 0 : bound(-1, vid_vsync.integer, 1);
+
+ if (!context)
+ return;
+/*
+Can't check first: on Wayland SDL_GL_GetSwapInterval() may initially return 0 when vsync is on.
+On Xorg it returns the correct value.
+ if (SDL_GL_GetSwapInterval() == vsyncwanted)
+ return;
+*/
+
+ if (SDL_GL_SetSwapInterval(vsyncwanted) >= 0)
+ Con_DPrintf("Vsync %s\n", vsyncwanted ? "activated" : "deactivated");
+ else
+ Con_Printf(CON_ERROR "ERROR: can't %s vsync because %s\n", vsyncwanted ? "activate" : "deactivate", SDL_GetError());
+}
+
+static void VID_SetHints_c(cvar_t *var)
+{
+ SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, vid_mouse_clickthrough.integer ? "1" : "0");
+ SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, vid_minimize_on_focus_loss.integer ? "1" : "0");
+}