/*
 * util.cc
 *
 * Copyright (C) 1995, 1996, 1997, 1997, 1998, 1999, 2000, 2001, 2002 Kenichi Kourai
 * Copyright (C) 1999, 2000, 2001, 2002 Luiz Blanes
 *
 * 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 of the License, 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 blwm; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_VFORK_H
#include <vfork.h>
#endif
#ifndef HAVE_USLEEP
#include <sys/time.h>
#endif
#ifdef __EMX__
#include <process.h>
#endif
#include <X11/Xlib.h>
#include <X11/Xlocale.h>
#include <X11/xpm.h>
#include "main.h"
#include "misc.h"
#include "blwm.h"
#include "menu.h"
#include "util.h"
#include "blwmrc.h"
#include "desktop.h"
#include "timer.h"
#include "callback.h"
#include "exec.h"
#include "paging.h"
#include "pager.h"
#include "pixmap_image.h"

/*
 * Gravity offset for frame decoration.
 */
Point GravityOffset[] = {
  Point(CENTER, CENTER),            // ForgetGravity
  Point(WEST,   NORTH),             // NorthWestGravity
  Point(CENTER, NORTH),             // NorthGravity
  Point(EAST,   NORTH),             // NorthEastGravity
  Point(WEST,   CENTER),            // WestGravity
  Point(CENTER, CENTER),            // CenterGravity
  Point(EAST,   CENTER),            // EastGravity
  Point(WEST,   SOUTH),             // SouthWestGravity
  Point(CENTER, SOUTH),             // SouthGravity
  Point(EAST,   SOUTH),             // SouthEastGravity
  Point(CENTER, CENTER),            // StaticGravity
};

List<ExecPending> ExecPending::m_pendingList;

/*
 * InRect --
 *   Return True if the point is in a given rectangle.
 */
Bool InRect(const Point& pt, const Rect& rc)
{
  if (pt.x < rc.x)
    return False;
  if (pt.x >= rc.x + rc.width)
    return False;
  if (pt.y < rc.y)
    return False;
  if (pt.y >= rc.y + rc.height)
    return False;

  return True;
}

/*
 * Intersect --
 *   Return True if two rectangles intersect each other.
 */
Bool Intersect(const Rect& rc1, const Rect& rc2)
{
  if (InRect(Point(rc1.x, rc1.y), rc2) ||
      InRect(Point(rc1.x + rc1.width - 1, rc1.y), rc2) ||
      InRect(Point(rc1.x, rc1.y + rc1.height - 1), rc2) ||
      InRect(Point(rc1.x + rc1.width - 1, rc1.y + rc1.height - 1), rc2) ||
      InRect(Point(rc2.x, rc2.y), rc1) ||
      InRect(Point(rc2.x + rc2.width - 1, rc2.y), rc1) ||
      InRect(Point(rc2.x, rc2.y + rc2.height - 1), rc1) ||
      InRect(Point(rc2.x + rc2.width - 1, rc2.y + rc2.height - 1), rc1))
    return True;

  return False;
}

/*
 * IsDoubleClick --
 *   Return True if the click is decided as double click from time and space.
 */
Bool IsDoubleClick(Time prevTime, Time clickTime, const Point& ptPrev,
		   const Point& ptClick)
{
  Rect rcPrev;

  if (clickTime - prevTime > (unsigned int)DoubleClickTime)
    return False;

  rcPrev = Rect(ptPrev.x-DoubleClickRange, ptPrev.y-DoubleClickRange,
		ptPrev.x+DoubleClickRange, ptPrev.y+DoubleClickRange);
  if (!InRect(ptClick, rcPrev))
    return False;

  return True;
}

/*
 * GetMenuItemNum --
 *   Return the number of elements in mItem.
 */
int GetMenuItemNum(const MenuElem* mItem)
{
  int num = 0;

  while (mItem) {
    num++;
    mItem = mItem->next;
  }

  return num;
}

