#!/usr/bin/env python2 """ Frontend for Onkyo controller program. """ DEBUG = False HOST = 'dublin' PORT = 8701 import pygtk, gtk, gobject import os, socket HELLO_MESSAGE = "OK:onkyocontrol" STATUSES = [ 'power', 'mute', 'mode', 'volume', 'input', 'tune', 'sleep', 'zone2power', 'zone2mute', 'zone2volume', 'zone2input', 'zone2tune', 'zone2sleep' ] def verify_frequency(freq): """ Verify that a frequency value given by the user can be mapped to an FM or AM frequency. If it is a valid FM frequency, return the value as a float; if it is a valid AM frequency return it as an int. If the frequency is not valid, raise a CommandException. """ try: floatval = float(freq) except ValueError: raise CommandException("Frequency not valid: %s" % freq) # attempt to validate the frequency if floatval < 87.4 or floatval > 108.0: # try AM instead if floatval < 530 or floatval > 1710: # we failed both validity tests raise CommandException("Frequency not valid: %s" % freq) else: # valid AM frequency return "AM", int(floatval) else: # valid FM frequency return "FM", floatval class OnkyoClientException(Exception): pass class ConnectionError(OnkyoClientException): pass class CommandException(OnkyoClientException): pass class OnkyoClient: """ This class holds information and methods for connecting to the Onkyo receiver daemon program. """ def __init__(self, host, port): self.host = host self.port = port # set up holding spaces for our two events- a timed event if we # can't immediately connect, and a watch event once we are connected self._connectevent = -1 self._iowatchevent = -1 # set up our socket descriptors self._sock = None # set up our notification file descriptor (fd_r, fd_w) = os.pipe() self._piperead = fd_r self._pipewrite = fd_w # our status container object self.status = dict() for item in STATUSES: self.status[item] = None self.status['epoch'] = 0 # attempt initial connection self.establish_connection() def __del__(self): if self._sock: self._disconnect() if self._piperead: os.close(self._piperead) os.close(self._pipewrite) def _connect(self): if self._sock: return msg = "getaddrinfo returns an empty list" try: flags = socket.AI_ADDRCONFIG except AttributeError: flags = 0 for res in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, flags): af, socktype, proto, canonname, sa = res try: self._sock = socket.socket(af, socktype, proto) self._sock.connect(sa) except socket.error, msg: if self._sock: self._sock.close() self._sock = None continue break if not self._sock: # problem opening socket, let caller know return False try: self._hello() except ConnectionError: self._disconnect() return False return True def _disconnect(self): if self._iowatchevent >= 0: gobject.source_remove(self._iowatchevent) self._iowatchevent = -1 if self._sock: self._sock.close() self._sock = None def establish_connection(self, force=False): if self._sock: if force: self._disconnect() else: return True # attempt connection, we know we are disconnected at this point if self._connect(): # allow the frontend to see that we may have stale data self.status['epoch'] = self.status['epoch'] + 1 # stop our timer connect event if it exists if self._connectevent >= 0: gobject.source_remove(self._connectevent) self._connectevent = -1 # set up our select-like watcher on our input eventid = gobject.io_add_watch(self._sock, gobject.IO_IN | gobject.IO_PRI | gobject.IO_ERR | gobject.IO_HUP, self._processinput) self._iowatchevent = eventid # we've verified the connection, get powered on status self.querypower() return True else: # connection failed, set up our timer connect event if it doesn't # already exist if self._connectevent < 0: # attempt again in 2 seconds eventid = gobject.timeout_add(2000, self.establish_connection) self._connectevent = eventid return True def _hello(self): lines = self._read() if len(lines) < 1 or not lines[0].startswith(HELLO_MESSAGE): raise ConnectionError("Invalid hello message: '%s'" % lines[0]) def _writeline(self, line): if DEBUG: print "sending line: %s" % line if self._sock == None: self.establish_connection() return False self._sock.sendall("%s\n" % line) return True def _read(self): data = self._sock.recv(4096) if not data.endswith("\n"): raise ConnectionError("Connection lost on read") lines = data.rstrip("\n").split("\n") if DEBUG: print "received lines: %s" % lines return lines def _processinput(self, fd, condition): if condition & gobject.IO_HUP: # we were disconnected, attempt to reconnect print "Attempting to reconnect to controller socket" self.establish_connection(True) return False elif condition & gobject.IO_ERR: print "Error on socket" return False else: try: lines = self._read() except ConnectionError, e: # we were disconnected, attempt to reconnect print "Attempting to reconnect to controller socket" self.establish_connection(True) return False for line in lines: line = line.split(":") # first check for errors if line[0] == "ERROR": print "Error received: %s" % line #raise OnkyoClientException("Error received: %s" % line) # primary zone processing elif line[1] == "power": if line[2] == "on": self.status['power'] = True else: self.status['power'] = False elif line[1] == "mute": if line[2] == "on": self.status['mute'] = True else: self.status['mute'] = False # For everything else, we use the literal string returned after # the second colon. Ensure we don't lose any actual colons by # rejoining after the second colon. elif line[1] == "mode": self.status['mode'] = ":".join(line[2:]) elif line[1] == "volume": self.status['volume'] = int(line[2]) elif line[1] == "input": self.status['input'] = ":".join(line[2:]) elif line[1] == "tune": self.status['tune'] = ":".join(line[2:]) elif line[1] == "sleep": self.status['sleep'] = int(line[2]) # zone 2 processing elif line[1] == "zone2power": if line[2] == "on": self.status['zone2power'] = True else: self.status['zone2power'] = False elif line[1] == "zone2mute": if line[2] == "on": self.status['zone2mute'] = True else: self.status['zone2mute'] = False elif line[1] == "zone2mode": self.status['zone2mode'] = line[2] elif line[1] == "zone2volume": self.status['zone2volume'] = int(line[2]) elif line[1] == "zone2input": self.status['zone2input'] = line[2] elif line[1] == "zone2tune": self.status['zone2tune'] = line[2] elif line[1] == "zone2sleep": self.status['zone2sleep'] = int(line[2]) # not sure what we have if we get here else: print "Unrecognized response: %s" % line # notify the pipe that we processed input os.write(self._pipewrite, '%r' % line) # return true in any case if we made it here return True def get_notify_fd(self): return self._piperead def querystatus(self): self._writeline("status") def queryzone2status(self): self._writeline("status zone2") def querysleep(self): self._writeline("sleep") def queryzone2sleep(self): self._writeline("zone2sleep") def querypower(self): self._writeline("power") self._writeline("zone2power") def setpower(self, state): self.status['power'] = bool(state) if state == True: self._writeline("power on") else: self._writeline("power off") def setmute(self, state): self.status['mute'] = bool(state) if state == True: self._writeline("mute on") else: self._writeline("mute off") def setvolume(self, volume): try: intval = int(volume) except ValueError: raise CommandException("Volume not an integer: %s" % volume) if intval < 0 or intval > 100: raise CommandException("Volume out of range: %d" % intval) self.status['volume'] = intval self._writeline("volume %d" % intval) def setinput(self, inp): valid_inputs = [ 'dvr', 'vcr', 'cable', 'sat', 'tv', 'aux', 'dvd', 'tape', 'phono', 'cd', 'fm', 'fm tuner', 'am', 'am tuner', 'tuner', 'multich', 'xm', 'sirius' ] if inp.lower() not in valid_inputs: raise CommandException("Input not valid: %s" % inp) self.status['input'] = inp self._writeline("input %s" % inp) def setmode(self, mode): valid_modes = [ 'stereo', 'direct', 'acstereo', 'fullmono', 'mono', 'pure', 'straight', 'thx', 'pliimovie', 'pliimusic', 'pliigame', 'neo6cinema', 'neo6music', 'pliithx', 'neo6thx', 'neuralthx' ] if mode.lower() not in valid_modes: raise CommandException("Listening mode not valid: %s" % mode) self.status['mode'] = mode self._writeline("mode %s" % mode) def settune(self, freq): # this will throw an exception if freq was invalid value = verify_frequency(freq) if value[0] == "AM": self._writeline("tune %d" % value[1]) else: self._writeline("tune %.1f" % value[1]) def setsleep(self, mins): try: intval = int(mins) except ValueError: raise CommandException("Sleep time not an integer: %s" % mins) if intval < 0 or intval > 90: raise CommandException("Sleep time out of range: %d" % intval) self.status['sleep'] = intval if intval > 0: self._writeline("sleep %d" % intval) else: self._writeline("sleep off") def setzone2power(self, state): self.status['zone2power'] = bool(state) if state == True: self._writeline("zone2power on") else: self._writeline("zone2power off") def setzone2mute(self, state): self.status['zone2mute'] = bool(state) if state == True: self._writeline("zone2mute on") else: self._writeline("zone2mute off") def setzone2volume(self, volume): try: intval = int(volume) except ValueError: raise CommandException("Volume not an integer: %s" % volume) if intval < 0 or intval > 100: raise CommandException("Volume out of range: %d" % intval) self.status['zone2volume'] = intval self._writeline("zone2volume %d" % intval) def setzone2input(self, inp): valid_inputs = [ 'dvr', 'vcr', 'cable', 'sat', 'tv', 'aux', 'dvd', 'tape', 'phono', 'cd', 'fm', 'fm tuner', 'am', 'am tuner', 'tuner', 'multich', 'xm', 'sirius', 'source', 'off' ] if inp.lower() not in valid_inputs: raise CommandException("Input not valid: %s" % inp) self.status['zone2input'] = inp self._writeline("zone2input %s" % inp) def setzone2tune(self, freq): # this will throw an exception if freq was invalid value = verify_frequency(freq) if value[0] == "AM": self._writeline("zone2tune %d" % value[1]) else: self._writeline("zone2tune %.1f" % value[1]) def setzone2sleep(self, mins): try: intval = int(mins) except ValueError: raise CommandException("Sleep time not an integer: %s" % mins) if intval < 0 or intval > 1440: raise CommandException("Sleep time out of range: %d" % intval) self.status['zone2sleep'] = intval if intval > 0: self._writeline("zone2sleep %d" % intval) else: self._writeline("zone2sleep off") class OnkyoFrontend: def __init__(self, host, port): # initialize our known status object self.known_status = dict() for item in STATUSES: self.known_status[item] = None self.known_status['epoch'] = 0 # create a new client object self.client = OnkyoClient(host, port) # make our GTK window self.setup_gui() # kick off our update function, listening on the client notify FD gobject.io_add_watch(self.client.get_notify_fd(), gobject.IO_IN, self.update_controls) def set_combobox_text(self, combobox, text): model = combobox.get_model() iter = model.get_iter_root() while iter != None: if model.get_value(iter, 0) == text: combobox.set_active_iter(iter) break iter = model.iter_next(iter) def delete_event(self, widget, data=None): return False def destroy(self, widget, data=None): gtk.main_quit() def errorbox(self, message): def response_handler(dialog, response_id): dialog.destroy() dialog = gtk.MessageDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message) dialog.connect("response", response_handler) dialog.show() def callback_power(self, widget, data=None): value = widget.get_active() if value != self.known_status['power']: self.client.setpower(value) self.set_main_sensitive(value) def callback_zone2power(self, widget, data=None): value = widget.get_active() if value != self.known_status['zone2power']: self.client.setzone2power(value) self.set_zone2_sensitive(value) def callback_input(self, widget, data=None): model = widget.get_model() iter = widget.get_active_iter() key = model.get_value(iter, 0) if key == self.known_status['input']: return value = self.available_inputs_dict[key] self.client.setinput(value) def callback_zone2input(self, widget, data=None): model = widget.get_model() iter = widget.get_active_iter() key = model.get_value(iter, 0) if key == self.known_status['zone2input']: return value = self.zone2_available_inputs_dict[key] self.client.setzone2input(value) def callback_mode(self, widget, data=None): model = widget.get_model() iter = widget.get_active_iter() key = model.get_value(iter, 0) if key == self.known_status['mode']: return value = self.available_modes_dict[key] self.client.setmode(value) def callback_mute(self, widget, data=None): value = widget.get_active() if value != self.known_status['mute']: self.client.setmute(value) def callback_zone2mute(self, widget, data=None): value = widget.get_active() if value != self.known_status['zone2mute']: self.client.setzone2mute(value) def callback_format_volume(self, widget, value, data=None): # value comes in between 0 and 100. By subtracting 82, we get our # dB reading, which is the same as shown on the receiver. intval = int(value) - 82 if intval <= -82: # -(infinity) dB return u"-\u221E dB" return "%+d dB" % intval def callback_volume(self, widget, data=None): value = int(widget.get_value()) if value != self.known_status['volume']: self.client.setvolume(value) def callback_zone2volume(self, widget, data=None): value = int(widget.get_value()) if value != self.known_status['zone2volume']: self.client.setzone2volume(value) def callback_tune(self, widget, data=None): value = self.tuneentry.get_text() self.tuneentry.set_text("") try: self.client.settune(value) except CommandException, e: self.errorbox(e.args[0]) def callback_zone2tune(self, widget, data=None): value = self.zone2tuneentry.get_text() self.zone2tuneentry.set_text("") try: self.client.setzone2tune(value) except CommandException, e: self.errorbox(e.args[0]) def callback_sleep(self, widget, data=None): value = self.sleepentry.get_text() self.sleepentry.set_text("") try: self.client.setsleep(value) except CommandException, e: self.errorbox(e.args[0]) def callback_sleepclear(self, widget, data=None): value = 0 self.sleepentry.set_text("") try: self.client.setsleep(value) except CommandException, e: self.errorbox(e.args[0]) def callback_zone2sleep(self, widget, data=None): value = self.zone2sleepentry.get_text() self.zone2sleepentry.set_text("") try: self.client.setzone2sleep(value) except CommandException, e: self.errorbox(e.args[0]) def callback_zone2sleepclear(self, widget, data=None): value = 0 self.zone2sleepentry.set_text("") try: self.client.setzone2sleep(value) except CommandException, e: self.errorbox(e.args[0]) def update_controls(self, fd, condition): # suck our input, put it in the console data = os.read(fd, 256) buf = self.console.get_buffer() buf.insert(buf.get_end_iter(), "%s\n" % data) self.console.scroll_to_iter(buf.get_end_iter(), 0) # convenience variable client_status = self.client.status status_updated = dict() # record our client statues in known_status so we don't do unnecessary # updates when we call each of the set_* methods for item in STATUSES: if self.known_status[item] != client_status[item]: self.known_status[item] = client_status[item] status_updated[item] = True else: status_updated[item] = False new_epoch = self.known_status['epoch'] != client_status['epoch'] if new_epoch: self.known_status['epoch'] = client_status['epoch'] # make sure to call correct method for each type of control # check for None value as it means we don't know the status if client_status['power'] != None and status_updated['power'] == True: self.power.set_active(client_status['power']) self.set_main_sensitive(client_status['power']) # query for a status update if the receiver power switched on if client_status['power'] == True: self.client.querystatus() self.client.querysleep() else: self.sleep.set_text("Off") # also query for a status update if power is on and new epoch elif client_status['power'] == True and new_epoch: self.client.querystatus() self.client.querysleep() if client_status['mute'] != None and status_updated['mute'] == True: self.mute.set_active(client_status['mute']) if client_status['mode'] != None and status_updated['mode'] == True: self.set_combobox_text(self.mode, client_status['mode']) if client_status['volume'] != None and status_updated['volume'] == True: self.volume.set_value(client_status['volume']) if client_status['input'] != None and status_updated['input'] == True: self.set_combobox_text(self.input, client_status['input']) if client_status['tune'] != None and status_updated['tune'] == True: self.tune.set_text(client_status['tune']) if client_status['sleep'] != None and status_updated['sleep'] == True: if client_status['sleep'] == 0: self.sleep.set_text("Off") else: self.sleep.set_text("%d min" % client_status['sleep']) if client_status['zone2power'] != None and \ status_updated['zone2power'] == True: self.zone2power.set_active(client_status['zone2power']) self.set_zone2_sensitive(client_status['zone2power']) if client_status['zone2power'] == True: self.client.queryzone2status() else: self.zone2sleep.set_text("Off") # also query for a status update if power is on and new epoch elif client_status['zone2power'] == True and new_epoch: self.client.queryzone2status() if client_status['zone2mute'] != None and \ status_updated['zone2mute'] == True: self.zone2mute.set_active(client_status['zone2mute']) # no zone2 mode if client_status['zone2volume'] != None and \ status_updated['zone2volume'] == True: self.zone2volume.set_value(client_status['zone2volume']) if client_status['zone2input'] != None and \ status_updated['zone2input'] == True: self.set_combobox_text(self.zone2input, client_status['zone2input']) if client_status['zone2tune'] != None and \ status_updated['zone2tune'] == True: self.zone2tune.set_text(client_status['zone2tune']) if client_status['zone2sleep'] != None and \ status_updated['zone2sleep'] == True: if client_status['zone2sleep'] == 0: self.zone2sleep.set_text("Off") else: self.zone2sleep.set_text("%d min" % client_status['zone2sleep']) return True def set_main_sensitive(self, sensitive): self.volume.set_sensitive(sensitive) self.mute.set_sensitive(sensitive) self.input.set_sensitive(sensitive) self.mode.set_sensitive(sensitive) self.tune.set_sensitive(sensitive) self.tuneentry.set_sensitive(sensitive) self.tuneentrybutton.set_sensitive(sensitive) self.sleepentry.set_sensitive(sensitive) self.sleepentrybutton.set_sensitive(sensitive) self.sleepclearbutton.set_sensitive(sensitive) def set_zone2_sensitive(self, sensitive): self.zone2volume.set_sensitive(sensitive) self.zone2mute.set_sensitive(sensitive) self.zone2input.set_sensitive(sensitive) self.zone2mode.set_sensitive(sensitive) self.zone2tune.set_sensitive(sensitive) self.zone2tuneentry.set_sensitive(sensitive) self.zone2tuneentrybutton.set_sensitive(sensitive) self.zone2sleepentry.set_sensitive(sensitive) self.zone2sleepentrybutton.set_sensitive(sensitive) self.zone2sleepclearbutton.set_sensitive(sensitive) def setup_gui(self): # some standard things self.available_inputs = [ ('DVR', 'dvr'), ('Cable', 'cable'), ('TV', 'tv'), ('Aux', 'aux'), ('DVD', 'dvd'), ('Tape', 'tape'), ('Phono', 'phono'), ('CD', 'cd'), ('FM Tuner', 'fm'), ('AM Tuner', 'am'), ('Multichannel', 'multich'), ('XM Radio', 'xm'), ('Sirius Radio', 'sirius'), ] self.available_inputs_dict = dict(self.available_inputs) # make a copy for zone 2, adding one element self.zone2_available_inputs = self.available_inputs[:] self.zone2_available_inputs.append( ('Source', 'source') ) self.zone2_available_inputs_dict = dict(self.zone2_available_inputs) self.available_modes = [ ('Pure Audio', 'pure'), ('Direct', 'direct'), ('Stereo', 'stereo'), ('All Channel Stereo', 'acstereo'), ('Mono', 'mono'), ('Full Mono', 'fullmono'), ('Mono Movie', 'monomovie'), ('Straight Decode', 'straight'), ('THX Cinema', 'thx'), ('Pro Logic IIx Movie', 'pliimovie'), ('Pro Logic IIx Music', 'pliimusic'), ('Pro Logic IIx Game', 'pliigame'), ('Neo:6 Cinema', 'neo6cinema'), ('Neo:6 Music', 'neo6music'), ('PLIIx THX Cinema', 'pliithx'), ('Neo:6 THX Cinema', 'neo6thx'), ('Neural THX', 'neuralthx'), ] self.available_modes_dict = dict(self.available_modes) # start framing out our window self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title("Onkyo Receiver Controller") self.window.set_role("mainWindow") self.window.set_resizable(True) self.window.set_border_width(10) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) # we are going to need some boxes self.mainbox = gtk.VBox(False, 10) self.upperbox = gtk.HBox(False, 10) self.secondarybox = gtk.VBox(False, 10) self.primarybox = gtk.VBox(False, 10) self.zone2secondarybox = gtk.VBox(False, 10) self.zone2primarybox = gtk.VBox(False, 10) self.upperbox.pack_start(self.secondarybox, True, False, 0) self.upperbox.pack_start(self.primarybox, False, False, 0) self.upperbox.pack_end(self.zone2secondarybox, True, False, 0) self.upperbox.pack_end(self.zone2primarybox, False, False, 0) self.mainbox.pack_start(self.upperbox, True, True, 0) self.window.add(self.mainbox) # secondary box (detailed control) elements self.mainzonelabel = gtk.Label() self.mainzonelabel.set_markup("Main Zone") self.mainzonelabel.set_justify(gtk.JUSTIFY_CENTER) self.secondarybox.pack_start(self.mainzonelabel, False, False, 0) self.mainzonelabel.show() self.inputbox = gtk.HBox(False, 0) self.inputlabel = gtk.Label("Input: ") self.input = gtk.combo_box_new_text() for value in self.available_inputs: self.input.append_text(value[0]) self.input.connect("changed", self.callback_input) self.inputbox.pack_start(self.inputlabel, False, False, 0) self.inputbox.pack_end(self.input, False, False, 0) self.secondarybox.pack_start(self.inputbox, False, False, 0) self.inputlabel.show() self.input.show() self.inputbox.show() self.modebox = gtk.HBox(False, 0) self.modelabel = gtk.Label("Listening Mode: ") self.mode = gtk.combo_box_new_text() for modeval in self.available_modes: self.mode.append_text(modeval[0]) self.mode.connect("changed", self.callback_mode) self.modebox.pack_start(self.modelabel, False, False, 0) self.modebox.pack_end(self.mode, False, False, 0) self.secondarybox.pack_start(self.modebox, False, False, 0) self.modelabel.show() self.mode.show() self.modebox.show() self.tunebox = gtk.HBox(False, 0) self.tunelabel = gtk.Label("Tuned Station: ") self.tune = gtk.Label("Unknown") self.tune.set_justify(gtk.JUSTIFY_RIGHT) self.tunebox.pack_start(self.tunelabel, False, False, 0) self.tunebox.pack_end(self.tune, False, False, 0) self.secondarybox.pack_start(self.tunebox, False, False, 0) self.tunelabel.show() self.tune.show() self.tunebox.show() self.tuneentrybox = gtk.HBox(False, 0) self.tuneentrylabel = gtk.Label("Tune To: ") self.tuneentry = gtk.Entry(6) self.tuneentry.set_width_chars(15) self.tuneentry.connect("activate", self.callback_tune) self.tuneentrybutton = gtk.Button("Go") self.tuneentrybutton.connect("clicked", self.callback_tune) self.tuneentrybox.pack_start(self.tuneentrylabel, False, False, 0) self.tuneentrybox.pack_end(self.tuneentrybutton, False, False, 0) self.tuneentrybox.pack_end(self.tuneentry, True, True, 0) self.secondarybox.pack_start(self.tuneentrybox, False, False, 0) self.tuneentrylabel.show() self.tuneentry.show() self.tuneentrybutton.show() self.tuneentrybox.show() self.sleepentrybox = gtk.HBox(False, 0) self.sleepentrylabel = gtk.Label("Sleep Timer: ") self.sleep = gtk.Label("Off") self.sleepentry = gtk.Entry(2) self.sleepentry.set_width_chars(6) self.sleepentry.connect("activate", self.callback_sleep) self.sleepentrybutton = gtk.Button("Set") self.sleepentrybutton.connect("clicked", self.callback_sleep) self.sleepclearbutton = gtk.Button("Clear") self.sleepclearbutton.connect("clicked", self.callback_sleepclear) self.sleepentrybox.pack_start(self.sleepentrylabel, False, False, 0) self.sleepentrybox.pack_start(self.sleep, False, False, 0) self.sleepentrybox.pack_end(self.sleepclearbutton, False, False, 0) self.sleepentrybox.pack_end(self.sleepentrybutton, False, False, 0) self.sleepentrybox.pack_end(self.sleepentry, False, True, 0) self.secondarybox.pack_start(self.sleepentrybox, False, False, 0) self.sleepentrylabel.show() self.sleep.show() self.sleepentry.show() self.sleepentrybutton.show() self.sleepclearbutton.show() self.sleepentrybox.show() # primary control box elements self.power = gtk.ToggleButton("Power") self.power.connect("toggled", self.callback_power) self.primarybox.pack_start(self.power, False, False, 0) self.power.show() self.volumelabel = gtk.Label("Volume") self.volumelabel.set_justify(gtk.JUSTIFY_CENTER) self.primarybox.pack_start(self.volumelabel, False, False, 0) self.volumelabel.show() self.volume = gtk.VScale() self.volume.set_range(0, 100) self.volume.set_inverted(True) # don't update volume value immediately, wait for settling self.volume.set_update_policy(gtk.UPDATE_DELAYED) self.volume.set_increments(1, 5) # make sure this widget is large enough to be usable self.volume.set_size_request(-1, 125) self.volume.connect("value-changed", self.callback_volume) self.volume.connect("format-value", self.callback_format_volume) self.primarybox.pack_start(self.volume, True, True, 0) self.volume.show() self.mute = gtk.ToggleButton("Mute") self.mute.connect("toggled", self.callback_mute) self.primarybox.pack_start(self.mute, False, False, 0) self.mute.show() # zone 2 secondary box (detailed control) elements self.zonetwolabel = gtk.Label() self.zonetwolabel.set_markup("Zone Two") self.zonetwolabel.set_justify(gtk.JUSTIFY_CENTER) self.zone2secondarybox.pack_start(self.zonetwolabel, False, False, 0) self.zonetwolabel.show() self.zone2inputbox = gtk.HBox(False, 0) self.zone2inputlabel = gtk.Label("Input: ") self.zone2input = gtk.combo_box_new_text() for value in self.zone2_available_inputs: self.zone2input.append_text(value[0]) self.zone2input.connect("changed", self.callback_zone2input) self.zone2inputbox.pack_start(self.zone2inputlabel, False, False, 0) self.zone2inputbox.pack_end(self.zone2input, False, False, 0) self.zone2secondarybox.pack_start(self.zone2inputbox, False, False, 0) self.zone2inputlabel.show() self.zone2input.show() self.zone2inputbox.show() self.zone2modebox = gtk.HBox(False, 0) self.zone2modelabel = gtk.Label("Listening Mode: ") self.zone2mode = gtk.Label("Stereo") self.zone2mode.set_justify(gtk.JUSTIFY_RIGHT) self.zone2modebox.pack_start(self.zone2modelabel, False, False, 0) self.zone2modebox.pack_end(self.zone2mode, False, False, 0) self.zone2secondarybox.pack_start(self.zone2modebox, False, False, 0) self.zone2modelabel.show() self.zone2mode.show() self.zone2modebox.show() self.zone2tunebox = gtk.HBox(False, 0) self.zone2tunelabel = gtk.Label("Tuned Station: ") self.zone2tune = gtk.Label("Unknown") self.zone2tune.set_justify(gtk.JUSTIFY_RIGHT) self.zone2tunebox.pack_start(self.zone2tunelabel, False, False, 0) self.zone2tunebox.pack_end(self.zone2tune, False, False, 0) self.zone2secondarybox.pack_start(self.zone2tunebox, False, False, 0) self.zone2tunelabel.show() self.zone2tune.show() self.zone2tunebox.show() self.zone2tuneentrybox = gtk.HBox(False, 0) self.zone2tuneentrylabel = gtk.Label("Tune To: ") self.zone2tuneentry = gtk.Entry(6) self.zone2tuneentry.set_width_chars(15) self.zone2tuneentry.connect("activate", self.callback_zone2tune) self.zone2tuneentrybutton = gtk.Button("Go") self.zone2tuneentrybutton.connect("clicked", self.callback_zone2tune) self.zone2tuneentrybox.pack_start(self.zone2tuneentrylabel, False, False, 0) self.zone2tuneentrybox.pack_end(self.zone2tuneentrybutton, False, False, 0) self.zone2tuneentrybox.pack_end(self.zone2tuneentry, True, True, 0) self.zone2secondarybox.pack_start(self.zone2tuneentrybox, False, False, 0) self.zone2tuneentrylabel.show() self.zone2tuneentry.show() self.zone2tuneentrybutton.show() self.zone2tuneentrybox.show() self.zone2sleepentrybox = gtk.HBox(False, 0) self.zone2sleepentrylabel = gtk.Label("Sleep Timer: ") self.zone2sleep = gtk.Label("Off") self.zone2sleepentry = gtk.Entry(4) self.zone2sleepentry.set_width_chars(6) self.zone2sleepentry.connect("activate", self.callback_zone2sleep) self.zone2sleepentrybutton = gtk.Button("Set") self.zone2sleepentrybutton.connect("clicked", self.callback_zone2sleep) self.zone2sleepclearbutton = gtk.Button("Clear") self.zone2sleepclearbutton.connect("clicked", self.callback_zone2sleepclear) self.zone2sleepentrybox.pack_start(self.zone2sleepentrylabel, False, False, 0) self.zone2sleepentrybox.pack_start(self.zone2sleep, False, False, 0) self.zone2sleepentrybox.pack_end(self.zone2sleepclearbutton, False, False, 0) self.zone2sleepentrybox.pack_end(self.zone2sleepentrybutton, False, False, 0) self.zone2sleepentrybox.pack_end(self.zone2sleepentry, False, True, 0) self.zone2secondarybox.pack_start(self.zone2sleepentrybox, False, False, 0) self.zone2sleepentrylabel.show() self.zone2sleep.show() self.zone2sleepentry.show() self.zone2sleepentrybutton.show() self.zone2sleepclearbutton.show() self.zone2sleepentrybox.show() # zone 2 primary control elements self.zone2power = gtk.ToggleButton("Z2 Power") self.zone2power.connect("toggled", self.callback_zone2power) self.zone2primarybox.pack_start(self.zone2power, False, False, 0) self.zone2power.show() self.zone2volumelabel = gtk.Label("Z2 Volume") self.zone2volumelabel.set_justify(gtk.JUSTIFY_CENTER) self.zone2primarybox.pack_start(self.zone2volumelabel, False, False, 0) self.zone2volumelabel.show() self.zone2volume = gtk.VScale() self.zone2volume.set_range(0, 100) self.zone2volume.set_inverted(True) # don't update volume value immediately, wait for settling self.zone2volume.set_update_policy(gtk.UPDATE_DELAYED) self.zone2volume.set_increments(1, 5) # make sure this widget is large enough to be usable self.zone2volume.set_size_request(-1, 125) self.zone2volume.connect("value-changed", self.callback_zone2volume) self.zone2volume.connect("format-value", self.callback_format_volume) self.zone2primarybox.pack_start(self.zone2volume, True, True, 0) self.zone2volume.show() self.zone2mute = gtk.ToggleButton("Z2 Mute") self.zone2mute.connect("toggled", self.callback_zone2mute) self.zone2primarybox.pack_start(self.zone2mute, False, False, 0) self.zone2mute.show() # add an output/debug console below everything else self.console = gtk.TextView() self.console.set_cursor_visible(False) self.console.set_editable(False) self.consolescroll = gtk.ScrolledWindow() self.consolescroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.consolescroll.add(self.console) self.consoleexpand = gtk.Expander("Debug Console") self.consoleexpand.add(self.consolescroll) self.mainbox.pack_end(self.consoleexpand, False, False, 0) self.console.show() self.consolescroll.show() self.consoleexpand.show() # disable our controls by default at start self.set_main_sensitive(False) self.set_zone2_sensitive(False) # our initial window is ready, show it self.secondarybox.show() self.primarybox.show() self.zone2primarybox.show() self.zone2secondarybox.show() self.upperbox.show() self.mainbox.show() self.window.show() accel_group = gtk.AccelGroup() accel_group.connect_group(ord('q'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_LOCKED, gtk.main_quit) self.window.add_accel_group(accel_group) def main(self): gtk.main() if __name__ == "__main__": import sys host = HOST port = PORT if len(sys.argv) > 1: host = sys.argv[1] if len(sys.argv) > 2: port = int(sys.argv[2]) app = OnkyoFrontend(host, port) app.main() # vim: set ts=4 sw=4 et: