1 // DEPRECATED
2 
3 // This file is part of BOINC.
4 // http://boinc.berkeley.edu
5 // Copyright (C) 2008 University of California
6 //
7 // BOINC is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU Lesser General Public License
9 // as published by the Free Software Foundation,
10 // either version 3 of the License, or (at your option) any later version.
11 //
12 // BOINC is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 // See the GNU Lesser General Public License for more details.
16 //
17 // You should have received a copy of the GNU Lesser General Public License
18 // along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
19 
20 #include "config.h"
21 #include <cstdlib>
22 #include <cstdio>
23 #include <csetjmp>
24 #include <unistd.h>
25 #include <pthread.h>
26 #include <cstring>
27 #include <csignal>
28 #include "x_opengl.h"
29 
30 #include "app_ipc.h"
31 #include "util.h"
32 #include "filesys.h"
33 
34 #include "boinc_gl.h"
35 #include "boinc_glut.h"
36 #include "graphics_api.h"
37 #include "graphics_impl.h"
38 
39 #define BOINC_WINDOW_CLASS_NAME "BOINC_app"
40 
41 #define TIMER_INTERVAL_MSEC 30
42 #define TIMER_INTERVAL_SUSPENDED_MSEC 1000
43 
44 static bool visible = true;
45 static int current_graphics_mode = MODE_HIDE_GRAPHICS;
46 static int acked_graphics_mode;
47 static int xpos = 100, ypos = 100;
48 static int win_width = 600, win_height = 400;
49 static int clicked_button;
50 static int win=0;
51 static bool glut_is_initialized = false;
52 static bool suspend_render = false;
53 
54 // freeglut extensions, not in GL/glut.h.
55 //
56 
57 // glutGet(GLUT_VERSION) returns the freeglut version.
58 // returns (major*10000)+(minor*100)+maint level
59 // NOTE - make sure freeglut is initialized before
60 //        getting the version.
61 // NOTE - this isn't valid in GLUT which makes it doubly
62 //        useful, the -1 return code says its _not_
63 //        running freeglut so makes for a useful way
64 //        we can tell at runtime which library
65 //        is in use.
66 //
67 #ifndef GLUT_VERSION
68 #define GLUT_VERSION 0x01FC
69 #endif
70 
71 // glutGet(GLUT_INIT_STATE) returns freegluts state.  Determines
72 // when to call glutInit again to reinitialize it.
73 //
74 #ifndef GLUT_INIT_STATE
75 #define GLUT_INIT_STATE 0x007C
76 #endif
77 
78 // Simple defines for checking freeglut states
79 //
80 
81 // Check whether freeglut is initialized.  Necessary
82 // since it deinitializes itself when the window is
83 // closed.  Only works with freeglut, although glut
84 // will return -1 and write a warning message to stdout
85 //
86 #define FREEGLUT_IS_INITIALIZED ((bool)(glutGet(GLUT_INIT_STATE)==1))
87 
88 
89 // Check whether a window exists. Necessary as glutDestroyWindow
90 // causes SIGABRT, so we hide and reshow windows instead
91 // of creating and deleting them.  (Talking freeglut here)
92 // note - make sure glutInit is called before checking,
93 // otherwise freeglut will SIGABRT.
94 //
95 #define GLUT_HAVE_WINDOW ((bool)(glutGetWindow()!=0))
96 
97 
98 // Runtime tag to tell we're using freeglut so can take precautions
99 // and avoid SIGABRTs.  Initialize the flag to 'true' and the
100 // init code in boinc_glut_init() will check whether we have
101 // openglut or freeglut, resetting the variable as necessary.
102 //
103 // Linux systems ship with freeglut, so default is 'true' and
104 // its checked.
105 //
106 // If running freeglut, also get the version.
107 //
108 #ifdef __APPLE__
109 static bool glut_is_freeglut = false;   // Avoid warning message to stderr from glutGet(GLUT_VERSION)
110 static bool need_show = false;
111 #else
112 static bool glut_is_freeglut = true;
113 #endif
114 static int glut_version = 0;
115 
116 
117 // State variables for freeglut windows.  Since freeglut has
118 // problems wiht glutDestroyWindow, keep it open and just
119 // hide/show it as necessary.
120 //
121 static int fg_window_state;   // values same as mode
122 static bool fg_window_is_fullscreen;
123 
124 static jmp_buf jbuf;    // longjump/setjump for exit/signal handler
125 static struct sigaction original_signal_handler; // store previous ABRT signal-handler
126 static void set_mode(int mode);
127 static void restart(void);
128 static void boinc_glut_init(void);
129 
130 static APP_INIT_DATA aid;
131 
132 extern pthread_t graphics_thread;    // thread info
133 extern pthread_t worker_thread;
134 
135 // possible longjmp-values to signal from where we jumped:
136 //
137 enum {
138   JUMP_NONE = 0,
139   JUMP_EXIT,
140   JUMP_ABORT,
141   JUMP_LAST
142 };
143 // 1= exit caught by atexit, 2 = signal caught by handler
144 
xwin_glut_is_initialized()145 int xwin_glut_is_initialized() {
146     return glut_is_initialized;
147 }
148 
149 bool debug = false;
150 
151 // This callback is invoked when a user presses a key.
152 //
keyboardD(unsigned char key,int,int)153 void keyboardD(unsigned char key, int /*x*/, int /*y*/) {
154     if (current_graphics_mode == MODE_FULLSCREEN) {
155         set_mode(MODE_HIDE_GRAPHICS);
156     } else {
157         boinc_app_key_press((int) key, 0);
158     }
159 }
160 
keyboardU(unsigned char key,int,int)161 void keyboardU(unsigned char key, int /*x*/, int /*y*/) {
162     if (current_graphics_mode == MODE_FULLSCREEN) {
163         set_mode(MODE_HIDE_GRAPHICS);
164     } else {
165         boinc_app_key_release((int) key, 0);
166     }
167 }
168 
mouse_click(int button,int state,int x,int y)169 void mouse_click(int button, int state, int x, int y){
170     clicked_button = button;
171     if (current_graphics_mode == MODE_FULLSCREEN) {
172         set_mode(MODE_HIDE_GRAPHICS);
173     } else {
174         if (state) {
175             boinc_app_mouse_button(x, y, button, false);
176         } else {
177             boinc_app_mouse_button(x, y, button, true);
178         }
179     }
180 }
181 
mouse_click_move(int x,int y)182 void mouse_click_move(int x, int y){
183     if (current_graphics_mode == MODE_FULLSCREEN){
184         set_mode(MODE_HIDE_GRAPHICS);
185     } else if (clicked_button == 2){
186         boinc_app_mouse_move(x, y, false, false, true);
187     } else if (clicked_button == 1){
188         boinc_app_mouse_move(x, y, false, true, false);
189     } else if (clicked_button == 0){
190         boinc_app_mouse_move(x, y, true, false, false);
191     } else{
192         boinc_app_mouse_move(x, y, false, false, false);
193     }
194 }
195 
196 // maybe_render() can be called directly by GLUT when a window is
197 // uncovered, so we let that happen even if suspend_render is true.
maybe_render()198 static void maybe_render() {
199     int width, height;
200     BOINC_STATUS boinc_status;
201 
202     if (visible && (current_graphics_mode != MODE_HIDE_GRAPHICS)) {
203         width = glutGet(GLUT_WINDOW_WIDTH);
204         height = glutGet(GLUT_WINDOW_HEIGHT);
205         if (throttled_app_render(width, height, dtime())) {
206             glutSwapBuffers();
207 #ifdef __APPLE__
208             switch (current_graphics_mode) {
209             case MODE_WINDOW:
210                 MacGLUTFix(false);
211                 if (need_show) {
212                     glutShowWindow();
213                     need_show = false;
214                 }
215                 break;
216             case MODE_FULLSCREEN:
217             case MODE_BLANKSCREEN:
218                 MacGLUTFix(true);
219                 if (need_show) {
220                     glutShowWindow();
221                     need_show = false;
222                 }
223                 break;
224             }
225 #endif
226             // Always render once after app is suspended in case app displays
227             //  different graphics when suspended, then stop rendering
228             boinc_get_status(&boinc_status);
229             if (boinc_status.suspended)
230                 suspend_render = true;
231         }
232     }
233 }
234 
CloseWindow()235 void CloseWindow() {
236   set_mode(MODE_HIDE_GRAPHICS);
237 }
238 
make_new_window(int mode)239 static void make_new_window(int mode) {
240     if ( (mode != MODE_WINDOW) &&  (mode != MODE_FULLSCREEN) ) {
241         // nothing to be done here
242         return;
243     }
244 
245     if (glut_is_initialized && glut_is_freeglut) {
246         if (!FREEGLUT_IS_INITIALIZED) {
247             glut_is_initialized = false;
248             fg_window_is_fullscreen = false;
249 	        fg_window_state = 0;
250         }
251     }
252     if (!glut_is_initialized)  {
253         boinc_glut_init();
254     }
255 
256     if (debug) fprintf(stderr, "make_new_window(): now calling glutCreateWindow(%s)...\n", aid.app_name);
257     char window_title[256];
258     get_window_title(aid, window_title, 256);
259 
260     // just show the window if its hidden
261     // if it used to be fullscreen (before
262     // it was hidden, reset size and position
263     // to defaults
264     //
265     bool have_window = false;
266     if (glut_is_freeglut && GLUT_HAVE_WINDOW) {
267           have_window = true;
268           glutShowWindow();
269           if (fg_window_is_fullscreen) {
270              glutPositionWindow(xpos, ypos);
271              glutReshapeWindow(600, 400);
272              fg_window_is_fullscreen = false;
273           }
274 	  fg_window_state = MODE_WINDOW;
275     }
276 
277 #ifdef __APPLE__
278     if (win)
279         have_window = true;
280 #endif
281 
282     if (!have_window) {
283         win = glutCreateWindow(window_title);
284         if (debug) fprintf(stderr, "glutCreateWindow() succeeded. win = %d\n", win);
285 
286         glutReshapeFunc(app_graphics_resize);
287         glutKeyboardFunc(keyboardD);
288         glutKeyboardUpFunc(keyboardU);
289         glutMouseFunc(mouse_click);
290         glutMotionFunc(mouse_click_move);
291         glutDisplayFunc(maybe_render);
292         glEnable(GL_DEPTH_TEST);
293 
294         app_graphics_init();
295     }
296 
297 #ifdef __APPLE__
298     glutWMCloseFunc(CloseWindow);   // Enable the window's close box
299     BringAppToFront();
300     // Show window only after a successful call to throttled_app_render();
301     // this avoids momentary display of old image when screensaver restarts
302     // which made image appear to "jump."
303     need_show = true;
304 #endif
305 
306     if (mode == MODE_FULLSCREEN)  {
307         glutFullScreen();
308     }
309 
310     return;
311 }
312 
313 // Initialize GLUT.
314 // Using GLUT, this should only called once,
315 // even if the window is closed and opened.
316 // Using freeGLUT, its called to reinit freeglut and
317 // the graphics window as freeglut 'deinitialized' itself
318 // when the window is destroyed
319 //
320 // note - this is called from make_new_window() when a new
321 // window is to be created.
322 //
boinc_glut_init()323 static void boinc_glut_init() {
324     const char* args[2] = {"screensaver", NULL};
325     int one=1;
326     static bool first = true;
327 
328     win = 0;
329     if (debug) fprintf(stderr, "Calling glutInit()... \n");
330     glutInit (&one, (char**)args);
331     if (debug) fprintf(stderr, "survived glutInit(). \n");
332 
333     // figure out whether we're running GLUT or freeglut.
334     //
335     // note - glutGet(GLUT_VERSION) is supported in freeglut,
336     //        other GLUT  returns -1 and outputs a warning
337     //        message to stderr.
338     //      - must be after glutInit or will get SIGABRT
339     //
340     if (first) {
341         first = false;
342         if (glut_is_freeglut) {
343             glut_version = glutGet(GLUT_VERSION);
344             if (glut_version == -1) {
345                 glut_version = 0;
346                 glut_is_freeglut = false;
347            } else {
348                int major = glut_version/10000;
349                int minor = (glut_version -(major*10000))/100;
350                int maint = glut_version - (major*10000) - (minor * 100);
351                if (debug) fprintf(stderr, "Running freeGLUT %d.%d.%d.\n", major, minor, maint);
352            }
353        }
354     }
355 
356     glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
357     glutInitWindowPosition(xpos, ypos);
358     glutInitWindowSize(600, 400);
359     g_bmsp->boinc_get_init_data_hook(aid);
360     if (!strlen(aid.app_name))  {
361         strlcpy(aid.app_name, "BOINC Application", sizeof(aid.app_name));
362     }
363 
364     glut_is_initialized = true;
365     if (glut_is_freeglut) {
366        fg_window_state = 0;
367     }
368 
369     return;
370 
371 }
372 
373 // Destroy current window if any
374 //
375 // Note - glutDestroyWindow results in SIGABRT in freeglut, so
376 //        instead of closing the window, just hide it.  And
377 //        before hiding it, make sure there is a window,
378 //        user could have closed it.
379 //
KillWindow()380 void KillWindow() {
381    if (win) {
382 #ifdef __APPLE__
383         if (current_graphics_mode == MODE_WINDOW) {
384             win_width = glutGet(GLUT_WINDOW_WIDTH);
385             win_height = glutGet(GLUT_WINDOW_HEIGHT);
386             xpos = glutGet(GLUT_WINDOW_X);
387             ypos = glutGet(GLUT_WINDOW_Y);
388         } else {
389             // If fullscreen, resize now to avoid ugly flash if we subsequently
390             // redisplay as MODE_WINDOW.
391             glutPositionWindow(xpos, ypos);
392             glutReshapeWindow(win_width, win_height);
393         }
394 
395         // On Intel Macs (but not on PowerPC Macs) GLUT's destuctors often crash when
396         // glutDestroyWindow() is called.  So far, this has only been reported for
397         // SETI@home. Since it doesn't occur on PowerPC Macs, we suspect a bug in GLUT.
398         // To work around this, we just hide the window instead.  Though this does not
399         // free up RAM and VM used by the graphics, glutDestroyWindow() doesn't free
400         // them either (surprisingly), so there is no additional penalty for doing it
401         // this way.
402         glutHideWindow();
403 #else
404       if (glut_is_freeglut && FREEGLUT_IS_INITIALIZED && GLUT_HAVE_WINDOW) {
405          glutHideWindow();
406       } else {
407          int oldwin = win;
408          win = 0;	// set this to 0 FIRST to avoid recursion if the following call fails.
409          glutDestroyWindow(oldwin);
410       }
411 #endif
412    }
413 }
414 
set_mode(int mode)415 void set_mode(int mode) {
416     if (mode == current_graphics_mode) {
417         // Bring graphics window to front whenever user presses "Show graphics"
418         if (mode == MODE_WINDOW) {
419 #ifdef __APPLE__
420             BringAppToFront();
421 #else
422             if (glut_is_freeglut && FREEGLUT_IS_INITIALIZED && (GLUT_HAVE_WINDOW > 0)) {
423 //              glutPopWindow();
424        	        glutShowWindow();
425             }
426 #endif
427        }
428         return;
429     }
430 
431     if (debug) fprintf(stderr, "set_mode(%d): current_mode = %d.\n", mode, current_graphics_mode);
432     if (glut_is_initialized) {
433         if (debug) fprintf(stderr, "Calling KillWindow(): win = %d\n", win);
434         KillWindow();
435         if (debug) fprintf(stderr, "KillWindow() survived.\n");
436     }
437 
438     if (mode != MODE_HIDE_GRAPHICS) {
439         if (debug) fprintf(stderr, "set_mode(): Calling make_new_window(%d)\n", mode);
440         make_new_window(mode);
441         if (debug) fprintf(stderr, "make_new_window() survived.\n");
442     }
443 #ifdef __APPLE__
444     else
445         HideThisApp();
446 #endif
447 
448     current_graphics_mode = mode;
449 
450     return;
451 }
452 
wait_for_initial_message()453 static void wait_for_initial_message() {
454     (*g_bmsp->app_client_shmp)->shm->graphics_reply.send_msg(
455         xml_graphics_modes[MODE_HIDE_GRAPHICS]
456     );
457     acked_graphics_mode = MODE_HIDE_GRAPHICS;
458     while (1) {
459         if ((*g_bmsp->app_client_shmp)->shm->graphics_request.has_msg()) {
460             break;
461         }
462         sleep(1);
463     }
464     return;
465 }
466 
timer_handler(int)467 static void timer_handler(int) {
468     char buf[MSG_CHANNEL_SIZE];
469     GRAPHICS_MSG m;
470     BOINC_STATUS boinc_status;
471 
472     if (*g_bmsp->app_client_shmp) {
473         if ((*g_bmsp->app_client_shmp)->shm->graphics_request.get_msg(buf)) {
474             (*g_bmsp->app_client_shmp)->decode_graphics_msg(buf, m);
475             switch (m.mode) {
476             case MODE_REREAD_PREFS:
477                 //only reread graphics prefs if we have a window open
478                 //
479                 switch (current_graphics_mode){
480                 case MODE_WINDOW:
481                 case MODE_FULLSCREEN:
482                     app_graphics_reread_prefs();
483                     break;
484                 }
485                 break;
486             case MODE_HIDE_GRAPHICS:
487             case MODE_WINDOW:
488             case MODE_FULLSCREEN:
489             case MODE_BLANKSCREEN:
490                 if (strlen(m.display)) {
491 		    setenv("DISPLAY",m.display,1);
492                 }
493                 set_mode(m.mode);
494                 break;
495             }
496         }
497         if (acked_graphics_mode != current_graphics_mode) {
498             bool sent = (*g_bmsp->app_client_shmp)->shm->graphics_reply.send_msg(
499                 xml_graphics_modes[current_graphics_mode]
500             );
501             if (sent) acked_graphics_mode = current_graphics_mode;
502         }
503     }
504     boinc_get_status(&boinc_status);
505     if (! boinc_status.suspended)
506         suspend_render = false;
507     if (suspend_render) {
508         glutTimerFunc(TIMER_INTERVAL_SUSPENDED_MSEC, timer_handler, 0);
509     } else {
510         maybe_render();
511         glutTimerFunc(TIMER_INTERVAL_MSEC, timer_handler, 0);
512     }
513 
514     return;
515 }
516 
517 // exit handler: really only meant to handle exits() called by GLUT...
518 //
restart()519 void restart() {
520     // don't do anything except when exit is called from the graphics-thread
521     if (!pthread_equal(pthread_self(), graphics_thread))  {
522         if (debug) fprintf(stderr, "exit() was called from worker-thread\n");
523         return;
524     }
525     if (debug) fprintf(stderr, "restart: exit() called from graphics-thread.\n");
526 
527     // if we are standalone and glut was initialized,
528     // we assume user pressed 'close', and we exit the app
529     //
530     //
531     if (glut_is_initialized ) {
532         if (boinc_is_standalone()) {
533             if (debug) fprintf(stderr,
534                 "Assuming user pressed 'close'... means we're exiting now.\n"
535             );
536             if (boinc_delete_file(LOCKFILE) != 0) {
537                 perror ("Failed to remove lockfile..\n");
538             }
539             return;
540         }
541     }
542 
543     // re-install the  exit-handler to catch glut's notorious exits()...
544     //
545     atexit(restart);
546 
547     // jump back to entry-point in xwin_graphics_event_loop();
548     //
549     if (debug) fprintf(stderr, "restart: Jumping back to event_loop.\n");
550     longjmp(jbuf, JUMP_EXIT);
551 }
552 
553 // deal with ABORT's, typically raised by GLUT
554 // If we are in the graphics thread, we just return back
555 // into xwin_graphics_event_loop.  If we are in the main thread, then
556 // restore the old signal handler and raise SIGABORT.
557 //
restart_sig(int)558 void restart_sig(int /*signal_number*/) {
559     if (pthread_equal(pthread_self(), graphics_thread)) {
560         // alternative approach is for the signal hander to call exit().
561         fprintf(stderr, "Caught SIGABRT in graphics thread\n");
562         if (debug) fprintf(stderr, "Caught SIGABRT in graphics thread. Jumping back to xwin_graphics_event_loop()...\n");
563         // jump back to entry-point in xwin_graphics_event_loop()
564         longjmp(jbuf, JUMP_ABORT);
565     } else {
566         // In non-graphics thread: use original signal handler
567         //
568         fprintf(stderr, "Caught SIGABRT in non-graphics thread\n");
569         if (debug) fprintf(stderr, "Caught SIGABRT in non-graphics thread. Trying to call previous ABRT-handler...\n");
570         if (sigaction(SIGABRT, &original_signal_handler, NULL)) {
571             perror("Unable to restore SIGABRT signal handler in non-graphics thread\n");
572             // what to do? call abort(3)??  call exit(nonzero)???
573             // Here we just return.
574         } else {
575             // we could conceivably try to call the old signal handler
576             // directly.  But this means checking how its flags/masks
577             // are set, making sure it's not NULL (for no action) and so
578             // on.  This seems simpler and more robust. On the other
579             // hand it may take some time for the signal to be
580             // delivered, and during that time, what's going to be
581             // executing?
582             raise(SIGABRT);
583         }
584     }
585 }
586 
587 
588 // graphics thread main-function
589 // NOTE: this should only be called *ONCE* by an application
590 //
xwin_graphics_event_loop()591 void xwin_graphics_event_loop() {
592     int restarted = 0;
593 
594     if (debug) fprintf(stderr, "Direct call to xwin_graphics_event_loop()\n");
595 
596     struct sigaction sa = {0};
597     memset(&sa, 0, sizeof(sa));
598     sa.sa_handler = restart_sig;
599     sa.sa_flags = SA_RESTART;
600 
601     // install signal handler to catch abort() from GLUT.  Note that
602     // signal handlers are global to ALL threads, so the signal
603     // handler that we record here in &original_signal_handler is the
604     // previous one that applied to BOTH threads
605     if (sigaction(SIGABRT, &sa, &original_signal_handler)) {
606         perror("unable to install signal handler to catch SIGABORT");
607     }
608 
609     // handle glut's notorious exits()..
610     atexit(restart);
611 
612     // THIS IS THE re-entry point at exit or ABORT in the graphics-thread
613     restarted = setjmp(jbuf);
614 
615     // if we came here from an ABORT-signal thrown in this thread,
616     // we put this thread to sleep
617     //
618     if (restarted == JUMP_ABORT) {
619         while(1)  {
620             sleep(1);
621         }
622     }
623 
624     // If using freeglut, check to see if we need to
625     // reinitialize glut and graphics.
626     // We're only resetting flags here, actual reinit function
627     // is called in make_new_window().
628     //
629     if (glut_is_initialized && glut_is_freeglut) {
630         if (1 == (glut_is_initialized = FREEGLUT_IS_INITIALIZED)) {
631 	       if (!GLUT_HAVE_WINDOW) {
632 	          win = 0;
633 	       }
634 	    }
635     }
636 
637     if (boinc_is_standalone()) {
638         if (restarted) {
639             while(1) {
640                 sleep(1);    // assuming glutInit() failed: put graphics-thread to sleep
641             }
642         } else {
643             // open the graphics-window
644             set_mode(MODE_WINDOW);
645             glutTimerFunc(TIMER_INTERVAL_MSEC, timer_handler, 0);
646         }
647     } else {
648         if (!glut_is_initialized) {
649 #ifdef __APPLE__
650             setMacPList();
651 #endif
652             set_mode(MODE_HIDE_GRAPHICS);
653             while ( current_graphics_mode == MODE_HIDE_GRAPHICS ) {
654                 if (debug) fprintf(stderr,
655                     "Graphics-thread now waiting for client-message...\n"
656                 );
657                 wait_for_initial_message();
658                 if (debug) fprintf(stderr, "got a graphics-message from client... \n");
659                 timer_handler(0);
660             }
661         } else
662             // here glut has been initialized previously
663             // probably the user pressed window-'close'
664             //
665             set_mode(MODE_HIDE_GRAPHICS);    // close any previously open windows
666     }
667 
668     // ok we should be ready & initialized by now to call glutMainLoop()
669     //
670     if (debug) fprintf(stderr, "now calling glutMainLoop()...\n");
671     glutMainLoop();
672     if (debug) fprintf(stderr, "glutMainLoop() returned!! This should never happen...\n");
673 }
674 
675 
676 const char *BOINC_RCSID_c457a14644 = "$Id$";
677