/*
 * The Idea behind this is quite simple: when all managed windows are closed
 * and the WM exits, there should have been an unref call for each ref call. To
 * test do something like
 *
 * export DISPLAY=:whatever;
 * export MB_DEBUG=obj-ref,obj-unref
 * matchbox-window-manager-2-simple &
 * gedit
 * kill -TERM $(pidof gedit)
 * kill -TERM $(pidof matchbox-window-manager-2-simple)
 *
 * If you see '=== object count at exit x ===' then we either have a leak
 * (x > 0) or are unrefing a dangling pointer (x < 0).
 */
static void
signal_handler (int sig)
{
  if (sig == SIGTERM)
    {
      int count;

      mb_wm_object_unref (MB_WM_OBJECT (wm));
      mb_wm_object_dump ();

      exit (sig);
    }
}
int
main(int argc, char **argv)
{
#if MBWM_WANT_DEBUG
  struct sigaction sa;
  sigfillset(&sa.sa_mask);
  sa.sa_handler = signal_handler;
  sigaction(SIGTERM, &sa, NULL);
#endif

  mb_wm_object_init();

  wm = maemo_window_manager_new(argc, argv);
  mb_wm_init (wm);

  if (wm == NULL)
    mb_wm_util_fatal_error("OOM?");

  mb_wm_keys_binding_add_with_spec (wm,
				    "<alt>d",
				    key_binding_func,
				    NULL,
				    (void*)KEY_ACTION_TOGGLE_DESKTOP);

  mb_wm_keys_binding_add_with_spec (wm,
				    "<alt>n",
				    key_binding_func,
				    NULL,
				    (void*)KEY_ACTION_PAGE_NEXT);

  mb_wm_keys_binding_add_with_spec (wm,
				    "<alt>p",
				    key_binding_func,
				    NULL,
				    (void*)KEY_ACTION_PAGE_PREV);

  mb_wm_main_loop (wm);

  mb_wm_object_unref (MB_WM_OBJECT (wm));

#if MBWM_WANT_DEBUG
  mb_wm_object_dump ();
#endif

  return 1;
}
static void
mb_wm_decor_destroy (MBWMObject* obj)
{
  MBWMDecor       * decor = MB_WM_DECOR(obj);
  MBWMList        * l;
  MBWMMainContext * ctx   = decor->parent_client->wmref->main_ctx;

  if (decor->themedata && decor->destroy_themedata)
    {
      decor->destroy_themedata (decor, decor->themedata);
      decor->themedata = NULL;
      decor->destroy_themedata = NULL;
    }

  mb_wm_decor_detach (decor);

  for (l = decor->buttons; l; l = l->next)
    {
      mb_wm_decor_button_unrealize((MBWMDecorButton *)l->data);
      mb_wm_object_unref (MB_WM_OBJECT (l->data));
      /* we don't free, because the dispose handler of the decor button
       * removes itself */
    }

  if (decor->buttons)
    mb_wm_util_list_free (decor->buttons);

  if (decor->press_cb_id)
    mb_wm_main_context_x_event_handler_remove (ctx, ButtonPress,
					       decor->press_cb_id);

  if (decor->release_cb_id)
    mb_wm_main_context_x_event_handler_remove (ctx, ButtonRelease,
					     decor->release_cb_id);

  if (decor->xwin != None)
    {
      mb_wm_util_async_trap_x_errors(decor->parent_client->wmref->xdpy);
      XDestroyWindow (decor->parent_client->wmref->xdpy, decor->xwin);
      mb_wm_util_async_untrap_x_errors();
    }

  memset (decor, 0, sizeof (*decor));
}
int
main(int argc, char **argv)
{
  Display * dpy = NULL;

#if MBWM_WANT_DEBUG
  struct sigaction sa;
  sigfillset(&sa.sa_mask);
  sa.sa_handler = signal_handler;
  sigaction(SIGTERM, &sa, NULL);
#endif

  mb_wm_object_init();

#if USE_GTK
  printf ("initializing gtk\n");

  gtk_init (&argc, &argv);
  dpy = GDK_DISPLAY();
#endif

#if ENABLE_CLUTTER_COMPOSITE_MANAGER
  /*
   * If using clutter, we share the display connection, and hook
   * our xevent handler into the clutter main loop.
   *
   * If we are also doing gtk integration, we need to make clutter to
   * use the gtk display connection.
   */
  if (dpy)
    clutter_x11_set_display (dpy);

#if USE_GTK
  clutter_x11_disable_event_retrieval ();
#endif

  clutter_init (&argc, &argv);

  if (!dpy)
    dpy = clutter_x11_get_default_display ();

#endif

  wm = mb_wm_new_with_dpy (argc, argv, dpy);
  mb_wm_init (wm);
  
  if (wm == NULL)
    mb_wm_util_fatal_error("OOM?");

  mb_wm_keys_binding_add_with_spec (wm,
				    "<alt>d",
				    key_binding_func,
				    NULL,
				    (void*)KEY_ACTION_TOGGLE_DESKTOP);

  mb_wm_keys_binding_add_with_spec (wm,
				    "<alt>n",
				    key_binding_func,
				    NULL,
				    (void*)KEY_ACTION_PAGE_NEXT);

  mb_wm_keys_binding_add_with_spec (wm,
				    "<alt>p",
				    key_binding_func,
				    NULL,
				    (void*)KEY_ACTION_PAGE_PREV);

  mb_wm_main_loop(wm);

  mb_wm_object_unref (MB_WM_OBJECT (wm));

#if MBWM_WANT_DEBUG
  mb_wm_object_dump ();
#endif

  return 1;
}
static Bool
mb_wm_decor_button_press_handler (XButtonEvent    *xev,
				  void            *userdata)
{
  MBWMDecorButton *button = (MBWMDecorButton *)userdata;
  MBWMDecor       *decor  = button->decor;
  MBWindowManager *wm;
  MBWMList        *transients = NULL;
  Bool             retval = True;
  Bool             unref_parent_client = False;

  if (!button->realized || !decor || !decor->parent_client)
    return False;

  wm = decor->parent_client->wmref;

  mb_wm_object_ref (MB_WM_OBJECT(button));

  if (xev->window == decor->xwin)
    {
      int xmin, ymin, xmax, ymax;
      MBWMList *l;

      transients = l = mb_wm_client_get_transients (decor->parent_client);

      /* Ignore events on the main window decor if transients other than
       * input methods are present
       */
      while (l)
	{
	  MBWindowManagerClient * c = l->data;

	  if (MB_WM_CLIENT_CLIENT_TYPE (c) != MBWMClientTypeInput &&
	      mb_wm_client_is_modal (c))
	    {
	      retval = True;
	      goto done;
	    }

	  l = l->next;
	}

      xmin = button->geom.x;
      ymin = button->geom.y;
      xmax = button->geom.x + button->geom.width;
      ymax = button->geom.y + button->geom.height;

      if (xev->x < xmin ||
	  xev->x > xmax ||
	  xev->y < ymin ||
	  xev->y > ymax)
	{
	  XUngrabPointer(wm->xdpy, CurrentTime);
	  retval = True;
          if (xev->type != ButtonRelease
              || xev->x < decor->geom.x
              || xev->x > decor->geom.x+decor->geom.width
              || xev->y < decor->geom.y
              || xev->y > decor->geom.x+decor->geom.height)
            goto done;
	  g_debug("%s not on button -- send GRAB_TRANSFER", __FUNCTION__);
	  mb_wm_client_deliver_message (decor->parent_client,
					wm->atoms[MBWM_ATOM_MB_GRAB_TRANSFER],
					xev->time,
					xev->subwindow,
					xev->button, 0, 0);

	  XSync (wm->xdpy, False); /* Necessary */
	  goto done;
	}
      if (xev->type != ButtonPress)
        {
	  retval = True;
          goto done;
        }
      g_debug("%s on button", __FUNCTION__);

      if (button->state != MBWMDecorButtonStatePressed)
	{
	  button->state = MBWMDecorButtonStatePressed;
	  mb_wm_theme_paint_button (wm->theme, button);
	}

      if (button->press_activated)
	{
	  XUngrabPointer(wm->xdpy, CurrentTime);

	  mb_wm_client_deliver_message (decor->parent_client,
					wm->atoms[MBWM_ATOM_MB_GRAB_TRANSFER],
					xev->time,
					xev->subwindow,
					xev->button, 0, 0);

	  XSync (wm->xdpy, False); /* Necessary */

	  if (button->press)
	    button->press(wm, button, button->userdata);
	  else
	    mb_wm_decor_button_stock_button_action (button);
	}
      else
	{
	  XEvent ev;
	  int status;

	  /*
	   * First, call the custom function if any.
	   */
	  if (button->press)
	    button->press(wm, button, button->userdata);

	  mb_wm_util_async_trap_x_errors(wm->xdpy);
	  status = XGrabPointer(wm->xdpy, xev->subwindow, False,
			   ButtonPressMask|ButtonReleaseMask|
			   PointerMotionMask|EnterWindowMask|LeaveWindowMask,
			   GrabModeAsync,
			   GrabModeAsync,
			   None, None, CurrentTime);
	  mb_wm_util_async_untrap_x_errors();
	  if (status == GrabSuccess)
	    {
              /* set up release handler to catch ButtonRelease while we
               * are spinning the main loop */
	      decor->release_cb_id = mb_wm_main_context_x_event_handler_add (
				 wm->main_ctx,
			         xev->subwindow,
			         ButtonRelease,
			         (MBWMXEventFunc)mb_wm_decor_release_handler,
			         decor);
	      if (button->state == MBWMDecorButtonStateInactive)
		{
		  button->state = MBWMDecorButtonStatePressed;
		  mb_wm_theme_paint_button (wm->theme, button);
		}

	      /* reference the client for the loop, since it could be
	       * unreffed while we are handling events there */
              mb_wm_object_ref (MB_WM_OBJECT(decor->parent_client));
	      unref_parent_client = True;

	      for (;;)
		{
		  /*
		   * First of all, we make sure that all events are flushed
		   * out (this is necessary to ensure that all the events we
		   * are interested in are actually intercepted here).
		   */
		  XSync (wm->xdpy, False);

		  /*
		   * Someone might destroy the window while we are waiting for
		   * the events here.
		   */
		  if (!button->realized)
                  {
                    /* if the window disappeared, ungrab was done by X.
                       Just remove the handler */
                    mb_wm_main_context_x_event_handler_remove (
                                   wm->main_ctx, ButtonRelease,
			           decor->release_cb_id);
                    decor->release_cb_id = 0;
		    retval = False;
		    goto done;
		  }

                  if (!decor->release_cb_id)
                    {
                     /* the handler was called while we spinned the loop */
		     if (button->state == MBWMDecorButtonStatePressed)
		       {
                         button->state = MBWMDecorButtonStateInactive;
                         mb_wm_theme_paint_button (wm->theme, button);
                       }
		     retval = False;
		     goto done;
                    }

		  if (XCheckMaskEvent(wm->xdpy,
				      ButtonPressMask|ButtonReleaseMask|
				      PointerMotionMask|EnterWindowMask|
				      LeaveWindowMask,
				      &ev))
		    {
		      switch (ev.type)
			{
			case MotionNotify:
			  {
			    XMotionEvent *pev = (XMotionEvent*)&ev;

			    if (pev->x < xmin || pev->x > xmax ||
				pev->y < ymin || pev->y > ymax)
			      {
				if (button->state ==
				    MBWMDecorButtonStatePressed)
				  {
				    button->state =
				      MBWMDecorButtonStateInactive;
				    mb_wm_theme_paint_button (wm->theme,button);
				  }
			      }
			    else
			      {
				if (button->state !=
				    MBWMDecorButtonStatePressed)
				  {
				    button->state = MBWMDecorButtonStatePressed;
				    mb_wm_theme_paint_button (wm->theme,button);
				  }
			      }
			  }
			  break;
			case EnterNotify:
			  if (button->state == MBWMDecorButtonStateInactive)
			    {
			      button->state = MBWMDecorButtonStatePressed;
			      mb_wm_theme_paint_button (wm->theme, button);
			    }
			  break;
			case LeaveNotify:
			  if (button->state != MBWMDecorButtonStateInactive)
			    {
			      button->state = MBWMDecorButtonStateInactive;
			      mb_wm_theme_paint_button (wm->theme, button);
			    }
			  break;
			case ButtonRelease:
			  {
			    XButtonEvent *pev = (XButtonEvent*)&ev;

			    if (button->state != MBWMDecorButtonStateInactive)
			      {
				button->state = MBWMDecorButtonStateInactive;
				mb_wm_theme_paint_button (wm->theme, button);
			      }

			    XUngrabPointer (wm->xdpy, CurrentTime);
			    XSync (wm->xdpy, False); /* necessary */

                            mb_wm_main_context_x_event_handler_remove (
                                   wm->main_ctx, ButtonRelease,
			           decor->release_cb_id);
                            decor->release_cb_id = 0;

			    if (pev->x < xmin || pev->x > xmax ||
				pev->y < ymin || pev->y > ymax)
			      {
				retval = False;
				goto done;
			      }

			    if (button->release)
			      button->release(wm, button, button->userdata);
			    else
			      mb_wm_decor_button_stock_button_action (button);

			    retval = False;
			    goto done;
			  }
			}
		    }
		  else
		    {
		      /*
		       * No pending X event, so spin the main loop (this allows
		       * things like timers to work.
		       */
		      if (!mb_wm_main_context_spin_loop (wm->main_ctx))
                        /* no events, sleep a while so we don't busy loop */
                        g_usleep (1000 * 100);
		    }
		}
	    }
	}

      retval = False;
    }

 done:
  mb_wm_util_list_free (transients);
  mb_wm_object_unref (MB_WM_OBJECT(button));
  if (unref_parent_client)
    mb_wm_object_unref (MB_WM_OBJECT(decor->parent_client));
  return retval;
}