/*
 * accessary.cc
 *
 * Copyright (C) 1995-2001 Kenichi Kourai
 *
 * 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 qvwm; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#ifdef USE_SHAPE
#include <X11/extensions/shape.h>
#endif
#include "main.h"
#include "accessory.h"
#include "util.h"
#include "callback.h"
#include "timer.h"
#include "extra_image.h"

Accessory::Accessory(char* filename, char* pos, char* mode)
: m_filename(filename), m_pos(pos)
{
  if (strcmp(mode, "Background") == 0)
    m_mode = ACC_BACK;
  else if (strcmp(mode, "OnTop") == 0)
    m_mode = ACC_ONTOP;
  else if (strcmp(mode, "Application") == 0)
    m_mode = ACC_APP;
  else {
    QvwmError("invalid accessory mode: %s", mode);
    m_mode = ACC_BACK;
  }

  m_pid = 0;
}

Accessory::~Accessory()
{
  if (m_pid)
    kill(m_pid, SIGTERM);
}

void Accessory::CreateAccessory()
{
  // make a child process process accessories in order to separate from qvwm
  // heavy task to create (animation) image and animate it if necessary
  if ((m_pid = fork()) != 0)
    return;

  // signal initialization (all ignore)
  signal(SIGINT, SIG_DFL);
  signal(SIGQUIT, SIG_DFL);
  signal(SIGTERM, SIG_DFL);
  signal(SIGSEGV, SIG_DFL);
  signal(SIGBUS, SIG_DFL);
  signal(SIGFPE, SIG_DFL);
  signal(SIGHUP, SIG_DFL);

  // error handler initialization
  XSetErrorHandler((XErrorHandler)NULL);
  XSetIOErrorHandler((XIOErrorHandler)NULL);

  // X initialization
  display = XOpenDisplay(displayName);
  screen = DefaultScreen(display);
  root = RootWindow(display, screen);

#ifdef USE_IMLIB
  ExtraImage::Initialize();
#endif

  // timer initialization
  timer = new Timer();

  m_img = CreateImageFromFile(m_filename, timer);
  if (m_img == NULL)
    exit(1);

  XSetWindowAttributes attributes;
  unsigned long valueMask;
  int gravity;
  Point pt = CalcPosition(m_pos, &gravity);
  Dim szImg = m_img->GetSize();

  if (m_mode == ACC_APP)
    attributes.override_redirect = False;
  else
    attributes.override_redirect = True;
  valueMask = CWOverrideRedirect;

  m_win = XCreateWindow(display, root,
			pt.x, pt.y, szImg.width, szImg.height,
			0, CopyFromParent, InputOutput, CopyFromParent,
			valueMask, &attributes);

  if (m_mode == ACC_APP) {
    XSizeHints hints;
    hints.flags = USPosition | PWinGravity;
    hints.win_gravity = gravity;
    XSetWMNormalHints(display, m_win, &hints);

    char name[256];
    sprintf(name, "Accessory: %s", m_filename);
    XStoreName(display, m_win, name);
  }

#ifdef USE_SHAPE
  if (m_mode != ACC_APP) {
    m_shapeMask = XCreatePixmap(display, m_win, szImg.width, szImg.height, 1);
    m_gcShape = XCreateGC(display, m_shapeMask, 0, 0);
  }
#endif // USE_SHAPE

  SetBgImage();
  
  m_img->SetDisplayCallback(new Callback<Accessory>(this,
						    &Accessory::SetBgImage));

  MapAccessory();
  EventLoop();

  XDestroyWindow(display, m_win);
  QvImage::Destroy(m_img);
#ifdef USE_SHAPE
  if (m_mode != ACC_APP) {
    XFreePixmap(display, m_shapeMask);
    XFreeGC(display, m_gcShape);
  }
#endif // USE_SHAPE

  XCloseDisplay(display);

  exit(0);
}

void Accessory::CreateShapedWindow()
{
#ifdef USE_SHAPE
  Dim szImg = m_img->GetSize();

  // make the whole transparent (pixel value 0 is transparent).
  XSetForeground(display, m_gcShape, 0);
  XFillRectangle(display, m_shapeMask, m_gcShape,
		 0, 0, szImg.width, szImg.height);
  
  // make opaque
  XSetForeground(display, m_gcShape, 1);
  XSetBackground(display, m_gcShape, 0);

  if (m_img->GetMask())
    XCopyPlane(display, m_img->GetMask(), m_shapeMask, m_gcShape,
	       0, 0, szImg.width, szImg.height, 0, 0, 1);
  else
    XFillRectangle(display, m_shapeMask, m_gcShape,
		   0, 0, szImg.width, szImg.height);

  XShapeCombineMask(display, m_win, ShapeBounding, 0, 0, m_shapeMask,
		    ShapeSet);
#endif // USE_SHAPE
}

Point Accessory::CalcPosition(char* pos, int* gravity)
{
  Dim szImg = m_img->GetSize();
  Dim szRoot(DisplayWidth(display, screen), DisplayHeight(display, screen));
  Point pt;
  
  if (strcmp(pos, "Center") == 0) {
    pt.x = (szRoot.width - szImg.width) / 2;
    pt.y = (szRoot.height - szImg.height) / 2;
    *gravity = CenterGravity;
  }
  else if (strcmp(pos, "Bottom") == 0) {
    pt.x = (szRoot.width - szImg.width) / 2;
    pt.y = szRoot.height - szImg.height;
    *gravity = SouthGravity;
  }
  else if (strcmp(pos, "Top") == 0) {
    pt.x = (szRoot.width - szImg.width) / 2;
    pt.y = 0;
    *gravity = NorthGravity;
  }
  else if (strcmp(pos, "Left") == 0) {
    pt.x = 0;
    pt.y = (szRoot.height - szImg.height) / 2;
    *gravity = WestGravity;
  }
  else if (strcmp(pos, "Right") == 0) {
    pt.x = szRoot.width - szImg.width;
    pt.y = (szRoot.height - szImg.height) / 2;
    *gravity = EastGravity;
  }
  else if (strcmp(pos, "TopLeft") == 0) {
    pt.x = 0;
    pt.y = 0;
    *gravity = NorthWestGravity;
  }
  else if (strcmp(pos, "TopRight") == 0) {
    pt.x = szRoot.width - szImg.width;
    pt.y = 0;
    *gravity = NorthEastGravity;
  }
  else if (strcmp(pos, "BottomLeft") == 0) {
    pt.x = 0;
    pt.y = szRoot.height - szImg.height;
    *gravity = SouthWestGravity;
  }
  else if (strcmp(pos, "BottomRight") == 0) {
    pt.x = szRoot.width - szImg.width;
    pt.y = szRoot.height - szImg.height;
    *gravity = SouthEastGravity;
  }
  else {
    unsigned int width, height;
    int bitmask;

    bitmask = XParseGeometry(pos, &pt.x, &pt.y, &width, &height);
    if ((bitmask & (XValue|YValue)) == (XValue|YValue)) {
      if (bitmask & XNegative)
	pt.x = szRoot.width + pt.x - szImg.width;
      if (bitmask & YNegative)
	pt.y = szRoot.height + pt.y - szImg.height;

      if ((bitmask & (XNegative | YNegative)) == 0)
	*gravity = NorthWestGravity;
      else if ((bitmask & (XNegative | YNegative)) == XNegative)
	*gravity = NorthEastGravity;
      else if ((bitmask & (XNegative | YNegative)) == YNegative)
	*gravity = SouthWestGravity;
      else if ((bitmask & (XNegative | YNegative)) == (XNegative | YNegative))
	*gravity = SouthEastGravity;
    }    
    else {
      pt.x = 0;
      pt.y = 0;
      *gravity = CenterGravity;
    }
  }

  return pt;
}

void Accessory::SetBgImage()
{
  XSetWindowBackgroundPixmap(display, m_win, m_img->GetPixmap());

  if (m_mode != ACC_APP)
    CreateShapedWindow();

  XClearWindow(display, m_win);
}

void Accessory::MapAccessory()
{
  XMapWindow(display, m_win);
  if (m_mode == ACC_BACK)
    XLowerWindow(display, m_win);
  else if (m_mode == ACC_ONTOP)
    XRaiseWindow(display, m_win);
}

void Accessory::EventLoop()
{
  XEvent ev;
  int fd = ConnectionNumber(display);
  fd_set fds;
  struct timeval tm, *tmp;

  XSynchronize(display, True);

  while (1) {
    FD_ZERO(&fds);
    FD_SET(fd, &fds);

    while (XPending(display) != 0) {
      XNextEvent(display, &ev);

      timer->CheckTimeout(&tm);
    }

    if (timer->CheckTimeout(&tm))
      tmp = &tm;
    else
      tmp = NULL;

    XFlush(display);

#if defined(__hpux) && !defined(_XPG4_EXTENDED)
    if (select(fd + 1, (int *)&fds, 0, 0, tmp) == 0) {    // timeout
#else
    if (select(fd + 1, &fds, 0, 0, tmp) == 0) {    // timeout
#endif
      timer->CheckTimeout(&tm);
    }
  }
}


syntax highlighted by Code2HTML, v. 0.9.1