/* * app_power - zavai power handling * * Copyright (C) 2009 Enrico Zini * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ using GLib; namespace zavai { namespace ui { namespace power { // Compute a-b in microseconds static long timediff(Posix.timeval* a, Posix.timeval* b) { return (a->tv_sec - b->tv_sec) * 1000000 + (a->tv_usec - b->tv_usec); } public class Power : zavai.Resource, Object { public dynamic DBus.Object usage; public dynamic DBus.Object gsm_device; public bool screen_locked; private int screen_lock_fd; // Timestamp of the past power button pressed even (0 if the button has // been released) private Posix.timeval last_down; private Posix.timeval last_short_press; private bool hide_after_closing_power_menu; public signal void screen_lock_changed(bool state); public signal void power_short_press(Posix.timeval* t); public signal void power_long_press(); private uint button_press_timeout; public Power() { screen_locked = false; screen_lock_fd = -1; hide_after_closing_power_menu = false; last_down.tv_sec = 0; last_down.tv_usec = 0; last_short_press.tv_sec = 0; last_short_press.tv_usec = 0; button_press_timeout = 0; usage = zavai.registry.sbus.get_object( "org.freesmartphone.ousaged", "/org/freesmartphone/Usage", "org.freesmartphone.Usage"); gsm_device = zavai.registry.sbus.get_object( "org.freesmartphone.ogsmd", "/org/freesmartphone/GSM/Device", "org.freesmartphone.Resource"); zavai.input.power_button.power_button += on_power_button; zavai.input.power_button.request("zavai.ui.powerbutton.power"); power_short_press += on_power_short_press; power_long_press += on_power_long_press; } public void shutdown() { zavai.input.power_button.release("zavai.ui.powerbutton.power"); } public void do_suspend() { bool done = false; if (!done) { try { usage.Suspend(); done = true; zavai.log.info("Suspend was done with ousaged."); } catch (Error e) { zavai.log.error("Suspending phone with ousaged: " + e.message); } } if (!done) { /* // From http://lindi.iki.fi/lindi/openmoko/susp try { gsm_device.Suspend(); } catch (Error e) { zavai.log.error("Cannot tell GSM to suspend (but never mind): " + e.message); } // amixer -q -d sset "Amp Spk" mute // sync;sync;sync // echo 0 | sudo tee /proc/sysrq-trigger { // Limit the scope of state, so that it's // closed before we resume FileStream state = FileStream.open("/sys/power/state", "w"); if (state != null) { state.puts("mem\n"); state.flush(); } } // amixer -q -d sset "Amp Spk" unmute try { gsm_device.Resume(); } catch (Error e) { zavai.log.error("Cannot tell GSM to resume (but never mind): " + e.message); } */ try { zavai.app.run_script("pm-suspend"); done = true; zavai.log.info("Suspend was done with zavai."); } catch (Error e) { zavai.log.error("Suspending phone: " + e.message); } } } public void do_shutdown() { try { //usage.Shutdown(); zavai.app.run_script("shutdown -h now"); } catch (Error e) { zavai.log.error("Shutting down phone: " + e.message); } } public void do_reboot() { try { //usage.Reboot(); zavai.app.run_script("shutdown -r now"); } catch (Error e) { zavai.log.error("Rebooting phone: " + e.message); } } public void set_screen_lock(bool locked) { if (locked && screen_locked) return; if (!locked && !screen_locked) return; if (locked) { screen_lock_fd = Posix.open("/dev/input/event1", Posix.O_RDWR | Posix.O_NONBLOCK); if (screen_lock_fd < 0) { zavai.log.error("Cannot open /dev/input/event1"); return; } // FIXME: X won't see events, but it's still generating interrupts, // isn't it? int EVIOCGRAB = 0x40044590; if (Posix.ioctl(screen_lock_fd, EVIOCGRAB, locked ? 1 : 0) != 0) { zavai.log.error("Cannot EVIOCGRAB /dev/input/event1"); Posix.close(screen_lock_fd); return; } backlight.lock_screen(); } else { Posix.close(screen_lock_fd); backlight.unlock_screen(); } screen_locked = locked; if (!locked) backlight.wiggle(); screen_lock_changed(locked); } private bool on_power_button_timeout() { last_down.tv_sec = 0; last_down.tv_usec = 0; power_long_press(); // Do not reschedule return false; } private void on_power_button(Posix.timeval* t, bool pressed) { bool short_press = false; bool long_press = false; if (pressed) { if (last_down.tv_sec == 0) { last_down = *t; button_press_timeout = Timeout.add(1000, on_power_button_timeout); } else { long diff = timediff(t, &last_down); long_press = diff >= 1000000; } } else { if (last_down.tv_sec == 0) { // Ignore: release has been simulated with the timeout } else { if (button_press_timeout != 0) { // Cancel the timeout Source.remove(button_press_timeout); button_press_timeout = 0; } long diff = timediff(t, &last_down); if (diff >= 1000000) long_press = true; else short_press = true; last_down.tv_sec = 0; last_down.tv_usec = 0; } } if (long_press) { power_long_press(); last_short_press.tv_sec = 0; last_short_press.tv_usec = 0; } if (short_press) power_short_press(t); } private void on_power_short_press(Posix.timeval* t) { long diff = timediff(t, &last_short_press); bool combo = screen_locked && (diff <= 5000000); last_short_press = *t; if (screen_locked) { // Short press: turn on backlight for a bit backlight.wiggle(); if (combo) { app.back_to_main(); app.toggle_visibility(); } } else // Short press: toggle power menu power_menu.toggle(); } private void on_power_long_press() { if (screen_locked) // Long press: unlock set_screen_lock(false); else // Long press: lock screen set_screen_lock(true); } } public class BatteryIcon : Gtk.StatusIcon { public Dkp.Device battery; public BatteryIcon(Dkp.Device dev) { battery = dev; battery.changed += on_changed; 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); update_icon(); } private void on_changed(void* obj) { update_icon(); } protected void update_icon() { string name = zavai.config.icondir + "/battery/"; Dkp.DeviceState state = (Dkp.DeviceState)battery.state; stderr.printf("New battery status: %s\n", Dkp.Device.state_to_text(state)); int capacity = (int)Math.round(battery.percentage/10); switch (state) { case Dkp.DeviceState.CHARGING: name += "%02d0_charging_500.png".printf(capacity); break; case Dkp.DeviceState.FULLY_CHARGED: name += "100_charging_500.png"; break; case Dkp.DeviceState.UNKNOWN: case Dkp.DeviceState.DISCHARGING: case Dkp.DeviceState.EMPTY: case Dkp.DeviceState.PENDING_CHARGE: case Dkp.DeviceState.PENDING_DISCHARGE: case Dkp.DeviceState.LAST: name += "%02d0.png".printf(capacity); break; } stderr.printf("Loading icon from %s\n", name); set_from_file(name); } } public class ScreenLockButton : Gtk.Button { public ScreenLockButton() { label = "Lock screen"; clicked += on_clicked; set_size_request(0, zavai.config.min_button_height); } public void on_clicked() { zavai.log.info("Locking screen"); power.set_screen_lock(true); power_menu.hide_menu(); } } public class SuspendButton : Gtk.Button { public SuspendButton() { label = "Suspend"; clicked += on_clicked; set_size_request(0, zavai.config.min_button_height); } public void on_clicked() { zavai.log.info("Suspending the phone"); power.do_suspend(); power_menu.hide_menu(); } } public class ShutdownButton : Gtk.Button { public ShutdownButton() { label = "Shut down"; clicked += on_clicked; set_size_request(0, zavai.config.min_button_height); } public void on_clicked() { zavai.log.info("Shutting down the phone"); power.do_shutdown(); power_menu.hide_menu(); } } public class RebootButton : Gtk.Button { public RebootButton() { label = "Reboot"; clicked += on_clicked; set_size_request(0, zavai.config.min_button_height); } public void on_clicked() { zavai.log.info("Rebooting the phone"); power.do_reboot(); power_menu.hide_menu(); } } // For a list of dbus services, look in /etc/dbus-1/system.d/ public class Backlight: zavai.Service { public Backlight() { Object(name: "backlight"); } // Turn the backlight on and then let it fade off public void wiggle() { try { zavai.app.run_script(zavai.config.homedir + "/display wiggle"); } catch (Error e) { zavai.log.error("Requesting/releasing resource Display: " + e.message); } } public void lock_screen() { if (!started) { try { zavai.app.run_script(zavai.config.homedir + "/display lock_off"); } catch (GLib.Error e) { zavai.log.error(e.message); } } } public void unlock_screen() { try { zavai.app.run_script(zavai.config.homedir + "/display defaults"); } catch (GLib.Error e) { zavai.log.error(e.message); } } public override void start() { if (started) return; try { zavai.app.run_script(zavai.config.homedir + "/display lock_on"); zavai.log.info("Acquired display"); base.start(); } catch (GLib.Error e) { zavai.log.error(e.message); } base.start(); } public override void stop() { if (!started) return; try { zavai.app.run_script(zavai.config.homedir + "/display defaults"); zavai.log.info("Released display"); base.stop(); } catch (GLib.Error e) { zavai.log.error(e.message); } base.stop(); } } public class PowerMenu : zavai.Resource, Gtk.Window { protected Gtk.VBox vbox; protected ScreenLockButton act_screen_lock; protected SuspendButton act_suspend; protected ShutdownButton act_shutdown; protected RebootButton act_reboot; protected ServiceRequestLink act_backlight_on; protected bool shown; public PowerMenu() { Object( type: Gtk.WindowType.TOPLEVEL, title: "Power Menu" ); shown = false; destroy_with_parent = true; set_transient_for(zavai.app); set_modal(true); set_position(Gtk.WindowPosition.CENTER_ON_PARENT); set_size_request(300, 500); vbox = new Gtk.VBox(false, 0); add(vbox); //destroy += Gtk.main_quit; //set_events(get_events() | Gdk.EventMask.VISIBILITY_NOTIFY_MASK); //visibility_notify_event += on_visibility; set_skip_pager_hint(true); set_skip_taskbar_hint(true); set_type_hint(Gdk.WindowTypeHint.POPUP_MENU); act_screen_lock = new ScreenLockButton(); vbox.pack_start(act_screen_lock, false, false, 0); act_suspend = new SuspendButton(); vbox.pack_start(act_suspend, false, false, 0); act_shutdown = new ShutdownButton(); vbox.pack_start(act_shutdown, false, false, 0); act_reboot = new RebootButton(); vbox.pack_start(act_reboot, false, false, 0); act_backlight_on = new ServiceRequestLink("backlight", "Keep backlight on", "Let backlight fade"); act_backlight_on.toggled += (src) => { this.hide_menu(); }; vbox.pack_start(act_backlight_on, false, false, 0); //vbox.show_all(); } public void toggle() { if (!shown) { show_all(); show(); visible = true; present(); shown = true; } else { // TODO: do more in case it is visible but has no visibility (is covered by others) visible = !visible; if (visible) present(); } } public void hide_menu() { visible = false; } public void shutdown() {} } /* public class TogglePowerMenu : Gtk.Button { public TogglePowerMenu() { label = "Toggle power menu"; clicked += on_clicked; set_size_request(0, zavai.config.min_button_height); } public void on_clicked() { zavai.log.info("Toggling power menu"); power_menu.toggle(); } } */ Power power; PowerMenu power_menu; Gee.ArrayList battery_icons; Backlight backlight; //TogglePowerMenu tpm; public void init() { power = new Power(); backlight = new Backlight(); zavai.registry.register_service(backlight); try { battery_icons = new Gee.ArrayList(); // Enumerate batteries var c = new Dkp.Client(); unowned GLib.PtrArray devs = c.enumerate_devices(); for (int i = 0; i < devs.len; ++i) { Dkp.Device dev = (Dkp.Device)devs.pdata[i]; stderr.printf("Found new device %s\n", dev.native_path); dev.print(); stderr.printf("Rechargeable: %s\n", dev.is_rechargeable ? "yes" : "no"); if (!dev.is_rechargeable) continue; var bi = new BatteryIcon(dev); bi.set_visible(true); battery_icons.add(bi); } power_menu = new PowerMenu(); zavai.registry.register_resource("powermenu", power_menu); } catch (Error e) { stderr.printf("Creating power menu: %s\n", e.message); power_menu = null; } //zavai.registry.getmenu("menu.main").add_applet("menu.power"); //tpm = new TogglePowerMenu(); //zavai.registry.getmenu("menu.main").add_widget(tpm); /* raise_icon = new RaiseIcon(); raise_icon.set_visible(true); close_or_back = new CloseOrBack(); close_or_back.set_visible(true); window_list = new WindowList("Current apps"); zavai.registry.register_applet("wm.list", window_list); zavai.registry.getmenu("menu.main").add_applet("wm.list"); try { launcher = new Launcher("Run program"); } catch (Error e) { zavai.log.error("Not running launcher: " + e.message); launcher = null; } if (launcher != null) { zavai.registry.register_applet("wm.launcher", launcher); zavai.registry.getmenu("menu.main").add_applet("wm.launcher"); } */ } } } }