# SDL: Implemented VT switching for KMSDRM on Linux

**URL:** <https://discourse.libsdl.org/t/sdl-implemented-vt-switching-for-kmsdrm-on-linux/47263>\
**Category:** SDL Commits\
**Created:** [November 9, 2023, 4:36am UTC](https://discourse.libsdl.org/t/sdl-implemented-vt-switching-for-kmsdrm-on-linux/47263 "2023-11-09T04:36:02Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![SDLPushPostBot](https://discourse.libsdl.org/user_avatar/discourse.libsdl.org/sdlpushpostbot/32/4370_2.png) [@SDLPushPostBot](https://discourse.libsdl.org/u/SDLPushPostBot)\
**Post date:** [November 9, 2023, 4:36am UTC](https://discourse.libsdl.org/t/sdl-implemented-vt-switching-for-kmsdrm-on-linux/47263/1 "2023-11-09T04:36:02Z")

</div>

> <https://github.com/libsdl-org/SDL/commit/f4b61fff308443727c52ae098923b9e934660609>
>
> Fixes https://github.com/libsdl-org/SDL/issues/3844

```diff
From f4b61fff308443727c52ae098923b9e934660609 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 8 Nov 2023 10:33:40 -0800
Subject: [PATCH] Implemented VT switching for KMSDRM on Linux

Fixes https://github.com/libsdl-org/SDL/issues/3844
---
 src/core/freebsd/SDL_evdev_kbd_freebsd.c | 16 +-
 src/core/linux/SDL_evdev.c | 10 ++
 src/core/linux/SDL_evdev.h | 2 +
 src/core/linux/SDL_evdev_kbd.c | 211 ++++++++++++++++++++---
 src/core/linux/SDL_evdev_kbd.h | 2 +
 src/video/kmsdrm/SDL_kmsdrmopengles.c | 9 +-
 src/video/kmsdrm/SDL_kmsdrmsym.h | 1 +
 src/video/kmsdrm/SDL_kmsdrmvideo.c | 32 ++++
 8 files changed, 252 insertions(+), 31 deletions(-)

diff --git a/src/core/freebsd/SDL_evdev_kbd_freebsd.c b/src/core/freebsd/SDL_evdev_kbd_freebsd.c
index 19a7a7114811..e9129feeb8c4 100644
--- a/src/core/freebsd/SDL_evdev_kbd_freebsd.c
+++ b/src/core/freebsd/SDL_evdev_kbd_freebsd.c
@@ -320,6 +320,18 @@ void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *kbd)
     SDL_free(kbd);
 }
 
+void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted)
+{
+}
+
+void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
+{
+}
+
+void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
+{
+}
+
 /*
  * Helper Functions.
  */
@@ -467,10 +479,6 @@ static void k_shift(SDL_EVDEV_keyboard_state *kbd, unsigned char value, char up_
     }
 }
 
-void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted)
-{
-}
-
 void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *kbd, unsigned int keycode, int down)
 {
     keymap_t key_map;
diff --git a/src/core/linux/SDL_evdev.c b/src/core/linux/SDL_evdev.c
index 453285793831..9ca3bdfaf7b7 100644
--- a/src/core/linux/SDL_evdev.c
+++ b/src/core/linux/SDL_evdev.c
@@ -288,6 +288,14 @@ static void SDL_EVDEV_udev_callback(SDL_UDEV_deviceevent udev_event, int udev_cl
 }
 #endif /* SDL_USE_LIBUDEV */
 
+void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data,
+ void (*acquire_callback)(void*), void *acquire_callback_data)
+{
+ SDL_EVDEV_kbd_set_vt_switch_callbacks(_this->kbd,
+ release_callback, release_callback_data, 
+ acquire_callback, acquire_callback_data);
+}
+
 int SDL_EVDEV_GetDeviceCount(int device_class)
 {
     SDL_evdevlist_item *item;
@@ -319,6 +327,8 @@ void SDL_EVDEV_Poll(void)
     SDL_UDEV_Poll();
 #endif
 
+ SDL_EVDEV_kbd_update(_this->kbd);
+
     mouse = SDL_GetMouse();
 
     for (item = _this->first; item != NULL; item = item->next) {
diff --git a/src/core/linux/SDL_evdev.h b/src/core/linux/SDL_evdev.h
index b0c6c7ef0e49..c9ac38edd2a3 100644
--- a/src/core/linux/SDL_evdev.h
+++ b/src/core/linux/SDL_evdev.h
@@ -30,6 +30,8 @@ struct input_event;
 
 extern int SDL_EVDEV_Init(void);
 extern void SDL_EVDEV_Quit(void);
+extern void SDL_EVDEV_SetVTSwitchCallbacks(void (*release_callback)(void*), void *release_callback_data,
+ void (*acquire_callback)(void*), void *acquire_callback_data);
 extern int SDL_EVDEV_GetDeviceCount(int device_class);
 extern void SDL_EVDEV_Poll(void);
 extern Uint64 SDL_EVDEV_GetEventTimestamp(struct input_event *event);
diff --git a/src/core/linux/SDL_evdev_kbd.c b/src/core/linux/SDL_evdev_kbd.c
index fb3036cd4447..2085b07e790f 100644
--- a/src/core/linux/SDL_evdev_kbd.c
+++ b/src/core/linux/SDL_evdev_kbd.c
@@ -99,6 +99,10 @@ struct SDL_EVDEV_keyboard_state
     char shift_state;
     char text[128];
     unsigned int text_len;
+ void (*vt_release_callback)(void *);
+ void *vt_release_callback_data;
+ void (*vt_acquire_callback)(void *);
+ void *vt_acquire_callback_data;
 };
 
 #ifdef DUMP_ACCENTS
@@ -296,6 +300,128 @@ static void kbd_register_emerg_cleanup(SDL_EVDEV_keyboard_state *kbd)
     }
 }
 
+enum {
+ VT_SIGNAL_NONE,
+ VT_SIGNAL_RELEASE,
+ VT_SIGNAL_ACQUIRE,
+};
+static int vt_release_signal;
+static int vt_acquire_signal;
+static SDL_AtomicInt vt_signal_pending;
+
+typedef void (*signal_handler)(int signum);
+
+static void kbd_vt_release_signal_action(int signum)
+{
+printf("kbd_vt_release_signal_action\n");
+ SDL_AtomicSet(&vt_signal_pending, VT_SIGNAL_RELEASE);
+}
+
+static void kbd_vt_acquire_signal_action(int signum)
+{
+printf("kbd_vt_acquire_signal_action\n");
+ SDL_AtomicSet(&vt_signal_pending, VT_SIGNAL_ACQUIRE);
+}
+
+static SDL_bool setup_vt_signal(int signum, signal_handler handler)
+{
+ struct sigaction *old_action_p;
+ struct sigaction new_action;
+ old_action_p = &(old_sigaction[signum]);
+ SDL_zero(new_action);
+ new_action.sa_handler = handler;
+ new_action.sa_flags = SA_RESTART;
+ if (sigaction(signum, &new_action, old_action_p) < 0) {
+ return SDL_FALSE;
+ }
+ if (old_action_p->sa_handler != SIG_DFL) {
+ /* This signal is already in use */
+ sigaction(signum, old_action_p, NULL);
+ return SDL_FALSE;
+ }
+ return SDL_TRUE;
+}
+
+static int find_free_signal(signal_handler handler)
+{
+#ifdef SIGRTMIN
+ int i;
+
+ for (i = SIGRTMIN + 2; i <= SIGRTMAX; ++i) {
+ if (setup_vt_signal(i, handler)) {
+ return i;
+ }
+ }
+#endif
+ if (setup_vt_signal(SIGUSR1, handler)) {
+ return SIGUSR1;
+ }
+ if (setup_vt_signal(SIGUSR2, handler)) {
+ return SIGUSR2;
+ }
+ return 0;
+}
+
+static void kbd_vt_quit(int console_fd)
+{
+ struct vt_mode mode;
+
+ if (vt_release_signal) {
+ sigaction(vt_release_signal, &old_sigaction[vt_release_signal], NULL);
+ vt_release_signal = 0;
+ }
+ if (vt_acquire_signal) {
+ sigaction(vt_acquire_signal, &old_sigaction[vt_acquire_signal], NULL);
+ vt_acquire_signal = 0;
+ }
+
+ SDL_zero(mode);
+ mode.mode = VT_AUTO;
+ ioctl(console_fd, VT_SETMODE, &mode);
+}
+
+static int kbd_vt_init(int console_fd)
+{
+ struct vt_mode mode;
+
+ vt_release_signal = find_free_signal(kbd_vt_release_signal_action);
+ vt_acquire_signal = find_free_signal(kbd_vt_acquire_signal_action);
+ if (!vt_release_signal || !vt_acquire_signal ) {
+ kbd_vt_quit(console_fd);
+ return -1;
+ }
+
+ SDL_zero(mode);
+ mode.mode = VT_PROCESS;
+ mode.relsig = vt_release_signal;
+ mode.acqsig = vt_acquire_signal;
+ mode.frsig = SIGIO;
+ if (ioctl(console_fd, VT_SETMODE, &mode) < 0) {
+ kbd_vt_quit(console_fd);
+ return -1;
+ }
+ return 0;
+}
+
+static void kbd_vt_update(SDL_EVDEV_keyboard_state *state)
+{
+ int signal_pending = SDL_AtomicGet(&vt_signal_pending);
+ if (signal_pending != VT_SIGNAL_NONE) {
+ if (signal_pending == VT_SIGNAL_RELEASE) {
+ if (state->vt_release_callback) {
+ state->vt_release_callback(state->vt_release_callback_data);
+ }
+ ioctl(state->console_fd, VT_RELDISP, 1);
+ } else {
+ if (state->vt_acquire_callback) {
+ state->vt_acquire_callback(state->vt_acquire_callback_data);
+ }
+ ioctl(state->console_fd, VT_RELDISP, VT_ACKACQ);
+ }
+ SDL_AtomicCAS(&vt_signal_pending, signal_pending, VT_SIGNAL_NONE);
+ }
+}
+
 SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
 {
     SDL_EVDEV_keyboard_state *kbd;
@@ -333,33 +459,9 @@ SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void)
         ioctl(kbd->console_fd, KDSKBMODE, K_UNICODE);
     }
 
- return kbd;
-}
+ kbd_vt_init(kbd->console_fd);
 
-void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state)
-{
- if (state == NULL) {
- return;
- }
-
- SDL_EVDEV_kbd_set_muted(state, SDL_FALSE);
-
- if (state->console_fd >= 0) {
- close(state->console_fd);
- state->console_fd = -1;
- }
-
- if (state->key_maps && state->key_maps != default_key_maps) {
- int i;
- for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
- if (state->key_maps[i]) {
- SDL_free(state->key_maps[i]);
- }
- }
- SDL_free(state->key_maps);
- }
-
- SDL_free(state);
+ return kbd;
 }
 
 void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted)
@@ -396,6 +498,55 @@ void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted)
     state->muted = muted;
 }
 
+void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
+{
+ if (state == NULL) {
+ return;
+ }
+
+ state->vt_release_callback = release_callback;
+ state->vt_release_callback_data = release_callback_data;
+ state->vt_acquire_callback = acquire_callback;
+ state->vt_acquire_callback_data = acquire_callback_data;
+}
+
+void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
+{
+ if (!state) {
+ return;
+ }
+
+ kbd_vt_update(state);
+}
+
+void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state)
+{
+ if (state == NULL) {
+ return;
+ }
+
+ SDL_EVDEV_kbd_set_muted(state, SDL_FALSE);
+
+ kbd_vt_quit(state->console_fd);
+
+ if (state->console_fd >= 0) {
+ close(state->console_fd);
+ state->console_fd = -1;
+ }
+
+ if (state->key_maps && state->key_maps != default_key_maps) {
+ int i;
+ for (i = 0; i < MAX_NR_KEYMAPS; ++i) {
+ if (state->key_maps[i]) {
+ SDL_free(state->key_maps[i]);
+ }
+ }
+ SDL_free(state->key_maps);
+ }
+
+ SDL_free(state);
+}
+
 /*
  * Helper Functions.
  */
@@ -830,6 +981,14 @@ void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted)
 {
 }
 
+void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data)
+{
+}
+
+void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state)
+{
+}
+
 void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down)
 {
 }
diff --git a/src/core/linux/SDL_evdev_kbd.h b/src/core/linux/SDL_evdev_kbd.h
index e00ca0b1445a..73cff0bc3a75 100644
--- a/src/core/linux/SDL_evdev_kbd.h
+++ b/src/core/linux/SDL_evdev_kbd.h
@@ -27,6 +27,8 @@ typedef struct SDL_EVDEV_keyboard_state SDL_EVDEV_keyboard_state;
 
 extern SDL_EVDEV_keyboard_state *SDL_EVDEV_kbd_init(void);
 extern void SDL_EVDEV_kbd_set_muted(SDL_EVDEV_keyboard_state *state, SDL_bool muted);
+extern void SDL_EVDEV_kbd_set_vt_switch_callbacks(SDL_EVDEV_keyboard_state *state, void (*release_callback)(void*), void *release_callback_data, void (*acquire_callback)(void*), void *acquire_callback_data);
+extern void SDL_EVDEV_kbd_update(SDL_EVDEV_keyboard_state *state);
 extern void SDL_EVDEV_kbd_keycode(SDL_EVDEV_keyboard_state *state, unsigned int keycode, int down);
 extern void SDL_EVDEV_kbd_quit(SDL_EVDEV_keyboard_state *state);
 
diff --git a/src/video/kmsdrm/SDL_kmsdrmopengles.c b/src/video/kmsdrm/SDL_kmsdrmopengles.c
index 2de35e0e86ba..1df556e23a98 100644
--- a/src/video/kmsdrm/SDL_kmsdrmopengles.c
+++ b/src/video/kmsdrm/SDL_kmsdrmopengles.c
@@ -96,6 +96,13 @@ int KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
        even if you do async flips. */
     uint32_t flip_flags = DRM_MODE_PAGE_FLIP_EVENT;
 
+ /* Skip the swap if we've switched away to another VT */
+ if (windata->egl_surface == EGL_NO_SURFACE) {
+ /* Wait a bit, throttling to ~100 FPS */
+ SDL_Delay(10);
+ return 0;
+ }
+
     /* Recreate the GBM / EGL surfaces if the display mode has changed */
     if (windata->egl_surface_dirty) {
         KMSDRM_CreateSurfaces(_this, window);
@@ -116,7 +123,7 @@ int KMSDRM_GLES_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
 
     windata->bo = windata->next_bo;
 
- /* Mark a buffer to becume the next front buffer.
+ /* Mark a buffer to become the next front buffer.
        This won't happen until pagelip completes. */
     if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display,
                                           windata->egl_surface))) {
diff --git a/src/video/kmsdrm/SDL_kmsdrmsym.h b/src/video/kmsdrm/SDL_kmsdrmsym.h
index e378fdfb07d6..d8f31ca9433c 100644
--- a/src/video/kmsdrm/SDL_kmsdrmsym.h
+++ b/src/video/kmsdrm/SDL_kmsdrmsym.h
@@ -42,6 +42,7 @@ SDL_KMSDRM_SYM(void,drmModeFreeConnector,(drmModeConnectorPtr ptr))
 SDL_KMSDRM_SYM(void,drmModeFreeEncoder,(drmModeEncoderPtr ptr))
 SDL_KMSDRM_SYM(int,drmGetCap,(int fd, uint64_t capability, uint64_t *value))
 SDL_KMSDRM_SYM(int,drmSetMaster,(int fd))
+SDL_KMSDRM_SYM(int,drmDropMaster,(int fd))
 SDL_KMSDRM_SYM(int,drmAuthMagic,(int fd, drm_magic_t magic))
 SDL_KMSDRM_SYM(drmModeResPtr,drmModeGetResources,(int fd))
 SDL_KMSDRM_SYM(int,drmModeAddFB,(int fd, uint32_t width, uint32_t height, uint8_t depth,
diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c
index 15b943b724c9..643a469d86d7 100644
--- a/src/video/kmsdrm/SDL_kmsdrmvideo.c
+++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c
@@ -1223,6 +1223,36 @@ int KMSDRM_CreateSurfaces(SDL_VideoDevice *_this, SDL_Window *window)
     return ret;
 }
 
+static void KMSDRM_ReleaseVT(void *userdata)
+{
+ SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata;
+ SDL_VideoData *viddata = _this->driverdata;
+ int i;
+
+ for (i = 0; i < viddata->num_windows; i++) {
+ SDL_Window *window = viddata->windows[i];
+ if (!(window->flags & SDL_WINDOW_VULKAN)) {
+ KMSDRM_DestroySurfaces(_this, window);
+ }
+ }
+ KMSDRM_drmDropMaster(viddata->drm_fd);
+}
+
+static void KMSDRM_AcquireVT(void *userdata)
+{
+ SDL_VideoDevice *_this = (SDL_VideoDevice *)userdata;
+ SDL_VideoData *viddata = _this->driverdata;
+ int i;
+
+ KMSDRM_drmSetMaster(viddata->drm_fd);
+ for (i = 0; i < viddata->num_windows; i++) {
+ SDL_Window *window = viddata->windows[i];
+ if (!(window->flags & SDL_WINDOW_VULKAN)) {
+ KMSDRM_CreateSurfaces(_this, window);
+ }
+ }
+}
+
 int KMSDRM_VideoInit(SDL_VideoDevice *_this)
 {
     int ret = 0;
@@ -1243,6 +1273,7 @@ int KMSDRM_VideoInit(SDL_VideoDevice *_this)
 
 #ifdef SDL_INPUT_LINUXEV
     SDL_EVDEV_Init();
+ SDL_EVDEV_SetVTSwitchCallbacks(KMSDRM_ReleaseVT, _this, KMSDRM_AcquireVT, _this);
 #elif defined(SDL_INPUT_WSCONS)
     SDL_WSCONS_Init();
 #endif
@@ -1261,6 +1292,7 @@ void KMSDRM_VideoQuit(SDL_VideoDevice *_this)
     KMSDRM_DeinitDisplays(_this);
 
 #ifdef SDL_INPUT_LINUXEV
+ SDL_EVDEV_SetVTSwitchCallbacks(NULL, NULL, NULL, NULL);
     SDL_EVDEV_Quit();
 #elif defined(SDL_INPUT_WSCONS)
     SDL_WSCONS_Quit();

```
