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();