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