/* $Id: glbiff.cc,v 1.23 2000/04/19 04:32:06 mac Exp $ */ /* * glbiff -- A Mesa/OpenGL-based `xbiff' substitute * Copyright (C) 2000 Maciej Kalisiak * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "draw.h" #include "glbiff.h" #include "mail.h" #include "astro.h" #include "cfg.h" ///////////////////////// structures struct timed_cb_item { long ms_delta; void (*cb)(void); timed_cb_item* next; }; ///////////////////////// globals // if you don't like these defaults, change them, or use command lines // switches to choose different actions char* cmd_mail_reader = strdup("xterm -e elm"); // default: bring up elm char* cmd_new_mail = strdup("echo -n \a"); // default: ring bell char* file_config = "~/.glbiffrc"; // default: ".glbiffrc" in home dir char* geom = "100x100"; // default: nice'n'small int mail_count=0; // determines how many evelopes will be drawn bool unreadmail=false; // is there any unread mail? bool mail_reader_up = false; // is the mail reader up? pid_t child_pid; // pid of mail-reader when it's up // bezier patch control points of the round top of mailbox GLfloat mbox_top_ctrlpts[4][4][3] = { {{0,0,0}, {0,0,1}, {1,0,1}, {1,0,0}}, {{0,0.33,0}, {0,0.33,1}, {1,0.33,1}, {1,0.33,0}}, {{0,0.66,0}, {0,0.66,1}, {1,0.66,1}, {1,0.66,0}}, {{0,1,0}, {0,1,1}, {1,1,1}, {1,1,0}}, }; // bezier curve control points for the mailbox door GLfloat mbox_door_ctrlpts[4][3] = { {0,0,0}, {0,0,1}, {1,0,1}, {1,0,0}, }; // X11 stuff Display *dpy=NULL; Window win=0; timed_cb_item* sched_root = new timed_cb_item; //sched_root->ms_delta=0; timed_cb_item* callme_root = new timed_cb_item; ////////////////////////// code /* * new_mail_arrived(): gets called whenever the arrival of new mail has * been detected. */ void new_mail_arrived(void) { if( cmd_new_mail ) system(cmd_new_mail); } /* * check_mail(): this is the function to call when you need to reassess * the status of all mailboxes */ void check_mail(void) { #ifdef DEBUG printf("-- checking mail\n"); #endif bool anymail, newmail; mail_count = check_all_mailboxes( anymail, unreadmail, newmail ); if( newmail ) new_mail_arrived(); refresh(); } void refresh(void) { redraw( dpy, win ); } void set_alarm(long millisecs) { #ifdef DEBUG printf("-- setting alarm for %ld ms\n", millisecs); #endif struct itimerval itim; itim.it_interval.tv_sec = 0; itim.it_interval.tv_usec = 0; itim.it_value.tv_sec = millisecs / 1000; itim.it_value.tv_usec = (millisecs % 1000) * 1000; if (!itim.it_value.tv_usec) itim.it_value.tv_usec = 1; if (setitimer(ITIMER_REAL, &itim, NULL)) { char mess[256]; sprintf(mess, "Problem setting the itimer to %ld secs, %ld usecs " "(in set_alarm())", (long)itim.it_value.tv_sec, (long)itim.it_value.tv_usec ); perror(mess); } } void timed_callback( void (*cb)(void), long millisecs ) { #ifdef DEBUG printf("-- timed callback requested (%ld ms)\n", millisecs); #endif timed_cb_item* p = sched_root; while( p->next ) { if( p->next->ms_delta > millisecs ) { break; } else { millisecs -= p->next->ms_delta; p = p->next; } } timed_cb_item* t = new timed_cb_item; t->next = p->next; t->ms_delta = millisecs; t->cb = cb; p->next = t; if( t->next ) t->next->ms_delta -= t->ms_delta; // (re)start the timer if the requested callback is at front of list // WARNING: if item just placed is first, we have lost info on any time // elapsed on the previous first item; TODO: fix if( sched_root->next == t ) set_alarm( t->ms_delta ); } /* * alarm_handler(): handles SIGALRM */ void alarm_handler( int ) { #ifdef DEBUG printf("Handling SIGALRM.\n"); #endif // assume that the ALARM was caused by the first item on the schedule timed_cb_item* p = sched_root->next; if( !p ) { printf("Unknown cause for alarm; schedule is hosed?\n"); exit(1); } sched_root->next = p->next; if( sched_root->next ) set_alarm( sched_root->next->ms_delta ); // keep the mail_check alarm sequence going if( p->cb == check_mail ) timed_callback( check_mail, check_interval*1000 ); // place the callback on the "callme" list timed_cb_item* cur = callme_root; while( cur->next ) cur = cur->next; cur->next = p; p->next = NULL; } /* * child_sig_handler(): handles SIGCHLD * It's main purpose is to watch for the mail reader * shutting down, and then taking appropriate action. */ void child_sig_handler( int ) { #ifdef DEBUG printf("Handling SIGCHLD.\n"); #endif if( !child_pid ) { #ifdef DEBUG printf("Child trying to handle a SIGCHILD. Ignoring...\n"); #endif return; } if( mail_reader_up ) { int status; pid_t res = waitpid( child_pid, &status, WNOHANG ); #ifdef DEBUG printf("waitpid() returned %d (child_pid==%d).\n",res,child_pid); #endif if( res==child_pid ) { #ifdef DEBUG printf("Detected mail program shutting down.\n"); #endif mail_reader_up = false; fDoorOpen = false; fLookHeadOn = false; check_mail(); // to reflect changes made by user in mboxes refresh(); } } } /* * mouse_handler() * * handles mouse button events */ void mouse_event( int button, bool button_release ) { if( button_release && button==Button1 ) { // update mailbox status check_mail(); if( !mail_reader_up ) { fDoorOpen = true; fLookHeadOn = true; refresh(); // launch mail reader ... mail_reader_up = true; child_pid = fork(); if( child_pid ) { // parent thread // do nothing (for now) #ifdef DEBUG printf("Parent doing nothing in fork.\n"); #endif } else { system( cmd_mail_reader ); exit(0); } } } if( button_release && button==Button3 ) { if( !mail_reader_up ) { // if mail reader is up, leave it alone // update mailbox status check_mail(); if( !fDoorOpen ) { fDoorOpen = true; // fXformOn = true; refresh(); } else { fDoorOpen = false; // fXformOn = false; refresh(); } } } } /* * general_init() * * some basic initialization of Mesa/OpenGL; also installs the signal * handlers. */ void general_init(void) { /////////////////// OpenGL setup // setup lights GLfloat ambient[] = { 0.3, 0.3, 0.3, 1.0 }; GLfloat diffuse[] = { 1.0, 1.0, 1.0, 1.0 }; // GLfloat specular[] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat position[] = { 0.5, 1, 3.0, 0.0 }; GLfloat lmodel_ambient[] = { 0.5, 0.5, 0.5, 1.0 }; GLfloat local_view[] = { 0.0 }; glLightfv(GL_LIGHT0, GL_AMBIENT, ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse); // glLightfv(GL_LIGHT0, GL_SPECULAR, specular); glLightfv(GL_LIGHT0, GL_POSITION, position); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient); glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, local_view); ///// gotta figure out how to properly do this... // glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE); glFrontFace(GL_CW); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_AUTO_NORMAL); glEnable(GL_NORMALIZE); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4, 0, 1, 12, 4, (float*)mbox_top_ctrlpts); glMapGrid2f(DOME_SEGS, 0.0, 1.0, DOME_SEGS, 0.0, 1.0); glMap1f(GL_MAP1_VERTEX_3, 0, 1, 3, 4, (float*)mbox_door_ctrlpts); // texture stuff make_check_image(); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glGenTextures(1,&texName); glBindTexture(GL_TEXTURE_2D, texName); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, check_image_w, check_image_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, check_image); ////////// non-OGL stuff // read the configuration read_configuration(cfg_file()); /////// signal handlers struct sigaction sig; // SIGALRM setup (used for waking up glbiff to check mailboxes again) sig.sa_handler = alarm_handler; sig.sa_flags = 0; sigemptyset( &sig.sa_mask ); if( sigaction(SIGALRM, &sig, NULL) ) printf("Error setting signal handler for SIGALRM (%d).\n", errno); #ifdef DEBUG else printf("SIGALRM handler installed ok.\n"); #endif // SIGCHLD setup (so that glbiff knows when email prog exits) sig.sa_handler = child_sig_handler; sig.sa_flags = 0; sigemptyset( &sig.sa_mask ); if( sigaction(SIGCHLD, &sig, NULL) ) printf("Error setting signal handler for SIGCHLD (%d).\n", errno); #ifdef DEBUG else printf("SIGCHLD handler installed ok.\n"); #endif // start the timer running (this has to be done after SIGALRM setup!) struct itimerval itim; itim.it_interval.tv_sec = check_interval; itim.it_interval.tv_usec = 0; itim.it_value.tv_sec = check_interval; itim.it_value.tv_usec = 0; if(setitimer(ITIMER_REAL, &itim, NULL)) { char mess[256]; sprintf(mess, "Problem setting the itimer to %ld secs, %ld usecs " "(in general_init())", (long)itim.it_value.tv_sec, (long)itim.it_interval.tv_usec); perror(mess); } #ifdef DEBUG else fprintf(stderr, "Timer initialized ok.\n"); #endif } /* * resize() * * This function gets called when the window is resized; (w,h) are the * new width and height. */ void resize(unsigned int w, unsigned int h) { // typical glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); double D=0.2; glFrustum(-D,D,-D,D,0.5,100); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void syntax(void) { cout << "Usage: glbiff [-h] [-v] [-n arg] [-m arg] [-f arg]" << endl << " (see manpage for the details)" << endl; exit(0); } void version(void) { cout << "glbiff v" << VERSION << endl; exit(0); } // stolen (and slightly modified) from "glxdemo.c" (in Mesa's ./xdemos) static Window make_rgb_db_window(Display *dpy, int x, int y, unsigned int width, unsigned int height) { int attrib[] = { GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, GLX_DOUBLEBUFFER, GLX_DEPTH_SIZE, 1, None }; int scrnum; XSetWindowAttributes attr; unsigned long mask; Window root; Window win; GLXContext ctx; XVisualInfo *visinfo; scrnum = DefaultScreen(dpy); root = RootWindow(dpy, scrnum); visinfo = glXChooseVisual(dpy, scrnum, attrib); if(!visinfo) { printf("Error: couldn't get an RGB, Double-buffered visual\n"); exit(1); } /* window attributes */ attr.background_pixel = 0; attr.border_pixel = 0; attr.colormap = XCreateColormap(dpy, root, visinfo->visual, AllocNone); attr.event_mask = StructureNotifyMask | ExposureMask | ButtonPressMask | ButtonReleaseMask; mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; // cout << "Creating window at (" << x << "," << y << ")" << endl; win = XCreateWindow(dpy, root, x, y, width, height, 0, visinfo->depth, InputOutput, visinfo->visual, mask, &attr); ctx = glXCreateContext(dpy, visinfo, NULL, True); glXMakeCurrent(dpy, win, ctx); return win; } // stolen (and slightly modified) from "glxdemo.c" (in Mesa's ./xdemos) // and the select code from the "select" man page static void event_loop( Display *dpy ) { for(;;) { while( XPending(dpy) ) { XEvent event; XNextEvent( dpy, &event ); switch (event.type) { case Expose: redraw( dpy, event.xany.window ); break; case ConfigureNotify: resize( event.xconfigure.width, event.xconfigure.height ); break; case ButtonPress: mouse_event( event.xbutton.button, false ); break; case ButtonRelease: mouse_event( event.xbutton.button, true ); break; } } // see if any callbacks are awaiting invocation while (callme_root->next) { #ifdef DEBUG printf("-- Calling a callback.\n"); #endif timed_cb_item* p = callme_root->next; p->cb(); callme_root->next = p->next; delete p; } // wait a bit (or until a signal is received) fd_set rfds; struct timeval tv; int retval; // Watch stdout (fd 1) to see when it has input (never, which is // what we want). Is there a better way of doing this? FD_ZERO(&rfds); // FD_SET(1, &rfds); // don't need this, and now it does not eat // cpu when the *term is killed from which this glbiff was started... const long sel_timeout_usec = 1000; tv.tv_sec = 0; tv.tv_usec = sel_timeout_usec; retval = select(2, &rfds, NULL, NULL, &tv); // Don't rely on the value of tv now! (according to "select" man // page, which is where this is ripped from) } } int main( int argc, char** argv ) { // parse the command line args for(int arg=1; arg=argc) syntax(); cmd_new_mail = strdup(argv[arg]); } else if(!strcmp(argv[arg], "--mailprog") || !strcmp(argv[arg], "-m")) { // what mail program to start when glbiff is left clicked arg++; if(arg>=argc) syntax(); cmd_mail_reader = strdup(argv[arg]); } else if(!strcmp(argv[arg], "--cfgfile") || !strcmp(argv[arg], "-f")) { // which configuration file to read arg++; if(arg>=argc) syntax(); file_config = strdup(argv[arg]); } else if(!strcmp(argv[arg], "--geometry") || !strcmp(argv[arg], "-geometry") || !strcmp(argv[arg], "-geom") || !strcmp(argv[arg], "-g") ) { // X11 geometry specs arg++; if(arg>=argc) syntax(); geom = strdup(argv[arg]); } else fprintf(stderr, "Ignoring unkown switch %s\n", argv[arg]); } // drop any path from the executable name char* slash=strrchr(argv[0], '/'); if(slash) { strcpy(argv[0], slash+1); } // open the display dpy = XOpenDisplay(NULL); // parse the geometry string int geom_x=0, geom_y=0; unsigned int geom_w=100, geom_h=100; int geom_rc = XParseGeometry(geom, &geom_x, &geom_y, &geom_w, &geom_h); // int flags, x, y, width, height, i; // flags = XParseGeometry(geometry, &x, &y, // (unsigned int *) &width, (unsigned int *) &height); if (XValue & geom_rc && XNegative & geom_rc) geom_x = DisplayWidth(dpy, DefaultScreen(dpy)) + geom_x - geom_w; if (YValue & geom_rc && YNegative & geom_rc) geom_y = DisplayHeight(dpy, DefaultScreen(dpy)) + geom_y - geom_h; // bring up the window win = make_rgb_db_window(dpy, geom_x, geom_y, geom_w, geom_h); // configure the window title, class hint, etc... XStoreName(dpy, win, "glbiff"); XClassHint *chint = XAllocClassHint(); if (!chint) { cerr << "Couldn't XAllocClassHint()." << endl; exit(1); } chint->res_name="glbiff"; chint->res_class="GLbiff"; XSetClassHint( dpy, win, chint ); XFree( chint ); XWMHints* wmhints = XAllocWMHints(); if (!wmhints) { cerr << "Couldn't XAllocWMHints()." << endl; exit(1); } wmhints->flags = WindowGroupHint; wmhints->window_group = win; XSetWMHints( dpy, win, wmhints ); XFree( wmhints ); XSetCommand( dpy, win, argv, argc ); general_init(); // XSizeHints sizeHints = {0}; // sizeHints.width = geom_w; // sizeHints.height = geom_h; // sizeHints.x = geom_x; // sizeHints.y = geom_y; // XSetStandardProperties(dpy, win, "gLBIFF", "GLBIFF", // None, argv, argc, &sizeHints); XMapWindow (dpy, win); XMoveWindow (dpy, win, geom_x, geom_y); // start the check_mail trigger sequence // (have mail checked *right away*) timed_callback( check_mail, 0 ); event_loop( dpy ); return 0; }