//===========================================
//  Lumina-DE source code
//  Copyright (c) 2014, Ken Moore
//  Available under the 3-clause BSD license
//  See the LICENSE file for full details
//===========================================
#include "LuminaX11.h"

#include <QString>
#include <QByteArray>
#include <QFile>
#include <QObject>
#include <QImage>

//X includes (these need to be last due to Qt compile issues)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/Xcomposite.h>

//XCB Library includes
#include <xcb/xcb.h>
#include <xcb/xcb_atom.h>
#include <xcb/xproto.h>
#include <xcb/xcb_ewmh.h>
#include <xcb/xcb_icccm.h>


//=====   WindowList() ========
QList<WId> LX11::WindowList(){
  QList<WId> output;
  output << LX11::GetClientList();

  
  //Validate windows
  int desk = LX11::GetCurrentDesktop();
  for(int i=0; i<output.length(); i++){
    bool remove=false;
    QString name = LX11::WindowClass(output[i]);
    if(output[i] == 0){ remove=true; }
    else if( desk >= 0 && LX11::WindowDesktop(output[i]) != desk){ remove = true; }
    else if( name=="Lumina Desktop Environment" ){ remove = true; }
    else if(name.startsWith("Lumina-")){
      //qDebug() << "Lumina Window:" << name << LX11::WindowName(output[i]);
      if(LX11::WindowName(output[i]).toLower()==name.toLower() ){ remove=true; }
    }
    /*else if( name.isEmpty() ){ 
      qDebug() << "Abnormal Window:" << output[i];
	qDebug() << " - Class:" << name;
	qDebug() << " - Text:" << LX11::WindowName(output[i]);
	qDebug() << " - Visible Name:" << LX11::WindowVisibleName(output[i]);
	qDebug() << " - Icon Name:" << LX11::WindowIconName(output[i]);
    }*/
    if(remove){
      //qDebug() << "Skip Window:" << output[i];
      output.removeAt(i);
      i--;
    }
  }
  //qDebug() << output;
  //Return the list
  return output;
}

// ===== GetClientList() =====
QList<WId> LX11::GetClientList(){
  QList<WId> output;
  //XCB Library
  /*qDebug() << "Get Client list cookie";
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_client_list_unchecked( LX11::EWMH_C(), 0);
  xcb_ewmh_get_windows_reply_t winlist;
  qDebug() << "Get client list";
  if( xcb_ewmh_get_client_list_reply( LX11::EWMH_C(), cookie, &winlist, NULL) ){
    qDebug() << " - Loop over items";
    for(unsigned int i=0; i<winlist.windows_len; i++){ output << winlist.windows[i]; }
  }*/
	
  //XLib
  Atom a = XInternAtom(QX11Info::display(), "_NET_CLIENT_LIST", true);
  Atom realType;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  int status = XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(), a, 0L, (~0L),
  	     false, AnyPropertyType, &realType, &format, &num, &bytes, &data);
  if( (status >= Success) && (num > 0) ){
    Window* array =  (Window*) data;
    for(unsigned int i=0; i<num; i++){
       output << array[i];
    }
    XFree(data);
  }
  return output;
}

// ===== GetClientStackingList() =====
QList<WId> LX11::GetClientStackingList(){
  QList<WId> output;
  Atom a = XInternAtom(QX11Info::display(), "_NET_CLIENT_LIST_STACKING", true);
  Atom realType;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  int status = XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(), a, 0L, (~0L),
  	     false, AnyPropertyType, &realType, &format, &num, &bytes, &data);
  if( (status >= Success) && (num > 0) ){
    Window* array =  (Window*) data;
    for(unsigned int i=0; i<num; i++){
       output << array[i];
    }
    XFree(data);
  }
  return output;
}

// ===== findChildren() =====
QList<WId> LX11::findChildren(WId parent, int levels){
  Window rootR, parentR;
  Window *childrenR;
  unsigned int num;
  int stat = XQueryTree(QX11Info::display(), parent, &rootR, &parentR, &childrenR, &num);
  QList<WId> output;
  if(stat != 0 && num > 0){
    for(int i=0; i<int(num); i++){
      output << childrenR[i];
      if(levels > 0){
       output << LX11::findChildren(childrenR[i], levels-1); //call this recursively
      }
    }
    XFree(childrenR);
  }	
  return output;
}

// ===== ActiveWindow() =====
WId LX11::ActiveWindow(){
  Display *disp = QX11Info::display();
  Atom SA = XInternAtom(disp, "_NET_ACTIVE_WINDOW", false);
  Atom type;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  int status = XGetWindowProperty( disp, QX11Info::appRootWindow() , SA, 0, ~(0L), false, AnyPropertyType,
  	  			&type, &format, &num, &bytes, &data);
  WId window=0;
  if(status >= Success && data){
    Window *array = (Window*) data;
    window = array[0];
    XFree(data);
  }
  return window;  		
}

// ===== SetNumberOfDesktops() =====
void LX11::SetNumberOfDesktops(int number){
  //XCB Library
	
  //XLib
  Display *display = QX11Info::display();
  Window rootWindow = QX11Info::appRootWindow();

  Atom atom = XInternAtom(display, "_NET_NUMBER_OF_DESKTOPS", False);
  XEvent xevent;
  xevent.type                 = ClientMessage;
  xevent.xclient.type         = ClientMessage; 
  xevent.xclient.display      = display;
  xevent.xclient.window       = rootWindow;
  xevent.xclient.message_type = atom;
  xevent.xclient.format       = 32;
  xevent.xclient.data.l[0]    = number;
  xevent.xclient.data.l[1]    = CurrentTime;
  xevent.xclient.data.l[2]    = 0;
  xevent.xclient.data.l[3]    = 0;
  xevent.xclient.data.l[4]    = 0;
  XSendEvent(display, rootWindow, False, SubstructureNotifyMask | SubstructureRedirectMask, &xevent);

  XFlush(display);	
}

// ===== SetCurrentDesktop() =====
void LX11::SetCurrentDesktop(int number){
  Display *display = QX11Info::display();
  Window rootWindow = QX11Info::appRootWindow();

  Atom atom = XInternAtom(display, "_NET_CURRENT_DESKTOP", False);
  XEvent xevent;
  xevent.type                 = ClientMessage;
  xevent.xclient.type         = ClientMessage; 
  xevent.xclient.display      = display;
  xevent.xclient.window       = rootWindow;
  xevent.xclient.message_type = atom;
  xevent.xclient.format       = 32;
  xevent.xclient.data.l[0]    = number;
  xevent.xclient.data.l[1]    = CurrentTime;
  xevent.xclient.data.l[2]    = 0;
  xevent.xclient.data.l[3]    = 0;
  xevent.xclient.data.l[4]    = 0;
  XSendEvent(display, rootWindow, False, SubstructureNotifyMask | SubstructureRedirectMask, &xevent);

  XFlush(display);	
}

