#!/usr/local/bin/python2.5 -OO
"""
PyPanel v2.4 - Lightweight panel/taskbar for X11 window managers
Copyright (c) 2003-2005 Jon Gelo (ziljian@users.sourceforge.net)

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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
"""

#----------------------------------------------------------------------------                
class Obj(object):
#---------------------------------------------------------------------------- 
    """ Multi-purpose class """
    #----------------------------
    def __init__(self, **kwargs):
    #---------------------------- 
        self.__dict__.update(kwargs)
        
#----------------------------------------------------------------------------
class PyPanel(object):
#----------------------------------------------------------------------------
    #---------------------------
    def __init__(self, display):
    #---------------------------
        """ Initialize and display the panel """
        self.display = display                   # Display obj
        self.screen  = display.screen()          # Screen obj
        self.root    = self.screen.root          # Display root
        self.error   = error.CatchError()        # Error Handler/Suppressor
        self.panel   = {"sections":[]}           # Panel data and layout
        self.colors  = {}                        # Alloc'd colors
        self.hidden  = 0                         # Panel hidden/minimized
        self.focus   = 0                         # Currently focused window
        self.rpm     = None                      # Root pixmap ID
                
        global P_HEIGHT, P_WIDTH, P_LOCATION
        
        # Misc. initializations
        if SHOWLINES or SHOWBORDER:
            self.lgc = self.root.create_gc(foreground=self.getColor(LINE_COLOR))
        if not P_WIDTH:
            P_WIDTH = self.screen.width_in_pixels - P_START
        if SHOWBORDER:
            P_HEIGHT += 2 
        if P_LOCATION:
            P_LOCATION = self.screen.height_in_pixels - P_HEIGHT
        else:
            P_LOCATION = 0
        
        # Setup the panel's window
        self.window = self.screen.root.create_window(P_START, P_LOCATION,
            P_WIDTH, P_HEIGHT, 0, self.screen.root_depth, window_class=X.InputOutput,
            visual=X.CopyFromParent, colormap=X.CopyFromParent, 
            event_mask=(X.ExposureMask|X.ButtonPressMask|X.ButtonReleaseMask|X.EnterWindowMask))
        ppinit(self.window.id, FONT)        
        
        # Init the panel sections
        if DESKTOP:
            self.panel["sections"].append(DESKTOP)
            self.panel[DESKTOP] = Obj(id="desktop", names=[], first=0, last=0)
        if CLOCK:
            self.panel["sections"].append(CLOCK)
            self.panel[CLOCK] = Obj(id="clock", name="", first=0, last=0, x1=0, x2=0,
                color=self.getColor(CLOCK_COLOR), shadow=self.getColor(CLOCK_SHADOW_COLOR))
        if TRAY:
            self.panel["sections"].append(TRAY)
            self.panel[TRAY] = Obj(id="tray", tasks={}, order=[], first=0, last=0, window=self.window)
            self.createTray(self.display, self.screen)
        if LAUNCHER and LAUNCH_LIST:
            self.panel["sections"].append(LAUNCHER)
            self.panel[LAUNCHER] = Obj(id="launcher", tasks={}, order=[], first=0, last=0)
            self.createLauncher()
        
        self.panel["sections"].append(TASKS)
        self.panel[TASKS] = Obj(id="tasks", tasks={}, order=[], first=0, last=0)      
        self.panel["sections"].sort()
        self.panel[self.panel["sections"][0]].first = 1
        self.panel[self.panel["sections"][-1]].last = 1

        # Init the properties and then start the event loop
        self.setProps(self.display, self.window)
        self.setStruts(self.window)
        self.getDesktopNames()
        self.root.change_attributes(event_mask=(X.PropertyChangeMask)) 
        self.window.map()
        self.display.flush()
        self.updatePanel(self.root, self.window, self.panel)
        self.loop(self.display, self.root, self.window, self.panel)
                
    #------------------------------------
    def clearPanel(self, x1, y1, x2, y2):
    #------------------------------------
        """ Clear panel at the given coordinates """ 
        ppclear(self.window.id, int(x1), y1, int(x2), y2)
        if SHOWBORDER:
            self.window.rectangle(self.lgc, 0, 0, P_WIDTH-1, P_HEIGHT-1)
        
    #------------------------
    def createLauncher(self):
    #------------------------
        """ Initialize the Application Launcher """
        order = []
        tasks = {}
        for app, icon in LAUNCH_LIST:
            order.append(app)
            iobj = Obj(path=icon, data="", width=0, height=0, pixmap=0, mask=0)
            tasks[app] = Obj(x1=0, x2=0, app=app+" &", icon=iobj)
            self.panel[LAUNCHER].tasks = tasks
            self.panel[LAUNCHER].order = order         
        
    #------------------------------
    def createTray(self, dsp, scr):
    #------------------------------
        """ Create the System Tray Selection Owner Window """
        self._OPCODE = dsp.intern_atom("_NET_SYSTEM_TRAY_OPCODE")
        manager      = dsp.intern_atom("MANAGER")
        selection    = dsp.intern_atom("_NET_SYSTEM_TRAY_S%d" % dsp.get_default_screen())
          
        # Selection owner window          
        self.selowin = scr.root.create_window(-1, -1, 1, 1, 0, self.screen.root_depth)
        self.selowin.set_selection_owner(selection, X.CurrentTime)
        self.sendEvent(self.root, manager,[X.CurrentTime, selection,
            self.selowin.id], (X.StructureNotifyMask))
    
    #---------------------------------
    def drawText(self, obj, x, width):
    #---------------------------------
        """ Draw the given objects name at x """
        if SHADOWS:
            ppfont(self.window.id, obj.shadow, x+1, P_HEIGHT+2, width, obj.name) 
        ppfont(self.window.id, obj.color, x, P_HEIGHT, width, obj.name)
        
    #----------------------------------
    def setStruts(self, win, hidden=0):
    #----------------------------------
        """ Set the panel struts according to the state (hidden/visible) """
        if P_LOCATION == 0:
            # top
            if not hidden:
                top = P_HEIGHT
            else:
                top = HIDDEN_SIZE
                    
            top_start = P_START
            top_end   = P_START + P_WIDTH
            bottom = bottom_start = bottom_end = 0
        else:
            # bottom
            top = top_start = top_end = 0
            if not hidden:
                bottom = P_HEIGHT
            else:
                bottom = HIDDEN_SIZE
                    
            bottom_start = P_START
            bottom_end   = P_START + P_WIDTH
                
        win.change_property(self._STRUT, Xatom.CARDINAL, 32, [0, 0, top, bottom])
        win.change_property(self._STRUTP, Xatom.CARDINAL, 32, [0, 0, top, bottom,
            0, 0, 0, 0, top_start, top_end, bottom_start, bottom_end])
            
    #----------------------------
    def setProps(self, dsp, win):
    #----------------------------
        """ Set necessary X atoms and panel window properties """   
        self._ABOVE           = dsp.intern_atom("_NET_WM_STATE_ABOVE")
        self._BELOW           = dsp.intern_atom("_NET_WM_STATE_BELOW")
        self._BLACKBOX        = dsp.intern_atom("_BLACKBOX_ATTRIBUTES")  
        self._CHANGE_STATE    = dsp.intern_atom("WM_CHANGE_STATE")
        self._CLIENT_LIST     = dsp.intern_atom("_NET_CLIENT_LIST")
        self._CURRENT_DESKTOP = dsp.intern_atom("_NET_CURRENT_DESKTOP")
        self._DESKTOP         = dsp.intern_atom("_NET_WM_DESKTOP")
        self._DESKTOP_COUNT   = dsp.intern_atom("_NET_NUMBER_OF_DESKTOPS")
        self._DESKTOP_NAMES   = dsp.intern_atom("_NET_DESKTOP_NAMES")   
        self._HIDDEN          = dsp.intern_atom("_NET_WM_STATE_HIDDEN")
        self._ICON            = dsp.intern_atom("_NET_WM_ICON")
        self._NAME            = dsp.intern_atom("_NET_WM_NAME")
        self._RPM             = dsp.intern_atom("_XROOTPMAP_ID")
        self._SHADED          = dsp.intern_atom("_NET_WM_STATE_SHADED")
        self._SHOWING_DESKTOP = dsp.intern_atom("_NET_SHOWING_DESKTOP")
        self._SKIP_PAGER      = dsp.intern_atom("_NET_WM_STATE_SKIP_PAGER")
        self._SKIP_TASKBAR    = dsp.intern_atom("_NET_WM_STATE_SKIP_TASKBAR")
        self._STATE           = dsp.intern_atom("_NET_WM_STATE")
        self._STICKY          = dsp.intern_atom("_NET_WM_STATE_STICKY")
        self._STRUT           = dsp.intern_atom("_NET_WM_STRUT")
        self._STRUTP          = dsp.intern_atom("_NET_WM_STRUT_PARTIAL")
        self._WMSTATE         = dsp.intern_atom("WM_STATE")
        
        win.set_wm_name("PyPanel")
        win.set_wm_class("pypanel","PyPanel") 
        win.set_wm_hints(flags=(Xutil.InputHint|Xutil.StateHint),
            input=0, initial_state=1)
        win.set_wm_normal_hints(flags=(
            Xutil.PPosition|Xutil.PMaxSize|Xutil.PMinSize),
            min_width=P_WIDTH, min_height=P_HEIGHT,
            max_width=P_WIDTH, max_height=P_HEIGHT)
        win.change_property(dsp.intern_atom("_WIN_STATE"),Xatom.CARDINAL,32,[1]) 
        win.change_property(dsp.intern_atom("_MOTIF_WM_HINTS"),
            dsp.intern_atom("_MOTIF_WM_HINTS"), 32, [0x2, 0x0, 0x0, 0x0, 0x0])
        win.change_property(self._DESKTOP, Xatom.CARDINAL, 32, [0xffffffffL])
        win.change_property(dsp.intern_atom("_NET_WM_WINDOW_TYPE"),
            Xatom.ATOM, 32, [dsp.intern_atom("_NET_WM_WINDOW_TYPE_DOCK")])
              
    #-----------------------------------------
    def setState(self, task, panel, win=None):
    #-----------------------------------------
        """ Set/Update a tasks state.  Returns -
            0 - no panel update needed
            1 - panel update needed
        """
        task.state   = []
                        
        try:
            task.state = task.obj.get_full_property(self._STATE, Xatom.ATOM).value
        except:
            pass
        try:
            wmstate = task.obj.get_full_property(self._WMSTATE, 0).value
            if wmstate and (wmstate[0] == Xutil.IconicState):
                task.state.insert(0, self._HIDDEN)
        except:
            if self.taskDelete(task.id, panel):
                return 1
                      
        # set color based on state
        if task.id == self.focus:
            task.color = self.getColor(FOCUSED_COLOR)
            task.shadow = self.getColor(FOCUSED_SHADOW_COLOR)
        elif self._SHADED in task.state:
            task.color = self.getColor(SHADED_COLOR)
            task.shadow = self.getColor(SHADED_SHADOW_COLOR)
        elif self._HIDDEN in task.state:     
            task.color = self.getColor(MINIMIZED_COLOR)
            task.shadow = self.getColor(MINIMIZED_SHADOW_COLOR) 
        else:
            task.color = self.getColor(TASK_COLOR)
            task.shadow = self.getColor(TASK_SHADOW_COLOR)
            
        if SHOWMINIMIZED:
            if (self._HIDDEN not in task.state and task.visible) or\
               (self._HIDDEN in task.state and not task.visible):
                return 1
                
        if win and task.visible:
            x = task.x1+P_SPACER
            if APPICONS:
                x += I_WIDTH+P_SPACER
            self.clearPanel(x, 0, task.x2-x, P_HEIGHT)
            self.drawText(task, x, task.x2-x-P_SPACER)
        
        return 0
    
    #----------------------------
    def setName(self, win, task):
    #----------------------------
        """ Set/update the name of the given task """       
        name = self.getName(task.obj)
        if task.name != name:
            task.name = name
            if task.visible:
                x = task.x1 + P_SPACER                 
                if APPICONS:
                    x += I_WIDTH+P_SPACER
                self.clearPanel(x, 0, task.x2-x, P_HEIGHT)
                self.drawText(task, x, task.x2-x-P_SPACER)
                
    #-----------------------
    def setIcon(self, task):
    #-----------------------
        """ Create an icon object for the given task """                     
        if not APPICONS:
            return
            
        icon = Obj(path="", data="", width=0, height=0, pixmap=0L, mask=0L)
            
        for tc in task.tclass:
            if tc in ICON_LIST:
                icon.path = ICON_LIST[tc]
                task.icon = icon
                return
        try:
            # _net_wm_icon
            data = task.obj.get_full_property(self._ICON, 0)
            if data:
                data        = data.value[:]
                icon.width  = data[0]
                icon.height = data[1]
                icon.data   = data[2:data[0]*data[1]+2].tostring()  
            else:
                # wmhints icon
                hints       = task.obj.get_wm_hints()
                geom        = hints.icon_pixmap.get_geometry()
                icon.pixmap = hints.icon_pixmap.id
                icon.mask   = hints.icon_mask.id
                icon.width  = geom.width
                icon.height = geom.height
                
                if icon.mask > sys.maxint:
                    icon.mask = 0
                if icon.pixmap > sys.maxint:
                    raise
        except:
            # default icon
            icon.path = ICON_LIST["default"] or "%s/pypanel/ppicon.png" % sysconfig.get_python_lib()
                   
        task.icon = icon 
            
    #--------------------------------------
    def getIcon(self, task, x, launcher=0):
    #--------------------------------------
        """ Get the icon from the given task and draw it at x """
        if not launcher and not APPICONS: 
            return 0
            
        if launcher:
            y = (P_HEIGHT-APPL_I_HEIGHT)/2
            w = APPL_I_WIDTH
            h = APPL_I_HEIGHT
            name = task.app
        else:
            y = (P_HEIGHT-I_HEIGHT)/2
            w = I_WIDTH
            h = I_HEIGHT
            name = task.tclass
        
        icon = task.icon           
        rc   = ppicon(self.window.id, icon.pixmap, icon.mask, x, y, icon.width,
                      icon.height, w, h, icon.data, icon.path)  
        if not rc:
            self.clearPanel(x, 0, w, P_HEIGHT)
            sys.stderr.write("Failed to get icon for '%s'\n%s\n\n" % (name, icon.path))
        
        return 1
    
    #-------------------------
    def getDesktopNames(self):
    #-------------------------
        """ Populate the desktop obj with the names of each desktop """
        if not DESKTOP:
            return
        
        if SHADOWS:
            shadow = self.getColor(DESKTOP_SHADOW_COLOR)
        else:
            shadow = None
        
        color         = self.getColor(DESKTOP_COLOR)    
        desktop       = self.panel[DESKTOP]
        desktop.names = []
        desktop.total = self.root.get_full_property(self._DESKTOP_COUNT, 0).value[0]
                                      
        if DESKTOP_NAMES:
            names = DESKTOP_NAMES
        else:
            names = self.root.get_full_property(self._DESKTOP_NAMES, 0)  
            if hasattr(names, "value"):
                names = names.value.split("\x00")
            else:
                names = []
                for x in range(desktop.total):
                    names.append(str(x)) 
                    
        if len(names) < desktop.total:
            for x in range(len(names), desktop.total):
                names.append(str(x))  
                  
        for name in names:
            obj = Obj(name=name, width=ppfontsize(name), color=color, shadow=shadow)
            desktop.names.append(obj)        
            
    #-------------------------------
    def getDesktop(self, task=None):
    #-------------------------------
        """ Return the desktop number of the given task obj """
        if task is None:
            return self.root.get_full_property(self._CURRENT_DESKTOP, Xatom.CARDINAL).value[0]  
           
        try:
            return task.get_full_property(self._DESKTOP, Xatom.CARDINAL).value[0]
        except:
            try:
                return task.get_full_property(self._BLACKBOX, 0).value[2]
            except:
                return None
            
    #-------------------------
    def getColor(self, color):
    #-------------------------
        """ Function to get/convert/alloc a color given a single hex str """
        if color in self.colors:
            return self.colors[color]
        else:      
            r = int("0x"+color[2:4],0)*257
            g = int("0x"+color[4:6],0)*257
            b = int("0x"+color[6:8],0)*257
            c = self.screen.default_colormap.alloc_color(r, g, b)
        
            if not c:
                sys.stderr.write("Error allocating color: %s\n" % color)
                return self.screen.white_pixel
            else:
                self.colors[color] = c.pixel
                return c.pixel
            
    #-----------------------
    def getName(self, task):
    #-----------------------
        """ Return the name of the given task obj """
        try:
            name = task.get_full_property(self._NAME, 0) or task.get_full_property(Xatom.WM_NAME, 0)
            return name.value
        except:
            return ""
        
    #------------------------------------------------
    def sendEvent(self, win, ctype, data, mask=None):
    #------------------------------------------------
        """ Send a ClientMessage event to the root """
        data = (data+[0]*(5-len(data)))[:5]
        ev = Xlib.protocol.event.ClientMessage(window=win, client_type=ctype, data=(32,(data)))

        if not mask:
            mask = (X.SubstructureRedirectMask|X.SubstructureNotifyMask)
        self.root.send_event(ev, event_mask=mask)
                                             
    #----------------------------
    def changeDesktop(self, num):
    #----------------------------
        """ Increase/Decrease the current desktop number by num """
        cur = self.getDesktop() + num

        if cur < 0:
            cur = self.panel[DESKTOP].total - 1
        elif cur == self.panel[DESKTOP].total:
            cur = 0
            
        self.sendEvent(self.root, self._CURRENT_DESKTOP, [cur])
            
    #---------------------
    def showDesktop(self):
    #---------------------
        """ Toggle between hiding and unhiding ALL applications """
        showing = self.root.get_full_property(self._SHOWING_DESKTOP, 0)
        
        if hasattr(showing, "value"):
            if showing.value[0] == 0:
                self.sendEvent(self.root, self._SHOWING_DESKTOP, [1])
            else:
                self.sendEvent(self.root, self._SHOWING_DESKTOP, [0])
        
    #----------------------
    def toggleHidden(self):
    #----------------------
        """ Hide/Unhide the Panel """
        if self.hidden:
            self.window.configure(y=P_LOCATION, height=P_HEIGHT)
            self.setStruts(self.window)
            if ABOVE:
                self.sendEvent(self.window, self._STATE, [0, self._BELOW])
                self.sendEvent(self.window, self._STATE, [1, self._ABOVE])
            else:
                self.sendEvent(self.window, self._STATE, [1, self._BELOW])
                self.sendEvent(self.window, self._STATE, [0, self._ABOVE])
        else:
            if P_LOCATION == 0:
                y = 0;
            else:
                y = self.screen.height_in_pixels - HIDDEN_SIZE

            self.window.configure(y=y, height=HIDDEN_SIZE)
            self.setStruts(self.window, hidden=1)
            
        self.hidden = not self.hidden
            
    #----------------------------------------
    def toggleMinimize(self, task, traise=1):
    #----------------------------------------
        """ Iconify/Deiconify a task """ 
        self.sendEvent(task.obj, self._STATE, [2, self._HIDDEN])
        if self._HIDDEN in task.state:
            task.obj.map()
        else:
            self.sendEvent(task.obj, self._CHANGE_STATE, [Xutil.IconicState])
        if traise:
            self.taskRaise(task, 0)
                
    #---------------------------
    def toggleShade(self, task):
    #---------------------------
        """ Shade/Unshade a task """
        self.sendEvent(task.obj, self._STATE, [2, self._SHADED])
        
    #--------------------------------
    def taskDelete(self, tid, panel):
    #--------------------------------
        """ Delete the given task ID if it's in the tray/task list """
        for section in (TASKS, TRAY):                
            if section and tid in panel[section].tasks:
                del panel[section].tasks[tid]
                panel[section].order.remove(tid)
                return 1
        return 0
            
    #-------------------------
    def taskFocus(self, task):
    #-------------------------
        """ Give focus to an unfocused task else toggle minimization """
        if task.id == self.focus or self._HIDDEN in task.state:
            self.toggleMinimize(task)
        else:
            self.taskRaise(task, 1)
            
    #----------------------------------
    def taskRaise(self, task, focus=0):
    #----------------------------------
        """ Raise a task """
        if self._HIDDEN not in task.state:
            task.obj.configure(stack_mode=X.Above)
            if focus:
                task.obj.set_input_focus(X.RevertToNone, X.CurrentTime)
    
    #----------------------------------
    def taskLower(self, task, focus=0):
    #----------------------------------
        """ Lower a task """
        if self._HIDDEN not in task.state:
            task.obj.configure(stack_mode=X.Below)
            if focus:
                task.obj.set_input_focus(X.RevertToNone, X.CurrentTime)
                
    #---------------------------------------
    def buttonRelease(self, root, panel, e):
    #---------------------------------------
        """ Button Release event handler """
        x = e.event_x
        for section in panel["sections"]:
            if panel[section].id == "tray":
                continue
            elif panel[section].id == "desktop":
                if x > panel[section].x1 and x < panel[section].x2:
                    desktopButtonEvent(self, e.detail)
                    return
            elif panel[section].id == "clock":
                if x > panel[section].x1 and x < panel[section].x2:
                    clockButtonEvent(self, e.detail)
                    return
            elif panel[section].id == "launcher" and e.detail == 1:
                for a in panel[section].tasks.values():
                    if x > a.x1 and x < a.x2:
                        os.system(a.app)
                        return   
            else:
                if not panel[TASKS].tasks:
                    try:
                        panelButtonEvent(self, e.detail)
                    except NameError, e:
                        sys.stderr.write("\n'panelButtonEvent()' is not defined in your pypanelrc.\n")
                        sys.stderr.write("A current pypanelrc example can be found here -\n")
                        sys.stderr.write("%s/pypanel/pypanelrc\n" % sysconfig.get_python_lib())
                else:
                    cdt = self.getDesktop()
                    for t in panel[TASKS].tasks.values():
                        if t.visible and x > t.x1 and x < t.x2:
                            cdt = self.getDesktop()
                            tdt = t.desk
                            if SHOWALL and cdt != tdt and tdt != 0xffffffffL:
                                if SHOWALL == 1:
                                    # Move task to current desktop
                                    self.sendEvent(t.obj, self._DESKTOP, [cdt])
                                elif SHOWALL == 2:
                                    # Switch to tasks desktop
                                    self.sendEvent(root, self._CURRENT_DESKTOP, [tdt])
                                t.obj.map()
                                t.obj.configure(stack_mode=X.Above)
                            else:
                                taskButtonEvent(self, e.detail, t)
                            return
                               
    #-------------------------------------
    def updateBackground(self, root, win):
    #-------------------------------------
        """ Check and update the panel background if necessary """  
        rpm = root.get_full_property(self._RPM, Xatom.PIXMAP)
        
        if hasattr(rpm, "value"):
            rpm = rpm.value[0]
        else:
            rpm = root.id
            
        if self.rpm != rpm:
            self.rpm = rpm
            r = int("0x"+BG_COLOR[2:4],0)
            g = int("0x"+BG_COLOR[4:6],0)
            b = int("0x"+BG_COLOR[6:8],0)
            ppshade(win.id, rpm, P_START, P_LOCATION, P_WIDTH, P_HEIGHT,
                r, g, b, SHADE)
                                         
    #---------------------------------------
    def updatePanel(self, root, win, panel):
    #---------------------------------------
        """ Redraw the panel """
        tasks    = panel[TASKS].tasks     # all tasks
        visible  = []                     # visible tasks
        curr_x   = 0
        space    = P_WIDTH
        cdt      = self.getDesktop()
        clock    = None
        desktop  = None
        tray     = None
        launcher = None
                
        if CLOCK:
            clock = panel[CLOCK]
            clock.name  = time.strftime(CLOCK_FORMAT, time.localtime())
            clock.width = ppfontsize(clock.name) + 2
            space -= clock.width + P_SPACER*2
        if DESKTOP:
            desktop = panel[DESKTOP]
            space -= desktop.names[cdt].width + P_SPACER*2
        if LAUNCHER and panel[LAUNCHER].tasks:
            launcher = panel[LAUNCHER]
            space -= len(launcher.order)*APPL_I_WIDTH + 2
        if TRAY and panel[TRAY].tasks:
            tray = panel[TRAY]
            space -= 2 
            for t in panel[TRAY].tasks.values():
                if TRAY_I_WIDTH:
                    t.width = TRAY_I_WIDTH
                    space -= t.width
                else:
                    try:
                        t.width = t.obj.get_wm_normal_hints().min_width
                        space -= t.width
                    except:
                        pass
        if TASKS and tasks: 
            for task in panel[TASKS].order:
                t = tasks[task]
                t.visible = 0
                if not t.hidden:
                    if SHOWALL or (t.desk == cdt or t.desk == 0xffffffffL):
                        t.visible = 1
                    if SHOWMINIMIZED and self._HIDDEN not in t.state:
                        t.visible = 0
                    if t.visible:
                        visible.append(task)
        
        # Clear the panel and add the objects
        self.updateBackground(root, win)
        self.clearPanel(0, 0, 0, 0)
            
        for section in panel["sections"]:
            if panel[section].id == "tasks" and TASKS:
                if not visible:
                    curr_x += space
                else:
                    limit = space/float(len(visible)) - P_SPACER*2 
                    if APPICONS:
                        limit -= I_WIDTH + P_SPACER 
                    if limit < 1:
                        limit = 1
                    for v in range(len(visible)):  
                        t = tasks[visible[v]]
                        t.x1 = curr_x
                        curr_x += P_SPACER
                        if self.getIcon(t, curr_x):
                            curr_x += I_WIDTH + P_SPACER
                        self.drawText(t, curr_x, limit)
                        curr_x += limit + P_SPACER
                        t.x2 = curr_x
                        if v < len(visible) and SHOWLINES:
                            win.poly_segment(self.lgc, [(curr_x, 0, curr_x, P_HEIGHT)])
                if SHOWLINES and not panel[section].last:
                    win.poly_segment(self.lgc, [(curr_x, 0, curr_x, P_HEIGHT)])
            elif panel[section].id == "clock":
                clock.x1 = curr_x
                curr_x += P_SPACER
                self.drawText(clock, curr_x, 0)
                curr_x += clock.width + P_SPACER
                clock.x2 = curr_x
                if SHOWLINES and not clock.last:
                    win.poly_segment(self.lgc, [(curr_x, 0, curr_x, P_HEIGHT)])
            elif panel[section].id == "desktop":
                desk = panel[section].names[cdt]
                desktop.x1 = curr_x
                curr_x += P_SPACER
                self.drawText(desk, curr_x, 0)
                curr_x += desk.width + P_SPACER
                desktop.x2 = curr_x
                if SHOWLINES and not desktop.last:
                    win.poly_segment(self.lgc, [(curr_x, 0, curr_x, P_HEIGHT)])
            elif panel[section].id == "tray" and tray:
                curr_x += 2
                for tid in tray.order:
                    t = tray.tasks[tid]
                    t.x = curr_x
                    t.y = (P_HEIGHT-t.height)/2
                    t.obj.configure(onerror=self.error, x=curr_x, y=t.y, 
                         width=t.width, height=t.height)
                    t.obj.map(onerror=self.error)
                    curr_x += t.width
                curr_x += 1
                if SHOWLINES and not tray.last:
                    win.poly_segment(self.lgc, [(curr_x, 0, curr_x, P_HEIGHT)])
            elif panel[section].id == "launcher" and launcher:
                curr_x += 2
                for app in launcher.order:
                    a = launcher.tasks[app]
                    a.x1 = curr_x
                    if self.getIcon(a, curr_x, 1):
                        curr_x += APPL_I_WIDTH
                    a.x2 = curr_x
                curr_x += 1    
                if SHOWLINES and not launcher.last:
                    win.poly_segment(self.lgc, [(curr_x, 0, curr_x, P_HEIGHT)])
                    
    #------------------------------------------------------
    def updateTasks(self, dsp, root, win, panel, update=0):
    #------------------------------------------------------
        """ Check the tasklist for additions/deletions/changes """     
        cdt   = self.getDesktop()
        tasks = root.get_full_property(self._CLIENT_LIST, Xatom.WINDOW).value
        count = 0
                 
        for task in [t for t in tasks if t not in panel[TASKS].tasks]: 
            obj  = dsp.create_resource_object("window", task)
            odt  = self.getDesktop(obj)
            name = self.getName(obj)
            hide = 0
            
            try:
                if self._SKIP_TASKBAR in obj.get_full_property(self._STATE, Xatom.ATOM).value:
                    hide = 1
            except:
                pass
            try:
                tclass = obj.get_full_property(Xatom.WM_CLASS, Xatom.STRING)
                      
                if tclass is None:
                    tclass = ["",""]
                else:
                    tclass = tclass.value.split("\0")[:2]
                    for t in tclass:
                        if t in HIDE_LIST:
                            hide = 1
            except:
                continue 
                
            if name == "PyPanel":
                hide = 1
                count += 1
                if count == 2:
                    sys.stderr.write("\nPyPanel is already running! Terminating ...\n\n")
                    sys.exit()              
            if not hide:
                obj.change_attributes(event_mask=(
                    X.PropertyChangeMask|X.FocusChangeMask|X.StructureNotifyMask))
            
            t = Obj(id=task, obj=obj, name=name, tclass=tclass, x1=-1, x2=-1,
                desk=odt, visible=0, hidden=hide)
                
            panel[TASKS].order.append(task)
            panel[TASKS].tasks[task] = t
            self.setIcon(t)
                                          
            if self.setState(t, panel):
                update = 1
            if not update and odt == cdt:
                update = 1
        
        if update:
            self.updatePanel(root, win, panel)
                              
    #-------------------------------------
    def loop(self, dsp, root, win, panel):
    #-------------------------------------
        """ Event loop - handle events as they occur until we're killed """ 
        if CLOCK:
            clock = panel[CLOCK]
        if TRAY:
            tray = panel[TRAY]
            
        tasks = panel[TASKS]  
        focus = dsp.get_input_focus().focus
        
        if hasattr(focus, "id"):
            self.focus = focus.id 
                   
        while 1:     
            while dsp.pending_events():
                e = dsp.next_event()
                if e.type == X.ButtonRelease:
                    self.buttonRelease(root, panel, e)
                elif e.type == X.DestroyNotify:
                    if self.taskDelete(e.window.id, panel):
                        self.updatePanel(root, win, panel)
                elif e.type == X.PropertyNotify:
                    if e.atom in [self._CURRENT_DESKTOP, self._DESKTOP]:
                        if hasattr(e, "window"):
                            if e.window.id in tasks.tasks:
                                t = tasks.tasks[e.window.id]
                                t.desk = self.getDesktop(t.obj)
                        self.updatePanel(root, win, panel)
                    elif e.atom in [self._DESKTOP_NAMES, self._DESKTOP_COUNT]:
                        self.getDesktopNames()
                        self.updatePanel(root, win, panel)                                  
                    elif e.atom == self._CLIENT_LIST:
                        self.updateTasks(dsp, root, win, panel)
                    elif e.atom == self._RPM:
                        self.updatePanel(root, win, panel)
                    elif e.window.id in tasks.tasks:
                        if e.atom in [self._STATE, self._WMSTATE]:
                            if self.setState(tasks.tasks[e.window.id], panel, win):
                                self.updatePanel(root, win, panel)
                        elif e.atom in [Xatom.WM_NAME, self._NAME]:
                            self.setName(win, tasks.tasks[e.window.id])
                        elif e.atom in [Xatom.WM_HINTS, self._ICON]:
                            t = tasks.tasks[e.window.id]
                            self.setIcon(t)
                            if t.visible:
                                x = t.x1 + P_SPACER
                                self.clearPanel(x, 0, t.x2-x, P_HEIGHT)
                                if self.getIcon(t, x):
                                    x += I_WIDTH+P_SPACER
                                self.drawText(t, x, t.x2-x-P_SPACER)
                elif e.type == X.ConfigureNotify and TRAY:
                    if e.window.id in tray.tasks:
                        task = tray.tasks[e.window.id]
                        task.obj.configure(onerror=self.error, width=task.width, height=task.height)                                            
                elif e.type == X.ClientMessage and TRAY:
                    if e.window == self.selowin:
                        data = e.data[1][1] # opcode
                        task = e.data[1][2] # taskid
                        if e.client_type == self._OPCODE and data == 0:
                            # SYSTEM_TRAY_REQUEST_DOCK opcode = 0
                            obj = dsp.create_resource_object("window", task)
                            obj.reparent(tray.window.id, 0, 0)   
                            obj.change_attributes(event_mask=(X.ExposureMask|X.StructureNotifyMask))
                            tray.tasks[task] = Obj(obj=obj, x=0, y=0, width=0, height=TRAY_I_HEIGHT)
                            tray.order.append(task)                            
                            self.updatePanel(root, win, panel)
                elif e.type == X.EnterNotify and self.hidden:
                    if e.window.id == win.id:
                        self.toggleHidden() 
                        self.updateTasks(dsp, root, win, panel)
                elif e.type == X.FocusIn:
                    prev_focus = self.focus
                    self.focus = e.window.id
                    for wid in (e.window.id, prev_focus):
                        if wid in tasks.tasks:
                            if self.setState(tasks.tasks[wid], panel, win):
                                self.updatePanel(root, win, panel)    
                elif e.type == X.Expose and e.count == 0:
                    if e.width == P_WIDTH:
                        win.change_property(self._DESKTOP, Xatom.CARDINAL, 32, [0xffffffffL])
                        self.sendEvent(win, self._STATE, [1, self._STICKY])
                        self.sendEvent(win, self._STATE, [1, self._SKIP_PAGER])
                        self.sendEvent(win, self._STATE, [1, self._SKIP_TASKBAR])
                        if ABOVE:
                            self.sendEvent(win, self._STATE, [1, self._ABOVE])
                        else:
                            self.sendEvent(win, self._STATE, [1, self._BELOW])
                        self.updateTasks(dsp, root, win, panel, 1)                      
                    else:
                        self.updatePanel(root, win, panel)
                        
            rs, ws, es = select.select([dsp.display.socket], [], [], CLOCK_DELAY) 
            if not rs:
                if AUTOHIDE and not self.hidden:
                    self.toggleHidden()
            if CLOCK:
                now = time.strftime(CLOCK_FORMAT, time.localtime())
                if clock.name != now:
                    clock.name = now
                    self.clearPanel(clock.x1+1, 0, clock.x2-(clock.x1+1), P_HEIGHT)
                    self.drawText(clock, clock.x1+P_SPACER, 0)
                                              
