Reduced usage of Gee
[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 }