//  bbsmount.cc for bbsmount - an tool for simple mounting in X11
//
//  Copyright (c) 2001 by Miroslav Jezbera, jezberam@phoenix.inf.upol.cz
//
//  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., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// (See the included file COPYING / GPL-2.0)
//

#ifdef HAVE_CONFIG_H
#   include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdio.h>
#if STDC_HEADERS
#   include <stdlib.h>
#   include <string.h>
#endif /* STDC_HEADERS */

#if HAVE_SIGNAL_H
#   include <signal.h>
#endif /* HAVE_SIGNAL_H */

#ifdef TIME_WITH_SYS_TIME
#   include <time.h>
#   include <sys/time.h>
#else /* !TIME_WITH_SYS_TIME */
#   include <time.h>
#endif /* !TIME_WITH_SYS_TIME */

#if HAVE_SYS_PARAM_H
#   include <sys/param.h>
#endif /* HAVE_SYS_PARAM_H */
#if HAVE_SYS_MOUNT_H
#   include <sys/mount.h>
#endif /* HAVE_SYS_MOUNT_H */
#if HAVE_SYS_STATFS_H
#   include <sys/statfs.h>
#endif /* HAVE_SYS_STATFS_H */
#if HAVE_SYS_STATVFS_H
#   include <sys/statvfs.h>
#endif /* HAVE_SYS_STATVFS_H */

#include <pthread.h>

#include <iostream>
#include <fstream>

#include "bbsmount.hh"
#include "i18n.hh"
#include "version.h"

#include "images/notfound.xpm"

#define USE_LARGER_UNIT(x)	((x) > 4096)
#define BUFFER_SIZE		1024
#if HAVE_STATVFS
#   define STATFS	statvfs
#elif HAVE_STATFS
#   define STATFS	statfs
#endif /* HAVE_STATVFS */

// Global variables
ToolWindow *AppWindow;
I18n i18n;
BToolTip tooltip;
bool finish_checking, do_reconfigure = false, is_statfs_filled = false;
string mount_point;
struct STATFS tooltip_statfs;
int debug_level;

// Functions
int
run_command_and_report_error(const char *command, string &err_msg)
{
    string err_command = command, tmp_file;
    int result;
    char buffer[BUFFER_SIZE];

    strcpy(buffer, "/tmp/."PACKAGE".XXXXXX");
#if HAVE_MKSTEMP
    result = mkstemp(buffer);
    if (result == -1)
	perror("mkstemp");
    else
	close(result);
#elif HAVE_TMPNAM
    if (!tmpnam(buffer))
	perror("tmpnam");
#else /* !HAVE_TMPNAM && !HAVE_MKSTEMP */
    if (!mktemp(buffer))
	perror("mktemp");
#endif /* !HAVE_TMPNAM && !HAVE_MKSTEMP */
    tmp_file = buffer;
    err_command += " 2>";
    err_command += tmp_file;
    result = system(err_command.c_str());
    if (result) {
	ifstream err_file(tmp_file.c_str());
	err_msg.erase();
	do {
	    err_file.read(buffer, BUFFER_SIZE);
	    err_msg.append(buffer, err_file.gcount());
	} while (err_file);
    }
    if (remove(tmp_file.c_str()))
	perror("remove");
    
    return result;
}

void *
run_command(void *data)
{
    command_data_type *cd = (command_data_type *)data;
    int retval;
    string err_msg;

    cd->mp->Lock();
    retval = run_command_and_report_error(cd->command, err_msg);
    if (retval == -1)
	cd->mp->setLastError(err_msg);
    else
	cd->mp->clearError();

    free(cd->command);
    cd->mp->Unlock();
    free(cd);
    pthread_exit(NULL);
    return NULL; // Never reached
}

int
run_command_in_foreground(const char *command, MountPoint &mp)
{
    int retval;
    string err_msg;

    if (strlen(command) == 0)
	return 0;
    retval = run_command_and_report_error(command, err_msg);
    if (retval == -1)
	mp.setLastError(err_msg);
    else
	mp.clearError();

    return retval;
}

