#! /usr/bin/env python # -*- coding: ISO-8859-15 -*- # PyKotIcon - Client side helper for PyKota # # (c) 2003, 2004, 2005, 2006 Jerome Alet # 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. # # $Id$ # # import sys import os import urllib import urllib2 import locale import gettext import socket import threading import xmlrpclib import SimpleXMLRPCServer import time if sys.platform == "win32" : isWindows = 1 try : import win32api except ImportError : raise ImportError, "Mark Hammond's Win32 Extensions are missing. Please install them." else : iconsdir = os.path.split(sys.argv[0])[0] else : isWindows = 0 iconsdir = "/usr/share/pykoticon" # TODO : change this import pwd try : import wx hasWxPython = 1 except ImportError : hasWxPython = 0 raise ImportError, "wxPython is missing. Please install it." def getCurrentUserName() : """Retrieves the current user's name.""" if isWindows : return win32api.GetUserName() else : try : return pwd.getpwuid(os.geteuid())[0] except : return "** Unknown **" class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer) : """My own server class.""" allow_reuse_address = True def __init__(self, frame, printserver, localport, debug=False) : SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, ('0.0.0.0', localport)) self.printServer = printserver self.frame = frame self.debug = debug loop = threading.Thread(target=self.mainloop) loop.start() def logDebug(self, message) : """Logs a debug message if debug mode is active.""" if self.debug : sys.stderr.write("%s\n" % message) def export_quitApplication(self) : """Makes the application quit.""" self.logDebug("Remote host asked to close the application.") self.frame.quitEvent.set() wx.CallAfter(self.frame.OnClose, None) return True def export_askDatas(self, labels, varnames, varvalues) : """Asks some textual datas defined by a list of labels, a list of variables' names and a list of variables values in a mapping.""" wx.CallAfter(self.frame.askDatas, labels, varnames, varvalues) # ugly, isn't it ? while self.frame.dialogAnswer is None : time.sleep(0.1) retcode = self.frame.dialogAnswer self.frame.dialogAnswer = None # prepare for next call, just in case return retcode def export_showDialog(self, message, yesno) : """Opens a notification or confirmation dialog.""" wx.CallAfter(self.frame.showDialog, message, yesno) # ugly, isn't it ? while self.frame.dialogAnswer is None : time.sleep(0.1) retcode = self.frame.dialogAnswer self.frame.dialogAnswer = None # prepare for next call, just in case return retcode def export_nop(self) : """Does nothing, but allows a clean shutdown from the frame itself.""" self.logDebug("No operation !") return True def verify_request(self, request, client_address) : """Ensures that requests which don't come from the print server are rejected.""" (client, port) = client_address if socket.gethostbyname(self.printServer) == client : self.logDebug("%s accepted." % client) return True else : # Unauthorized access ! self.logDebug("%s rejected." % client) return False def _dispatch(self, method, params) : """Ensure that only export_* methods are available.""" return getattr(self, "export_%s" % method)(*params) def mainloop(self) : """XML-RPC Server's main loop.""" self.register_function(self.export_askDatas) self.register_function(self.export_showDialog) self.register_function(self.export_quitApplication) self.register_function(self.export_nop) while not self.frame.quitEvent.isSet() : self.handle_request() self.server_close() sys.exit(0) class GenericInputDialog(wx.Dialog) : """Generic input dialog box.""" def __init__(self, parent, id, labels, varnames, varvalues): wx.Dialog.__init__(self, parent, id, \ _("PyKotIcon data input"), \ style = wx.CAPTION \ | wx.THICK_FRAME \ | wx.STAY_ON_TOP \ | wx.DIALOG_MODAL) self.variables = [] vsizer = wx.BoxSizer(wx.VERTICAL) for i in range(len(varnames)) : varname = varnames[i] try : label = labels[i] except IndexError : label = "" labelid = wx.NewId() varid = wx.NewId() label = wx.StaticText(self, labelid, label) if varname.lower().find("password") != -1 : variable = wx.TextCtrl(self, varid, varvalues.get(varname, ""), style=wx.TE_PASSWORD) else : variable = wx.TextCtrl(self, varid, varvalues.get(varname, "")) self.variables.append(variable) hsizer = wx.BoxSizer(wx.HORIZONTAL) hsizer.Add(label, 0, wx.ALIGN_CENTER | wx.ALIGN_RIGHT | wx.ALL, 5) hsizer.Add(variable, 0, wx.ALIGN_CENTER | wx.ALIGN_LEFT | wx.ALL, 5) vsizer.Add(hsizer, 0, wx.ALIGN_CENTER | wx.ALL, 5) okbutton = wx.Button(self, wx.ID_OK, "OK") vsizer.Add(okbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5) self.SetAutoLayout(True) self.SetSizerAndFit(vsizer) self.Layout() class PyKotIcon(wx.Frame): """Main class.""" def __init__(self, parent, id): self.dialogAnswer = None wx.Frame.__init__(self, parent, id, \ _("PyKotIcon info for %s") % getCurrentUserName(), \ size = (-1, -1), \ style = wx.DEFAULT_FRAME_STYLE \ | wx.SIZE_AUTO_HEIGHT \ | wx.SIZE_AUTO_WIDTH \ | wx.NO_FULL_REPAINT_ON_RESIZE) try : self.tbicon = wx.TaskBarIcon() except AttributeError : self.tbicon = None # No taskbar icon facility self.greenicon = wx.Icon(os.path.join(iconsdir, "pykoticon-green.ico"), \ wx.BITMAP_TYPE_ICO) self.redicon = wx.Icon(os.path.join(iconsdir, "pykoticon-red.ico"), \ wx.BITMAP_TYPE_ICO) self.SetIcon(self.greenicon) if self.tbicon is not None : self.tbicon.SetIcon(self.greenicon, "PyKotIcon") wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate) wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu) self.TBMENU_RESTORE = wx.NewId() self.TBMENU_CLOSE = wx.NewId() wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \ self.OnTaskBarActivate) wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \ self.OnTaskBarClose) self.menu = wx.wxMenu() self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota")) self.menu.Append(self.TBMENU_CLOSE, _("Quit")) wx.EVT_ICONIZE(self, self.OnIconify) wx.EVT_CLOSE(self, self.OnClose) self.Show(True) def OnIconify(self, event) : if not self.IsIconized() : self.Iconize(True) #self.Hide() def OnTaskBarActivate(self, event) : if self.IsIconized() : self.Iconize(False) if not self.IsShown() : self.Show(True) self.Raise() def OnClose(self, event) : self.closeServer() if hasattr(self, "menu") : self.menu.Destroy() del self.menu if hasattr(self, "tbicon") and self.tbicon : self.tbicon.Destroy() del self.tbicon self.Destroy() def OnTaskBarMenu(self, event) : if self.tbicon : self.tbicon.PopupMenu(self.menu) def OnTaskBarClose(self, event) : self.Close() def showDialog(self, message, yesno) : """Opens a notification dialog.""" self.dialogAnswer = None if yesno : caption = _("Confirmation") style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION else : caption = _("Information") style = wx.OK | wx.ICON_INFORMATION style |= wx.STAY_ON_TOP dialog = wx.MessageDialog(self, message, caption, style) self.dialogAnswer = ((dialog.ShowModal() == wx.ID_NO) and "CANCEL") or "OK" dialog.Destroy() def askDatas(self, labels, varnames, varvalues) : """Opens a dialog box asking for data entry.""" # use it this way : self.askDatas(["Username", "Password", "Billing code"], ["username", "password", "billingcode"]) self.dialogAnswer = None dialog = GenericInputDialog(self, wx.ID_ANY, labels, varnames, varvalues) retvalues = {} if dialog.ShowModal() == wx.ID_OK : retvalues["isValid"] = True for i in range(len(varnames)) : retvalues[varnames[i]] = dialog.variables[i].GetValue() else : retvalues["isValid"] = False for k in varvalues.keys() : retvalues[k] = "" self.dialogAnswer = retvalues dialog.Destroy() def closeServer(self) : """Tells the xml-rpc server to exit.""" if not self.quitEvent.isSet() : self.quitEvent.set() server = xmlrpclib.ServerProxy("http://localhost:%s" % self.port) try : # wake the server with an empty request # for it to see the event object # which has just been set server.nop() except : # Probably already stopped pass def postInit(self, printserver, localport) : """Starts the XML-RPC server.""" self.quitEvent = threading.Event() self.port = localport self.server = MyXMLRPCServer(self, printserver, localport, debug=True) class PyKotIconApp(wx.PySimpleApp): def OnInit(self) : self.frame = PyKotIcon(None, wx.ID_ANY) return True def postInit(self, printserver, localport) : """Continues processing.""" self.frame.postInit(printserver, localport) #self.frame.Show(True) def main(printserver, localport): """Program's entry point.""" try : locale.setlocale(locale.LC_ALL, "") except (locale.Error, IOError) : sys.stderr.write("Problem while setting locale.\n") try : gettext.install("pykoticon") except : gettext.NullTranslations().install() app = PyKotIconApp() try : localport = int(localport) except (TypeError, ValueError) : raise ValueError, "Invalid TCP port parameter %s\n" % localport app.postInit(printserver, localport) app.MainLoop() def crashed() : """Minimal crash method.""" import traceback lines = [] for line in traceback.format_exception(*sys.exc_info()) : lines.extend([l for l in line.split("\n") if l]) msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)]) sys.stderr.write(msg) sys.stderr.flush() if __name__ == '__main__': if len(sys.argv) >= 2 : arg = sys.argv[1] if arg in ("-v", "--version") : print "0.3" elif arg in ("-h", "--help") : sys.stderr.write("usage : pykoticon pykota_server_hostname_or_ip_address localTCPPort\n") else : main(*sys.argv[1:3]) else : sys.stderr.write("usage : pykoticon pykota_server_hostname_or_ip_address localTCPPort\n")