// ===== GetNumberOfDesktops() =====
int LX11::GetNumberOfDesktops(){
  int number = -1;
  Atom a = XInternAtom(QX11Info::display(), "_NET_NUMBER_OF_DESKTOPS", true);
  Atom realType;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  int status = XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(), a, 0L, (~0L),
             false, AnyPropertyType, &realType, &format, &num, &bytes, &data);
  if( (status >= Success) && (num > 0) ){
    number = *data;
    XFree(data);
  }
  return number;
}

// ===== GetCurrentDesktop =====
int LX11::GetCurrentDesktop(){
  int number = -1;
  Atom a = XInternAtom(QX11Info::display(), "_NET_CURRENT_DESKTOP", true);
  Atom realType;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  int status = XGetWindowProperty(QX11Info::display(), QX11Info::appRootWindow(), a, 0L, (~0L),
             false, AnyPropertyType, &realType, &format, &num, &bytes, &data);
  if( (status >= Success) && (num > 0) ){
    number = data[0];
    XFree(data);
  }
  return number;	
}

// ===== ValidWindowEvent() =====
/*bool LX11::ValidWindowEvent(Atom evAtom){
	
  if(evAtom == XInternAtom(QX11Info::display(),"_NET_CLIENT_LIST",false) ){ return true; }
  else if( evAtom == XInternAtom(QX11Info::display(),"_NET_ACTIVE_WINDOW",false) ){ return true; }
  else if( evAtom == XInternAtom(QX11Info::display(),"_NET_WM_NAME",false) ){ return true; }
  else if( evAtom == XInternAtom(QX11Info::display(),"_NET_WM_VISIBLE_NAME",false) ){ return true; }
  else if( evAtom == XInternAtom(QX11Info::display(),"_NET_WM_ICON_NAME",false) ){ return true; }
  else if( evAtom == XInternAtom(QX11Info::display(),"_NET_WM_VISIBLE_ICON_NAME",false) ){ return true; }
  else{ return false; }
}*/

// ===== CloseWindow() =====
void LX11::CloseWindow(WId win){
  Display *display = QX11Info::display();
  Window rootWindow = QX11Info::appRootWindow();

  Atom atom = XInternAtom(display, "_NET_CLOSE_WINDOW", False);
  XEvent xevent;
  xevent.type                 = ClientMessage;
  xevent.xclient.type         = ClientMessage; 
  xevent.xclient.display      = display;
  xevent.xclient.window       = win;
  xevent.xclient.message_type = atom;
  xevent.xclient.format       = 32;
  xevent.xclient.data.l[0]    = CurrentTime;
  xevent.xclient.data.l[1]    = 2;
  xevent.xclient.data.l[2]    = 0;
  xevent.xclient.data.l[3]    = 0;
  xevent.xclient.data.l[4]    = 0;
  XSendEvent(display, rootWindow, False, SubstructureNotifyMask | SubstructureRedirectMask, &xevent);

  XFlush(display);
}

void LX11::KillWindow(WId win){
  XKillClient(QX11Info::display(),win);
}

// ===== IconifyWindow() =====
void LX11::IconifyWindow(WId win){
  XIconifyWindow(QX11Info::display(), win, QX11Info::appScreen());	
}

// ===== RestoreWindow() =====
void LX11::RestoreWindow(WId win){
  //XCB Library
    uint32_t val = XCB_STACK_MODE_ABOVE;
    xcb_configure_window(QX11Info::connection(),  win, XCB_CONFIG_WINDOW_STACK_MODE, &val); //raise it
    xcb_map_window(QX11Info::connection(), win); //map it
  //XLib
  //Display *disp = QX11Info::display();
    //XMapRaised(disp, win); //make it visible again and raise it to the top
}

// ===== ActivateWindow() =====
void LX11::ActivateWindow(WId win){
  Display *display = QX11Info::display();
  Window rootWindow = QX11Info::appRootWindow();

  Atom atom = XInternAtom(display, "_NET_ACTIVE_WINDOW", False);
  XEvent xevent;
  xevent.type                 = ClientMessage;
  xevent.xclient.type         = ClientMessage; 
  xevent.xclient.display      = display;
  xevent.xclient.window       = win;
  xevent.xclient.message_type = atom;
  xevent.xclient.format       = 32;
  xevent.xclient.data.l[0]    = 2;
  xevent.xclient.data.l[1]    = CurrentTime;
  xevent.xclient.data.l[2]    = LX11::ActiveWindow();
  xevent.xclient.data.l[3]    = 0;
  xevent.xclient.data.l[4]    = 0;
  XSendEvent(display, rootWindow, False, SubstructureNotifyMask | SubstructureRedirectMask, &xevent);

  XFlush(display);
}

// ===== ReservePanelLocation() =====
void LX11::ReservePanelLocation(WId win, int xstart, int ystart, int width, int height, QString loc){
  unsigned long strut[12];
  for(int i=0; i<12; i++){ strut[i] = 0; } //initialize it to all zeros
  if(loc=="top"){
    //top of screen
    strut[2] = height; //top width
    strut[8] = xstart; //top x start
    strut[9] = xstart+width; //top x end
  }else if(loc=="bottom"){
    //bottom of screen
    strut[3] = height; //bottom width
    strut[10] = xstart; //bottom x start
    strut[11] = xstart+width; //bottom x end
  }else if(loc=="left"){
    strut[0] = width;
    strut[4]=ystart;
    strut[5]=ystart+height;
  }else{ //right
    strut[1] = width;
    strut[6]=ystart;
    strut[7]=ystart+height;	  
  }
  Display *disp = QX11Info::display();
  Atom WTYPE = XInternAtom(disp, "_NET_WM_STRUT_PARTIAL", false);
  XChangeProperty( disp, win, WTYPE, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) strut, 12);
  
  //Also set the _NET_WM_STRUT property, just in case the WM does not use the newer type
  WTYPE = XInternAtom(disp, "_NET_WM_STRUT", false);
  XChangeProperty( disp, win, WTYPE, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) strut, 4);
}

// ===== SetAsSticky() =====
void LX11::SetAsSticky(WId win){
  //make this window "stick" to all virtual desktops
	
  //XCB Library
 // xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_APPEND, win, _NET_WM_STATE, XCB_ATOM, 32, 1, _NET_WM_STATE_STICKY);
	
  //XLib
  Display *disp = QX11Info::display();
  Atom stick = XInternAtom(disp, "_NET_WM_STATE_STICKY",false);
  Atom state = XInternAtom(disp, "_NET_WM_STATE", false);
  
  XChangeProperty(disp, win, state, XA_ATOM, 32, PropModeAppend, (unsigned char*) &stick, 1L);
}

