#!/usr/bin/env python
######################################################################
######################################################################
# Fluxspace idesk support module
######################################################################
######################################################################

import os
import os.path
import sys
import os.path
from glob import glob
from fluxspace.config import Config

ideskCmd   = 'idesk'
idesktops  = os.path.expanduser('~/.fluxbox/idesktops')
idesktop   = os.path.expanduser('~/.idesktop')
desktopFmt = 'desktop%d'
iconFmt = '''\
table Icon
  Caption: %s
  Command: "%s"
  Icon: %s
  X: %d
  Y: %d
end'''

# Get offsets from configuration
offsetx = offsety = 0
fluxspaceConfig = Config()
fluxspaceConfig.Load()
config = fluxspaceConfig.ModuleConfig('idesk')
if config.getAttribute('offsetx'):
    offsetx = int(config.getAttribute('offsetx'))
if config.getAttribute('offsety'):
    offsety = int(config.getAttribute('offsety'))

class IdeskDesktopsDirectoryUnavailable(Exception):
    pass
class IdeskDesktopDirectoryUnavailable(Exception):
    pass

######################################################################
def SetDesktop(helper, n = -1, link = 1):
######################################################################
    if n >= 0:
        dir = os.path.join(idesktops, desktopFmt % n)
    else:
        dir = idesktops
    if os.path.exists(dir):
        if not os.path.isdir(dir):
            helper.Message(2, 'Desktop directory "%s" already exists as a non-directory' % dir)
            raise IdeskDesktopsDirectoryUnavailable()
    else:
        helper.Message(0, 'Creating desktop directory "%s"...' % dir)
        os.makedirs(dir)
    # Only link if it's a specific desktop and the caller requests it
    if n >= 0 and link:
        helper.Trace('Linking desktop %d...' % n)
        if os.path.exists(idesktop):
            if not os.path.islink(idesktop):
                helper.Message(2, 'idesk directory "%s" already exists as a non-directory' % idesktop)
                raise IdeskDesktopDirectoryUnavailable()
            os.unlink(idesktop)
        os.symlink(dir, idesktop)
    return dir

######################################################################
class CellSpec:
######################################################################
    def __init__(self, width, height):
        self.width  = width
        self.height = height
        self.midx   = width / 2
        self.midy   = width / 2

######################################################################
class Desktop:
######################################################################
    def __init__(self, helper, workspace):
        global offsetx, offsety
        self.helper = helper
        self.width   = 640
        self.height  = 480
        self.offsetx = offsetx
        self.offsety = offsety
        self.GetScreenDimensions()
        self.Reset(workspace)

    def Reset(self, workspace):
        self.cellSpec  = CellSpec(100, 100)
        self.cells     = {}  # map cell grid usage hashed on (x,y) pairs
        self.workspace = workspace
        self.dir       = SetDesktop(self.helper, self.workspace, 0)
        self.MapIcons()

    def OffsetIcons(self, offsetx, offsety):
        self.offsetx = offsetx
        self.offsety = offsety

    # Deduce the width and height using xwininfo on the root window
    def GetScreenDimensions(self):
        for line in os.popen('xwininfo -root').readlines():
            try:
                (tag, val) = line.split()[:2]
                if tag.lower() == 'width:':
                    self.width = int(val)
                if tag.lower() == 'height:':
                    self.height = int(val)
            except ValueError:
                pass

    def Key(self, x, y):
        return (int(x-self.offsetx/self.cellSpec.width),
                int(y-self.offsety/self.cellSpec.height))

    def Mark(self, x, y):
        key = self.Key(x,y)
        if not self.cells.has_key(key):
            self.cells[key] = 0
        self.cells[key] += 1

    def IsMarked(self, x, y):
        key = self.Key(x,y)
        return (self.cells.has_key(key) and self.cells[key] > 0)

    def FindCell(self):
        for x in range(self.cellSpec.midx+self.offsetx, self.width, self.cellSpec.width):
            for y in range(self.cellSpec.midy+self.offsety, self.height, self.cellSpec.height):
                if not self.IsMarked(x, y):
                    return (x, y)
        return (self.cellSpec.midx+self.offsetx, self.cellSpec.midy+self.offsety)

        # Mark the cells occupied by other icons as used
    def MapIcons(self):
        for lnk in glob(os.path.join(self.dir, '*.lnk')):
            (x, y) = (0, 0)
            lnkf = open(lnk)
            for line in lnkf.readlines():
                try:
                    (tag, val) = line.split()[:2]
                    if tag.lower() == 'x:':
                        x = int(val)
                    if tag.lower() == 'y:':
                        y = int(val)
                except ValueError:
                    pass
            lnkf.close()
            self.Mark(x, y)

    def AddIcon(self, caption, command, iconPath, x = -1, y = -1):
        # Find a reasonable position based on the other desktop icons
        if x < 0 or y < 0:
            (x, y) = self.FindCell()

        # Determine a unique idesk icon file name
        name = os.path.split(command.split()[0])[1]
        path = os.path.join(self.dir, '%s.lnk' % name)
        i = 0
        while os.path.exists(path):
            i += 1
            path = os.path.join(self.dir, '%s%d.lnk' % (name, i))

        # Write the idesk icon file
        lnkf = open(path, 'w')
        lnkf.write(iconFmt % (caption, command, iconPath, x, y))
        lnkf.close()

        self.Mark(x, y)
        return (x, y)

    # Mirror a directory containing Rox AppDirs on a desktop
    def SyncAppDirs(self, directory, clean = 1):
        if clean:
            lnks = glob(os.path.join(self.dir, '*.lnk'))
            for lnk in lnks:
                try:
                    os.remove(lnk)
                except OSError:
                    pass
            self.Reset(self.workspace)
        for f in glob(os.path.join(directory, '*')):
            apprun = os.path.join(f, 'AppRun')
            if os.path.isdir(f) and os.access(apprun, os.X_OK):
                caption = os.path.split(f)[1]
                command = apprun
                iconPath = os.path.join(f, '.DirIcon')
                self.AddIcon(caption, command, iconPath)


syntax highlighted by Code2HTML, v. 0.9.1