int
run_command_in_background(const char *command, MountPoint &mp)
{
    if (strlen(command) == 0)
	return 0;

    char *str = strdup(command);    // prevents change and compiler warnings
    command_data_type *data;
    int retval = 0;
    pthread_t run;
    pthread_attr_t attribs;

    data = (command_data_type *)malloc(sizeof(command_data_type));
    if (data == NULL) {
	fprintf(stderr, "bbsmount: Memory allocation failed!\n");
	return -1;
    }
    data->command = str;
    data->mp = &mp;
    if (pthread_attr_init(&attribs) != 0) {
	perror("pthread_attr_init");
	return -1;
    }
    if (pthread_attr_setdetachstate(&attribs, PTHREAD_CREATE_DETACHED) != 0) {
	perror("pthread_attr_setdetachstate");
	pthread_attr_destroy(&attribs);
	return -1;
    }
    if (pthread_create(&run, &attribs, run_command, (void *)data) != 0) {
	perror("pthread_create");
	retval = -1;
    }
    if (pthread_attr_destroy(&attribs) != 0)
	perror("pthread_attr_destroy");

    return retval;
}

set<int>
what_is_mounted(const map<string, int> &mount_points)
{
    set<int> result;
    map<string, int>::const_iterator pointer;
    char buffer[max_mtab_line_length], md[max_mount_device_point_size], mp[max_mount_device_point_size];
    FILE *mtab;

#if READ_MOUNT
    mtab = popen(MOUNT_COMMAND, "r");
    if (!mtab) {
	fprintf(stderr, "Can't read output from mount command!");
	return result;
    }
#else /* !READ_MOUNT */
    mtab = fopen(MTAB_FILE, "rt");
    if (!mtab) {
	fprintf(stderr, "Can't open mtab file!");
	return result;
    }
#endif /* !READ_MOUNT */
    
    while (!feof(mtab)) {
	if (fgets(buffer, max_mtab_line_length, mtab) > 0) {
#if READ_MOUNT
	    sscanf(buffer, "%s on %s", md, mp);
#else /* !READ_MOUNT */
	    sscanf(buffer, "%s %s", md, mp);
#endif /* !READ_MOUNT */
	    pointer = mount_points.find(string(mp));
	    if (pointer != mount_points.end())
		result.insert(pointer->second);
	}
    }
    
#if READ_MOUNT
    pclose(mtab);
#else /* !READ_MOUNT */
    fclose(mtab);
#endif /* !READ_MOUNT */
    return result;
}

bool
update_mount_points(const map<string, int> &mount_points)
{
    set<int> result;
    size_t count = AppWindow->GetMountPointsCount();
    bool change = false;
    int counter;

    result = what_is_mounted(mount_points);
    for (counter = 0; counter < (int)count; counter++)
	if (AppWindow->GetMountPoint(counter).IsMounted() != (result.find(counter) != result.end())) {
	    AppWindow->GetMountPoint(counter).SetMounted(result.find(counter) != result.end());
	    change = true;
	}
    return change;
}

void
create_notify_event(void)
{
    XEvent event;

    memset((void *)&event, 0, sizeof(XExposeEvent));
    event.type = Expose;		/* How can I notify my thread waiting in XNextEvent, that there is some work ? */
    event.xexpose.window = AppWindow->GetRootWindow();
    event.xexpose.display = AppWindow->getXDisplay();
    event.xexpose.send_event = True;
    XSendEvent(AppWindow->getXDisplay(), AppWindow->GetRootWindow(), False, NoEventMask, &event);
    XFlush(AppWindow->getXDisplay());
}

void *
check_mounts(void *param)
{
#if !READ_MOUNT
    struct stat info;
    int mtab_size = 0;
    time_t mtab_time = time(NULL);
#endif /* !READ_MOUNT */
    int return_value, counter;
    map<string, int> mount_points;
    struct STATFS new_stats;

    for (counter = 0; counter < (int)AppWindow->GetMountPointsCount(); counter++)
	mount_points[AppWindow->GetMountPoint(counter).GetMountPoint()] = counter;
    while (!finish_checking) {
#if READ_MOUNT
	if (update_mount_points(mount_points))
		create_notify_event();
#else /* !READ_MOUNT */
	stat(MTAB_FILE, &info);
	if ((difftime(info.st_mtime, mtab_time) > 0) || (mtab_size != info.st_size)) {
	    mtab_size = info.st_size;
	    mtab_time = info.st_mtime;
	    if (update_mount_points(mount_points))
		create_notify_event();
	}
#endif /* !READ_MOUNT */
	if (is_statfs_filled) {
	    if (STATFS(mount_point.c_str(), &new_stats) != 0)
		perror("Can't get filesystem info!");
	    else {
		if (new_stats.f_bfree != tooltip_statfs.f_bfree || new_stats.f_bavail != tooltip_statfs.f_bavail) {
		    memcpy((void *)&tooltip_statfs, (void *)&new_stats, sizeof(struct STATFS));
		    AppWindow->updateTooltip();
		}
	    }
	}
	return_value = sleep(AppWindow->GetResources()->app.refresh_time);
	if (return_value != 0)
	    finish_checking = true;
    }

    pthread_exit(NULL);
    return NULL;
}

