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