/* 
 *  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: kdocker.cpp,v 1.24 2005/02/04 10:25:46 cs19713 Exp $

#include <qsessionmanager.h>
#include <qdir.h>
#include <qfile.h>
#include <qtranslator.h>
#include <qtextcodec.h>
#include <qtextstream.h>
#include <qtimer.h>
#include <qstring.h>

#include "trace.h"
#include "traylabelmgr.h"
#include "kdocker.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

// #define TMPFILE_PREFIX QString("/tmp/kdocker.")
#define TMPFILE_PREFIX QDir::homeDirPath() + "/.kdocker."

KDocker::KDocker(int& argc, char** argv)
  :QApplication(argc, argv), mTrayLabelMgr(0)
{
  INIT_TRACE();

  /*
   * Load localisation strings. Most examples I have seen load QTranslator
   * in main(). As a result the translator object lingers around till the end
   * of the program. I tried the same thing here and all i got was translations
   * for usage(). You dont want to know about the sleepless night i spent
   * trying to figure this out (yup, the source helped)
   */
  QTranslator *translator = new QTranslator(0);
  QString f = QString("kdocker_") + QTextCodec::locale();

  if (!translator->load(f, QString(TRANSLATIONS_PATH)) &&
      !translator->load(f, applicationDirPath() + "/i18n") &&
      !translator->load(f, QDir::currentDirPath() + "/i18n")) {
      qDebug("Sorry, your locale is not supported. If you are interested "
             "in providing translations for your locale, contact "
             "gramakri@uiuc.edu\n");
  }
  installTranslator(translator);
 
  // Attempt doing anything only if the CLI arguments were good
  opterr = 0; // suppress the warning
  int option;
  while ((option = getopt(argc, argv, TrayLabelMgr::options().latin1())) != EOF)
  {
    if (option == '?')
    {
        if (optopt == 'v') printVersion(); else printUsage(optopt);
        ::exit(0);
    }
  }

  /*
   * Detect and transfer control to previous instance (if one exists)
   * _KDOCKER_RUNNING is a X Selection. We start out by trying to locate the
   * selection owner. If someone else owns it, transfer control to that
   * instance of KDocker
   */
  Display *display = QPaintDevice::x11AppDisplay();
  Atom kdocker = XInternAtom(display, "_KDOCKER_RUNNING", False);
  Window prev_instance = XGetSelectionOwner(display, kdocker);

  if (prev_instance == None)
  {
    mSelectionOwner = XCreateSimpleWindow(display, qt_xrootwin(), 1, 1, 1,
                                          1, 1, 1, 1);
    XSetSelectionOwner(display, kdocker, mSelectionOwner, CurrentTime);
    TRACE("Selection owner set to 0x%x", (unsigned) mSelectionOwner);
    mTrayLabelMgr = TrayLabelMgr::instance();
  }
  else
    notifyPreviousInstance(prev_instance);           // does not return
}

void KDocker::printVersion(void)
{
  qDebug("Qt: %s", qVersion());
  qDebug("KDocker: %s", KDOCKER_APP_VERSION);
}

// Prints the CLI arguments. Does not return
void KDocker::printUsage(char optopt)
{
  if (optopt != 'h') qDebug(tr("kdocker: invalid option -- %1").arg(optopt));

  qDebug(tr("Usage: KDocker [options] command\n"));
  qDebug(tr("Docks any application into the system tray\n"));
  qDebug(tr("command \tCommand to execute\n"));
  qDebug(tr("Options"));
  qDebug(tr("-a     \tShow author information"));
  qDebug(tr("-b     \tDont warn about non-normal windows (blind mode)"));
  qDebug(tr("-d     \tDisable session management"));
  qDebug(tr("-e     \tEnable session management"));
  qDebug(tr("-f     \tDock window that has the focus(active window)"));
  qDebug(tr("-h     \tDisplay this help"));
  qDebug(tr("-i icon\tCustom dock Icon"));
  qDebug(tr("-l     \tLaunch on startup"));
  qDebug(tr("-m     \tKeep application window mapped (dont hide on dock)"));
  qDebug(tr("-o     \tDock when obscured"));
	qDebug(tr("-p secs\tSet ballooning timeout (popup time)"));
  qDebug(tr("-q     \tDisable ballooning title changes (quiet)"));
  qDebug(tr("-t     \tRemove this application from the task bar"));
  qDebug(tr("-v     \tDisplay version"));
  qDebug(tr("-w wid \tWindow id of the application to dock\n"));
  
  qDebug(tr("NOTE: Use -d for all startup scripts.\n"));

  qDebug(tr("Bugs and wishes to gramakri@uiuc.edu"));
  qDebug(tr("Project information at http://kdocker.sourceforge.net"));
}

