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