/*
 * Copyright (c) 1999, 2000  Motoyuki Kasahara.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer,
 *    without modification, immediately at the beginning of the file.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

#include <X11/Xatom.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/extensions/shape.h>
#include <X11/xpm.h>

#include "pccard.h"
#include "pccard_alias.h"

#include "wmpccard.xpm"
#include "wmpccard2.xpm"
#include "digits.xpm"
#include "defs.h"

/*
 * Application name and version.
 */
#define APPLICATION_NAME	"wmpccard"
#ifndef APPLICATION_VERSION
#define APPLICATION_VERSION	"?.?"
#endif

/*
 * Resource class.
 */
#define RESOURCE_CLASS		"WMPccard"

/*
 * Window size.
 */
#define WINDOW_WIDTH		64
#define WINDOW_HEIGHT		64

/*
 * Colors.
 */
#define FOREGROUND_COLOR	"cyan"
#define BACKGROUND_COLOR	"gray20"

/*
 * Font.
 */
#define FONT_NAME		"6x10"

/*
 * Geometry and size information for the card name area.
 */
#define CARD_NAME_AREA_X1	5
#define CARD_NAME_AREA_Y1	5
#define CARD_NAME_AREA_WIDTH	54
#define CARD_NAME_AREA_HEIGHT	39

/*
 * Geometry and size information for the power button.
 */
#define POWER_BUTTON_X1		4
#define POWER_BUTTON_Y1		47
#define POWER_BUTTON_X2		28
#define POWER_BUTTON_Y2		59
#define POWER_BUTTON_WIDTH	(POWER_BUTTON_X2 - POWER_BUTTON_X1 + 1)
#define POWER_BUTTON_HEIGHT	(POWER_BUTTON_Y2 - POWER_BUTTON_Y1 + 1)

/*
 * Geometry and size information for the slot button.
 */
#define SLOT_BUTTON_X1		35
#define SLOT_BUTTON_Y1		47
#define SLOT_BUTTON_X2		46
#define SLOT_BUTTON_Y2		59
#define SLOT_BUTTON_WIDTH	(SLOT_BUTTON_X2 - SLOT_BUTTON_X1 + 1)
#define SLOT_BUTTON_HEIGHT	(SLOT_BUTTON_Y2 - SLOT_BUTTON_Y1 + 1)

/*
 * Geometry information for the slot number display.
 */
#define SLOT_NUMBER_X1		49
#define SLOT_NUMBER_Y1		48

/*
 * Size information for digit drawn on the slot number display.
 */
#define DIGIT_WIDTH		10
#define DIGIT_HEIGHT		11
#define MAX_SLOTS		3

/*
 * Maximum length of a string displayed in the card name area.
 */
#define MAX_CARD_NAME_LENGTH	127

/*
 * Display
 */
Display *display = NULL;

/*
 * Window.
 */
Window icon_window;
Window master_window;

/*
 * Program name (argv[0]).
 */
char *program_name;

/*
 * Display name.
 */
char *display_name = "";

/*
 * Font name.
 */
char *font_name = FONT_NAME;

/*
 * Graphic context.
 */
GC gc;

/*
 * Font information.
 */
XFontStruct *font;

/*
 * Pixmaps for icon window.
 */
Pixmap icon_pixmap;

/*
 * Pixmap and mask data for "wmpccard.xpm".
 */
Pixmap back_pixmap;
Pixmap back_mask_pixmap;

/*
 * pixmap and mask data for "wmpccard2.xpm".
 */
Pixmap back2_pixmap;
Pixmap back2_mask_pixmap;

/*
 * pixmap and mask data for "digits.xpm".
 */
Pixmap digits_pixmap;
Pixmap digits_mask_pixmap;

/*
 * Socket connected to X server.
 */
int display_socket;

/*
 * Socket for communicating with pccardd.
 */
int pccard_socket;

/*
 * The number of PCcard slots your note PC has.
 */
int number_of_slots;

/*
 * The slot number that wmpccard currently displayes its information.
 */
int current_slot;

/*
 * True if the slot button is pressed.
 */
int slot_button_pressed = 0;

/*
 * PCcard alias file name.
 */
const char *alias_file_name = ALIAS_FILE_NAME;


/*
 * Draw the card name.
 */
