/* 
 *  Copyright (C) 2004 Girish Ramakrishnan All Rights Reserved.
 *
 * This 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 software 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 software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

// $Id: util.cpp,v 1.7 2005/01/29 09:56:08 cs19713 Exp $

#include "trace.h"
#include "util.h"

#include <X11/Xutil.h>
#include <X11/Xmu/WinUtil.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>

#include <stdio.h>

/*
 * Assert validity of the window id. Get window attributes for the heck of it
 * and see if the request went through.
 */
bool isValidWindowId(Display *display, Window w)
{
  XWindowAttributes attrib;
  return XGetWindowAttributes(display, w, &attrib) != 0;
}

/*
 * Checks if this window is a normal window (i.e) 
 * - Has a WM_STATE
 * - Not modal window
 * - Not a purely transient window (with no window type set)
 * - Not a special window (desktop/menu/util) as indicated in the window type
 */
bool isNormalWindow(Display *display, Window w)
{
  Atom __attribute__ ((unused)) type;
  int __attribute__ ((unused)) format;
  unsigned long __attribute__ ((unused)) left;
  Atom *data = NULL;
  unsigned long nitems;
  Window transient_for = None;

  static Atom wmState     = XInternAtom(display, "WM_STATE", False);
  static Atom windowState = XInternAtom(display, "_NET_WM_STATE", False);
  static Atom modalWindow = 
    XInternAtom(display, "_NET_WM_STATE_MODAL", False);

  static Atom windowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
  static Atom normalWindow = 
    XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", False);
  static Atom dialogWindow = 
    XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False);

  int ret = XGetWindowProperty(display, w, wmState, 0,
                               10, False, AnyPropertyType, &type, &format,
                               &nitems, &left, (unsigned char **) &data);

  TRACE("0x%x (%i)", (unsigned) w, (unsigned) w);

  if (ret != Success || data == NULL) return false;
  TRACE("\tHas WM_STATE");
  if (data) XFree(data);

  ret = XGetWindowProperty(display, w, windowState, 0,
                           10, False, AnyPropertyType, &type, &format,
                           &nitems, &left, (unsigned char **) &data);
  if (ret == Success)
  {
    unsigned int i;
    for (i = 0; i < nitems; i++)
    {
      if (data[i] == modalWindow) break;
    }
    XFree(data);
    if (i < nitems) { TRACE("\tMODAL"); return false; }
  }
  else TRACE("\tNo _NET_WM_STATE");

  XGetTransientForHint(display, w, &transient_for);
  TRACE("\tTransientFor=0x%x", (unsigned) transient_for);

  ret = XGetWindowProperty(display, w, windowType, 0,
                           10, False, AnyPropertyType, &type, &format,
                           &nitems, &left, (unsigned char **) &data);

  if ((ret == Success) && data)
  {
    unsigned int i;
    for (i = 0; i < nitems; i++)
    {
      if (data[i] != normalWindow && data[i] != dialogWindow) break;
    }
    XFree(data);
    TRACE("\tAbnormal = %i", (i == nitems));
    return (i == nitems);
  }
  else
    return (transient_for == None);
}

/*
 * Returns the contents of the _NET_WM_PID (which is supposed to contain the
 * process id of the application that created the window)
 */
pid_t pid(Display *display, Window w)
{
  Atom actual_type;
  int actual_format;
  unsigned long nitems, leftover;
  unsigned char *pid;
  pid_t pid_return = -1;

  if (XGetWindowProperty(display, w,
       XInternAtom(display, "_NET_WM_PID", False), 0,
       1, False, XA_CARDINAL, &actual_type,
       &actual_format, &nitems, &leftover, &pid) == Success)
  {
    if (pid) pid_return = *(pid_t *) pid;
    XFree(pid);
  }
  TRACE("0x%x has PID=%i", (unsigned) w, pid_return);
  return pid_return;
}

/*
 * Sends ClientMessage to a window
 */
void sendMessage(Display* display, Window to, Window w, char* type, 
                 int format, long mask, void* data, int size)
{
  XEvent ev;
  memset(&ev, 0, sizeof(ev));
  ev.xclient.type = ClientMessage;
  ev.xclient.window = w;
  ev.xclient.message_type = XInternAtom(display, type, True);
  ev.xclient.format = format;
  memcpy((char *) &ev.xclient.data, (const char *) data, size);
  XSendEvent(display, to, False, mask, &ev);
  XSync(display, False);
}

/*
 * The Grand Window Analyzer. Checks if window w has a expected pid of epid
 * or a expected name of ename
 */
