Code cleanup
[gregoa/zavai.git] / src / app_power.vala
1 /*
2  * app_power - zavai power handling
3  *
4  * Copyright (C) 2009  Enrico Zini <enrico@enricozini.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 using GLib;
22
23 namespace zavai {
24 namespace ui {
25 namespace power {
26
27 // Compute a-b in microseconds
28 static long timediff(Posix.timeval* a, Posix.timeval* b)
29 {
30         return (a->tv_sec - b->tv_sec) * 1000000 + (a->tv_usec - b->tv_usec);
31 }
32
33 public class Power : zavai.Resource, Object
34 {
35         public dynamic DBus.Object usage;
36         public dynamic DBus.Object gsm_device;
37         public bool screen_locked;
38         private int screen_lock_fd;
39         // Timestamp of the past power button pressed even (0 if the button has
40         // been released)
41         private Posix.timeval last_down;
42         private Posix.timeval last_short_press;
43
44         private bool hide_after_closing_power_menu;
45
46         public signal void screen_lock_changed(bool state);
47
48         public signal void power_short_press(Posix.timeval* t);
49         public signal void power_long_press();
50         private uint button_press_timeout;
51
52         public Power()
53         {
54                 screen_locked = false;
55                 screen_lock_fd = -1;
56                 hide_after_closing_power_menu = false;
57                 last_down.tv_sec = 0;
58                 last_down.tv_usec = 0;
59                 last_short_press.tv_sec = 0;
60                 last_short_press.tv_usec = 0;
61                 button_press_timeout = 0;
62
63                 usage = zavai.registry.sbus.get_object(
64                         "org.freesmartphone.ousaged",
65                         "/org/freesmartphone/Usage",
66                         "org.freesmartphone.Usage");
67                 gsm_device = zavai.registry.sbus.get_object(
68                         "org.freesmartphone.ogsmd",
69                         "/org/freesmartphone/GSM/Device",
70                         "org.freesmartphone.Resource");
71
72                 zavai.input.power_button.power_button += on_power_button;
73                 zavai.input.power_button.request("zavai.ui.powerbutton.power");
74
75                 power_short_press += on_power_short_press;
76                 power_long_press += on_power_long_press;
77         }
78
79         public void shutdown()
80         {
81                 zavai.input.power_button.release("zavai.ui.powerbutton.power");
82         }
83
84         public void do_suspend()
85         {
86                 bool done = false;
87                 /*
88                 if (!done)
89                 {
90                         try {
91                                 usage.Suspend();
92                                 done = true;
93                                 zavai.log.info("Suspend was done with ousaged.");
94                         } catch (Error e) {
95                                 zavai.log.error("Suspending phone with ousaged: " + e.message);
96                         }
97                 }
98                 */
99                 if (!done)
100                 {
101                         /*
102                         // From http://lindi.iki.fi/lindi/openmoko/susp
103                         try {
104                                 gsm_device.Suspend();
105                         } catch (Error e) {
106                                 zavai.log.error("Cannot tell GSM to suspend (but never mind): " + e.message);
107                         }
108                         // amixer -q -d sset "Amp Spk" mute
109                         // sync;sync;sync
110                         // echo 0 | sudo tee /proc/sysrq-trigger
111                         {
112                                 // Limit the scope of state, so that it's
113                                 // closed before we resume
114                                 FileStream state = FileStream.open("/sys/power/state", "w");
115                                 if (state != null)
116                                 {
117                                         state.puts("mem\n");
118                                         state.flush();
119                                 }
120                         }
121                         // amixer -q -d sset "Amp Spk" unmute
122                         try {
123                                 gsm_device.Resume();
124                         } catch (Error e) {
125                                 zavai.log.error("Cannot tell GSM to resume (but never mind): " + e.message);
126                         }
127                         */
128                         try {
129                                 zavai.app.run_script("pm-suspend");
130                                 done = true;
131                                 zavai.log.info("Suspend was done with zavai.");
132                         } catch (Error e) {
133                                 zavai.log.error("Suspending phone: " + e.message);
134                         }
135                 }
136         }
137         public void do_shutdown()
138         {
139                 try {
140                         //usage.Shutdown();
141                         zavai.app.run_script("shutdown -h now");
142                 } catch (Error e) {
143                         zavai.log.error("Shutting down phone: " + e.message);
144                 }
145         }
146         public void do_reboot()
147         {
148                 try {
149                         //usage.Reboot();
150                         zavai.app.run_script("shutdown -r now");
151                 } catch (Error e) {
152                         zavai.log.error("Rebooting phone: " + e.message);
153                 }
154         }
155
156         public void set_screen_lock(bool locked)
157         {
158                 if (locked && screen_locked)
159                         return;
160                 if (!locked && !screen_locked)
161                         return;
162
163                 if (locked)
164                 {
165                         screen_lock_fd = Posix.open("/dev/input/event1", Posix.O_RDWR | Posix.O_NONBLOCK);
166                         if (screen_lock_fd < 0)
167                         {
168                                 zavai.log.error("Cannot open /dev/input/event1");
169                                 return;
170                         }
171
172                         // FIXME: X won't see events, but it's still generating interrupts,
173                         // isn't it?
174                         int EVIOCGRAB = 0x40044590;
175                         if (Posix.ioctl(screen_lock_fd, EVIOCGRAB, locked ? 1 : 0) != 0)
176                         {
177                                 zavai.log.error("Cannot EVIOCGRAB /dev/input/event1");
178                                 Posix.close(screen_lock_fd);
179                                 return;
180                         }
181
182                         backlight.lock_screen();
183                 } else {
184                         Posix.close(screen_lock_fd);
185                         backlight.unlock_screen();
186                 }
187                 screen_locked = locked;
188                 if (!locked)
189                         backlight.wiggle();
190
191                 screen_lock_changed(locked);
192         }
193
194         private bool on_power_button_timeout()
195         {
196                 last_down.tv_sec = 0;
197                 last_down.tv_usec = 0;
198                 power_long_press();
199                 // Do not reschedule
200                 return false;
201         }
202
203         private void on_power_button(Posix.timeval* t, bool pressed)
204         {
205                 bool short_press = false;
206                 bool long_press = false;
207
208                 if (pressed)
209                 {
210                         if (last_down.tv_sec == 0)
211                         {
212                                 last_down = *t;
213                                 button_press_timeout = Timeout.add(1000, on_power_button_timeout);
214                         }
215                         else
216                         {
217                                 long diff = timediff(t, &last_down);
218                                 long_press = diff >= 1000000;
219                         }
220                 } else {
221                         if (last_down.tv_sec == 0)
222                         {
223                                 // Ignore: release has been simulated with the timeout
224                         } else {
225                                 if (button_press_timeout != 0)
226                                 {
227                                         // Cancel the timeout
228                                         Source.remove(button_press_timeout);
229                                         button_press_timeout = 0;
230                                 }
231                                 long diff = timediff(t, &last_down);
232                                 if (diff >= 1000000)
233                                         long_press = true;
234                                 else
235                                         short_press = true;
236
237                                 last_down.tv_sec = 0;
238                                 last_down.tv_usec = 0;
239                         }
240                 }
241
242                 if (long_press)
243                 {
244                         power_long_press();
245                         last_short_press.tv_sec = 0;
246                         last_short_press.tv_usec = 0;
247                 }
248                 if (short_press) power_short_press(t);
249         }
250
251         private void on_power_short_press(Posix.timeval* t)
252         {
253                 long diff = timediff(t, &last_short_press);
254                 bool combo = screen_locked && (diff <= 5000000);
255                 last_short_press = *t;
256
257                 if (screen_locked)
258                 {
259                         // Short press: turn on backlight for a bit
260                         backlight.wiggle();
261                         if (combo)
262                         {
263                                 app.back_to_main();
264                                 app.toggle_visibility();
265                         }
266                 }
267                 else
268                         // Short press: toggle power menu
269                         power_menu.toggle();
270         }
271
272         private void on_power_long_press()
273         {
274                 if (screen_locked)
275                         // Long press: unlock
276                         set_screen_lock(false);
277                 else
278                         // Long press: lock screen
279                         set_screen_lock(true);
280         }
281 }
282
283 public class BatteryIcon : Gtk.StatusIcon
284 {
285         public Dkp.Device battery;
286
287         public BatteryIcon(Dkp.Device dev)
288         {
289                 battery = dev;
290                 battery.changed += on_changed;
291
292 //              stderr.printf("New battery icon for %s online %s perc %f isrec %s tte %lld ttf %lld\n", dev.native_path, dev.online ? "yes" : "no", dev.percentage, dev.is_rechargeable ? "yes" : "no", dev.time_to_empty, dev.time_to_full);
293
294                 update_icon();
295         }
296
297         private void on_changed(void* obj)
298         {
299                 update_icon();
300         }
301
302         protected void update_icon()
303         {
304                 string name = zavai.config.icondir + "/battery/";
305                 Dkp.DeviceState state = (Dkp.DeviceState)battery.state;
306
307 //stderr.printf("New battery status: %s\n", Dkp.Device.state_to_text(state));
308                 int capacity = (int)Math.round(battery.percentage/10);
309                 switch (state)
310                 {
311                         case Dkp.DeviceState.CHARGING:
312                                 name += "%02d0_charging_500.png".printf(capacity);
313                                 break;
314                         case Dkp.DeviceState.FULLY_CHARGED:
315                                 name += "100_charging_500.png";
316                                 break;
317                         case Dkp.DeviceState.UNKNOWN:
318                         case Dkp.DeviceState.DISCHARGING:
319                         case Dkp.DeviceState.EMPTY:
320                         case Dkp.DeviceState.PENDING_CHARGE:
321                         case Dkp.DeviceState.PENDING_DISCHARGE:
322                         case Dkp.DeviceState.LAST:
323                                 name += "%02d0.png".printf(capacity);
324                                 break;
325                 }
326
327 //stderr.printf("Loading icon from %s\n", name);
328
329                 set_from_file(name);
330         }
331 }
332
333 public class ScreenLockButton : Gtk.Button
334 {
335         public ScreenLockButton()
336         {
337                 label = "Lock screen";
338                 clicked += on_clicked;
339                 set_size_request(0, zavai.config.min_button_height);
340         }
341
342         public void on_clicked()
343         {
344                 zavai.log.info("Locking screen");
345                 power.set_screen_lock(true);
346                 power_menu.hide_menu();
347         }
348 }
349
350 public class SuspendButton : Gtk.Button
351 {
352         public SuspendButton()
353         {
354                 label = "Suspend";
355                 clicked += on_clicked;
356                 set_size_request(0, zavai.config.min_button_height);
357         }
358
359         public void on_clicked()
360         {
361                 zavai.log.info("Suspending the phone");
362                 power.do_suspend();
363                 power_menu.hide_menu();
364         }
365 }
366
367 public class ShutdownButton : Gtk.Button
368 {
369         public ShutdownButton()
370         {
371                 label = "Shut down";
372                 clicked += on_clicked;
373                 set_size_request(0, zavai.config.min_button_height);
374         }
375
376         public void on_clicked()
377         {
378                 zavai.log.info("Shutting down the phone");
379                 power.do_shutdown();
380                 power_menu.hide_menu();
381         }
382 }
383
384 public class RebootButton : Gtk.Button
385 {
386         public RebootButton()
387         {
388                 label = "Reboot";
389                 clicked += on_clicked;
390                 set_size_request(0, zavai.config.min_button_height);
391         }
392
393         public void on_clicked()
394         {
395                 zavai.log.info("Rebooting the phone");
396                 power.do_reboot();
397                 power_menu.hide_menu();
398         }
399 }
400
401 // For a list of dbus services, look in /etc/dbus-1/system.d/
402 public class Backlight: zavai.Service
403 {
404         public Backlight()
405         {
406                 Object(name: "backlight");
407         }
408
409         // Turn the backlight on and then let it fade off
410         public void wiggle()
411         {
412                 try {
413                         zavai.app.run_script(zavai.config.homedir + "/display wiggle");
414                 } catch (Error e) {
415                         zavai.log.error("Requesting/releasing resource Display: " + e.message);
416                 }
417         }
418
419         public void lock_screen()
420         {
421                 if (!started)
422                 {
423                         try {
424                                 zavai.app.run_script(zavai.config.homedir + "/display lock_off");
425                         } catch (GLib.Error e) {
426                                 zavai.log.error(e.message);
427                         }
428                 }
429         }
430
431         public void unlock_screen()
432         {
433                 try {
434                         zavai.app.run_script(zavai.config.homedir + "/display defaults");
435                 } catch (GLib.Error e) {
436                         zavai.log.error(e.message);
437                 }
438         }
439
440         public override void start()
441         {
442                 if (started) return;
443                 try {
444                         zavai.app.run_script(zavai.config.homedir + "/display lock_on");
445                         zavai.log.info("Acquired display");
446                         base.start();
447                 } catch (GLib.Error e) {
448                         zavai.log.error(e.message);
449                 }
450                 base.start();
451         }
452
453         public override void stop()
454         {
455                 if (!started) return;
456                 try {
457                         zavai.app.run_script(zavai.config.homedir + "/display defaults");
458                         zavai.log.info("Released display");
459                         base.stop();
460                 } catch (GLib.Error e) {
461                         zavai.log.error(e.message);
462                 }
463                 base.stop();
464         }
465 }
466
467 public class BrightnessAdjustment : Gtk.Adjustment
468 {
469         public BrightnessAdjustment()
470         {
471                 lower = 0;
472                 upper = Omhacks.Screen.Brightness.get_max();
473                 value = Omhacks.Screen.Brightness.get();
474                 step_increment = 1;
475                 page_increment = upper/10;
476                 page_size = upper/10;
477                 value_changed += on_value_changed;
478
479                 /*
480                 Gtk.Adjustment(
481                         zavai.config.backlight_max/2,
482                         0, zavai.config.backlight_max,
483                         1, zavai.config.backlight_max/10, zavai.config.backlight_max/10);
484                 */
485         }
486
487         protected void on_value_changed()
488         {
489                 Omhacks.Screen.Brightness.set((int)value);
490         }
491 }
492
493 public class PowerMenu : zavai.Resource, Gtk.Window
494 {
495         protected Gtk.VBox vbox;
496         protected Gtk.HBox hbox;
497         protected ScreenLockButton act_screen_lock;
498         protected SuspendButton act_suspend;
499         protected ShutdownButton act_shutdown;
500         protected RebootButton act_reboot;
501         protected ServiceRequestLink act_backlight_on;
502         protected Gtk.VScrollbar bscroll;
503         protected bool shown;
504
505         public PowerMenu()
506         {
507                 Object(
508                         type: Gtk.WindowType.TOPLEVEL,
509                         title: "Power Menu"
510                 );
511                 shown = false;
512                 destroy_with_parent = true;
513                 set_transient_for(zavai.app);
514                 set_modal(true);
515                 set_position(Gtk.WindowPosition.CENTER_ON_PARENT);
516                 set_size_request(300, 500);
517
518                 hbox = new Gtk.HBox(false, 0);
519                 add(hbox);
520
521                 vbox = new Gtk.VBox(false, 0);
522                 hbox.pack_start(vbox, true, true, 0);
523
524                 bscroll = new Gtk.VScrollbar(brightness);
525                 bscroll.inverted = true;
526                 hbox.pack_start(bscroll, false, false, 0);
527
528                 //destroy += Gtk.main_quit;
529                 //set_events(get_events() | Gdk.EventMask.VISIBILITY_NOTIFY_MASK);
530                 //visibility_notify_event += on_visibility;
531                 set_skip_pager_hint(true);
532                 set_skip_taskbar_hint(true);
533                 set_type_hint(Gdk.WindowTypeHint.POPUP_MENU);
534
535                 act_screen_lock = new ScreenLockButton();
536                 vbox.pack_start(act_screen_lock, false, false, 0);
537
538                 act_suspend = new SuspendButton();
539                 vbox.pack_start(act_suspend, false, false, 0);
540
541                 act_shutdown = new ShutdownButton();
542                 vbox.pack_start(act_shutdown, false, false, 0);
543
544                 act_reboot = new RebootButton();
545                 vbox.pack_start(act_reboot, false, false, 0);
546
547                 act_backlight_on = new ServiceRequestLink("backlight", "Keep backlight on", "Let backlight fade");
548                 act_backlight_on.toggled += (src) => { this.hide_menu(); };
549                 vbox.pack_start(act_backlight_on, false, false, 0);
550
551                 //vbox.show_all();
552         }
553
554         public void toggle()
555         {
556                 if (!shown)
557                 {
558                         show_all();
559                         show();
560                         visible = true;
561                         present();
562                         shown = true;
563                 } else {
564                         // TODO: do more in case it is visible but has no visibility (is covered by others)
565                         visible = !visible;
566                         if (visible)
567                                 present();
568                 }                               
569         }
570
571         public void hide_menu()
572         {
573                 visible = false;
574         }
575
576         public void shutdown() {}
577 }
578
579 /*
580 public class TogglePowerMenu : Gtk.Button
581 {
582         public TogglePowerMenu()
583         {
584                 label = "Toggle power menu";
585                 clicked += on_clicked;
586                 set_size_request(0, zavai.config.min_button_height);
587         }
588
589         public void on_clicked()
590         {
591                 zavai.log.info("Toggling power menu");
592                 power_menu.toggle();
593         }
594 }
595 */
596
597 Power power;
598 PowerMenu power_menu;
599 Gee.ArrayList<BatteryIcon> battery_icons;
600 Backlight backlight;
601 BrightnessAdjustment brightness;
602 //TogglePowerMenu tpm;
603
604 public void init()
605 {
606         power = new Power();
607         backlight = new Backlight();
608         zavai.registry.register_service(backlight);
609         brightness = new BrightnessAdjustment();
610         
611         try {
612                 battery_icons = new Gee.ArrayList<BatteryIcon>();
613                 // Enumerate batteries
614                 var c = new Dkp.Client();
615                 unowned GLib.PtrArray devs = c.enumerate_devices();
616                 for (int i = 0; i < devs.len; ++i)
617                 {
618                         Dkp.Device dev = (Dkp.Device)devs.pdata[i];
619                         stderr.printf("Found new device %s\n", dev.native_path);
620                         dev.print();
621                         stderr.printf("Rechargeable: %s\n", dev.is_rechargeable ? "yes" : "no");
622                         if (!dev.is_rechargeable) continue;
623                         var bi = new BatteryIcon(dev);
624                         bi.set_visible(true);
625                         battery_icons.add(bi);
626                 }
627
628                 power_menu = new PowerMenu();
629                 zavai.registry.register_resource("powermenu", power_menu);
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 }