static void
draw_card_name(void)
{
    char card_name[MAX_CARD_NAME_LENGTH + 1];
    size_t card_name_length;
    const char *manufacturer;
    const char *driver;
    const char *version;
    const char *alias;
    int card_status;
    int rows;
    int columns;
    int font_width;
    int font_height;
    int i;

    /*
     * Set card name to display.
     */
    card_status = pccard_status(current_slot);
    switch (card_status) {
    case PCCARD_STATUS_FILLED:
	manufacturer = pccard_manufacturer(current_slot);
	driver = pccard_driver(current_slot);
	version = pccard_version(current_slot);
	alias = pccard_alias(current_slot);
	if (alias != NULL && *alias != '\0') {
	    snprintf(card_name, MAX_CARD_NAME_LENGTH + 1, "%s:%s",
		driver, alias);
	} else {
	    snprintf(card_name, MAX_CARD_NAME_LENGTH + 1, "%s:%s %s",
		driver, manufacturer, version);
	}
	break;

    case PCCARD_STATUS_INACTIVE:
	strcpy(card_name, "inactive");
	break;

    case PCCARD_STATUS_EMPTY:
	strcpy(card_name, "empty");
	break;

    case PCCARD_STATUS_FILLED2:
    case PCCARD_STATUS_INACTIVE2:
	XCopyArea(display, back2_pixmap, icon_pixmap, gc,
	    CARD_NAME_AREA_X1, CARD_NAME_AREA_Y1,
	    CARD_NAME_AREA_WIDTH, CARD_NAME_AREA_HEIGHT,
	    CARD_NAME_AREA_X1, CARD_NAME_AREA_Y1);
	return;

    default:
	strcpy(card_name, "unknown status");
    }
    card_name_length = strlen(card_name);

    /*
     * Calclate rows and columns.
     */
    font_width = font->max_bounds.width;
    font_height = font->ascent + font->descent;
    columns = CARD_NAME_AREA_WIDTH / font_width;
    rows = CARD_NAME_AREA_HEIGHT / font_height;

    /*
     * Draw background.
     */
    XCopyArea(display, back_pixmap, icon_pixmap, gc,
	CARD_NAME_AREA_X1, CARD_NAME_AREA_Y1,
	CARD_NAME_AREA_WIDTH, CARD_NAME_AREA_HEIGHT,
	CARD_NAME_AREA_X1, CARD_NAME_AREA_Y1);

    /*
     * Draw the card name string.
     */
    for (i = 0; i < rows && columns * i < card_name_length; i++) {
	if (columns * i + columns < card_name_length) {
	    XDrawString(display, icon_pixmap, gc,
		CARD_NAME_AREA_X1,
		CARD_NAME_AREA_Y1 + font_height * i + font->ascent,
		card_name + columns * i, columns);
	} else {
	    XDrawString(display, icon_pixmap, gc,
		CARD_NAME_AREA_X1,
		CARD_NAME_AREA_Y1 + font_height * i + font->ascent,
		card_name + columns * i, card_name_length - columns * i);
	}
    }
}


/*
 * Draw the slot number (0, 1, 2 ...).
 */
static void
draw_slot_number(void)
{
    XCopyArea(display, digits_pixmap, icon_pixmap, gc,
	DIGIT_WIDTH * current_slot, 0, DIGIT_WIDTH, DIGIT_HEIGHT,
	SLOT_NUMBER_X1, SLOT_NUMBER_Y1);
}


/*
 * Draw the power button.
 */
static void
draw_power_button(void)
{
    int card_status;

    card_status = pccard_status(current_slot);
    switch (card_status) {
    case PCCARD_STATUS_FILLED:
    case PCCARD_STATUS_INACTIVE2:
	XCopyArea(display, back2_pixmap, icon_pixmap, gc,
	    POWER_BUTTON_X1, POWER_BUTTON_Y1,
	    POWER_BUTTON_WIDTH, POWER_BUTTON_HEIGHT,
	    POWER_BUTTON_X1, POWER_BUTTON_Y1);
	break;

    default:
	XCopyArea(display, back_pixmap, icon_pixmap, gc,
	    POWER_BUTTON_X1, POWER_BUTTON_Y1,
	    POWER_BUTTON_WIDTH, POWER_BUTTON_HEIGHT,
	    POWER_BUTTON_X1, POWER_BUTTON_Y1);
    }
}


/*
 * Draw the slot button.
 */
static void
draw_slot_button(void)
{
    if (slot_button_pressed) {
	XCopyArea(display, back2_pixmap, icon_pixmap, gc,
	    SLOT_BUTTON_X1, SLOT_BUTTON_Y1,
	    SLOT_BUTTON_WIDTH, SLOT_BUTTON_HEIGHT,
	    SLOT_BUTTON_X1, SLOT_BUTTON_Y1);
    } else {
	XCopyArea(display, back_pixmap, icon_pixmap, gc,
	    SLOT_BUTTON_X1, SLOT_BUTTON_Y1,
	    SLOT_BUTTON_WIDTH, SLOT_BUTTON_HEIGHT,
	    SLOT_BUTTON_X1, SLOT_BUTTON_Y1);
    }
}


