1 /* 2 Copyright (c) 2005-2022 Intel Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Uncomment next line to disable shared memory features if you do not have libXext 18 // (http://www.xfree86.org/current/mit-shm.html) 19 //#define X_NOSHMEM 20 21 // Note that it may happen that the build environment supports the shared-memory extension 22 // (so there's no build-time reason to disable the relevant code by defining X_NOSHMEM), 23 // but that using shared memory still fails at run time. 24 // This situation will (ultimately) cause the error handler set by XSetErrorHandler() 25 // to be invoked with XErrorEvent::minor_code==X_ShmAttach. The code below tries to make 26 // such a determination at XShmAttach() time, which seems plausible, but unfortunately 27 // it has also been observed in a specific environment that the error may be reported 28 // at a later time instead, even after video::init_window() has returned. 29 // It is not clear whether this may happen in that way in any environment where it might 30 // depend on the kind of display, e.g., local vs. over "ssh -X", so #define'ing X_NOSHMEM 31 // may not always be the appropriate solution, therefore an environment variable 32 // has been introduced to disable shared memory at run time. 33 // A diagnostic has been added to advise the user about possible workarounds. 34 // X_ShmAttach macro was changed to 1 due to recent changes to X11/extensions/XShm.h header. 35 36 #include "video.hpp" 37 #include <string.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <math.h> 41 #include <X11/Xlib.h> 42 #include <X11/Xutil.h> 43 #include <X11/keysym.h> 44 #include <sys/time.h> 45 #include <signal.h> 46 #include <pthread.h> 47 48 #ifndef X_NOSHMEM 49 #include <errno.h> 50 #include <X11/extensions/XShm.h> 51 #include <sys/ipc.h> 52 #include <sys/shm.h> 53 54 static XShmSegmentInfo shmseginfo; 55 static Pixmap pixmap = 0; 56 static bool already_called_X_ShmAttach = false; 57 static bool already_advised_about_NOSHMEM_workarounds = false; 58 #endif 59 static char *display_name = nullptr; 60 static Display *dpy = nullptr; 61 static Screen *scrn; 62 static Visual *vis; 63 static Colormap cmap; 64 static GC gc; 65 static Window win, rootW; 66 static int dispdepth = 0; 67 static XGCValues xgcv; 68 static XImage *ximage; 69 static int x_error = 0; 70 static int vidtype = 3; 71 int g_sizex, g_sizey; 72 static video *g_video = 0; 73 unsigned int *g_pImg = 0; 74 static int g_fps = 0; 75 struct timeval g_time; 76 static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; 77 Atom _XA_WM_DELETE_WINDOW = 0; // like in Xatom.h 78 79 ///////////////////////////////////////////// public methods of video class /////////////////////// 80 81 video::video() { 82 assert(g_video == 0); 83 g_video = this; 84 title = "Video"; 85 calc_fps = running = false; 86 updating = true; 87 } 88 89 inline void mask2bits(unsigned int mask, unsigned int &save, depth_t &shift) { 90 save = mask; 91 if (!mask) { 92 shift = dispdepth / 3; 93 return; 94 } 95 shift = 0; 96 while (!(mask & 1)) 97 ++shift, mask >>= 1; 98 int bits = 0; 99 while (mask & 1) 100 ++bits, mask >>= 1; 101 shift += bits - 8; 102 } 103 104 int xerr_handler(Display *dpy_, XErrorEvent *error) { 105 x_error = error->error_code; 106 if (g_video) 107 g_video->running = false; 108 #ifndef X_NOSHMEM 109 if (error->minor_code == 1 /*X_ShmAttach*/ && already_called_X_ShmAttach && 110 !already_advised_about_NOSHMEM_workarounds) { 111 char err[256]; 112 XGetErrorText(dpy_, x_error, err, 255); 113 fprintf(stderr, "Warning: Can't attach shared memory to display: %s (%d)\n", err, x_error); 114 fprintf( 115 stderr, 116 "If you are seeing a black output window, сheck if you have installed Xext library and rebuild project"); 117 already_advised_about_NOSHMEM_workarounds = true; 118 } 119 #else 120 (void)dpy_; // warning prevention 121 #endif 122 return 0; 123 } 124 125 bool video::init_window(int xsize, int ysize) { 126 { //enclose local variables before fail label 127 g_sizex = xsize; 128 g_sizey = ysize; 129 130 // Open the display 131 if (!dpy) { 132 dpy = XOpenDisplay(display_name); 133 if (!dpy) { 134 fprintf(stderr, "Can't open X11 display %s\n", XDisplayName(display_name)); 135 goto fail; 136 } 137 } 138 int theScreen = DefaultScreen(dpy); 139 scrn = ScreenOfDisplay(dpy, theScreen); 140 dispdepth = DefaultDepth(dpy, theScreen); 141 XVisualInfo vinfo; 142 if (!((dispdepth >= 15 && dispdepth <= 32 && 143 XMatchVisualInfo(dpy, theScreen, dispdepth, TrueColor, &vinfo)) || 144 XMatchVisualInfo(dpy, theScreen, 24, TrueColor, &vinfo) || 145 XMatchVisualInfo(dpy, theScreen, 32, TrueColor, &vinfo) || 146 XMatchVisualInfo(dpy, theScreen, 16, TrueColor, &vinfo) || 147 XMatchVisualInfo(dpy, theScreen, 15, TrueColor, &vinfo))) { 148 fprintf(stderr, "Display has no appropriate True Color visual\n"); 149 goto fail; 150 } 151 vis = vinfo.visual; 152 depth = dispdepth = vinfo.depth; 153 mask2bits(vinfo.red_mask, red_mask, red_shift); 154 mask2bits(vinfo.green_mask, green_mask, green_shift); 155 mask2bits(vinfo.blue_mask, blue_mask, blue_shift); 156 rootW = RootWindow(dpy, theScreen); 157 cmap = XCreateColormap(dpy, rootW, vis, AllocNone); 158 XSetWindowAttributes attrs; 159 attrs.backing_store = Always; 160 attrs.colormap = cmap; 161 attrs.event_mask = StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask; 162 attrs.background_pixel = BlackPixelOfScreen(scrn); 163 attrs.border_pixel = WhitePixelOfScreen(scrn); 164 win = XCreateWindow(dpy, 165 rootW, 166 0, 167 0, 168 xsize, 169 ysize, 170 2, 171 dispdepth, 172 InputOutput, 173 vis, 174 CWBackingStore | CWColormap | CWEventMask | CWBackPixel | CWBorderPixel, 175 &attrs); 176 if (!win) { 177 fprintf(stderr, "Can't create the window\n"); 178 goto fail; 179 } 180 XSizeHints sh; 181 sh.flags = PSize | PMinSize | PMaxSize; 182 sh.width = sh.min_width = sh.max_width = xsize; 183 sh.height = sh.min_height = sh.max_height = ysize; 184 XSetStandardProperties(dpy, win, g_video->title, g_video->title, None, nullptr, 0, &sh); 185 _XA_WM_DELETE_WINDOW = XInternAtom(dpy, "WM_DELETE_WINDOW", false); 186 XSetWMProtocols(dpy, win, &_XA_WM_DELETE_WINDOW, 1); 187 gc = XCreateGC(dpy, win, 0L, &xgcv); 188 XMapRaised(dpy, win); 189 XFlush(dpy); 190 #ifdef X_FULLSYNC 191 XSynchronize(dpy, true); 192 #endif 193 XSetErrorHandler(xerr_handler); 194 195 int imgbytes = xsize * ysize * (dispdepth <= 16 ? 2 : 4); 196 const char *vidstr; 197 #ifndef X_NOSHMEM 198 int major, minor, pixmaps; 199 if (XShmQueryExtension(dpy) && 200 XShmQueryVersion(dpy, &major, &minor, &pixmaps)) { // Shared memory 201 shmseginfo.shmid = shmget(IPC_PRIVATE, imgbytes, IPC_CREAT | 0777); 202 if (shmseginfo.shmid < 0) { 203 fprintf(stderr, "Warning: Can't get shared memory: %s\n", strerror(errno)); 204 goto generic; 205 } 206 g_pImg = (unsigned int *)(shmseginfo.shmaddr = (char *)shmat(shmseginfo.shmid, 0, 0)); 207 if (g_pImg == (unsigned int *)-1) { 208 fprintf(stderr, "Warning: Can't attach to shared memory: %s\n", strerror(errno)); 209 shmctl(shmseginfo.shmid, IPC_RMID, nullptr); 210 goto generic; 211 } 212 shmseginfo.readOnly = false; 213 if (!XShmAttach(dpy, &shmseginfo) || x_error) { 214 char err[256]; 215 XGetErrorText(dpy, x_error, err, 255); 216 fprintf(stderr, 217 "Warning: Can't attach shared memory to display: %s (%d)\n", 218 err, 219 x_error); 220 shmdt(shmseginfo.shmaddr); 221 shmctl(shmseginfo.shmid, IPC_RMID, nullptr); 222 goto generic; 223 } 224 already_called_X_ShmAttach = true; 225 226 #ifndef X_NOSHMPIX 227 if (pixmaps && XShmPixmapFormat(dpy) == ZPixmap) { // Pixmaps 228 vidtype = 2; 229 vidstr = "X11 shared memory pixmap"; 230 pixmap = XShmCreatePixmap( 231 dpy, win, (char *)g_pImg, &shmseginfo, xsize, ysize, dispdepth); 232 XSetWindowBackgroundPixmap(dpy, win, pixmap); 233 } 234 else 235 #endif //!X_NOSHMPIX 236 { // Standard 237 vidtype = 1; 238 vidstr = "X11 shared memory"; 239 ximage = 240 XShmCreateImage(dpy, vis, dispdepth, ZPixmap, 0, &shmseginfo, xsize, ysize); 241 if (!ximage) { 242 fprintf(stderr, "Can't create the shared image\n"); 243 goto fail; 244 } 245 assert(ximage->bytes_per_line == xsize * (dispdepth <= 16 ? 2 : 4)); 246 ximage->data = shmseginfo.shmaddr; 247 } 248 } 249 else 250 #endif 251 { 252 #ifndef X_NOSHMEM 253 generic: 254 #endif 255 vidtype = 0; 256 vidstr = "generic X11"; 257 g_pImg = new unsigned int[imgbytes / sizeof(int)]; 258 ximage = XCreateImage(dpy, 259 vis, 260 dispdepth, 261 ZPixmap, 262 0, 263 (char *)g_pImg, 264 xsize, 265 ysize, 266 32, 267 imgbytes / ysize); 268 if (!ximage) { 269 fprintf(stderr, "Can't create the image\n"); 270 goto fail; 271 } 272 } 273 if (ximage) { 274 // Note: It may be more efficient to adopt the server's byte order 275 // and swap once per get_color() call instead of once per pixel. 276 const uint32_t probe = 0x03020100; 277 const bool big_endian = (((const char *)(&probe))[0] == 0x03); 278 ximage->byte_order = big_endian ? MSBFirst : LSBFirst; 279 } 280 printf("Note: using %s with %s visual for %d-bit color depth\n", 281 vidstr, 282 vis == DefaultVisual(dpy, theScreen) ? "default" : "non-default", 283 dispdepth); 284 running = true; 285 return true; 286 } // end of enclosing local variables 287 fail: 288 terminate(); 289 init_console(); 290 return false; 291 } 292 293 bool video::init_console() { 294 if (!g_pImg && g_sizex && g_sizey) { 295 dispdepth = 24; 296 red_shift = 16; 297 vidtype = 3; // fake video 298 g_pImg = new unsigned int[g_sizex * g_sizey]; 299 running = true; 300 } 301 return true; 302 } 303 304 void video::terminate() { 305 running = false; 306 if (dpy) { 307 vidtype = 3; // stop video 308 if (threaded) { 309 pthread_mutex_lock(&g_mutex); 310 pthread_mutex_unlock(&g_mutex); 311 } 312 if (ximage) { 313 XDestroyImage(ximage); 314 ximage = 0; 315 g_pImg = 0; 316 } // it frees g_pImg for vidtype == 0 317 #ifndef X_NOSHMEM 318 if (pixmap) 319 XFreePixmap(dpy, pixmap); 320 if (shmseginfo.shmaddr) { 321 XShmDetach(dpy, &shmseginfo); 322 shmdt(shmseginfo.shmaddr); 323 g_pImg = 0; 324 } 325 if (shmseginfo.shmid >= 0) 326 shmctl(shmseginfo.shmid, IPC_RMID, nullptr); 327 #endif 328 if (gc) 329 XFreeGC(dpy, gc); 330 if (win) 331 XDestroyWindow(dpy, win); 332 XCloseDisplay(dpy); 333 dpy = 0; 334 } 335 if (g_pImg) { 336 delete[] g_pImg; 337 g_pImg = 0; 338 } // if was allocated for console mode 339 } 340 341 video::~video() { 342 if (g_video) 343 terminate(); 344 g_video = 0; 345 } 346 347 //! Do standard event loop 348 void video::main_loop() { 349 struct timezone tz; 350 gettimeofday(&g_time, &tz); 351 on_process(); 352 } 353 354 //! Check for pending events once 355 bool video::next_frame() { 356 if (!running) 357 return false; 358 //! try acquire mutex if threaded code, returns on failure 359 if (vidtype == 3 || threaded && pthread_mutex_trylock(&g_mutex)) 360 return running; 361 //! Refresh screen picture 362 g_fps++; 363 #ifndef X_NOSHMPIX 364 if (vidtype == 2 && updating) 365 XClearWindow(dpy, win); 366 #endif 367 while (XPending(dpy)) { 368 XEvent report; 369 XNextEvent(dpy, &report); 370 switch (report.type) { 371 case ClientMessage: 372 if (report.xclient.format != 32 || report.xclient.data.l[0] != _XA_WM_DELETE_WINDOW) 373 break; 374 case DestroyNotify: running = false; 375 case KeyPress: on_key(XLookupKeysym(&report.xkey, 0)); break; 376 case ButtonPress: 377 on_mouse(report.xbutton.x, report.xbutton.y, report.xbutton.button); 378 break; 379 case ButtonRelease: 380 on_mouse(report.xbutton.x, report.xbutton.y, -report.xbutton.button); 381 break; 382 } 383 } 384 struct timezone tz; 385 struct timeval now_time; 386 gettimeofday(&now_time, &tz); 387 double sec = (now_time.tv_sec + 1.0 * now_time.tv_usec / 1000000.0) - 388 (g_time.tv_sec + 1.0 * g_time.tv_usec / 1000000.0); 389 if (sec > 1) { 390 memcpy(&g_time, &now_time, sizeof(g_time)); 391 if (calc_fps) { 392 double fps = g_fps; 393 g_fps = 0; 394 char buffer[256]; 395 snprintf(buffer, 396 256, 397 "%s%s: %d fps", 398 title, 399 updating ? "" : " (no updating)", 400 int(fps / sec)); 401 XStoreName(dpy, win, buffer); 402 } 403 #ifndef X_FULLSYNC 404 XSync(dpy, false); // It is often better then using XSynchronize(dpy, true) 405 #endif //X_FULLSYNC 406 } 407 if (threaded) 408 pthread_mutex_unlock(&g_mutex); 409 return true; 410 } 411 412 //! Change window title 413 void video::show_title() { 414 if (vidtype < 3) 415 XStoreName(dpy, win, title); 416 } 417 418 drawing_area::drawing_area(int x, int y, int sizex, int sizey) 419 : base_index(y * g_sizex + x), 420 max_index(g_sizex * g_sizey), 421 index_stride(g_sizex), 422 pixel_depth(dispdepth), 423 ptr32(g_pImg), 424 start_x(x), 425 start_y(y), 426 size_x(sizex), 427 size_y(sizey) { 428 assert(x < g_sizex); 429 assert(y < g_sizey); 430 assert(x + sizex <= g_sizex); 431 assert(y + sizey <= g_sizey); 432 433 index = base_index; // current index 434 } 435 436 void drawing_area::update() { 437 if (!g_video->updating) 438 return; 439 #ifndef X_NOSHMEM 440 switch (vidtype) { 441 case 0: 442 #endif 443 pthread_mutex_lock(&g_mutex); 444 if (vidtype == 0) 445 XPutImage(dpy, win, gc, ximage, start_x, start_y, start_x, start_y, size_x, size_y); 446 pthread_mutex_unlock(&g_mutex); 447 #ifndef X_NOSHMEM 448 break; 449 case 1: 450 pthread_mutex_lock(&g_mutex); 451 if (vidtype == 1) 452 XShmPutImage(dpy, 453 win, 454 gc, 455 ximage, 456 start_x, 457 start_y, 458 start_x, 459 start_y, 460 size_x, 461 size_y, 462 false); 463 pthread_mutex_unlock(&g_mutex); 464 break; 465 /*case 2: make it in next_frame(); break;*/ 466 } 467 #endif 468 } 469