root / pykoticon / trunk / bin / pykoticon @ 92

Revision 92, 10.1 kB (checked in by jerome, 18 years ago)

Now exit is really clean

  • 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 wxPython.wx
53    import wx
54    hasWxPython = 1
55except ImportError :   
56    hasWxPython = 0
57    raise ImportError, "wxPython is missing. Please install it."
58   
59def getCurrentUserName() :
60    """Retrieves the current user's name."""
61    if isWindows :
62        return win32api.GetUserName()
63    else :   
64        try :
65            return pwd.getpwuid(os.geteuid())[0]
66        except :
67            return "** Unknown **"
68       
69class MyXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer) :
70    """My own server class."""
71    allow_reuse_address = True
72    def __init__(self, frame, printserver, localport, debug=False) :
73        SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self, ('0.0.0.0', localport))
74        self.printServer = printserver
75        self.frame = frame
76        self.debug = debug
77        loop = threading.Thread(target=self.mainloop)
78        loop.start()
79       
80    def logDebug(self, message) :   
81        """Logs a debug message if debug mode is active."""
82        if self.debug :
83            sys.stderr.write("%s\n" % message)
84           
85    def export_quitApplication(self) :   
86        """Makes the application quit."""
87        self.logDebug("Remote host asked to close the application.")
88        self.frame.quitEvent.set()
89        wx.CallAfter(self.frame.OnClose, None)
90        return True
91       
92    def export_openConfirmDialog(self, printername, username, jobid, jobtitle, jobsize) :   
93        """Opens a dialog to ask the user to confirm or cancel the print job.
94       
95           Returns True to confirm, False to cancel.
96        """   
97        wx.CallAfter(self.frame.askConfirmation, printername, username, jobid, jobtitle, jobsize)
98       
99        # ugly, isn't it ?
100        while self.frame.askConfirmationResult is None :
101            time.sleep(0.5)
102        retcode = self.frame.askConfirmationResult   
103        self.frame.askConfirmationResult = None # prepare for next call, just in case
104        return retcode
105       
106    def export_nop(self) :   
107        """Does nothing, but allows a clean shutdown from the frame itself."""
108        self.logDebug("No operation !")
109        return True
110       
111    def verify_request(self, request, client_address) :
112        """Ensures that requests which don't come from the print server are rejected."""
113        (client, port) = client_address
114        if socket.gethostbyname(self.printServer) == client :
115            self.logDebug("%s accepted." % client)
116            return True
117        else :
118            # Unauthorized access !
119            self.logDebug("%s rejected." % client)
120            return False
121       
122    def _dispatch(self, method, params) :   
123        """Ensure that only export_* methods are available."""
124        return getattr(self, "export_%s" % method)(*params)
125       
126    def mainloop(self) :
127        """XML-RPC Server's main loop."""
128        self.register_function(self.export_openConfirmDialog)
129        self.register_function(self.export_quitApplication)
130        self.register_function(self.export_nop)
131        while not self.frame.quitEvent.isSet() :
132            self.handle_request()
133        self.server_close()   
134        sys.exit(0)
135   
136class PyKotIcon(wx.Frame):
137    """Main class."""
138    def __init__(self, parent, id):
139        self.askConfirmationResult = None
140        wx.Frame.__init__(self, parent, wx.ID_ANY, \
141               _("PyKota info for  %s") % getCurrentUserName(), \
142               size = (-1, -1), \
143               style = wxPython.wx.wxDEFAULT_FRAME_STYLE \
144                     | wxPython.wx.wxSIZE_AUTO_HEIGHT \
145                     | wxPython.wx.wxSIZE_AUTO_WIDTH \
146                     | wxPython.wx.wxNO_FULL_REPAINT_ON_RESIZE)
147#        try :             
148#            self.tbicon = wxPython.wx.wxTaskBarIcon()
149#        except AttributeError :   
150#            self.tbicon = None # No taskbar icon facility, old wxWidgets maybe
151#       
152#        self.greenicon = wxPython.wx.wxIcon(os.path.join(iconsdir, "pykoticon-green.ico"), \
153#                                  wxPython.wx.wxBITMAP_TYPE_ICO)
154#        self.redicon = wxPython.wx.wxIcon(os.path.join(iconsdir, "pykoticon-red.ico"), \
155#                                  wxPython.wx.wxBITMAP_TYPE_ICO)
156#       
157#        self.SetIcon(self.greenicon)
158#        if self.tbicon is not None :
159#            self.tbicon.SetIcon(self.greenicon, "PyKotIcon")
160#            wxPython.wx.EVT_TASKBAR_LEFT_DCLICK(self.tbicon, self.OnTaskBarActivate)
161#            wxPython.wx.EVT_TASKBAR_RIGHT_UP(self.tbicon, self.OnTaskBarMenu)
162#       
163#            self.TBMENU_RESTORE = wx.NewId()
164#            self.TBMENU_CLOSE = wx.NewId()
165#            wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_RESTORE, \
166#                                              self.OnTaskBarActivate)
167#            wxPython.wx.EVT_MENU(self.tbicon, self.TBMENU_CLOSE, \
168#                                              self.OnTaskBarClose)
169#            self.menu = wxPython.wx.wxMenu()
170#            self.menu.Append(self.TBMENU_RESTORE, _("Show Print Quota"))
171#            self.menu.Append(self.TBMENU_CLOSE, _("Quit"))
172#       
173        wxPython.wx.EVT_ICONIZE(self, self.OnIconify)
174        wxPython.wx.EVT_CLOSE(self, self.OnClose)
175        self.Show(True)
176       
177    def closeServer(self) :   
178        """Tells the xml-rpc server to exit."""
179        if not self.quitEvent.isSet() :
180            self.quitEvent.set()
181        server = xmlrpclib.ServerProxy("http://localhost:%s" % self.port)   
182        try :
183            # wake the server with an empty request
184            # for it to see the event object
185            # which has just been set
186            server.nop()
187        except :   
188            # Probably already stopped
189            pass
190       
191    def postInit(self, printserver, localport) :   
192        """Starts the XML-RPC server."""
193        self.quitEvent = threading.Event()
194        self.port = localport
195        self.server = MyXMLRPCServer(self, printserver, localport, debug=True)
196   
197    def OnIconify(self, event) :
198        self.Hide()
199
200    def OnTaskBarActivate(self, event) :
201        #if self.IsIconized() :
202        #    self.Iconize(False)
203        if not self.IsShown() :
204            self.Show(True)
205        self.Raise()
206
207    def OnClose(self, event) :
208        sys.stderr.write("Close event !\n")
209        self.closeServer()
210        if hasattr(self, "menu") :
211            self.menu.Destroy()
212            del self.menu
213        if hasattr(self, "tbicon") and self.tbicon :
214            self.tbicon.Destroy()
215            del self.tbicon
216        self.Destroy()
217
218    def OnTaskBarMenu(self, event) :
219        #if self.tbicon :
220        #    self.tbicon.PopupMenu(self.menu)
221        pass
222
223    def OnTaskBarClose(self, event) :
224        self.Close()
225       
226    def askConfirmation(self, printername, username, jobid, jobtitle, jobsize) :
227        """Asks for confirmation before printing."""
228        message = _("""Hello %(username)s,
229       
230You sent job %(jobid)s (%(jobtitle)s) to printer %(printername)s.
231
232This job seems to be %(jobsize)s pages long.
233
234Please confirm or cancel.""") % locals()
235                     
236        dialog = wx.MessageDialog(self, message, _("Confirmation"), wx.OK | wx.CANCEL)
237        self.askConfirmationResult = dialog.ShowModal()
238        dialog.Destroy()
239
240class PyKotIconApp(wx.PySimpleApp):
241    def OnInit(self) :
242        self.frame = PyKotIcon(None, -1)
243        return True
244       
245    def postInit(self, printserver, localport) :   
246        """Continues processing."""
247        self.frame.postInit(printserver, localport)
248        self.frame.Show(True)
249       
250def main(printserver, localport):
251    """Program's entry point."""
252    try :
253        locale.setlocale(locale.LC_ALL, "")
254    except (locale.Error, IOError) :
255        sys.stderr.write("Problem while setting locale.\n")
256    try :
257        gettext.install("pykoticon")
258    except :
259        gettext.NullTranslations().install()
260    app = PyKotIconApp()
261    try :
262        localport = int(localport)   
263    except (TypeError, ValueError) :   
264        raise ValueError, "Invalid TCP port parameter %s\n" % localport
265    app.postInit(printserver, localport)
266    app.MainLoop()
267   
268def crashed() :   
269    """Minimal crash method."""
270    import traceback
271    lines = []
272    for line in traceback.format_exception(*sys.exc_info()) :
273        lines.extend([l for l in line.split("\n") if l])
274    msg = "ERROR: ".join(["%s\n" % l for l in (["ERROR: PyKotIcon"] + lines)])
275    sys.stderr.write(msg)
276    sys.stderr.flush()
277   
278if __name__ == '__main__':
279    if len(sys.argv) >= 2 :
280        arg = sys.argv[1]
281        if arg in ("-v", "--version") :   
282            print "0.3"
283        elif arg in ("-h", "--help") :   
284            print "usage : pykoticon printserver_hostname localport"
285        else :
286            main(*sys.argv[1:3])
287    else :   
288        main("localhost", "7654")
Note: See TracBrowser for help on using the browser.