/*
 * Update the window.
 */
static void
update_window(void)
{
    XCopyArea(display, icon_pixmap, icon_window, gc, 0, 0,
	WINDOW_WIDTH, WINDOW_HEIGHT, 0, 0);
}


/*
 * Process a pccard event.  (pccardd sends me an update notice.)
 */
static int
event_pccardd(void)
{
    int slot;

    /*
     * Get an unpdate notify.
     */
    slot = pccard_update();
    if (slot == -1)
	return 0;
    else if (slot == current_slot) {
	draw_power_button();
	draw_card_name();
	update_window();
    }

    return 1;
}


/*
 * Process a destroy notify event.
 */
static int
event_destroy_notify(void)
{
    pccard_finalize_client();
    XCloseDisplay(display);
    fflush(stderr);
    _exit(0);

    /* never reached */
    return 1;
}

/*
 * Process an event that the button 1 is pressed on the power button.
 */
static int
event_power_button_pressed(void)
{
    int card_status;

    /*
     * Send a request to change status of the current slot.
     */
    card_status = pccard_status(current_slot);
    if (card_status == PCCARD_STATUS_FILLED) {
	if (pccard_remove(current_slot) == -1)
	    return 0;
	draw_power_button();
	draw_card_name();
	update_window();
    } else if (card_status == PCCARD_STATUS_INACTIVE) {
	if (pccard_insert(current_slot) == -1)
	    return 0;
	draw_power_button();
	draw_card_name();
	update_window();
    }

    return 1;
}

/*
 * Process an event that the button 1 is pressed on the slot button.
 */
static int
event_slot_button_pressed(void)
{
    slot_button_pressed = 1;

    /*
     * Draw the pressed button.
     */
    draw_slot_button();

    /*
     * Update the current slot number.
     */
    current_slot = (current_slot + 1) % number_of_slots;

    /*
     * Draw the slot number, power button and cardname.
     */
    draw_slot_number();
    draw_power_button();
    draw_card_name();
    update_window();

    return 1;
}

/*
 * Process an event that the button 1 is released on the slot button.
 */
static int
event_slot_button_released(void)
{
    slot_button_pressed = 0;

    /*
     * Draw the unpressed button.
     */
    draw_slot_button();
    update_window();

    return 1;
}


/*
 * Exit handler for exit().
 */
static void
atexit_handler(void)
{
    pccard_finalize_client();
    if (display != NULL)
	XCloseDisplay(display);
    fflush(stderr);
}


/*
 * Signal handler for SIGINT, SIGQUIT and SIGTERM.
 */
static void
exit_signal_handler(int sig)
{
    pccard_finalize_client();
    if (display != NULL)
	XCloseDisplay(display);
    fflush(stderr);
    _exit(1);
}


/*
 * Signal handler for SIGHUP.
 */
static void
restart_signal_handler(int sig)
{
    pccard_synchronize();
    draw_power_button();
    draw_card_name();
    update_window();
}


/*
 * Main.
 */