RETSIGTYPE
shutdown(int signal)
{
#if DEBUG
    if (debug_level >= dbg_all_work)
	printf("Stopping bbsmount.\n");
#endif
    finish_checking = true;
    do_reconfigure = false;
    AppWindow->Stop();
    create_notify_event();
}

RETSIGTYPE
usersignal(int signal)
{
#if DEBUG
    if (debug_level >= dbg_all_work)
	printf("Reconfiguring bbsmount.\n");
#endif
    AppWindow->reconfigure();
}

ToolWindow::ToolWindow(int argc,char **argv,struct CMDOPTIONS *options) : Basewindow(argc,argv,options)
{
    XrmInitialize();
    resource = new Resource(this);
    CreateWindow(false);
    configure();
    stop = false;
    tooltip_for_mp = NULL;
}

ToolWindow::~ToolWindow(void)
{
    XUnmapSubwindows(getXDisplay(), rootwin);
    FreeAll();
    XUnmapWindow(getXDisplay(), rootwin);

    /* destroy pixmaps */
    if (pixmap.frame)
	getImageControl()->removeImage(pixmap.frame);
    if (pixmap.draw)
	getImageControl()->removeImage(pixmap.draw);
    if (pixmap.draw_pressed)
	getImageControl()->removeImage(pixmap.draw_pressed);

    delete resource;
    XDestroyWindow(getXDisplay(), rootwin);
    XFlush(getXDisplay());
}
 
const MyImage &
ToolWindow::GetImage(const int imageidx) const
{
    return *images[imageidx];
}

const MountPoint &
ToolWindow::GetMountPoint(const int mntidx) const
{
    return mount_points[mntidx];
}

const PIXMAP &
ToolWindow::GetPixmaps(void) const
{
    return pixmap;
}

size_t
ToolWindow::GetMountPointsCount(void) const
{
    return mount_points.size();
}

MountPoint &
ToolWindow::GetMountPoint(const int mntidx)
{
    return mount_points[mntidx];
}

