/* 
 * Oroborus Window Manager
 *
 * Copyright (C) 2001 Ken Lynch
 * Copyright (C) 2002 Stefan Pfetzing
 *
 * OroboROX Window Manager
 * 
 * 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, 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.  
 */

#define __MAIN_C__

#include "config.h"


/* Take care of NLS matters.  */

#if ENABLE_NLS
# include <libintl.h>
# define _(Text) gettext (Text)
#else
# undef bindtextdomain
# define bindtextdomain(Domain, Directory)	/* empty */
# undef textdomain
# define textdomain(Domain)		/* empty */
# define _(Text) Text
#endif

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <locale.h>
#include <sys/wait.h>
#include <glib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xmd.h>
#include <ft2build.h>
#include <X11/Xft/Xft.h>

#ifdef HAVE_RANDR
#include "X11/extensions/Xrandr.h"
#endif

#include "hints.h"
#include "client.h"
#include "events.h"
#include "xinerama.h"
#include "mouse.h"
#include "i18n.h"
#include "workspaces.h"
#include "frame.h"
#include "ping.h"
#include "stacking.h"
#include "startup_notification.h"
#include "xerror.h"

#ifdef HAVE_GETOPT_LONG
#include <getopt.h>
#endif

#ifdef __APPLE__
#include <getopt.h>
#endif

#define DBUG_VERBOSE(x)  // DBUG(x)


GMainLoop *main_loop;
int display_width, display_height;
char *rcfile = NULL;
char *display = NULL;
Display *dpy;

Window root;
//Window win;

Colormap cmap;
int screen;
int depth;
Visual *visual;

//gulong margins[4];
int quit = False, reload = False;
int shape = 0, shape_event = 0;

#ifdef HAVE_RANDR
int xrandrSupported = 0, xrandrEventBase = 0, xrandrErrorBase = 0;
#endif

Cursor resize_cursor[7], button_cursor[BUTTON_COUNT];
Cursor move_cursor, busy_cursor, root_cursor, kill_cursor;

int want_verbose = 0;

char **stat_argv;
Time timestamp;
extern char **environ;


static void set_compliant(void) 
/* lets clients know were compliant (ish) */
{
    unsigned long  val[1];
	Window  win;
    XSetWindowAttributes attr;
   
    attr.override_redirect = True;
    win = XCreateWindow(dpy, root,
		       -200, -200, 5, 5, 0,
		       CopyFromParent,
		       CopyFromParent,
		       CopyFromParent,
		       CWOverrideRedirect, &attr );

    val[0] = win;

    XChangeProperty(dpy, root, intern_atoms[NET_SUPPORTING_WM_CHECK],
		   XA_WINDOW, 32, PropModeReplace, (unsigned char *)val, 1);

    XChangeProperty(dpy, win, intern_atoms[NET_SUPPORTING_WM_CHECK],
		   XA_WINDOW, 32, PropModeReplace,
		   (unsigned char *)val, 1);


   /* set utf8 name  */
    XChangeProperty(dpy, win, intern_atoms[NET_WM_NAME], 
		   intern_atoms[XA_UTF8_STRING], 8, PropModeReplace, 
		   (unsigned char *)PROJECT, strlen(PROJECT)+1);

	//XEvent event;
	//XWindowEvent(dpy, root, PropertyChangeMask, &event);
	//timestamp = event.xproperty.time;

    XStoreName(dpy, win, PROJECT);   

    set_property_card32(root, intern_atoms[NET_WM_PID], getpid());

	/* _NET_DESKTOP_VIEWPORT x, y, CARDINAL[][2]/32 */
	/* OroboROX does not implement viewports.  However, according to the
	   specification, OroboROX MUST set this property to (0,0) */
    set_property_xy(root, intern_atoms[NET_DESKTOP_VIEWPORT], 0, 0);

	set_property_card32(root, intern_atoms[NET_SHOWING_DESKTOP], 0);
}



int x_free(void *p)
{
	if (p)
		return XFree(p);
	else
		return TRUE;
}


