DOSBox-X
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
src/gui/sdlmain_linux.cpp
00001 
00002 #include "config.h"
00003 
00004 #ifdef LINUX
00005 #include "logging.h"
00006 #include "keymap.h"
00007 #include "SDL.h"
00008 #include "SDL_version.h"
00009 #include "SDL_syswm.h"
00010 #include "sdlmain.h"
00011 
00012 #include <X11/XKBlib.h>
00013 #include <X11/extensions/XKBrules.h>
00014 
00015 void UpdateWindowDimensions(Bitu width, Bitu height);
00016 void UpdateWindowMaximized(bool flag);
00017 
00018 #if C_X11_XRANDR
00019 #include <X11/extensions/Xrandr.h>
00020 #endif
00021 
00022 #if !defined(C_SDL2)
00023 extern "C" void SDL1_hax_X11_jpfix(int ro_scan,int yen_scan);
00024 
00025 # define _NET_WM_STATE_REMOVE        0    // remove/unset property
00026 # define _NET_WM_STATE_ADD           1    // add/set property
00027 # define _NET_WM_STATE_TOGGLE        2    // toggle property
00028 #endif
00029 
00030 void LinuxX11_OnTop(bool f) {
00031     (void)f;
00032 
00033 #if !defined(C_SDL2)
00034     SDL_SysWMinfo wminfo;
00035     memset(&wminfo,0,sizeof(wminfo));
00036     SDL_VERSION(&wminfo.version);
00037     if (SDL_GetWMInfo(&wminfo) >= 0) {
00038         if (wminfo.subsystem == SDL_SYSWM_X11 && wminfo.info.x11.display != NULL) {
00039             Atom wmStateAbove = XInternAtom(wminfo.info.x11.display, "_NET_WM_STATE_ABOVE", 1);
00040             if (wmStateAbove == None) return;
00041 
00042             Atom wmNetWmState = XInternAtom(wminfo.info.x11.display, "_NET_WM_STATE", 1);
00043             if (wmNetWmState == None) return;
00044 
00045             XClientMessageEvent xclient;
00046             memset(&xclient,0,sizeof(xclient));
00047 
00048             xclient.type = ClientMessage;
00049             xclient.window = wminfo.info.x11.wmwindow;
00050             xclient.message_type = wmNetWmState;
00051             xclient.format = 32;
00052             xclient.data.l[0] = f ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
00053             xclient.data.l[1] = (long int)wmStateAbove;
00054             xclient.data.l[2] = 0;
00055             xclient.data.l[3] = 0;
00056             xclient.data.l[4] = 0;
00057 
00058             XSendEvent(
00059                     wminfo.info.x11.display,
00060                     DefaultRootWindow(wminfo.info.x11.display),
00061                     False,
00062                     SubstructureRedirectMask | SubstructureNotifyMask,
00063                     (XEvent *)&xclient );
00064         }
00065     }
00066 #endif
00067 }
00068 
00069 char *LinuxX11_KeySymName(Uint32 x) {
00070     (void)x;
00071 #if !defined(C_SDL2)
00072     SDL_SysWMinfo wminfo;
00073     memset(&wminfo,0,sizeof(wminfo));
00074     SDL_VERSION(&wminfo.version);
00075     if (SDL_GetWMInfo(&wminfo) >= 0) {
00076         if (wminfo.subsystem == SDL_SYSWM_X11 && wminfo.info.x11.display != NULL) {
00077             return XKeysymToString(x);
00078         }
00079     }
00080 #endif
00081 
00082     return NULL;
00083 }
00084 
00085 void Linux_JPXKBFix(void) {
00086 #if !defined(C_SDL2)
00087     SDL_SysWMinfo wminfo;
00088     memset(&wminfo,0,sizeof(wminfo));
00089     SDL_VERSION(&wminfo.version);
00090     if (SDL_GetWMInfo(&wminfo) >= 0) {
00091         if (wminfo.subsystem == SDL_SYSWM_X11 && wminfo.info.x11.display != NULL) {
00092             /* detect xkbmap with Ro and Yen keys mapped to \, determine the scan codes,
00093              * and then hack the SDL 1.x X11 driver to handle it.
00094              *
00095              * Never assume specific scan codes, even though on my system the scan codes
00096              * are 97 (Yen) and 132 (Ro) both mapped to \ backslash.
00097              *
00098              * We can use the index to look for keysyms that mention backslash and underscore (Ro)
00099              * or backslash and bar (Yen) */
00100             /* TODO: If xkbmap actually maps these keys properly, how can we tell? */
00101             int ro=-1,yen=-1;
00102             unsigned int i,j;
00103             KeySym sym[4];
00104 
00105             for (i=0;i < 256;i++) {
00106                 for (j=0;j < 4;j++)
00107                     sym[j] = XKeycodeToKeysym(wminfo.info.x11.display,(KeyCode)i,(int)j);
00108 
00109                 if (sym[0] == '\\') {
00110                     if (sym[1] == '_') { /* shift + backslash == _   means we found Ro */
00111                         if (ro < 0) ro = (int)i;
00112                     }
00113                     else if (sym[1] == '|') { /* shift + backslash == |   means we found Yen */
00114                         if (yen < 0) yen = (int)i;
00115                     }
00116                 }
00117             }
00118 
00119             LOG_MSG("JP Linux/X11 fix: Found Ro=%d Yen=%d",ro,yen);
00120 
00121             SDL1_hax_X11_jpfix(ro,yen);
00122         }
00123     }
00124 #endif
00125 }
00126 
00127 unsigned int Linux_GetKeyboardLayout(void) {
00128     unsigned int ret = DKM_US;
00129 
00130 #if defined(C_SDL2)
00131     // TODO
00132 #else
00133     SDL_SysWMinfo wminfo;
00134     memset(&wminfo,0,sizeof(wminfo));
00135     SDL_VERSION(&wminfo.version);
00136     if (SDL_GetWMInfo(&wminfo) >= 0) {
00137         if (wminfo.subsystem == SDL_SYSWM_X11 && wminfo.info.x11.display != NULL) {
00138             XkbRF_VarDefsRec vd = {0};
00139             XkbStateRec state = {0};
00140 
00141             XkbGetState(wminfo.info.x11.display, XkbUseCoreKbd, &state);
00142 
00143             XkbDescPtr desc = XkbGetKeyboard(wminfo.info.x11.display, XkbAllComponentsMask, XkbUseCoreKbd);
00144             char *group = desc ? XGetAtomName(wminfo.info.x11.display, desc->names->groups[state.group]) : NULL;
00145 
00146             if (group != NULL) LOG_MSG("Current X11 keyboard layout (full name) is: '%s'\n",group);
00147 
00148             /* FIXME: Does this allocate anything? Do I need to free it? I am concerned about memory leaks --J.C. */
00149             XkbRF_GetNamesProp(wminfo.info.x11.display, NULL, &vd);
00150 
00151             char *tok = vd.layout ? strtok(vd.layout, ",") : NULL;
00152 
00153             if (tok != NULL) {
00154                 for (int i = 0; i < state.group; i++) {
00155                     tok = strtok(NULL, ",");
00156                     if (tok == NULL) break;
00157                 }
00158                 if (tok != NULL) {
00159                     LOG_MSG("Current X11 keyboard layout (token) is: '%s'\n",tok);
00160 
00161                     if (!strcmp(tok,"us"))
00162                         ret = DKM_US;
00163                     else if (!strcmp(tok,"jp"))
00164                         ret = DKM_JPN;
00165                     else if (!strcmp(tok,"de"))
00166                         ret = DKM_DEU;
00167                 }
00168             }
00169 
00170             if (group) XFree(group);
00171             if (desc) XFree(desc);
00172         }
00173     }
00174 #endif
00175 
00176     return ret;
00177 }
00178 
00179 void UpdateWindowDimensions_Linux(void) {
00180 #if defined(C_SDL2)
00181     // TODO
00182 #else
00183     bool GFX_IsFullscreen();
00184 
00185     SDL_SysWMinfo wminfo;
00186     memset(&wminfo,0,sizeof(wminfo));
00187     SDL_VERSION(&wminfo.version);
00188     if (SDL_GetWMInfo(&wminfo) >= 0) {
00189         if (wminfo.subsystem == SDL_SYSWM_X11 && wminfo.info.x11.display != NULL) {
00190             XWindowAttributes attr;
00191             bool maximized = false;
00192 
00193             memset(&attr,0,sizeof(attr));
00194             XGetWindowAttributes(wminfo.info.x11.display, GFX_IsFullscreen() ? wminfo.info.x11.fswindow : wminfo.info.x11.wmwindow, &attr);
00195 
00196             /* we also want to ask X11 if the various atoms have been set by the window manager
00197              * to signal that our window has been maximized. generally when the window has been
00198              * maximized, SDL video mode setting has no effect on the dimensions of the window
00199              * when it's been maximized so it helps to know in order to make better choices */
00200             if (GFX_IsFullscreen()) {
00201             }
00202             else {
00203                 Atom atomWmState = XInternAtom(wminfo.info.x11.display, "_NET_WM_STATE",                False);
00204                 Atom atomMaxVert = XInternAtom(wminfo.info.x11.display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
00205                 Atom atomMaxHorz = XInternAtom(wminfo.info.x11.display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
00206 
00207                 unsigned long nItem = 0, bytesAfter = 0;
00208                 unsigned char *properties = NULL;
00209                 Atom type = None;
00210                 int format = 0;
00211 
00212                 XGetWindowProperty(wminfo.info.x11.display, wminfo.info.x11.wmwindow,
00213                     /*atom to query*/atomWmState,/*offset*/0,/*length*/16384,/*delete*/False,
00214                     /*request type*/AnyPropertyType,
00215                     /*returns...*/&type,&format,&nItem,&bytesAfter,&properties);
00216 
00217                 if (properties != NULL) {
00218                     if (format == 32) { /* it usually is */
00219                         for (unsigned long i=0;i < nItem;i++) {
00220                             uint32_t val = ((uint32_t*)properties)[i];
00221 
00222                             if ((Atom)val == atomMaxVert || (Atom)val == atomMaxHorz)
00223                                 maximized = true;
00224                         }
00225                     }
00226 
00227                     XFree(properties);
00228                 }
00229             }
00230 
00231             LOG_MSG("X11 main window is %u x %u maximized=%u",attr.width, attr.height, maximized);
00232 
00233             UpdateWindowDimensions((unsigned int)attr.width, (unsigned int)attr.height);
00234             UpdateWindowMaximized(maximized);
00235         }
00236     }
00237 #endif
00238 }
00239 
00240 /* Retrieve screen size/dimensions/DPI using XRandR */
00241 static bool Linux_TryXRandrGetDPI(ScreenSizeInfo &info,Display *display,Window window) {
00242     bool result = false;
00243 #if C_X11_XRANDR
00244     XRRScreenResources *xr_screen;
00245     XWindowAttributes attr;
00246     int x = 0, y = 0;
00247     Window child;
00248 
00249     memset(&attr,0,sizeof(attr));
00250     XGetWindowAttributes(display, window, &attr);
00251     XTranslateCoordinates(display, window, DefaultRootWindow(display), 0, 0, &x, &y, &child );
00252 
00253     attr.x += x;
00254     attr.y += y;
00255 
00256     if ((xr_screen=XRRGetScreenResources(display, DefaultRootWindow(display))) != NULL) {
00257         /* Look for a valid CRTC, don't assume the first is valid (as the StackOverflow example does) */
00258         for (int c=0;c < xr_screen->ncrtc;c++) {
00259             XRRCrtcInfo *chk = XRRGetCrtcInfo(display, xr_screen, xr_screen->crtcs[c]);
00260             if (chk == NULL) continue;
00261             if (chk->width == 0 || chk->height == 0) continue;
00262 
00263             LOG_MSG("XRandR CRTC %u: pos=(%d,%d) size=(%d,%d) outputs=%d",
00264                 c,chk->x,chk->y,chk->width,chk->height,chk->noutput);
00265 
00266             /* match our window position to the display, use the center */
00267             int match_x = attr.x + (attr.width / 2);
00268             int match_y = attr.y + (attr.height / 2);
00269 
00270             if (match_x >= chk->x && match_x < (chk->x+(int)chk->width) &&
00271                 match_y >= chk->y && match_y < (chk->y+(int)chk->height)) {
00272                 LOG_MSG("Our window lies on this CRTC display (window pos=(%d,%d) size=(%d,%d) match=(%d,%d)).",
00273                     attr.x,attr.y,
00274                     attr.width,attr.height,
00275                     match_x,match_y);
00276 
00277                 if (chk->noutput > 0) {
00278                     for (int o=0;o < chk->noutput;o++) {
00279                         XRROutputInfo *ochk = XRRGetOutputInfo(display, xr_screen, chk->outputs[o]);
00280                         if (ochk == NULL) continue;
00281 
00282                         std::string oname;
00283 
00284                         if (ochk->nameLen > 0 && ochk->name != NULL)
00285                             oname = std::string(ochk->name,ochk->nameLen);
00286 
00287                         LOG_MSG("  Goes to output %u: name='%s' size_mm=(%lu x %lu)",
00288                                 o,oname.c_str(),ochk->mm_width,ochk->mm_height);
00289 
00290                         if (true/*TODO: Any decision making?*/) {
00291                             o = chk->noutput; /* short circuit the for loop */
00292                             c = xr_screen->ncrtc; /* and the other */
00293                             result = true;
00294 
00295                             /* choose this combo to determine screen size, and dimensions */
00296                             info.method = ScreenSizeInfo::METHOD_XRANDR;
00297 
00298                             info.screen_dimensions_pixels.width  = chk->width;
00299                             info.screen_dimensions_pixels.height = chk->height;
00300 
00301                             info.screen_dimensions_mm.width      = ochk->mm_width;
00302                             info.screen_dimensions_mm.height     = ochk->mm_height;
00303 
00304                             if (info.screen_dimensions_mm.width > 0)
00305                                 info.screen_dpi.width =
00306                                     ((((double)info.screen_dimensions_pixels.width) * 25.4) /
00307                                      ((double)info.screen_dimensions_mm.width));
00308 
00309                             if (info.screen_dimensions_mm.height > 0)
00310                                 info.screen_dpi.height =
00311                                     ((((double)info.screen_dimensions_pixels.height) * 25.4) /
00312                                      ((double)info.screen_dimensions_mm.height));
00313                         }
00314 
00315                         XRRFreeOutputInfo(ochk);
00316                         ochk = NULL;
00317                     }
00318                 }
00319             }
00320 
00321             XRRFreeCrtcInfo(chk);
00322             chk = NULL;
00323         }
00324 
00325         XRRFreeScreenResources(xr_screen);
00326         xr_screen = NULL;
00327     }
00328 #endif
00329 
00330     return result;
00331 }
00332 
00333 void Linux_GetWindowDPI(ScreenSizeInfo &info) {
00334     info.clear();
00335 
00336 #if defined(C_SDL2)
00337     /* TODO */
00338 #else
00339         SDL_SysWMinfo wminfo;
00340         memset(&wminfo,0,sizeof(wminfo));
00341         SDL_VERSION(&wminfo.version);
00342         if (SDL_GetWMInfo(&wminfo) >= 0) {
00343                 if (wminfo.subsystem == SDL_SYSWM_X11 && wminfo.info.x11.display != NULL) {
00344             if (Linux_TryXRandrGetDPI(info,wminfo.info.x11.display,GFX_IsFullscreen() ? wminfo.info.x11.fswindow : wminfo.info.x11.wmwindow)) {
00345                 /* got it */
00346             }
00347             else {
00348                 /* fallback to X11 method, which may not return accurate info on modern systems */
00349                 Window rootWindow = DefaultRootWindow(wminfo.info.x11.display);
00350                 if (rootWindow != 0) {
00351                     int screen = 0;
00352 
00353                     info.method = ScreenSizeInfo::METHOD_X11;
00354 
00355                     /* found on StackOverflow */
00356 
00357                     /*
00358                      * there are 2.54 centimeters to an inch; so there are 25.4 millimeters.
00359                      *
00360                      *     dpi = N pixels / (M millimeters / (25.4 millimeters / 1 inch))
00361                      *         = N pixels / (M inch / 25.4)
00362                      *         = N * 25.4 pixels / M inch
00363                      */
00364                     info.screen_dimensions_pixels.width  = DisplayWidth(   wminfo.info.x11.display,screen);
00365                     info.screen_dimensions_pixels.height = DisplayHeight(  wminfo.info.x11.display,screen);
00366 
00367                     info.screen_dimensions_mm.width      = DisplayWidthMM( wminfo.info.x11.display,screen);
00368                     info.screen_dimensions_mm.height     = DisplayHeightMM(wminfo.info.x11.display,screen);
00369 
00370                     if (info.screen_dimensions_mm.width > 0)
00371                         info.screen_dpi.width =
00372                             ((((double)info.screen_dimensions_pixels.width) * 25.4) /
00373                              ((double)info.screen_dimensions_mm.width));
00374 
00375                     if (info.screen_dimensions_mm.height > 0)
00376                         info.screen_dpi.height =
00377                             ((((double)info.screen_dimensions_pixels.height) * 25.4) /
00378                              ((double)info.screen_dimensions_mm.height));
00379                 }
00380             }
00381         }
00382     }
00383 #endif
00384 }
00385 
00386 void Linux_GetDesktopResolution(int *width,int *height) {
00387 #if defined(C_SDL2)
00388     *width = 1024; // guess
00389     *height = 768;
00390 #else
00391         /* We're most likely running on an X-windows desktop (through SDL). */
00392         SDL_SysWMinfo wminfo;
00393         memset(&wminfo,0,sizeof(wminfo));
00394         SDL_VERSION(&wminfo.version);
00395         if (SDL_GetWMInfo(&wminfo) >= 0) {
00396                 if (wminfo.subsystem == SDL_SYSWM_X11 && wminfo.info.x11.display != NULL) {
00397                         LOG_MSG("GetDesktopResolution reading X11 desktop resolution");
00398 
00399                         Window rootWindow = DefaultRootWindow(wminfo.info.x11.display);
00400                         if (rootWindow != 0) {
00401                                 XWindowAttributes rootWinAttr;
00402 
00403                                 memset(&rootWinAttr,0,sizeof(rootWinAttr));
00404                                 XGetWindowAttributes(wminfo.info.x11.display,rootWindow,&rootWinAttr);
00405                                 LOG_MSG("Root window (ID %lu) is %lu x %lu",(unsigned long)rootWindow,(unsigned long)rootWinAttr.width,(unsigned long)rootWinAttr.height);
00406 
00407                                 if (rootWinAttr.width > 0 && rootWinAttr.height > 0) {
00408                                         *width = (int)rootWinAttr.width;
00409                                         *height = (int)rootWinAttr.height;
00410                                 }
00411                                 else {
00412                                         *width = 1024; // guess
00413                                         *height = 768;
00414                                 }
00415                         }
00416                         else {
00417                                 *width = 1024; // guess
00418                                 *height = 768;
00419                         }
00420                 }
00421                 else {
00422                         *width = 1024; // guess
00423                         *height = 768;
00424                 }
00425         }
00426         else {
00427                 *width = 1024; // guess
00428                 *height = 768;
00429         }
00430 #endif
00431 }
00432 #endif
00433