/* Emacs style mode select -*- C++ -*- *----------------------------------------------------------------------------- * * * X11 display code for LxDoom. Based on the original linuxdoom i_video.c * Copyright (C) 1993-1996 by id Software * Copyright (C) 1999-2000 by Colin Phipps (cph@lxdoom.linuxgames.com) * * 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 the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * DESCRIPTION: * DOOM graphics stuff for X11, UNIX. * *----------------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include #include "i_system.h" #include "m_argv.h" #include "doomstat.h" #include "doomdef.h" #include "doomtype.h" #include "v_video.h" #include "r_draw.h" #include "d_main.h" #include "d_event.h" #include "v_video_trans.h" #include "i_joy.h" #include "i_video.h" #include "z_zone.h" #include "s_sound.h" #include "sounds.h" #include "w_wad.h" #include "lprintf.h" /* uncomment to force DGA off */ /* #undef HAVE_LIBXXF86DGA */ #ifdef HAVE_LIBXEXT // CPhipps - if available, use MITShm X extension #include #include #include // Had to dig up XShm.c for this one. // It is in the libXext, but not in the XFree86 headers. int XShmGetEventBase( Display* dpy ); // problems with g++? int XShmQueryExtension(Display* dpy); // CP added #endif #ifdef HAVE_LIBXXF86DGA #include #endif void M_QuitDOOM(int choice); int use_vsync = 0; // Included not to break m_misc, but not relevant to X static Display* X_display=NULL; static int X_screen; static Window X_mainWindow; static Colormap X_cmap; static Visual* X_visual; static GC X_gc; static XEvent X_event; static XImage* image=NULL; static int X_width; static int X_height; static Atom X_deletewin; // This is the pointer to the buffer to blit pval * out_buffer = NULL; #define INITIALISED (X_display != NULL) #ifdef HAVE_LIBXXF86DGA // xf86 DGA extension static boolean doDga; static int DGA_real_width, DGA_pagelen, DGA_memlen; static int DGA_width, DGA_height; #endif #ifdef HAVE_LIBXEXT // MIT SHared Memory extension. static boolean doShm; static XShmSegmentInfo X_shminfo; static int X_shmeventtype; #endif // Common const strings static const char lcase_lxdoom[] = { "lxdoom" }; static const char ucase_lxdoom[] = { "LXDOOM" }; //////////////////////////////////////////////////////////////////////////// // Input code // POLL_POINTER means do a XQueryPointer once pre frame, to measure // mouse motion. The alternative is an XGrabPointer which instructs // XFree86 to send events when the pointer moves - however X seems to // buffer these events, making it unusable #define POLL_POINTER // AGGR_FOCUS is short for 'aggressive focusing'. That means that // whenever the mouse is grabbed, grab the focus too. The alternative // is passive focusing, which queries if we have the input focus and // sets the mouse grab state to match. // AGGR_FOCUS is more likely to be asynchronous, but could make it // harder to get focus away from lxdoom in some window managers. // #define AGGR_FOCUS // Event handling static event_t event; // CPhipps - static because several subs need it // so save having several scattered around static boolean shmFinished = true; // Are we awaiting an MITShm completion? static int vis_flag = VisibilityUnobscured; // Is lxdoom visible? extern int X_opt; // Options to change the way we use X // it's a bitfield, currently 1 = no MitSHM, 2 = pretend 24bpp is 32bpp int X_bpp; // bpp that we tell X // Mouse handling extern int usemouse; // config file var int use_fullscreen; /* cph - config file dummy */ static boolean grabMouse; // internal var static boolean grabbed = false; // Is the mouse currently grabbed // Little struct to keep things together typedef struct { int x, y; } mouse_t; static mouse_t lastmouse; // Mouse status last time static mouse_t newmouse; // Mouse status just queried #ifndef POLL_POINTER static int buttons; #endif ///////////////////////////////////////////////////////////////////////////////// // Keyboard handling // // Translates the key currently in X_event // static int I_XTranslateKey(XEvent* pEvent) { int rc; switch(rc = XKeycodeToKeysym(X_display, pEvent->xkey.keycode, 0)) { #ifdef NETWINDER /* CPhipps - Jamie's hacks to work with NetWinder X server. */ case 65430: rc = KEYD_LEFTARROW; break; case 65432: rc = KEYD_RIGHTARROW; break; case 65433: rc = KEYD_DOWNARROW; break; case 65431: rc = KEYD_UPARROW; break; #endif case XK_Left: rc = KEYD_LEFTARROW; break; case XK_Right: rc = KEYD_RIGHTARROW; break; case XK_Down: rc = KEYD_DOWNARROW; break; case XK_Up: rc = KEYD_UPARROW; break; case XK_Escape: rc = KEYD_ESCAPE; break; case XK_Return: rc = KEYD_ENTER; break; case XK_Tab: rc = KEYD_TAB; break; case XK_F1: rc = KEYD_F1; break; case XK_F2: rc = KEYD_F2; break; case XK_F3: rc = KEYD_F3; break; case XK_F4: rc = KEYD_F4; break; case XK_F5: rc = KEYD_F5; break; case XK_F6: rc = KEYD_F6; break; case XK_F7: rc = KEYD_F7; break; case XK_F8: rc = KEYD_F8; break; case XK_F9: rc = KEYD_F9; break; case XK_F10: rc = KEYD_F10; break; case XK_F11: rc = KEYD_F11; break; case XK_F12: rc = KEYD_F12; break; case XK_BackSpace: case XK_Delete: rc = KEYD_BACKSPACE; break; case XK_Pause: rc = KEYD_PAUSE; break; case XK_equal: rc = KEYD_EQUALS; break; case XK_minus: rc = KEYD_MINUS; break; case XK_Shift_L: case XK_Shift_R: rc = KEYD_RSHIFT; break; case XK_Control_L: case XK_Control_R: rc = KEYD_RCTRL; break; case XK_Alt_L: case XK_Meta_L: case XK_Alt_R: case XK_Meta_R: rc = KEYD_RALT; break; case XK_KP_0: case XK_KP_Insert: rc = KEYD_KEYPAD0; break; case XK_KP_1: case XK_KP_End: rc = KEYD_KEYPAD1; break; case XK_KP_2: case XK_KP_Down: rc = KEYD_KEYPAD2; break; case XK_KP_3: case XK_KP_Page_Down: rc = KEYD_KEYPAD3; break; case XK_KP_4: case XK_KP_Left: rc = KEYD_KEYPAD4; break; case XK_KP_5: rc = KEYD_KEYPAD0; break; case XK_KP_6: case XK_KP_Right: rc = KEYD_KEYPAD6; break; case XK_KP_7: case XK_KP_Home: rc = KEYD_KEYPAD7; break; case XK_KP_8: case XK_KP_Up: rc = KEYD_KEYPAD8; break; case XK_KP_9: case XK_KP_Page_Up: rc = KEYD_KEYPAD9; break; case XK_KP_Add: rc = KEYD_KEYPADPLUS; break; case XK_KP_Subtract: rc = KEYD_KEYPADMINUS; break; case XK_KP_Divide: rc = KEYD_KEYPADDIVIDE; break; case XK_KP_Multiply: rc = KEYD_KEYPADMULTIPLY; break; case XK_KP_Enter: rc = KEYD_KEYPADENTER; break; default: if (rc >= XK_space && rc <= XK_asciitilde) rc = rc - XK_space + ' '; if (rc >= 'A' && rc <= 'Z') rc = rc - 'A' + 'a'; break; } return rc; } // Null keyboard translation to satisfy m_misc.c int I_DoomCode2ScanCode(int c) { return c; } int I_ScanCode2DoomCode(int c) { return c; } //////////////////////////////////////////////////////////////////////////////// // Mouse handling static boolean I_QueryPointer(mouse_t* p, unsigned int* pmask) { int rootx, rooty; Window root, child; mouse_t altmouse; unsigned int altmask; // If NULL parameter, use a dummy instead if (p == NULL) p = &altmouse; if (pmask == NULL) pmask = &altmask; return (XQueryPointer(X_display, X_mainWindow, &root, &child, &rootx, &rooty, &(p->x), &(p->y), pmask) ? true : false); } static void I_VerifyPointerGrabState(void) { #ifndef AGGR_FOCUS boolean focused; // Are we the focused window? { Window fwin = None; int revert_to = 0; XGetInputFocus(X_display, &fwin, &revert_to); // WARNING: non-portable? focused = (fwin == X_mainWindow) ? true : false; } #endif if (!grabMouse) return; // CPhipps - do not grab the mouse if paused // or not playing a level or the window is obscured // or during demo playback // DO grab in menus, because needed for key bindings screen if (paused || (gamestate != GS_LEVEL) || (vis_flag != VisibilityUnobscured) #ifndef AGGR_FOCUS || !focused #endif || demoplayback) { if (grabbed) { // Release the mouse XUngrabPointer(X_display, CurrentTime); // Post a Doom event saying there is no movement and no buttons event.type = ev_mouse; event.data1 = event.data2 = event.data3 = 0; } grabbed = false; } else { #ifdef AGGR_FOCUS XSetInputFocus(X_display, X_mainWindow, RevertToParent, CurrentTime); #endif // grabs the pointer so it is restricted to this window if (!grabbed && (vis_flag == VisibilityUnobscured)) { XGrabPointer(X_display, X_mainWindow, True, #ifndef POLL_POINTER ButtonPressMask|ButtonReleaseMask|PointerMotionMask, #else 0, #endif GrabModeAsync, GrabModeAsync, X_mainWindow, None, CurrentTime); I_QueryPointer(&newmouse, NULL); } grabbed = true; /* Warp the pointer back to the middle of the window * or it could end up stuck on an edge * Only warping if needed */ if (D_abs(newmouse.x - X_width/2) > (X_width/2 - 32) || D_abs(newmouse.y - X_height/2) > (X_height/2 - 20)) { /*mead allow larger deltas by preserving pre-warp portion.*/ lastmouse.x = (X_width/2 - (newmouse.x - lastmouse.x)); lastmouse.y = (X_height/2 - (newmouse.y - lastmouse.y)); XWarpPointer( X_display, None, X_mainWindow, 0, 0, 0, 0, X_width/2, X_height/2); } else { lastmouse = newmouse; } } } ///////////////////////////////////////////////////////////////////////////////// // Main input code static void I_GetEvent(void) { // CPhipps - make this local XEvent X_event; // put event-grabbing stuff in here XNextEvent(X_display, &X_event); switch (X_event.type) { case KeyPress: event.type = ev_keydown; event.data1 = I_XTranslateKey(&X_event); D_PostEvent(&event); // fprintf(stderr, "k"); break; case KeyRelease: event.type = ev_keyup; event.data1 = I_XTranslateKey(&X_event); D_PostEvent(&event); // fprintf(stderr, "ku"); break; #ifndef POLL_POINTER case ButtonPress: event.data2 = event.data3 = 0; event.type = ev_mouse; buttons = event.data1 = (X_event.xbutton.state & Button1Mask ? 1 : 0) | (X_event.xbutton.state & Button2Mask ? 2 : 0) | (X_event.xbutton.state & Button3Mask ? 4 : 0) | (X_event.xbutton.button == Button1 ? 1 : 0) | (X_event.xbutton.button == Button2 ? 2 : 0) | (X_event.xbutton.button == Button3 ? 4 : 0); D_PostEvent(&event); break; case ButtonRelease: event.data2 = event.data3 = 0; event.type = ev_mouse; event.data1 = (X_event.xbutton.state & Button1Mask ? 1 : 0) | (X_event.xbutton.state & Button2Mask ? 2 : 0) | (X_event.xbutton.state & Button3Mask ? 4 : 0); // suggest parentheses around arithmetic in operand of | buttons = event.data1 = event.data1 ^ (X_event.xbutton.button == Button1 ? 1 : 0) ^ (X_event.xbutton.button == Button2 ? 2 : 0) ^ (X_event.xbutton.button == Button3 ? 4 : 0); D_PostEvent(&event); break; case MotionNotify: event.type = ev_mouse; buttons = (X_event.xmotion.state & Button1Mask ? 1 : 0) | (X_event.xmotion.state & Button2Mask ? 2 : 0) | (X_event.xmotion.state & Button3Mask ? 4 : 0); newmousex = X_event.xmotion.x; newmousey = X_event.xmotion.y; break; #endif #ifdef MONITOR_VISIBILITY case VisibilityNotify: vis_flag = X_event.xvisibility.state; break; #endif case ClientMessage: // CPhipps - allow WM quit if (X_event.xclient.data.l[0] == X_deletewin) { S_StartSound(NULL, sfx_swtchn); M_QuitDOOM(0); } break; case Expose: case ConfigureNotify: break; default: #ifdef HAVE_LIBXEXT if (doShm && X_event.type == X_shmeventtype) shmFinished = true; #endif break; } } #ifdef HAVE_LIBXEXT // // I_WaitShm // static void I_WaitShm(void) { while (!shmFinished) { if (XPending(X_display)) I_GetEvent(); else usleep(10000); } } #endif // // I_StartTic // void I_StartTic (void) { if (!X_display) return; #ifndef POLL_POINTER newmouse = lastmouse; #endif while (XPending(X_display)) I_GetEvent(); if (grabbed) { #ifndef POLL_POINTER if (newmouse.y != lastmouse.y || newmouse.x != lastmouse.x) { event.type = ev_mouse; event.data1 = buttons; #else unsigned int mask; if (I_QueryPointer(&newmouse, &mask)) { // It only reaches here if pointer is on our screen event.data1 = ((mask & Button1Mask) ? 1 : 0) | ((mask & Button2Mask) ? 2 : 0) | ((mask & Button3Mask) ? 4 : 0); #endif event.type = ev_mouse; event.data2 = (newmouse.x - lastmouse.x) << 5; /*mead: make lxdoom move */ event.data3 = (lastmouse.y - newmouse.y) << 5; /* more like lsdoom */ D_PostEvent(&event); } } I_VerifyPointerGrabState(); I_PollJoystick(); } // // I_StartFrame // void I_StartFrame (void) { } // // I_XInitInputs // static void I_XInitInputs(void) { // Make sure we have focus XSetInputFocus(X_display, X_mainWindow, RevertToParent, CurrentTime); // check if the user wants to grab the mouse (quite unnice) grabMouse = M_CheckParm("-nomouse") ? false : usemouse ? true : false; I_VerifyPointerGrabState(); I_InitJoystick(); } ///////////////////////////////////////////////////////////////////////////// // I_SkipFrame // // Returns true if it thinks we can afford to skip this frame inline static boolean I_SkipFrame(void) { static int frameno; frameno++; switch (gamestate) { case GS_LEVEL: if (!paused) return false; default: // Skip odd frames return (frameno & 1) ? true : false; } } /////////////////////////////////////////////////////////// // Palette stuff. // static void I_UploadNewPalette(int pal) { // This is used to replace the current 256 colour cmap with a new one // Used by 256 colour PseudoColor modes // Array of XColor structs used for setting the 256-colour palette static XColor* colours; static int cachedgamma; static size_t num_pals; if ((colours == NULL) || (cachedgamma != usegamma)) { int lump = W_GetNumForName("PLAYPAL"); const byte *palette = W_CacheLumpNum(lump); register const byte *const gtable = gammatable[cachedgamma = usegamma]; register int i; num_pals = W_LumpLength(lump) / (3*256); num_pals *= 256; if (!colours) { // First call - allocate and prepare colour array colours = malloc(sizeof(*colours)*num_pals); for (i=0 ; i= num_pals) I_Error("I_UploadNewPalette: Palette number out of range (%d>=%d)", pal, num_pals); #endif // store the colors to the current colormap XStoreColors(X_display, X_cmap, colours + 256*pal, 256); #ifdef HAVE_LIBXXF86DGA /* install DGA colormap */ if(doDga) XF86DGAInstallColormap(X_display, X_screen, X_cmap); #endif } ///////////////////////////////////////////////////////////////////////// // Internal helper functions static Cursor I_XCreateNullCursor( Display* display, Window root ) { Pixmap cursormask; XGCValues xgc; GC gc; XColor dummycolour; Cursor cursor; cursormask = XCreatePixmap(display, root, 1, 1, 1); // NOTE: depth in the above call is 1 because the image is monochrone, // i.e a mask xgc.function = GXclear; gc = XCreateGC(display, cursormask, GCFunction, &xgc); XFillRectangle(display, cursormask, gc, 0, 0, 1, 1); dummycolour.pixel = 0; dummycolour.red = 0; dummycolour.flags = 04; cursor = XCreatePixmapCursor(display, cursormask, cursormask, &dummycolour,&dummycolour, 0,0); XFreePixmap(display,cursormask); XFreeGC(display,gc); return cursor; } #ifdef HAVE_LIBXEXT // // This function is probably redundant, // if XShmDetach works properly. // ddt never detached the XShm memory, // thus there might have been stale // handles accumulating. // static void I_XShmGrabSharedMemory(size_t size) { int key = ('d'<<24) | ('o'<<16) | ('o'<<8) | 'm'; struct shmid_ds shminfo; int minsize = 320*200; int id; int rc; // UNUSED int done=0; int pollution=5; // try to use what was here before do { id = shmget((key_t) key, minsize, 0777); // just get the id if (id != -1) { rc=shmctl(id, IPC_STAT, &shminfo); // get stats on it if (!rc) { if (shminfo.shm_nattch) { fprintf(stderr, "User %d appears to be running " "DOOM. Is that wise?\n", shminfo.shm_cpid); key++; } else { if (getuid() == shminfo.shm_perm.cuid) { rc = shmctl(id, IPC_RMID, 0); if (!rc) fprintf(stderr, "Was able to kill my old shared memory\n"); else I_Error("Was NOT able to kill my old shared memory"); id = shmget((key_t)key, size, IPC_CREAT|0777); if (id==-1) I_Error("Could not get shared memory"); rc=shmctl(id, IPC_STAT, &shminfo); break; } if (size >= shminfo.shm_segsz) { fprintf(stderr, "will use %d's stale shared memory\n", shminfo.shm_cpid); break; } else { fprintf(stderr, "warning: can't use stale " "shared memory belonging to id %d, " "key=0x%x\n", shminfo.shm_cpid, key); key++; } } }else { I_Error("could not get stats on key=%d", key); } } else { id = shmget((key_t)key, size, IPC_CREAT|0777); if (id==-1) { extern int errno; fprintf(stderr, "errno=%d\n", errno); I_Error("Could not get any shared memory"); } break; } } while (--pollution); if (!pollution) { I_Error("Sorry, system too polluted with stale shared memory segments.\n"); } X_shminfo.shmid = id; // attach to the shared memory segment image->data = X_shminfo.shmaddr = shmat(id, 0, 0); // fprintf(stderr, "shared memory id=%d, addr=0x%x\n", id, (int) (image->data)); } #endif ////////////////////////////////////////////////////////////////// // Mode querying // // I_FindMode // // Scans the table above, finding a supported mode // static void I_PrintMode(const char* s) { fprintf(stderr, "%s %d bpp %s, scale x%d\n", s, BITSPP, true_color ? "TrueColor" : "PseudoColor", multiply); } static Visual* I_FindMode(int screen) { XVisualInfo X_visualinfo; int i; const int nice_depths[]={ 8, 16, 15, 32, 24, 0}; // 0 terminated XPixmapFormatValues *pfv; int npfv; fprintf(stderr, "I_FindMode: "); for (i=0; nice_depths[i]!=0; i++) { dest_bpp = nice_depths[i]; if (I_QueryImageTranslation()) { if (XMatchVisualInfo(X_display, screen, dest_bpp, true_color ? TrueColor : PseudoColor, &X_visualinfo)) break; if (devparm) I_PrintMode("no visual like:"); } else if (devparm) I_PrintMode("no translation to:"); } if (nice_depths[i] == 0) I_Error("Unable to find supported visual settings"); X_bpp = dest_bpp; if ((dest_bpp == 24) && (X_opt & 2)) dest_bpp = 32; // kludge for 24bpp /* find out whether depth 24 is sparse or packed */ pfv=XListPixmapFormats(X_display,&npfv); if(pfv) { for(i=0;idata = NULL; } else #endif { if (multiply != 1) { free(image->data); } image->data = NULL; // Prevent server trying to free image data XDestroyImage(image); fprintf(stderr, "Released XImage memory\n"); } XDestroyWindow(X_display, X_mainWindow); XCloseDisplay(X_display); X_display = NULL; } // // I_UpdateNoBlit // void I_UpdateNoBlit (void) { } // // I_FinishUpdate // void I_FinishUpdate (void) { // Protect against calls before I_InitGraphics if (!INITIALISED || I_SkipFrame()) return; #ifdef MONITOR_VISIBILITY if (vis_flag == VisibilityFullyObscured) { return; } #endif #ifdef HAVE_LIBXEXT if (doShm && expand_buffer) I_WaitShm(); #endif // scales the screen size before blitting it if (expand_buffer) (*I_ExpandImage)(out_buffer, screens[0]); #ifdef HAVE_LIBXXF86DGA if (doDga) { int i; for(i=0;ibytes_per_line * image->height); if (!(out_buffer = (pval*)image->data)) { perror(""); I_Error("shmat() failed in I_InitGraphics()"); } // get the X server to attach to it if (!XShmAttach(X_display, &X_shminfo)) I_Error("XShmAttach() failed in InitGraphics()"); if (!expand_buffer) { // Render directly into MitSHM memory Z_Free(screens[0]); screens[0] = (unsigned char *) (image->data); } } else #endif { if (!expand_buffer) { // Very efficient, render directly into image out_buffer = (pval*)screens[0]; } else { // Will have to enlarge from rendering buffer // into image buffer out_buffer = (pval*)malloc (X_width * X_height * BYTESPP); } image=XCreateImage( X_display, X_visual, X_bpp, ZPixmap, 0, (char*)out_buffer, X_width, X_height, X_bpp, 0); if (!image) I_Error("XCreateImage: failed to create image %dx%d %d bpp", X_width, X_height, X_bpp); } atexit(I_ShutdownGraphics); I_XInitInputs(); }