diff --git a/src/video/x11/SDL_x11events.c b/src/video/x11/SDL_x11events.c index 72d7b391b..c3de0c798 100644 --- a/src/video/x11/SDL_x11events.c +++ b/src/video/x11/SDL_x11events.c @@ -447,6 +447,34 @@ void X11_ReconcileKeyboardState(_THIS) } } +static void X11_ShowChildren(_THIS, SDL_Window *window) +{ + for (window = window->first_child; window != NULL; window = window->next_sibling) { + window->driverdata->hidden_by_parent_focus = SDL_FALSE; + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + X11_ShowWindow(_this, window); + } + + if (window->first_child) { + X11_ShowChildren(_this, window); + } + } +} + +static void X11_HideChildren(_THIS, SDL_Window *window) +{ + for (window = window->first_child; window != NULL; window = window->next_sibling) { + if (!(window->flags & SDL_WINDOW_HIDDEN)) { + window->driverdata->hidden_by_parent_focus = SDL_TRUE; + X11_HideWindow(_this, window); + } + + if (window->first_child) { + X11_HideChildren(_this, window); + } + } +} + static void X11_DispatchFocusIn(_THIS, SDL_WindowData *data) { #ifdef DEBUG_XEVENTS @@ -465,6 +493,9 @@ static void X11_DispatchFocusIn(_THIS, SDL_WindowData *data) if (data->flashing_window) { X11_FlashWindow(_this, data->window, SDL_FLASH_CANCEL); } + if (data->window->parent == NULL) { + X11_ShowChildren(_this, data->window); + } } static void X11_DispatchFocusOut(_THIS, SDL_WindowData *data) @@ -487,6 +518,9 @@ static void X11_DispatchFocusOut(_THIS, SDL_WindowData *data) #ifdef SDL_USE_IME SDL_IME_SetFocus(SDL_FALSE); #endif + if (data->window->parent == NULL) { + X11_HideChildren(_this, data->window); + } } static void X11_DispatchMapNotify(SDL_WindowData *data) @@ -501,8 +535,10 @@ static void X11_DispatchMapNotify(SDL_WindowData *data) static void X11_DispatchUnmapNotify(SDL_WindowData *data) { - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); - SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + if (!data->hidden_by_parent_focus) { + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_HIDDEN, 0, 0); + SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MINIMIZED, 0, 0); + } } static void InitiateWindowMove(_THIS, const SDL_WindowData *data, const SDL_Point *point) @@ -1168,14 +1204,23 @@ static void X11_DispatchEvent(_THIS, XEvent *xevent) if (xevent->xconfigure.x != data->last_xconfigure.x || xevent->xconfigure.y != data->last_xconfigure.y) { + SDL_Window *w; + int x = xevent->xconfigure.x; + int y = xevent->xconfigure.y; + + SDL_GlobalToRelativeForWindow(data->window, x, y, &x, &y); SDL_SendWindowEvent(data->window, SDL_EVENT_WINDOW_MOVED, - xevent->xconfigure.x, xevent->xconfigure.y); + x, y); + #ifdef SDL_USE_IME if (SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) { /* Update IME candidate list position */ SDL_IME_UpdateTextRect(NULL); } #endif + for (w = data->window->first_child; w != NULL; w = w->next_sibling) { + X11_UpdateWindowPosition(w); + } } if (xevent->xconfigure.width != data->last_xconfigure.width || xevent->xconfigure.height != data->last_xconfigure.height) { diff --git a/src/video/x11/SDL_x11sym.h b/src/video/x11/SDL_x11sym.h index 17734ad59..7111b176c 100644 --- a/src/video/x11/SDL_x11sym.h +++ b/src/video/x11/SDL_x11sym.h @@ -155,6 +155,8 @@ SDL_X11_SYM(void,XRefreshKeyboardMapping,(XMappingEvent *a),(a),) SDL_X11_SYM(int,XQueryTree,(Display* a,Window b,Window* c,Window* d,Window** e,unsigned int* f),(a,b,c,d,e,f),return) SDL_X11_SYM(Bool,XSupportsLocale,(void),(),return) SDL_X11_SYM(Status,XmbTextListToTextProperty,(Display* a,char** b,int c,XICCEncodingStyle d,XTextProperty* e),(a,b,c,d,e),return) +SDL_X11_SYM(Region,XCreateRegion,(void),(),return) +SDL_X11_SYM(void,XDestroyRegion,(Region),(a),) #if SDL_VIDEO_DRIVER_X11_XFIXES SDL_X11_MODULE(XFIXES) @@ -314,6 +316,7 @@ SDL_X11_SYM(void,XScreenSaverSuspend,(Display *dpy,Bool suspend),(dpy,suspend),r #if SDL_VIDEO_DRIVER_X11_XSHAPE SDL_X11_MODULE(XSHAPE) SDL_X11_SYM(void,XShapeCombineMask,(Display *dpy,Window dest,int dest_kind,int x_off,int y_off,Pixmap src,int op),(dpy,dest,dest_kind,x_off,y_off,src,op),) +SDL_X11_SYM(void,XShapeCombineRegion,(Display *a,Window b,int c,int d,int e,Region f,int g),(a,b,c,d,e,f,g),) #endif #undef SDL_X11_MODULE diff --git a/src/video/x11/SDL_x11video.c b/src/video/x11/SDL_x11video.c index 1d3424b91..3d182b2ba 100644 --- a/src/video/x11/SDL_x11video.c +++ b/src/video/x11/SDL_x11video.c @@ -321,6 +321,8 @@ static SDL_VideoDevice *X11_CreateDevice(void) device->system_theme = SDL_SystemTheme_Get(); #endif + device->quirk_flags = VIDEO_DEVICE_QUIRK_HAS_POPUP_WINDOW_SUPPORT; + return device; } diff --git a/src/video/x11/SDL_x11window.c b/src/video/x11/SDL_x11window.c index 5812bd9c4..0117d5b16 100644 --- a/src/video/x11/SDL_x11window.c +++ b/src/video/x11/SDL_x11window.c @@ -166,6 +166,58 @@ void X11_SetNetWMState(_THIS, Window xwindow, Uint32 flags) } } +static void X11_ConstrainPopup(SDL_Window *window) +{ + /* Clamp popup windows to the output borders */ + if (SDL_WINDOW_IS_POPUP(window)) { + SDL_Window *w; + SDL_DisplayID displayID; + SDL_Rect rect; + int abs_x = window->x; + int abs_y = window->y; + int offset_x = 0, offset_y = 0; + + /* Calculate the total offset from the parents */ + for (w = window->parent; w->parent != NULL; w = w->parent) { + offset_x += w->x; + offset_y += w->y; + } + + offset_x += w->x; + offset_y += w->y; + abs_x += offset_x; + abs_y += offset_y; + + displayID = SDL_GetDisplayForWindow(w); + + SDL_GetDisplayBounds(displayID, &rect); + if (abs_x + window->w > rect.x + rect.w) { + abs_x -= (abs_x + window->w) - (rect.x + rect.w); + } + if (abs_y + window->h > rect.y + rect.h) { + abs_y -= (abs_y + window->h) - (rect.y + rect.h); + } + abs_x = SDL_max(abs_x, rect.x); + abs_y = SDL_max(abs_y, rect.y); + + window->x = window->windowed.x = abs_x - offset_x; + window->y = window->windowed.y = abs_y - offset_y; + } +} + +static void X11_SetKeyboardFocus(SDL_Window *window) +{ + SDL_Window *topmost = window; + + /* Find the topmost parent */ + while (topmost->parent != NULL) { + topmost = topmost->parent; + } + + topmost->driverdata->keyboard_focus = window; + SDL_SetKeyboardFocus(window); +} + Uint32 X11_GetNetWMState(_THIS, SDL_Window *window, Window xwindow) { @@ -302,8 +354,10 @@ static int SetupWindowData(_THIS, SDL_Window *window, Window w, BOOL created) XWindowAttributes attrib; X11_XGetWindowAttributes(data->videodata->display, w, &attrib); - window->x = attrib.x; - window->y = attrib.y; + if (!SDL_WINDOW_IS_POPUP(window)) { + window->x = attrib.x; + window->y = attrib.y; + } window->w = attrib.width; window->h = attrib.height; if (attrib.map_state != IsUnmapped) { @@ -392,6 +446,7 @@ int X11_CreateWindow(_THIS, SDL_Window *window) Atom _NET_WM_PID; long fevent = 0; const char *hint = NULL; + int win_x, win_y; SDL_bool undefined_position = SDL_FALSE; #if SDL_VIDEO_OPENGL_GLX || SDL_VIDEO_OPENGL_EGL @@ -533,6 +588,13 @@ int X11_CreateWindow(_THIS, SDL_Window *window) undefined_position = SDL_TRUE; } + if (SDL_WINDOW_IS_POPUP(window)) { + X11_ConstrainPopup(window); + } + SDL_RelativeToGlobalForWindow(window, + window->windowed.x, window->windowed.y, + &win_x, &win_y); + /* Always create this with the window->windowed.* fields; if we're creating a windowed mode window, that's fine. If we're creating a fullscreen window, the window manager will want to know these values @@ -540,7 +602,7 @@ int X11_CreateWindow(_THIS, SDL_Window *window) migration to fullscreen after CreateSDLWindow returns, which will put all the SDL_Window fields and system state as expected. */ w = X11_XCreateWindow(display, RootWindow(display, screen), - window->windowed.x, window->windowed.y, window->windowed.w, window->windowed.h, + win_x, win_y, window->windowed.w, window->windowed.h, 0, depth, InputOutput, visual, (CWOverrideRedirect | CWBackPixmap | CWBorderPixel | CWBackingStore | CWColormap), @@ -561,8 +623,8 @@ int X11_CreateWindow(_THIS, SDL_Window *window) sizehints->flags |= (PMaxSize | PMinSize); } if (!undefined_position) { - sizehints->x = window->x; - sizehints->y = window->y; + sizehints->x = win_x; + sizehints->y = win_y; sizehints->flags |= USPosition; } @@ -675,6 +737,13 @@ int X11_CreateWindow(_THIS, SDL_Window *window) } #endif + /* Tooltips do not receive input */ + if ((window->flags & SDL_WINDOW_TOOLTIP) && SDL_X11_HAVE_XSHAPE) { + Region region = X11_XCreateRegion(); + X11_XShapeCombineRegion(display, w, ShapeInput, 0, 0, region, ShapeSet); + X11_XDestroyRegion(region); + } + X11_Xinput2SelectTouch(_this, window); X11_XSelectInput(display, w, @@ -814,8 +883,9 @@ int X11_SetWindowIcon(_THIS, SDL_Window *window, SDL_Surface *icon) return rc; } -void X11_SetWindowPosition(_THIS, SDL_Window *window) +void X11_UpdateWindowPosition(SDL_Window *window) { + SDL_Window *w; SDL_WindowData *data = window->driverdata; Display *display = data->videodata->display; int (*prev_handler)(Display *, XErrorEvent *) = NULL; @@ -823,6 +893,7 @@ void X11_SetWindowPosition(_THIS, SDL_Window *window) Window childReturn, root, parent; Window *children; XWindowAttributes attrs; + int dest_x, dest_y; int orig_x, orig_y; Uint64 timeout; @@ -832,8 +903,12 @@ void X11_SetWindowPosition(_THIS, SDL_Window *window) X11_XTranslateCoordinates(display, parent, DefaultRootWindow(display), attrs.x, attrs.y, &orig_x, &orig_y, &childReturn); + SDL_RelativeToGlobalForWindow(window, + window->x - data->border_left, window->y - data->border_top, + &dest_x, &dest_y); + /*Attempt to move the window*/ - X11_XMoveWindow(display, data->xwindow, window->x - data->border_left, window->y - data->border_top); + X11_XMoveWindow(display, data->xwindow, dest_x, dest_y); /* Wait a brief time to see if the window manager decided to let this move happen. If the window changes at all, even to an unexpected value, we break out. */ @@ -852,10 +927,13 @@ void X11_SetWindowPosition(_THIS, SDL_Window *window) if (!caught_x11_error) { if ((x != orig_x) || (y != orig_y)) { + if (SDL_WINDOW_IS_POPUP(window)) { + SDL_GlobalToRelativeForWindow(window, x, y, &x, &y); + } window->x = x; window->y = y; break; /* window moved, time to go. */ - } else if ((x == window->x) && (y == window->y)) { + } else if ((x == dest_x) && (y == dest_y)) { break; /* we're at the place we wanted to be anyhow, drop out. */ } } @@ -869,6 +947,18 @@ void X11_SetWindowPosition(_THIS, SDL_Window *window) X11_XSetErrorHandler(prev_handler); caught_x11_error = SDL_FALSE; + + for (w = window->first_child; w != NULL; w = w->next_sibling) { + X11_UpdateWindowPosition(w); + } +} + +void X11_SetWindowPosition(_THIS, SDL_Window *window) +{ + if (SDL_WINDOW_IS_POPUP(window)) { + X11_ConstrainPopup(window); + } + X11_UpdateWindowPosition(window); } void X11_SetWindowMinimumSize(_THIS, SDL_Window *window) @@ -1174,6 +1264,10 @@ void X11_ShowWindow(_THIS, SDL_Window *window) Display *display = data->videodata->display; XEvent event; + if (window->driverdata->hidden_by_parent_focus) { + return; + } + if (!X11_IsWindowMapped(_this, window)) { X11_XMapRaised(display, data->xwindow); /* Blocking wait for "MapNotify" event. @@ -1191,6 +1285,13 @@ void X11_ShowWindow(_THIS, SDL_Window *window) X11_XSetInputFocus(display, data->xwindow, RevertToNone, CurrentTime); X11_XFlush(display); } + + /* Popup menus grab the keyboard */ + if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (window->parent == SDL_GetKeyboardFocus()) { + X11_SetKeyboardFocus(window); + } + } } void X11_HideWindow(_THIS, SDL_Window *window) @@ -1208,6 +1309,20 @@ void X11_HideWindow(_THIS, SDL_Window *window) } X11_XFlush(display); } + + /* Transfer keyboard focus back to the parent */ + if (window->flags & SDL_WINDOW_POPUP_MENU) { + if (window == SDL_GetKeyboardFocus()) { + SDL_Window *new_focus = window->parent; + + /* Find the highest level window that isn't being hidden or destroyed. */ + while (new_focus->parent != NULL && (new_focus->is_hiding || new_focus->is_destroying)) { + new_focus = new_focus->parent; + } + + X11_SetKeyboardFocus(new_focus); + } + } } static void X11_SetWindowActive(_THIS, SDL_Window *window) diff --git a/src/video/x11/SDL_x11window.h b/src/video/x11/SDL_x11window.h index fe1321849..416f7d6b1 100644 --- a/src/video/x11/SDL_x11window.h +++ b/src/video/x11/SDL_x11window.h @@ -60,6 +60,7 @@ struct SDL_WindowData int border_top; int border_bottom; SDL_bool mouse_grabbed; + SDL_bool hidden_by_parent_focus; Uint64 last_focus_event_time; PendingFocusEnum pending_focus; Uint64 pending_focus_time; @@ -70,6 +71,7 @@ struct SDL_WindowData Window xdnd_source; SDL_bool flashing_window; Uint64 flash_cancel_time; + SDL_Window *keyboard_focus; #if SDL_VIDEO_OPENGL_EGL EGLSurface egl_surface; #endif @@ -116,5 +118,6 @@ extern void X11_AcceptDragAndDrop(SDL_Window *window, SDL_bool accept); extern int X11_FlashWindow(_THIS, SDL_Window *window, SDL_FlashOperation operation); int SDL_X11_SetWindowTitle(Display *display, Window xwindow, char *title); +void X11_UpdateWindowPosition(SDL_Window *window); #endif /* SDL_x11window_h_ */