static int handleXError(Display * dpy, XErrorEvent * err)
{
	switch (err->error_code)
	{
		case BadAccess:
			if (err->resourceid == root)
			{
				// Another WM is running, so use DBUS to tell ROX-Session that we
				// want this as our WM instead.
				if (system("dbus-send --type=method_call "
						   "--print-reply "		// In case we get an error back
						   "--dest=net.sf.rox.Session /Settings "
						   "net.sf.rox.Session.Settings.SetString "
						   "string:ROX/WindowManager "
						   "string:\"0launch http://rox.sourceforge.net/2005/interfaces/OroboROX\"") != 0)
					g_warning("dbus-send failed. You should probably upgrade your ROX-Session");

				exit(2);
			}
	}
	return 0;
}

void cleanUp(void)
{

#ifdef DEBUG
	printf("entering cleanUp\n");
#endif

	clientUnframeAll();
	sn_close_display();
	unloadSettings();
	XFreeCursor(dpy, root_cursor);
	XFreeCursor(dpy, move_cursor);
	XFreeCursor(dpy, busy_cursor);
	XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
	XCloseDisplay(dpy);
}

static void handleSignal(int sig)
{
	DBUG("entering handleSignal");
	int *status = NULL;

	switch (sig)
	{
		case SIGINT:
		case SIGTERM:
			quit = True;
			break;
		case SIGSEGV:
			g_warning("Segmentation fault");
			cleanUp();
			abort();
			break;
		case SIGHUP:
			reload = True;
			break;
		case SIGCHLD:
			puts("waiting for a child");
			waitpid(-1, status, WNOHANG);
			break;
	}

	/* dummy XEvent to cause a mainloop iteration*/
	set_property_card32(root, intern_atoms[NET_WM_PID], getpid());

}

#ifdef HAVE_GETOPT_LONG
/* the structure of all long options for getopt */
static struct option const long_options[] = {
	{"verbose", no_argument, 0, 'v'},
	{"help", no_argument, 0, 'h'},
	{"version", no_argument, 0, 'V'},
	{"file", required_argument, 0, 'r'},
	{"display", required_argument, 0, 'd'},
	{NULL, 0, NULL, 0}
};
#endif

static void usage(int status)
{
	puts(_(PROJECT " - lightweight themeable window manager for X."));
	puts(_("Usage: " PROJECT " [OPTION]... "));


#ifdef HAVE_GETOPT_LONG
	puts(_("Options:\n"
		   "  -r, --file=FILE            use an alternative rcfile\n"
		   "                             instead of $XDG_CONFIG_HOME/OroboROX/oroborusrc\n"
		   "  -v, --verbose              print more information\n"
		   "  -h, --help                 display this help and exit\n"
		   "  -V, --version              output version information and exit\n"
		   "  -d, --display=DISPLAY      use an alternative display instead of,\n"
		   "                             $DISPLAY\n"));
#else
	puts(_("Options:\n"
		   "  -r FILE,                   use an alternative rcfile\n"
		   "                             instead of $XDG_CONFIG_HOME/OroboROX/oroborusrc\n"
		   "  -v,                        print more information\n"
		   "  -h,                        display this help and exit\n"
		   "  -V,                        output version information and exit\n"
		   "  -d,                        use an alternative display instead of,\n"
		   "                             $DISPLAY\n"));
#endif

	exit(status);
}

static int decode_switches(int argc, char **argv)
{
	int c;

	while (1)
	{
#ifdef HAVE_GETOPT_LONG
		c = getopt_long(argc, argv, "v"	/* verbose */
						"h"		/* help */
						"V"		/* version */
						"r:"	/* file */
						"d:",	/* display */
						long_options, (int *) 0);
#else
		c = getopt(argc, argv, "v"	/* verbose */
				   "h"			/* help */
				   "V"			/* version */
				   "r:"			/* file */
				   "d:"			/* display */);
#endif
		if (c == EOF)
			break;				/* No more options */

		switch (c)
		{
			case 'd':			/* --display */
				display = optarg;
				break;
			case 'v':			/* --verbose */
				want_verbose = 1;
				break;
			case 'r':			/* --file */
				rcfile = optarg;
				break;
			case 'V':
				printf("%s %s\n", PROJECT, VERSION);
				exit(0);
			case 'h':
				usage(0);
			default:
				usage(EXIT_FAILURE);
		}
	}
	return optind;
}



