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