bool analyzeWindow(Display *display, Window w, pid_t epid, const TQString &ename)
{
  XClassHint ch;
  pid_t apid = pid(display, w);

  TRACE("WID=0x%x EPID=%i APID=%i ExpectedName=%s", (unsigned) w, (unsigned) epid, (unsigned) apid,
                                         ename.local8Bit());
  if (epid == apid) return true;
  // Only analyze window name if no process pid was provided, to avoid associating
  // the wrong window to a given process
  if (epid) return false;

  // no plans to analyze windows without a name
  char *window_name = NULL;
  if (!XFetchName(display, w, &window_name)) return false;
  TRACE("Window has name [%s]", window_name);
  if (window_name) XFree(window_name); else return false;

  bool this_is_our_man = false;
  // lets try the program name
  if (XGetClassHint(display, w, &ch))
  {
    if (TQString(ch.res_name).find(ename, 0, FALSE) != -1)
    {
      TRACE("ResName [%s] matched", ch.res_name);
      this_is_our_man = true;
    }
    else if (TQString(ch.res_class).find(ename, 0, FALSE) != -1)
    {
      TRACE("ResClass [%s] matched", ch.res_class);
      this_is_our_man = true;
    } 
    else
    {
      // sheer desperation
      char *wm_name = NULL;
      XFetchName(display, w, &wm_name);
      if (wm_name && (TQString(wm_name).find(ename, 0, FALSE) != -1))
      {
        TRACE("WM_NAME [%s] matched", wm_name);
        this_is_our_man = true;
      }
    }

    if (ch.res_class) XFree(ch.res_class);
    if (ch.res_name) XFree(ch.res_name);
  }

  // its probably a good idea to check (obsolete) WM_COMMAND here
  return this_is_our_man;
}

Window activeWindow(Display *display)
{
  Atom active_window_atom = XInternAtom(display, "_NET_ACTIVE_WINDOW", True);
  Atom type = None;
  int format;
  unsigned long nitems, after;
  unsigned char *data = NULL;
  int screen = DefaultScreen(display);
  Window root = RootWindow(display, screen);
  int r = XGetWindowProperty(display, root, active_window_atom,0, 1,
          False, AnyPropertyType, &type, &format, &nitems, &after, &data);

  Window w = None;
  if ((r == Success) && data && (*(Window *)data != None))
    w = *(Window *)data;
  else
  {
    int revert;
    XGetInputFocus(display, &w, &revert);
  }
  TRACE("Active window is 0x%x", (unsigned) w);
  return w;
}

/*
 * Requests user to select a window by grabbing the mouse. A left click will
 * select the application. Clicking any other button will abort the selection
 */
Window selectWindow(Display *display, const char **err)
{
  int screen = DefaultScreen(display);
  Window root = RootWindow(display, screen);

  if (err) *err = NULL;

  Cursor cursor = XCreateFontCursor(display, XC_draped_box);
  if (cursor == None)
  {
    if (err) *err = "Failed to create XC_draped_box";
    return None;
  }

  if (XGrabPointer(display, root, False, ButtonPressMask | ButtonReleaseMask,
                   GrabModeSync, GrabModeAsync, None, cursor, CurrentTime)
      != GrabSuccess)
  {
    if (err) *err = "Failed to grab mouse";
    return None;
  }

  XAllowEvents(display, SyncPointer, CurrentTime);
  XEvent event;
  XWindowEvent(display, root, ButtonPressMask, &event);
  Window selected_window =
    (event.xbutton.subwindow == None) ? RootWindow(display, screen)
                                      : event.xbutton.subwindow;
  XUngrabPointer(display, CurrentTime);
  XFreeCursor(display, cursor);

  if (event.xbutton.button != Button1) return None;
  return XmuClientWindow(display, selected_window);
}

void subscribe(Display *display, Window w, long mask, bool set)
{
  Window root = RootWindow(display, DefaultScreen(display));
  XWindowAttributes attr;

  TRACE("Window=0x%x Mask=%ld Set=%i", (unsigned) w, mask, set);

  XGetWindowAttributes(display, w == None ? root : w, &attr);

  if (set && (attr.your_event_mask & mask == mask)) return;
  if (!set && (attr.your_event_mask | mask == attr.your_event_mask)) return;

  XSelectInput(display, w == None ? root : w, 
         set ? attr.your_event_mask | mask : attr.your_event_mask & mask);
  XSync(display, False);
}

// Returns the state of the SystemTray and the Wid if it exists
SysTrayState sysTrayStatus(Display *display, Window *tray)
{
  Screen *screen = XDefaultScreenOfDisplay(display);
  Window sys_tray;
  Atom selection = None;

  char temp[50];
  sprintf(temp, "_NET_SYSTEM_TRAY_S%i", XScreenNumberOfScreen(screen));
  selection = XInternAtom(display, temp, True);
  if (selection == None) return SysTrayAbsent;
  sys_tray = XGetSelectionOwner(display, selection);
  if (tray) *tray = sys_tray;
  return sys_tray == None ? SysTrayHidden : SysTrayPresent;
}

bool getCardinalProperty(Display *display, Window w, Atom prop, long *data)
{
  Atom type;
  int format;
  unsigned long nitems, bytes;
  unsigned char *d = NULL;

  if (XGetWindowProperty(display, w, prop, 0, 1, False, XA_CARDINAL,&type, 
                         &format, &nitems, &bytes, &d) == Success && d)
  {
    TRACE("%ld", *(long *)d);
    if (data) *data = *(long *)d;
    XFree(d);
    return true;
  }
  TRACE("FAILED");
  return false;
}