// ===== SetAsPanel() =====
void LX11::SetAsPanel(WId win){
  //Set this window as the "Dock" type (for showing on top of everthing else)
  long data[1];
  Display *disp = QX11Info::display();
  Atom WTYPE = XInternAtom(disp, "_NET_WM_WINDOW_TYPE", false);
  Atom DOCK = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DOCK",false);
  data[0] = DOCK;
  XChangeProperty( disp, win, WTYPE, XA_ATOM, 32, PropModeReplace, (unsigned char *) &data, 1L);
}

// ===== SetAsDesktop() =====
void LX11::SetAsDesktop(WId win){
  //Set this window as the "Desktop" type (for showing below everthing else)
  long data[1];
  Display *disp = QX11Info::display();
  Atom WTYPE = XInternAtom(disp, "_NET_WM_WINDOW_TYPE", false);
  Atom DOCK = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DESKTOP",false);
  data[0] = DOCK;
  XChangeProperty( disp, win, WTYPE, XA_ATOM, 32, PropModeReplace, (unsigned char *) &data, 1L);
}

// ===== MoveResizeWindow() =====
void LX11::MoveResizeWindow(WId win, QRect rect){
  //Note: rect needs to be in global coordinates!!
  XMoveResizeWindow(QX11Info::display(), win, rect.x(), rect.y(), rect.width(), rect.height());
}

// ===== ResizeWindow() =====
void LX11::ResizeWindow(WId win, int width, int height){
  XResizeWindow(QX11Info::display(), win, width, height);
}

// ===== CreateWindow() =====
WId LX11::CreateWindow(WId parent, QRect rect){
  if(parent==0){ parent = QX11Info::appRootWindow(); }
  XWindowAttributes patt;
  XSetWindowAttributes att;
    att.background_pixel=0;
    att.border_pixel=0;
  //Try to set the same attributes as the parent
  if( ! XGetWindowAttributes(QX11Info::display(), parent, &patt) ){
    //Use some simple defaults instead
    att.colormap = None;
  }else{
    att.colormap = patt.colormap;
  }
  return XCreateWindow( QX11Info::display(), parent, rect.x(), rect.y(), rect.width(), rect.height(), 0, 
		CopyFromParent, InputOutput, CopyFromParent, CWColormap|CWBackPixel|CWBorderPixel, &att);
}

// ===== DestroyWindow() =====
void LX11::DestroyWindow(WId win){
  XDestroyWindow( QX11Info::display(), win);
}

// ===== EmbedWindow() =====
bool LX11::EmbedWindow(WId win, WId container){
  Display *disp = QX11Info::display();
  if(win==0 || container==0){ return false; }
  //Reparent the window
  //XCompositeRedirectSubwindows(disp, container, CompositeRedirectAutomatic); //container/window should be aware of each other
  //qDebug() << "Embed Window:" << win << container;
  XReparentWindow(disp, win, container,0,0);
  XSync(disp, false);
  //Check that the window has _XEMBED_INFO
  //qDebug() << " - check for _XEMBED_INFO";
  Atom embinfo = XInternAtom(disp, "_XEMBED_INFO",false);
  uchar *data=0;
  ulong num, bytes;
  int fmt;
  Atom junk;
  if(Success != XGetWindowProperty(disp, win, embinfo, 0, 2, false, embinfo, &junk, &fmt, &num, &bytes, &data) ){
    return false; //Embedding error (no info?)
  }
  if(data){ XFree(data); } // clean up any data found
	
  //Now send the embed event to the app
  //qDebug() << " - send _XEMBED event";
  XEvent ev;
	ev.xclient.type=ClientMessage;
	ev.xclient.serial=0;
	ev.xclient.send_event=true;
	ev.xclient.message_type = XInternAtom(disp, "_XEMBED", false);
	ev.xclient.window = win;
	ev.xclient.format = 32;
	ev.xclient.data.l[0] = CurrentTime;
	ev.xclient.data.l[1] = 0; //XEMBED_EMBEDDED_NOTIFY
	ev.xclient.data.l[2] = 0;
	ev.xclient.data.l[3] = container;
	ev.xclient.data.l[4] = 0;
  XSendEvent(disp, win, false, 0xFFFFFF, &ev);
  //Now setup any redirects and return
  //qDebug() << " - select Input";
  XSelectInput(disp, win, StructureNotifyMask); //Notify of structure changes
  //qDebug() << " - Composite Redirect";
  XCompositeRedirectWindow(disp, win, CompositeRedirectManual);
  //qDebug() << " - Done";
  return true;
}

// ===== UnembedWindow() =====
bool LX11::UnembedWindow(WId win){
  Display *disp = QX11Info::display();
  //Remove redirects
  XSelectInput(disp, win, NoEventMask);
  //Make sure it is invisible
  XUnmapWindow(disp, win);
  //Reparent the window back to the root window
  XReparentWindow(disp, win, QX11Info::appRootWindow(),0,0);
  XSync(disp, false);
  return true;
}

// ===== WindowClass() =====
QString LX11::WindowClass(WId win){
  XClassHint hint;
  QString clss;
  if(0 != XGetClassHint(QX11Info::display(), win, &hint) ){
    clss = QString(hint.res_class); //class is often capitalized properly, while name is not
    XFree(hint.res_name);
    XFree(hint.res_class);
  }
  return clss;
}

// ===== WindowName() =====
QString LX11::WindowName(WId win){
  QString nm = LX11::getNetWMProp(win, "_NET_WM_NAME");
  if(nm.isEmpty()){ 
    char *txt;
    if( XFetchName(QX11Info::display(), win, &txt) != 0){
      nm = QString(txt);
    }
    XFree(txt);
  }
  return nm;
}

// ===== WindowVisibleName() =====
QString LX11::WindowVisibleName(WId win){
  return LX11::getNetWMProp(win, "_NET_WM_VISIBLE_NAME");	
}

// ===== WindowIconName() =====
QString LX11::WindowIconName(WId win){
  return LX11::getNetWMProp(win, "_NET_WM_ICON_NAME");	
}

// ===== WindowVisibleIconName() =====
QString LX11::WindowVisibleIconName(WId win){
  return LX11::getNetWMProp(win, "_NET_WM_VISIBLE_ICON_NAME");	
}

// ===== WindowIcon() =====
QIcon LX11::WindowIcon(WId win){
  //Use the _NET_WM_ICON value instead of the WMHints pixmaps
	// - the pixmaps are very unstable and erratic
  QIcon icon;
  Display *disp = QX11Info::display();
  Atom type;
  Atom SA = XInternAtom(disp, "_NET_WM_ICON", false);
  int format;
  unsigned long num, bytes;
  unsigned long *data = 0;
  XGetWindowProperty( disp, win, SA, 0, LONG_MAX, False, AnyPropertyType,
  	  			&type, &format, &num, &bytes, (uchar**)&data);
  if(data != 0){
    //qDebug() << "Icon Data Found:" << win;
    ulong* dat = data;
    while(dat < data+num){ //consider the fact that there may be multiple graphical layers
    //Now convert it into a Qt image
    // - first 2 elements are width and height
    // - data in rows from left to right and top to bottom
      QImage image(dat[0], dat[1], QImage::Format_ARGB32); //initial setup
	dat+=2; //remember the first 2 element offset
	for(int i=0; i<image.byteCount()/4; ++i, ++dat){
	  ((uint*)image.bits())[i] = *dat; 
	}
      icon.addPixmap(QPixmap::fromImage(image)); //layer this pixmap onto the icon
    }
    XFree(data);
  }
  return icon;
}


// ===== WindowImage() =====
QPixmap LX11::WindowImage(WId win, bool useleader){
  QPixmap pix;
  Display *disp = QX11Info::display();
  WId leader = LX11::leaderWindow(win); //check for an alternate window that contains the image
  if(leader!=0 && useleader){ win = leader; } //use the leader window instead
  //First get the size of the window image (embedded in the window attributes)
  XWindowAttributes att; 
  if( 0 == XGetWindowAttributes(disp, win, &att) ){ return pix; } //invalid window attributes
  //Now extract the image
  XImage *xim = XGetImage(disp, win, 0,0, att.width, att.height, AllPlanes, ZPixmap);
  if(xim!=0){
    //Convert the X image to a Qt Image
    pix.convertFromImage( QImage( (const uchar*) xim->data, xim->width, xim->height, xim->bytes_per_line, QImage::Format_ARGB32_Premultiplied) );
    XDestroyImage(xim); //clean up
  }
  //Return the pixmap
  return pix;
}

// ===== GetNumberOfDesktops() =====
int LX11::WindowDesktop(WId win){
  int number = -1;
  Atom a = XInternAtom(QX11Info::display(), "_NET_WM_DESKTOP", true);
  Atom realType;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  int status = XGetWindowProperty(QX11Info::display(), win, a, 0L, (~0L),
             false, AnyPropertyType, &realType, &format, &num, &bytes, &data);
  if( (status >= Success) && (num > 0) ){
    number = *data;
    XFree(data);
  }
  return number;
}

// ===== GetWindowState() =====
LX11::WINDOWSTATE LX11::GetWindowState(WId win){
  LX11::WINDOWSTATE state = LX11::VISIBLE;
	
  //XCB Library (TO DO)

  
  //XLib
  Display *disp = QX11Info::display(); 
  Atom SA = XInternAtom(disp, "_NET_WM_STATE", true);
  Atom ATTENTION = XInternAtom(disp, "_NET_WM_STATE_DEMANDS_ATTENTION", false);
  Atom SKIPP = XInternAtom(disp, "_NET_WM_STATE_SKIP_PAGER", false);
  Atom HIDDEN = XInternAtom(disp, "_NET_WM_STATE_HIDDEN", false);
  Atom SKIPT = XInternAtom(disp, "_NET_WM_STATE_SKIP_TASKBAR", false);
  //Atom MODAL = XInternAtom(disp, "_NET_WM_STATE_MODAL", false); 
  Atom type;
  int format;
  unsigned long num, bytes;
  unsigned long *data = 0;
  
  int status = XGetWindowProperty( disp, win, SA, 0, ~(0L), false, AnyPropertyType,
  	  			&type, &format, &num, &bytes, (unsigned char**) &data);

  if(status >= Success && data){
    for(unsigned int i=0; i<num; i++){
      if(data[i] == SKIPP || data[i]==SKIPT){
      	state = LX11::IGNORE;
	//qDebug() << "Ignore Window:" << win;
      	break;
      }else if(data[i]==HIDDEN){
	//qDebug() << "Hidden Window:" << win;
	state = LX11::INVISIBLE;
      }else if(data[i]==ATTENTION){
	//qDebug() << "Attention Window: " << win;
	state = LX11::ATTENTION;
	break; //This state has priority over others
      }
    }
    XFree(data);
  }
  
  //LX11::WINDOWSTATE state = LX11::VISIBLE;
  if(state==LX11::VISIBLE){
    XWindowAttributes attr;
    if( 0 != XGetWindowAttributes(disp, win, &attr) ){
      if(attr.map_state==IsUnmapped || attr.map_state==IsUnviewable){
	state = LX11::INVISIBLE;
      }
    }
  }
  //If visible, check whether it is the active window
  if(state == LX11::VISIBLE){
    if(win == LX11::ActiveWindow()){
      state = LX11::ACTIVE;
    }	    
  }
  //(ALTERNATE) Also check whether the window has the URGENT flag set (override all other states)
  if(state!= LX11::ATTENTION){
    XWMHints *hints = XGetWMHints(disp, win);
    if(hints!=0){
      if(hints->flags & URGENCYHINT){
        qDebug() <<  "Found Urgent Flag:";
        state = LX11::ATTENTION;
      }
      XFree(hints);
    }
  }
  
  return state;  	
}

WId LX11::leaderWindow(WId win){
  //Get the client leader for this window if it has one
  Display *disp = QX11Info::display();
  Atom SA = XInternAtom(disp, "WM_CLIENT_LEADER", false);
  Atom type;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  WId leader = 0;
  int status = XGetWindowProperty( disp, win, SA, 0, ~(0L), false, AnyPropertyType,
  	  			&type, &format, &num, &bytes, &data);
  if(status >= Success && data){
    Window *array = reinterpret_cast<Window*> (data);
    if(array!=0){
      leader = array[0];
    }
    XFree(data);
  }
  return leader;
}

// ===== isNormalWindow() =====
bool LX11::isNormalWindow(WId win, bool includeDialogs){
  //Check to see if it is a "normal" window (as opposed to tooltips, popups, menus, etc)
  Display *disp = QX11Info::display();
  Atom SA = XInternAtom(disp, "_NET_WM_WINDOW_TYPE", false);
  Atom NORMAL = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_NORMAL", false);
  Atom DIALOG = XInternAtom(disp, "_NET_WM_WINDOW_TYPE_DIALOG", false);
  Atom type;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  int status = XGetWindowProperty( disp, win, SA, 0, ~(0L), false, AnyPropertyType,
  	  			&type, &format, &num, &bytes, &data);
	
  bool isNormal = true; //assume normal is true if unlisted (the standard use)
  if(status >= Success && data){
    for(unsigned int i=0; i<num; i++){
      if( data[i] == NORMAL ){ isNormal = true; break; }
      else if(data[i]==DIALOG && includeDialogs){ isNormal=true; break; }
      else{ isNormal = false; } //don't break here, might be explicitly listed next
    }
    XFree(data);
  }
  return isNormal;
}

