PanTilt  1.3.2
RoadNarrows Robotics Pan-Tilt Project
pantilt_console.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 
3 ###############################################################################
4 #
5 # Package: RoadNarrows Robotics Laelaps System V Init.d Console
6 #
7 # Link: https://github.com/roadnarrows-robotics/laelaps
8 #
9 # File: laelaps_init.d
10 #
11 ## \file
12 ##
13 ## $LastChangedDate: 2016-03-18 09:57:27 -0600 (Fri, 18 Mar 2016) $
14 ## $Rev: 4354 $
15 ##
16 ## \brief Graphical user interface console to control the Laelaps init.d
17 ## daemons.
18 ##
19 ## \author Robin Knight (robin.knight@roadnarrows.com)
20 ##
21 ## \copyright
22 ## \h_copy 2016-2017. RoadNarrows LLC.\n
23 ## http://www.roadnarrows.com\n
24 ## All Rights Reserved
25 ##
26 # @EulaBegin@
27 # @EulaEnd@
28 #
29 ###############################################################################
30 
31 import sys
32 import os
33 import time
34 import math
35 import subprocess
36 import re
37 import threading
38 import getopt
39 
40 from Tkinter import *
41 from Tkconstants import *
42 from tkFileDialog import *
43 import tkFont
44 
45 # running as su does not necessary have all paths setup - so fix up here
46 sys.path.insert(0, "/usr/local/lib/python2.7/site-packages")
47 sys.path.insert(0, "/prj/lib/python2.7/site-packages")
48 
49 from Laelaps.Utils import *
50 
51 ## \brief Application version. Update as needed.
52 appVersion = '1.0.0'
53 
54 ## \brief Additional image search paths.
55 imagePath = [
56  "/prj/share/Laelaps/images",
57  "/prj/share/Laelaps/images/icons",
58  "/prj/share/appkit/images",
59  "/prj/share/appkit/images/icons",
60  "/usr/local/share/Laelaps/images",
61  "/usr/local/share/Laelaps/images/icons"
62  "/usr/local/share/appkit/images",
63  "/usr/local/share/appkit/images/icons"
64 ]
65 
66 ## \brief Common foreground colors.
67 fgColors = {
68  'normal': 'black',
69  'ok': '#008800',
70  'focus': '#0000aa',
71  'warning': '#aa6600',
72  'error': '#cc0000'
73 }
74 
75 ## \brief Status text foreground colors.
76 statusText = {
77  'unknown': fgColors['normal'],
78  'running': fgColors['ok'],
79  'stopped': fgColors['error']
80 }
81 
82 # pre-compiled regular expressions
83 reDone = re.compile(r"done", re.IGNORECASE)
84 reFail = re.compile(r"fail", re.IGNORECASE)
85 reDoneDone = re.compile(r"done.*done", re.DOTALL | re.IGNORECASE)
86 reFailDone = re.compile(r"fail.*done", re.DOTALL | re.IGNORECASE)
87 reRunning = re.compile(r"\s*is\s*running", re.IGNORECASE)
88 reNotRunning = re.compile(r"\s*is\s*not\s*running", re.IGNORECASE)
89 
90 
91 # ------------------------------------------------------------------------------
92 # Class window
93 # ------------------------------------------------------------------------------
94 
95 ##
96 ## \brief Window class supporting application.
97 ##
98 class window(Frame):
99  #
100  ## \brief Constructor.
101  ##
102  ## \param master Window parent master widget.
103  ## \param cnf Configuration dictionary.
104  ## \param kw Keyword options.
105  #
106  def __init__(self, master=None, cnf={}, **kw):
107  # intialize window data
108  kw = self.initData(kw)
109 
110  self.m_imageLoader = ImageLoader(py_pkg='Laelaps.images',
111  image_paths=imagePath)
112 
113  Frame.__init__(self, master=master, cnf=cnf, **kw)
114  self.master.title("Laelaps Init.d Console")
115 
116  self.m_icons['app_icon'] = \
117  self.m_imageLoader.loadImage("icons/LaelapsInitIcon.png")
118  if self.m_icons['app_icon'] is not None:
119  self.master.tk.call('wm', 'iconphoto', self.master._w,
120  self.m_icons['app_icon'])
121 
122  # craete and show widgets
123  self.createWidgets()
124 
125  self.grid(row=0, column=0, padx=5, pady=5)
126 
127  self.after(100, self.autoRefresh)
128 
129  #
130  ## \brief Initialize class state data.
131  ##
132  ## Any keywords for this application specific window that are not supported
133  ## by the Frame Tkinter class must be removed.
134  ##
135  ## \param kw Keyword options.
136  ##
137  ## \return Modified keywords sans this specific class.
138  #
139  def initData(self, kw):
140  self.m_debug = False # default debug level
141  self.m_icons = {} # must keep loaded icons referenced
142  self.m_wBttn = {} # button widgets
143  self.m_svcKeys = [
144  'laelaps_bsproxy', 'laelaps_roscore', 'laelaps_control',
145  'laelaps_xbox', 'laelaps_teleop']
146  self.m_svcDesc = {
147  'laelaps_bsproxy': 'BotSense Proxy Server',
148  'laelaps_roscore': 'ROS Master, Parameter Server, rosout logging node',
149  'laelaps_control': 'Laelaps Control ROS node',
150  'laelaps_xbox': 'HID Xbox360 daemon / ROS node',
151  'laelaps_teleop': 'Laelaps Teleoperation ROS node'}
152  self.m_lock = threading.Lock()
153 
154  if kw.has_key('debug'):
155  self.m_debug = kw['debug']
156  del kw['debug']
157 
158  # variables only used for debugging
159  if self.m_debug:
160  pass
161 
162  return kw
163 
164  #
165  ## \brief Create gui widgets with supporting data and show.
166  #
167  def createWidgets(self):
168  self.createHeading()
169  self.createLeftButtons()
170  self.createCenterPanel()
171  self.createRightButtons()
172  self.update_idletasks()
173  self.createStatusBar()
174 
175  #
176  ## \brief Create top gui heading.
177  #
178  def createHeading(self):
179  # rn logo
180  w = Label(self)
181  self.m_icons['rn_logo'] = self.m_imageLoader.loadImage("RNLogo48.png");
182  if self.m_icons['rn_logo']:
183  w['image'] = self.m_icons['rn_logo']
184  else:
185  w['text'] = 'rn'
186  w['anchor'] = W
187  w['width'] = 5
188  w.grid(row=0, column=0, sticky=W)
189 
190  # top heading
191  w = Label(self)
192  w['font'] = ('Helvetica', 16)
193  w['text'] = 'Laelaps Init.d Console'
194  w['anchor'] = CENTER
195  w.grid(row=0, column=1, sticky=E+W)
196 
197  # laelaps logo
198  w = Label(self)
199  self.m_icons['laelaps_logo'] = \
200  self.m_imageLoader.loadImage("icon_laelaps_logo.png");
201  if self.m_icons['laelaps_logo']:
202  w['image'] = self.m_icons['laelaps_logo']
203  w['anchor'] = E
204  else:
205  w['text'] = 'laelaps'
206  w['anchor'] = E
207  w['width'] = 5
208  w.grid(row=0, column=2, sticky=E)
209 
210  #
211  ## \brief Create gui left hand side buttons.
212  #
213  def createLeftButtons(self):
214  wframe = Frame(self)
215  wframe['borderwidth'] = 2
216  wframe['relief'] = 'ridge'
217  wframe.grid(row=1, column=0, padx=1, pady=3, sticky=N+W+E)
218 
219  row = 0
220 
221  # start
222  w = self.createButton(wframe, "Start", "icon_play.png",
223  self.cbStartServices)
224  w['state'] = 'disabled'
225  w.grid(row=row, column=0, sticky=N+E+W)
226 
227  row += 1
228 
229  # stop
230  w = self.createButton(wframe, "Stop", "icon_stop.png",
231  self.cbStopServices)
232  w['state'] = 'disabled'
233  w.grid(row=row, column=0, sticky=N+E+W)
234 
235  row += 1
236 
237  # restart
238  w = self.createButton(wframe, "Restart", "icon_resume.png",
239  self.cbRestartServices)
240  w['state'] = 'disabled'
241  w.grid(row=row, column=0, sticky=N+E+W)
242 
243  row += 1
244 
245  #
246  ## \brief Create gui center panel.
247  #
248  def createCenterPanel(self):
249  wframe = Frame(self)
250  wframe['borderwidth'] = 2
251  wframe['relief'] = 'ridge'
252  wframe.grid(row=1, column=1, padx=1, pady=3, sticky=N+W+E)
253 
254  row = 0
255  col = 0
256 
257  for text in ['Sel', 'Status', 'Service', 'Description']:
258 
259  w = Label(wframe, text=text, foreground=fgColors['focus'])
260  w['font'] = ('Helvetica', 10, "bold")
261  w.grid(row=row, column=col, padx=3, pady=3, stick=W)
262 
263  col += 1
264 
265  row += 1
266  colspan = len(self.m_svcKeys)
267  bg0 = '#ffffcc'
268  bg1 = wframe['bg']
269  bg = bg0
270 
271  self.m_service = { }
272 
273  for key in self.m_svcKeys:
274  col = 0
275  service = { }
276 
277  service['sel'] = IntVar(0)
278 
279  w = Checkbutton(wframe, bg=bg, variable=service['sel'],
280  command=self.cbSelect)
281  w.grid(row=row, column=col, padx=1, pady=3, sticky=W)
282 
283  col += 1
284 
285  service['status'] = StringVar()
286  service['status'].set("unknown")
287 
288  w = Label(wframe, bg=bg, textvariable=service['status'])
289  w['fg'] = statusText[service['status'].get()]
290  w['width'] = 8
291  w.grid(row=row, column=col, padx=1, pady=3, stick=W)
292  service['wstatus'] = w
293 
294  col += 1
295 
296  w = Label(wframe, bg=bg, anchor=W, justify=LEFT, text=key)
297  w.grid(row=row, column=col, padx=1, pady=3, stick=W+E)
298 
299  col += 1
300 
301  w = Label(wframe, bg=bg, anchor=W, justify=LEFT, text=self.m_svcDesc[key])
302  w.grid(row=row, column=col, padx=1, pady=3, stick=W+E)
303 
304  col += 1
305 
306  self.m_service[key] = service
307 
308  if bg == bg0:
309  bg = bg1
310  else:
311  bg = bg0
312 
313  row += 1
314 
315  #
316  ## \brief Create gui right hand side buttons.
317  #
319  wframe = Frame(self)
320  wframe['borderwidth'] = 2
321  wframe['relief'] = 'ridge'
322  wframe.grid(row=1, column=2, padx=1, pady=3, sticky=N+W+E)
323 
324  row = 0
325 
326  # refresh
327  w = self.createButton(wframe, "Refresh", "icon_refresh.png",
328  self.cbRefreshStatus)
329  w.grid(row=row, column=0, sticky=N+E+W)
330 
331  row += 1
332  # save
333  w = self.createButton(wframe, "Save", "icon_floppy.png",
334  self.cbSave)
335  w['state'] = 'disabled'
336  w.grid(row=row, column=0, sticky=N+E+W)
337 
338  row += 1
339 
340  # save
341  w = self.createButton(wframe, "Quit", "icon_exit.png",
342  self.destroy)
343  w.grid(row=row, column=0, sticky=N+E+W)
344 
345  #
346  ## \brief Create gui multi-line status bar at bottom of gui window.
347  #
348  def createStatusBar(self):
349  wframe = Frame(self)
350  wframe['borderwidth'] = 2
351  wframe['relief'] = 'ridge'
352  wframe.grid(row=2, column=0, columnspan=3, padx=1, pady=3, sticky=N+E+W+S)
353 
354  w = Scrollbar(wframe)
355  w.grid(row=0, column=1, sticky=N+S)
356  self.m_wScrollBar = w;
357 
358  self.m_wStatusBar = Text(wframe)
359  self.m_wStatusBar['width'] = 105
360  self.m_wStatusBar['height'] = 10
361  self.m_wStatusBar['wrap'] = WORD
362  self.m_wStatusBar['relief'] = 'flat'
363  self.m_wStatusBar['fg'] = fgColors['normal']
364  self.m_wStatusBar['state'] = 'disabled'
365  self.m_wStatusBar.grid(row=0, column=0, padx=3, pady=3, sticky=N+E+W+S)
366 
367  # attach
368  self.m_wStatusBar['yscrollcommand'] = self.m_wScrollBar.set
369  self.m_wScrollBar['command'] = self.m_wStatusBar.yview
370 
371  #
372  ## \brief Create button.
373  ##
374  ## \param parent Parent widget.
375  ## \param text Button text.
376  ## \param imagefile Image file name. None for no image.
377  ## \param command Callback for button push.
378  ## \param fg Foreground text color.
379  ##
380  ## \return Button widget.
381  #
382  def createButton(self, parent, text, imagefile, command, fg='black'):
383  key = str.lower(text.replace("\n", "_"))
384  self.m_icons[key] = self.m_imageLoader.loadImage(imagefile)
385  w = Button(parent)
386  w['text'] = text
387  if self.m_icons[key]:
388  w['image'] = self.m_icons[key]
389  w['compound'] = LEFT
390  w['padx'] = 0
391  w['pady'] = 0
392  w['anchor'] = W
393  w['width'] = 105
394  else:
395  w['anchor'] = CENTER
396  w['width'] = 10
397  w['fg'] = fg
398  w['command'] = command
399  self.m_wBttn[key] = w
400  return self.m_wBttn[key]
401 
402  #
403  ## \brief Clear all select checkboxes.
404  #
405  def clearSelect(self):
406  for key in self.m_svcKeys:
407  self.m_service[key]['sel'].set(0)
408  self.cbSelect()
409 
410  #
411  ## \brief Checkbox change state callback.
412  #
413  def cbSelect(self):
414  bttns = ['start', 'stop', 'restart']
415  nselected = 0
416  for key in self.m_svcKeys:
417  if self.m_service[key]['sel'].get():
418  nselected += 1
419  if nselected > 0:
420  state = 'normal'
421  else:
422  state = 'disabled'
423  for key in bttns:
424  self.m_wBttn[key]['state'] = state
425 
426  #
427  ## \brief Start selected services callback.
428  #
429  def cbStartServices(self):
430  self.showSbInfo("Starting selected services...\n")
431  for key in self.m_svcKeys:
432  if self.m_service[key]['sel'].get():
433  text = " Starting {0} service".format(key)
434  self.showSbInfo(text)
435  self.update_idletasks()
436  pf = self.execStart(key)
437  self.showSbResult(text, pf)
438  #self.clearSelect()
439  self.refresh()
440 
441  #
442  ## \brief Stop selected services callback.
443  #
444  def cbStopServices(self):
445  self.showSbInfo("Stopping selected services...\n")
446  for key in self.m_svcKeys:
447  if self.m_service[key]['sel'].get():
448  text = " Stopping {0} service".format(key)
449  self.showSbInfo(text)
450  self.update_idletasks()
451  pf = self.execStop(key)
452  self.showSbResult(text, pf)
453  #self.clearSelect()
454  self.refresh()
455 
456  #
457  ## \brief Restart selected services callback.
458  #
459  def cbRestartServices(self):
460  self.showSbInfo("Restarting selected services...\n")
461  for key in self.m_svcKeys:
462  if self.m_service[key]['sel'].get():
463  text = " Restarting {0} service".format(key)
464  self.showSbInfo(text)
465  self.update_idletasks()
466  pf = self.execRestart(key)
467  self.showSbResult(text, pf)
468  #self.clearSelect()
469  self.refresh()
470 
471  #
472  ## \brief Refresh services status callback.
473  #
474  def cbRefreshStatus(self):
475  self.showSbInfo("Refreshing status of all services...\n")
476  for key in self.m_svcKeys:
477  text = " Checking {0} status".format(key)
478  self.showSbInfo(text)
479  self.update_idletasks()
480  status,output = self.execStatus(key)
481  self.showSbStatus(text, status)
482  self.setStatus(key, status)
483 
484  #
485  ## \brief Save new settings callback.
486  #
487  def cbSave(self):
488  self.showSbInfo("Saving new settings...\n")
489 
490  #
491  ## \brief Destroy window callback.
492  #
493  def destroy(self):
494  self.quit()
495 
496  #
497  ## \brief Show information text on status bar.
498  ##
499  ## \param text Info text string.
500  #
501  def showSbInfo(self, text):
502  self.m_wStatusBar["state"] = "normal"
503  idx0 = self.m_wStatusBar.index(INSERT)
504  self.m_wStatusBar.insert(END, text)
505  idx1 = self.m_wStatusBar.index(INSERT)
506  self.m_wStatusBar.tag_add("norm", idx0, idx1)
507  self.m_wStatusBar.tag_config("norm", foreground=fgColors['normal'])
508  self.m_wStatusBar.see(END)
509  self.m_wStatusBar["state"] = "disabled"
510 
511  #
512  ## \brief Show error text on status bar.
513  ##
514  ## \param text Error text string.
515  #
516  def showSbError(self, text):
517  self.m_wStatusBar["state"] = "normal"
518  idx0 = self.m_wStatusBar.index(INSERT)
519  self.m_wStatusBar.insert(END, text)
520  idx1 = self.m_wStatusBar.index(INSERT)
521  self.m_wStatusBar.tag_add("err", idx0, idx1)
522  self.m_wStatusBar.tag_config("err", foreground=fgColors['error'])
523  self.m_wStatusBar.see(END)
524  self.m_wStatusBar["state"] = "disabled"
525 
526  #
527  ## \brief Show ok text on status bar.
528  ##
529  ## \param text Ok text string.
530  #
531  def showSbOk(self, text):
532  self.m_wStatusBar["state"] = "normal"
533  idx0 = self.m_wStatusBar.index(INSERT)
534  self.m_wStatusBar.insert(END, text)
535  idx1 = self.m_wStatusBar.index(INSERT)
536  self.m_wStatusBar.tag_add("ok", idx0, idx1)
537  self.m_wStatusBar.tag_config("ok", foreground=fgColors['ok'])
538  self.m_wStatusBar.see(END)
539  self.m_wStatusBar["state"] = "disabled"
540 
541  #
542  ## \brief Show operation result on status bar.
543  ##
544  ## \param text Prefix text already displayed on current status bar line.
545  ## \param success Operation was [not] a success.
546  #
547  def showSbResult(self, text, success):
548  n = self.m_wStatusBar['width'] - len(text)
549  if success:
550  rc = '[ok]'
551  self.showSbOk("%*s\n" % (n, rc))
552  else:
553  rc = '[failed]'
554  self.showSbError("%*s\n" % (n, rc))
555 
556  #
557  ## \brief Show service status result on status bar.
558  ##
559  ## \param text Prefix text already displayed on current status bar line.
560  ## \param status Service status. One of: 'running' 'stopped' 'unknown'
561  #
562  def showSbStatus(self, text, status):
563  n = self.m_wStatusBar['width'] - len(text)
564  if status == 'running':
565  rc = '[running]'
566  self.showSbOk("%*s\n" % (n, rc))
567  elif status == 'stopped':
568  rc = '[stopped]'
569  self.showSbError("%*s\n" % (n, rc))
570  else:
571  rc = '[unknown]'
572  self.showSbInfo("%*s\n" % (n, rc))
573 
574  #
575  ## \brief Set service status field.
576  ##
577  ## \param servcie Service (key).
578  ## \param status Service status. One of: 'running' 'stopped' 'unknown'
579  #
580  def setStatus(self, service, status):
581  self.m_service[service]['status'].set(status)
582  self.m_service[service]['wstatus']['fg'] = statusText[status]
583 
584  def autoRefresh(self):
585  self.refresh()
586  self.after(2000, self.autoRefresh)
587 
588  #
589  ## \brief Refresh status of all services.
590  #
591  def refresh(self):
592  for key in self.m_svcKeys:
593  status,output = self.execStatus(key)
594  self.setStatus(key, status)
595 
596  #
597  ## \brief Execute 'service <service> start' subprocess.
598  ##
599  ## \param servcie Service (key).
600  ##
601  ## \return Returns True on success, False on failure.
602  #
603  def execStart(self, service):
604  s = ''
605  hasLock = self.m_lock.acquire()
606  try:
607  s = subprocess.check_output(["service", service, "start"],
608  stderr=subprocess.STDOUT)
609  except subprocess.CalledProcessError, inst:
610  self.m_lock.release()
611  return False
612  self.m_lock.release()
613  if reFail.search(s):
614  return False
615  else:
616  return True
617 
618  #
619  ## \brief Execute 'service <service> stop' subprocess.
620  ##
621  ## \param servcie Service (key).
622  ##
623  ## \return Returns True on success, False on failure.
624  #
625  def execStop(self, service):
626  s = ''
627  hasLock = self.m_lock.acquire()
628  try:
629  s = subprocess.check_output(["service", service, "stop"],
630  stderr=subprocess.STDOUT)
631  except subprocess.CalledProcessError, inst:
632  self.m_lock.release()
633  return False
634  self.m_lock.release()
635  if reFail.search(s):
636  return False
637  else:
638  return True
639 
640  #
641  ## \brief Execute 'service <service> restart' subprocess.
642  ##
643  ## \param servcie Service (key).
644  ##
645  ## \return Returns True on success, False on failure.
646  #
647  def execRestart(self, service):
648  s = ''
649  hasLock = self.m_lock.acquire()
650  try:
651  s = subprocess.check_output(["service", service, "restart"],
652  stderr=subprocess.STDOUT)
653  except subprocess.CalledProcessError, inst:
654  self.m_lock.release()
655  return False
656  self.m_lock.release()
657  if reDoneDone.search(s):
658  return True
659  elif reFailDone.search(s):
660  return True
661  else:
662  return False
663 
664  #
665  ## \brief Execute 'service <service> status' subprocess.
666  ##
667  ## \param servcie Service (key).
668  ##
669  ## \return Service status. One of: 'running' 'stopped' 'unknown'
670  #
671  def execStatus(self, service):
672  s = ''
673  hasLock = self.m_lock.acquire()
674  try:
675  s = subprocess.check_output(["service", service, "status"],
676  stderr=subprocess.STDOUT)
677  except subprocess.CalledProcessError, inst:
678  s = inst.output
679  self.m_lock.release()
680  if reRunning.search(s):
681  return ('running', s)
682  elif reNotRunning.search(s):
683  return ('stopped', s)
684  else:
685  return ('unknown', s)
686 
687 
688 # ------------------------------------------------------------------------------
689 # Exception Class usage
690 # ------------------------------------------------------------------------------
691 
692 ##
693 ## \brief Unit test command-line exception class.
694 ##
695 ## Raise usage excpetion.
696 ##
697 class usage(Exception):
698 
699  ##
700  ## \brief Constructor.
701  ##
702  ## \param msg Error message string.
703  ##
704  def __init__(self, msg):
705  ## error message attribute
706  self.msg = msg
707 
708 
709 # ------------------------------------------------------------------------------
710 # Class application
711 # ------------------------------------------------------------------------------
712 
713 ##
714 ## \brief Laelaps control panel.
715 ##
716 class application():
717 
718  #
719  ## \brief Constructor.
720  #
721  def __init__(self):
722  self._Argv0 = __file__
723  self.m_win = None
724 
725  #
726  ## \brief Print usage error.
727  ##
728  ## \param emsg Error message string.
729  #
730  def printUsageErr(self, emsg):
731  if emsg:
732  print "%s: %s" % (self._Argv0, emsg)
733  else:
734  print "%s: error" % (self._Argv0)
735  print "Try '%s --help' for more information." % (self._Argv0)
736 
737  ## \brief Print Command-Line Usage Message.
738  def printUsage(self):
739  print \
740 """
741 usage: %s [OPTIONS]
742  %s --help
743 
744 Options and arguments:
745 
746 -h, --help : Display this help and exit.
747 """ % (self._Argv0, self._Argv0)
748 
749  #
750  ## \brief Get command-line options
751  ##
752  ## \param argv Argument list. If not None, then overrides
753  ## command-line arguments.
754  ## \param [out] kwargs Keyword argument list.
755  ##
756  ## \return Parsed keyword arguments.
757  #
758  def getOptions(self, argv=None, **kwargs):
759  if argv is None:
760  argv = sys.argv
761 
762  self._Argv0 = kwargs.get('argv0', __file__)
763 
764  # defaults
765  kwargs['debug'] = False # Set to False when finished debugging.
766 
767  # parse command-line options
768  try:
769  opts, args = getopt.getopt(argv[1:], "?h",
770  ['help', ''])
771  except getopt.error, msg:
772  raise usage(msg)
773  for opt, optarg in opts:
774  if opt in ('-h', '--help', '-?'):
775  self.printUsage()
776  sys.exit(0)
777 
778  #if len(args) < 1:
779  # self.printUsageErr("No input xml file specified")
780  # sys.exit(2)
781  #else:
782  # kwargs['filename'] = args[0]
783 
784  return kwargs
785 
786  #
787  ## \brief Run application.
788  ##
789  ## \param argv Optional argument list to override command-line arguments.
790  ## \param kwargs Optional keyword argument list.
791  ##
792  ## \return Exit code.
793  #
794  def run(self, argv=None, **kwargs):
795 
796  # parse command-line options and arguments
797  try:
798  kwargs = self.getOptions(argv, **kwargs)
799  except usage, e:
800  print e.msg
801  return 2
802 
803  # create root
804  root = Tk()
805 
806  # create application window
807  self.m_win = window(master=root, **kwargs)
808 
809  root.protocol('WM_DELETE_WINDOW', root.destroy)
810 
811  # loop
812  self.m_win.mainloop()
813 
814  return 0
815 
816 
817 # ------------------------------------------------------------------------------
818 # main
819 # ------------------------------------------------------------------------------
820 if __name__ == '__main__':
821  app = application();
822  sys.exit( app.run() );
def __init__(self, msg)
Constructor.
def printUsageErr(self, emsg)
Print usage error.
def cbSelect(self)
Checkbox change state callback.
def getOptions(self, argv=None, kwargs)
Get command-line options.
def createHeading(self)
Create top gui heading.
def cbRestartServices(self)
Restart selected services callback.
def showSbOk(self, text)
Show ok text on status bar.
def __init__(self)
Constructor.
Laelaps control panel.
def refresh(self)
Refresh status of all services.
def clearSelect(self)
Clear all select checkboxes.
def cbStopServices(self)
Stop selected services callback.
def printUsage(self)
Print Command-Line Usage Message.
def createWidgets(self)
Create gui widgets with supporting data and show.
def cbSave(self)
Save new settings callback.
def __init__(self, master=None, cnf={}, kw)
Constructor.
def execRestart(self, service)
Execute &#39;service <service> restart&#39; subprocess.
def cbRefreshStatus(self)
Refresh services status callback.
def createStatusBar(self)
Create gui multi-line status bar at bottom of gui window.
def destroy(self)
Destroy window callback.
Window class supporting application.
def showSbInfo(self, text)
Show information text on status bar.
def setStatus(self, service, status)
Set service status field.
def showSbResult(self, text, success)
Show operation result on status bar.
def showSbStatus(self, text, status)
Show service status result on status bar.
def showSbError(self, text)
Show error text on status bar.
def createCenterPanel(self)
Create gui center panel.
def createButton(self, parent, text, imagefile, command, fg='black')
Create button.
def cbStartServices(self)
Start selected services callback.
def execStop(self, service)
Execute &#39;service <service> stop&#39; subprocess.
def createRightButtons(self)
Create gui right hand side buttons.
def execStart(self, service)
Execute &#39;service <service> start&#39; subprocess.
def initData(self, kw)
Initialize class state data.
def execStatus(self, service)
Execute &#39;service <service> status&#39; subprocess.
Unit test command-line exception class.
def createLeftButtons(self)
Create gui left hand side buttons.
def run(self, argv=None, kwargs)
Run application.
msg
error message attribute