Use a timer to detect long presses
[gregoa/zavai.git] / src / app_power.vala
1 /*
2  * app_power - zavai power 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 using GLib;
22
23 namespace zavai {
24 namespace ui {
25 namespace power {
26
27 // Compute a-b in microseconds
28 static long timediff(Posix.timeval* a, Posix.timeval* b)
29 {
30         return (a->tv_sec - b->tv_sec) * 1000000 + (a->tv_usec - b->tv_usec);
31 }
32
33 public class Power : zavai.Resource, Object
34 {
35         public dynamic DBus.Object usage;
36         public bool screen_locked;
37         private int screen_lock_fd;
38         // Timestamp of the past power button pressed even (0 if the button has
39         // been released)
40         private Posix.timeval last_down;
41
42         private bool hide_after_closing_power_menu;
43
44         public signal void screen_lock_changed(bool state);
45
46         public signal void power_short_press();
47         public signal void power_long_press();
48         private uint button_press_timeout;
49
50         public Power()
51         {
52                 screen_locked = false;
53                 screen_lock_fd = -1;
54                 hide_after_closing_power_menu = false;
55                 last_down.tv_sec = 0;
56                 last_down.tv_usec = 0;
57                 button_press_timeout = 0;
58
59                 usage = zavai.registry.sbus.get_object(
60                         "org.freesmartphone.ousaged",
61                         "/org/freesmartphone/Usage",
62                         "org.freesmartphone.Usage");
63
64                 zavai.input.power_button.power_button += on_power_button;
65                 zavai.input.power_button.request("zavai.ui.powerbutton.power");
66
67                 power_short_press += on_power_short_press;
68                 power_long_press += on_power_long_press;
69         }
70
71         public void shutdown()
72         {
73                 zavai.input.power_button.release("zavai.ui.powerbutton.power");
74         }
75
76         public void do_suspend() { usage.Suspend(); }
77         public void do_shutdown() { usage.Shutdown(); }
78         public void do_reboot() { usage.Reboot(); }
79
80         public void set_screen_lock(bool locked)
81         {
82                 if (locked && screen_locked)
83                         return;
84                 if (!locked && !screen_locked)
85                         return;
86
87                 if (locked)
88                 {
89                         screen_lock_fd = Posix.open("/dev/input/event1", Posix.O_RDWR | Posix.O_NONBLOCK);
90                         if (screen_lock_fd < 0)
91                         {
92                                 zavai.log.error("Cannot open /dev/input/event1");
93                                 return;
94                         }
95
96                         int EVIOCGRAB = 0x40044590;
97                         if (Posix.ioctl(screen_lock_fd, EVIOCGRAB, locked ? 1 : 0) != 0)
98                         {
99                                 zavai.log.error("Cannot EVIOCGRAB /dev/input/event1");
100                                 Posix.close(screen_lock_fd);
101                                 return;
102                         }
103                 } else {
104                         Posix.close(screen_lock_fd);
105                 }
106                 screen_locked = locked;
107                 if (!locked)
108                         backlight.wiggle();
109
110                 screen_lock_changed(locked);
111         }
112
113         private bool on_power_button_timeout()
114         {
115                 last_down.tv_sec = 0;
116                 last_down.tv_usec = 0;
117                 power_long_press();
118                 // Do not reschedule
119                 return false;
120         }
121
122         private void on_power_button(Posix.timeval* t, bool pressed)
123         {
124                 bool short_press = false;
125                 bool long_press = false;
126
127 stderr.printf("EVENT %d\n", (int)pressed);
128
129                 if (pressed)
130                 {
131                         if (last_down.tv_sec == 0)
132                         {
133 stderr.printf("  FIRST PRESSED\n");
134                                 last_down = *t;
135                                 button_press_timeout = Timeout.add(1500, on_power_button_timeout);
136                         }
137                         else
138                         {
139                                 long diff = timediff(t, &last_down);
140 stderr.printf("  PRESSED FOR %ld\n", diff);
141                                 long_press = diff >= 1500000;
142                         }
143                 } else {
144                         if (last_down.tv_sec == 0)
145                         {
146                                 // Ignore: release has been simulated with the timeout
147                         } else {
148                                 if (button_press_timeout != 0)
149                                 {
150                                         // Cancel the timeout
151                                         Source.remove(button_press_timeout);
152                                         button_press_timeout = 0;
153                                 }
154                                 long diff = timediff(t, &last_down);
155 stderr.printf("  RELEASED AFTER %ld\n", diff);
156                                 if (diff >= 1500000)
157                                         long_press = true;
158                                 else
159                                         short_press = true;
160
161                                 last_down.tv_sec = 0;
162                                 last_down.tv_usec = 0;
163                         }
164                 }
165
166 stderr.printf("  LP %d SP %d\n", (int)long_press, (int)short_press);
167
168                 if (long_press) power_long_press();
169                 if (short_press) power_short_press();
170         }
171
172         private void on_power_short_press()
173         {
174                 if (screen_locked)
175                         // Short press: turn on backlight for a bit
176                         backlight.wiggle();
177                 else
178                         // Short press: toggle power menu
179                         power_menu.toggle();
180         }
181
182         private void on_power_long_press()
183         {
184                 if (screen_locked)
185                         // Long press: unlock
186                         set_screen_lock(false);
187                 else
188                         // Long press: lock screen
189                         set_screen_lock(true);
190         }
191 }
192
193 public class BatteryIcon : Gtk.StatusIcon
194 {
195         public dynamic DBus.Object battery;
196         protected string last_status;
197         protected int last_capacity;
198
199         public BatteryIcon()
200         {
201                 battery = zavai.registry.sbus.get_object(
202                         "org.freesmartphone.odeviced",
203                         "/org/freesmartphone/Device/PowerSupply/battery",
204                         "org.freesmartphone.Device.PowerSupply");
205
206                 // activate += on_activate;
207
208                 battery.PowerStatus += on_power_status;
209                 battery.Capacity += on_capacity;
210
211                 last_status = battery.GetPowerStatus();
212                 last_capacity = battery.GetCapacity();
213                 
214                 update_icon();
215         }
216
217         private void on_power_status(dynamic DBus.Object bat, string status)
218         {
219                 zavai.log.info("New battery status: " + status);
220                 last_status = status;
221                 update_icon();
222         }
223
224         private void on_capacity(dynamic DBus.Object bat, int val)
225         {
226 stderr.printf("NEW CAPACITY: %d\n", val);
227                 last_capacity = val;
228                 update_icon();
229         }
230
231         /*
232         private void on_activate()
233         {
234         }
235         */
236
237         protected void update_icon()
238         {
239                 string name = zavai.config.icondir + "/battery/";
240
241                 if (last_status == "charging")
242                         name += "%02d0_charging_500.png".printf(last_capacity/10);
243                 else
244                         name += "%02d0.png".printf(last_capacity/10);
245
246 stderr.printf("Loading icon from %s\n", name);
247
248                 set_from_file(name);
249         }
250 }
251
252 public class ScreenLockButton : Gtk.Button
253 {
254         public ScreenLockButton()
255         {
256                 label = "Lock screen";
257                 clicked += on_clicked;
258                 set_size_request(0, zavai.config.min_button_height);
259         }
260
261         public void on_clicked()
262         {
263                 zavai.log.info("Locking screen");
264                 power.set_screen_lock(true);
265                 power_menu.hide();
266         }
267 }
268
269 public class SuspendButton : Gtk.Button
270 {
271         public SuspendButton()
272         {
273                 label = "Suspend";
274                 clicked += on_clicked;
275                 set_size_request(0, zavai.config.min_button_height);
276         }
277
278         public void on_clicked()
279         {
280                 zavai.log.info("Suspending the phone via FSO");
281                 power.do_suspend();
282                 power_menu.hide();
283         }
284 }
285
286 public class ShutdownButton : Gtk.Button
287 {
288         public ShutdownButton()
289         {
290                 label = "Shut down";
291                 clicked += on_clicked;
292                 set_size_request(0, zavai.config.min_button_height);
293         }
294
295         public void on_clicked()
296         {
297                 zavai.log.info("Shutting down the phone via FSO");
298                 power.do_shutdown();
299                 power_menu.hide();
300         }
301 }
302
303 public class RebootButton : Gtk.Button
304 {
305         public RebootButton()
306         {
307                 label = "Reboot";
308                 clicked += on_clicked;
309                 set_size_request(0, zavai.config.min_button_height);
310         }
311
312         public void on_clicked()
313         {
314                 zavai.log.info("Rebooting the phone via FSO");
315                 power.do_reboot();
316                 power_menu.hide();
317         }
318 }
319
320 // For a list of dbus services, look in /etc/dbus-1/system.d/
321 public class Backlight: zavai.Service
322 {
323         public dynamic DBus.Object usage;
324
325         public Backlight()
326         {
327                 name = "backlight";
328
329                 usage = zavai.registry.sbus.get_object(
330                         "org.freesmartphone.ousaged",
331                         "/org/freesmartphone/Usage",
332                         "org.freesmartphone.Usage");
333         }
334
335         // Turn the backlight and then let it fade off
336         public void wiggle()
337         {
338                 // There must be a better method
339                 usage.RequestResource("Display");
340                 usage.ReleaseResource("Display");
341         }
342
343         /// Request GPS resource
344         public override void start()
345         {
346                 if (started) return;
347                 try {
348                         usage.RequestResource("Display");
349                         zavai.log.info("Acquired display");
350                         base.start();
351                 } catch (GLib.Error e) {
352                         zavai.log.error(e.message);
353                 }
354                 base.start();
355         }
356
357         // Release usage of GPS
358         public override void stop()
359         {
360                 if (!started) return;
361                 try {
362                         usage.ReleaseResource("Display");
363                         zavai.log.info("Released display");
364                         base.stop();
365                 } catch (GLib.Error e) {
366                         zavai.log.error(e.message);
367                 }
368                 base.stop();
369         }
370 }
371
372 public class PowerMenu : zavai.Resource, Gtk.Window
373 {
374         protected Gtk.VBox vbox;
375         protected ScreenLockButton act_screen_lock;
376         protected SuspendButton act_suspend;
377         protected ShutdownButton act_shutdown;
378         protected RebootButton act_reboot;
379         protected ServiceRequestLink act_backlight_on;
380         protected bool shown;
381
382         public PowerMenu()
383         {
384                 type = Gtk.WindowType.TOPLEVEL;
385                 title = "Power Menu";
386                 shown = false;
387                 destroy_with_parent = true;
388                 set_transient_for(zavai.app);
389                 set_modal(true);
390                 set_position(Gtk.WindowPosition.CENTER_ON_PARENT);
391                 set_size_request(300, 500);
392
393                 vbox = new Gtk.VBox(false, 0);
394                 add(vbox);
395
396                 //destroy += Gtk.main_quit;
397                 //set_events(get_events() | Gdk.EventMask.VISIBILITY_NOTIFY_MASK);
398                 //visibility_notify_event += on_visibility;
399                 set_skip_pager_hint(true);
400                 set_skip_taskbar_hint(true);
401                 set_type_hint(Gdk.WindowTypeHint.POPUP_MENU);
402
403                 act_screen_lock = new ScreenLockButton();
404                 vbox.pack_start(act_screen_lock, false, false, 0);
405
406                 act_suspend = new SuspendButton();
407                 vbox.pack_start(act_suspend, false, false, 0);
408
409                 act_shutdown = new ShutdownButton();
410                 vbox.pack_start(act_shutdown, false, false, 0);
411
412                 act_reboot = new RebootButton();
413                 vbox.pack_start(act_reboot, false, false, 0);
414
415                 act_backlight_on = new ServiceRequestLink("backlight", "Keep backlight on", "Let backlight fade");
416                 vbox.pack_start(act_backlight_on, false, false, 0);
417
418                 //vbox.show_all();
419         }
420
421         public void toggle()
422         {
423                 if (!shown)
424                 {
425                         show_all();
426                         show();
427                         visible = true;
428                         present();
429                         shown = true;
430                 } else {
431                         // TODO: do more in case it is visible but has no visibility (is covered by others)
432                         visible = !visible;
433                         if (visible)
434                                 present();
435                 }                               
436         }
437
438         public void hide()
439         {
440                 visible = false;
441         }
442
443         public void shutdown() {}
444 }
445
446 /*
447 public class TogglePowerMenu : Gtk.Button
448 {
449         public TogglePowerMenu()
450         {
451                 label = "Toggle power menu";
452                 clicked += on_clicked;
453                 set_size_request(0, zavai.config.min_button_height);
454         }
455
456         public void on_clicked()
457         {
458                 zavai.log.info("Toggling power menu");
459                 power_menu.toggle();
460         }
461 }
462 */
463
464 Power power;
465 PowerMenu power_menu;
466 BatteryIcon battery_icon;
467 Backlight backlight;
468 //TogglePowerMenu tpm;
469
470 public void init()
471 {
472         power = new Power();
473         backlight = new Backlight();
474         zavai.registry.register_service(backlight);
475
476         battery_icon = new BatteryIcon();
477         battery_icon.set_visible(true);
478
479         power_menu = new PowerMenu();
480         zavai.registry.register_resource("powermenu", power_menu);
481         
482     //zavai.registry.getmenu("menu.main").add_applet("menu.power");
483         //tpm = new TogglePowerMenu();
484     //zavai.registry.getmenu("menu.main").add_widget(tpm);
485
486     /*
487         raise_icon = new RaiseIcon();
488         raise_icon.set_visible(true);
489
490         close_or_back = new CloseOrBack();
491         close_or_back.set_visible(true);
492
493         window_list = new WindowList("Current apps");
494         zavai.registry.register_applet("wm.list", window_list);
495         zavai.registry.getmenu("menu.main").add_applet("wm.list");
496
497         try {
498                 launcher = new Launcher("Run program");
499         } catch (Error e) {
500                 zavai.log.error("Not running launcher: " + e.message);
501                 launcher = null;
502         }
503
504         if (launcher != null)
505         {
506                 zavai.registry.register_applet("wm.launcher", launcher);
507                 zavai.registry.getmenu("menu.main").add_applet("wm.launcher");
508         }
509     */
510 }
511
512 }
513 }
514 }