9 from ConfigModel import *
11 # ================================================================
12 # Todos and known bugs
13 # ================================================================
15 # - compute PreviewPane constants PP_DX and PP_DY
16 # - test commandline interface
18 # ================================================================
20 # ================================================================
23 GUI_DESCRIPTION = 'tvout.glade'
24 PROFILES = '/etc/pandora/conf/tvout-profiles.conf'
26 # Shell command to change settings:
27 SET_CONFIG_CMD = '"sudo /usr/pandora/scripts/op_tvout.sh %s"'
28 CONFIG_PARAMS = '-t %(encoding)s -c %(connection)s -l %(layer)s -%(type)ss %(width)s,%(height)s -%(type)sp %(x)s,%(y)s'
30 # Maximum vertical resolutions
34 # Profile header string, should be a single line only
35 # This does NOT conflict with profile names.
36 PROF_HEADER = 'Last written configuration:'
38 # Paths for reading different configuration options
40 size='/sys/devices/platform/omapdss/overlay2/output_size',
41 position='/sys/devices/platform/omapdss/overlay2/position',
42 enabled='/sys/devices/platform/omapdss/display1/enabled',
43 connection='/sys/devices/platform/omapdss/display1/venc_type', # if it exists
44 fb0='/sys/class/graphics/fb0/overlays',
45 fb1='/sys/class/graphics/fb1/overlays',
48 # format for saving/loading
50 "enabled", "encoding", "connection", "layer", "x", "y", "width", "height")
52 # Default configurations
53 DC_DISABLED = "Disabled"
54 DC_DISABLED_DICT = dict(
55 enabled="False", encoding="pal", connection="composite",
56 layer="0", width="658", height="520", x="35", y="35")
58 DC_DISABLED: DC_DISABLED_DICT,
62 W_RADIO_BUTTONS = ('pal', 'ntsc', 'composite', 'svideo', 'layer0', 'layer1')
63 W_ADJUSTMENTS = ('x', 'y', 'width', 'height')
64 W_WIDGETS = ('enabled', 'pal', 'ntsc', 'composite', 'svideo',
65 'layer0', 'layer1', 'width', 'height', 'x', 'y')
68 PP_WIDGET = 'previewAlignment'
69 PP_HANDLES = ('xPaned', 'yPaned', 'widthPaned', 'heightPaned')
70 PP_DX = 26.0 # Todo: compute these
71 PP_DY = 14.0 # (they might be theme dependent)
73 ENABLED_USERDATA = 'enabled'
74 RADIO_USERDATA = dict(
75 pal='encoding', ntsc='encoding',
76 composite='connection', svideo='connection',
77 layer0='layer', layer1='layer')
78 ADJUSTMENT_USERDATA = ('width', 'height', 'x', 'y')
80 # Documentation for commandline options
81 HELP_ENABLED = "Enable TV-out. Valid values: True, False"
82 HELP_ENCODING = "Set encoding type (pal or ntsc)."
83 HELP_CONNECTION = "Set connection type (composite or svideo)"
84 HELP_LAYER = "Sets video layer to either main (0) or HW scaler / overlay (1)"
85 HELP_WIDTH = "Screen width (max 720)"
86 HELP_HEIGHT = "Screen height (max %s for pal and %s for ntsc)" % (Y_RES_PAL, Y_RES_NTSC)
87 HELP_X = "X coordinate of top left corner of the display area. Max 720 - width."
88 HELP_Y = "Y coordinate of top left corner of the display area. Y + HEIGHT may not exceed the max values specified in the height option."
89 HELP_SAVE = "Store current configuration as specified profile."
90 HELP_APPLY = "Activate the currently specified configuration."
91 HELP_LOAD = "Load and apply the specified configuration profile."
92 HELP_DEL = "Delete specified configuration profile."
94 # Commandline input type checking
96 enabled='(True|False)', layer='(0|1)',
97 encoding='(pal|ntsc)', connection='(composite|svideo)',
98 width='(\d+)', height='(\d+)', x='(\d+)', y='(\d+)',
100 RE_FORMAT = ' '.join(TYPECHECK[k] for k in FILE_ORDER)
101 BOUNDS = dict(width=(0,720), height=(0, Y_RES_PAL),
102 x=(0,720), y=(0,Y_RES_PAL))
104 # ================================================================
105 # Misc. auxiliary functions
106 # ================================================================
109 """Verifies all values in a profile string (see TVoutModel).
111 This is used to validate inputs passed through the commandline interface.
113 @value: a profile string.
115 mo = re.match(RE_FORMAT, value)
118 else: # verify bounds
119 values = dict(zip(FILE_ORDER, mo.groups()))
123 w = int(values['width'])
124 h = int(values['height'])
125 Y = Y_RES_PAL if values['encoding'] == 'pal' else Y_RES_NTSC
128 print "Invalid position and/or size specified, x (%s) + width (%s) may not exceed 720." % (x, w)
131 print "Invalid position and/or size specified, y (%s) + height (%s) may not exceed %s." % (y, h, Y)
134 if not all(BOUNDS[k][0] <= int(values[k]) <= BOUNDS[k][1] for k in BOUNDS):
135 print "Invalid position and/or size bounds specified"
140 # ================================================================
141 # Main model & gui classes
142 # ================================================================
144 class TVoutModel(ConfigModel):
145 """Model for TV-out configuration.
147 Since not all TV-out settings can be easily read back from the system,
148 the last-saved profile is stored to fill in the gaps (mainly encoding).
150 This currently stores the following key/value pairs (all strings):
151 - "enabled": "True" | "False" # TV-out turned on/off
152 - "encoding": "pal" | "ntsc" # which colour encoding is utilized
153 - "connection": "composite" | "svideo"
154 - "layer": "0" | "1" # main video layer or hw scaler/overlay
155 - "x": "\d+" # left side of area displayed area
156 - "y": "\d+" # top side of the displayed area
157 - "width": "\d+" # right side of the displayed area,
159 - "height": "\d+" # bottom side of the displayed area,
162 def __init__(self, *views):
163 self.last_written = None
164 ConfigModel.__init__(self, PROFILES, *views, **DC_DEFAULTS)
166 def fetch_profiles(self):
167 """Loads the profiles.
169 The header line is ignored and is included for user documentation only.
170 The second line is the last written profile.
171 The remainder of the file consists of a series of lines which
172 alternating contain the profile name and a profile.
174 Profiles are stored as space separated values, in the following order:
175 <enabled> <encoding> <connection> <layer> <x> <y> <width> <height>
177 with open(self.profiles_file, 'r') as f:
178 f.readline() # header
179 self.last_written = self.string_to_profile(f.readline().strip()) # last_saved profile
186 self.profiles[name] = self.string_to_profile(line.rstrip())
189 def store_profiles(self):
190 """Write profiles to file.
192 Refer to the doc-string of fetch_profiles for the file format.
194 with open(self.profiles_file, 'w') as f:
195 f.write('%s\n%s\n' % (PROF_HEADER, self.profile_to_string(self.last_written)))
196 for name in sorted(self.profiles.keys()):
197 f.write('%s\n%s\n' % (name, self.profile_to_string(self.profiles[name])))
199 def read_settings(self):
200 """Reads current TV-out configuration from the system.
202 The way the encoding is stored is tricky to convert back to pal/ntsc
203 and might change in later hotfixes / firmware releases. Therefore this
204 value is read from the last written profile instead.
206 Layer is determined by interpreting the framebuffer values.
207 If an unknown configuration is encountered, which is quite possible,
208 then the last written configuration is read instead.
210 # Uncomment to test on desktop linux:
211 # self.load_profile(self.last_written)
215 for key, path in SETTINGS.iteritems():
217 with open(path, 'r') as f:
218 settings[key] = f.readline().strip()
220 settings[key] = self.last_written[key] if key in self.last_written else None
222 # This one is quite tricky to read directly from the system, yet unlikely to change often.
223 settings['encoding'] = self.last_written['encoding']
225 # These ones are stored as a single value
226 settings['width'], settings['height'] = settings.pop('size').split(',')
227 settings['x'], settings['y'] = settings.pop('position').split(',')
229 # To determine the layer we interpret the framebuffer values
230 fb0 = settings.pop('fb0')
231 fb1 = settings.pop('fb1')
232 settings['layer'] = ('1' if fb0 == '0' and fb1 == '1,2' else
233 ('0' if fb0 == '0,2' and fb1 == '1' else
234 self.last_written['layer']))
236 self.load_profile(settings)
238 def write_settings(self):
239 """Write configuration to the system.
241 This relies on op_tvout.sh to write the configuration, such that
242 the details can be easily changed in later firmware versions.
244 Documentation for op_tvout.sh:
245 op_tvout.sh [-d] [-t pal|ntsc] [-c composite|svideo] [-l 0|1]
246 [-{p|n}s w,h] [-{p|n}p x,y]
248 - op_tvout.sh -d # just disables tv-out
249 - t ntsc -c composite # enable NTSC/composite mode
251 layer, 0 is the main layer and 1 is hardware scaler/overlay
252 (video layer, used by some emus too), only one can be used at a time)
254 display position and size in 720xSomething space,
255 this has to be tuned for every TV by user for best results,
256 can't go out of range of 720xSomething (I think).
257 - np 0,0 -ns 640 480 # same for NTSC
259 The script expects you to supply everything at once.
261 self.last_written.update(self.settings)
262 if self.settings['enabled'] == 'False':
263 #print SET_CONFIG_CMD % '-d'
264 os.system(SET_CONFIG_CMD % '-d')
266 pp = self.settings.copy()
267 pp['type'] = self.settings['encoding'][0]
268 #print SET_CONFIG_CMD % (CONFIG_PARAMS % pp)
269 os.system(SET_CONFIG_CMD % (CONFIG_PARAMS % pp))
271 def profile_to_string(self, dct):
272 """Converts settings dictionary to a string.
274 Profiles are stored as space separated values, in the following order:
275 <enabled> <encoding> <connection> <layer> <x> <y> <width> <height>
277 return ' '.join(dct[k] for k in FILE_ORDER)
279 def string_to_profile(self, s):
280 """Converts a profile string back to a settings dictionary.
282 Profiles are stored as space separated values, in the following order:
283 <enabled> <encoding> <connection> <layer> <x> <y> <width> <height>
285 return dict(zip(FILE_ORDER, s.split(' ')))
287 class TVoutConfig(object):
288 """GUI application for modifying the pandora TV-out configuration
290 Design (tvout.glade):
291 +------------+---------------+--------------------+
292 | | Position----- | Size-------------- |
293 | Logo | X: <spin btn> | Width: <spin btn> |
294 | | Y: <spin btn> | Height: <spin btn> |
295 +------------+---------------+--------------------+
296 |Encoding | Overscan |
297 |. pal | +--------------------------------+ |
298 |. ntsc | | <undisplayed area> | |
299 +------------+ | +-----------------------+ | |
300 |Connection | | | | | |
301 |. composite | | | <displayed area> | | |
302 |. S-video | | | | | |
303 +------------+ | | | | |
304 |Layer | | +-----------------------+ | |
306 |. overlay | +--------------------------------+ |
307 +------------+------------------------------------+
308 |[v] enabled | [ Read settings ] [Write settings] |
309 |[delete p.] | {.............|v} [ Save profile ] |
310 +------------+------------------------------------+
312 Important widget names:
313 - X,Y,Width and Height spinbuttons: x,y,width and height
314 - Radio buttons (for Encoding, Connection, Layer):
315 pal, ntsc, composite, svideo, layer0, layer1
317 These names correspond to their values as they are written,
318 except for layer0 and layer1 which are stored as strings(!) 0 and 1.
319 - The checkbox is called: enabled
320 - The buttons are called:
321 readSettings, writeSettings, deleteProfile, saveProfile
322 - The combobox is called: ProfileComboEntry
323 - The displayed area is created by a set of GtkHPaned and GtkVPaned
324 widgets called: xPaned, yPaned, widthPaned, heightPaned
325 They are contained in a frame called previewAlignment which can be
326 used to determine the total allocated size.
330 """Initialize the GUI application.
332 Loads glade file, creates widget references, connects unbound handlers
333 (mainly those handlers which require userdata) and finally initializes
336 builder = gtk.Builder()
337 builder.add_from_file(
338 os.path.join(os.path.dirname(__file__), GUI_DESCRIPTION))
339 builder.connect_signals(self)
342 for widgetname in W_WIDGETS:
343 self.widgets[widgetname] = builder.get_object(widgetname)
345 # Bind radiobutton signals
346 for widget, setting in RADIO_USERDATA.iteritems():
347 self.widgets[widget].connect('clicked', self.on_clicked,
348 setting, widget.lstrip('layer'))
350 # Bind adjustment signals
351 for widget in ADJUSTMENT_USERDATA:
352 self.widgets[widget].get_adjustment().connect(
353 'value_changed', self.on_value_changed, widget)
356 # The allocated size of this widget is utilized to convert the
357 # slider positions to absolute x,y,width and height values.
358 self.previewPane = builder.get_object(PP_WIDGET)
360 self.panedwidgets = {}
361 # Bind previewpane handle signals
362 for key in PP_HANDLES:
363 w = builder.get_object(key)
364 self.panedwidgets[key] = w
365 w.connect("notify::position", self.on_paned_position_change, key)
367 self.model = TVoutModel(self)
369 # We have two sets of widgets manipulating position and size
370 # (spinbuttons and the handles in the previewpane).
371 # To prevent an update race between these sets, the following flags
372 # are set to block the handlers of the widgets which we are NOT
373 # manipulating. The spinbuttons are more precise and therefore leading.
374 self.suppress_handles = False
375 self.suppress_spinbuttons = False
377 self.statusbar = builder.get_object('statusbar')
378 self.contextid = self.statusbar.get_context_id('')
380 self.profiles = gtk.ListStore(str)
381 for profile in DC_DEFAULTS:
382 self.profiles.append([profile])
383 for name in self.model.profiles:
384 self.profiles.append([name])
386 self.comboentry = builder.get_object('ProfileComboEntry')
387 self.comboentry.set_model(self.profiles)
388 self.comboentry.set_text_column(0)
389 self.comboentry.connect('changed', self.on_combo_changed)
391 self.entry = self.comboentry.get_child()
392 self.entry.connect('activate', self.on_LoadProfile_clicked)
394 # we need a reference to change their sensitive
395 # setting according to the profile
396 self.delete = builder.get_object('deleteProfile')
397 self.save = builder.get_object('saveProfile')
399 # this also changes sensitive of self.save & self.load
400 self.comboentry.set_active(0)
402 self.window = builder.get_object("window")
403 self.window.show_all()
405 accelgrp = gtk.AccelGroup()
406 key, mod = gtk.accelerator_parse('<Control>Q')
407 accelgrp.connect_group(key, mod, 0, self.on_window_destroy)
408 self.window.add_accel_group(accelgrp)
410 # read current config
411 self.model.read_settings()
413 def Notify(self, message):
414 """Displays a message on the statusbar"""
415 self.statusbar.pop(self.contextid)
416 self.statusbar.push(self.contextid, message)
418 def get_y_resolution(self):
419 """Returns the (mode-dependent) maximum Y resolution"""
420 return Y_RES_PAL if self.widgets['pal'].get_active() else Y_RES_NTSC
422 # --------------------------------
423 # View updates when model changes
424 # --------------------------------
426 def _update_profile_list(self, new, profiles):
427 """Show all default profiles and then the custom profiles.
429 @new: name of newly selected profile
430 @profiles: dictionary of all profiles.
432 self.profiles.clear()
434 for i, key in enumerate(DC_DEFAULTS):
435 self.profiles.append([key])
438 offset = len(DC_DEFAULTS)
439 for i, key in enumerate(sorted(profiles.keys())):
440 self.profiles.append([key])
442 activate = i + offset
443 self.comboentry.set_active(activate)
445 def _update_widgets(self, settings):
446 """Update all widgets to reflect the configuration in settings"""
447 enabled = eval(settings['enabled'])
448 self.widgets['enabled'].set_active(enabled)
450 self.widgets[settings['encoding']].set_active(True)
451 self.widgets[settings['connection']].set_active(True)
452 self.widgets['layer'+settings['layer']].set_active(True)
454 for key in W_ADJUSTMENTS:
455 w = self.widgets[key]
456 w.get_adjustment().set_value(eval(settings[key]))
457 w.set_sensitive(enabled)
459 for key in W_RADIO_BUTTONS:
460 self.widgets[key].set_sensitive(enabled)
462 def update_view(self, reason, *data):
463 """Dispatcher for events generated by the model."""
464 if reason == MODEL_PROFILE_CHANGE:
465 self._update_profile_list(*data)
466 elif reason == MODEL_VALUE_CHANGE:
467 self._update_widgets(*data)
469 def update_adjustments(self):
470 """Update all Gtk adjustments to recompute their maximum.
472 The value of x+width should not exceed 720.
473 The value of y+height should not exceed the maximum Y resolution.
475 x = self.widgets['x'].get_adjustment()
476 y = self.widgets['y'].get_adjustment()
478 xval = int(self.model.settings['x'])
479 yval = int(self.model.settings['y'])
480 wval = int(self.model.settings['width'])
481 hval = int(self.model.settings['height'])
483 # update vertical resolution thresholds
484 y.set_upper(self.get_y_resolution())
485 y.set_value(min(yval, y.get_upper()))
487 # update width thresholds
488 width = self.widgets['width'].get_adjustment()
489 width.set_upper(x.get_upper()-xval)
490 width.set_value(min(wval, width.get_upper()))
492 # update height thresholds
493 height = self.widgets['height'].get_adjustment()
494 height.set_upper(y.get_upper()-yval)
495 height.set_value(min(hval, height.get_upper()))
497 # --------------------------------
498 # Handling of widget changes
499 # --------------------------------
501 def on_toggled(self, widget, *data):
502 """Handles changes of the enabled radio button."""
503 enabled = widget.get_active()
504 self.model.settings[ENABLED_USERDATA] = str(enabled)
505 for name, widget in self.widgets.iteritems():
506 if name != 'enabled':
507 widget.set_sensitive(enabled)
509 def on_clicked(self, widget, key=None, value=None):
510 """Handles changes of the radio buttons.
512 @key: the setting being changed, i.e.
513 - "encoding" for pal/ntsc,
514 - "connection" for composite/svideo
515 - "layer" for layer0/layer1.
516 These correspond to the keys in the profile settings dictionaries.
517 @value: the value to be written to the dictionary.
519 When the encoding changes, so does the maximum Y resolution.
520 Hence it requires the adjustments to be updated.
523 self.model.settings[key] = value
524 if key == 'encoding':
525 self.update_adjustments()
527 def on_value_changed(self, widget, data):
528 """Handles spinbutton events.
530 If the user manipulates the spinbutton, the model is updated.
531 Note that suppress_handles is set to prevent the GtkPaned widgets
532 from retriggering an update. The precision of the Paned widgets
533 depends on screen resolution and tends to be less precise than
534 that of the spinbuttons causing an update-race.
536 if self.suppress_spinbuttons:
539 self.model.settings[data] = str(int(widget.get_value()))
540 self.update_adjustments()
541 yresolution = self.get_y_resolution()
543 # update the handle displays, but don't generate a new signal.
544 self.suppress_handles = True
545 self.set_pane_position(data + 'Paned', yresolution)
546 self.suppress_handles = False
548 # --------------------------------
550 # --------------------------------
552 def on_readSettings_clicked(self, widget, *data):
553 """Handles a click on the readSettings button.
555 Since it doesn't require userdata, this is connected through glade.
557 self.model.read_settings()
558 self.Notify("Active TV-out configuration loaded.")
560 def on_writeSettings_clicked(self, widget, *data):
561 """Handles a click on the writeSettings button.
563 Since it doesn't require userdata, this is connected through glade.
565 self.model.write_settings()
566 self.Notify("TV-out configuration updated.")
568 def on_deleteProfile_clicked(self, widget, *data):
569 """Handles a click on the deleteProfile button.
571 Since it doesn't require userdata, this is connected through glade.
572 The touch-screen is not the highest precision input and has no
573 visual feedback before a click. Therefore we better prompt for
574 confirmation. Having an undo instead would probably be better.
576 name = self.entry.get_text()
577 if name not in self.model.profiles or name in DC_DEFAULTS:
578 self.Notify("Cannot remove profile, please select an existing non-default profile.")
580 dialog = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_WARNING,
581 buttons=gtk.BUTTONS_YES_NO,
582 message_format="Are you sure you want to delete profile %s?" % name)
583 if dialog.run() == gtk.RESPONSE_YES:
584 self.model.delete_profile(name)
587 def on_saveProfile_clicked(self, widget, *data):
588 """Handles a click on the saveProfile button.
590 Since it doesn't require userdata, this is connected through glade.
591 Default profiles cannot be overwritten.
593 name = self.entry.get_text()
594 if name == '' or name in DC_DEFAULTS:
595 self.Notify("Invalid profile name")
597 self.model.set_profile(name, self.model.settings)
598 self.Notify("Profile saved as: %s" % name)
600 # --------------------------------
602 # --------------------------------
604 def _lose_active(self):
605 """Forces the comboboxentry to lose its active selection.
607 This ensures that it also generates a changed signal when we
608 select the same value twice in a row such that a user can undo
609 any changes made to a loaded profile by loading the profile again.
611 temp = self.entry.get_text()
612 self.entry.set_text('')
613 self.entry.set_text(temp)
615 def on_combo_changed(self, widget, *data):
616 """Handles selection in the profile combo box."""
617 name = self.entry.get_text()
618 self.delete.set_sensitive(name in self.model.profiles)
619 self.save.set_sensitive(name != '' and name not in DC_DEFAULTS)
620 if self.comboentry.get_active() != -1:
621 self.on_LoadProfile_clicked(None)
623 def on_LoadProfile_clicked(self, widget, *data):
624 """Loads profile in the entry widget (by name).
626 It is directly activated by the activation event of the combobox entry
627 (i.e. when the user presses enter) and indirectly called by
628 on_combo_changed (when the user performs a mouse selection).
630 self._lose_active() # force widget to always emit changed signals,
631 name = self.entry.get_text() # ok since we always lookup by text value anyway
634 elif name not in self.model.profiles and name not in DC_DEFAULTS:
635 self.Notify("Cannot load profile, please select an existing profile.")
637 self.model.load_named_profile(name)
638 self.Notify("Profile loaded, hit 'Write settings' to make it active")
640 # --------------------------------
642 def set_pane_position(self, key, yresolution):
643 """Sync the GtkPaned widgets with the spinbuttons.
645 The Paned handles need their position specified in pixels.
646 This converts an absolute value to a relative position as the pane is
647 not actually 720 pixels wide (nor high).
649 PP_DX and PP_DY are corrections for the border around the client area.
650 They are measured for a specific theme, I'm not sure if changing theme
651 can invalidate this value but I guess it can.
653 value = int(self.model.settings[key.rstrip('Paned')])
654 widget = self.panedwidgets[key]
655 if key == 'widthPaned' or key == 'xPaned':
656 maxval = self.previewPane.allocation.width - PP_DX
657 newpos = (value / 720.) * maxval
659 maxval = self.previewPane.allocation.height - PP_DY
660 newpos = (value / float(yresolution)) * maxval
661 widget.set_position(int(newpos))
663 def on_paned_position_change(self, widget, param, key):
664 """Handle dragging of the GtkPaned widgets.
666 This turns of handling of the spinbuttons as otherwise the spinbutton
667 update would re-trigger this event handler causing an update race due
668 to differences in precision.
670 if self.suppress_handles:
673 yresolution = self.get_y_resolution()
674 self.suppress_spinbuttons = True
676 maxval = self.previewPane.allocation.width - PP_DX
677 for key in ('xPaned', 'widthPaned'):
678 pos = int((self.panedwidgets[key].get_position()/maxval)*720)
679 adj = self.widgets[key.rstrip('Paned')].get_adjustment()
681 self.model.settings[key.rstrip('Paned')] = str(int(pos))
683 maxval = self.previewPane.allocation.height - PP_DY
684 for key in ('yPaned', 'heightPaned'):
685 pos = (self.panedwidgets[key].get_position()/maxval) * yresolution
686 adj = self.widgets[key.rstrip('Paned')].get_adjustment()
687 adj.set_value(int(pos))
688 self.model.settings[key.rstrip('Paned')] = str(int(pos))
690 self.update_adjustments()
691 self.suppress_spinbuttons = False
693 def on_window_destroy(self, widget, *data):
694 """Handle application shutdown."""
695 self.Notify("Storing profiles...")
696 self.model.store_profiles()
699 # ================================================================
702 """Runs the application.
704 @args: command-line arguments (typically sys.argv).
706 if len(args) == 1: # no params: run gui app
709 else: # run command line app
710 parser = optparse.OptionParser()
712 parser.add_option('-e', '--enabled', default='', help=HELP_ENABLED)
713 parser.add_option('-t', '--encoding_type', default='', help=HELP_ENCODING)
714 parser.add_option('-c', '--connection_type', default='', help=HELP_CONNECTION)
715 parser.add_option('-l', '--layer', default='', help=HELP_LAYER)
716 parser.add_option('-w', '--width', default='', help=HELP_WIDTH)
717 parser.add_option('-g', '--height', default='', help=HELP_HEIGHT)
718 parser.add_option('-x', '--x_position', default='', help=HELP_X)
719 parser.add_option('-y', '--y_position', default='', help=HELP_Y)
721 parser.add_option('-s', '--save_profile', default='', help=HELP_SAVE)
722 parser.add_option('-a', '--apply', default=False, action='store_true', help=HELP_APPLY)
723 parser.add_option('-p', '--load_profile', default='', help=HELP_LOAD)
724 parser.add_option('-d', '--remove_profile', default='', help=HELP_DEL)
726 options, args = parser.parse_args()
729 model.read_settings()
731 model.settings['enabled'] = options.enabled
732 if options.encoding_type:
733 model.settings['encoding'] = options.encoding_type
734 if options.connection_type:
735 model.settings['connection'] = options.connection_type
737 model.settings['layer'] = options.layer
739 model.settings['width'] = options.width
741 model.settings['height'] = options.height
742 if options.x_position:
743 model.settings['x'] = options.x_position
744 if options.y_position:
745 model.settings['y'] = options.y_position
747 profile_string = model.profile_to_string(model.settings)
748 config = Validate(profile_string)
751 print "Invalid values encountered in profile (%s), terminating." % profile_string
754 if options.save_profile and options.save_profile not in DC_DEFAULTS:
755 model.set_profile(options.save_profile, self.model.settings)
757 model.write_settings()
758 if options.load_profile:
759 model.load_named_profile(options.load_profile)
760 model.write_settings()
761 if options.remove_profile:
762 model.delete_profile(options.remove_profile)
764 if options.save_profile or options.remove_profile:
765 model.store_profiles()
767 if __name__ == '__main__':