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)];
55 source.read_chars(buf, out count_read);
57 zavai.log.error("Reading from " + device + ": " + e.message);
60 //stderr.printf("READ %zu chars\n", count_read);
62 LinuxInput.Event* ie = (LinuxInput.Event*)buf;
63 //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);
66 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
67 #print ts1, ts2, type, code, value
68 if type == 1 and code == 119:
70 #print "BUTTON RELEASE"
73 if self.last_button_press + 1 < ts1:
74 self.last_button_press = ts1
75 if self.button_press_handler is not None:
76 self.button_press_handler()
78 elif type == 5 and code == 2:
80 info("Headset plugged in")
81 self.mixer_for_headset(self)
83 info("Headset plugged out")
84 self.mixer_for_handset(self)
89 /// Start reading from the device
90 public override void start()
97 // Open the device and listed to it using the GObject main loop
98 zavai.log.info("Opening device " + device);
99 fd = new IOChannel.file(device, "r");
101 fd.set_encoding(null);
103 zavai.log.error("Setting encoding to null on " + device + ": " + e.message);
105 fd.set_buffered(false);
106 fd_watch = fd.add_watch(IOCondition.IN, on_input_data);
111 // Stop reading from the device
112 public override void stop()
114 if (!started) return;
118 Source.remove(fd_watch);
126 public class PowerButton : DevInput
128 public signal void power_button(Posix.timeval* time, bool pressed);
132 // FIXME: change to event0 for the power button
133 // FIXME: change to event4 for the aux button and headset button
134 //device = "/dev/input/event1";
136 name: "input.power_button",
137 device: "/dev/input/event0"
143 protected bool on_event(LinuxInput.Event* ev)
145 if (ev->type == LinuxInput.Type.KEY &&
146 ev->code == LinuxInput.Key.POWER)
148 power_button(&(ev->time), ev->val == 0 ? false : true);
156 # - hook into the headset plugged/unplugged event
157 # - if unplugged, turn on handset microphone
158 # - if plugged, redo headest mixer settings
160 "Handle mixer settings, audio recording and headset button presses"
161 def __init__(self, button_press_handler = None):
162 self.saved_scenario = os.path.expanduser("~/.audiomap.state")
165 # Set mixer to record from headset and handle headset button
166 self.save_scenario(self.saved_scenario)
167 self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
169 # This is a work-around because I have not found a way to query for the
170 # current headset state, I can only know when it changes. So in my
171 # system I configured oeventsd with a rule to touch this file when the
172 # headset is plugged in, and remove the file when it's plugged out.
173 if os.path.exists("/tmp/has_headset"):
174 self.mixer_for_headset(True)
176 self.mixer_for_handset(True)
178 #self.mixer_set("DAPM Handset Mic", "mute")
179 #self.mixer_set("DAPM Headset Mic", "unmute")
180 #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
181 #self.mixer_set("ALC Mixer Mic1", "cap")
182 #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
184 # Watch the headset button
185 self.button_press_handler = button_press_handler
186 self.input_fd = open("/dev/input/event4", "rb")
187 self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
189 self.last_button_press = 0
193 def mixer_for_headset(self, force=False):
194 if not force and self.has_headset: return
195 info("Setting mixer for headset")
196 # TODO: find out how to disable the handset microphone: this does not
197 # seem to be sufficient
199 ("DAPM Handset Mic", "mute"),
200 ("DAPM Headset Mic", "unmute"),
201 ("Left Mixer Sidetone Playback Sw", "unmute"),
202 ("ALC Mixer Mic1", "cap"),
203 ("Amp Spk", "mute") # We don't need the phone playing what we say
205 self.has_headset = True
207 def mixer_for_handset(self, force=False):
208 if not force and not self.has_headset: return
209 info("Setting mixer for handset")
211 ("DAPM Handset Mic", "unmute"),
212 ("DAPM Headset Mic", "mute"),
213 ("Left Mixer Sidetone Playback Sw", "mute"),
214 ("ALC Mixer Mic1", "cap"),
215 ("Amp Spk", "mute") # We don't need the phone playing what we say
217 self.has_headset = False
219 def set_basename(self, basename):
220 self.basename = basename
222 def start_recording(self):
223 if self.basename is None:
224 raise RuntimeError("Recording requested but basename not set")
225 self.recorder = subprocess.Popen(
226 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", self.basename + ".wav"])
228 def start_levels(self):
229 self.recorder = subprocess.Popen(
230 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
233 if self.recorder is not None:
234 os.kill(self.recorder.pid, signal.SIGINT)
237 # Restore mixer settings
238 self.load_scenario(self.saved_scenario)
240 gobject.source_remove(self.input_watch)
241 self.input_fd.close()
243 def on_input_data(self, source, condition):
244 buf = self.input_fd.read(16)
245 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
246 #print ts1, ts2, type, code, value
247 if type == 1 and code == 119:
249 #print "BUTTON RELEASE"
252 if self.last_button_press + 1 < ts1:
253 self.last_button_press = ts1
254 if self.button_press_handler is not None:
255 self.button_press_handler()
256 #print "BUTTON PRESS"
257 elif type == 5 and code == 2:
259 info("Headset plugged in")
260 self.mixer_for_headset(self)
262 info("Headset plugged out")
263 self.mixer_for_handset(self)
266 def save_scenario(self, name):
267 res = subprocess.call(["alsactl", "store", "-f", name])
269 raise RuntimeError("Saving audio scenario to '%s' failed" % name)
271 def load_scenario(self, name):
272 res = subprocess.call(["alsactl", "restore", "-f", name])
274 raise RuntimeError("Loading audio scenario '%s' failed" % name)
276 def mixer_set(self, name, *args):
277 args = map(str, args)
278 res = subprocess.call(["amixer", "-q", "set", name] + args)
280 raise RuntimeError("Setting mixer '%s' to %s failed" % (name, " ".join(args)))
282 # Will do this when we find out the syntax for giving amixer commands on stdin
283 def mixer_set_many(self, *args):
284 """Perform many mixer set operations via amixer --stdin"""
285 proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
288 cmd_input.append("sset " + repr(k) + " " + repr(v))
289 (out, err) = proc.communicate(input="\n".join(cmd_input))
292 raise RuntimeError("Setting mixer failed")
296 public PowerButton power_button = null;
300 power_button = new PowerButton();