string
ToolWindow::getTooltip(const string &format, const MountPoint &mp, bool actual_statfs) const
{
    string result, pformat;
    char buffer[1024], unit = '\0', last_unit = '\0';
    unsigned int counter;
    bool was_percent = false;

    is_statfs_filled = actual_statfs;
    mount_point = mp.GetMountPoint();
    for (counter = 0; counter < format.length(); counter++) {
	if (was_percent) {
	    was_percent = false;
	    switch (format[counter]) {
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '.':
		    pformat += format[counter];
		    was_percent = true;
		    break;
		case 'B':
		case 'k':
		case 'M':
		case 'G':   // units
		    unit = format[counter];
		    was_percent = true;
		    break;
		case 'u':   // used
		    float used;

		    if (!is_statfs_filled) {
			if (STATFS(mp.GetMountPoint().c_str(), &tooltip_statfs) != 0)
			    return string("Can't get filesystem info!");
			is_statfs_filled = true;
		    }
		    pformat.insert(pformat.begin(), '%');
		    pformat += 'f';
		    used = (float)(tooltip_statfs.f_blocks - tooltip_statfs.f_bfree) * tooltip_statfs.f_bsize;
		    switch (unit) {
			case 'B':
			    sprintf(buffer, pformat.c_str(), used);
			    break;
			case 'k':
			    sprintf(buffer, pformat.c_str(), used / 1024);
			    break;
			case 'M':
			    sprintf(buffer, pformat.c_str(), used / 1048576);
			    break;
			case 'G':
			    sprintf(buffer, pformat.c_str(), used / 1073741824);
			    break;
			default:
			    if (USE_LARGER_UNIT(used)) {
				unit = 'k';
				used /= 1024;
			    }
			    if (USE_LARGER_UNIT(used)) {
				unit = 'M';
				used /= 1024;
			    }
			    if (USE_LARGER_UNIT(used)) {
				unit = 'G';
				used /= 1024;
			    }
			    sprintf(buffer, pformat.c_str(), used);
			    break;
		    }
		    last_unit = unit;
		    result += buffer;
		    break;
		case 'f':   // free
		    float free;

		    if (!is_statfs_filled) {
			if (STATFS(mp.GetMountPoint().c_str(), &tooltip_statfs) != 0)
			    return string("Can't get filesystem info!");
			is_statfs_filled = true;
		    }
		    pformat.insert(pformat.begin(), '%');
		    pformat += 'f';
		    free = (float)tooltip_statfs.f_bavail * tooltip_statfs.f_bsize;
		    switch (unit) {
			case 'B':
			    sprintf(buffer, pformat.c_str(), free);
			    break;
			case 'k':
			    sprintf(buffer, pformat.c_str(), free / 1024);
			    break;
			case 'M':
			    sprintf(buffer, pformat.c_str(), free / 1048576);
			    break;
			case 'G':
			    sprintf(buffer, pformat.c_str(), free / 1073741824);
			    break;
			default:
			    if (USE_LARGER_UNIT(free)) {
				unit = 'k';
				free /= 1024;
			    }
			    if (USE_LARGER_UNIT(free)) {
				unit = 'M';
				free /= 1024;
			    }
			    if (USE_LARGER_UNIT(free)) {
				unit = 'G';
				free /= 1024;
			    }
			    sprintf(buffer, pformat.c_str(), free);
			    break;
		    }
		    last_unit = unit;
		    result += buffer;
		    break;
		case 't':   // total
		    float total;

		    if (!is_statfs_filled) {
			if (STATFS(mp.GetMountPoint().c_str(), &tooltip_statfs) != 0)
			    return string("Can't get filesystem info!");
			is_statfs_filled = true;
		    }
		    pformat.insert(pformat.begin(), '%');
		    pformat += 'f';
		    total = (float)(tooltip_statfs.f_blocks - tooltip_statfs.f_bfree + tooltip_statfs.f_bavail) * tooltip_statfs.f_bsize;
		    switch (unit) {
			case 'B':
			    sprintf(buffer, pformat.c_str(), total);
			    break;
			case 'k':
			    sprintf(buffer, pformat.c_str(), total / 1024);
			    break;
			case 'M':
			    sprintf(buffer, pformat.c_str(), total / 1048576);
			    break;
			case 'G':
			    sprintf(buffer, pformat.c_str(), total / 1073741824);
			    break;
			default:
			    if (USE_LARGER_UNIT(total)) {
				unit = 'k';
				total /= 1024;
			    }
			    if (USE_LARGER_UNIT(total)) {
				unit = 'M';
				total /= 1024;
			    }
			    if (USE_LARGER_UNIT(total)) {
				unit = 'G';
				total /= 1024;
			    }
			    sprintf(buffer, pformat.c_str(), total);
			    break;
		    }
		    last_unit = unit;
		    result += buffer;
		    break;
		case 'v':
		    if (last_unit == 'k' || last_unit == 'M' || last_unit == 'G')
			result += last_unit;
		    break;
		case 'U':   // used %
		    if (!is_statfs_filled) {
			if (STATFS(mp.GetMountPoint().c_str(), &tooltip_statfs) != 0)
			    return string("Can't get filesystem info!");
			is_statfs_filled = true;
		    }
		    pformat.insert(pformat.begin(), '%');
		    pformat += 'f';
		    sprintf(buffer, pformat.c_str(), 100.0 - (float)tooltip_statfs.f_bavail * 100/tooltip_statfs.f_blocks);
		    result += buffer;
		    break;
		case 'F':   // free %
		    if (!is_statfs_filled) {
			if (STATFS(mp.GetMountPoint().c_str(), &tooltip_statfs) != 0)
			    return string("Can't get filesystem info!");
			is_statfs_filled = true;
		    }
		    pformat.insert(pformat.begin(), '%');
		    pformat += 'f';
		    sprintf(buffer, pformat.c_str(), (float)tooltip_statfs.f_bavail * 100/tooltip_statfs.f_blocks);
		    result += buffer;
		    break;
		case 'd':   // description
		    pformat.insert(pformat.begin(), '%');
		    pformat += 's';
		    sprintf(buffer, pformat.c_str(), mp.getDescription().c_str());
		    result += buffer;
		    break;
		case 's':   // mount point
		    pformat.insert(pformat.begin(), '%');
		    pformat += 's';
		    sprintf(buffer, pformat.c_str(), mp.GetMountPoint().c_str());
		    result += buffer;
		    break;
		case 'e':   // last error
		    pformat.insert(pformat.begin(), '%');
		    pformat += 's';
		    sprintf(buffer, pformat.c_str(), mp.getLastError().c_str());
		    result += buffer;
		    break;
		default:
		    result += format[counter];
		    break;
	    }
	    if (!was_percent) {
		pformat.erase();
		unit = '\0';
	    }
	} else {
	    if (format[counter] == '%')
		was_percent = true;
	    else
		result += format[counter];
	}
    }
    return result;
}

