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