Merge branch 'master' into alarm
[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 dynamic DBus.Object gsm_device;
37         public bool screen_locked;
38         private int screen_lock_fd;
39         // Timestamp of the past power button pressed even (0 if the button has
40         // been released)
41         private Posix.timeval last_down;
42         private Posix.timeval last_short_press;
43
44         private bool hide_after_closing_power_menu;
45
46         public signal void screen_lock_changed(bool state);
47
48         public signal void power_short_press(Posix.timeval* t);
49         public signal void power_long_press();
50         private uint button_press_timeout;
51
52         public Power()
53         {
54                 screen_locked = false;
55                 screen_lock_fd = -1;
56                 hide_after_closing_power_menu = false;
57                 last_down.tv_sec = 0;
58                 last_down.tv_usec = 0;
59                 last_short_press.tv_sec = 0;
60                 last_short_press.tv_usec = 0;
61                 button_press_timeout = 0;
62
63                 usage = zavai.registry.sbus.get_object(
64                         "org.freesmartphone.ousaged",
65                         "/org/freesmartphone/Usage",
66                         "org.freesmartphone.Usage");
67                 gsm_device = zavai.registry.sbus.get_object(
68                         "org.freesmartphone.ogsmd",
69                         "/org/freesmartphone/GSM/Device",
70                         "org.freesmartphone.Resource");
71
72                 zavai.input.power_button.power_button += on_power_button;
73                 zavai.input.power_button.request("zavai.ui.powerbutton.power");
74
75                 power_short_press += on_power_short_press;
76                 power_long_press += on_power_long_press;
77         }
78
79         public void shutdown()
80         {
81                 zavai.input.power_button.release("zavai.ui.powerbutton.power");
82         }
83
84         public void do_suspend()
85         {
86                 bool done = false;
87                 if (!done)
88                 {
89                         try {
90                                 usage.Suspend();
91                                 done = true;
92                                 zavai.log.info("Suspend was done with ousaged.");
93                         } catch (Error e) {
94                                 zavai.log.error("Suspending phone with ousaged: " + e.message);
95                         }
96                 }
97                 if (!done)
98                 {
99                         // From http://lindi.iki.fi/lindi/openmoko/susp
100                         try {
101                                 gsm_device.Suspend();
102                         } catch (Error e) {
103                                 zavai.log.error("Cannot tell GSM to suspend (but never mind): " + e.message);
104                         }
105                         // amixer -q -d sset "Amp Spk" mute
106                         // sync;sync;sync
107                         // echo 0 | sudo tee /proc/sysrq-trigger
108                         {
109                                 // Limit the scope of state, so that it's
110                                 // closed before we resume
111                                 FileStream state = FileStream.open("/sys/power/state", "w");
112                                 if (state != null)
113                                 {
114                                         state.puts("mem\n");
115                                         state.flush();
116                                 }
117                         }
118                         // amixer -q -d sset "Amp Spk" unmute
119                         try {
120                                 gsm_device.Resume();
121                         } catch (Error e) {
122                                 zavai.log.error("Cannot tell GSM to resume (but never mind): " + e.message);
123                         }
124                         done = true;
125                         zavai.log.info("Suspend was done with zavai.");
126                 }
127         }
128         public void do_shutdown()
129         {
130                 try {
131                         //usage.Shutdown();
132                         zavai.app.run_script("shutdown -h now");
133                 } catch (Error e) {
134                         zavai.log.error("Shutting down phone: " + e.message);
135                 }
136         }
137         public void do_reboot()
138         {
139                 try {
140                         //usage.Reboot();
141                         zavai.app.run_script("shutdown -r now");
142                 } catch (Error e) {
143                         zavai.log.error("Rebooting phone: " + e.message);
144                 }
145         }
146
147         public void set_screen_lock(bool locked)
148         {
149                 if (locked && screen_locked)
150                         return;
151                 if (!locked && !screen_locked)
152                         return;
153
154                 if (locked)
155                 {
156                         screen_lock_fd = Posix.open("/dev/input/event1", Posix.O_RDWR | Posix.O_NONBLOCK);
157                         if (screen_lock_fd < 0)
158                         {
159                                 zavai.log.error("Cannot open /dev/input/event1");
160                                 return;
161                         }
162
163                         // FIXME: X won't see events, but it's still generating interrupts,
164                         // isn't it?
165                         int EVIOCGRAB = 0x40044590;
166                         if (Posix.ioctl(screen_lock_fd, EVIOCGRAB, locked ? 1 : 0) != 0)
167                         {
168                                 zavai.log.error("Cannot EVIOCGRAB /dev/input/event1");
169                                 Posix.close(screen_lock_fd);
170                                 return;
171                         }
172
173                         backlight.lock_screen();
174                 } else {
175                         Posix.close(screen_lock_fd);
176                         backlight.unlock_screen();
177                 }
178                 screen_locked = locked;
179                 if (!locked)
180                         backlight.wiggle();
181
182                 screen_lock_changed(locked);
183         }
184
185         private bool on_power_button_timeout()
186         {
187                 last_down.tv_sec = 0;
188                 last_down.tv_usec = 0;
189                 power_long_press();
190                 // Do not reschedule
191                 return false;
192         }
193
194         private void on_power_button(Posix.timeval* t, bool pressed)
195         {
196                 bool short_press = false;
197                 bool long_press = false;
198
199                 if (pressed)
200                 {
201                         if (last_down.tv_sec == 0)
202                         {
203                                 last_down = *t;
204                                 button_press_timeout = Timeout.add(1000, on_power_button_timeout);
205                         }
206                         else
207                         {
208                                 long diff = timediff(t, &last_down);
209                                 long_press = diff >= 1000000;
210                         }
211                 } else {
212                         if (last_down.tv_sec == 0)
213                         {
214                                 // Ignore: release has been simulated with the timeout
215                         } else {
216                                 if (button_press_timeout != 0)
217                                 {
218                                         // Cancel the timeout
219                                         Source.remove(button_press_timeout);
220                                         button_press_timeout = 0;
221                                 }
222                                 long diff = timediff(t, &last_down);
223                                 if (diff >= 1000000)
224                                         long_press = true;
225                                 else
226                                         short_press = true;
227
228                                 last_down.tv_sec = 0;
229                                 last_down.tv_usec = 0;
230                         }
231                 }
232
233                 if (long_press)
234                 {
235                         power_long_press();
236                         last_short_press.tv_sec = 0;
237                         last_short_press.tv_usec = 0;
238                 }
239                 if (short_press) power_short_press(t);
240         }
241
242         private void on_power_short_press(Posix.timeval* t)
243         {
244                 long diff = timediff(t, &last_short_press);
245                 bool combo = screen_locked && (diff <= 5000000);
246                 last_short_press = *t;
247
248                 if (screen_locked)
249                 {
250                         // Short press: turn on backlight for a bit
251                         backlight.wiggle();
252                         if (combo)
253                         {
254                                 app.back_to_main();
255                                 app.toggle_visibility();
256                         }
257                 }
258                 else
259                         // Short press: toggle power menu
260                         power_menu.toggle();
261         }
262
263         private void on_power_long_press()
264         {
265                 if (screen_locked)
266                         // Long press: unlock
267                         set_screen_lock(false);
268                 else
269                         // Long press: lock screen
270                         set_screen_lock(true);
271         }
272 }
273
274 public class BatteryIcon : Gtk.StatusIcon
275 {
276         public Dkp.Device battery;
277
278         public BatteryIcon(Dkp.Device dev)
279         {
280                 battery = dev;
281                 battery.changed += on_changed;
282
283                 stderr.printf("New battery icon for %s online %s perc %f isrec %s tte %lld ttf %lld\n", dev.native_path, dev.online ? "yes" : "no", dev.percentage, dev.is_rechargeable ? "yes" : "no", dev.time_to_empty, dev.time_to_full);
284
285                 update_icon();
286         }
287
288         private void on_changed(void* obj)
289         {
290                 update_icon();
291         }
292
293         protected void update_icon()
294         {
295                 string name = zavai.config.icondir + "/battery/";
296                 Dkp.DeviceState state = (Dkp.DeviceState)battery.state;
297
298 stderr.printf("New battery status: %s\n", Dkp.Device.state_to_text(state));
299                 int capacity = (int)Math.round(battery.percentage/10);
300                 switch (state)
301                 {
302                         case Dkp.DeviceState.CHARGING:
303                                 name += "%02d0_charging_500.png".printf(capacity);
304                                 break;
305                         case Dkp.DeviceState.FULLY_CHARGED:
306                                 name += "100_charging_500.png";
307                                 break;
308                         case Dkp.DeviceState.UNKNOWN:
309                         case Dkp.DeviceState.DISCHARGING:
310                         case Dkp.DeviceState.EMPTY:
311                         case Dkp.DeviceState.PENDING_CHARGE:
312                         case Dkp.DeviceState.PENDING_DISCHARGE:
313                         case Dkp.DeviceState.LAST:
314                                 name += "%02d0.png".printf(capacity);
315                                 break;
316                 }
317
318 stderr.printf("Loading icon from %s\n", name);
319
320                 set_from_file(name);
321         }
322 }
323
324 public class ScreenLockButton : Gtk.Button
325 {
326         public ScreenLockButton()
327         {
328                 label = "Lock screen";
329                 clicked += on_clicked;
330                 set_size_request(0, zavai.config.min_button_height);
331         }
332
333         public void on_clicked()
334         {
335                 zavai.log.info("Locking screen");
336                 power.set_screen_lock(true);
337                 power_menu.hide_menu();
338         }
339 }
340
341 public class SuspendButton : Gtk.Button
342 {
343         public SuspendButton()
344         {
345                 label = "Suspend";
346                 clicked += on_clicked;
347                 set_size_request(0, zavai.config.min_button_height);
348         }
349
350         public void on_clicked()
351         {
352                 zavai.log.info("Suspending the phone");
353                 power.do_suspend();
354                 power_menu.hide_menu();
355         }
356 }
357
358 public class ShutdownButton : Gtk.Button
359 {
360         public ShutdownButton()
361         {
362                 label = "Shut down";
363                 clicked += on_clicked;
364                 set_size_request(0, zavai.config.min_button_height);
365         }
366
367         public void on_clicked()
368         {
369                 zavai.log.info("Shutting down the phone");
370                 power.do_shutdown();
371                 power_menu.hide_menu();
372         }
373 }
374
375 public class RebootButton : Gtk.Button
376 {
377         public RebootButton()
378         {
379                 label = "Reboot";
380                 clicked += on_clicked;
381                 set_size_request(0, zavai.config.min_button_height);
382         }
383
384         public void on_clicked()
385         {
386                 zavai.log.info("Rebooting the phone");
387                 power.do_reboot();
388                 power_menu.hide_menu();
389         }
390 }
391
392 // For a list of dbus services, look in /etc/dbus-1/system.d/
393 public class Backlight: zavai.Service
394 {
395         public Backlight()
396         {
397                 Object(name: "backlight");
398         }
399
400         // Turn the backlight on and then let it fade off
401         public void wiggle()
402         {
403                 try {
404                         zavai.app.run_script(zavai.config.homedir + "/display wiggle");
405                 } catch (Error e) {
406                         zavai.log.error("Requesting/releasing resource Display: " + e.message);
407                 }
408         }
409
410         public void lock_screen()
411         {
412                 if (!started)
413                 {
414                         try {
415                                 zavai.app.run_script(zavai.config.homedir + "/display lock_off");
416                         } catch (GLib.Error e) {
417                                 zavai.log.error(e.message);
418                         }
419                 }
420         }
421
422         public void unlock_screen()
423         {
424                 try {
425                         zavai.app.run_script(zavai.config.homedir + "/display defaults");
426                 } catch (GLib.Error e) {
427                         zavai.log.error(e.message);
428                 }
429         }
430
431         public override void start()
432         {
433                 if (started) return;
434                 try {
435                         zavai.app.run_script(zavai.config.homedir + "/display lock_on");
436                         zavai.log.info("Acquired display");
437                         base.start();
438                 } catch (GLib.Error e) {
439                         zavai.log.error(e.message);
440                 }
441                 base.start();
442         }
443
444         public override void stop()
445         {
446                 if (!started) return;
447                 try {
448                         zavai.app.run_script(zavai.config.homedir + "/display defaults");
449                         zavai.log.info("Released display");
450                         base.stop();
451                 } catch (GLib.Error e) {
452                         zavai.log.error(e.message);
453                 }
454                 base.stop();
455         }
456 }
457
458 public class PowerMenu : zavai.Resource, Gtk.Window
459 {
460         protected Gtk.VBox vbox;
461         protected ScreenLockButton act_screen_lock;
462         protected SuspendButton act_suspend;
463         protected ShutdownButton act_shutdown;
464         protected RebootButton act_reboot;
465         protected ServiceRequestLink act_backlight_on;
466         protected bool shown;
467
468         public PowerMenu()
469         {
470                 Object(
471                         type: Gtk.WindowType.TOPLEVEL,
472                         title: "Power Menu"
473                 );
474                 shown = false;
475                 destroy_with_parent = true;
476                 set_transient_for(zavai.app);
477                 set_modal(true);
478                 set_position(Gtk.WindowPosition.CENTER_ON_PARENT);
479                 set_size_request(300, 500);
480
481                 vbox = new Gtk.VBox(false, 0);
482                 add(vbox);
483
484                 //destroy += Gtk.main_quit;
485                 //set_events(get_events() | Gdk.EventMask.VISIBILITY_NOTIFY_MASK);
486                 //visibility_notify_event += on_visibility;
487                 set_skip_pager_hint(true);
488                 set_skip_taskbar_hint(true);
489                 set_type_hint(Gdk.WindowTypeHint.POPUP_MENU);
490
491                 act_screen_lock = new ScreenLockButton();
492                 vbox.pack_start(act_screen_lock, false, false, 0);
493
494                 act_suspend = new SuspendButton();
495                 vbox.pack_start(act_suspend, false, false, 0);
496
497                 act_shutdown = new ShutdownButton();
498                 vbox.pack_start(act_shutdown, false, false, 0);
499
500                 act_reboot = new RebootButton();
501                 vbox.pack_start(act_reboot, false, false, 0);
502
503                 act_backlight_on = new ServiceRequestLink("backlight", "Keep backlight on", "Let backlight fade");
504                 act_backlight_on.toggled += (src) => { this.hide_menu(); };
505                 vbox.pack_start(act_backlight_on, false, false, 0);
506
507                 //vbox.show_all();
508         }
509
510         public void toggle()
511         {
512                 if (!shown)
513                 {
514                         show_all();
515                         show();
516                         visible = true;
517                         present();
518                         shown = true;
519                 } else {
520                         // TODO: do more in case it is visible but has no visibility (is covered by others)
521                         visible = !visible;
522                         if (visible)
523                                 present();
524                 }                               
525         }
526
527         public void hide_menu()
528         {
529                 visible = false;
530         }
531
532         public void shutdown() {}
533 }
534
535 /*
536 public class TogglePowerMenu : Gtk.Button
537 {
538         public TogglePowerMenu()
539         {
540                 label = "Toggle power menu";
541                 clicked += on_clicked;
542                 set_size_request(0, zavai.config.min_button_height);
543         }
544
545         public void on_clicked()
546         {
547                 zavai.log.info("Toggling power menu");
548                 power_menu.toggle();
549         }
550 }
551 */
552
553 Power power;
554 PowerMenu power_menu;
555 Gee.ArrayList<BatteryIcon> battery_icons;
556 Backlight backlight;
557 //TogglePowerMenu tpm;
558
559 public void init()
560 {
561         power = new Power();
562         backlight = new Backlight();
563         zavai.registry.register_service(backlight);
564         
565         try {
566                 battery_icons = new Gee.ArrayList<BatteryIcon>();
567                 // Enumerate batteries
568                 var c = new Dkp.Client();
569                 unowned GLib.PtrArray devs = c.enumerate_devices();
570                 for (int i = 0; i < devs.len; ++i)
571                 {
572                         Dkp.Device dev = (Dkp.Device)devs.pdata[i];
573                         stderr.printf("Found new device %s\n", dev.native_path);
574                         dev.print();
575                         stderr.printf("Rechargeable: %s\n", dev.is_rechargeable ? "yes" : "no");
576                         if (!dev.is_rechargeable) continue;
577                         var bi = new BatteryIcon(dev);
578                         bi.set_visible(true);
579                         battery_icons.add(bi);
580                 }
581
582                 power_menu = new PowerMenu();
583                 zavai.registry.register_resource("powermenu", power_menu);
584         } catch (Error e) {
585                 stderr.printf("Creating power menu: %s\n", e.message);
586                 power_menu = null;
587         }
588         
589     //zavai.registry.getmenu("menu.main").add_applet("menu.power");
590         //tpm = new TogglePowerMenu();
591     //zavai.registry.getmenu("menu.main").add_widget(tpm);
592
593     /*
594         raise_icon = new RaiseIcon();
595         raise_icon.set_visible(true);
596
597         close_or_back = new CloseOrBack();
598         close_or_back.set_visible(true);
599
600         window_list = new WindowList("Current apps");
601         zavai.registry.register_applet("wm.list", window_list);
602         zavai.registry.getmenu("menu.main").add_applet("wm.list");
603
604         try {
605                 launcher = new Launcher("Run program");
606         } catch (Error e) {
607                 zavai.log.error("Not running launcher: " + e.message);
608                 launcher = null;
609         }
610
611         if (launcher != null)
612         {
613                 zavai.registry.register_applet("wm.launcher", launcher);
614                 zavai.registry.getmenu("menu.main").add_applet("wm.launcher");
615         }
616     */
617 }
618
619 }
620 }
621 }