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 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 public DevInput(string name, string device)
35 Object(name: "input.power_button", device: "/dev/input/event0");
38 protected void close_fd()
44 } catch (IOChannelError e) {
45 zavai.log.error("When closing " + device + ": " + e.message);
52 protected bool on_input_data(IOChannel source, IOCondition condition)
54 if (condition != IOCondition.IN) return true;
56 //stderr.printf("GOT INPUT ON %s %d\n", device, source.unix_get_fd());
57 char[] buf = new char[sizeof(LinuxInput.Event)];
60 source.read_chars(buf, out count_read);
62 zavai.log.error("Reading from " + device + ": " + e.message);
65 //stderr.printf("READ %zu chars\n", count_read);
67 LinuxInput.Event* ie = (LinuxInput.Event*)buf;
68 //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);
71 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
72 #print ts1, ts2, type, code, value
73 if type == 1 and code == 119:
75 #print "BUTTON RELEASE"
78 if self.last_button_press + 1 < ts1:
79 self.last_button_press = ts1
80 if self.button_press_handler is not None:
81 self.button_press_handler()
83 elif type == 5 and code == 2:
85 info("Headset plugged in")
86 self.mixer_for_headset(self)
88 info("Headset plugged out")
89 self.mixer_for_handset(self)
94 /// Start reading from the device
95 public override void start()
102 // Open the device and listed to it using the GObject main loop
103 zavai.log.info("Opening device " + device);
104 fd = new IOChannel.file(device, "r");
106 fd.set_encoding(null);
108 zavai.log.error("Setting encoding to null on " + device + ": " + e.message);
110 fd.set_buffered(false);
111 fd_watch = fd.add_watch(IOCondition.IN, on_input_data);
116 // Stop reading from the device
117 public override void stop()
119 if (!started) return;
123 Source.remove(fd_watch);
131 public class HotKeys : zavai.Service
133 protected List<int> grabbed;
134 public signal bool hotkey(uint keycode, ulong time, bool pressed);
138 Object(name: "input.hotkeys");
140 grabbed = new List<int>();
143 // Hotkey handlink takes inspiration from
144 // http://old.nabble.com/-PATCH--Initial-non-working-implementation-of-wxWindow::%28Un%29RegisterHotKey-on-wxGTK-td14557263.html
145 private static Gdk.FilterReturn on_hotkey(Gdk.XEvent* xevent, Gdk.Event? event, void* data)
147 // Global events don't get their data translated to gdk, as there is no
148 // GdkWindow to work with, therefore we need to use the xevent, because
149 // GdkEvent* is always GDK_NOTHING
150 X.Event* xev = (X.Event*)xevent;
154 case X.EventType.KeyPress:
155 return zavai.input.hotkeys.my_on_hotkey(xev, true);
156 case X.EventType.KeyRelease:
157 return zavai.input.hotkeys.my_on_hotkey(xev, false);
159 return Gdk.FilterReturn.CONTINUE;
163 //public Gdk.FilterReturn on_hotkey(Gdk.XEvent xevent, Gdk.Event? event)
164 private Gdk.FilterReturn my_on_hotkey(X.Event* xev, bool pressed)
166 // From http://tronche.com/gui/x/xlib/input/pointer-grabbing.html:
168 // A timestamp is a time value, expressed in milliseconds. It typically is the
169 // time since the last server reset. Timestamp values wrap around (after about
170 // 49.7 days). The server, given its current time is represented by timestamp
171 // T, always interprets timestamps from clients by treating half of the
172 // timestamp space as being later in time than T. One timestamp value, named
173 // CurrentTime, is never generated by the server. This value is reserved for
174 // use in requests to represent the current server time.
176 if (grabbed.index((int)xev->xkey.keycode) == -1)
177 return Gdk.FilterReturn.CONTINUE;
179 if (hotkey(xev->xkey.keycode, xev->xkey.time, pressed))
180 return Gdk.FilterReturn.REMOVE;
181 return Gdk.FilterReturn.CONTINUE;
184 public void grab(int keycode, int modifiers, bool owner_events)
186 // We need to grab the keys we want to listen to
187 int res = Gdk.x11_get_default_xdisplay().grab_key(keycode, modifiers, Gdk.x11_get_default_root_xwindow(), owner_events, X.GrabMode.Async, X.GrabMode.Async);
189 stderr.printf("Grab result: %d\n", res); // We get BadRequest and don't know why
190 grabbed.append(keycode);
193 /// Start reading from the device
194 public override void start()
198 //gdk_window_add_filter (NULL, _wxgtk_global_hotkey_callback, this);
199 ((Gdk.Window*)null)->add_filter((Gdk.FilterFunc)on_hotkey);
206 // Stop reading from the device
207 public override void stop()
209 if (!started) return;
211 //gdk_window_remove_filter(NULL, _wxgtk_global_hotkey_callback, this);
212 ((Gdk.Window*)null)->remove_filter((Gdk.FilterFunc)on_hotkey);
218 public class PowerButton : zavai.Service
220 protected DevInput devinput;
222 public signal void power_button(Posix.timeval* time, bool pressed);
226 // FIXME: change to event0 for the power button
227 // FIXME: change to event4 for the aux button and headset button
228 string inputdev = "/dev/input/event0";
229 if (Posix.access(inputdev, Posix.R_OK) == 0)
231 zavai.log.info("Handle power button via " + inputdev);
232 // Listen via input device
233 devinput = new DevInput("input.power_button", "/dev/input/event0");
234 devinput.event += on_event;
235 devinput.request("powerbutton");
237 zavai.log.info("Handle power button via XGrabKey on keycode " + zavai.config.power_button_keycode.to_string());
239 hotkeys.hotkey += on_hotkey;
240 hotkeys.grab(zavai.config.power_button_keycode, 0, false);
241 hotkeys.request("powerbutton");
245 protected bool on_event(LinuxInput.Event* ev)
247 if (ev->type == LinuxInput.Type.KEY &&
248 ev->code == LinuxInput.Key.POWER)
250 power_button(&(ev->time), ev->val == 0 ? false : true);
255 protected bool on_hotkey(uint keycode, ulong time, bool pressed)
257 if (keycode == zavai.config.power_button_keycode)
259 // Convert X time to a fake timeval
260 // TODO: handle wraparound
262 (time_t)(time / 1000),
263 (long)((time % 1000) * 1000)
265 power_button(&tv, pressed);
275 # - hook into the headset plugged/unplugged event
276 # - if unplugged, turn on handset microphone
277 # - if plugged, redo headest mixer settings
279 "Handle mixer settings, audio recording and headset button presses"
280 def __init__(self, button_press_handler = None):
281 self.saved_scenario = os.path.expanduser("~/.audiomap.state")
284 # Set mixer to record from headset and handle headset button
285 self.save_scenario(self.saved_scenario)
286 self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
288 # This is a work-around because I have not found a way to query for the
289 # current headset state, I can only know when it changes. So in my
290 # system I configured oeventsd with a rule to touch this file when the
291 # headset is plugged in, and remove the file when it's plugged out.
292 if os.path.exists("/tmp/has_headset"):
293 self.mixer_for_headset(True)
295 self.mixer_for_handset(True)
297 #self.mixer_set("DAPM Handset Mic", "mute")
298 #self.mixer_set("DAPM Headset Mic", "unmute")
299 #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
300 #self.mixer_set("ALC Mixer Mic1", "cap")
301 #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
303 # Watch the headset button
304 self.button_press_handler = button_press_handler
305 self.input_fd = open("/dev/input/event4", "rb")
306 self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
308 self.last_button_press = 0
312 def mixer_for_headset(self, force=False):
313 if not force and self.has_headset: return
314 info("Setting mixer for headset")
315 # TODO: find out how to disable the handset microphone: this does not
316 # seem to be sufficient
318 ("DAPM Handset Mic", "mute"),
319 ("DAPM Headset Mic", "unmute"),
320 ("Left Mixer Sidetone Playback Sw", "unmute"),
321 ("ALC Mixer Mic1", "cap"),
322 ("Amp Spk", "mute") # We don't need the phone playing what we say
324 self.has_headset = True
326 def mixer_for_handset(self, force=False):
327 if not force and not self.has_headset: return
328 info("Setting mixer for handset")
330 ("DAPM Handset Mic", "unmute"),
331 ("DAPM Headset Mic", "mute"),
332 ("Left Mixer Sidetone Playback Sw", "mute"),
333 ("ALC Mixer Mic1", "cap"),
334 ("Amp Spk", "mute") # We don't need the phone playing what we say
336 self.has_headset = False
338 def set_basename(self, basename):
339 self.basename = basename
341 def start_recording(self):
342 if self.basename is None:
343 raise RuntimeError("Recording requested but basename not set")
344 self.recorder = subprocess.Popen(
345 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", self.basename + ".wav"])
347 def start_levels(self):
348 self.recorder = subprocess.Popen(
349 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
352 if self.recorder is not None:
353 os.kill(self.recorder.pid, signal.SIGINT)
356 # Restore mixer settings
357 self.load_scenario(self.saved_scenario)
359 gobject.source_remove(self.input_watch)
360 self.input_fd.close()
362 def on_input_data(self, source, condition):
363 buf = self.input_fd.read(16)
364 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
365 #print ts1, ts2, type, code, value
366 if type == 1 and code == 119:
368 #print "BUTTON RELEASE"
371 if self.last_button_press + 1 < ts1:
372 self.last_button_press = ts1
373 if self.button_press_handler is not None:
374 self.button_press_handler()
375 #print "BUTTON PRESS"
376 elif type == 5 and code == 2:
378 info("Headset plugged in")
379 self.mixer_for_headset(self)
381 info("Headset plugged out")
382 self.mixer_for_handset(self)
385 def save_scenario(self, name):
386 res = subprocess.call(["alsactl", "store", "-f", name])
388 raise RuntimeError("Saving audio scenario to '%s' failed" % name)
390 def load_scenario(self, name):
391 res = subprocess.call(["alsactl", "restore", "-f", name])
393 raise RuntimeError("Loading audio scenario '%s' failed" % name)
395 def mixer_set(self, name, *args):
396 args = map(str, args)
397 res = subprocess.call(["amixer", "-q", "set", name] + args)
399 raise RuntimeError("Setting mixer '%s' to %s failed" % (name, " ".join(args)))
401 # Will do this when we find out the syntax for giving amixer commands on stdin
402 def mixer_set_many(self, *args):
403 """Perform many mixer set operations via amixer --stdin"""
404 proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
407 cmd_input.append("sset " + repr(k) + " " + repr(v))
408 (out, err) = proc.communicate(input="\n".join(cmd_input))
411 raise RuntimeError("Setting mixer failed")
415 public HotKeys hotkeys = null;
416 public PowerButton power_button = null;
420 hotkeys = new HotKeys();
421 power_button = new PowerButton();