int
main(int argc, char *argv[])
{
    XClassHint class_hint;
    XWMHints wm_hints;
    XGCValues gc_values;
    unsigned long gc_mask;
    XColor foreground, background;
    struct sigaction sigact;
    sigset_t sigset;
    int option;

    /*
     * Set program name.
     */
    program_name = strrchr(argv[0], '/');
    if (program_name != NULL)
	program_name++;
    else
	program_name = argv[0];

    /*
     * Parse command line arguments.
     */
    for (;;) {
	option = getopt(argc, argv, "a:d:f:hs:tv");
	if (option == EOF)
	    break;

	switch(option) {
	case 'a':
	    alias_file_name = (strcmp(optarg, "-") != 0) ? optarg : NULL;
	    break;

	case 'd':
	    display_name = optarg;
	    break;

	case 'f':
	    font_name = optarg;
	    break;

	case 'h':
	    printf("Usage: %s [-option...]\n", program_name);
	    printf("Options:\n");
	    printf("  -a <file>     set alias file name\n");
	    printf("                (default: %s)\n", ALIAS_FILE_NAME);
	    printf("  -d <display>  set display\n");
	    printf("  -f <font>     set font (default: %s)\n", FONT_NAME);
	    printf("  -h            show this help and exit\n");
	    printf("  -s <number>   set initial slot (default: 0)\n");
	    printf("  -t            trace mode\n");
	    printf("  -v            show version number and exit\n");
	    exit(0);

	case 's':
	    current_slot = atoi(optarg);
	    break;

	case 't':
	    pccard_set_debug_flag();
	    break;

	case 'v':
	    printf("%s version %s\n", APPLICATION_NAME, APPLICATION_VERSION);
	    exit(0);

	default:
	    fprintf(stderr, "type `%s -help' for more information\n",
		program_name);
	    goto die;
	}
    }

    if (argc - optind > 0) {
	fprintf(stderr, "too many arguments\n");
	fprintf(stderr, "type `%s -help' for more information\n",
	    program_name);
	goto die;
    }

    /*
     * Register a function to be called on exit().
     */
    atexit(atexit_handler);

    /*
     * Block SIGHUP.
     */
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGHUP);
    sigprocmask(SIG_BLOCK, &sigset, NULL);

    /*
     * Set signal handler.
     */
    sigact.sa_handler = restart_signal_handler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = SA_NOCLDSTOP;
    sigaction(SIGHUP, &sigact, NULL);

    sigact.sa_handler = exit_signal_handler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = SA_NOCLDSTOP;
    sigaction(SIGINT, &sigact, NULL);
    sigaction(SIGQUIT, &sigact, NULL);
    sigaction(SIGTERM, &sigact, NULL);

    /*
     * Connect to pccardd.
     */
    if (pccard_initialize_client(alias_file_name) == -1)
	goto die;

    number_of_slots = pccard_number_of_slots();
    pccard_socket = pccard_socket_file();
    if (number_of_slots == 0) {
	fprintf(stderr, "this system has no card slot\n");
	goto die;
    }
    if (number_of_slots <= current_slot) {
	fprintf(stderr, "warning: this system doesn't have the slot %d\n",
	    number_of_slots);
	fprintf(stderr, "warning: initial slot is set to 0\n");
	current_slot = 0;
    }
    if (number_of_slots > MAX_SLOTS)
	number_of_slots = MAX_SLOTS;

    /*
     * Open a display.
     */
    display = XOpenDisplay(display_name);
    if (display == NULL) {
        fprintf(stderr, "failed to open display %s\n", display_name);
	goto die;
    }
    display_socket = ConnectionNumber(display);

    /*
     * Create windows.
     */
    icon_window = XCreateSimpleWindow(display, DefaultRootWindow(display),
	0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, 0, 0);
    master_window = XCreateSimpleWindow(display, DefaultRootWindow(display),
	0, 0, 1, 1, 0, 0, 0);

    /*
     * Set font.
     */
    font = XLoadQueryFont(display, font_name);
    if (font == NULL) {
	fprintf(stderr, "failed to load font: %s\n", font_name);
	goto die;
    }

    /*
     * Set foreground color.
     */
    if (!XParseColor(display, DefaultColormap(display, DefaultScreen(display)),
	FOREGROUND_COLOR, &foreground)) {
	fprintf(stderr, "failed to parse color: %s\n", FOREGROUND_COLOR);
	goto die;
    }
    if (!XAllocColor(display, DefaultColormap(display, DefaultScreen(display)),
	&foreground)) {	
	fprintf(stderr, "failed to allocate color: %s\n", FOREGROUND_COLOR);
    }
    
    /*
     * Set background color.
     */
    if (!XParseColor(display, DefaultColormap(display, DefaultScreen(display)),
	BACKGROUND_COLOR, &background)) {
	fprintf(stderr, "failed to parse color: %s\n", BACKGROUND_COLOR);
	goto die;
    }
    if (!XAllocColor(display, DefaultColormap(display, DefaultScreen(display)),
	&background)) {	
	fprintf(stderr, "failed to allocate color: %s\n", BACKGROUND_COLOR);
    }
    
    /*
     * Set GC.
     */
    gc_mask = GCForeground | GCBackground | GCFont;
    gc_values.foreground = foreground.pixel;
    gc_values.background = background.pixel;
    gc_values.font = font->fid;
    gc = XCreateGC(display, DefaultRootWindow(display), gc_mask, &gc_values);

    /*
     * Set class hints.
     */
    class_hint.res_class = RESOURCE_CLASS;
    class_hint.res_name = program_name;
    XSetClassHint(display, master_window, &class_hint);

    /*
     * Set select input.
     */
    XSelectInput(display, master_window, ButtonPressMask | ButtonReleaseMask
	| StructureNotifyMask);
    XSelectInput(display, icon_window, ButtonPressMask | ButtonReleaseMask
	| StructureNotifyMask);

    /*
     * Set wm hints.
     */
    wm_hints.flags = IconWindowHint | WindowGroupHint | StateHint;
    wm_hints.window_group = master_window;
    wm_hints.icon_window = icon_window;
    wm_hints.initial_state = WithdrawnState;
    XSetWMHints(display, master_window, &wm_hints);

    /*
     * Set command.
     */
    XSetCommand(display, master_window, argv, argc);

    /*
     * Set pixmap.
     */
    icon_pixmap = XCreatePixmap(display, icon_window,
	WINDOW_WIDTH, WINDOW_HEIGHT,
	DefaultDepth(display, DefaultScreen(display)));
    XpmCreatePixmapFromData(display, icon_window, wmpccard_xpm,
	&back_pixmap, &back_mask_pixmap, NULL);
    XpmCreatePixmapFromData(display, icon_window, wmpccard2_xpm,
	&back2_pixmap, &back2_mask_pixmap, NULL);
    XpmCreatePixmapFromData(display, icon_window, digits_xpm,
	&digits_pixmap, &digits_mask_pixmap, NULL);
    XShapeCombineMask(display, icon_window, ShapeBounding, 0, 0,
	back_mask_pixmap, ShapeSet);

    /*
     * Display the window.
     */
    XCopyArea(display, back_pixmap, icon_pixmap, gc, 0, 0,
	WINDOW_WIDTH, WINDOW_HEIGHT, 0, 0);
    draw_slot_number();
    draw_power_button();
    draw_card_name();
    XSetWindowBackgroundPixmap(display, icon_window, icon_pixmap);
    XClearWindow(display, icon_window);
    XMapRaised(display, master_window);
    XFlush(display);

    /*
     * Main loop.
     */
    for (;;) {
	XEvent event;
	fd_set fdset;
	int nfiles;

	/*
	 * Unblock SIGHUP.
	 */
	sigemptyset(&sigset);
	sigaddset(&sigset, SIGHUP);
	sigprocmask(SIG_UNBLOCK, &sigset, NULL);

	/*
	 * Wait an event.
	 */
	/* XSync(display, False); */
	FD_ZERO(&fdset);
	FD_SET(display_socket, &fdset);
	FD_SET(pccard_socket, &fdset);
	nfiles = (display_socket < pccard_socket)
	    ? pccard_socket + 1 : display_socket + 1;
	if (select(nfiles, &fdset, NULL, NULL, NULL) == -1)
	    continue;

	/*
	 * Block SIGHUP.
	 */
	sigemptyset(&sigset);
	sigaddset(&sigset, SIGHUP);
	sigprocmask(SIG_BLOCK, &sigset, NULL);

	/*
	 * Process an event sent by pccardd.
	 */
	if (FD_ISSET(pccard_socket, &fdset)) {
	    if (!event_pccardd())
		goto die;
	}

	/*
	 * Process an event sent by X server.
	 */
	while (XPending(display)) {
	    XNextEvent(display, &event);

	    if (event.type == DestroyNotify) {
		/*
		 * Destroy Window.
		 */
		event_destroy_notify();

	    } else if (event.type == ButtonPress
		&& event.xbutton.button == 1
		&& POWER_BUTTON_X1 <= event.xbutton.x
		&& POWER_BUTTON_Y1 <= event.xbutton.y
		&& event.xbutton.x <= POWER_BUTTON_X2
		&& event.xbutton.y <= POWER_BUTTON_Y2) {
		/*
		 * Button 1 is pressed on the power button.
		 */
		if (!event_power_button_pressed())
		    goto die;

	    } else if (event.type == ButtonPress
		&& event.xbutton.button == 1
		&& SLOT_BUTTON_X1 <= event.xbutton.x
		&& SLOT_BUTTON_Y1 <= event.xbutton.y
		&& event.xbutton.x <= SLOT_BUTTON_X2
		&& event.xbutton.y <= SLOT_BUTTON_Y2) {
		/*
		 * Button 1 is pressed on the slot button.
		 */
		if (!event_slot_button_pressed())
		    goto die;

	    } else if (event.type == ButtonRelease
		&& event.xbutton.button == 1 && slot_button_pressed) {
		/*
		 * Button 1 is released when the slot button is pressed.
		 */
		if (!event_slot_button_released())
		    goto die;
	    }
	}
    }

    /*
     * Error occurs.
     */
  die:
    pccard_finalize_client();
    if (display != NULL)
	XCloseDisplay(display);
    fflush(stderr);
    _exit(1);
}



syntax highlighted by Code2HTML, v. 0.9.1