/* context_x11.c general x11 context layer Copyright (C) 1996-1997 Id Software, Inc. Copyright (C) 2000 Zephaniah E. Hull Copyright (C) 2000, 2001 Jeff Teunissen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ static const char rcsid[] = "$Id$"; #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_EXECINFO_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_VIDMODE # include #endif #include "QF/console.h" #include "context_x11.h" #include "QF/cvar.h" #include "dga_check.h" #include "QF/input.h" #include "QF/qargs.h" #include "QF/qtypes.h" #include "QF/sys.h" #include "QF/va.h" #include "QF/vid.h" static void (*event_handlers[LASTEvent]) (XEvent *); qboolean oktodraw = false; int x_shmeventtype; static int x_disp_ref_count = 0; Display *x_disp = NULL; int x_screen; Window x_root = None; XVisualInfo *x_visinfo; Visual *x_vis; Window x_win; Cursor nullcursor = None; static Atom aWMDelete = 0; #define X_MASK (VisibilityChangeMask | StructureNotifyMask | ExposureMask) #define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | PointerMotionMask) #ifdef HAVE_VIDMODE static XF86VidModeModeInfo **vidmodes; static int nummodes; static int original_mode = 0; static double x_gamma; static qboolean vidmode_avail = false; #endif static qboolean vidmode_active = false; cvar_t *vid_system_gamma; qboolean vid_fullscreen_active; static qboolean vid_context_created = false; static int window_x, window_y, window_saved; cvar_t *sys_backtrace; cvar_t *sys_dump_core; static int xss_timeout; static int xss_interval; static int xss_blanking; static int xss_exposures; void dump_core_callback (cvar_t *sys_dump_core) { #ifndef HAVE_UNISTD_H if (sys_dump_core->int_val) Con_Printf ("support for dumping core has not been compiled in!\n"); #endif } void backtrace_callback (cvar_t *sys_backtrace) { #ifndef HAVE_EXECINFO_H if (sys_backtrace->int_val) Con_Printf ("support for printing backtraces has not been compiled in!\n"); #endif } qboolean X11_AddEvent (int event, void (*event_handler) (XEvent *)) { if (event >= LASTEvent) { Sys_Printf ("event: %d, LASTEvent: %d\n", event, LASTEvent); return false; } if (event_handlers[event]) return false; event_handlers[event] = event_handler; return true; } qboolean X11_RemoveEvent (int event, void (*event_handler) (XEvent *)) { if (event >= LASTEvent) return false; if (event_handlers[event] != event_handler) return false; event_handlers[event] = NULL; return true; } void X11_ProcessEventProxy(XEvent *x_event) { if (x_event->type >= LASTEvent) { if (x_event->type == x_shmeventtype) oktodraw = 1; return; } if (event_handlers[x_event->type]) event_handlers[x_event->type] (x_event); } void X11_ProcessEvent (void) { XEvent x_event; XNextEvent (x_disp, &x_event); if (x_event.type >= LASTEvent) { if (x_event.type == x_shmeventtype) oktodraw = 1; return; } if (event_handlers[x_event.type]) event_handlers[x_event.type] (&x_event); } void X11_ProcessEvents (void) { /* Get events from X server. */ while (XPending (x_disp)) { X11_ProcessEvent (); } } // ======================================================================== // Tragic death handler // ======================================================================== void dump_backtrace () { #ifdef HAVE_EXECINFO_H #define MAXDEPTH 30 static int count = 0; // don't wanna do this too many times void *array[MAXDEPTH]; size_t size; if (sys_backtrace && (count < sys_backtrace->int_val)) { count++; size = backtrace (array, MAXDEPTH); fflush (stderr); backtrace_symbols_fd (array, size, STDERR_FILENO); } #endif } static void TragicDeath (int sig) { #ifdef HAVE_UNISTD_H if (sys_dump_core && sys_dump_core->int_val == 1) { // paranoid check that is NOT needed at time of writing (cvar init happens before rest of init) if (fork()) { printf ("Received signal %d, dumping core and exiting...\n", sig); dump_backtrace (); Sys_Quit (); } else { signal (SIGABRT, SIG_IGN); // is xlib setting a handler on us? abort (); } } else { #endif printf ("Received signal %d, exiting...\n", sig); dump_backtrace (); Sys_Quit (); abort(); // Hopefully not an infinite loop. // never reached #ifdef HAVE_UNISTD_H } #endif } void X11_OpenDisplay (void) { if (!x_disp) { x_disp = XOpenDisplay (NULL); if (!x_disp) { Sys_Error ("X11_OpenDisplay: Could not open display [%s]\n", XDisplayName (NULL)); } x_screen = DefaultScreen (x_disp); x_root = RootWindow (x_disp, x_screen); // catch signals signal (SIGHUP, TragicDeath); signal (SIGINT, TragicDeath); signal (SIGQUIT, TragicDeath); signal (SIGILL, TragicDeath); signal (SIGTRAP, TragicDeath); signal (SIGIOT, TragicDeath); signal (SIGBUS, TragicDeath); // signal(SIGFPE, TragicDeath); signal (SIGSEGV, TragicDeath); signal (SIGTERM, TragicDeath); // for debugging only XSynchronize (x_disp, True); x_disp_ref_count = 1; } else { x_disp_ref_count++; } } void X11_CloseDisplay (void) { if (nullcursor != None) { XFreeCursor (x_disp, nullcursor); nullcursor = None; } if (!--x_disp_ref_count) { XCloseDisplay (x_disp); x_disp = 0; } } /* X11_CreateNullCursor Create an empty cursor (in other words, make it disappear) */ void X11_CreateNullCursor (void) { Pixmap cursormask; XGCValues xgc; GC gc; XColor dummycolour; if (nullcursor != None) return; cursormask = XCreatePixmap (x_disp, x_root, 1, 1, 1); xgc.function = GXclear; gc = XCreateGC (x_disp, cursormask, GCFunction, &xgc); XFillRectangle (x_disp, cursormask, gc, 0, 0, 1, 1); dummycolour.pixel = 0; dummycolour.red = 0; dummycolour.flags = 04; nullcursor = XCreatePixmapCursor (x_disp, cursormask, cursormask, &dummycolour, &dummycolour, 0, 0); XFreePixmap (x_disp, cursormask); XFreeGC (x_disp, gc); XDefineCursor (x_disp, x_win, nullcursor); } void X11_ForceMove (int x, int y) { int nx, ny; if (!vid_context_created) return; XMoveWindow (x_disp, x_win, x, y); XFlush(x_disp); while (1) { XEvent ev; XMaskEvent(x_disp,StructureNotifyMask,&ev); if (ev.type==ConfigureNotify) { nx=ev.xconfigure.x; ny=ev.xconfigure.y; X11_ProcessEventProxy(&ev); break; } X11_ProcessEventProxy(&ev); } //X11_GetWindowCoords (&nx, &ny); nx -= x; ny -= y; if (nx == 0 || ny == 0) { return; } x -= nx; y -= ny; #if 0 // hopefully this isn't needed! enable if it is. if (x < 1 - vid.width) x=0; if (y < 1 - vid.height) y=0; #endif XMoveWindow (x_disp, x_win, x, y); XSync (x_disp, false); /*this is the best we can do. */ while (1) { XEvent ev; XMaskEvent(x_disp,StructureNotifyMask,&ev); if (ev.type==ConfigureNotify) { X11_ProcessEventProxy(&ev); break; } X11_ProcessEventProxy(&ev); } } void X11_SetVidMode (int width, int height) { const char *str = getenv ("MESA_GLX_FX"); if (vidmode_active) return; if (str && (tolower (*str) == 'f')) { Cvar_Set (vid_fullscreen, "1"); } #ifdef HAVE_VIDMODE if (!vidmode_avail) vidmode_avail = VID_CheckVMode (x_disp, NULL, NULL); if (vidmode_avail) { vid_gamma_avail = ((x_gamma = X11_GetGamma ()) > 0); } if (vid_fullscreen->int_val && vidmode_avail) { int i, dotclock; int best_mode = 0; qboolean found_mode = false; XF86VidModeModeLine orig_data; XF86VidModeGetAllModeLines (x_disp, x_screen, &nummodes, &vidmodes); XF86VidModeGetModeLine (x_disp, x_screen, &dotclock, &orig_data); for (i = 0; i < nummodes; i++) { if ((vidmodes[i]->hdisplay == orig_data.hdisplay) && (vidmodes[i]->vdisplay == orig_data.vdisplay)) { original_mode = i; break; } } for (i = 0; i < nummodes; i++) { if ((vidmodes[i]->hdisplay == vid.width) && (vidmodes[i]->vdisplay == vid.height)) { found_mode = true; best_mode = i; break; } } if (found_mode) { Con_DPrintf ("VID: Chose video mode: %dx%d\n", vid.width, vid.height); XF86VidModeSwitchToMode (x_disp, x_screen, vidmodes[best_mode]); X11_ForceViewPort (); vidmode_active = true; X11_SetScreenSaver (); } else { Con_Printf ("VID: Mode %dx%d can't go fullscreen.\n", vid.width, vid.height); vid_gamma_avail = vidmode_avail = vidmode_active = false; } } #endif } void X11_UpdateFullscreen (cvar_t *fullscreen) { if (!vid_context_created) { return; } if (!fullscreen->int_val) { X11_RestoreVidMode (); if (window_saved) { X11_ForceMove(window_x, window_y); window_saved = 0; } if (in_grab) { IN_UpdateGrab(in_grab); } return; } else { if (in_grab) { IN_UpdateGrab(in_grab); } if (X11_GetWindowCoords (&window_x, &window_y)) window_saved = 1; X11_SetVidMode (scr_width, scr_height); if (!vidmode_active) { if (in_grab) { IN_UpdateGrab(in_grab); } window_saved = 0; return; } if (in_grab) { IN_UpdateGrab(in_grab); } X11_ForceMove(0, 0); XWarpPointer(x_disp,None,x_win,0,0,0,0,vid.width/2,vid.height/2); X11_ForceViewPort (); /* Done in X11_SetVidMode but moved the window since then */ } } void X11_Init_Cvars (void) { vid_fullscreen = Cvar_Get ("vid_fullscreen", "0", CVAR_ARCHIVE, &X11_UpdateFullscreen, "Toggles fullscreen game mode"); vid_system_gamma = Cvar_Get ("vid_system_gamma", "1", CVAR_ARCHIVE, NULL, "Use system gamma control if available"); sys_dump_core = Cvar_Get ("sys_dump_core", "0", CVAR_NONE, dump_core_callback, "Dump core on Tragic Death. Be sure to check 'ulimit -c'"); sys_backtrace = Cvar_Get ("sys_backtrace", "0", CVAR_NONE, backtrace_callback, "Dump a backtrace on Tragic Death. Value is the max number of times to dump core incase of recursive shutdown"); } void X11_CreateWindow (int width, int height) { XSetWindowAttributes attr; XClassHint *ClassHint; XSizeHints *SizeHints; char *resname; unsigned long mask; /* window attributes */ attr.background_pixel = 0; attr.colormap = XCreateColormap (x_disp, x_root, x_vis, AllocNone); attr.event_mask = X_MASK; mask = CWBackPixel | CWColormap | CWEventMask; x_win = XCreateWindow (x_disp, x_root, 0, 0, width, height, 0, x_visinfo->depth, InputOutput, x_vis, mask, &attr); // Set window size hints SizeHints = XAllocSizeHints (); if (SizeHints) { SizeHints->flags = (PMinSize | PMaxSize); SizeHints->min_width = width; SizeHints->min_height = height; SizeHints->max_width = width; SizeHints->max_height = height; XSetWMNormalHints (x_disp, x_win, SizeHints); XFree (SizeHints); } // Set window title X11_SetCaption (va ("%s %s", PROGRAM, VERSION)); // Set icon name XSetIconName (x_disp, x_win, PROGRAM); // Set window class ClassHint = XAllocClassHint (); if (ClassHint) { resname = strrchr (com_argv[0], '/'); ClassHint->res_name = (resname ? resname + 1 : com_argv[0]); ClassHint->res_class = PACKAGE; XSetClassHint (x_disp, x_win, ClassHint); XFree (ClassHint); } // Make window respond to Delete events aWMDelete = XInternAtom (x_disp, "WM_DELETE_WINDOW", False); XSetWMProtocols (x_disp, x_win, &aWMDelete, 1); XMapWindow (x_disp, x_win); while (1) { XEvent ev; XMaskEvent(x_disp,StructureNotifyMask,&ev); if (ev.type==MapNotify) { X11_ProcessEventProxy(&ev); break; } X11_ProcessEventProxy(&ev); } vid_context_created = true; if (vid_fullscreen->int_val) { X11_UpdateFullscreen(vid_fullscreen); } XRaiseWindow (x_disp, x_win); } void X11_RestoreVidMode (void) { #ifdef HAVE_VIDMODE if (vidmode_active) { X11_RestoreScreenSaver (); X11_RestoreGamma (); XF86VidModeSwitchToMode (x_disp, x_screen, vidmodes[original_mode]); XFree (vidmodes); vidmode_active=false; } #endif } void X11_GrabKeyboardBool(qboolean yes) { static qboolean is_grabbed=false; if (yes) { if (!is_grabbed) { if (XGrabKeyboard (x_disp, x_win, 1, GrabModeAsync, GrabModeAsync, CurrentTime)==GrabSuccess) { is_grabbed=true; XSetInputFocus(x_disp,x_win, RevertToPointerRoot,CurrentTime); } } } else { XUngrabKeyboard (x_disp, CurrentTime); is_grabbed=false; } } void X11_GrabKeyboard (void) { XGrabKeyboard (x_disp, x_win, 1, GrabModeAsync, GrabModeAsync, CurrentTime); XSetInputFocus(x_disp,x_win, RevertToPointerRoot,CurrentTime); } void X11_GrabMouseBool(qboolean yes) { static qboolean is_grabbed=false; if (yes) { if (!is_grabbed) { if (XGrabPointer (x_disp, x_win, True, MOUSE_MASK, GrabModeAsync, GrabModeAsync, x_win, None, CurrentTime)==GrabSuccess) { is_grabbed=true; } } } else { XUngrabPointer (x_disp, CurrentTime); is_grabbed=false; XWarpPointer(x_disp,x_win,x_win,0,0,0,0,vid.width/2,vid.height/2); } } void X11_GrabMouse(void) { XGrabPointer (x_disp, x_win, True, MOUSE_MASK, GrabModeAsync, GrabModeAsync, x_win, None, CurrentTime); } void X11_UngrabMouse(void) { XUngrabPointer(x_disp,CurrentTime); XWarpPointer(x_disp,x_win,x_win,0,0,0,0,vid.width/2,vid.height/2); } void X11_UngrabKeyboard (void) { XUngrabKeyboard (x_disp, CurrentTime); } void X11_Grabber(qboolean grab) { #if 0 static qboolean is_grabbed=false; #endif if (!vid_context_created) { Con_Printf("No video context to grab to!\n"); return; } X11_GrabMouseBool(grab); X11_GrabKeyboardBool(grab); #if 0 if (grab) { if (!is_grabbed) { X11_GrabKeyboard(); X11_GrabMouse(); is_grabbed=true; } } else { if (is_grabbed) { X11_UngrabKeyboard(); X11_UngrabMouse(); is_grabbed=false; } } #endif XSync(x_disp,false); } void X11_SetCaption (const char *text) { if (x_disp && x_win && text) XStoreName (x_disp, x_win, text); } qboolean X11_GetWindowCoords (int *ax, int *ay) { #ifdef HAVE_VIDMODE Window theroot, scrap; int x, y; unsigned int width, height, bdwidth, depth; XSync(x_disp,false); if ((XGetGeometry (x_disp, x_win, &theroot, &x, &y, &width, &height, &bdwidth, &depth) == False)) { Con_Printf ("XGetWindowAttributes failed in X11_GetWindowCoords.\n"); return false; } else { XTranslateCoordinates (x_disp,x_win,theroot, -bdwidth, -bdwidth, ax, ay, &scrap); Con_DPrintf ("Window coords = %dx%d (%d,%d)\n", *ax, *ay, width, height); return true; } #endif return false; } void X11_ForceViewPort (void) { #ifdef HAVE_VIDMODE int ax, ay; if (!vidmode_avail || !vid_context_created) return; if (!X11_GetWindowCoords (&ax, &ay)) { /* "icky kludge code" */ Con_Printf ("VID: Falling back on warp kludge to set viewport.\n"); XWarpPointer (x_disp, None, x_win, 0, 0, 0, 0, scr_width, scr_height); XWarpPointer (x_disp, None, x_win, 0, 0, 0, 0, 0, 0); } XF86VidModeSetViewPort (x_disp, x_screen, ax, ay); #endif } double X11_GetGamma (void) { #ifdef HAVE_VIDMODE # ifdef X_XF86VidModeGetGamma XF86VidModeGamma xgamma; if (vidmode_avail && vid_system_gamma->int_val) { if (XF86VidModeGetGamma (x_disp, x_screen, &xgamma)) { return ((xgamma.red + xgamma.green + xgamma.blue) / 3); } } # endif #endif return -1.0; } qboolean X11_SetGamma (double gamma) { #ifdef HAVE_VIDMODE # ifdef X_XF86VidModeSetGamma XF86VidModeGamma xgamma; if (vid_gamma_avail && vid_system_gamma->int_val) { xgamma.red = xgamma.green = xgamma.blue = (float) gamma; if (XF86VidModeSetGamma (x_disp, x_screen, &xgamma)) return true; } # endif #endif return false; } void X11_RestoreGamma (void) { #ifdef HAVE_VIDMODE # ifdef X_XF86VidModeSetGamma XF86VidModeGamma xgamma; if (vid_gamma_avail) { xgamma.red = xgamma.green = xgamma.blue = (float) x_gamma; XF86VidModeSetGamma (x_disp, x_screen, &xgamma); } # endif #endif } void X11_SetScreenSaver (void) { XGetScreenSaver (x_disp, &xss_timeout, &xss_interval, &xss_blanking, &xss_exposures); XSetScreenSaver (x_disp, 0, xss_interval, xss_blanking, xss_exposures); } void X11_RestoreScreenSaver (void) { XSetScreenSaver (x_disp, xss_timeout, xss_interval, xss_blanking, xss_exposures); }