/*inline static void set_wm_icon_size_hint (void)
{
#define N_VALS 6
  gulong vals[N_VALS];

  // min width, min height, max w, max h, width inc, height inc
  vals[0] = META_ICON_WIDTH;
  vals[1] = META_ICON_HEIGHT;
  vals[2] = META_ICON_WIDTH;
  vals[3] = META_ICON_HEIGHT;
  vals[4] = 0;
  vals[5] = 0;
  
  XChangeProperty (dpy, root, intern_atoms[NET_WM_ICON_SIZE], XA_CARDINAL,
                   32, PropModeReplace, (guchar*) vals, N_VALS);
  
#undef N_VALS
}*/

void cache_screen_info(void)
{
	display_width = XDisplayWidth(dpy, screen);
	display_height = XDisplayHeight(dpy, screen);
	xinerama_cache_screen_info();

	/* _NET_DESKTOP_GEOMETRY width, height, CARDINAL[2]/32 */
	/* Array of two cardinals that defines the common size of all desktops 
	 * (this is equal to the screen size if the Window Manager doesn't support 
	 * large desktops, otherwise it's equal to the virtual size of the desktop).
	 * This property SHOULD be set by the Window Manager.
	 A Pager can request a change in the desktop geometry by sending a 
	 _NET_DESKTOP_GEOMETRY client message to the root window.*/
	set_property_xy(root, intern_atoms[NET_DESKTOP_GEOMETRY], display_width, display_height);

	setNetWorkarea();

#ifdef DEBUG
	fprintf(stderr, "Screen size change: %dx%d\n", display_width, display_height);
#endif
}

static GPollFD x_event_fd;

/* Called before poll(). Returns TRUE if we have events to process right now,
 * and so we don't need to call poll.
 */
static gboolean x_event_prepare(GSource *source, gint *timeout)
{
	DBUG_VERBOSE("x_event_prepare");
	*timeout = -1;		// No timeout
	return XPending(dpy);
}

/* Called after poll(). Returns TRUE if we have something to process. */
static gboolean x_event_check(GSource *source) 
{
	DBUG_VERBOSE("x_event_check");
	if (x_event_fd.revents & G_IO_IN)
		return XPending(dpy);
	else
		return FALSE;
}

/* Called whenever x_event_prepare or x_event_check has returned TRUE.
 * ie: we have events to process!
 */
static gboolean x_event_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
{
	XEvent xevent;
	GIOChannel *channel = user_data;

	DBUG_VERBOSE("x_event_dispatch");


	if (quit)
	{
		dbg("leaving eventLoop");
		g_io_channel_unref(channel);
		g_main_loop_quit(main_loop);
		return FALSE;
	}

	if (reload)
	{
		reloadSettings();
		reload = FALSE;
		cancel_pending_raise();
	}

	while (XPending (dpy)) {
		DBUG_VERBOSE("getting event");
		XNextEvent(dpy, &xevent);
		handleEvent(&xevent);
		DBUG_VERBOSE("done event");
	}

	return TRUE;
}

static GSourceFuncs event_funcs = {
	x_event_prepare,
	x_event_check,
	x_event_dispatch,
	NULL
};



