root / pykoticon / trunk / bin / pykoticon @ 109

Revision 109, 12.9 kB (checked in by jerome, 18 years ago)

Temporarily removed menu which causes problems with wxPython 2.5 and higher.

  • Property svn:keywords set to Id
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# PyKotIcon - Client side helper for PyKota
5#
6# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
21# $Id$
22#
23#
24
25import sys
26import os
27import urllib
28import urllib2
29import locale
30import gettext
31import socket
32import threading
33import xmlrpclib
34import SimpleXMLRPCServer
35
36import time
37
38if sys.platform == "win32" :
39    isWindows = 1
40    try :
41        import win32api
42    except ImportError :   
43        raise ImportError, "Mark Hammond's Win32 Extensions are missing. Please install them."
44    else :   
45        iconsdir = os.path.split(sys.argv[0])[0]
46else :       
47    isWindows = 0
48    iconsdir = "/usr/share/pykoticon"   # TODO : change this
49    import pwd
50   
51try :   
52    import wx
53    hasWxPython = 1
54except ImportError :   
55    hasWxPython = 0
56    raise ImportError, "wxPython is missing. Please install it."
57   
58def getCurrentUserName() :
59    """Retrieves the current user's name."""
60    if isWindows :
61        return win32api.GetUserName()
62    else :   
63        try :
64            return pwd.getpwuid(os.geteuid())[0]
65        except :
66            return "** Unknown **"
67       
68class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer) :
69    """My own server class."""
70    allow_reuse_address = True
71    def __init__(self, frame, printserver, localport, debug=False) :
72        SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, ('0.0.0.0', localport))
73        self.printServer = printserver
74        self.frame = frame
75        self.debug = debug
76        loop = threading.Thread(target=self.mainloop)
77        loop.start()
78       
79    def logDebug(self, message) :   
80        """Logs a debug message if debug mode is active."""
81        if self.debug :
82            sys.stderr.write("%s\n" % message)
83           
84    def export_quitApplication(self) :   
85        """Makes the application quit."""
86        self.frame.quitEvent.set()
87        wx.CallAfter(self.frame.OnClose, None)
88        return True
89       
90    def export_askDatas(self, labels, varnames, varvalues) :
91        """Asks some textual datas defined by a list of labels, a list of variables' names and a list of variables values in a mapping."""
92        values = {}
93        for (k, v) in varvalues.items() :
94            values[k] = v.data
95        wx.CallAfter(self.frame.askDatas, [ v.data for v in labels ], \
96                                          varnames, \
97                                          values)
98        # ugly, isn't it ?
99        while self.frame.dialogAnswer is None :
100            time.sleep(0.1)
101        retcode = self.frame.dialogAnswer   
102        for (k, v) in retcode.items() :
103            if k != "isValid" :
104                retcode[k] = xmlrpclib.Binary(v)
105        self.frame.dialogAnswer = None # prepare for next call, just in case
106        return retcode
107       
108    def export_showDialog(self, message, yesno) :
109        """Opens a notification or confirmation dialog."""
110        wx.CallAfter(self.frame.showDialog, message.data, yesno)
111        # ugly, isn't it ?
112        while self.frame.dialogAnswer is None :
113            time.sleep(0.1)
114        retcode = self.frame.dialogAnswer   
115        self.frame.dialogAnswer = None # prepare for next call, just in case
116        return retcode
117       
118    def export_nop(self) :   
119        """Does nothing, but allows a clean shutdown from the frame itself."""
120        return True
121       
122    def verify_request(self, request, client_address) :
123        """Ensures that requests which don't come from the print server are rejected."""
124        (client, port) = client_address
125        if socket.gethostbyname(self.printServer) == client :
126            self.logDebug("%s accepted." % client)
127            return True
128        else :
129            # Unauthorized access !
130            self.logDebug("%s rejected." % client)
131            return False
132       
133    def _dispatch(self, method, params) :   
134        """Ensure that only export_* methods are available."""
135        return getattr(self, "export_%s" % method)(*params)
136       
137    def mainloop(self) :
138        """XML-RPC Server's main loop."""
139        self.register_function(self.export_askDatas)
140        self.register_function(self.export_showDialog)
141        self.register_function(self.export_quitApplication)
142        self.register_function(self.export_nop)
143        while not self.frame.quitEvent.isSet() :
144            self.handle_request()
145        self.server_close()   
146        sys.exit(0)
147   
148class GenericInputDialog(wx.Dialog) :
149    """Generic input dialog box."""
150    def __init__(self, parent, id, labels, varnames, varvalues):
151        wx.Dialog.__init__(self, parent, id, \
152               _("PyKotIcon data input"), \
153               style = wx.CAPTION \
154                     | wx.THICK_FRAME \
155                     | wx.STAY_ON_TOP \
156                     | wx.DIALOG_MODAL)
157
158        self.variables = []
159        vsizer = wx.BoxSizer(wx.VERTICAL)
160        for i in range(len(varnames)) :
161            varname = varnames[i]
162            try :
163                label = labels[i]
164            except IndexError :   
165                label = ""
166            labelid = wx.NewId()   
167            varid = wx.NewId()
168            label = wx.StaticText(self, labelid, label)
169            if varname.lower().find("password") != -1 :
170                variable = wx.TextCtrl(self, varid, varvalues.get(varname, ""), style=wx.TE_PASSWORD)
171            else :
172                variable = wx.TextCtrl(self, varid, varvalues.get(varname, ""))
173            self.variables.append(variable)   
174            hsizer = wx.BoxSizer(wx.HORIZONTAL)
175            hsizer.Add(label, 0, wx.ALIGN_CENTER | wx.ALIGN_RIGHT | wx.ALL, 5)
176            hsizer.Add(variable, 0, wx.ALIGN_CENTER | wx.ALIGN_LEFT | wx.ALL, 5)
177            vsizer.Add(hsizer, 0, wx.ALIGN_CENTER | wx.ALL, 5)
178           
179        okbutton = wx.Button(self, wx.ID_OK, "OK")   
180        vsizer.Add(okbutton, 0, wx.ALIGN_CENTER | wx.ALL, 5)
181       
182        self.SetAutoLayout(True)
183        self.SetSizerAndFit(vsizer)
184        self.Layout()
185       
186class PyKotIcon(wx.Frame):
187    """Main class."""
188    def __init__(self, parent, id):
189        self.dialogAnswer = None
190        wx.Frame.__init__(self, parent, id, \
191               _("PyKotIcon info for %s") % getCurrentUserName(), \
192               size = (-1, -1), \
193               style = wx.DEFAULT_FRAME_STYLE \
194                     | wx.SIZE_AUTO_HEIGHT \
195                     | wx.SIZE_AUTO_WIDTH \
196                     | wx.NO_FULL_REPAINT_ON_RESIZE)
197        try :             
198            self.tbicon = wx.TaskBarIcon()
199        except AttributeError :   
200            self.tbicon = None # No taskbar icon facility
201       
202        self.greenicon = wx.Icon(os.path.join(iconsdir, "pykoticon-green.ico"), \
203                                  wx.BITMAP_TYPE_ICO)
204        self.redicon = wx.Icon(os.path.join(iconsdir, "pykoticon-red.ico"), \
205                                  wx.BITMAP_TYPE_ICO)
206       
207        self.SetIcon(self.greenicon)
208        if self.tbicon is not None :
209            self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
210            wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate)
211            wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu)
212       
213            self.TBMENU_RESTORE = wx.NewId()
214            self.TBMENU_CLOSE = wx.NewId()
215            wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \
216                                              self.OnTaskBarActivate)
217            wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \
218                                              self.OnTaskBarClose)
219            #self.menu = wx.wxMenu()
220            #self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota"))
221            #self.menu.Append(self.TBMENU_CLOSE, _("Quit"))
222       
223        wx.EVT_ICONIZE(self, self.OnIconify)
224        wx.EVT_CLOSE(self, self.OnClose)
225        self.Show(True)
226       
227    def OnIconify(self, event) :
228        if not self.IsIconized() :
229            self.Iconize(True)
230        #self.Hide()
231
232    def OnTaskBarActivate(self, event) :
233        if self.IsIconized() :
234            self.Iconize(False)
235        if not self.IsShown() :
236            self.Show(True)
237        self.Raise()
238
239    def OnClose(self, event) :
240        self.closeServer()
241        try :
242            self.menu.Destroy()
243        except AttributeError :   
244            pass
245        try :   
246            self.tbicon.Destroy()
247        except AttributeError :   
248            pass
249        self.Destroy()
250
251    def OnTaskBarMenu(self, event) :
252        if self.tbicon :
253            try :
254                self.tbicon.PopupMenu(self.menu)
255            except AttributeError :   
256                pass
257
258    def OnTaskBarClose(self, event) :
259        self.Close()
260       
261    def showDialog(self, message, yesno) :
262        """Opens a notification dialog."""
263        self.dialogAnswer = None
264        if yesno :
265            caption = _("Confirmation")
266            style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION
267        else :
268            caption = _("Information")
269            style = wx.OK | wx.ICON_INFORMATION
270        style |= wx.STAY_ON_TOP   
271        dialog = wx.MessageDialog(self, message, caption, style)
272        self.dialogAnswer = ((dialog.ShowModal() == wx.ID_NO) and "CANCEL") or "OK"
273        dialog.Destroy()
274       
275    def askDatas(self, labels, varnames, varvalues) :
276        """Opens a dialog box asking for data entry."""
277        # use it this way : self.askDatas(["Username", "Password", "Billing code"], ["username", "password", "billingcode"])
278        self.dialogAnswer = None
279        dialog = GenericInputDialog(self, wx.ID_ANY, labels, varnames, varvalues)
280        retvalues = {}
281        if dialog.ShowModal() == wx.ID_OK :
282            retvalues["isValid"] = True
283            for i in range(len(varnames)) :
284                retvalues[varnames[i]] = dialog.variables[i].GetValue()
285        else :       
286            retvalues["isValid"] = False
287            for k in varvalues.keys() :
288                retvalues[k] = ""
289        self.dialogAnswer = retvalues
290        dialog.Destroy()
291       
292    def closeServer(self) :   
293        """Tells the xml-rpc server to exit."""
294        if not self.quitEvent.isSet() :
295            self.quitEvent.set()
296        server = xmlrpclib.ServerProxy("http://localhost:%s" % self.port)   
297        try :
298            # wake the server with an empty request
299            # for it to see the event object
300            # which has just been set
301            server.nop()
302        except :   
303            # Probably already stopped
304            pass
305       
306    def postInit(self, printserver, localport) :   
307        """Starts the XML-RPC server."""
308        self.quitEvent = threading.Event()
309        self.port = localport
310        self.server = MyXMLRPCServer(self, printserver, localport)
311   
312
313class PyKotIconApp(wx.PySimpleApp):
314    def OnInit(self) :
315        self.frame = PyKotIcon(None, wx.ID_ANY)
316        return True
317       
318    def postInit(self, printserver, localport) :   
319        """Continues processing."""
320        self.frame.postInit(printserver, localport)
321        #self.frame.Show(True)
322       
323def main(printserver, localport):
324    """Program's entry point."""
325    try :
326        locale.setlocale(locale.LC_ALL, "")
327    except (locale.Error, IOError) :
328        sys.stderr.write("Problem while setting locale.\n")
329    try :
330        gettext.install("pykoticon")
331    except :
332        gettext.NullTranslations().install()
333    app = PyKotIconApp()
334    try :
335        localport = int(localport)   
336    except (TypeError, ValueError) :   
337        raise ValueError, "Invalid TCP port parameter %s\n" % localport
338    app.postInit(printserver, localport)
339    app.MainLoop()
340   
341def crashed() :   
342    """Minimal crash method."""
343    import traceback
344    lines = []
345    for line in traceback.format_exception(*sys.exc_info()) :
346        lines.extend([l for l in line.split("\n") if l])
347    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)])
348    sys.stderr.write(msg)
349    sys.stderr.flush()
350   
351if __name__ == '__main__':
352    if len(sys.argv) >= 2 :
353        arg = sys.argv[1]
354        if arg in ("-v", "--version") :   
355            print "0.3"
356        elif arg in ("-h", "--help") :   
357            sys.stderr.write("usage : pykoticon  pykota_server_hostname_or_ip_address  localTCPPort\n")
358        else :
359            main(*sys.argv[1:3])
360    else :   
361        sys.stderr.write("usage : pykoticon  pykota_server_hostname_or_ip_address  localTCPPort\n")
Note: See TracBrowser for help on using the browser.