/* * asmail is the AfterStep mailbox monitor * Copyright (c) 2002 Albert Dorofeev * For the updates see http://www.tigr.net/ * * This software is distributed under GPL. For details see LICENSE file. */ /* * Ok, here is how it works: * * X11 is not always thread safe. Many libraries are even less so. * That's why we have to stick to a less then efficient implementation * of the X interface. Namely, single-threaded. * * All setup is done by a single thread. This thread creates the * windows and sets up the icons. Then it goes to sleep for the * refresh rate number of 1/100ths of a second. * When we wake up, we have to check: * - if the state of the mailboxes was updated * - if there is some animation to be done * - if there are any X events pending * When necessary, we redraw the whole thing then. */ #include #include #include #include #include #include #include #include #include #include #include "globals.h" #include "gui.h" #include "x_color.h" /*#define DEBUG 1*/ #define LEFTBUTTON 1 /* Left mousebutton id */ #define MIDDLEBUTTON 2 /* Middle mousebutton id */ #define RIGHTBUTTON 3 /* Right mousebutton id */ #include "pixmaps/frame.xpm" #include "pixmaps/newmail.xpm" #include "pixmaps/oldmail.xpm" #include "pixmaps/nomail.xpm" #include "pixmaps/newmail_s.xpm" #include "pixmaps/oldmail_s.xpm" #include "pixmaps/nomail_s.xpm" /* Controls how close the color should be to the one we ask * for when we run out of color cells. * http://www.faqs.org/faqs/motif-faq/part5/section-34.html */ #define XPMCLOSENESS 40000 struct XpmIcon { Pixmap pixmap; Pixmap mask; XpmAttributes attributes; struct XpmIcon *next; }; struct XpmIcon *Frame = NULL, *NoMail = NULL, *OldMail = NULL, *NewMail = NULL, *Current = NULL; int saved_status = MAIL_NONE; int need_animation = 0; /* Here we work with lots of static variables so this * section will be protected by a mutex from multithreading. * Actually, in the future we may make many windows here :) */ pthread_mutex_t x11_mutex = PTHREAD_MUTEX_INITIALIZER; /* X windows related global variables */ Display *mainDisplay = 0; /* The display we are working on */ Window Root; /* The root window of X11 */ Window mainWindow; /* Application window */ Window iconWindow; /* Icon window */ Pixmap drawWindow; /* Drawing window - prepare the final image */ XGCValues mainGCV; /* graphics context values */ GC mainGC; /* Graphics context */ Atom wm_delete_window; Atom wm_protocols; XTextItem NumOfMsg; char NumOfMsgText[MAX_INPUT_LENGTH+1]; Pixel NumOfMsgColor; XFontStruct * font_struct; /* Foreground / background colors for the label */ Pixel back_pix; Pixel fore_pix; /* The size of the window */ XPoint winsize = {64,64}; void XPMError(int Code, const char * info) { switch (Code) { case 0: break; case 1: case -4: printf("asmail: XPMError: %s: not enough free color cells\n", info); break; case -1: case -2: printf("asmail: XPMError: %s: could not load xpm\n", info); break; case -3: printf("asmail: XPMError: %s: not enough memory free\n", info); break; default: printf("asmail: XPMError: %s: unknown xpm-error\n", info); break; } if (Code != 0) exit(1); } void LoadXPM(struct pixfile * filelist, struct XpmIcon ** destination) { struct XpmIcon * icon; struct pixfile * file_ptr; file_ptr = filelist; while ( file_ptr ) { icon = (struct XpmIcon *) calloc(1, sizeof(struct XpmIcon)); icon->attributes.valuemask |= XpmCloseness; icon->attributes.closeness = XPMCLOSENESS; XPMError(XpmReadFileToPixmap(mainDisplay, Root, file_ptr->name, &icon->pixmap, &icon->mask, &icon->attributes), file_ptr->name); icon->next = *destination; *destination = icon; file_ptr = file_ptr->next; } } struct XpmIcon *GetXPM(char **data) { struct XpmIcon *icon = (struct XpmIcon *) calloc(1, sizeof(struct XpmIcon)); icon->attributes.valuemask |= XpmCloseness; icon->attributes.closeness = XPMCLOSENESS; XPMError(XpmCreatePixmapFromData(mainDisplay, Root, data, &icon->pixmap, &icon->mask, &icon->attributes), "built-in"); icon->next = (struct XpmIcon *) icon; return icon; } void execute_on_new() { if ( x11_set.beep ) XBell(mainDisplay, 0); if ( strlen(x11_set.on_new_mail) ) system(x11_set.on_new_mail); } void x_cleanup() { XFreeFont(mainDisplay, font_struct); XCloseDisplay(mainDisplay); exit(0); } void add_status(char * line, int s) { #ifdef DEBUG printf("asmail: add_status: %d\n", s); #endif if ( s & STAT_RUN ) sprintf(&line[strlen(line)], "R"); else sprintf(&line[strlen(line)], " "); if ( s & STAT_FAIL ) sprintf(&line[strlen(line)], "F"); else if ( s & STAT_CONN ) sprintf(&line[strlen(line)], "C"); else if ( s & STAT_LOGIN ) sprintf(&line[strlen(line)], "L"); else if ( s & STAT_TIMEOUT ) sprintf(&line[strlen(line)], "T"); else sprintf(&line[strlen(line)], " "); } void setup_window(Window win) { int x,y; if ( ! x11_set.shape ) return; #ifdef DEBUG printf("asmail: setup_window: Setting up the window borders...\n"); #endif /* Offset for the image shape (centered) */ x = (winsize.x - Current->attributes.width)/2; y = (winsize.y - Current->attributes.height)/2; if ( x11_set.use_frame ) { XShapeCombineMask(mainDisplay, win, ShapeBounding, 0, 0, Frame->mask, ShapeSet); XShapeCombineMask(mainDisplay, win, ShapeBounding, x, y, Current->mask, ShapeUnion); } else { XShapeCombineMask(mainDisplay, win, ShapeBounding, x, y, Current->mask, ShapeSet); } } void draw_window(Window win) { int x,y; struct mbox_struct * mb; int o = 0; int n = 0; int s = STAT_IDLE; int direction_return, ascent_return, descent_return; XCharStruct overall_return; #ifdef DEBUG printf("asmail: draw_window: Redrawing the window...\n"); #endif /* x = (Frame->attributes.width - Current->attributes.width)/2; y = (Frame->attributes.height - Current->attributes.height)/2;*/ /* Offset for clip origin (centered) */ x = (winsize.x - Current->attributes.width)/2; y = (winsize.y - Current->attributes.height)/2; if ( x11_set.use_frame ) { XCopyArea(mainDisplay, Frame->pixmap, drawWindow, mainGC, 0, 0, winsize.x, winsize.y, 0, 0); } if ( x11_set.shape ) { XSetClipOrigin(mainDisplay, mainGC, x, y); XSetClipMask(mainDisplay, mainGC, Current->mask); } XCopyArea(mainDisplay, Current->pixmap, drawWindow, mainGC, 0, 0, Current->attributes.width, Current->attributes.height, x, y); if ( x11_set.shape ) XSetClipMask(mainDisplay, mainGC, None); /* * Display the summary of the mailboxes */ mb = mbox; while (mb) { if ( mb->ctotal ) o += mb->ctotal; if ( mb->cnew ) n += mb->cnew; s |= mb->status; mb = mb->next; } #ifdef DEBUG printf("asmail: draw_window: message count: %d new, %d old\n", n, o); #endif if ( x11_set.total ) { NumOfMsg.chars[0] = '\0'; if ( x11_set.status ) { add_status(NumOfMsg.chars, s); sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], " "); } if ( x11_set.new ) { if ( 0 != n ) sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], "%d", n); else sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], "-"); } if ( x11_set.old && x11_set.new ) { sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], x11_set.delimiter); } if ( x11_set.old ) { if ( 0 != o ) sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], "%d", o); else sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], "-"); } NumOfMsg.nchars = strlen(NumOfMsg.chars); XDrawText(mainDisplay, drawWindow, mainGC, x + x11_set.x, y + x11_set.y, &NumOfMsg, 1); XTextExtents(font_struct, NumOfMsg.chars, strlen(NumOfMsg.chars), &direction_return, &ascent_return, &descent_return, &overall_return); y += ascent_return + descent_return + 2; } /* * Display information for each mailbox */ if ( x11_set.each ) { mb = mbox; while (mb) { NumOfMsg.chars[0] = '\0'; if ( x11_set.status ) { add_status(NumOfMsg.chars, mb->status); sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], " "); } if ( x11_set.new ) { if ( 0 != mb->cnew ) sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], "%d", mb->cnew); else sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], "-"); } if ( x11_set.old && x11_set.new ) { sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], x11_set.delimiter); } if ( x11_set.old ) { if ( 0 != mb->ctotal ) sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], "%d", mb->ctotal); else sprintf(&NumOfMsg.chars[strlen(NumOfMsg.chars)], "-"); } NumOfMsg.nchars = strlen(NumOfMsg.chars); XDrawText(mainDisplay, drawWindow, mainGC, x + x11_set.x, y + x11_set.y, &NumOfMsg, 1); XTextExtents(font_struct, NumOfMsg.chars, strlen(NumOfMsg.chars), &direction_return, &ascent_return, &descent_return, &overall_return); y += ascent_return + descent_return + 2; mb = mb->next; } } /* * One final step - copy contents from the temp drawing * window into the actual window. */ XCopyArea(mainDisplay, drawWindow, win, mainGC, 0, 0, winsize.x, winsize.y, 0, 0); } void setup_pixmap() { #ifdef DEBUG printf("asmail: setup_pixmap: Choosing pixmap...\n"); #endif switch (x11_set.status) { case MAIL_NONE: Current = NoMail; break; case MAIL_OLD: Current = OldMail; break; case MAIL_NEW: Current = NewMail; break; } } void redraw() { #ifdef DEBUG printf("asmail: redraw: Redrawing the application windows...\n"); #endif setup_window(mainWindow); draw_window(mainWindow); setup_window(iconWindow); draw_window(iconWindow); XFlush(mainDisplay); } /* * Verify the status of mailboxes. If the status changes, * return 1, otherwise return 0. If the status changes, * call to set up the correct icon. */ int check_mbox() { struct mbox_struct * mb; mb = mbox; x11_set.status = MAIL_NONE; while ( mb ) { if ( mb->mail == MAIL_NEW ) x11_set.status = MAIL_NEW; else if ( (mb->mail == MAIL_OLD) && (x11_set.status != MAIL_NEW) ) x11_set.status = MAIL_OLD; if ( mb->flags & FLAG_ARRIVED ) { pthread_mutex_lock(&mb->mutex); mb->flags &= ~FLAG_ARRIVED; pthread_mutex_unlock(&mb->mutex); execute_on_new(); } mb = mb->next; } if ( saved_status != x11_set.status ) { #ifdef DEBUG printf("asmail: check_mbox: Determined mail status as %s (was %s)\n", x11_set.status == MAIL_NEW ? "NEW" : x11_set.status == MAIL_OLD ? "OLD" : "NONE", saved_status == MAIL_NEW ? "NEW" : saved_status == MAIL_OLD ? "OLD" : "NONE"); #endif setup_pixmap(); saved_status = x11_set.status; if ( Current && Current->next && Current->next != Current ) need_animation = 1; else need_animation = 0; return 1; } return 0; } void ButtonHandler(XEvent * E) { if (E->xbutton.button == LEFTBUTTON) { if (strlen(x11_set.on_left)) system(x11_set.on_left); } else if (E->xbutton.button == MIDDLEBUTTON) { if (strlen(x11_set.on_middle)) system(x11_set.on_middle); } else if (E->xbutton.button == RIGHTBUTTON) { if (strlen(x11_set.on_right)) system(x11_set.on_right); } } void x11_event() { XEvent Event; while ( XPending(mainDisplay) ) { XNextEvent(mainDisplay, &Event); switch (Event.type) { case Expose: #ifdef DEBUG printf("asmail: x11_event: Expose event caught: (%d %d) (%d x %d)\n", ((XExposeEvent *) & Event)->x, ((XExposeEvent *) & Event)->y, ((XExposeEvent *) & Event)->width, ((XExposeEvent *) & Event)->height); #endif if (Event.xexpose.count == 0) { /*signal_xkick();*/ draw_window(((XExposeEvent *) & Event)->window); } break; case ButtonPress: ButtonHandler(&Event); break; case ClientMessage: if ((Event.xclient.message_type == wm_protocols) && (Event.xclient.data.l[0] == wm_delete_window)) { #ifdef DEBUG printf("asmail: caught wm_delete_window, closing\n"); #endif x_cleanup(); } break; } } } void main_loop() { int need_redraw; check_mbox(); redraw(); x11_event(); while (1) { /* sleep_update will return 1 if it was signalled * and not simply timed out */ need_redraw = sleep_update( x11_set.refresh ); #ifdef DEBUG printf("asmail: main_loop: awake because of %s\n", need_redraw ? "SIGNAL" : "TIMEOUT"); #endif if ( need_animation ) { if ( Current ) { if ( Current->next ) Current = Current->next; else setup_pixmap(); } need_redraw = 1; } if ( check_mbox() ) need_redraw = 1; if ( need_redraw ) redraw(); /* Check if there are events pending for us. * Usually, if we redraw, there will be Expose * events - so check for events last. */ x11_event(); } } void startx(void * ptr) { int screen; int color_depth; XSizeHints SizeHints; int result; int x_negative = 0; int y_negative = 0; int x_size_forced = 0; int y_size_forced = 0; Status status; XTextProperty title; XClassHint classHint; XWMHints WmHints; XEvent Event; char * char_p; if ( pthread_mutex_trylock(&x11_mutex) == EBUSY ) { printf("asmail: startx: non-reentrant function!\n"); exit(0); } /* Use the $DISPLAY display */ mainDisplay = XOpenDisplay(NULL); if (!mainDisplay) { printf("asmail: startx: can't open display %s\n", XDisplayName(NULL)); exit(1); } screen = DefaultScreen(mainDisplay); Root = RootWindow(mainDisplay, screen); back_pix = GetColor("#385971", mainDisplay, Root); fore_pix = GetColor(x11_set.color, mainDisplay, Root); color_depth = DefaultDepth(mainDisplay, screen); #ifdef DEBUG printf("asmail: detected color depth %d bpp\n", color_depth); #endif /* Load the label font */ if ( x11_set.each || x11_set.total ) { font_struct = XLoadQueryFont( mainDisplay, x11_set.font ); if ( ! font_struct ) { printf("asmail: failed to load font %s\n", x11_set.font); printf("asmail: the indicators of message numbers"); printf("will not be drawn\n"); x11_set.each = 0; x11_set.total = 0; } #ifdef DEBUG else { printf("asmail: loaded font %s\n", x11_set.font); } #endif } NumOfMsg.chars = NumOfMsgText; NumOfMsg.font = XLoadFont(mainDisplay, x11_set.font); NumOfMsgColor = fore_pix; /* Here we have 2 possibilities. Either there are no pics * given in the config file -> use the built-in and no * animation. Or we got a list of pics and we will have * to animate through that list. */ if ( x11_set.nomail ) { LoadXPM(x11_set.nomail, &NoMail); } else { NoMail = GetXPM((x11_set.shape) ? nomail_s : nomail); NoMail->next = NoMail; } if ( x11_set.oldmail ) { LoadXPM(x11_set.oldmail, &OldMail); } else { OldMail = GetXPM((x11_set.shape) ? oldmail_s : oldmail); OldMail->next = OldMail; } if ( x11_set.newmail ) { LoadXPM(x11_set.newmail, &NewMail); } else { NewMail = GetXPM((x11_set.shape) ? newmail_s : newmail); NewMail->next = NewMail; } if ( x11_set.frame ) { LoadXPM(x11_set.frame, &Frame); } else { if ( x11_set.use_frame ) { Frame = GetXPM(frame); Frame->next = Frame; } } Current = NoMail; #ifdef DEBUG printf("asmail: frame %s, nomail %s, old-mail %s, new-mail %s\n", Frame ? "yes":"no", NoMail ? "yes":"no", OldMail ? "yes":"no", NewMail ? "yes":"no"); if ( Frame ) printf("asmail: frame %d depth, %d x %d\n", Frame->attributes.depth, Frame->attributes.width, Frame->attributes.height); if ( Current ) printf("asmail: first picture %d depth, %d x %d\n", Current->attributes.depth, Current->attributes.width, Current->attributes.height); #endif SizeHints.x = 0; SizeHints.y = 0; if ( x11_set.use_frame ) { winsize.x = Frame->attributes.width; winsize.y = Frame->attributes.height; } else { /* No frame is used, we have to figure * out the size of the window ourselves */ winsize.x = Current->attributes.width; winsize.y = Current->attributes.height; } SizeHints.flags = USSize | USPosition; /* Parsing the geometry */ if ( strlen(x11_set.geometry) ) { result = XParseGeometry(x11_set.geometry, &SizeHints.x, &SizeHints.y, (unsigned int *)&SizeHints.width, (unsigned int *)&SizeHints.height); if (result & WidthValue) { x_size_forced = 1; winsize.x = SizeHints.width; } if (result & HeightValue) { y_size_forced = 1; winsize.y = SizeHints.height; } if (result & XNegative) x_negative = 1; if (result & YNegative) y_negative = 1; } /* Make it non-resizeable */ SizeHints.min_width = SizeHints.max_width = SizeHints.width = winsize.x; SizeHints.min_height = SizeHints.max_height = SizeHints.height = winsize.y; SizeHints.flags |= PMinSize | PMaxSize; XWMGeometry(mainDisplay, screen, x11_set.geometry, NULL, 0, &SizeHints, &SizeHints.x, &SizeHints.y, &SizeHints.width, &SizeHints.height, &SizeHints.win_gravity); /* Correct the gravity for correct offsets if the X/Y are negative */ SizeHints.win_gravity = NorthWestGravity; if (x_negative) { SizeHints.win_gravity = NorthEastGravity; } if (y_negative) { if (x_negative) SizeHints.win_gravity = SouthEastGravity; else SizeHints.win_gravity = SouthWestGravity; } SizeHints.flags |= PWinGravity; #ifdef DEBUG printf("asmail: Size %d x %d, Position %d %d, gravity %d\n", SizeHints.width, SizeHints.height, SizeHints.x, SizeHints.y, SizeHints.win_gravity); printf(" (gravity: NW %d, NE %d, SE %d, SW %d)\n", NorthWestGravity, NorthEastGravity, SouthEastGravity, SouthWestGravity); #endif drawWindow = XCreatePixmap( mainDisplay, /* display */ Root, /* parent */ winsize.x, /* width */ winsize.y, /* height */ color_depth /* color depth */ ); mainWindow = XCreateSimpleWindow( mainDisplay, /* display */ Root, /* parent */ SizeHints.x, /* x */ SizeHints.y, /* y */ winsize.x, /* width */ winsize.y, /* height */ 0, /* border_width */ fore_pix, /* border */ back_pix /* background */ ); iconWindow = XCreateSimpleWindow( mainDisplay, /* display */ Root, /* parent */ SizeHints.x, /* x */ SizeHints.y, /* y */ winsize.x, /* width */ winsize.y, /* height */ 0, /* border_width */ fore_pix, /* border */ back_pix /* background */ ); XSetWMNormalHints(mainDisplay, mainWindow, &SizeHints); XSetWMNormalHints(mainDisplay, iconWindow, &SizeHints); status = XClearWindow(mainDisplay, mainWindow); status = XClearWindow(mainDisplay, iconWindow); char_p = x11_set.title; status = XStringListToTextProperty(&char_p, 1, &title); XSetWMName(mainDisplay, mainWindow, &title); XSetWMName(mainDisplay, iconWindow, &title); classHint.res_name = "asmail"; classHint.res_class = "ASMAIL"; XSetClassHint(mainDisplay, mainWindow, &classHint); XStoreName(mainDisplay, mainWindow, x11_set.title); XSetIconName(mainDisplay, mainWindow, x11_set.title); /* We need some events */ status = XSelectInput( mainDisplay, mainWindow, ExposureMask | ButtonPressMask ); status = XSelectInput( mainDisplay, iconWindow, ExposureMask | ButtonPressMask ); /* Remember the command line */ status = XSetCommand(mainDisplay, mainWindow, x11_set.argv, x11_set.argc); /* Set up the event for quitting the window */ wm_delete_window = XInternAtom(mainDisplay, "WM_DELETE_WINDOW", False ); wm_protocols = XInternAtom(mainDisplay, "WM_PROTOCOLS", False ); status = XSetWMProtocols(mainDisplay, mainWindow, &wm_delete_window, 1 ); status = XSetWMProtocols(mainDisplay, iconWindow, &wm_delete_window, 1 ); WmHints.flags = StateHint | IconWindowHint; WmHints.initial_state = x11_set.withdrawn ? WithdrawnState : x11_set.iconic ? IconicState : NormalState; WmHints.icon_window = iconWindow; if (x11_set.withdrawn) { WmHints.window_group = mainWindow; WmHints.flags |= WindowGroupHint; } if (x11_set.iconic || x11_set.withdrawn) { WmHints.icon_x = SizeHints.x; WmHints.icon_y = SizeHints.y; WmHints.flags |= IconPositionHint; } XSetWMHints(mainDisplay, mainWindow, &WmHints); mainGCV.foreground = fore_pix; mainGCV.background = back_pix; mainGCV.graphics_exposures = False; mainGC = XCreateGC(mainDisplay, Root, GCForeground | GCBackground, &mainGCV); status = XMapWindow(mainDisplay, mainWindow); /* wait for the Expose event now */ XNextEvent(mainDisplay, &Event); /* We've got Expose -> draw the window. */ redraw(); XFlush(mainDisplay); main_loop(); XFreeFont(mainDisplay, font_struct); XCloseDisplay(mainDisplay); pthread_exit(NULL); }