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 zavai.log.info("Opening device " + device);
94 fd = new IOChannel.file(device, "r");
95 fd.set_encoding(null);
96 fd.set_buffered(false);
97 fd_watch = fd.add_watch(IOCondition.IN, on_input_data);
102 // Stop reading from the device
103 public override void stop()
105 if (!started) return;
109 Source.remove(fd_watch);
117 public class PowerButton : DevInput
119 public signal void power_button(Posix.timeval* time, bool pressed);
123 name = "input.power_button";
124 // FIXME: change to event0 for the power button
125 // FIXME: change to event4 for the aux button and headset button
126 //device = "/dev/input/event1";
127 device = "/dev/input/event0";
130 power_button += on_power_button;
133 protected bool on_event(LinuxInput.Event* ev)
135 if (ev->type == LinuxInput.Type.KEY &&
136 ev->code == LinuxInput.Key.POWER)
138 power_button(&(ev->time), ev->val == 0 ? false : true);
143 protected void on_power_button(Posix.timeval* time, bool pressed)
147 zavai.app.push_applet("menu.power");
154 # - hook into the headset plugged/unplugged event
155 # - if unplugged, turn on handset microphone
156 # - if plugged, redo headest mixer settings
158 "Handle mixer settings, audio recording and headset button presses"
159 def __init__(self, button_press_handler = None):
160 self.saved_scenario = os.path.expanduser("~/.audiomap.state")
163 # Set mixer to record from headset and handle headset button
164 self.save_scenario(self.saved_scenario)
165 self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
167 # This is a work-around because I have not found a way to query for the
168 # current headset state, I can only know when it changes. So in my
169 # system I configured oeventsd with a rule to touch this file when the
170 # headset is plugged in, and remove the file when it's plugged out.
171 if os.path.exists("/tmp/has_headset"):
172 self.mixer_for_headset(True)
174 self.mixer_for_handset(True)
176 #self.mixer_set("DAPM Handset Mic", "mute")
177 #self.mixer_set("DAPM Headset Mic", "unmute")
178 #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
179 #self.mixer_set("ALC Mixer Mic1", "cap")
180 #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
182 # Watch the headset button
183 self.button_press_handler = button_press_handler
184 self.input_fd = open("/dev/input/event4", "rb")
185 self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
187 self.last_button_press = 0
191 def mixer_for_headset(self, force=False):
192 if not force and self.has_headset: return
193 info("Setting mixer for headset")
194 # TODO: find out how to disable the handset microphone: this does not
195 # seem to be sufficient
197 ("DAPM Handset Mic", "mute"),
198 ("DAPM Headset Mic", "unmute"),
199 ("Left Mixer Sidetone Playback Sw", "unmute"),
200 ("ALC Mixer Mic1", "cap"),
201 ("Amp Spk", "mute") # We don't need the phone playing what we say
203 self.has_headset = True
205 def mixer_for_handset(self, force=False):
206 if not force and not self.has_headset: return
207 info("Setting mixer for handset")
209 ("DAPM Handset Mic", "unmute"),
210 ("DAPM Headset Mic", "mute"),
211 ("Left Mixer Sidetone Playback Sw", "mute"),
212 ("ALC Mixer Mic1", "cap"),
213 ("Amp Spk", "mute") # We don't need the phone playing what we say
215 self.has_headset = False
217 def set_basename(self, basename):
218 self.basename = basename
220 def start_recording(self):
221 if self.basename is None:
222 raise RuntimeError("Recording requested but basename not set")
223 self.recorder = subprocess.Popen(
224 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", self.basename + ".wav"])
226 def start_levels(self):
227 self.recorder = subprocess.Popen(
228 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
231 if self.recorder is not None:
232 os.kill(self.recorder.pid, signal.SIGINT)
235 # Restore mixer settings
236 self.load_scenario(self.saved_scenario)
238 gobject.source_remove(self.input_watch)
239 self.input_fd.close()
241 def on_input_data(self, source, condition):
242 buf = self.input_fd.read(16)
243 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
244 #print ts1, ts2, type, code, value
245 if type == 1 and code == 119:
247 #print "BUTTON RELEASE"
250 if self.last_button_press + 1 < ts1:
251 self.last_button_press = ts1
252 if self.button_press_handler is not None:
253 self.button_press_handler()
254 #print "BUTTON PRESS"
255 elif type == 5 and code == 2:
257 info("Headset plugged in")
258 self.mixer_for_headset(self)
260 info("Headset plugged out")
261 self.mixer_for_handset(self)
264 def save_scenario(self, name):
265 res = subprocess.call(["alsactl", "store", "-f", name])
267 raise RuntimeError("Saving audio scenario to '%s' failed" % name)
269 def load_scenario(self, name):
270 res = subprocess.call(["alsactl", "restore", "-f", name])
272 raise RuntimeError("Loading audio scenario '%s' failed" % name)
274 def mixer_set(self, name, *args):
275 args = map(str, args)
276 res = subprocess.call(["amixer", "-q", "set", name] + args)
278 raise RuntimeError("Setting mixer '%s' to %s failed" % (name, " ".join(args)))
280 # Will do this when we find out the syntax for giving amixer commands on stdin
281 def mixer_set_many(self, *args):
282 """Perform many mixer set operations via amixer --stdin"""
283 proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
286 cmd_input.append("sset " + repr(k) + " " + repr(v))
287 (out, err) = proc.communicate(input="\n".join(cmd_input))
290 raise RuntimeError("Setting mixer failed")
292 public class CommandButton : Gtk.Button
294 private string command;
296 public CommandButton(string name, string command)
299 this.command = command;
300 clicked += on_clicked;
301 set_size_request(0, zavai.config.min_button_height);
304 public void on_clicked()
306 zavai.log.info("Run program: " + command);
307 string[] args = command.split(" ");
310 Environment.get_home_dir(),
313 SpawnFlags.SEARCH_PATH,
320 public PowerButton power_button = null;
324 power_button = new PowerButton();
326 zavai.registry.register_service(power_button);
329 public void ui_init()
332 var menu_power = new zavai.Menu("Power menu");
333 menu_power.add_widget(new CommandButton("Suspend", "apm -s"));
334 menu_power.add_widget(new CommandButton("Shutdown", "shutdown -h now"));
335 menu_power.add_widget(new CommandButton("Reboot", "shutdown -r now"));
336 zavai.registry.register_menu("menu.power", menu_power);
338 zavai.registry.getmenu("menu.main").add_applet("menu.power");