508 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			508 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #! /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, 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, (code, msg):
 | ||
|         if code <> errno.EINVAL:
 | ||
|             raise
 | ||
|     device.close()
 | ||
|             
 | ||
| 
 | ||
| 
 | ||
| if __name__ == '__main__':
 | ||
|     main()
 |