/ src / hal / user_comps / gladevcp.py
gladevcp.py
  1  #!/usr/bin/env python
  2  # vim: sts=4 sw=4 et
  3  #    This is a component of EMC
  4  #    gladevcp Copyright 2010 Chris Morley
  5  #
  6  #
  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 20  
 21  """ Python / GLADE based Virtual Control Panel for EMC
 22  
 23      A virtual control panel (VCP) is used to display and control
 24      HAL pins.
 25  
 26      Usage: gladevcp -g position -c compname -H halfile -x windowid myfile.glade
 27      compname is the name of the HAL component to be created.
 28      halfile contains hal commands to be executed with halcmd after the hal component is ready
 29      The name of the HAL pins associated with the VCP will begin with 'compname.'
 30  
 31      myfile.glade is an XML file which specifies the layout of the VCP.
 32  
 33      -g option allows setting of the initial position of the panel
 34  """
 35  import sys, os, subprocess
 36  import traceback
 37  import warnings
 38  
 39  import hal
 40  from optparse import Option, OptionParser
 41  import gtk
 42  import gtk.glade
 43  import gobject
 44  import signal
 45  
 46  import gladevcp.makepins
 47  from gladevcp.gladebuilder import GladeBuilder
 48  from gladevcp import xembed
 49  from hal_glib import GStat
 50  GSTAT = GStat()
 51  
 52  options = [ Option( '-c', dest='component', metavar='NAME'
 53                    , help="Set component name to NAME. Default is basename of UI file")
 54            , Option( '-d', action='store_true', dest='debug'
 55                    , help="Enable debug output")
 56            , Option( '-g', dest='geometry', default="", help="""Set geometry WIDTHxHEIGHT+XOFFSET+YOFFSET.
 57  Values are in pixel units, XOFFSET/YOFFSET is referenced from top left of screen
 58  use -g WIDTHxHEIGHT for just setting size or -g +XOFFSET+YOFFSET for just position""")
 59            , Option( '-H', dest='halfile', metavar='FILE'
 60                    , help="execute hal statements from FILE with halcmd after the component is set up and ready")
 61            , Option( '-m', dest='maximum', default=False, help="Force panel window to maxumize")
 62            , Option( '-r', dest='gtk_rc', default="",
 63                      help="read custom GTK rc file to set widget style")
 64            , Option( '-R', dest='gtk_workaround', action='store_false',default=True,
 65                      help="disable workaround for GTK bug to properly read ~/.gtkrc-2.0 gtkrc files")
 66            , Option( '-t', dest='theme', default="", help="Set gtk theme. Default is system theme")
 67            , Option( '-x', dest='parent', type=int, metavar='XID'
 68                    , help="Reparent gladevcp into an existing window XID instead of creating a new top level window")
 69            , Option( '--xid', action='store_true', dest='push_XID'
 70                    , help="reparent window into a plug add push the plug xid number to standardout")
 71            , Option( '-u', dest='usermod', action='append', default=[], metavar='FILE'
 72                    , help='Use FILEs as additional user defined modules with handlers')
 73            , Option( '-U', dest='useropts', action='append', metavar='USEROPT', default=[]
 74                    , help='pass USEROPTs to Python modules')
 75            , Option( '--always_above', action='store_true', dest='always_above_flag'
 76                    , help="Request the window To always be above other windows")
 77            ]
 78  
 79  signal_func = 'on_unix_signal'
 80  
 81  gladevcp_debug = 0
 82  def dbg(string):
 83      global gladevcp_debug
 84      if not gladevcp_debug: return
 85      print string
 86  
 87  def on_window_destroy(widget, data=None):
 88          gtk.main_quit()
 89  
 90  class Trampoline(object):
 91      def __init__(self,methods):
 92          self.methods = methods
 93  
 94      def __call__(self, *a, **kw):
 95          for m in self.methods:
 96              m(*a, **kw)
 97  
 98  def load_handlers(usermod,halcomp,builder,useropts):
 99      hdl_func = 'get_handlers'