#----------------------------------------------------------------------------
#                                  Main
#----------------------------------------------------------------------------
from distutils import sysconfig
from ppmodule import ppinit, ppshade, ppicon, ppfont, ppfontsize, ppclear
from Xlib import X, display, error, Xatom, Xutil
import Xlib.protocol.event
import locale, os, pwd, select, sys, time

# New default config options which may not exist in ~/.pypanelrc if upgrading
# v2.2
HIDDEN_SIZE   = 2   
SHOWBORDER    = 0 
SHOWMINIMIZED = 0
# v2.3
ABOVE         = 1 
LAUNCHER      = 0  
LAUNCH_LIST   = [] 
APPL_I_HEIGHT = 24         
APPL_I_WIDTH  = 24  
ICON_LIST     = {"default":""}  
# v2.4
SHADOWS                = 0   
TASK_SHADOW_COLOR      = "0xffffff"
FOCUSED_SHADOW_COLOR   = "0xffffff"
SHADED_SHADOW_COLOR    = "0xffffff"
MINIMIZED_SHADOW_COLOR = "0xffffff" 
DESKTOP_SHADOW_COLOR   = "0xffffff"
CLOCK_SHADOW_COLOR     = "0xffffff"

#-------------------------
if __name__ == "__main__":
#-------------------------
    try:
        src  = None
        dst  = None
        home = pwd.getpwuid(os.getuid())[5]
        if os.access("/etc/pypanelrc", os.F_OK|os.R_OK):
            src = "/etc/pypanelrc"
            execfile(src)
        if not os.access("%s/.pypanelrc" % home, os.F_OK|os.R_OK):
            # Create ~/.pypanelrc from /etc/pypanelrc if it exists else
            # create it from /<pythonlib>/site-packages/pypanel/pypanelrc
            import shutil
            if not src:
                src = "%s/pypanel/pypanelrc" % sysconfig.get_python_lib()
            dst = "%s/.pypanelrc" % home
            shutil.copyfile(src, dst)
        execfile("%s/.pypanelrc" % home)
        del src, dst, home
    except StandardError, e:
        sys.stderr.write("\nFailed to open ~/.pypanelrc -\n\n")
        sys.stderr.write(str(e)+"\n\n")
        sys.exit()
        
    # Version check
    main   = 2.4
    config = globals().get("VERSION", None)
    
    # Get the startup delay
    delay  = globals().get("STARTUP_DELAY", None)

    # Set locale to user's default
    locale.setlocale(locale.LC_ALL, "")
    
    if not config or config != main:
        sys.stderr.write("\npypanelrc version : %s\n" % config)
        sys.stderr.write("pypanel   version : %s\n" % main)
        sys.stderr.write("\nA current pypanelrc example can be found here -\n")
        sys.stderr.write("%s/pypanel/pypanelrc\n\n" % sysconfig.get_python_lib())
    del main, config

    # If delay is set, pause, and let windowmanager load
    if delay:
        time.sleep(delay)

    PyPanel(display.Display())
