Class around PixMap objects that allows more python-like access. By Joe Strout.
[python/dscho.git] / Tools / audiopy / audiopy
blob12994ab60994bbdee1468c933f0c2776963ac1a7
1 #! /usr/bin/env python
3 """audiopy -- a program to control the Solaris audio device.
5 Contact: Barry Warsaw
6 Email: bwarsaw@python.org
7 Version: %(__version__)s
9 When no arguments are given, this pops up a graphical window which lets you
10 choose the audio input and output devices.
12 This program can be driven via the command line, and when done so, no window
13 pops up. Options have the general form:
15 --device[={0,1}]
16 -d[={0,1}]
17 Set the I/O device. With no value, it toggles the specified device.
18 With a value, 0 turns the device off and 1 turns the device on.
20 The list of devices and their short options are:
22 (input)
23 microphone -- m
24 linein -- i
25 cd -- c
27 (output)
28 headphones -- p
29 speaker -- s
30 lineout -- o
32 Other options are:
34 --version
36 Print the version number and exit.
38 --help
40 Print this message and exit.
41 """
43 import sys
44 import os
45 import string
46 import sunaudiodev
47 from SUNAUDIODEV import *
49 # Milliseconds between interrupt checks
50 KEEPALIVE_TIMER = 500
52 __version__ = '0.1'
56 class MainWindow:
57 def __init__(self, device):
58 from Tkinter import *
59 self.__helpwin = None
60 self.__devctl = device
61 info = device.getinfo()
63 self.__tkroot = tkroot = Tk(className='Audiopy')
64 tkroot.withdraw()
65 # create the menubar
66 menubar = Menu(tkroot)
67 filemenu = Menu(menubar, tearoff=0)
68 filemenu.add_command(label='Quit',
69 command=self.__quit,
70 accelerator='Alt-Q',
71 underline=0)
72 helpmenu = Menu(menubar, name='help', tearoff=0)
73 helpmenu.add_command(label='About Audiopy...',
74 command=self.__popup_about,
75 underline=0)
76 helpmenu.add_command(label='Help...',
77 command=self.__popup_using,
78 underline=0)
79 menubar.add_cascade(label='File',
80 menu=filemenu,
81 underline=0)
82 menubar.add_cascade(label='Help',
83 menu=helpmenu,
84 underline=0)
85 # now create the top level window
86 root = self.__root = Toplevel(tkroot, class_='Audiopy', menu=menubar)
87 root.protocol('WM_DELETE_WINDOW', self.__quit)
88 root.title('audiopy ' + __version__)
89 root.iconname('audiopy ' + __version__)
90 root.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
92 buttons = []
94 # where does input come from?
95 frame = Frame(root, bd=1, relief=RAISED)
96 frame.grid(row=1, column=0, sticky='NSEW')
97 label = Label(frame, text='Input From:')
98 label.grid(row=0, column=0, sticky=E)
99 self.__inputvar = IntVar()
101 btn = Radiobutton(frame,
102 text='None',
103 variable=self.__inputvar,
104 value=0,
105 command=self.__pushtodev,
106 underline=0)
107 btn.grid(row=0, column=1, sticky=W)
108 root.bind('<Alt-n>', self.__none)
109 root.bind('<Alt-N>', self.__none)
110 if not info.i_avail_ports & MICROPHONE:
111 btn.configure(state=DISABLED)
112 buttons.append(btn)
114 btn = Radiobutton(frame,
115 text='Microphone',
116 variable=self.__inputvar,
117 value=MICROPHONE,
118 command=self.__pushtodev,
119 underline=0)
120 btn.grid(row=1, column=1, sticky=W)
121 root.bind('<Alt-m>', self.__mic)
122 root.bind('<Alt-M>', self.__mic)
123 if not info.i_avail_ports & MICROPHONE:
124 btn.configure(state=DISABLED)
125 buttons.append(btn)
127 btn = Radiobutton(frame,
128 text='Line In',
129 variable=self.__inputvar,
130 value=LINE_IN,
131 command=self.__pushtodev,
132 underline=5)
133 btn.grid(row=2, column=1, sticky=W)
134 root.bind('<Alt-i>', self.__linein)
135 root.bind('<Alt-I>', self.__linein)
136 if not info.i_avail_ports & LINE_IN:
137 btn.configure(state=DISABLED)
138 buttons.append(btn)
139 ## if SUNAUDIODEV was built on an older version of Solaris, the CD
140 ## input device won't exist
141 try:
142 btn = Radiobutton(frame,
143 text='CD',
144 variable=self.__inputvar,
145 value=CD,
146 command=self.__pushtodev,
147 underline=0)
148 btn.grid(row=3, column=1, sticky=W)
149 root.bind('<Alt-c>', self.__cd)
150 root.bind('<Alt-C>', self.__cd)
151 if not info.i_avail_ports & CD:
152 btn.configure(state=DISABLED)
153 buttons.append(btn)
154 except NameError:
155 pass
157 # where does output go to?
158 frame = Frame(root, bd=1, relief=RAISED)
159 frame.grid(row=2, column=0, sticky='NSEW')
160 label = Label(frame, text='Output To:')
161 label.grid(row=0, column=0, sticky=E)
162 self.__spkvar = IntVar()
163 btn = Checkbutton(frame,
164 text='Speaker',
165 variable=self.__spkvar,
166 onvalue=SPEAKER,
167 command=self.__pushtodev,
168 underline=0)
169 btn.grid(row=0, column=1, sticky=W)
170 root.bind('<Alt-s>', self.__speaker)
171 root.bind('<Alt-S>', self.__speaker)
172 if not info.o_avail_ports & SPEAKER:
173 btn.configure(state=DISABLED)
174 buttons.append(btn)
176 self.__headvar = IntVar()
177 btn = Checkbutton(frame,
178 text='Headphones',
179 variable=self.__headvar,
180 onvalue=HEADPHONE,
181 command=self.__pushtodev,
182 underline=4)
183 btn.grid(row=1, column=1, sticky=W)
184 root.bind('<Alt-p>', self.__headphones)
185 root.bind('<Alt-P>', self.__headphones)
186 if not info.o_avail_ports & HEADPHONE:
187 btn.configure(state=DISABLED)
188 buttons.append(btn)
190 self.__linevar = IntVar()
191 btn = Checkbutton(frame,
192 variable=self.__linevar,
193 onvalue=LINE_OUT,
194 text='Line Out',
195 command=self.__pushtodev,
196 underline=0)
197 btn.grid(row=2, column=1, sticky=W)
198 root.bind('<Alt-l>', self.__lineout)
199 root.bind('<Alt-L>', self.__lineout)
200 if not info.o_avail_ports & LINE_OUT:
201 btn.configure(state=DISABLED)
202 buttons.append(btn)
204 # Fix up widths
205 widest = 0
206 for b in buttons:
207 width = b['width']
208 if width > widest:
209 widest = width
210 for b in buttons:
211 b.configure(width=widest)
212 # root bindings
213 root.bind('<Alt-q>', self.__quit)
214 root.bind('<Alt-Q>', self.__quit)
216 # do we need to poll for changes?
217 self.__needtopoll = 1
218 try:
219 fd = self.__devctl.fileno()
220 self.__needtopoll = 0
221 except AttributeError:
222 pass
223 else:
224 import fcntl
225 import signal
226 import STROPTS
227 # set up the signal handler
228 signal.signal(signal.SIGPOLL, self.__update)
229 fcntl.ioctl(fd, STROPTS.I_SETSIG, STROPTS.S_MSG)
230 self.__update()
232 def __quit(self, event=None):
233 self.__devctl.close()
234 self.__root.quit()
236 def __popup_about(self, event=None):
237 import tkMessageBox
238 tkMessageBox.showinfo('About Audiopy ' + __version__,
239 '''\
240 Audiopy %s
241 Control the Solaris audio device
243 For information
244 Contact: Barry A. Warsaw
245 Email: bwarsaw@python.org''' % __version__)
247 def __popup_using(self, event=None):
248 if not self.__helpwin:
249 self.__helpwin = Helpwin(self.__tkroot, self.__quit)
250 self.__helpwin.deiconify()
253 def __keepalive(self):
254 # Exercise the Python interpreter regularly so keyboard interrupts get
255 # through.
256 self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
257 if self.__needtopoll:
258 self.__update()
260 def __update(self, num=None, frame=None):
261 # We have to poll because the device could have changed state and the
262 # underlying module does not support the SIGPOLL notification
263 # interface.
264 info = self.__devctl.getinfo()
265 # input
266 self.__inputvar.set(info.i_port)
267 # output
268 self.__spkvar.set(info.o_port & SPEAKER)
269 self.__headvar.set(info.o_port & HEADPHONE)
270 self.__linevar.set(info.o_port & LINE_OUT)
272 def __pushtodev(self, event=None):
273 info = self.__devctl.getinfo()
274 info.o_port = self.__spkvar.get() + \
275 self.__headvar.get() + \
276 self.__linevar.get()
277 info.i_port = self.__inputvar.get()
278 self.__devctl.setinfo(info)
280 def __getset(self, var, onvalue):
281 if var.get() == onvalue:
282 var.set(0)
283 else:
284 var.set(onvalue)
285 self.__pushtodev()
287 def __none(self, event=None):
288 self.__inputvar.set(0)
289 self.__pushtodev()
291 def __mic(self, event=None):
292 self.__getset(self.__inputvar, MICROPHONE)
294 def __linein(self, event=None):
295 self.__getset(self.__inputvar, LINE_IN)
297 def __cd(self, event=None):
298 self.__getset(self.__inputvar, CD)
300 def __speaker(self, event=None):
301 self.__getset(self.__spkvar, SPEAKER)
303 def __headphones(self, event=None):
304 self.__getset(self.__headvar, HEADPHONE)
306 def __lineout(self, event=None):
307 self.__getset(self.__linevar, LINE_OUT)
309 def start(self):
310 self.__keepalive()
311 self.__tkroot.mainloop()
315 class Helpwin:
316 def __init__(self, master, quitfunc):
317 from Tkinter import *
318 self.__root = root = Toplevel(master, class_='Audiopy')
319 root.protocol('WM_DELETE_WINDOW', self.__withdraw)
320 root.title('Audiopy Help Window')
321 root.iconname('Audiopy Help Window')
322 root.bind('<Alt-q>', quitfunc)
323 root.bind('<Alt-Q>', quitfunc)
324 root.bind('<Alt-w>', self.__withdraw)
325 root.bind('<Alt-W>', self.__withdraw)
327 # more elaborate help is available in the README file
328 readmefile = os.path.join(sys.path[0], 'README')
329 try:
330 fp = None
331 try:
332 fp = open(readmefile)
333 contents = fp.read()
334 # wax the last page, it contains Emacs cruft
335 i = string.rfind(contents, '\f')
336 if i > 0:
337 contents = string.rstrip(contents[:i])
338 finally:
339 if fp:
340 fp.close()
341 except IOError:
342 sys.stderr.write("Couldn't open audiopy's README, "
343 'using docstring instead.\n')
344 contents = __doc__ % globals()
346 self.__text = text = Text(root, relief=SUNKEN,
347 width=80, height=24)
348 text.insert(0.0, contents)
349 scrollbar = Scrollbar(root)
350 scrollbar.pack(fill=Y, side=RIGHT)
351 text.pack(fill=BOTH, expand=YES)
352 text.configure(yscrollcommand=(scrollbar, 'set'))
353 scrollbar.configure(command=(text, 'yview'))
355 def __withdraw(self, event=None):
356 self.__root.withdraw()
358 def deiconify(self):
359 self.__root.deiconify()
364 def usage(msg='', code=1):
365 print __doc__ % globals()
366 if msg:
367 print msg
368 sys.exit(code)
371 def main():
373 # Open up the audio control device and query for the current output
374 # device
375 device = sunaudiodev.open('control')
377 if len(sys.argv) == 1:
378 # GUI
379 w = MainWindow(device)
380 try:
381 w.start()
382 except KeyboardInterrupt:
383 pass
384 return
386 # spec: LONG OPT, SHORT OPT, 0=input,1=output, MASK
387 options = [('--microphone', '-m', 0, MICROPHONE),
388 ('--linein', '-i', 0, LINE_IN),
389 ('--headphones', '-p', 1, HEADPHONE),
390 ('--speaker', '-s', 1, SPEAKER),
391 ('--lineout', '-o', 1, LINE_OUT),
393 # See the comment above about `CD'
394 try:
395 options.append(('--cd', '-c', 0, CD))
396 except NameError:
397 pass
399 info = device.getinfo()
400 # first get the existing values
401 for arg in sys.argv[1:]:
402 if arg in ('-h', '--help'):
403 usage(code=0)
404 # does not return
405 elif arg in ('-v', '--version'):
406 print '''\
407 audiopy -- a program to control the Solaris audio device.
408 Contact: Barry Warsaw
409 Email: bwarsaw@python.org
410 Version: %s''' % __version__
411 sys.exit(0)
412 for long, short, io, mask in options:
413 if arg in (long, short):
414 # toggle the option
415 if io == 0:
416 info.i_port = info.i_port ^ mask
417 else:
418 info.o_port = info.o_port ^ mask
419 break
420 val = None
421 try:
422 if arg[:len(long)+1] == long+'=':
423 val = int(arg[len(long)+1:])
424 elif arg[:len(short)+1] == short+'=':
425 val = int(arg[len(short)+1:])
426 except ValueError:
427 usage(msg='Invalid option: ' + arg)
428 # does not return
429 if val == 0:
430 if io == 0:
431 info.i_port = info.i_port & ~mask
432 else:
433 info.o_port = info.o_port & ~mask
434 break
435 elif val == 1:
436 if io == 0:
437 info.i_port = info.i_port | mask
438 else:
439 info.o_port = info.o_port | mask
440 break
441 # else keep trying next option
442 else:
443 usage(msg='Invalid option: ' + arg)
444 # now set the values
445 device.setinfo(info)
446 device.close()
450 if __name__ == '__main__':
451 main()