100      mod = object = None
101      def add_handler(method, f):
102          if method in handlers:
103              handlers[method].append(f)
104          else:
105              handlers[method] = [f]
106  
107      handlers = {}
108      for u in usermod:
109          (directory,filename) = os.path.split(u)
110          (basename,extension) = os.path.splitext(filename)
111          if directory == '':
112              directory = '.'
113          if directory not in sys.path:
114              sys.path.insert(0,directory)
115              dbg('adding import dir %s' % directory)
116  
117          try:
118              mod = __import__(basename)
119          except ImportError,msg:
120              print "module '%s' skipped - import error: %s" %(basename,msg)
121              continue
122          dbg("module '%s' imported OK" % mod.__name__)
123          try:
124              # look for 'get_handlers' function
125              h = getattr(mod,hdl_func,None)
126  
127              if h and callable(h):
128                  dbg("module '%s' : '%s' function found" % (mod.__name__,hdl_func))
129                  objlist = h(halcomp,builder,useropts)
130              else:
131                  # the module has no get_handlers() callable.
132                  # in this case we permit any callable except class Objects in the module to register as handler
133                  dbg("module '%s': no '%s' function - registering only functions as callbacks" % (mod.__name__,hdl_func))
134                  objlist =  [mod]
135              # extract callback candidates
136              for object in objlist:
137                  dbg("Registering handlers in module %s object %s" % (mod.__name__, object))
138                  if isinstance(object, dict):
139                      methods = dict.items()
140                  else:
141                      methods = map(lambda n: (n, getattr(object, n, None)), dir(object))
142                  for method,f in methods:
143                      if method.startswith('_'):
144                          continue
145                      if callable(f):
146                          dbg("Register callback '%s' in %s" % (method, object))
147                          add_handler(method, f)
148          except Exception, e:
149              print "gladevcp: trouble looking for handlers in '%s': %s" %(basename, e)
150              traceback.print_exc()
151  
152      # Wrap lists in Trampoline, unwrap single functions
153      for n,v in list(handlers.items()):
154          if len(v) == 1:
155              handlers[n] = v[0]
156          else:
157              handlers[n] = Trampoline(v)
158  
159      return handlers, mod, object
160  
161  def main():
162      """ creates a HAL component.
163          parsees a glade XML file with gtk.builder or libglade
164          calls gladevcp.makepins with the specified XML file
165          to create pins and register callbacks.
166          main window must be called "window1"
167      """
168      global gladevcp_debug
169      (progdir, progname) = os.path.split(sys.argv[0])
170  
171      usage = "usage: %prog [options] myfile.ui"
172      parser = OptionParser(usage=usage)
173      parser.disable_interspersed_args()
174      parser.add_options(options)
175  
176      (opts, args) = parser.parse_args()
177      if not args:
178          parser.print_help()
179          sys.exit(1)
180  
181      gladevcp_debug = debug = opts.debug
182      xmlname = args[0]
183  
184      #if there was no component name specified use the xml file name
185      if opts.component is None:
186          opts.component = os.path.splitext(os.path.basename(xmlname))[0]
187  
188      #try loading as a libglade project
189      try:
190          builder = gtk.Builder()
191          builder.add_from_file(xmlname)
192      except:
193          try:
194              # try loading as a gtk.builder project
195              dbg("**** GLADE VCP INFO:    Not a builder project, trying to load as a lib glade project")
196              builder = gtk.glade.XML(xmlname)
197              builder = GladeBuilder(builder)
198  
199          except Exception,e:
200              print >> sys.stderr, "**** GLADE VCP ERROR:    With xml file: %s : %s" % (xmlname,e)
201              sys.exit(0)
202  
203      window = builder.get_object("window1")
204  
205      window.set_title(opts.component)
206  
207      try:
208          halcomp = hal.component(opts.component)
209      except:
210          print >> sys.stderr, "*** GLADE VCP ERROR:    Asking for a HAL component using a name that already exists."
211          sys.exit(0)
212  
213      panel = gladevcp.makepins.GladePanel( halcomp, xmlname, builder, None)
214  
215      # at this point, any glade HL widgets and their pins are set up.
216      handlers, mod, obj = load_handlers(opts.usermod,halcomp,builder,opts.useropts)
217  
218      # so widgets can call handler functions - give them refeence to the handler object
219      panel.set_handler(obj)
220  
221      builder.connect_signals(handlers)
222  
223      # This option puts the gladevcp panel into a plug and pushed the plug's
224      # X window id number to standard output - so it can be reparented exterally
225      # it also forwards events to qtvcp
226      if opts.push_XID:
227          if not opts.debug:
228              # supress warnings when x window closes
229              warnings.filterwarnings("ignore")
230          # block X errors since gdk error handling silently exits the
231          # program without even the atexit handler given a chance
232          gtk.gdk.error_trap_push()
233  
234          forward = os.environ.get('QTVCP_FORWARD_EVENTS_TO', None)
235          if forward:
236              xembed.keyboard_forward(window, forward)
237  
238      # This option reparents gladevcp in a given X window id.
239      # it also forwards keyboard events from gladevcp to AXIS
240      if opts.parent:
241          if not opts.debug:
242              # supress warnings when x window closes
243              warnings.filterwarnings("ignore")
244          # block X errors since gdk error handling silently exits the
245          # program without even the atexit handler given a chance
246          gtk.gdk.error_trap_push()
247  
248          window = xembed.reparent(window, opts.parent)
249  
250          forward = os.environ.get('AXIS_FORWARD_EVENTS_TO', None)
251          if forward:
252              xembed.keyboard_forward(window, forward)
253  
254      window.connect("destroy", on_window_destroy)
255      window.show()
256  
257      # for window resize and or position options
258      if "+" in opts.geometry:
259          try:
260              j =  opts.geometry.partition("+")
261              pos = j[2].partition("+")
262              window.move( int(pos[0]), int(pos[2]) )
263          except:
264              print >> sys.stderr, "**** GLADE VCP ERROR:    With window position data"
265              parser.print_usage()
266              sys.exit(1)
267      if "x" in opts.geometry:
268          try:
269              if "+" in opts.geometry:
270                  j =  opts.geometry.partition("+")
271                  t = j[0].partition("x")
272              else:
273                  t = window_geometry.partition("x")
274              window.resize( int(t[0]), int(t[2]) )
275          except:
276              print >> sys.stderr, "**** GLADE VCP ERROR:    With window resize data"
277              parser.print_usage()
278              sys.exit(1)
279  
280      if opts.gtk_workaround:
281          # work around https://bugs.launchpad.net/ubuntu/+source/pygtk/+bug/507739
282          # this makes widget and widget_class matches in gtkrc and theme files actually work
283          dbg( "activating GTK bug workaround for gtkrc files")
284          for o in builder.get_objects():
285              if isinstance(o, gtk.Widget):
286                  # retrieving the name works only for GtkBuilder files, not for
287                  # libglade files, so be cautious about it
288                  name = gtk.Buildable.get_name(o)
289                  if name: o.set_name(name)
290  
291      if opts.gtk_rc:
292          dbg( "**** GLADE VCP INFO: %s reading gtkrc file '%s'" %(opts.component,opts.gtk_rc))
293          gtk.rc_add_default_file(opts.gtk_rc)
294          gtk.rc_parse(opts.gtk_rc)
295  
296      if opts.theme:
297          dbg("**** GLADE VCP INFO:    Switching %s to '%s' theme" %(opts.component,opts.theme))
298          settings = gtk.settings_get_default()
299          settings.set_string_property("gtk-theme-name", opts.theme, "")
300  
301      # This needs to be done after geometry moves so on dual screens the window maxumizes to the actual used screen size.
302      if opts.maximum:
303          window.window.maximize()
304      if opts.always_above_flag:
305          window.set_keep_above(True)
306      if opts.halfile:
307          if opts.halfile[-4:] == ".tcl":
308              cmd = ["haltcl", opts.halfile]
309          else:
310              cmd = ["halcmd", "-f", opts.halfile]
311          res = subprocess.call(cmd, stdout=sys.stdout, stderr=sys.stderr)
312          if res:
313              print >> sys.stderr, "'%s' exited with %d" %(' '.join(cmd), res)
314              sys.exit(res)
315  
316      # User components are set up so report that we are ready
317      halcomp.ready()
318      GSTAT.forced_update()
319  
320      # push the XWindow id number to standard out
321      if opts.push_XID or opts.parent:
322          gdkwin = window.get_window()
323          w_id = gdkwin.xid
324          print >> sys.stdout,w_id
325          sys.stdout.flush()
326  
327      if handlers.has_key(signal_func):
328          dbg("Register callback '%s' for SIGINT and SIGTERM" %(signal_func))
329          signal.signal(signal.SIGTERM, handlers[signal_func])
330          signal.signal(signal.SIGINT,  handlers[signal_func])
331  
332      try:
333          gtk.main()
334      except KeyboardInterrupt:
335          sys.exit(0)
336      finally:
337          halcomp.exit()
338  
339      if opts.parent or opts.push_XID:
340          gtk.gdk.flush()
341          error = gtk.gdk.error_trap_pop()
342          if error and opts.debug:
343              print >> sys.stderr, "**** GLADE VCP ERROR:    X Protocol Error: %s" % str(error)
344  
345  
346  if __name__ == '__main__':
347      main()