void
ToolWindow::FreeAll(void)
{
    vector<MyImage *>::iterator pointer = images.begin();
    vector<MountWindow *>::iterator mwpointer = mount_windows.begin();
    
    mount_points.clear();
    commands.clear();
    infotexts.clear();
    for (; pointer != images.end(); pointer++)
	delete *pointer;
    images.clear();
    for (; mwpointer != mount_windows.end(); mwpointer++)
	delete *mwpointer;
    mount_windows.clear();
}

void
ToolWindow::rereadResources(void)
{
    delete resource;
    resource = new Resource(this);
}

void
ToolWindow::configure(void)
{
    vector<string>::const_iterator pointer, end;
    XpmImage new_image;
    int image_counter;

    commands = resource->GetCommands();
    infotexts = resource->getInfoTexts();
    mount_points = resource->GetMountPoints();
    pointer = resource->GetImages().begin();
    end = resource->GetImages().end();
    
    XpmCreateXpmImageFromData(notfound_xpm, &new_image, NULL);
    SetImage(0, new MyImage(getXDisplay(), rootwin, &new_image));
    XpmFreeXpmImage(&new_image);
    image_counter = 1;

    for (; pointer != end; pointer++) {
	File imagefile(pointer->c_str(), resource->GetImagePrefix().c_str());
	MyImage *my_new_image;

	if (imagefile.Exist()) {
#if DEBUG
	    if (debug_level >= dbg_all_work)
		printf("Loading Xpm image %s.\n", imagefile.GetPath());
#endif /* DEBUG */
	    if (XpmReadFileToXpmImage(imagefile.GetPath(), &new_image, NULL) != XpmSuccess) {
		XpmCreateXpmImageFromData(notfound_xpm, &new_image, NULL);
#if DEBUG
		printf("Can't read image data from file %s!\n", imagefile.GetPath());
#endif /* DEBUG */
	    }
	}
	else {
	    XpmCreateXpmImageFromData(notfound_xpm, &new_image, NULL);
#if DEBUG
	    printf("Can't find image file %s!\n", pointer->c_str());
#endif /* DEBUG */
	}
	
	my_new_image = new MyImage(getXDisplay(), rootwin, &new_image);
	SetImage(image_counter++, my_new_image);
	XpmFreeXpmImage(&new_image);
    }
    resource->FreeAll();
}

/*Display *
ToolWindow::GetDisplay(void)
{
    return dpy;
}*/

Resource *
ToolWindow::GetResources(void)
{
    return resource;
}

Window
ToolWindow::GetRootWindow(void)
{
    return rootwin;
}

void
ToolWindow::CreateWindow(const bool do_reconfigure)
{
    XSetWindowAttributes attrib;
    XWMHints wmhints;
    unsigned long create_mask;

    create_mask = CWBackPixmap | CWOverrideRedirect | CWCursor | CWEventMask; 

    drawarea.width = resource->app.column_width * resource->app.columns;
    drawarea.height = resource->app.row_height * resource->app.rows;

    rootgeometry.width = resource->app.column_width * resource->app.columns +
	resource->frame.bevelWidth * 2 + (resource->app.columns - 1) * resource->app.button_padding;
    rootgeometry.height = resource->app.row_height * resource->app.rows +
	resource->frame.bevelWidth * 2 + (resource->app.rows - 1) * resource->app.button_padding;

    if (resource->position.mask & XNegative) {
	resource->position.x = getCurrentScreenInfo()->getWidth() + resource->position.x - rootgeometry.width;
    }
    if (resource->position.mask & YNegative) {
	resource->position.y = getCurrentScreenInfo()->getHeight() + resource->position.y - rootgeometry.height;
    }
    if (withdrawn) {
	attrib.override_redirect = False;
	wmhints.initial_state = WithdrawnState;
    }
    else {
	attrib.override_redirect = True;
	wmhints.initial_state = NormalState;
    }

    attrib.background_pixmap = ParentRelative;
    attrib.cursor = getSessionCursor();
    attrib.event_mask = ButtonPressMask | ButtonReleaseMask | ExposureMask |
			FocusChangeMask | KeyPressMask | StructureNotifyMask;

    pixmap.frame = getImageControl()->renderImage(rootgeometry.width, rootgeometry.height, &resource->frame.texture);
    pixmap.draw = getImageControl()->renderImage(resource->app.column_width, resource->app.row_height, &resource->app.mount_texture);
    pixmap.draw_pressed = getImageControl()->renderImage(resource->app.column_width, resource->app.row_height, &resource->app.mount_texture_pressed);

    if (!do_reconfigure) {
	rootwin = XCreateWindow(getXDisplay(), getCurrentScreenInfo()->getRootWindow(), resource->position.x, resource->position.y, rootgeometry.width,
		rootgeometry.height, 0, getCurrentScreenInfo()->getDepth(), InputOutput, getCurrentScreenInfo()->getVisual(), create_mask, &attrib);
    }
    else
	if (!withdrawn) {
	    XMoveResizeWindow(getXDisplay(), rootwin, resource->position.x, resource->position.y,
		   rootgeometry.width, rootgeometry.height);
	}
	else {
	    XResizeWindow(getXDisplay(), rootwin, rootgeometry.width, rootgeometry.height);
	}

    wmhints.flags = IconWindowHint | StateHint;
    wmhints.icon_window = rootwin;
    XSetStandardProperties(getXDisplay(), rootwin, BBTOOL, BBTOOL, None,
	    getArgv(), getArgc(), NULL);
    XSetWMProtocols (getXDisplay(), rootwin, &wm_delete_window, 1);
    XSetWMHints(getXDisplay(), rootwin, &wmhints);

    if (!shape)
	XSetWindowBackgroundPixmap(getXDisplay(), rootwin, pixmap.frame);

    if (!do_reconfigure) {
	XClearWindow(getXDisplay(), rootwin);
	XMapWindow(getXDisplay(), rootwin);
    }
}