void open_display(const char *name)
{
	GSource *display_source;

	if (!(dpy = XOpenDisplay(display)))
		g_error("failed to open display %s!\n", display ? display : g_getenv("DISPLAY"));

	display_source = g_source_new(&event_funcs, sizeof(GSource));
	x_event_fd.fd = ConnectionNumber(dpy);
	x_event_fd.events = G_IO_IN;

	//g_print("Listening for events on X fd: %d\n", x_event_fd.fd);

	g_source_add_poll(display_source, &x_event_fd);
	g_source_set_can_recurse(display_source, TRUE);
	g_source_attach(display_source, NULL);

	// for debugging purposes:
	//XSynchronize(dpy, True);

	root = XDefaultRootWindow(dpy);
	screen = XDefaultScreen(dpy);
	depth = DefaultDepth(dpy, screen);
	cmap = DefaultColormap(dpy, screen);
	visual = DefaultVisual(dpy, screen);
	sn_init_display(dpy, screen);

	/* Tell root win we wanna be wm */

	XSetWindowAttributes sattr; /* for root win */
	sattr.event_mask =  SubstructureRedirectMask
                       |SubstructureNotifyMask
#ifdef HAVE_RANDR
                       |StructureNotifyMask
#endif
                       |PropertyChangeMask;

    
    XChangeWindowAttributes(dpy, root, CWEventMask, &sattr);
    XSelectInput(dpy, root, sattr.event_mask);

	initHints();
	
	//fprintf(stderr, "Dump Resource Manager:\n");
	//fprintf(stderr, XResourceManagerString(dpy));

	int dummy;

	shape = XShapeQueryExtension(dpy, &shape_event, &dummy);

#ifdef HAVE_RANDR
	xrandrSupported = XRRQueryExtension(dpy, &xrandrEventBase, &xrandrErrorBase);
	dbg("%d, %d, %d \n", xrandrSupported, xrandrEventBase, xrandrErrorBase);

	if (xrandrSupported)
	{
#if RANDR_MAJOR >= 1
		XRRSelectInput(dpy, root, True);
#else
		XRRScreenChangeSelectInput(dpy, root, True);
#endif
	}
#endif

	client_list = g_array_new(FALSE, FALSE, sizeof(Window));
	client_list_stacking = g_array_new(FALSE, FALSE, sizeof(Window));

	set_compliant();
	cache_screen_info();
	initModifiers(dpy);
}



static void sigaction_warn(const int sig, const struct sigaction *act, const gchar * text)
{
	if (sigaction(sig, act, NULL))
		g_warning("Couldn't install %s Handler!", text);
}


/** Main OroboROX Function.
 *  The C compiler typically starts here, and all of the work is done elsewhere.
 */
int main(int argc, char **argv)
{
	struct sigaction act;
	char *locale = "";

	decode_switches(argc, argv);

	puts("\n" PROJECT "\n(c) 2004 Guido Schimmels\n"
		 "(c) Distributed under the terms and conditions of the GPL\n");

	locale = setlocale(LC_ALL, "");

	stat_argv = argv;

	if (!locale || !strcmp(locale, "C") || !strcmp(locale, "POSIX") || !XSupportsLocale())
		use_fontset = False;
	else
		use_fontset = True;

	if (!XSetLocaleModifiers(""))
		g_warning("Cannot set locale modifiers for the X server.");

	act.sa_handler = handleSignal;
	act.sa_flags = 0;
	sigaction_warn(SIGINT, &act, "SIGINT");
	sigaction_warn(SIGTERM, &act, "SIGTERM");
	sigaction_warn(SIGHUP, &act, "SIGHUP");
	sigaction_warn(SIGSEGV, &act, "SIGSEGV");
	sigaction_warn(SIGCHLD, &act, "SIGCHLD");
	XSetErrorHandler(handleXError);

	GMainContext *main_context = g_main_context_default();
	open_display(display);

	//get_property_card32(root, intern_atoms[NET_WM_DESKTOP], &workspace);
	workspace = 0;				/* always start at first workspace, right ? */
	set_property_card32(root, intern_atoms[NET_CURRENT_DESKTOP], workspace);

	loadSettings();

	clientFrameAll();
	focus_workspace();
	update_focus();

	g_main_context_ref(main_context);
	main_loop = g_main_loop_new(main_context, FALSE);
	g_main_loop_ref(main_loop);
	g_main_loop_run(main_loop);
	g_main_loop_unref(main_loop);
	g_main_context_unref(main_context);

	cleanUp();
	return 0;
}