BlImage* CreateImageFromFile(char* file, Timer* timer)
{
  switch (file[0]) {
  case '/':
    return CreateImage(file, timer);

  case '~':
  case '$':
    {
      char* filename = ExtractPathName(file);
      return CreateImage(filename, timer);
    }

  default:
    {
      BlImage* img;
      char* paths = new char[strlen(ImagePath) + 1 + strlen(IMGDIR) + 1];
      int filelen = strlen(file);

      sprintf(paths, "%s:%s", ImagePath, IMGDIR);
      char* pathname = strtok(paths, ":");

      while (pathname) {
	char* realpath = ExtractPathName(pathname);
	int len = strlen(realpath) + 1 + filelen;
	char* filename = new char[len + 1];

	sprintf(filename, "%s/%s", realpath, file);
	delete [] realpath;

	img = CreateImage(filename, timer);

	delete [] filename;

	if (img) {
	  delete [] paths;
	  return img;
	}

	pathname = strtok(NULL, ":");
      }

      delete [] paths;
      BlwmError("Can't find image '%s'", file);
      return NULL;
    }
  }
}

BlImage* CreateImage(char* filename, Timer* timer)
{
  BlImage* img;
  struct stat st;
  int len = strlen(filename);

#ifdef __EMX__
  if (stat(__XOS2RedirRoot(filename), &st) == 0) {
#else
  if (stat(filename, &st) == 0) {
#endif
    
      if (strcmp(&filename[len - 4], ".xpm") == 0)
	img = new PixmapImage(filename);
      else {
	BlwmError("unsupported image file: %s", filename);
	img = NULL;
      }

    

    if (img && img->GetError()) {
      BlImage::Destroy(img);
      img = NULL;
    }

    return img;
  }

  return NULL;
}

/*
 * GetFixName --
 */
char* GetFixName(XFontSet& fs, const char* name, int width)
{
  const int dotWidth = 11;
  int len, curWidth;
  char *str;
  XRectangle ink, log;

  len = strlen(name);

  // str is enough short
  if ((curWidth = XmbTextExtents(fs, name, len, &ink, &log)) <= width){
    str = new char[len + 1];
    strcpy(str, name);
    return str;
  }

  // can't write even "..."
  if (width <= dotWidth) {
    str = new char[1];
    *str = '\0';
    return str;
  }

  if (curWidth < (width-dotWidth) * 2) {
    while (XmbTextExtents(fs, name, len, &ink, &log) > width - dotWidth)
      if (--len == 0) {
	str = new char[1];
	*str = '\0';
	return str;
      }
  }
  else {
    len = 0;
    while (XmbTextExtents(fs, name, len, &ink, &log) < width - dotWidth)
      len++;
    len--;
  }
  
  str = new char[len + 1];
  strncpy(str, name, len);
  str[len] = '\0';

  return str;
}

/*
 * MappedNotOverride --
 *   Return True if the window attribute doesn't include override_redirect.
 */
Bool MappedNotOverride(Window w, long* state)
{
  XWindowAttributes attr;
  Atom atype;
  int aformat;
  unsigned long nitems, bytes_remain;
  unsigned char* prop;

  if(!XGetWindowAttributes(display, w, &attr))
    return False;

  *state = NormalState;

  if (XGetWindowProperty(display, w, _XA_WM_STATE, 0, 3, False, _XA_WM_STATE,
			 &atype, &aformat, &nitems, &bytes_remain, &prop)
      ==Success) {
    if (prop != NULL) {
      *state = *(long *)prop;
      XFree(prop);
    }
  }

  return (((*state == IconicState) || (attr.map_state != IsUnmapped))
	  && !attr.override_redirect);
}

GC CreateTileGC(Drawable d)
{
  char data[] = {0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa};
  Pixmap pattern;
  GC gcTile;

  pattern = XCreateBitmapFromData(display, d, data, 8, 8);

  gcTile = XCreateGC(display, d, 0, 0);
  XSetFillStyle(display, gcTile, FillStippled);
  XSetStipple(display, gcTile, pattern);

  XFreePixmap(display, pattern);

  return gcTile;
}

/*
 * DarkenScreen --
 *   Darken whole screen.
 */
void DarkenScreen()
{
  GC gcDark;

  gcDark = CreateTileGC(root);

  XSetSubwindowMode(display, gcDark, IncludeInferiors);
  XSetForeground(display, gcDark, black.pixel);

  XFillRectangle(display, root, gcDark,
		 0, 0,
		 DisplayWidth(display, screen),
		 DisplayHeight(display, screen));

  XFreeGC(display, gcDark);
}

/*
 * RefreshScreen --
 *   Refresh whole screen.
 */
void RefreshScreen()
{
  Window w;

  w = XCreateSimpleWindow(display, root,
			  0, 0,
			  DisplayWidth(display, screen),
			  DisplayHeight(display, screen),
			  0, black.pixel, white.pixel);
  
  XMapWindow(display, w);
  XFlush(display);
  XDestroyWindow(display, w);
}

// tcsh does not work...
#define SHELL "/bin/sh"

pid_t ExecCommand(char* exec)
{
  pid_t pid;

  if (strncmp(exec, "EXEC ", 5) == 0) {
    char cmd[256], *argv[16];
    int i = 1;

    strcpy(cmd, &exec[5]);
    argv[0] = strtok(cmd, " ");
    while ((argv[i] = strtok(NULL, " ")) != NULL)
      i++;

    desktop.FinishBlwm(True);
    execvp(cmd, argv);
  }
  else if (strncmp(exec, "PAGE", 4) == 0) {
    char cmd[256], *comp;
    Point pageoff;
    
    if (exec[4] == '[') {
      char *next;

      strcpy(cmd, &exec[5]);
      
      // x page offset
      next = strtok(cmd, ",");
      if (next == NULL) {
	BlwmError("Syntax error: first ',' is missing");
	BlwmError("  %s", exec);
	return -1;
      }
      pageoff.x = atoi(next) * DisplayWidth(display, screen);

      // y page offset
      next = strtok(NULL, ",");
      if (next == NULL) {
	BlwmError("Syntax error: second ',' is missing");
	BlwmError("  %s", exec);
	return -1;
      }
      pageoff.y = atoi(next) * DisplayHeight(display, screen);

      // compare string
      comp = strtok(NULL, "]");
    }
    else if (strncmp(&exec[4], "CUR[", 4) == 0) {
      pageoff = paging->origin;
      strcpy(cmd, &exec[8]);
      comp = strtok(cmd, "]");
    }
    else {
      BlwmError("Syntax error: %s", exec);
      return -1;
    }

    if (comp == NULL) {
      BlwmError("Syntax error: compare string is missing");
      BlwmError("  %s", exec);
      return -1;
    }

    // eliminate spaces before the string
    while (*comp == ' ')
      comp++;

    // return if the compare string is not specified
    int len = strlen(comp);
    if (len == 0) {
      BlwmError("Syntax error: compare string is missing");
      BlwmError("  %s", exec);
      return -1;
    }

    // eliminate spaces after the compare string
    char* ptr = &comp[len - 1];
    while (*ptr == ' ' && ptr > comp)
      ptr--;
    *(ptr + 1) = '\0';

    ExecPending* pending = new ExecPending(pageoff, comp);
    ExecPending::m_pendingList.InsertTail(pending);

    char* excmd = strchr(exec, ']');
    excmd++;
    while (*excmd == ' ')
      excmd++;
    if (*excmd == '\0') {
      BlwmError("Syntax error: external command is not specified");
      BlwmError("  %s", exec);
      return -1;
    }

    exec = excmd;
  }

  char cmd[1024];

  if (strlen(exec) + 5 >= sizeof(cmd)) {
    BlwmError("too long command string: '%s'", exec);
    return -1;
  }
  sprintf(cmd, "exec %s", exec);

  if (HourGlassTime > 0) {
    XDefineCursor(display, root, cursor[BGWORK]);

    timer->ResetTimeout(new GlobalCallback(&RestoreCursor));
    timer->SetTimeout(HourGlassTime, new GlobalCallback(&RestoreCursor));
  }

#ifdef __EMX__  
  if (spawnl(P_NOWAIT, "cmd.exe", "cmd.exe", "/C", exec, NULL) == -1)
    BlwmError("Can't execute '%s'", exec);
#else
  if ((pid = vfork()) == 0) {
    if (execl(SHELL, SHELL, "-c", cmd, NULL) == -1) {
      BlwmError("Can't execute '%s'", &cmd[5]);
      _exit(1);
    }
  }
#endif    

  return (pid);
}

void RestoreCursor()
{
  XDefineCursor(display, root, cursor[SYS]);
}

#define MAX_ENV_NAME 255
#define MAX_USER_NAME 32

/*
 * Extract ~ and environment variable in a path name.
 */
char* ExtractPathName(char* name)
{
  char* exname;

  if (name == NULL)
    return NULL;

  if (name[0] == '~') {
    if (name[1] == '/' || name[1] == '\0') {
      char* home = getenv("HOME");
      char* dir = &name[1];

      if (home == NULL) {
	BlwmError("Undefined environment variable HOME\n");
	exname = new char[strlen(dir) + 1];
	strcpy(exname, dir);
      }
      else {
	exname = new char[strlen(home) + strlen(dir) + 1];
	sprintf(exname, "%s%s", home, dir);
      }
    }
    else {
      char user[MAX_USER_NAME + 1];
      char* dir;

      dir = GetNextDelim(&name[1], user, MAX_USER_NAME + 1);
      struct passwd* pw = getpwnam(user);

      if (pw == NULL) {
	if (user[0] != '\0')
	  BlwmError("No user %s\n", user);
	exname = new char[strlen(name) + 1];
	strcpy(exname, name);
      }
      else {
	exname = new char[strlen(pw->pw_dir) + strlen(dir) + 1];
	sprintf(exname, "%s%s", pw->pw_dir, dir);
      }
    }
    return exname;
  }
  else if (name[0] == '$') {
    char env[MAX_ENV_NAME + 1];
    char* dir;
    
    dir = GetNextDelim(&name[1], env, MAX_ENV_NAME + 1);
    char* exenv = getenv(env);

    if (exenv == NULL) {
      if (env[0] != '\0')
	BlwmError("No environment variable %s\n", env);
      exname = new char[strlen(dir) + 1];
      strcpy(exname, dir);
    }
    else {
      exname = new char[strlen(exenv) + strlen(dir) + 1];
      sprintf(exname, "%s%s", exenv, dir);
    }

    return exname;
  }

  exname = new char[strlen(name) + 1];
  strcpy(exname, name);

  return exname;
}

// maxSize includes null for termination
char* GetNextDelim(char* path, char* name, int maxSize)
{
  int i;

  for (i = 0; path[i] != '/' && path[i] != '\0'; i++) {
    if (i >= maxSize - 1) {
      name[0] = '\0';
      BlwmError("Too long before delim: %s\n", path);
      return path;
    }
    name[i] = path[i];
  }
  name[i] = '\0';

  return &path[i];
}

#if !defined(HAVE_USLEEP) && !defined(__EMX__)

#if defined(sun) && !defined(__SVR4)
extern "C" int select(int, fd_set *, fd_set *, fd_set *, struct timeval *);
#endif

void usleep(unsigned long usec)
{
  struct timeval tm;
  
  if (usec <= 0)
    return;
  
  tm.tv_usec = usec % 1000000;
  tm.tv_sec = usec / 1000000;
  
  select(1, NULL, NULL, NULL, &tm);
}
#endif // HAVE_USLEEP

// Get string width excluding "\B" for bold font and "\&" for underscore
int GetRealWidth(XFontSet fs, char* str)
{
  XRectangle ink, log;
  unsigned int width;

  if (*str == '\\' && *(str + 1) == 'B')
    str += 2;

  char* keyStr = strstr(str, "\\&");
  if (keyStr == NULL)
    width = XmbTextExtents(fs, str, strlen(str), &ink, &log);
  else {
    // length before "\&"
    width = XmbTextExtents(fs, str, keyStr - str, &ink, &log);
    // length after "\&"
    width += XmbTextExtents(fs, keyStr + 2, str + strlen(str) - (keyStr + 2),
			    &ink, &log);
  }

  return width;
}

// Draw "...\&?..." -> "...?..."
//                         ~
void DrawRealString(Window w, XFontSet fs, GC gc, int x, int y, char* str)
{
  Bool bold = False;

  if (*str == '\\' && *(str + 1) == 'B') {
    str += 2;
    bold = True;
  }

  char* keyStr = strstr(str, "\\&");
  XRectangle ink, log;
  int len = strlen(str);

  if (keyStr == NULL) {
    XmbDrawString(display, w, fs, gc, x, y, str, len);
    if (bold)
      XmbDrawString(display, w, fs, gc, x, y + 1, str, len);
  }
  else {
    // draw "..."
    XmbDrawString(display, w, fs, gc, x, y, str, keyStr - str);
    if (bold)
      XmbDrawString(display, w, fs, gc, x, y + 1, str, keyStr - str);

    // draw "?..."
    XmbTextExtents(fs, str, keyStr - str, &ink, &log);
    x += log.width;
    XmbDrawString(display, w, fs, gc, x, y,
		  keyStr + 2, str + len - (keyStr + 2));
    if (bold)
      XmbDrawString(display, w, fs, gc, x, y + 1,
		    keyStr + 2, str + len - (keyStr + 2));
    
    // draw underscore
    XmbTextExtents(fs, keyStr + 2, 1, &ink, &log);
    XDrawLine(display, w, gc, x, y + 1, x + log.width, y + 1);
  }
}


Bool IsPointerInWindow(const Point& ptRoot)
{
  List<Blwm>::Iterator i(&desktop.GetBlwmList());
  
  for (Blwm* blWm = i.GetHead(); blWm; blWm = i.GetNext()) {
    Rect rcReal = blWm->GetRect();
    rcReal.x -= paging->origin.x;
    rcReal.y -= paging->origin.y;

    if (InRect(ptRoot, rcReal))
      return True;
  }

  if (UseTaskbar) {
    if (InRect(ptRoot, taskBar->GetRect()))
      return True;
  }

  if (UsePager) {
    if (InRect(ptRoot, pager->GetRect()))
      return True;
  }

  return False;
}

/*
 * CreateColor --
 *   Allocate color from red, green and blue.
 */
int CreateColor(unsigned short red, unsigned short green,
 		unsigned short blue, XColor* color, XColor* substitute,
 		const char* comment)

{
  int status;

  color->red = red;
  color->green = green;
  color->blue = blue;

  status = XAllocColor(display, colormap, color);
 
   if (status == 0) {
     if (comment)
       BlwmError("cannot allocate color: %s", comment);
 
     if (substitute)
       *color = *substitute;
     else
       color->pixel = 0;
   }


  return status;
 }
 
 // require is a set of indispensable parameters
 Bool SetGeometry(char* str, InternGeom* geom)
 {
   if (geom == NULL)
     return False;
 
   // initialize
   geom->rc = Rect(0, 0, 0, 0);
 
   geom->bitmask = XParseGeometry(str, &geom->rc.x, &geom->rc.y,
 				 (unsigned int *)&geom->rc.width,
 				 (unsigned int *)&geom->rc.height);
 
 
   if (geom->bitmask & XNegative)    // EastGravity
     geom->gravity.x = EAST;
   else if (geom->bitmask & XValue)  // WestGravity
     geom->gravity.x = WEST;
   else                              // CenterGravity
     geom->gravity.x = CENTER;
 
   if (geom->bitmask & YNegative)    // SouthGravity
     geom->gravity.y = SOUTH;
   else if (geom->bitmask & YValue)  // NorthGravity
     geom->gravity.y = NORTH;
   else                              // CenterGravity
     geom->gravity.y = CENTER;
 
   return True;
 }
 
 void SetDisplayEnv(const char* displayName)
 {
   static char envStr[256];
 
   snprintf(envStr, 256, "DISPLAY=%s", displayName);
 
   putenv(envStr);
 }
 
 int GetXColorFromName(const char* str, XColor* color)
 {
   if (strcmp(str, "blgray") == 0)
     *color = gray;
   else if (strcmp(str, "bldarkgray") == 0)
     *color = darkGray;
   else if (strcmp(str, "blgrey") == 0)
     *color = grey;
   else if (strcmp(str, "bldarkgrey") == 0)
     *color = darkGrey;
   else if (strcmp(str, "blblue") == 0)
     *color = blue;
   else if (strcmp(str, "bllightblue") == 0)
     *color = lightBlue;
   else if (strcmp(str, "blroyalblue") == 0)
     *color = royalBlue;
   else if (strcmp(str, "blyellow") == 0)
     *color = yellow;
   else if (strcmp(str, "bllightyellow") == 0)
     *color = lightYellow;
   else if (strcmp(str, "blgray95") == 0) {
     if (gray95.pixel == 0)
       CreateColor(0xbefb, 0xbefb, 0xbefb, &gray95, &white, "blgray95");
     *color = gray95;
   }
   else if (strcmp(str, "bldarkgray95") == 0) {
     if (darkGray95.pixel == 0)
       CreateColor(0x79e7, 0x7df7, 0x79e7, &darkGray95, &black,
 		  "bldarkgray95");
     *color = darkGray95;
   }
   else if (strcmp(str, "bllightgray95") == 0) {
     if (lightGray95.pixel == 0)
       CreateColor(0xdf7d, 0xdf7d, 0xdf7d, &lightGray95, &gray95,
 		  "bllightgray95");
     *color = lightGray95;
   }
   else if (strcmp(str, "blgrey95") == 0) { // not blgray!
     if (grey95.pixel == 0)
       CreateColor(0xb6da, 0xb2ca, 0xb6da, &grey95, &gray95, "blgrey95");
     *color = grey95;
   }
   else if (strcmp(str, "blblue95") == 0) {
     if (blue95.pixel == 0)
       CreateColor(0x0000, 0x0000, 0x79e7, &blue95, &black, "blblue95");
     *color = blue95;
   }
   else if (strcmp(str, "bllightblue95") == 0) {
     if (lightBlue95.pixel == 0)
       CreateColor(0x0820, 0x8207, 0xcf3c, &lightBlue95, &blue95,
 		  "bllightblue95");
     *color = lightBlue95;
   }
   else if (strcmp(str, "blgreen95") == 0) {
     if (green95.pixel == 0)
       CreateColor(0x0000, 0x7df7, 0x79e7, &green95, &blue95,
 		  "blgreen95");
     *color = green95;
   }
   else if (strcmp(str, "blyellow95") == 0) {
     if (yellow95.pixel == 0)
       CreateColor(0xffff, 0xffff, 0xdf7d, &yellow95, &white,
 		  "blyellow95");
     *color = yellow95;
   }
   else if (XParseColor(display, colormap, str, color) != 0)
     XAllocColor(display, colormap, color);
   else
     return -1;
 
   return 0;

}


syntax highlighted by Code2HTML, v. 0.9.1