void
ToolWindow::CreateSubWindows(const bool do_reconfigure)
{
    int col = 0, row = 0;
    vector<MountPoint>::iterator pointer;
    vector<MountWindow *>::iterator mwpointer = mount_windows.begin();

    if (!do_reconfigure) {
	for (pointer = mount_points.begin(); pointer != mount_points.end(); pointer++) {
	    if (col == resource->app.columns) {
		if (++row == resource->app.rows)
		    break;
		col = 0;
	    }
	    mount_windows.push_back(new MountWindow(&*pointer, getXDisplay(), rootwin, resource->frame.bevelWidth +
		    col * resource->app.column_width + resource->app.button_padding * col,
		    resource->frame.bevelWidth + row * resource->app.row_height +
		    resource->app.button_padding * row, resource->app.column_width,
		    resource->app.row_height));
	    mount_windows.back()->Draw();
	    col++;
	}
    }
    else
	if (!withdrawn) {
	    for (; mwpointer != mount_windows.end(); mwpointer++)
		delete *mwpointer;
	    mount_windows.clear();
	    for (pointer = mount_points.begin(); pointer != mount_points.end(); pointer++) {
		if (col == resource->app.columns) {
		    if (++row == resource->app.rows)
			break;
		    col = 0;
		}
		mount_windows.push_back(new MountWindow(&*pointer, getXDisplay(), rootwin, resource->frame.bevelWidth +
			col * resource->app.column_width + resource->app.button_padding * col,
			resource->frame.bevelWidth + row * resource->app.row_height +
			resource->app.button_padding * row, resource->app.column_width,
			resource->app.row_height));
		mount_windows.back()->Draw();
		col++;
	    }
	}
	else {
	    for (; mwpointer != mount_windows.end(); mwpointer++)
		delete *mwpointer;
	    mount_windows.clear();
	    for (pointer = mount_points.begin(); pointer != mount_points.end(); pointer++) {
		if (col == resource->app.columns) {
		    if (++row == resource->app.rows)
			break;
		    col = 0;
		}
		mount_windows.push_back(new MountWindow(&*pointer, getXDisplay(), rootwin, resource->frame.bevelWidth +
			col * resource->app.column_width + resource->app.button_padding * col,
			resource->frame.bevelWidth + row * resource->app.row_height +
			resource->app.button_padding * row, resource->app.column_width,
			resource->app.row_height));
		mount_windows.back()->Draw();
		col++;
	    }
	}

    XMapSubwindows(getXDisplay(), rootwin);
}

