2 * devinput - zavai /dev/input device handling
4 * Copyright (C) 2009 Enrico Zini <enrico@enricozini.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 public abstract class DevInput : zavai.Service
26 public string device { get; construct; }
28 public signal bool event(LinuxInput.Event* ev);
30 protected IOChannel fd = null;
31 protected uint fd_watch = 0;
33 protected void close_fd()
39 } catch (IOChannelError e) {
40 zavai.log.error("When closing " + device + ": " + e.message);
47 protected bool on_input_data(IOChannel source, IOCondition condition)
49 if (condition != IOCondition.IN) return true;
51 stderr.printf("GOT INPUT ON %s %d\n", device, source.unix_get_fd());
52 char[] buf = new char[sizeof(LinuxInput.Event)];
54 source.read_chars(buf, out count_read);
55 stderr.printf("READ %zu chars\n", count_read);
57 LinuxInput.Event* ie = (LinuxInput.Event*)buf;
58 stderr.printf("INPUT EVENT time %lu.%lu type %hu code %hu val %d\n", (ulong)ie->time.tv_sec, ie->time.tv_usec, ie->type, ie->code, ie->val);
61 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
62 #print ts1, ts2, type, code, value
63 if type == 1 and code == 119:
65 #print "BUTTON RELEASE"
68 if self.last_button_press + 1 < ts1:
69 self.last_button_press = ts1
70 if self.button_press_handler is not None:
71 self.button_press_handler()
73 elif type == 5 and code == 2:
75 info("Headset plugged in")
76 self.mixer_for_headset(self)
78 info("Headset plugged out")
79 self.mixer_for_handset(self)
84 /// Start reading from the device
85 public override void start()
92 // Open the device and listed to it using the GObject main loop
93 fd = new IOChannel.file(device, "r");
94 fd.set_encoding(null);
95 fd.set_buffered(false);
96 fd_watch = fd.add_watch(IOCondition.IN, on_input_data);
101 // Stop reading from the device
102 public override void stop()
104 if (!started) return;
108 Source.remove(fd_watch);
116 public class PowerButton : DevInput
118 public signal void power_button(bool pressed);
122 name = "input.power_button";
123 // FIXME: change to event0 for the power button
124 // FIXME: change to event4 for the aux button and headset button
125 //device = "/dev/input/event1";
126 device = "/dev/input/event0";
131 protected bool on_event(LinuxInput.Event* ev)
133 if (ev->type == LinuxInput.Type.KEY &&
134 ev->code == LinuxInput.Key.POWER)
136 power_button(ev->val == 0 ? false : true);
144 # - hook into the headset plugged/unplugged event
145 # - if unplugged, turn on handset microphone
146 # - if plugged, redo headest mixer settings
148 "Handle mixer settings, audio recording and headset button presses"
149 def __init__(self, button_press_handler = None):
150 self.saved_scenario = os.path.expanduser("~/.audiomap.state")
153 # Set mixer to record from headset and handle headset button
154 self.save_scenario(self.saved_scenario)
155 self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
157 # This is a work-around because I have not found a way to query for the
158 # current headset state, I can only know when it changes. So in my
159 # system I configured oeventsd with a rule to touch this file when the
160 # headset is plugged in, and remove the file when it's plugged out.
161 if os.path.exists("/tmp/has_headset"):
162 self.mixer_for_headset(True)
164 self.mixer_for_handset(True)
166 #self.mixer_set("DAPM Handset Mic", "mute")
167 #self.mixer_set("DAPM Headset Mic", "unmute")
168 #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
169 #self.mixer_set("ALC Mixer Mic1", "cap")
170 #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
172 # Watch the headset button
173 self.button_press_handler = button_press_handler
174 self.input_fd = open("/dev/input/event4", "rb")
175 self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
177 self.last_button_press = 0
181 def mixer_for_headset(self, force=False):
182 if not force and self.has_headset: return
183 info("Setting mixer for headset")
184 # TODO: find out how to disable the handset microphone: this does not
185 # seem to be sufficient
187 ("DAPM Handset Mic", "mute"),
188 ("DAPM Headset Mic", "unmute"),
189 ("Left Mixer Sidetone Playback Sw", "unmute"),
190 ("ALC Mixer Mic1", "cap"),
191 ("Amp Spk", "mute") # We don't need the phone playing what we say
193 self.has_headset = True
195 def mixer_for_handset(self, force=False):
196 if not force and not self.has_headset: return
197 info("Setting mixer for handset")
199 ("DAPM Handset Mic", "unmute"),
200 ("DAPM Headset Mic", "mute"),
201 ("Left Mixer Sidetone Playback Sw", "mute"),
202 ("ALC Mixer Mic1", "cap"),
203 ("Amp Spk", "mute") # We don't need the phone playing what we say
205 self.has_headset = False
207 def set_basename(self, basename):
208 self.basename = basename
210 def start_recording(self):
211 if self.basename is None:
212 raise RuntimeError("Recording requested but basename not set")
213 self.recorder = subprocess.Popen(
214 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", self.basename + ".wav"])
216 def start_levels(self):
217 self.recorder = subprocess.Popen(
218 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
221 if self.recorder is not None:
222 os.kill(self.recorder.pid, signal.SIGINT)
225 # Restore mixer settings
226 self.load_scenario(self.saved_scenario)
228 gobject.source_remove(self.input_watch)
229 self.input_fd.close()
231 def on_input_data(self, source, condition):
232 buf = self.input_fd.read(16)
233 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
234 #print ts1, ts2, type, code, value
235 if type == 1 and code == 119:
237 #print "BUTTON RELEASE"
240 if self.last_button_press + 1 < ts1:
241 self.last_button_press = ts1
242 if self.button_press_handler is not None:
243 self.button_press_handler()
244 #print "BUTTON PRESS"
245 elif type == 5 and code == 2:
247 info("Headset plugged in")
248 self.mixer_for_headset(self)
250 info("Headset plugged out")
251 self.mixer_for_handset(self)
254 def save_scenario(self, name):
255 res = subprocess.call(["alsactl", "store", "-f", name])
257 raise RuntimeError("Saving audio scenario to '%s' failed" % name)
259 def load_scenario(self, name):
260 res = subprocess.call(["alsactl", "restore", "-f", name])
262 raise RuntimeError("Loading audio scenario '%s' failed" % name)
264 def mixer_set(self, name, *args):
265 args = map(str, args)
266 res = subprocess.call(["amixer", "-q", "set", name] + args)
268 raise RuntimeError("Setting mixer '%s' to %s failed" % (name, " ".join(args)))
270 # Will do this when we find out the syntax for giving amixer commands on stdin
271 def mixer_set_many(self, *args):
272 """Perform many mixer set operations via amixer --stdin"""
273 proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
276 cmd_input.append("sset " + repr(k) + " " + repr(v))
277 (out, err) = proc.communicate(input="\n".join(cmd_input))
280 raise RuntimeError("Setting mixer failed")
283 public PowerButton power_button = null;
287 power_button = new PowerButton();
289 zavai.registry.register_service(power_button);