/*
* asmail is the AfterStep mailbox monitor
* Copyright (c) 2002 Albert Dorofeev <albert@tigr.net>
* 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 <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>
#include <string.h>
#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);
}
syntax highlighted by Code2HTML, v. 0.9.1