void
ToolWindow::EventLoop(void)
{
    XEvent event;
    vector<MountWindow *>::iterator pointer;
    unsigned int command;

    while (!stop) {
	XNextEvent(getXDisplay(), &event);
	switch (event.type) {
	    case Expose:
		if (stop)
		    return;
		if (event.xexpose.window == rootwin) {
		    if (event.xexpose.send_event == True) {
			for (pointer = mount_windows.begin(); pointer != mount_windows.end(); pointer++) {
				(*pointer)->Update();
				if ((*tooltip_for_mp) == (*pointer)->GetMountPoint())
				    updateTooltip();
			}
		    }
		    else
			Draw(event.xexpose.x, event.xexpose.y, event.xexpose.width, event.xexpose.height);
		}
		else {
		    for (pointer = mount_windows.begin(); pointer != mount_windows.end(); pointer++)
			if (event.xexpose.window == (*pointer)->GetWindow()) {
				(*pointer)->Draw(event.xexpose.x, event.xexpose.y, event.xexpose.width,
					event.xexpose.height);
				break;
			}
		    tooltip.processEvent(event);
		}
		break;
	    case ButtonPress:
		tooltip.hide();
		is_statfs_filled = false;
		tooltip_for_mp = NULL;
		for (pointer = mount_windows.begin(); pointer != mount_windows.end(); pointer++)
		    if (event.xbutton.window == (*pointer)->GetWindow()) {
			command = (*pointer)->GetMountPoint().GetCommand(event.xbutton.button, event.xbutton.state);
			if (command) {
			    (*pointer)->SetPressed(true);
			    char *comm = (char *)malloc(sizeof(char) * (commands[command].length() + (*pointer)->GetMountPoint().GetMountPoint().length()));
			    sprintf(comm, commands[command].c_str(), (*pointer)->GetMountPoint().GetMountPoint().c_str());
#if DEBUG
			    if (debug_level >= dbg_all_work)
				printf("Executing command: `%s`\n", comm);
#endif /* DEBUG */
#if RUN_IN_BACKGROUND
			    run_command_in_background(comm, (*pointer)->GetMountPoint());
#else /* !RUN_IN_BACKGROUND */
			    run_command_in_foreground(comm, (*pointer)->GetMountPoint());
#endif /* !RUN_IN_BACKGROUND */
			    free(comm);
			}
		    }
		break;
	    case ButtonRelease:
		tooltip.hide();
		is_statfs_filled = false;
		tooltip_for_mp = NULL;
		for (pointer = mount_windows.begin(); pointer != mount_windows.end(); pointer++)
		    if (event.xbutton.window == (*pointer)->GetWindow())
			(*pointer)->SetPressed(false);
		break;
	    case MotionNotify:
		if (tooltip.getTimeout()) {
		    tooltip.hide();
		    is_statfs_filled = false;
		    tooltip_for_mp = NULL;
		    ShowTooltip(event.xmotion.window, event.xmotion.x, event.xmotion.y, event.xmotion.x_root, event.xmotion.y_root);
		}
		break;
	    case EnterNotify:
		tooltip.hide();
		is_statfs_filled = false;
		tooltip_for_mp = NULL;
		ShowTooltip(event.xcrossing.window, event.xcrossing.x, event.xcrossing.y, event.xcrossing.x_root, event.xcrossing.y_root);
		break;
	    case LeaveNotify:
		tooltip.hide();
		is_statfs_filled = false;
		tooltip_for_mp = NULL;
		break;
	    case ConfigureNotify:
#if DEBUG
		if (debug_level >= dbg_all_work)
		    printf("ConfigureNotify detected.\n");
#endif /* DEBUG */
		if (do_reconfigure)
		    return;
		break;
	}
    }
}

void
ToolWindow::Draw(void)
{
    XClearWindow(getXDisplay(), rootwin);
}

void
ToolWindow::Draw(const int x, const int y, const unsigned int width, const unsigned int height)
{
    XClearArea(getXDisplay(), rootwin, x, y, width, height, False);
}

void
ToolWindow::SetCommand(const int cmdidx, const string command)
{
    if (cmdidx == (int)commands.size())
	commands.push_back(command);
    else
	commands[cmdidx] = command;
}

void
ToolWindow::SetImage(const int imgidx, MyImage *image)
{
    if (imgidx == (int)images.size())
	images.push_back(image);
    else
	images[imgidx] = image;
}

void
ToolWindow::SetMountPoint(const int mntidx, const MountPoint &mntpoint)
{
    if (mntidx == (int)mount_points.size())
	mount_points.push_back(mntpoint);
    else
	mount_points[mntidx] = mntpoint;
}

void
ToolWindow::Stop(void)
{
    stop = true;
}

