/* * devinput - zavai /dev/input device handling * * Copyright (C) 2009 Enrico Zini * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ namespace zavai { namespace input { public abstract class DevInput : zavai.Service { public string device { get; construct; } public signal bool event(LinuxInput.Event* ev); protected IOChannel fd = null; protected uint fd_watch = 0; protected void close_fd() { if (fd != null) { try { fd.shutdown(false); } catch (IOChannelError e) { zavai.log.error("When closing " + device + ": " + e.message); } fd = null; } } protected bool on_input_data(IOChannel source, IOCondition condition) { if (condition != IOCondition.IN) return true; stderr.printf("GOT INPUT ON %s %d\n", device, source.unix_get_fd()); char[] buf = new char[sizeof(LinuxInput.Event)]; size_t count_read; source.read_chars(buf, out count_read); stderr.printf("READ %zu chars\n", count_read); LinuxInput.Event* ie = (LinuxInput.Event*)buf; 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); /* ts1, ts2, type, code, value = struct.unpack("LLHHI", buf) #print ts1, ts2, type, code, value if type == 1 and code == 119: if value: #print "BUTTON RELEASE" pass else: if self.last_button_press + 1 < ts1: self.last_button_press = ts1 if self.button_press_handler is not None: self.button_press_handler() #print "BUTTON PRESS" elif type == 5 and code == 2: if value: info("Headset plugged in") self.mixer_for_headset(self) else: info("Headset plugged out") self.mixer_for_handset(self) */ return event(ie); } /// Start reading from the device public override void start() { if (started) return; if (fd != null) close_fd(); // Open the device and listed to it using the GObject main loop zavai.log.info("Opening device " + device); fd = new IOChannel.file(device, "r"); fd.set_encoding(null); fd.set_buffered(false); fd_watch = fd.add_watch(IOCondition.IN, on_input_data); base.start(); } // Stop reading from the device public override void stop() { if (!started) return; if (fd != null) { Source.remove(fd_watch); close_fd(); } base.stop(); } } public class PowerButton : DevInput { public signal void power_button(Posix.timeval* time, bool pressed); public PowerButton() { name = "input.power_button"; // FIXME: change to event0 for the power button // FIXME: change to event4 for the aux button and headset button //device = "/dev/input/event1"; device = "/dev/input/event0"; event += on_event; power_button += on_power_button; } protected bool on_event(LinuxInput.Event* ev) { if (ev->type == LinuxInput.Type.KEY && ev->code == LinuxInput.Key.POWER) { power_button(&(ev->time), ev->val == 0 ? false : true); } return true; } protected void on_power_button(Posix.timeval* time, bool pressed) { if (!pressed) { zavai.app.push_applet("menu.power"); } } } /* # TODO: # - hook into the headset plugged/unplugged event # - if unplugged, turn on handset microphone # - if plugged, redo headest mixer settings class Audio: "Handle mixer settings, audio recording and headset button presses" def __init__(self, button_press_handler = None): self.saved_scenario = os.path.expanduser("~/.audiomap.state") # Setup the mixer # Set mixer to record from headset and handle headset button self.save_scenario(self.saved_scenario) self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state") # This is a work-around because I have not found a way to query for the # current headset state, I can only know when it changes. So in my # system I configured oeventsd with a rule to touch this file when the # headset is plugged in, and remove the file when it's plugged out. if os.path.exists("/tmp/has_headset"): self.mixer_for_headset(True) else: self.mixer_for_handset(True) #self.mixer_set("DAPM Handset Mic", "mute") #self.mixer_set("DAPM Headset Mic", "unmute") #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute") #self.mixer_set("ALC Mixer Mic1", "cap") #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say # Watch the headset button self.button_press_handler = button_press_handler self.input_fd = open("/dev/input/event4", "rb") self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data) self.last_button_press = 0 self.recorder = None self.basename = None def mixer_for_headset(self, force=False): if not force and self.has_headset: return info("Setting mixer for headset") # TODO: find out how to disable the handset microphone: this does not # seem to be sufficient self.mixer_set_many( ("DAPM Handset Mic", "mute"), ("DAPM Headset Mic", "unmute"), ("Left Mixer Sidetone Playback Sw", "unmute"), ("ALC Mixer Mic1", "cap"), ("Amp Spk", "mute") # We don't need the phone playing what we say ) self.has_headset = True def mixer_for_handset(self, force=False): if not force and not self.has_headset: return info("Setting mixer for handset") self.mixer_set_many( ("DAPM Handset Mic", "unmute"), ("DAPM Headset Mic", "mute"), ("Left Mixer Sidetone Playback Sw", "mute"), ("ALC Mixer Mic1", "cap"), ("Amp Spk", "mute") # We don't need the phone playing what we say ) self.has_headset = False def set_basename(self, basename): self.basename = basename def start_recording(self): if self.basename is None: raise RuntimeError("Recording requested but basename not set") self.recorder = subprocess.Popen( ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", self.basename + ".wav"]) def start_levels(self): self.recorder = subprocess.Popen( ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"]) def close(self): if self.recorder is not None: os.kill(self.recorder.pid, signal.SIGINT) self.recorder.wait() # Restore mixer settings self.load_scenario(self.saved_scenario) gobject.source_remove(self.input_watch) self.input_fd.close() def on_input_data(self, source, condition): buf = self.input_fd.read(16) ts1, ts2, type, code, value = struct.unpack("LLHHI", buf) #print ts1, ts2, type, code, value if type == 1 and code == 119: if value: #print "BUTTON RELEASE" pass else: if self.last_button_press + 1 < ts1: self.last_button_press = ts1 if self.button_press_handler is not None: self.button_press_handler() #print "BUTTON PRESS" elif type == 5 and code == 2: if value: info("Headset plugged in") self.mixer_for_headset(self) else: info("Headset plugged out") self.mixer_for_handset(self) return True def save_scenario(self, name): res = subprocess.call(["alsactl", "store", "-f", name]) if res != 0: raise RuntimeError("Saving audio scenario to '%s' failed" % name) def load_scenario(self, name): res = subprocess.call(["alsactl", "restore", "-f", name]) if res != 0: raise RuntimeError("Loading audio scenario '%s' failed" % name) def mixer_set(self, name, *args): args = map(str, args) res = subprocess.call(["amixer", "-q", "set", name] + args) if res != 0: raise RuntimeError("Setting mixer '%s' to %s failed" % (name, " ".join(args))) # Will do this when we find out the syntax for giving amixer commands on stdin def mixer_set_many(self, *args): """Perform many mixer set operations via amixer --stdin""" proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE) cmd_input = [] for k, v in args: cmd_input.append("sset " + repr(k) + " " + repr(v)) (out, err) = proc.communicate(input="\n".join(cmd_input)) res = proc.wait() if res != 0: raise RuntimeError("Setting mixer failed") */ public class CommandButton : Gtk.Button { private string command; public CommandButton(string name, string command) { label = name; this.command = command; clicked += on_clicked; set_size_request(0, zavai.config.min_button_height); } public void on_clicked() { zavai.log.info("Run program: " + command); string[] args = command.split(" "); Pid pid; Process.spawn_async( Environment.get_home_dir(), args, null, SpawnFlags.SEARCH_PATH, null, out pid); } } public PowerButton power_button = null; public void init() { power_button = new PowerButton(); zavai.registry.register_service(power_button); } public void ui_init() { // Menus var menu_power = new zavai.Menu("Power menu"); menu_power.add_widget(new CommandButton("Suspend", "apm -s")); menu_power.add_widget(new CommandButton("Shutdown", "shutdown -h now")); menu_power.add_widget(new CommandButton("Reboot", "shutdown -r now")); zavai.registry.register_menu("menu.power", menu_power); zavai.registry.getmenu("menu.main").add_applet("menu.power"); } } }