/* $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 <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <errno.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <X11/Xlib.h>
#include <iostream>
#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; arg++) {
// show the command syntax
if(!strcmp(argv[arg], "--help") || !strcmp(argv[arg], "-h")) {
syntax();
} else if(!strcmp(argv[arg], "--version") || !strcmp(argv[arg], "-v")) {
version();
} else if(!strcmp(argv[arg], "--newmail") || !strcmp(argv[arg], "-n")) {
// what external program to run when new mail arrives
arg++;
if(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;
}
syntax highlighted by Code2HTML, v. 0.9.1