| #! /usr/bin/env python |
| |
| """audiopy -- a program to control the Solaris audio device. |
| |
| Contact: Barry Warsaw |
| Email: bwarsaw@python.org |
| Version: %(__version__)s |
| |
| When no arguments are given, this pops up a graphical window which lets you |
| choose the audio input and output devices, and set the output volume. |
| |
| This program can be driven via the command line, and when done so, no window |
| pops up. Most options have the general form: |
| |
| --device[={0,1}] |
| -d[={0,1}] |
| Set the I/O device. With no value, it toggles the specified device. |
| With a value, 0 turns the device off and 1 turns the device on. |
| |
| The list of devices and their short options are: |
| |
| (input) |
| microphone -- m |
| linein -- i |
| cd -- c |
| |
| (output) |
| headphones -- p |
| speaker -- s |
| lineout -- o |
| |
| Other options are: |
| |
| --gain volume |
| -g volume |
| Sets the output gain to the specified volume, which must be an integer |
| in the range [%(MIN_GAIN)s..%(MAX_GAIN)s] |
| |
| --version |
| -v |
| Print the version number and exit. |
| |
| --help |
| -h |
| Print this message and exit. |
| """ |
| |
| import sys |
| import os |
| import errno |
| import sunaudiodev |
| from SUNAUDIODEV import * |
| |
| # Milliseconds between interrupt checks |
| KEEPALIVE_TIMER = 500 |
| |
| __version__ = '1.1' |
| |
| |
| |
| class MainWindow: |
| def __init__(self, device): |
| from Tkinter import * |
| self.__helpwin = None |
| self.__devctl = device |
| info = device.getinfo() |
| # |
| self.__tkroot = tkroot = Tk(className='Audiopy') |
| tkroot.withdraw() |
| # create the menubar |
| menubar = Menu(tkroot) |
| filemenu = Menu(menubar, tearoff=0) |
| filemenu.add_command(label='Quit', |
| command=self.__quit, |
| accelerator='Alt-Q', |
| underline=0) |
| helpmenu = Menu(menubar, name='help', tearoff=0) |
| helpmenu.add_command(label='About Audiopy...', |
| command=self.__popup_about, |
| underline=0) |
| helpmenu.add_command(label='Help...', |
| command=self.__popup_using, |
| underline=0) |
| menubar.add_cascade(label='File', |
| menu=filemenu, |
| underline=0) |
| menubar.add_cascade(label='Help', |
| menu=helpmenu, |
| underline=0) |
| # now create the top level window |
| root = self.__root = Toplevel(tkroot, class_='Audiopy', menu=menubar) |
| root.protocol('WM_DELETE_WINDOW', self.__quit) |
| root.title('audiopy ' + __version__) |
| root.iconname('audiopy ' + __version__) |
| root.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive) |
| # |
| buttons = [] |
| # |
| # where does input come from? |
| frame = Frame(root, bd=1, relief=RAISED) |
| frame.grid(row=1, column=0, sticky='NSEW') |
| label = Label(frame, text='Input From:') |
| label.grid(row=0, column=0, sticky=E) |
| self.__inputvar = IntVar() |
| ## |
| btn = Radiobutton(frame, |
| text='None', |
| variable=self.__inputvar, |
| value=0, |
| command=self.__pushtodev, |
| underline=0) |
| btn.grid(row=0, column=1, sticky=W) |
| root.bind('<Alt-n>', self.__none) |
| root.bind('<Alt-N>', self.__none) |
| if not info.i_avail_ports & MICROPHONE: |
| btn.configure(state=DISABLED) |
| buttons.append(btn) |
| ## |
| btn = Radiobutton(frame, |
| text='Microphone', |
| variable=self.__inputvar, |
| value=MICROPHONE, |
| command=self.__pushtodev, |
| underline=0) |
| btn.grid(row=1, column=1, sticky=W) |
| root.bind('<Alt-m>', self.__mic) |
| root.bind('<Alt-M>', self.__mic) |
| if not info.i_avail_ports & MICROPHONE: |
| btn.configure(state=DISABLED) |
| buttons.append(btn) |
| ## |
| btn = Radiobutton(frame, |
| text='Line In', |
| variable=self.__inputvar, |
| value=LINE_IN, |
| command=self.__pushtodev, |
| underline=5) |
| btn.grid(row=2, column=1, sticky=W) |
| root.bind('<Alt-i>', self.__linein) |
| root.bind('<Alt-I>', self.__linein) |
| if not info.i_avail_ports & LINE_IN: |
| btn.configure(state=DISABLED) |
| buttons.append(btn) |
| ## if SUNAUDIODEV was built on an older version of Solaris, the CD |
| ## input device won't exist |
| try: |
| btn = Radiobutton(frame, |
| text='CD', |
| variable=self.__inputvar, |
| value=CD, |
| command=self.__pushtodev, |
| underline=0) |
| btn.grid(row=3, column=1, sticky=W) |
| root.bind('<Alt-c>', self.__cd) |
| root.bind('<Alt-C>', self.__cd) |
| if not info.i_avail_ports & CD: |
| btn.configure(state=DISABLED) |
| buttons.append(btn) |
| except NameError: |
| pass |
| # |
| # where does output go to? |
| frame = Frame(root, bd=1, relief=RAISED) |
| frame.grid(row=2, column=0, sticky='NSEW') |
| label = Label(frame, text='Output To:') |
| label.grid(row=0, column=0, sticky=E) |
| self.__spkvar = IntVar() |
| btn = Checkbutton(frame, |
| text='Speaker', |
| variable=self.__spkvar, |
| onvalue=SPEAKER, |
| command=self.__pushtodev, |
| underline=0) |
| btn.grid(row=0, column=1, sticky=W) |
| root.bind('<Alt-s>', self.__speaker) |
| root.bind('<Alt-S>', self.__speaker) |
| if not info.o_avail_ports & SPEAKER: |
| btn.configure(state=DISABLED) |
| buttons.append(btn) |
| ## |
| self.__headvar = IntVar() |
| btn = Checkbutton(frame, |
| text='Headphones', |
| variable=self.__headvar, |
| onvalue=HEADPHONE, |
| command=self.__pushtodev, |
| underline=4) |
| btn.grid(row=1, column=1, sticky=W) |
| root.bind('<Alt-p>', self.__headphones) |
| root.bind('<Alt-P>', self.__headphones) |
| if not info.o_avail_ports & HEADPHONE: |
| btn.configure(state=DISABLED) |
| buttons.append(btn) |
| ## |
| self.__linevar = IntVar() |
| btn = Checkbutton(frame, |
| variable=self.__linevar, |
| onvalue=LINE_OUT, |
| text='Line Out', |
| command=self.__pushtodev, |
| underline=0) |
| btn.grid(row=2, column=1, sticky=W) |
| root.bind('<Alt-l>', self.__lineout) |
| root.bind('<Alt-L>', self.__lineout) |
| if not info.o_avail_ports & LINE_OUT: |
| btn.configure(state=DISABLED) |
| buttons.append(btn) |
| # |
| # Fix up widths |
| widest = 0 |
| for b in buttons: |
| width = b['width'] |
| if width > widest: |
| widest = width |
| for b in buttons: |
| b.configure(width=widest) |
| # root bindings |
| root.bind('<Alt-q>', self.__quit) |
| root.bind('<Alt-Q>', self.__quit) |
| # |
| # Volume |
| frame = Frame(root, bd=1, relief=RAISED) |
| frame.grid(row=3, column=0, sticky='NSEW') |
| label = Label(frame, text='Output Volume:') |
| label.grid(row=0, column=0, sticky=W) |
| self.__scalevar = IntVar() |
| self.__scale = Scale(frame, |
| orient=HORIZONTAL, |
| from_=MIN_GAIN, |
| to=MAX_GAIN, |
| length=200, |
| variable=self.__scalevar, |
| command=self.__volume) |
| self.__scale.grid(row=1, column=0, sticky=EW) |
| # |
| # do we need to poll for changes? |
| self.__needtopoll = 1 |
| try: |
| fd = self.__devctl.fileno() |
| self.__needtopoll = 0 |
| except AttributeError: |
| pass |
| else: |
| import fcntl |
| import signal |
| import STROPTS |
| # set up the signal handler |
| signal.signal(signal.SIGPOLL, self.__update) |
| fcntl.ioctl(fd, STROPTS.I_SETSIG, STROPTS.S_MSG) |
| self.__update() |
| |
| def __quit(self, event=None): |
| self.__devctl.close() |
| self.__root.quit() |
| |
| def __popup_about(self, event=None): |
| import tkMessageBox |
| tkMessageBox.showinfo('About Audiopy ' + __version__, |
| '''\ |
| Audiopy %s |
| Control the Solaris audio device |
| |
| For information |
| Contact: Barry A. Warsaw |
| Email: bwarsaw@python.org''' % __version__) |
| |
| def __popup_using(self, event=None): |
| if not self.__helpwin: |
| self.__helpwin = Helpwin(self.__tkroot, self.__quit) |
| self.__helpwin.deiconify() |
| |
| |
| def __keepalive(self): |
| # Exercise the Python interpreter regularly so keyboard interrupts get |
| # through. |
| self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive) |
| if self.__needtopoll: |
| self.__update() |
| |
| def __update(self, num=None, frame=None): |
| # It's possible (although I have never seen it) to get an interrupted |
| # system call during the getinfo() call. If so, and we're polling, |
| # don't sweat it because we'll come around again later. Otherwise, |
| # we'll give it a couple of tries and then give up until next time. |
| tries = 0 |
| while 1: |
| try: |
| info = self.__devctl.getinfo() |
| break |
| except sunaudiodev.error: |
| if self.__needtopoll or tries > 3: |
| return |
| tries = tries + 1 |
| # input |
| self.__inputvar.set(info.i_port) |
| # output |
| self.__spkvar.set(info.o_port & SPEAKER) |
| self.__headvar.set(info.o_port & HEADPHONE) |
| self.__linevar.set(info.o_port & LINE_OUT) |
| # volume |
| self.__scalevar.set(info.o_gain) |
| |
| def __pushtodev(self, event=None): |
| info = self.__devctl.getinfo() |
| info.o_port = self.__spkvar.get() + \ |
| self.__headvar.get() + \ |
| self.__linevar.get() |
| info.i_port = self.__inputvar.get() |
| info.o_gain = self.__scalevar.get() |
| try: |
| self.__devctl.setinfo(info) |
| except sunaudiodev.error as msg: |
| # TBD: what to do? it's probably temporary. |
| pass |
| |
| def __getset(self, var, onvalue): |
| if var.get() == onvalue: |
| var.set(0) |
| else: |
| var.set(onvalue) |
| self.__pushtodev() |
| |
| def __none(self, event=None): |
| self.__inputvar.set(0) |
| self.__pushtodev() |
| |
| def __mic(self, event=None): |
| self.__getset(self.__inputvar, MICROPHONE) |
| |
| def __linein(self, event=None): |
| self.__getset(self.__inputvar, LINE_IN) |
| |
| def __cd(self, event=None): |
| self.__getset(self.__inputvar, CD) |
| |
| def __speaker(self, event=None): |
| self.__getset(self.__spkvar, SPEAKER) |
| |
| def __headphones(self, event=None): |
| self.__getset(self.__headvar, HEADPHONE) |
| |
| def __lineout(self, event=None): |
| self.__getset(self.__linevar, LINE_OUT) |
| |
| def __volume(self, event=None): |
| self.__pushtodev() |
| |
| def start(self): |
| self.__keepalive() |
| self.__tkroot.mainloop() |
| |
| |
| |
| class Helpwin: |
| def __init__(self, master, quitfunc): |
| from Tkinter import * |
| self.__root = root = Toplevel(master, class_='Audiopy') |
| root.protocol('WM_DELETE_WINDOW', self.__withdraw) |
| root.title('Audiopy Help Window') |
| root.iconname('Audiopy Help Window') |
| root.bind('<Alt-q>', quitfunc) |
| root.bind('<Alt-Q>', quitfunc) |
| root.bind('<Alt-w>', self.__withdraw) |
| root.bind('<Alt-W>', self.__withdraw) |
| |
| # more elaborate help is available in the README file |
| readmefile = os.path.join(sys.path[0], 'README') |
| try: |
| fp = None |
| try: |
| fp = open(readmefile) |
| contents = fp.read() |
| # wax the last page, it contains Emacs cruft |
| i = contents.rfind('\f') |
| if i > 0: |
| contents = contents[:i].rstrip() |
| finally: |
| if fp: |
| fp.close() |
| except IOError: |
| sys.stderr.write("Couldn't open audiopy's README, " |
| 'using docstring instead.\n') |
| contents = __doc__ % globals() |
| |
| self.__text = text = Text(root, relief=SUNKEN, |
| width=80, height=24) |
| text.insert(0.0, contents) |
| scrollbar = Scrollbar(root) |
| scrollbar.pack(fill=Y, side=RIGHT) |
| text.pack(fill=BOTH, expand=YES) |
| text.configure(yscrollcommand=(scrollbar, 'set')) |
| scrollbar.configure(command=(text, 'yview')) |
| |
| def __withdraw(self, event=None): |
| self.__root.withdraw() |
| |
| def deiconify(self): |
| self.__root.deiconify() |
| |
| |
| |
| |
| def usage(code, msg=''): |
| print __doc__ % globals() |
| if msg: |
| print msg |
| sys.exit(code) |
| |
| |
| def main(): |
| # |
| # Open up the audio control device and query for the current output |
| # device |
| device = sunaudiodev.open('control') |
| |
| if len(sys.argv) == 1: |
| # GUI |
| w = MainWindow(device) |
| try: |
| w.start() |
| except KeyboardInterrupt: |
| pass |
| return |
| |
| # spec: LONG OPT, SHORT OPT, 0=input,1=output, MASK |
| options = [('--microphone', '-m', 0, MICROPHONE), |
| ('--linein', '-i', 0, LINE_IN), |
| ('--headphones', '-p', 1, HEADPHONE), |
| ('--speaker', '-s', 1, SPEAKER), |
| ('--lineout', '-o', 1, LINE_OUT), |
| ] |
| # See the comment above about `CD' |
| try: |
| options.append(('--cd', '-c', 0, CD)) |
| except NameError: |
| pass |
| |
| info = device.getinfo() |
| # first get the existing values |
| i = 0 |
| while i < len(sys.argv)-1: |
| i = i + 1 |
| arg = sys.argv[i] |
| if arg in ('-h', '--help'): |
| usage(0) |
| # does not return |
| elif arg in ('-g', '--gain'): |
| gainspec = '<missing>' |
| try: |
| gainspec = sys.argv[i+1] |
| gain = int(gainspec) |
| except (ValueError, IndexError): |
| usage(1, 'Bad gain specification: ' + gainspec) |
| info.o_gain = gain |
| i = i + 1 |
| continue |
| elif arg in ('-v', '--version'): |
| print '''\ |
| audiopy -- a program to control the Solaris audio device. |
| Contact: Barry Warsaw |
| Email: bwarsaw@python.org |
| Version: %s''' % __version__ |
| sys.exit(0) |
| for long, short, io, mask in options: |
| if arg in (long, short): |
| # toggle the option |
| if io == 0: |
| info.i_port = info.i_port ^ mask |
| else: |
| info.o_port = info.o_port ^ mask |
| break |
| val = None |
| try: |
| if arg[:len(long)+1] == long+'=': |
| val = int(arg[len(long)+1:]) |
| elif arg[:len(short)+1] == short+'=': |
| val = int(arg[len(short)+1:]) |
| except ValueError: |
| usage(1, msg='Invalid option: ' + arg) |
| # does not return |
| if val == 0: |
| if io == 0: |
| info.i_port = info.i_port & ~mask |
| else: |
| info.o_port = info.o_port & ~mask |
| break |
| elif val == 1: |
| if io == 0: |
| info.i_port = info.i_port | mask |
| else: |
| info.o_port = info.o_port | mask |
| break |
| # else keep trying next option |
| else: |
| usage(1, msg='Invalid option: ' + arg) |
| # now set the values |
| try: |
| device.setinfo(info) |
| except sunaudiodev.error as e: |
| (code, msg) = e |
| if code <> errno.EINVAL: |
| raise |
| device.close() |
| |
| |
| |
| if __name__ == '__main__': |
| main() |