// ===== startSystemTray() =====
WId LX11::startSystemTray(int screen){
  qDebug() << "Starting System Tray:" << screen;
  //Setup the freedesktop standards compliance
  Display *disp = QX11Info::display();
  Window root = QX11Info::appRootWindow();
  
  //Get the appropriate atom for this screen
  QString str = QString("_NET_SYSTEM_TRAY_S%1").arg(QString::number(screen));
  qDebug() << "Default Screen Atom Name:" << str;
  Atom _NET_SYSTEM_TRAY_S = XInternAtom(disp,str.toLatin1(),false);
  //Make sure that there is no other system tray running
  if(XGetSelectionOwner(disp, _NET_SYSTEM_TRAY_S) != None){
    qWarning() << "An alternate system tray is currently in use";
    return 0;
  }
  //Create a simple window to register as the tray (not visible - just off the screen)
  Window LuminaSessionTrayID = XCreateSimpleWindow(disp, root,-1,-1,1,1,0,0,0);
  //register this widget as the system tray
  XSetSelectionOwner(disp, _NET_SYSTEM_TRAY_S, LuminaSessionTrayID, CurrentTime);
  //Make sure that it was registered properly
  if(XGetSelectionOwner(disp, _NET_SYSTEM_TRAY_S) != LuminaSessionTrayID){
    qWarning() << "Could not register the system tray";
    XDestroyWindow(disp, LuminaSessionTrayID);
    return 0;
  }
  //Now register the orientation of the system tray
  int horz = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
  XChangeProperty(disp, LuminaSessionTrayID, XInternAtom(disp,"_NET_SYSTEM_TRAY_ORIENTATION",False),
  	  	XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &horz, 1);
  //Now get the visual ID for the system tray
  XVisualInfo *XVI = new XVisualInfo;
    XVI->screen = QX11Info::appScreen();
    XVI->depth = 32;
    XVI->c_class = TrueColor;
  int num;
  XVI = XGetVisualInfo(disp, VisualScreenMask | VisualDepthMask | VisualClassMask , XVI, &num);
  VisualID vis = 0;
  if(XVI != 0){
    XRenderPictFormat *fmt;
    for(int i=0; i<num; i++){
      fmt = XRenderFindVisualFormat(disp, XVI[i].visual);
      if( (fmt !=0) && (fmt->type == PictTypeDirect) && (fmt->direct.alphaMask!=0) ){
        vis = XVI[i].visualid;
        break;
      }
    }
  }
  XFree(XVI); //done with this - clean it up
  //Now register the visual ID
  if(vis!=0){
    XChangeProperty(disp, LuminaSessionTrayID, XInternAtom(disp,"_NET_SYSTEM_TRAY_VISUAL",true),
  	  	XA_VISUALID, 32, PropModeReplace, (unsigned char*) &vis, 1);	  
  }
  //Finally, send out an X event letting others know that the system tray is up and running
  XClientMessageEvent msg;
    msg.type = ClientMessage;
    msg.window = root;
    msg.message_type = XInternAtom(disp,"MANAGER",true);
    msg.format = 32;
    msg.data.l[0] = CurrentTime;
    msg.data.l[1] = _NET_SYSTEM_TRAY_S;
    msg.data.l[2] = LuminaSessionTrayID;
    msg.data.l[3] = 0;
    msg.data.l[4] = 0;
  XSendEvent(disp, root, False, StructureNotifyMask, (XEvent*)&msg);
  
  //Success
  return LuminaSessionTrayID;
}

// ===== closeSystemTray() =====
void LX11::closeSystemTray(WId trayID){
  XDestroyWindow(QX11Info::display(), trayID);
}

// ===== findOrphanTrayWindows() =====
QList<WId> LX11::findOrphanTrayWindows(){
  //Scan the first level of root windows and see if any of them
    // are tray apps waiting to be embedded
  Display *disp = QX11Info::display();
  QList<WId> wins = LX11::findChildren(QX11Info::appRootWindow(), 0); //only go one level deep
  Atom embinfo = XInternAtom(disp, "_XEMBED_INFO",false);	
  for(int i=0; i<wins.length(); i++){
    uchar *data=0;
    ulong num, bytes;
    int fmt;
    Atom junk;
    bool ok = (Success != XGetWindowProperty(disp, wins[i], embinfo, 0, 2, false, embinfo, &junk, &fmt, &num, &bytes, &data) );
    if(ok){ //successfully found info
      ok = (data!=0);
    }
    
    if(!ok){
      //no embed info - not a tray app
      qDebug() << "Remove non-xembed window:" << wins[i];
      wins.removeAt(i);
      i--;
    }
    if(data){ XFree(data); } // clean up any data found
  }
  return wins; //anything left in the list must be a tray app that is still unembedded (root window parent)
}

// ===== getNetWMProp() =====
QString LX11::getNetWMProp(WId win, QString prop){
  Display *disp = QX11Info::display();
  Atom NA = XInternAtom(disp, prop.toUtf8(), false);
  Atom utf = XInternAtom(disp, "UTF8_STRING", false);
  Atom type;
  int format;
  unsigned long num, bytes;
  unsigned char *data = 0;
  int status = XGetWindowProperty( disp, win, NA, 0, 65536, false, utf,
  	  			&type, &format, &num, &bytes, &data);
  QString property;
  if(status >= Success && data){
    property = QString::fromUtf8( (char *) data);
    XFree(data);
  }
  return property;
}

//===============================
//===============================
// XCB LIBRARY FUNCTIONS
//===============================
//===============================
LXCB::LXCB(){
   xcb_intern_atom_cookie_t *cookie = xcb_ewmh_init_atoms(QX11Info::connection(), &EWMH);
   if(!xcb_ewmh_init_atoms_replies(&EWMH, cookie, NULL) ){
     qDebug() << "Error with XCB atom initializations";
   }else{
     qDebug() << "Number of XCB screens:" << EWMH.nb_screens;
   }
}
LXCB::~LXCB(){
  xcb_ewmh_connection_wipe(&EWMH);
}

// === WindowList() ===
QList<WId> LXCB::WindowList(bool rawlist){
  QList<WId> output;
  //qDebug() << "Get Client list cookie";
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_client_list_unchecked( &EWMH, 0);
  xcb_ewmh_get_windows_reply_t winlist;
  //qDebug() << "Get client list";
  if( 1 == xcb_ewmh_get_client_list_reply( &EWMH, cookie, &winlist, NULL) ){
    //qDebug() << " - Loop over items";
    unsigned int wkspace = CurrentWorkspace();
    for(unsigned int i=0; i<winlist.windows_len; i++){ 
      //Filter out the Lumina Desktop windows
      if(WindowClass(winlist.windows[i]) == "Lumina Desktop Environment"){ continue; }
      //Also filter out windows not on the active workspace
      else if( (WindowWorkspace(winlist.windows[i])!=wkspace) && !rawlist ){ continue; }
      else{
        output << winlist.windows[i]; 
      }
    }
  }
  return output;
}

// === CurrentWorkspace() ===
unsigned int LXCB::CurrentWorkspace(){
  //qDebug() << "Get Current Workspace";
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_current_desktop_unchecked(&EWMH, 0);
  uint32_t wkspace = 0;
  xcb_ewmh_get_current_desktop_reply(&EWMH, cookie, &wkspace, NULL);
  //qDebug() << " - done:" << wkspace;
  return wkspace;
}

// === ActiveWindow() ===
WId LXCB::ActiveWindow(){
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_active_window_unchecked(&EWMH, 0);
  xcb_window_t actwin;
  if(1 == xcb_ewmh_get_active_window_reply(&EWMH, cookie, &actwin, NULL) ){
    return actwin;
  }else{
    return 0; //invalid ID/failure
  }
}

// === RegisterVirtualRoots() ===
void LXCB::RegisterVirtualRoots(QList<WId> roots){
  //First convert the QList into the proper format
  xcb_window_t *list = new xcb_window_t[ roots.length() ];
  for(int i=0; i<roots.length(); i++){
    list[i] = roots[i]; //move from the QList to the array
  }
  //Now set the property
  xcb_ewmh_set_virtual_roots(&EWMH, 0, roots.length(), list);
  //Now delete the temporary array from memory
  delete list;
}

// === WindowClass() ===
QString LXCB::WindowClass(WId win){
  QString out;
  xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_class_unchecked(QX11Info::connection(), win);
  if(cookie.sequence == 0){ return out; } 
  xcb_icccm_get_wm_class_reply_t value;
  if( 1== xcb_icccm_get_wm_class_reply( QX11Info::connection(), cookie, &value, NULL) ){
    out = QString::fromUtf8(value.class_name);
    xcb_icccm_get_wm_class_reply_wipe(&value);
  }
  return out;
}

// === WindowWorkspace() ===
unsigned int LXCB::WindowWorkspace(WId win){
  //qDebug() << "Get Window Workspace";
  uint32_t wkspace = 0;
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_desktop_unchecked(&EWMH, win);
  if(cookie.sequence == 0){ return wkspace; } 
  xcb_ewmh_get_wm_desktop_reply(&EWMH, cookie, &wkspace, NULL);
  //qDebug() << " - done: " << wkspace;
  return wkspace;	
}

// === WindowGeometry() ===
QRect LXCB::WindowGeometry(WId win, bool includeFrame){
  QRect geom;
  xcb_get_geometry_cookie_t cookie = xcb_get_geometry(QX11Info::connection(), win);
  xcb_get_geometry_reply_t *reply = xcb_get_geometry_reply(QX11Info::connection(), cookie, NULL);
  //qDebug() << "Get Window Geometry:" << reply;
  if(reply != 0){
    geom = QRect(0, 0, reply->width, reply->height); //make sure to use the origin point for the window
    //qDebug() << " - "<<reply->x << reply->y << reply->width << reply->height;
    free(reply);
    if(includeFrame){
      //Need to add/include the frame extents as well (assuming the frame info is available)
      xcb_get_property_cookie_t cookie = xcb_ewmh_get_frame_extents_unchecked(&EWMH, win);
      if(cookie.sequence != 0){
	xcb_ewmh_get_extents_reply_t frame;
        if(1== xcb_ewmh_get_frame_extents_reply(&EWMH, cookie, &frame, NULL) ){
	    //adjust the origin point to account for the frame
	    geom.translate(-frame.left, -frame.top); //move to the orign point for the frame
	    //adjust the size (include the frame sizes)
	    //geom.setWidth( geom.width() + frame.left + frame.right );
	    //geom.setHeight( geom.height() + frame.top + frame.bottom );
	}
	//qDebug() << " - Frame:" << frame.left << frame.right << frame.top << frame.bottom;
	//qDebug() << " - Modified with Frame:" << geom.x() << geom.y() << geom.width() << geom.height();
      }
    }
    //Now need to convert this to absolute coordinates (not parent-relavitve)
      xcb_translate_coordinates_cookie_t tcookie = xcb_translate_coordinates(QX11Info::connection(), win, QX11Info::appRootWindow(), geom.x(), geom.y());
      xcb_translate_coordinates_reply_t *trans = xcb_translate_coordinates_reply(QX11Info::connection(), tcookie, NULL);
      if(trans!=0){
	//qDebug() << " - Got Translation:" << trans->dst_x << trans->dst_y;
	//Replace the origin point with the global position (sizing remains the same)
        geom.moveLeft(trans->dst_x); //adjust X coordinate (no size change)
	geom.moveTop(trans->dst_y); //adjust Y coordinate (no size change)
	free(trans);
      }
  }else{
    //Need to do another catch for this situation (probably not mapped yet)
  }
  
  return geom;
}

// === WindowState() ===
LXCB::WINDOWSTATE LXCB::WindowState(WId win){
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_state_unchecked(&EWMH, win);
  if(cookie.sequence == 0){ return IGNORE; } 
  xcb_ewmh_get_atoms_reply_t states;
  WINDOWSTATE cstate = IGNORE;
  //First Check for special states (ATTENTION in particular);
  if( 1 == xcb_ewmh_get_wm_state_reply(&EWMH, cookie, &states, NULL) ){
    for(unsigned int i=0; i<states.atoms_len; i++){
      if(states.atoms[i] == EWMH._NET_WM_STATE_DEMANDS_ATTENTION){ cstate = ATTENTION; break; } //nothing more urgent - stop here
      else if(states.atoms[i] == EWMH._NET_WM_STATE_HIDDEN){ cstate = INVISIBLE; }
    }
  }
  //Now check to see if the window is the active one
  if(cstate == IGNORE){
    xcb_get_property_cookie_t cookie = xcb_ewmh_get_active_window_unchecked(&EWMH, 0);
    xcb_window_t actwin;
    if(1 == xcb_ewmh_get_active_window_reply(&EWMH, cookie, &actwin, NULL) ){
      if(actwin == win){ cstate = ACTIVE; }
    }
  }
  //Now check for ICCCM Urgency hint (not sure if this is still valid with EWMH instead)
  /*if(cstate == IGNORE){
    xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(QX11Info::connection(), win);
    xcb_icccm_wm_hints_t hints;
    if( 1== xcb_icccm_get_wm_hints_reply(QX11Info::connection(), cookie, &hints, NULL) ){
      if(xcb_icccm_wm_hints_get_urgency(hints) ){ cstate = ATTENTION; };
    }
  }*/
  //Now check for standard visible/invisible attribute (current mapping state)
  if(cstate == IGNORE){
    xcb_get_window_attributes_cookie_t cookie = xcb_get_window_attributes(QX11Info::connection(), win);
    xcb_get_window_attributes_reply_t *attr = xcb_get_window_attributes_reply(QX11Info::connection(), cookie, NULL);
    if(attr!=0){
      if(attr->map_state==XCB_MAP_STATE_VIEWABLE){ cstate = VISIBLE; }
      else{ cstate = INVISIBLE; }
      free(attr);	    
    }
  }
  return cstate;
}

