/* * $Id: x11.c,v 1.32 2005/01/14 18:58:03 phil Exp $ * X11 support for XY display simulator * Phil Budne * September 2003 * * Changes from Douglas A. Gwyn, Jan 8, 2004 * * started from PDP-8/E simulator (vc8e.c & kc8e.c); * This PDP8 Emulator was written by Douglas W. Jones at the * University of Iowa. It is distributed as freeware, of * uncertain function and uncertain utility. */ /* * Copyright (c) 2003-2018, Philip L. Budne * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Except as contained in this notice, the names of the authors shall * not be used in advertising or otherwise to promote the sale, use or * other dealings in this Software without prior written authorization * from the authors. */ #ifndef USE_XKB #define USE_XKB 1 #endif #include #include #include #include "ws.h" #include "display.h" #include #include #include #include #include #include #include #ifdef USE_XKB #include #endif #include #include #ifndef PIX_SIZE #define PIX_SIZE 1 #endif /* * light pen location * see ws.h for full description */ int ws_lp_x = -1; int ws_lp_y = -1; static XtAppContext app_context; /* the topmost context for everything */ static Display* dpy; /* its display */ static int scr; /* its screen */ static Colormap cmap; /* its colormap */ static Widget crtshell; /* the X window shell */ static Widget crt; /* the X window in which output will plot */ static int xpixels, ypixels; #ifdef FULL_SCREEN /* occupy entire screen for vintage computer fan Sellam Ismail */ static int xoffset, yoffset; #endif static GC whiteGC; /* gc with white foreground */ static GC blackGC; /* gc with black foreground */ static int buttons = 0; /* tracks state of all buttons */ static int os_pollfd(int, int); /* forward */ /* here on any mouse button down, AND movement when any button down */ static void handle_button_press(w, d, e, b) Widget w; XtPointer d; XEvent *e; Boolean *b; { int x, y; x = e->xbutton.x; y = e->xbutton.y; #ifdef FULL_SCREEN /* untested! */ x -= xoffset; y -= yoffset; #endif #if PIX_SIZE > 1 x *= PIX_SIZE; y *= PIX_SIZE; #endif if (!display_tablet) /* crosshair cursor to indicate tip of active pen */ XDefineCursor(dpy, XtWindow(crt), (Cursor) XCreateFontCursor(dpy, XC_crosshair)); y = ypixels - y - 1; /*printf("lightpen at %d,%d\n", x, y); fflush(stdout);*/ ws_lp_x = x; ws_lp_y = y; if (e->type == ButtonPress) { buttons |= e->xbutton.button; if (e->xbutton.button == 1) { display_lp_sw = 1; /*printf("tip switch activated\n"); fflush(stdout);*/ } } if (b) *b = TRUE; } static void handle_button_release(w, d, e, b) Widget w; XtPointer d; XEvent *e; Boolean *b; { if ((buttons &= ~e->xbutton.button) == 0) { /* all buttons released */ if (!display_tablet) /* pencil cursor (close to a pen!) to indicate inactive pen posn */ XDefineCursor(dpy, XtWindow(crt), (Cursor) XCreateFontCursor(dpy, XC_pencil)); /* XXX change cursor back?? */ ws_lp_x = ws_lp_y = -1; } if (e->xbutton.button == 1) { display_lp_sw = 0; /*printf("tip switch deactivated\n"); fflush(stdout);*/ } if (b) *b = TRUE; } /* * map keyboard XEvent to 8 bit char or -1 */ static int mapkey(e) XEvent *e; { int shift = (ShiftMask & e->xkey.state) != 0; KeySym key; #ifdef USE_XKB /* * X Keyboard Extension * described in * https://www.x.org/releases/X11R7.7/doc/libX11/XKB/xkblib.html#Xkb_Keyboard_Extension_Support_for_Keyboards * copyright 1995, 1996 * * use XkbLibraryVersion and/or XkbQueryExtension * to determine if available??? */ key = XkbKeycodeToKeysym(dpy, e->xkey.keycode, 0, shift); #elif 1 /* * documented in * Xlib: C Language X Interface (X Version 11, Release 4) * copyright 1989 */ int keysyms_per_keycode_return; KeySym *keysyms = XGetKeyboardMapping(dpy, e->xkey.keycode, 1, &keysyms_per_keycode_return); key = keysyms[0]; XFree(keysyms); #else /* just in case... */ /* XKeycodeToKeysym deprecated */ key = XKeycodeToKeysym(dpy, e->xkey.keycode, shift); #endif if ((key & 0xff00) == 0) return key & 0xff; switch (key) { case XK_Return: return '\r'; } /* printf("ignoring keycode %#x\r\n", key); /**/ return -1; } static void handle_key_press(w, d, e, b) Widget w; XtPointer d; XEvent *e; Boolean *b; { int k = mapkey(e); if (k >= 0) display_keydown(k); if (b) *b = TRUE; } static void handle_key_release(w, d, e, b) Widget w; XtPointer d; XEvent *e; Boolean *b; { int k = mapkey(e); if (k >= 0) display_keyup(k); if (b) *b = TRUE; } static void handle_exposure(w, d, e, b) Widget w; XtPointer d; XEvent *e; Boolean *b; { display_repaint(); if (b) *b = TRUE; } int ws_init(const char *crtname, /* crt type name */ int xp, int yp, /* screen size in pixels */ int colors, /* colors to support (not used) */ void *dptr) { Arg arg[25]; XGCValues gcvalues; unsigned int n; int argc; char *argv[1]; int height, width; #ifdef USE_XKB Bool supported; #endif xpixels = xp; /* save screen size */ ypixels = yp; if (getenv ("DISPLAY") == NULL) { return 0; } XtToolkitInitialize(); app_context = XtCreateApplicationContext(); argc = 0; argv[0] = NULL; dpy = XtOpenDisplay( app_context, NULL, NULL, crtname, NULL, 0, &argc, argv); #ifdef USE_XKB /* * suppress synthetic key up events from autorepeat * (will still see repeated down events) * see keymap function for XKb history */ supported = False; (void) XkbSetDetectableAutoRepeat(dpy, True, &supported); #endif scr = DefaultScreen(dpy); crtshell = XtAppCreateShell( crtname, /* app name */ crtname, /* app class */ applicationShellWidgetClass, /* wclass */ dpy, /* display */ NULL, /* arglist */ 0); /* nargs */ cmap = DefaultColormap(dpy, scr); /* * Create a drawing area */ n = 0; #ifdef FULL_SCREEN /* center raster in full-screen black window */ width = DisplayWidth(dpy,scr); height = DisplayHeight(dpy,scr); xoffset = (width - xpixels*PIX_SIZE)/2; yoffset = (height - ypixels*PIX_SIZE)/2; #else width = xpixels*PIX_SIZE; height = ypixels*PIX_SIZE; #endif XtSetArg(arg[n], XtNwidth, width); n++; XtSetArg(arg[n], XtNheight, height); n++; XtSetArg(arg[n], XtNbackground, BlackPixel( dpy, scr )); n++; crt = XtCreateWidget( crtname, widgetClass, crtshell, arg, n); XtManageChild(crt); XtPopup(crtshell, XtGrabNonexclusive); XtSetKeyboardFocus(crtshell, crt); /* experimental? */ /* * Create black and white Graphics Contexts */ gcvalues.foreground = BlackPixel( dpy, scr ); gcvalues.background = BlackPixel( dpy, scr ); blackGC = XCreateGC(dpy, XtWindow(crt), GCForeground | GCBackground, &gcvalues); gcvalues.foreground = WhitePixel( dpy, scr ); whiteGC = XCreateGC(dpy, XtWindow(crt), GCForeground | GCBackground, &gcvalues); if (!display_tablet) { /* pencil cursor */ XDefineCursor(dpy, XtWindow(crt), (Cursor) XCreateFontCursor(dpy, XC_pencil)); } /* * Setup to handle events */ XtAddEventHandler(crt, ButtonPressMask|ButtonMotionMask, FALSE, handle_button_press, NULL); XtAddEventHandler(crt, ButtonReleaseMask, FALSE, handle_button_release, NULL); XtAddEventHandler(crt, KeyPressMask, FALSE, handle_key_press, NULL); XtAddEventHandler(crt, KeyReleaseMask, FALSE, handle_key_release, NULL); XtAddEventHandler(crt, ExposureMask, FALSE, handle_exposure, NULL); return 1; } /* ws_init */ void ws_shutdown (void) { } void * ws_color_black(void) { return blackGC; } void * ws_color_white(void) { return whiteGC; } void * ws_color_rgb(int r, int g, int b) { XColor color; color.red = r; color.green = g; color.blue = b; /* ignores flags */ if (XAllocColor(dpy, cmap, &color)) { XGCValues gcvalues; memset(&gcvalues, 0, sizeof(gcvalues)); gcvalues.foreground = gcvalues.background = color.pixel; return XCreateGC(dpy, XtWindow(crt), GCForeground | GCBackground, &gcvalues); } /* allocation failed */ return NULL; } /* put a point on the screen */ void ws_display_point(int x, int y, void *color) { GC gc = (GC) color; if (x > xpixels || y > ypixels) return; y = ypixels - y - 1; /* X11 coordinate system */ #ifdef FULL_SCREEN x += xoffset; y += yoffset; #endif if (gc == NULL) gc = blackGC; /* default to off */ #if PIX_SIZE == 1 XDrawPoint(dpy, XtWindow(crt), gc, x, y); #else XFillRectangle(dpy, XtWindow(crt), gc, x*PIX_SIZE, y*PIX_SIZE, PIX_SIZE, PIX_SIZE); #endif } void ws_sync(void) { XFlush(dpy); } /* * elapsed wall clock time since last call * +INF on first call */ struct elapsed_state { struct timeval tvs[2]; int new; }; static unsigned long elapsed(struct elapsed_state *ep) { unsigned long val; gettimeofday(&ep->tvs[ep->new], NULL); if (ep->tvs[!ep->new].tv_sec == 0) val = ~0L; else val = ((ep->tvs[ep->new].tv_sec - ep->tvs[!ep->new].tv_sec) * 1000000 + (ep->tvs[ep->new].tv_usec - ep->tvs[!ep->new].tv_usec)); ep->new = !ep->new; return val; } /* called periodically */ int ws_poll(int *valp, int maxusec) { static struct elapsed_state es; /* static to avoid clearing! */ #ifdef WS_POLL_DEBUG printf("ws_poll %d\n", maxusec); fflush(stdout); #endif elapsed(&es); /* start clock */ do { unsigned long e; /* tried checking return, but lost on TCP connections? */ os_pollfd(ConnectionNumber(dpy), maxusec); while (XtAppPending(app_context)) { XEvent event; /* XXX check for connection loss; set *valp? return 0 */ XtAppNextEvent(app_context, &event ); XtDispatchEvent( &event ); } e = elapsed(&es); #ifdef WS_POLL_DEBUG printf(" maxusec %d e %d\r\n", maxusec, e); fflush(stdout); #endif maxusec -= e; } while (maxusec > 10000); /* 10ms */ return 1; } /* utility: can be called from main program * which is willing to cede control */ int ws_loop(void (*func)(void *), void *arg) { int val; /* XXX use XtAppAddWorkProc & XtAppMainLoop? */ while (ws_poll(&val,0)) (*func)(arg); return val; } void ws_beep(void) { XBell(dpy, 0); /* ring at base volume */ XFlush(dpy); } /**************** * could move these to unix.c, if VMS versions needed * (or just (GASP!) ifdef) */ /* public version, used by delay code */ unsigned long os_elapsed(void) { static struct elapsed_state es; return elapsed(&es); } /* * select/DisplayNumber works on VMS 7.0+? * could move to "unix.c" * (I have some nasty VMS code that's supposed to to the job * for older systems) */ /* * sleep for maxus microseconds, returning TRUE sooner if fd is readable * used by X11 driver */ static int os_pollfd(int fd, int maxus) { /* use trusty old select (most portable) */ fd_set rfds; struct timeval tv; if (maxus >= 1000000) { /* not bloody likely, avoid divide */ tv.tv_sec = maxus / 1000000; tv.tv_usec = maxus % 1000000; } else { tv.tv_sec = 0; tv.tv_usec = maxus; } FD_ZERO(&rfds); FD_SET(fd, &rfds); return select(fd+1, &rfds, NULL, NULL, &tv) > 0; }