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