void KDocker::notifyPreviousInstance(Window prevInstance)
{
  Display *display = QPaintDevice::x11AppDisplay();

  TRACE("Notifying previous instance [%x]", (unsigned) prevInstance);

  // Dump all arguments in temporary file
  QFile f(TMPFILE_PREFIX + QString().setNum(getpid()));
  if (!f.open(IO_WriteOnly)) return;
  QTextStream s(&f);
	
  /*
   * Its normal to use KDocker in startup scripts. We could be getting restored
   * from a session at the same time. So, if we were getting restored and
   * another instance already exists, send across the session id. Remember, qt 
   * strips out all the arguments that it understands. So need to do it by hand.
   */
  if (isSessionRestored()) 
    s << argv()[0] << " " << "-session" << " " << sessionId();
  else
    for (int i = 0; i < argc(); i++) s << argv()[i] << " ";

  f.close();

  /*
   * Now tell our previous instance that we came to pass. Actually, it can
   * figure it out itself using PropertyNotify events but this is a lot nicer
   */
  XClientMessageEvent dock_event;
  memset(&dock_event, 0, sizeof(XClientMessageEvent));
  dock_event.display = display;
  dock_event.window = prevInstance;
  dock_event.send_event = True;
  dock_event.type = ClientMessage;
  dock_event.message_type = 0x220679; // it all started this day
  dock_event.format = 8;
  dock_event.data.l[0] = 0xBABE;	// love letter ;)
  dock_event.data.l[1] = getpid();
  XSendEvent(display, prevInstance, False, 0, (XEvent *) &dock_event);
  XSync(display, False);

  ::exit(0);
}

/*
 * The X11 Event filter called by Qt. Look out for ClientMessage events from
 * our new instance
 */
bool KDocker::x11EventFilter(XEvent * event)
{
  if (event->type == ClientMessage)
  {
    // look for requests from a new instance of kdocker
    XClientMessageEvent *client = (XClientMessageEvent *) event;
    if (!(client->message_type == 0x220679 && client->data.l[0] == 0xBABE))
      return FALSE;

    TRACE("ClientMessage from PID=%ld. SelOwn=0x%x", 
          client->data.l[1], (unsigned) mSelectionOwner);
    char tmp[50];
    struct stat buf;
    sprintf(tmp, TMPFILE_PREFIX "%ld", client->data.l[1]);
    if (stat(tmp, &buf) || (getuid()!=buf.st_uid))
    {
     /*
      * We make sure that the owner of this process and the owner of the file
      * are the same. This will prevent someone from executing arbitrary
      * programs by sending client message. Of course, you can send a message
      * only if you are authenticated to the X session and have permission to
      * create files in TMPFILE_PREFIX. So this code is there just for the 
      * heck of it.
      */
      TRACE("User %i is trying something fishy...", buf.st_uid);
      unlink(tmp);
      return TRUE;
    }
    QFile f(tmp);
    if (!f.open(IO_ReadOnly)) return TRUE;
    QTextStream s(&f);
    QStringList argv;
    while (!s.atEnd()) { QString x; s >> x; argv += x; }
    f.close();
    unlink(tmp); // delete the tmp file
    mTrayLabelMgr->processCommand(argv);
    return TRUE;
  }
  else return mTrayLabelMgr->x11EventFilter(event);
}

/*
 * XSMP Support
 */
void KDocker::saveState(QSessionManager &sm)
{
  QString sf = mTrayLabelMgr->saveSession();

  QStringList discard_command;
  discard_command << "rm" << sf;
  sm.setDiscardCommand(discard_command);

  sm.setRestartHint(QSessionManager::RestartIfRunning);
  QStringList restart_command;
  restart_command << this->argv()[0]
                  << "-session" << sm.sessionId();
  sm.setRestartCommand(restart_command);

  TRACE("SessionFile=%s AppName=%s", sf.latin1(), this->argv()[0]);
  DUMP_TRACE(QDir::homeDirPath() + "/kdocker.trace");
  // sm.setRestartCommand(applicationFilePath());
}

