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