void
ToolWindow::ShowTooltip(const Window w, const int x, const int y, const int screen_x, const int screen_y)
{
    int space_left, space_right, rootwin_width, win_no = 0;
    XWindowAttributes attrs;
    vector<MountWindow *>::iterator pointer;

    XGetWindowAttributes(getXDisplay(), rootwin, &attrs);
    space_left = screen_x - attrs.x;
    space_right = getCurrentScreenInfo()->getWidth() - screen_x - attrs.x - attrs.width ;
    rootwin_width = attrs.width;
    for (pointer = mount_windows.begin(); pointer != mount_windows.end(); pointer++) {
	if (w == (*pointer)->GetWindow()) {
	    XGetWindowAttributes(getXDisplay(), (*pointer)->GetWindow(), &attrs);
	    if (space_left > space_right)
		tooltip.setPosition(space_left - attrs.x - x - 1, screen_y,
			(win_no / resource->app.columns == resource->app.rows - 1 ? top_left : bottom_left));
	    else
		tooltip.setPosition(space_left - attrs.x - x + rootwin_width + 1, screen_y,
			(win_no / resource->app.columns == resource->app.rows - 1 ? top_right : bottom_right));
	    tooltip.setText(getTooltip(infotexts[(*pointer)->GetMountPoint().getCurrentInfo()], (*pointer)->GetMountPoint()));
	    tooltip_for_mp = &(*pointer)->GetMountPoint();
	    tooltip.show();
	}
	win_no++;
    }
}

void
ToolWindow::updateTooltip(void) {
    tooltip.setText(getTooltip(infotexts[tooltip_for_mp->getCurrentInfo()], *tooltip_for_mp, true));
}

void
ToolWindow::reconfigure(void)
{
    XEvent event;

    do_reconfigure = true;
    memset((void *)&event, 0, sizeof(XConfigureEvent));
    event.type = ConfigureNotify;
    event.xexpose.window = AppWindow->GetRootWindow();
    event.xexpose.display = AppWindow->getXDisplay();
    event.xexpose.send_event = True;
    XSendEvent(AppWindow->getXDisplay(), AppWindow->GetRootWindow(), False, NoEventMask, &event);
    XFlush(AppWindow->getXDisplay());
}

int
main(int argc, char *argv[])
{
    int result;
    pthread_t check_thread;
    void *thread_return;
    struct CMDOPTIONS options;

#if DEBUG
    if (debug_level >= dbg_summary && i18n.multibyte())
	printf("Using multibyte strings.\n");
#endif
    if (XInitThreads() == 0) {
    	perror("bbsmount: Can't initialize X11!");
    	exit(EXIT_FAILURE);
    }

    read_options(argc, argv, options);
    AppWindow = new ToolWindow(argc, argv, &options);

    AppWindow->CreateSubWindows(false);

#ifdef HAVE_SIGACTION
    struct sigaction act;

    act.sa_handler = shutdown;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    sigaction(SIGINT, &act, 0);
    sigaction(SIGTERM, &act, 0);
    act.sa_handler = usersignal;
    sigaction(SIGUSR1, &act, 0);
#else /* !HAVE_SIGACTION */
    signal(SIGINT, shutdown);
    signal(SIGTERM, shutdown);
    signal(SIGUSR1, usersignal);
#endif /* !HAVE_SIGACTION */

    do {
	if (do_reconfigure) {
//	    delete AppWindow;
//	    AppWindow = new ToolWindow(argc, argv, &options);

//	    AppWindow->CreateSubWindows(false);
	    AppWindow->FreeAll();
	    AppWindow->rereadResources();
	    AppWindow->CreateWindow(true);
	    AppWindow->configure();
	    AppWindow->CreateSubWindows(true);
	}

	finish_checking = false;
	do_reconfigure = false;

	result = pthread_create(&check_thread, NULL, check_mounts, NULL);
	if (result != 0) {
	    perror("bbsmount: Can't create thread!");
	    exit(EXIT_FAILURE);
	}
	tooltip.setTimeout(AppWindow->GetResources()->getTooltipTimeout());
#if DEBUG
	if (debug_level >= dbg_all_work)
	    printf("Waiting for events.\n");
#endif
	AppWindow->EventLoop();
#if DEBUG
	if (debug_level >= dbg_all_work)
	    printf("Event loop ended.\n");
#endif

	finish_checking = true;
	result = pthread_join(check_thread, &thread_return);
	if (result != 0) {
	    perror("bbsmount: Can't join thread!");
	    exit(EXIT_FAILURE);
	}
    } while (do_reconfigure);
    
    delete AppWindow;

    return EXIT_SUCCESS;
}


syntax highlighted by Code2HTML, v. 0.9.1