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