Browse Source

Implemented VT switching for KMSDRM on Linux

Fixes https://github.com/libsdl-org/SDL/issues/3844

(cherry picked from commit f4b61fff308443727c52ae098923b9e934660609)
Sam Lantinga 1 year ago
parent
commit
dfc38ef460

+ 12 - 4
src/core/freebsd/SDL_evdev_kbd_freebsd.c

@@ -321,6 +321,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.
  */
@@ -468,10 +480,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;

+ 10 - 0
src/core/linux/SDL_evdev.c

@@ -283,6 +283,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;
@@ -314,6 +322,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) {

+ 2 - 0
src/core/linux/SDL_evdev.h

@@ -30,6 +30,8 @@
 
 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);
 

+ 183 - 26
src/core/linux/SDL_evdev_kbd.c

@@ -100,6 +100,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
@@ -297,6 +301,126 @@ 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_atomic_t vt_signal_pending;
+
+typedef void (*signal_handler)(int signum);
+
+static void kbd_vt_release_signal_action(int signum)
+{
+    SDL_AtomicSet(&vt_signal_pending, VT_SIGNAL_RELEASE);
+}
+
+static void kbd_vt_acquire_signal_action(int signum)
+{
+    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;
@@ -334,33 +458,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)
@@ -397,6 +497,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.
  */
@@ -831,6 +980,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)
 {
 }

+ 2 - 0
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);
 

+ 9 - 1
src/video/kmsdrm/SDL_kmsdrmopengles.c

@@ -24,6 +24,7 @@
 #if SDL_VIDEO_DRIVER_KMSDRM
 
 #include "SDL_log.h"
+#include "SDL_timer.h"
 
 #include "SDL_kmsdrmvideo.h"
 #include "SDL_kmsdrmopengles.h"
@@ -97,6 +98,13 @@ int KMSDRM_GLES_SwapWindow(_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);
@@ -117,7 +125,7 @@ int KMSDRM_GLES_SwapWindow(_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))) {

+ 1 - 0
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,

+ 32 - 0
src/video/kmsdrm/SDL_kmsdrmvideo.c

@@ -1206,6 +1206,36 @@ cleanup:
     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(_THIS)
 {
     int ret = 0;
@@ -1226,6 +1256,7 @@ int KMSDRM_VideoInit(_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
@@ -1244,6 +1275,7 @@ void KMSDRM_VideoQuit(_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();