// === WindowVisibleIconName() ===
QString LXCB::WindowVisibleIconName(WId win){ //_WM_VISIBLE_ICON_NAME
  QString out;
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_icon_name_unchecked(&EWMH, win);
  if(cookie.sequence == 0){ return out; } 
  xcb_ewmh_get_utf8_strings_reply_t data;
  if( 1 == xcb_ewmh_get_wm_visible_icon_name_reply(&EWMH, cookie, &data, NULL) ){
      out = QString::fromUtf8(data.strings, data.strings_len);
  }
  return out;
}

// === WindowIconName() ===
QString LXCB::WindowIconName(WId win){ //_WM_ICON_NAME
  QString out;
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_icon_name_unchecked(&EWMH, win);
  if(cookie.sequence == 0){ return out; } 
  xcb_ewmh_get_utf8_strings_reply_t data;
  if( 1 == xcb_ewmh_get_wm_icon_name_reply(&EWMH, cookie, &data, NULL) ){
      out = QString::fromUtf8(data.strings, data.strings_len);
  }
  return out;
}

// === WindowVisibleName() ===
QString LXCB::WindowVisibleName(WId win){ //_WM_VISIBLE_NAME
  QString out;
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_visible_name_unchecked(&EWMH, win);
  if(cookie.sequence == 0){ return out; } 
  xcb_ewmh_get_utf8_strings_reply_t data;
  if( 1 == xcb_ewmh_get_wm_visible_name_reply(&EWMH, cookie, &data, NULL) ){
      out = QString::fromUtf8(data.strings, data.strings_len);
  }
  return out;
}

// === WindowName() ===
QString LXCB::WindowName(WId win){ //_WM_NAME
  QString out;
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_name_unchecked(&EWMH, win);
  if(cookie.sequence == 0){ return out; } 
  xcb_ewmh_get_utf8_strings_reply_t data;
  if( 1 == xcb_ewmh_get_wm_name_reply(&EWMH, cookie, &data, NULL) ){
      out = QString::fromUtf8(data.strings, data.strings_len);
  }
  return out;
}

// === WindowIsMaximized() ===
bool LXCB::WindowIsMaximized(WId win){
  //See if the _NET_WM_STATE_MAXIMIZED_[VERT/HORZ] flags are set on the window
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_wm_state_unchecked(&EWMH, win);
  if(cookie.sequence == 0){ return false; } 
  xcb_ewmh_get_atoms_reply_t states;
  if( 1 == xcb_ewmh_get_wm_state_reply(&EWMH, cookie, &states, NULL) ){
    //Loop over the states
    for(unsigned int i=0; i<states.atoms_len; i++){
      if(states.atoms[i] == EWMH._NET_WM_STATE_MAXIMIZED_HORZ \
	      || states.atoms[i] == EWMH._NET_WM_STATE_MAXIMIZED_VERT ){
	return true;
      }
    }
  }
  return false;
}

// === SetAsSticky() ===
void LXCB::SetAsSticky(WId win){
  //Need to send a client message event for the window so the WM picks it up
  xcb_client_message_event_t event;
    event.response_type = XCB_CLIENT_MESSAGE;
    event.format = 32;
    event.window = win;
    event.type = EWMH._NET_WM_STATE;
    event.data.data32[0] = 1; //set to enabled
    event.data.data32[1] = EWMH._NET_WM_STATE_STICKY;
    event.data.data32[2] = 0;
    event.data.data32[3] = 0;
    event.data.data32[4] = 0;

  xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(),  XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event);
	
  //This method changes the property on the window directly - the WM is not aware of it	
  /*xcb_change_property( QX11Info::connection(), XCB_PROP_MODE_APPEND, win, EWMH._NET_WM_STATE, XCB_ATOM_ATOM, 32, 1, &(EWMH._NET_WM_STATE_STICKY) );
  xcb_flush(QX11Info::connection()); //apply it right away*/
}

// === SetAsPanel() ===
void LXCB::SetAsPanel(WId win){
  //Disable Input focus (panel activation ruins task manager window detection routines)
  //  - Disable Input flag in WM_HINTS
  xcb_icccm_wm_hints_t hints;
  qDebug() << " - Disable WM_HINTS input flag";
  xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(QX11Info::connection(), win);
  qDebug() << " -- got cookie";
  if(1 == xcb_icccm_get_wm_hints_reply(QX11Info::connection(), cookie, &hints, NULL) ){
    qDebug() << " -- Set no inputs flag";
     xcb_icccm_wm_hints_set_input(&hints, False); //set no input focus
     xcb_icccm_set_wm_hints(QX11Info::connection(), win, &hints); //save hints back to window
    qDebug() << " -- Free the hints structure";
     free(&hints); //free up hints structure
  }
  //  - Remove WM_TAKE_FOCUS from the WM_PROTOCOLS for the window
  //  - - Generate the necessary atoms
  qDebug() << " - Generate WM_PROTOCOLS and WM_TAKE_FOCUS atoms";
  xcb_atom_t WM_PROTOCOLS, WM_TAKE_FOCUS; //the two atoms needed
  xcb_intern_atom_reply_t *preply = xcb_intern_atom_reply(QX11Info::connection(), \
			xcb_intern_atom(QX11Info::connection(), 0, 12, "WM_PROTOCOLS"), NULL);
  xcb_intern_atom_reply_t *freply = xcb_intern_atom_reply(QX11Info::connection(), \
			xcb_intern_atom(QX11Info::connection(), 0, 13, "WM_TAKE_FOCUS"), NULL); 
  bool gotatoms = false;
  if(preply && freply){
    WM_PROTOCOLS = preply->atom;
    WM_TAKE_FOCUS = freply->atom;
    free(preply);
    free(freply);
    gotatoms = true;
    qDebug() << " -- success";
  }
  //  - - Now update the protocols for the window
  if(gotatoms){ //requires the atoms
    qDebug() << " - Get WM_PROTOCOLS";
    xcb_icccm_get_wm_protocols_reply_t proto;
    if( 1 == xcb_icccm_get_wm_protocols_reply(QX11Info::connection(), \
			xcb_icccm_get_wm_protocols_unchecked(QX11Info::connection(), win, WM_PROTOCOLS), \
			&proto, NULL) ){
	
      //Found the current protocols, see if it has the focus atom set
			//remove the take focus atom and re-save them
      bool needremove = false;
      //Note: This first loop is required so that we can initialize the modified list with a valid size
      qDebug() << " -- Check current protocols";
      for(unsigned int i=0; i<proto.atoms_len; i++){
        if(proto.atoms[i] == WM_TAKE_FOCUS){ needremove = true; break;}
      }
      if(needremove){
	qDebug() << " -- Remove WM_TAKE_FOCUS protocol";
	xcb_atom_t *protolist = new xcb_atom_t[proto.atoms_len-1];
	int num = 0;
	for(unsigned int i=0; i<proto.atoms_len; i++){
	  if(proto.atoms[i] != WM_TAKE_FOCUS){
	    protolist[num] = proto.atoms[i];
	    num++;
	  }
	}
	qDebug() << " -- Re-save modified protocols";
	xcb_icccm_set_wm_protocols(QX11Info::connection(), win, WM_PROTOCOLS, num, protolist);
      }
      qDebug() << " -- Clear protocols reply";
      xcb_icccm_get_wm_protocols_reply_wipe(&proto);
    }//end of get protocols check
  } //end of gotatoms check
  //Make sure it has the "dock" window type
  //  - get the current window types (Not necessary, only 1 type of window needed)
  
  //  - set the adjusted window type(s)
  qDebug() << " - Adjust window type";
  xcb_atom_t list[1]; 
    list[0] = EWMH._NET_WM_WINDOW_TYPE_DOCK;
  xcb_ewmh_set_wm_window_type(&EWMH, win, 1, list);
  
  //Make sure it is on all workspaces
  qDebug() << " - Set window as sticky";
  SetAsSticky(win);
	
}

