src/video/wayland/SDL_waylandwindow.c
author Ryan C. Gordon <icculus@icculus.org>
Sun, 11 Jun 2017 00:50:26 -0400
changeset 11088 69452f9839d5
parent 10737 3406a0f8b041
child 11092 9c9b7b7e46e1
permissions -rw-r--r--
syswm: prevent buffer overflow if SDL and app have different config headers.

This only affects Wayland and DirectFB, as a Unix system generally has X11
support. Other platforms also have different sizes for the C union in
question, but are likely the only target for that platform, etc.

Apps that might run on Wayland or DirectFB will need to be compiled against
new headers from an official 2.0.6 release, or be prepared to force the x11
target, or not use SDL_GetWindowWMInfo().

Fixes Bugzilla #3428.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 
    22 #include "../../SDL_internal.h"
    23 
    24 #if SDL_VIDEO_DRIVER_WAYLAND && SDL_VIDEO_OPENGL_EGL
    25 
    26 #include "../SDL_sysvideo.h"
    27 #include "../../events/SDL_windowevents_c.h"
    28 #include "../SDL_egl_c.h"
    29 #include "SDL_waylandevents_c.h"
    30 #include "SDL_waylandwindow.h"
    31 #include "SDL_waylandvideo.h"
    32 #include "SDL_waylandtouch.h"
    33 #include "SDL_waylanddyn.h"
    34 #include "SDL_hints.h"
    35 
    36 static void
    37 handle_ping(void *data, struct wl_shell_surface *shell_surface,
    38             uint32_t serial)
    39 {
    40     wl_shell_surface_pong(shell_surface, serial);
    41 }
    42 
    43 static void
    44 handle_configure(void *data, struct wl_shell_surface *shell_surface,
    45                  uint32_t edges, int32_t width, int32_t height)
    46 {
    47     SDL_WindowData *wind = (SDL_WindowData *)data;
    48     SDL_Window *window = wind->sdlwindow;
    49     struct wl_region *region;
    50 
    51     /* wl_shell_surface spec states that this is a suggestion.
    52        Ignore if less than or greater than max/min size. */
    53 
    54     if (width == 0 || height == 0) {
    55         return;
    56     }
    57 
    58     if (!(window->flags & SDL_WINDOW_FULLSCREEN)) {
    59         if ((window->flags & SDL_WINDOW_RESIZABLE)) {
    60             if (window->max_w > 0) {
    61                 width = SDL_min(width, window->max_w);
    62             } 
    63             width = SDL_max(width, window->min_w);
    64 
    65             if (window->max_h > 0) {
    66                 height = SDL_min(height, window->max_h);
    67             }
    68             height = SDL_max(height, window->min_h);
    69         } else {
    70             return;
    71         }
    72     }
    73 
    74     if (width == window->w && height == window->h) {
    75         return;
    76     }
    77 
    78     window->w = width;
    79     window->h = height;
    80     WAYLAND_wl_egl_window_resize(wind->egl_window, window->w, window->h, 0, 0);
    81 
    82     region = wl_compositor_create_region(wind->waylandData->compositor);
    83     wl_region_add(region, 0, 0, window->w, window->h);
    84     wl_surface_set_opaque_region(wind->surface, region);
    85     wl_region_destroy(region);
    86     SDL_SendWindowEvent(window, SDL_WINDOWEVENT_RESIZED, window->w, window->h);
    87 }
    88 
    89 static void
    90 handle_popup_done(void *data, struct wl_shell_surface *shell_surface)
    91 {
    92 }
    93 
    94 static const struct wl_shell_surface_listener shell_surface_listener = {
    95     handle_ping,
    96     handle_configure,
    97     handle_popup_done
    98 };
    99 
   100 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
   101 static void
   102 handle_onscreen_visibility(void *data,
   103         struct qt_extended_surface *qt_extended_surface, int32_t visible)
   104 {
   105 }
   106 
   107 static void
   108 handle_set_generic_property(void *data,
   109         struct qt_extended_surface *qt_extended_surface, const char *name,
   110         struct wl_array *value)
   111 {
   112 }
   113 
   114 static void
   115 handle_close(void *data, struct qt_extended_surface *qt_extended_surface)
   116 {
   117     SDL_WindowData *window = (SDL_WindowData *)data;
   118     SDL_SendWindowEvent(window->sdlwindow, SDL_WINDOWEVENT_CLOSE, 0, 0);
   119 }
   120 
   121 static const struct qt_extended_surface_listener extended_surface_listener = {
   122     handle_onscreen_visibility,
   123     handle_set_generic_property,
   124     handle_close,
   125 };
   126 #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
   127 
   128 SDL_bool
   129 Wayland_GetWindowWMInfo(_THIS, SDL_Window * window, SDL_SysWMinfo * info)
   130 {
   131     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
   132     const Uint32 version = ((((Uint32) info->version.major) * 1000000) +
   133                             (((Uint32) info->version.minor) * 10000) +
   134                             (((Uint32) info->version.patch)));
   135 
   136     /* Before 2.0.6, it was possible to build an SDL with Wayland support
   137        (SDL_SysWMinfo will be large enough to hold Wayland info), but build
   138        your app against SDL headers that didn't have Wayland support
   139        (SDL_SysWMinfo could be smaller than Wayland needs. This would lead
   140        to an app properly using SDL_GetWindowWMInfo() but we'd accidentally
   141        overflow memory on the stack or heap. To protect against this, we've
   142        padded out the struct unconditionally in the headers and Wayland will
   143        just return an error for older apps using this function. Those apps
   144        will need to be recompiled against newer headers or not use Wayland,
   145        maybe by forcing SDL_VIDEODRIVER=x11. */
   146     if (version < 2000006) {
   147         info->subsystem = SDL_SYSWM_UNKNOWN;
   148         return SDL_FALSE;
   149     }
   150 
   151     info->info.wl.display = data->waylandData->display;
   152     info->info.wl.surface = data->surface;
   153     info->info.wl.shell_surface = data->shell_surface;
   154     info->subsystem = SDL_SYSWM_WAYLAND;
   155 
   156     return SDL_TRUE;
   157 }
   158 
   159 int
   160 Wayland_SetWindowHitTest(SDL_Window *window, SDL_bool enabled)
   161 {
   162     return 0;  /* just succeed, the real work is done elsewhere. */
   163 }
   164 
   165 void Wayland_ShowWindow(_THIS, SDL_Window *window)
   166 {
   167     SDL_WindowData *wind = window->driverdata;
   168 
   169     if (window->flags & SDL_WINDOW_FULLSCREEN)
   170         wl_shell_surface_set_fullscreen(wind->shell_surface,
   171                                         WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
   172                                         0, (struct wl_output *)window->fullscreen_mode.driverdata);
   173     else
   174         wl_shell_surface_set_toplevel(wind->shell_surface);
   175 
   176     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
   177 }
   178 
   179 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
   180 static void QtExtendedSurface_OnHintChanged(void *userdata, const char *name,
   181         const char *oldValue, const char *newValue)
   182 {
   183     struct qt_extended_surface *qt_extended_surface = userdata;
   184 
   185     if (name == NULL) {
   186         return;
   187     }
   188 
   189     if (strcmp(name, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION) == 0) {
   190         int32_t orientation = QT_EXTENDED_SURFACE_ORIENTATION_PRIMARYORIENTATION;
   191 
   192         if (newValue != NULL) {
   193             if (strcmp(newValue, "portrait") == 0) {
   194                 orientation = QT_EXTENDED_SURFACE_ORIENTATION_PORTRAITORIENTATION;
   195             } else if (strcmp(newValue, "landscape") == 0) {
   196                 orientation = QT_EXTENDED_SURFACE_ORIENTATION_LANDSCAPEORIENTATION;
   197             } else if (strcmp(newValue, "inverted-portrait") == 0) {
   198                 orientation = QT_EXTENDED_SURFACE_ORIENTATION_INVERTEDPORTRAITORIENTATION;
   199             } else if (strcmp(newValue, "inverted-landscape") == 0) {
   200                 orientation = QT_EXTENDED_SURFACE_ORIENTATION_INVERTEDLANDSCAPEORIENTATION;
   201             }
   202         }
   203 
   204         qt_extended_surface_set_content_orientation(qt_extended_surface, orientation);
   205     } else if (strcmp(name, SDL_HINT_QTWAYLAND_WINDOW_FLAGS) == 0) {
   206         uint32_t flags = 0;
   207 
   208         if (newValue != NULL) {
   209             char *tmp = strdup(newValue);
   210             char *saveptr = NULL;
   211 
   212             char *flag = strtok_r(tmp, " ", &saveptr);
   213             while (flag) {
   214                 if (strcmp(flag, "OverridesSystemGestures") == 0) {
   215                     flags |= QT_EXTENDED_SURFACE_WINDOWFLAG_OVERRIDESSYSTEMGESTURES;
   216                 } else if (strcmp(flag, "StaysOnTop") == 0) {
   217                     flags |= QT_EXTENDED_SURFACE_WINDOWFLAG_STAYSONTOP;
   218                 } else if (strcmp(flag, "BypassWindowManager") == 0) {
   219                     // See https://github.com/qtproject/qtwayland/commit/fb4267103d
   220                     flags |= 4 /* QT_EXTENDED_SURFACE_WINDOWFLAG_BYPASSWINDOWMANAGER */;
   221                 }
   222 
   223                 flag = strtok_r(NULL, " ", &saveptr);
   224             }
   225 
   226             free(tmp);
   227         }
   228 
   229         qt_extended_surface_set_window_flags(qt_extended_surface, flags);
   230     }
   231 }
   232 
   233 static void QtExtendedSurface_Subscribe(struct qt_extended_surface *surface, const char *name)
   234 {
   235     SDL_AddHintCallback(name, QtExtendedSurface_OnHintChanged, surface);
   236 }
   237 
   238 static void QtExtendedSurface_Unsubscribe(struct qt_extended_surface *surface, const char *name)
   239 {
   240     SDL_DelHintCallback(name, QtExtendedSurface_OnHintChanged, surface);
   241 }
   242 #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
   243 
   244 void
   245 Wayland_SetWindowFullscreen(_THIS, SDL_Window * window,
   246                             SDL_VideoDisplay * _display, SDL_bool fullscreen)
   247 {
   248     SDL_WindowData *wind = window->driverdata;
   249 
   250     if (fullscreen)
   251         wl_shell_surface_set_fullscreen(wind->shell_surface,
   252                                         WL_SHELL_SURFACE_FULLSCREEN_METHOD_SCALE,
   253                                         0, (struct wl_output *)_display->driverdata);
   254     else
   255         wl_shell_surface_set_toplevel(wind->shell_surface);
   256 
   257     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
   258 }
   259 
   260 void
   261 Wayland_RestoreWindow(_THIS, SDL_Window * window)
   262 {
   263     SDL_WindowData *wind = window->driverdata;
   264 
   265     wl_shell_surface_set_toplevel(wind->shell_surface);
   266 
   267     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
   268 }
   269 
   270 void
   271 Wayland_MaximizeWindow(_THIS, SDL_Window * window)
   272 {
   273     SDL_WindowData *wind = window->driverdata;
   274 
   275     wl_shell_surface_set_maximized(wind->shell_surface, NULL);
   276 
   277     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
   278 }
   279 
   280 int Wayland_CreateWindow(_THIS, SDL_Window *window)
   281 {
   282     SDL_WindowData *data;
   283     SDL_VideoData *c;
   284     struct wl_region *region;
   285 
   286     data = calloc(1, sizeof *data);
   287     if (data == NULL)
   288         return SDL_OutOfMemory();
   289 
   290     c = _this->driverdata;
   291     window->driverdata = data;
   292 
   293     if (!(window->flags & SDL_WINDOW_OPENGL)) {
   294         SDL_GL_LoadLibrary(NULL);
   295         window->flags |= SDL_WINDOW_OPENGL;
   296     }
   297 
   298     if (window->x == SDL_WINDOWPOS_UNDEFINED) {
   299         window->x = 0;
   300     }
   301     if (window->y == SDL_WINDOWPOS_UNDEFINED) {
   302         window->y = 0;
   303     }
   304 
   305     data->waylandData = c;
   306     data->sdlwindow = window;
   307 
   308     data->surface =
   309         wl_compositor_create_surface(c->compositor);
   310     wl_surface_set_user_data(data->surface, data);
   311     data->shell_surface = wl_shell_get_shell_surface(c->shell,
   312                                                      data->surface);
   313     wl_shell_surface_set_class (data->shell_surface, c->classname);
   314 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
   315     if (c->surface_extension) {
   316         data->extended_surface = qt_surface_extension_get_extended_surface(
   317                 c->surface_extension, data->surface);
   318 
   319         QtExtendedSurface_Subscribe(data->extended_surface, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION);
   320         QtExtendedSurface_Subscribe(data->extended_surface, SDL_HINT_QTWAYLAND_WINDOW_FLAGS);
   321     }
   322 #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
   323 
   324     data->egl_window = WAYLAND_wl_egl_window_create(data->surface,
   325                                             window->w, window->h);
   326 
   327     /* Create the GLES window surface */
   328     data->egl_surface = SDL_EGL_CreateSurface(_this, (NativeWindowType) data->egl_window);
   329     
   330     if (data->egl_surface == EGL_NO_SURFACE) {
   331         return SDL_SetError("failed to create a window surface");
   332     }
   333 
   334     if (data->shell_surface) {
   335         wl_shell_surface_set_user_data(data->shell_surface, data);
   336         wl_shell_surface_add_listener(data->shell_surface,
   337                                       &shell_surface_listener, data);
   338     }
   339 
   340 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
   341     if (data->extended_surface) {
   342         qt_extended_surface_set_user_data(data->extended_surface, data);
   343         qt_extended_surface_add_listener(data->extended_surface,
   344                                          &extended_surface_listener, data);
   345     }
   346 #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
   347 
   348     region = wl_compositor_create_region(c->compositor);
   349     wl_region_add(region, 0, 0, window->w, window->h);
   350     wl_surface_set_opaque_region(data->surface, region);
   351     wl_region_destroy(region);
   352 
   353     if (c->relative_mouse_mode) {
   354         Wayland_input_lock_pointer(c->input);
   355     }
   356 
   357     WAYLAND_wl_display_flush(c->display);
   358 
   359     return 0;
   360 }
   361 
   362 void Wayland_SetWindowSize(_THIS, SDL_Window * window)
   363 {
   364     SDL_VideoData *data = _this->driverdata;
   365     SDL_WindowData *wind = window->driverdata;
   366     struct wl_region *region;
   367 
   368     WAYLAND_wl_egl_window_resize(wind->egl_window, window->w, window->h, 0, 0);
   369 
   370     region =wl_compositor_create_region(data->compositor);
   371     wl_region_add(region, 0, 0, window->w, window->h);
   372     wl_surface_set_opaque_region(wind->surface, region);
   373     wl_region_destroy(region);
   374 }
   375 
   376 void Wayland_SetWindowTitle(_THIS, SDL_Window * window)
   377 {
   378     SDL_WindowData *wind = window->driverdata;
   379     
   380     if (window->title != NULL) {
   381         wl_shell_surface_set_title(wind->shell_surface, window->title);
   382     }
   383 
   384     WAYLAND_wl_display_flush( ((SDL_VideoData*)_this->driverdata)->display );
   385 }
   386 
   387 void Wayland_DestroyWindow(_THIS, SDL_Window *window)
   388 {
   389     SDL_VideoData *data = _this->driverdata;
   390     SDL_WindowData *wind = window->driverdata;
   391 
   392     if (data) {
   393         SDL_EGL_DestroySurface(_this, wind->egl_surface);
   394         WAYLAND_wl_egl_window_destroy(wind->egl_window);
   395 
   396         if (wind->shell_surface)
   397             wl_shell_surface_destroy(wind->shell_surface);
   398 
   399 #ifdef SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH
   400         if (wind->extended_surface) {
   401             QtExtendedSurface_Unsubscribe(wind->extended_surface, SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION);
   402             QtExtendedSurface_Unsubscribe(wind->extended_surface, SDL_HINT_QTWAYLAND_WINDOW_FLAGS);
   403             qt_extended_surface_destroy(wind->extended_surface);
   404         }
   405 #endif /* SDL_VIDEO_DRIVER_WAYLAND_QT_TOUCH */
   406         wl_surface_destroy(wind->surface);
   407 
   408         SDL_free(wind);
   409         WAYLAND_wl_display_flush(data->display);
   410     }
   411     window->driverdata = NULL;
   412 }
   413 
   414 #endif /* SDL_VIDEO_DRIVER_WAYLAND && SDL_VIDEO_OPENGL_EGL */
   415 
   416 /* vi: set ts=4 sw=4 expandtab: */