a49223e76c8b01393ea71a263d1f6197b25e8816
[gregoa/zavai.git] / src / input.vala
1 /*
2  * devinput - zavai /dev/input device handling
3  *
4  * Copyright (C) 2009  Enrico Zini <enrico@enricozini.org>
5  *
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.
10  *
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.
15  *
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
19  */
20
21 namespace zavai {
22 namespace input {
23
24 public abstract class DevInput : zavai.Service
25 {
26         public string device { get; construct; }
27
28         public signal bool event(LinuxInput.Event* ev);
29
30     protected IOChannel fd = null;
31     protected uint fd_watch = 0;
32
33     protected void close_fd()
34     {
35         if (fd != null)
36         {
37             try {
38                 fd.shutdown(false);
39             } catch (IOChannelError e) {
40                 zavai.log.error("When closing " + device + ": " + e.message);
41             }
42
43             fd = null;
44         }
45     }
46
47     protected bool on_input_data(IOChannel source, IOCondition condition)
48     {
49                 if (condition != IOCondition.IN) return true;
50
51         stderr.printf("GOT INPUT ON %s %d\n", device, source.unix_get_fd());
52         char[] buf = new char[sizeof(LinuxInput.Event)];
53                 size_t count_read;
54                 source.read_chars(buf, out count_read);
55         stderr.printf("READ %zu chars\n", count_read);
56
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);
59
60         /*
61         ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
62         #print ts1, ts2, type, code, value
63         if type == 1 and code == 119:
64             if value:
65                 #print "BUTTON RELEASE"
66                 pass
67             else:
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()
72                     #print "BUTTON PRESS"
73         elif type == 5 and code == 2:
74             if value:
75                 info("Headset plugged in")
76                 self.mixer_for_headset(self)
77             else:
78                 info("Headset plugged out")
79                 self.mixer_for_handset(self)
80         */
81         return event(ie);
82     }
83
84         /// Start reading from the device
85         public override void start()
86         {
87                 if (started) return;
88
89         if (fd != null)
90             close_fd();
91
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);
97
98                 base.start();
99         }
100
101         // Stop reading from the device
102         public override void stop()
103         {
104                 if (!started) return;
105
106         if (fd != null)
107         {
108             Source.remove(fd_watch);
109             close_fd();
110         }
111
112                 base.stop();
113         }
114 }
115
116 public class PowerButton : DevInput
117 {
118         public signal void power_button(Posix.timeval* time, bool pressed);
119
120     public PowerButton()
121     {
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";
127
128                 event += on_event;
129                 power_button += on_power_button;
130     }
131
132         protected bool on_event(LinuxInput.Event* ev)
133         {
134                 if (ev->type == LinuxInput.Type.KEY && 
135                         ev->code == LinuxInput.Key.POWER)
136                 {
137                         power_button(&(ev->time), ev->val == 0 ? false : true);
138                 }
139                 return true;
140         }
141
142         protected void on_power_button(Posix.timeval* time, bool pressed)
143         {
144                 if (!pressed)
145                 {
146                         zavai.app.push_applet("menu.power");
147                 }
148         }
149 }
150
151 /*
152 # TODO:
153 #  - hook into the headset plugged/unplugged event
154 #  - if unplugged, turn on handset microphone
155 #  - if plugged, redo headest mixer settings
156 class Audio:
157     "Handle mixer settings, audio recording and headset button presses"
158     def __init__(self, button_press_handler = None):
159         self.saved_scenario = os.path.expanduser("~/.audiomap.state")
160
161         # Setup the mixer
162         # Set mixer to record from headset and handle headset button
163         self.save_scenario(self.saved_scenario)
164         self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
165
166         # This is a work-around because I have not found a way to query for the
167         # current headset state, I can only know when it changes. So in my
168         # system I configured oeventsd with a rule to touch this file when the
169         # headset is plugged in, and remove the file when it's plugged out.
170         if os.path.exists("/tmp/has_headset"):
171             self.mixer_for_headset(True)
172         else:
173             self.mixer_for_handset(True)
174
175         #self.mixer_set("DAPM Handset Mic", "mute")
176         #self.mixer_set("DAPM Headset Mic", "unmute")
177         #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
178         #self.mixer_set("ALC Mixer Mic1", "cap")
179         #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
180
181         # Watch the headset button
182         self.button_press_handler = button_press_handler
183         self.input_fd = open("/dev/input/event4", "rb")
184         self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
185
186         self.last_button_press = 0
187         self.recorder = None
188         self.basename = None
189
190     def mixer_for_headset(self, force=False):
191         if not force and self.has_headset: return
192         info("Setting mixer for headset")
193         # TODO: find out how to disable the handset microphone: this does not
194         # seem to be sufficient
195         self.mixer_set_many(
196                 ("DAPM Handset Mic", "mute"),
197                 ("DAPM Headset Mic", "unmute"),
198                 ("Left Mixer Sidetone Playback Sw", "unmute"),
199                 ("ALC Mixer Mic1", "cap"),
200                 ("Amp Spk", "mute") # We don't need the phone playing what we say
201         )
202         self.has_headset = True
203
204     def mixer_for_handset(self, force=False):
205         if not force and not self.has_headset: return
206         info("Setting mixer for handset")
207         self.mixer_set_many(
208                 ("DAPM Handset Mic", "unmute"),
209                 ("DAPM Headset Mic", "mute"),
210                 ("Left Mixer Sidetone Playback Sw", "mute"),
211                 ("ALC Mixer Mic1", "cap"),
212                 ("Amp Spk", "mute") # We don't need the phone playing what we say
213         )
214         self.has_headset = False
215
216     def set_basename(self, basename):
217         self.basename = basename
218
219     def start_recording(self):
220         if self.basename is None:
221             raise RuntimeError("Recording requested but basename not set")
222         self.recorder = subprocess.Popen(
223             ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", self.basename + ".wav"])
224
225     def start_levels(self):
226         self.recorder = subprocess.Popen(
227             ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
228
229     def close(self):
230         if self.recorder is not None:
231             os.kill(self.recorder.pid, signal.SIGINT)
232             self.recorder.wait()
233
234         # Restore mixer settings
235         self.load_scenario(self.saved_scenario)
236
237         gobject.source_remove(self.input_watch)
238         self.input_fd.close()
239
240     def on_input_data(self, source, condition):
241         buf = self.input_fd.read(16)
242         ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
243         #print ts1, ts2, type, code, value
244         if type == 1 and code == 119:
245             if value:
246                 #print "BUTTON RELEASE"
247                 pass
248             else:
249                 if self.last_button_press + 1 < ts1:
250                     self.last_button_press = ts1
251                     if self.button_press_handler is not None:
252                         self.button_press_handler()
253                     #print "BUTTON PRESS"
254         elif type == 5 and code == 2:
255             if value:
256                 info("Headset plugged in")
257                 self.mixer_for_headset(self)
258             else:
259                 info("Headset plugged out")
260                 self.mixer_for_handset(self)
261         return True
262
263     def save_scenario(self, name):
264         res = subprocess.call(["alsactl", "store", "-f", name])
265         if res != 0:
266             raise RuntimeError("Saving audio scenario to '%s' failed" % name)
267
268     def load_scenario(self, name):
269         res = subprocess.call(["alsactl", "restore", "-f", name])
270         if res != 0:
271             raise RuntimeError("Loading audio scenario '%s' failed" % name)
272
273     def mixer_set(self, name, *args):
274         args = map(str, args)
275         res = subprocess.call(["amixer", "-q", "set", name] + args)
276         if res != 0:
277             raise RuntimeError("Setting mixer '%s' to %s failed" % (name, " ".join(args)))
278
279     # Will do this when we find out the syntax for giving amixer commands on stdin
280     def mixer_set_many(self, *args):
281         """Perform many mixer set operations via amixer --stdin"""
282         proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
283         cmd_input = []
284         for k, v in args:
285             cmd_input.append("sset " + repr(k) + " " + repr(v))
286         (out, err) = proc.communicate(input="\n".join(cmd_input))
287         res = proc.wait()
288         if res != 0:
289             raise RuntimeError("Setting mixer failed")
290 */
291 public class CommandButton : Gtk.Button
292 {
293         private string command;
294
295         public CommandButton(string name, string command)
296         {
297                 label = name;
298                 this.command = command;
299                 clicked += on_clicked;
300                 set_size_request(0, zavai.config.min_button_height);
301         }
302
303         public void on_clicked()
304         {
305                 zavai.log.info("Run program: " + command);
306                 string[] args = command.split(" ");
307                 Pid pid;
308                 Process.spawn_async(
309                         Environment.get_home_dir(),
310                         args,
311                         null,
312                         SpawnFlags.SEARCH_PATH,
313                         null,
314                         out pid);
315         }
316 }
317
318
319 public PowerButton power_button = null;
320
321 public void init()
322 {
323         power_button = new PowerButton();
324
325         zavai.registry.register_service(power_button);
326 }
327
328 public void ui_init()
329 {
330     // Menus
331     var menu_power = new zavai.Menu("Power menu");
332         menu_power.add_widget(new CommandButton("Suspend", "apm -s"));
333         menu_power.add_widget(new CommandButton("Shutdown", "shutdown -h now"));
334         menu_power.add_widget(new CommandButton("Reboot", "shutdown -r now"));
335     zavai.registry.register_menu("menu.power", menu_power);
336
337     zavai.registry.getmenu("menu.main").add_applet("menu.power");
338 }
339
340 }
341 }