// === CloseWindow() ===
void LXCB::CloseWindow(WId win){
  xcb_ewmh_request_close_window(&EWMH, 0, win, QX11Info::getTimestamp(), XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER);
}

// === MinimizeWindow() ===
void LXCB::MinimizeWindow(WId win){ //request that the window be unmapped/minimized
  //Note: Fluxbox completely removes this window from the open list if unmapped manually
 // xcb_unmap_window(QX11Info::connection(), win);
  //xcb_flush(QX11Info::connection()); //make sure the command is sent out right away
	
  //Need to send a client message event for the window so the WM picks it up
  xcb_client_message_event_t event;
    event.response_type = XCB_CLIENT_MESSAGE;
    event.format = 32;
    event.window = win;
    event.type = EWMH._NET_WM_STATE;
    event.data.data32[0] = 1; //set to toggle (switch back and forth)
    event.data.data32[1] = EWMH._NET_WM_STATE_HIDDEN;
    event.data.data32[2] = 0;
    event.data.data32[3] = 0;
    event.data.data32[4] = 0;

  xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(),  XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event);
}

// === ActivateWindow() ===
void LXCB::ActivateWindow(WId win){ //request that the window become active
  //First need to get the currently active window
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_active_window_unchecked(&EWMH, 0);
  xcb_window_t actwin;
  if(1 != xcb_ewmh_get_active_window_reply(&EWMH, cookie, &actwin, NULL) ){
    actwin = 0;
  }	
  if(actwin == win){ return; } //requested window is already active
  
//Need to send a client message event for the window so the WM picks it up
  xcb_client_message_event_t event;
    event.response_type = XCB_CLIENT_MESSAGE;
    event.format = 32;
    event.window = win; //window to activate
    event.type = EWMH._NET_ACTIVE_WINDOW;
    event.data.data32[0] = 2; //pager/direct user interaction
    event.data.data32[1] = QX11Info::getTimestamp(); //current timestamp
    event.data.data32[2] = actwin; //currently active window (0 if none)
    event.data.data32[3] = 0;
    event.data.data32[4] = 0;

  xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(),  XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event);
	
}

// === MaximizeWindow() ===
void LXCB::MaximizeWindow(WId win, bool flagsonly){ //request that the window become maximized
	
  if(flagsonly){
    //Directly set the flags on the window (bypassing the WM)
    xcb_atom_t list[2];
      list[0] = EWMH._NET_WM_STATE_MAXIMIZED_VERT;
      list[1] = EWMH._NET_WM_STATE_MAXIMIZED_HORZ;
    xcb_ewmh_set_wm_state(&EWMH, win, 2, list);
	  
  }else{
    //Need to send a client message event for the window so the WM picks it up
    xcb_client_message_event_t event;
    event.response_type = XCB_CLIENT_MESSAGE;
    event.format = 32;
    event.window = win;
    event.type = EWMH._NET_WM_STATE;
    event.data.data32[0] = 2; //set to toggle (switch back and forth)
    event.data.data32[1] = EWMH._NET_WM_STATE_MAXIMIZED_VERT;
    event.data.data32[2] = EWMH._NET_WM_STATE_MAXIMIZED_HORZ;
    event.data.data32[3] = 0;
    event.data.data32[4] = 0;

    xcb_send_event(QX11Info::connection(), 0, QX11Info::appRootWindow(),  XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (const char *) &event);
  }
}

// === MoveResizeWindow() ===
void LXCB::MoveResizeWindow(WId win, QRect geom){
  //NOTE: geom needs to be in root/absolute coordinates!
  //qDebug() << "MoveResize Window:" << geom.x() << geom.y() << geom.width() << geom.height();
 
  //Move the window
  /*xcb_ewmh_request_moveresize_window(&EWMH, 0, win, XCB_GRAVITY_STATIC, XCB_EWMH_CLIENT_SOURCE_TYPE_OTHER, \
		XCB_EWMH_MOVERESIZE_WINDOW_X | XCB_EWMH_MOVERESIZE_WINDOW_Y | XCB_MOVERESIZE_WINDOW_WIDTH | XCB_MOVERESIZE_WINDOW_HEIGHT, \
		geom.x(), geom.y(), geom.width(), geom.height());*/
  
  //Use the basic XCB functions instead of ewmh (Issues with combining the XCB_EWMH_MOVERESIZE _*flags)
  uint32_t values[4];
  values[0] = geom.x(); values[1] = geom.y();
  values[2] = geom.width(); values[3] = geom.height();
  xcb_configure_window(QX11Info::connection(), win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y | XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
	
}

// === SetScreenWorkArea() ===
/*void LXCB::SetScreenWorkArea(unsigned int screen, QRect rect){
  //This is only useful because Fluxbox does not set the _NET_WORKAREA root atom
  // 	This needs to be better incorporated into the new window manager later

  //First get the current workarea array (for all monitors/screens)
  xcb_get_property_cookie_t cookie = xcb_ewmh_get_workarea_unchecked(&EWMH, 0);
  xcb_ewmh_get_workarea_reply_t work;
  if(0==xcb_ewmh_get_workarea_reply(&EWMH, cookie, &work, NULL)){ return; } //Error: Could not retrieve current work areas
  //Save what we need only from the reply
  unsigned int desks = work.workarea_len;
  if(desks <= screen){ return; } //invalid screen to modify
  qDebug() << "Number of desktops/screens:" << desks;
  xcb_ewmh_geometry_t *dareas = work.workarea;
  //Adjust the work area for the input monitor/screen
  dareas[screen].x = rect.x();
  dareas[screen].y = rect.y();
  dareas[screen].width = rect.width();
  dareas[screen].height = rect.height();
  //Now save the array again
  xcb_ewmh_set_workarea(&EWMH, 0, desks, dareas); //_NET_WORKAREA
  //Make sure to clear that reply
  xcb_ewmh_get_workarea_reply_wipe(&work);
}*/