#!/usr/bin/python3 -sP
# encoding: utf-8
#
# Copyright 2014 Kai Huuhko <kai.huuhko@gmail.com>
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

AGENT_DBUS_PATH = "/org/enlightenment/polkit/Agent"
AGENT_DBUS_NAME = "org.enlightenment.polkit"

import sys
from datetime import datetime
import logging
from distutils.version import StrictVersion

import gettext
gettext.install("polkit-efl")
_ = __builtins__._

LOG_LEVEL = logging.INFO

log = logging.getLogger("polkit-efl")
log.propagate = False
log.setLevel(LOG_LEVEL)
log_handler = logging.StreamHandler()
log_formatter = logging.Formatter(
    "%(name)s %(relativeCreated)d: %(levelname)s %(message)s"
    )
log_handler.setFormatter(log_formatter)
log.addHandler(log_handler)

from efl.ecore import Exe, ECORE_EXE_PIPE_READ, ECORE_EXE_PIPE_WRITE, \
    ECORE_EXE_PIPE_ERROR

from efl import elementary
elementary.init()

USE_ECORE_X = False
try:
    import efl.ecore.x as ecore_x
    USE_ECORE_X = True
except ImportError:
    try:
        import efl.ecore_x as ecore_x
        USE_ECORE_X = True
    except ImportError:
        pass

if USE_ECORE_X:
    if not ecore_x.init():
        USE_ECORE_X = False

from efl.elementary.window import Window, ELM_WIN_DOCK
from efl.elementary.button import Button
from efl.elementary.box import Box
from efl.elementary.entry import Entry
from efl.elementary.hoversel import Hoversel, HoverselItem
from efl.elementary.separator import Separator
from efl.elementary.innerwindow import InnerWindow
from efl.elementary.layout import Layout
from efl.elementary.icon import Icon
from efl.elementary.genlist import Genlist, GenlistItem, GenlistItemClass, \
    ELM_GENLIST_ITEM_NONE, ELM_GENLIST_ITEM_GROUP, ELM_LIST_COMPRESS
from efl.elementary.theme import Theme
from efl.evas import EVAS_HINT_EXPAND, EVAS_HINT_FILL, EVAS_CALLBACK_KEY_UP, \
    EVAS_CALLBACK_CANVAS_FOCUS_IN, EVAS_CALLBACK_CANVAS_FOCUS_OUT

EXPAND_BOTH = EVAS_HINT_EXPAND, EVAS_HINT_EXPAND
EXPAND_HORIZ = EVAS_HINT_EXPAND, 0.0
FILL_BOTH = EVAS_HINT_FILL, EVAS_HINT_FILL
FILL_HORIZ = EVAS_HINT_FILL, 0.5

pk_path_error = False

POLKIT_HELPER_PREFIX = ""
POLKIT_HELPER_LIBDIR = ""


def prefix_data_cb(obj, ev):
    global POLKIT_HELPER_PREFIX
    POLKIT_HELPER_PREFIX = ev.data.rstrip()


def libdir_data_cb(obj, ev):
    global POLKIT_HELPER_LIBDIR
    POLKIT_HELPER_LIBDIR = ev.data.rstrip()


def error_cb(obj, ev):
    global pk_path_error
    log.error("Error: " + ev.data)
    pk_path_error = True

prefix_call = Exe(
    "pkg-config --variable prefix polkit-agent-1",
    ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_WRITE | ECORE_EXE_PIPE_ERROR
    )
prefix_call.on_data_event_add(prefix_data_cb)
prefix_call.on_error_event_add(error_cb)
prefix_call.on_del_event_add(lambda x, y: elementary.exit())

elementary.run()

libdir_call = Exe(
    "pkg-config --variable libdir polkit-agent-1",
    ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_WRITE | ECORE_EXE_PIPE_ERROR
    )
libdir_call.on_data_event_add(libdir_data_cb)
libdir_call.on_error_event_add(error_cb)
libdir_call.on_del_event_add(lambda x, y: elementary.exit())

elementary.run()

if pk_path_error:
    raise SystemExit("Error when getting prefix for agent helper binary")

import os

POLKIT_HELPER_PATH = ""

for pk_name in "polkit-1", "policykit-1":
    path = os.path.join(
        POLKIT_HELPER_PREFIX, "lib", pk_name, "polkit-agent-helper-1"
        )
    if os.path.exists(path):
        POLKIT_HELPER_PATH = path
        break
    path = os.path.join(
        POLKIT_HELPER_LIBDIR, pk_name, "polkit-agent-helper-1"
        )
    if os.path.exists(path):
        POLKIT_HELPER_PATH = path
        break

if not POLKIT_HELPER_PATH:
    raise SystemExit("Could not determine polkit helper path.")

import pwd
import locale

import dbus
from efl.dbus_mainloop import DBusEcoreMainLoop
ml = DBusEcoreMainLoop()
dbus.set_default_main_loop(ml)
import dbus.service

sysbus = dbus.SystemBus()
try:
    dbusname = dbus.service.BusName(AGENT_DBUS_NAME, sysbus)
except dbus.DBusException as e:
    log.debug(e)

pk_auth_obj = sysbus.get_object(
    "org.freedesktop.PolicyKit1",
    "/org/freedesktop/PolicyKit1/Authority"
    )
pk_auth_iface = dbus.Interface(
    pk_auth_obj,
    "org.freedesktop.PolicyKit1.Authority"
    )

pid = os.getpid()
uid = os.getuid()
ck_ses = None
sd_ses = None

try:
    ck_obj = sysbus.get_object(
        "org.freedesktop.ConsoleKit",
        "/org/freedesktop/ConsoleKit/Manager"
        )
    ck_iface = dbus.Interface(
        ck_obj,
        "org.freedesktop.ConsoleKit.Manager"
        )
    ck_ses = ck_iface.GetCurrentSession()
    log.debug("Consolekit session id is %s" % (ck_ses))
except dbus.DBusException as e:
    log.debug(e)

try:
    sd_obj = sysbus.get_object(
        "org.freedesktop.login1",
        "/org/freedesktop/login1"
        )
    sd_man_iface = dbus.Interface(
        sd_obj,
        "org.freedesktop.login1.Manager"
        )
    ses_path = sd_man_iface.GetSessionByPID(pid)
    log.debug("Systemd session dbus path is %s" % (ses_path))
    ses_obj = sysbus.get_object(
        "org.freedesktop.login1",
        ses_path
        )
    ses_props_iface = dbus.Interface(
        ses_obj,
        "org.freedesktop.DBus.Properties"
        )
    sd_ses = ses_props_iface.Get("org.freedesktop.login1.Session", "Id")
    ses_user = ses_props_iface.Get("org.freedesktop.login1.Session", "User")
    ses_uid, ses_user_path = ses_user
    assert ses_uid == uid, "Session uid %d != process uid %d" % (ses_uid, uid)
    log.debug("Systemd session id is %s" % (sd_ses))
except dbus.DBusException as e:
    log.debug(e)

if ck_ses and sd_ses:
    log.warn(_("Both consolekit and systemd sessions found, using consolekit"))
    ses = ck_ses
elif ck_ses:
    ses = ck_ses
elif sd_ses:
    ses = sd_ses
else:
    raise SystemExit(
        _("Could not get session info by either systemd nor consolekit!")
        )


class DetailsItemClass(GenlistItemClass):

    def text_get(self, genlist, part, item_data):
        parts = "polkit.text.header", "polkit.text.desc"
        return item_data[parts.index(part)]


class AuthCancelled(dbus.DBusException):
    _dbus_error_name = "org.freedesktop.PolicyKit1.Error.Cancelled"


class EFLPolkitAuthAgent(dbus.service.Object):

    dialogs = {}
    helper_processes = {}

    def __init__(self):
        dbus.service.Object.__init__(
            self, sysbus, AGENT_DBUS_PATH, AGENT_DBUS_NAME
            )

    @dbus.service.method(
        dbus_interface="org.freedesktop.PolicyKit1.AuthenticationAgent",
        in_signature="sssa{ss}sa(sa{sv})",
        async_callbacks=("return_cb", "error_cb")
        )
    def BeginAuthentication(
        self, action_id, message, icon_name, details, cookie, identities,
        return_cb, error_cb
            ):
        log.info(_("Begin authentication"))
        try:
            names = []
            for i in identities:
                name_type, name_dict = i
                if name_type == "unix-user":
                    auth_id = name_dict["uid"]
                    names.append(pwd.getpwuid(auth_id))
                # elif name_type == "unix-group":
                #     auth_id = name_dict["gid"]
                #     name = pdf.getpwgid(auth_id)
                else:
                    log.error(
                        _("Unsupported authentication type %s") % (name_type)
                        )
            self.dialogs[cookie] = AuthenticationDialog(
                self,
                action_id, message, icon_name, details, cookie, names,
                return_cb, error_cb
                )
        except Exception:
            log.exception("Exception in method BeginAuthentication")

    @dbus.service.method(
        dbus_interface="org.freedesktop.PolicyKit1.AuthenticationAgent",
        in_signature="s"
        )
    def CancelAuthentication(self, cookie):
        try:
            self.dialogs[cookie].delete()  # FIXME
        except Exception:
            log.exception("Exception in method CancelAuthentication")
        else:
            log.info(_("Authentication cancelled"))

    @dbus.service.method(
        dbus_interface="org.enlightenment.polkit"
        )
    def Quit(self):
        self.unregister()
        elementary.exit()

    def authenticate(self, name, cookie, return_cb, error_cb):
        s = "%s %s %s" % (POLKIT_HELPER_PATH, name, cookie)

        def exe_data_cb(obj, event):
            d = event.data
            log.debug("Data: " + str(d).rstrip())
            dlg = self.dialogs[cookie]
            if d.startswith("PAM_PROMPT"):
                if "Password" in d:
                    log.debug("PAM requested a password")
                    dlg.ask_password(self.send_password, obj)
            elif d.startswith("PAM_TEXT_INFO "):
                log.debug("PAM sent text info")
                dlg.show_info(d[14:].rstrip())
            elif d.startswith("PAM_ERROR_MSG "):
                log.debug("PAM sent an error")
                dlg.show_error(d[14:].rstrip())
            elif "SUCCESS" in d:
                log.info(_("Authentication successful!"))
                dlg.auth_success()
                return_cb()
            elif "FAILURE" in d:
                log.info(_("Authentication failed!"))
                dlg.auth_failure()
                return_cb()
            else:
                log.warn("Unknown Exe Data: " + str(d))

        def exe_error_cb(obj, event):
            log.error("Error: " + str(event.data))

        def exe_del_cb(*args):
            log.debug("Helper process exited")
            self.helper_processes.pop(cookie)

        e = self.helper_processes[cookie] = Exe(
            s,
            ECORE_EXE_PIPE_READ | ECORE_EXE_PIPE_WRITE | ECORE_EXE_PIPE_ERROR
            )
        e.on_data_event_add(exe_data_cb)
        e.on_error_event_add(exe_error_cb)
        e.on_del_event_add(exe_del_cb)

    def send_password(self, exe_obj, password):
        log.debug("Sending password")
        exe_obj.send(password + "\n")

    def cancel_authentication(self, cookie, error_cb):
        log.info(_("Authentication cancelled"))
        error_cb(AuthCancelled())
        if cookie in self.helper_processes:
            self.helper_processes[cookie].terminate()

    def register(self):
        log.info(_("Registering agent"))

        defloc = locale.getdefaultlocale()
        if defloc[0] and defloc[1]:
            defloc = ".".join(defloc)
        else:
            defloc = "C"

        try:
            pk_auth_iface.RegisterAuthenticationAgent(
                (
                    "unix-session",
                    {"session-id": dbus.String(ses)}
                ),
                defloc,
                AGENT_DBUS_PATH
            )
        except dbus.DBusException:
            log.exception(_("Agent registration failed"))
            elementary.exit()
        else:
            log.info(_("Agent registered"))

    def unregister(self):
        log.info(_("Unregistering agent"))
        try:
            pk_auth_iface.UnregisterAuthenticationAgent(
                (
                    "unix-session",
                    {"session-id": dbus.String(ses)}
                ),
                AGENT_DBUS_PATH
            )
        except dbus.DBusException:
            log.exception(_("Agent unregistration failed"))
        else:
            log.info(_("Agent unregistered"))

    def query_details_from_action_id(self, action_id):
        try:
            actions = pk_auth_iface.EnumerateActions(
                ".".join(locale.getdefaultlocale())
                )
        except dbus.DBusException as e:
            log.debug(e)
        else:
            for a in actions:
                if a[0] == action_id:
                    return a


class AuthenticationDialog(Window):

    cancelled = False
    authenticating = False
    logger_entry = None
    hiding = False
    pw_visible = False

    def __init__(
        self, agent,
        action_id, message, icon_name, details, cookie, names,
        return_cb, error_cb
            ):
        Window.__init__(
            self,
            "efl-polkit-authentication-agent", ELM_WIN_DOCK,
            title=_("Authentication request"),
        )
        self.alpha = True
        self.override = True
        self.size = self.screen_size[2], self.screen_size[3]

        def canvas_focus_in_cb(canvas):
            log.debug("Dialog canvas focus in")

        def canvas_focus_out_cb(canvas):
            log.debug("Dialog canvas focus out")
            self.activate()

        self.evas.event_callback_add(
            EVAS_CALLBACK_CANVAS_FOCUS_IN, canvas_focus_in_cb)
        self.evas.event_callback_add(
            EVAS_CALLBACK_CANVAS_FOCUS_OUT, canvas_focus_out_cb)

        script_path = os.path.dirname(os.path.abspath(__file__))
        for td in (
            os.path.join(script_path, "data", "theme", "default.edj"),
            os.path.join(
                sys.prefix, "share", "polkit-efl", "theme", "default.edj"
                )
                ):
            if os.path.exists(td):
                Theme.default_get().extension_add(td)

        # === MAIN LAYOUT ===
        layout = self.layout = Layout(
            self, size_hint_weight=EXPAND_BOTH,
            theme=("layout", "polkit-auth", "base")
            )
        layout.part_text_set("polkit.text.info", message)

        self.resize_object_add(layout)

        def show_done_cb(*args):
            log.debug("Dialog now visible")
            if USE_ECORE_X:
                xid = self.xwindow_xid
                xwin = ecore_x.Window_from_xid(xid)
                if not xwin.keyboard_grab():
                    pass  # TODO: Cancel auth? Or just warn user
            self.activate()

        def hide_done_cb(*args):
            log.debug("Dialog now hidden, deleting")
            if USE_ECORE_X:
                ecore_x.keyboard_ungrab()
            self.hiding = False
            dlg = agent.dialogs.pop(cookie)
            dlg.delete()

        def password_show_done_cb(*args):
            log.debug("Password entry visible")
            layout.text_set("polkit.text.help", _("Enter password:"))
            try:
                ic = self.layout.content_get("polkit.swallow.info")
                ic.standard = "keyboard"
            except Exception as e:
                log.debug(e)
            pw_entry.focus = True

        def password_hide_done_cb(*args):
            log.debug("Password entry hidden")
            layout.text_set(
                "polkit.text.help",
                _("Select Authentication ID")
                )
            for w in ok_bt, name_sel:
                w.disabled = False
            try:
                ic = self.layout.content_get("polkit.swallow.info")
                ic.standard = icon_name
            except Exception as e:
                log.debug(e)
            pw_entry.focus = False
            layout.focus = True

        layout.signal_callback_add(
            "polkit,show,done", "polkit", show_done_cb
            )
        layout.signal_callback_add(
            "polkit,hide,done", "polkit", hide_done_cb
            )
        layout.signal_callback_add(
            "polkit,password,visible,done", "polkit", password_show_done_cb
            )
        layout.signal_callback_add(
            "polkit,password,hidden,done", "polkit", password_hide_done_cb
            )
        layout.signal_callback_add(
            "edje,change,file", "edje", self.resend_signals
            )

        # === PASSWORD ENTRY ===
        pw_entry = self.pw_entry = PasswordEntry(layout)
        layout.content_set("polkit.swallow.password", pw_entry)

        layout.text_set(
            "polkit.text.help",
            _("Select Authentication ID")
            )

        # === AUTH ID SELECTOR ===
        name_sel = TrackingHoversel(
            self, size_hint_align=FILL_HORIZ,
            #text=_("Select Authentication ID")
            )
        for i in names:
            TrackingHoverselItem(i[0]).add_to(name_sel)

        def selected_cb(obj, it):
            log.debug("Name %s selected" % (it.text))
            pw_entry.text = ""
            name_sel.text = it.text
            ok_cb(None)

        name_sel.callback_selected_add(selected_cb)
        name_sel.display_item(name_sel.items[0])

        layout.content_set("polkit.swallow.users", name_sel)

        # === BUTTON BOX ===
        bt_box = self.bt_box = Box(
            layout, size_hint_align=FILL_HORIZ, horizontal=True,
            focus_allow=False
            )

        def ok_cb(obj):
            name = name_sel.text
            if name == _("Select Authentication ID"):
                return
            for w in ok_bt, name_sel:
                w.disabled = True
            self.authenticating = True
            log.debug("Auth ID selected, authenticating...")
            agent.authenticate(name, cookie, return_cb, error_cb)

        def cancel_cb(obj):
            log.debug("Auth cancel selected")
            self.hiding = True
            self.layout.signal_emit("polkit,hide", "polkit")
            agent.cancel_authentication(cookie, error_cb)

        ok_bt = Button(bt_box, text=_("OK"), focus_allow=False)
        ok_bt.callback_clicked_add(ok_cb)
        ca_bt = Button(bt_box, text=_("Cancel"), focus_allow=False)
        ca_bt.callback_clicked_add(cancel_cb)
        sep = Separator(bt_box, horizontal=False)

        def det_cb(obj):
            action_details = agent.query_details_from_action_id(action_id)
            Details(self, action_details, details)

        act_bt = Button(bt_box, text=_("Details"), focus_allow=False)
        act_bt.callback_clicked_add(det_cb)

        for w in ok_bt, ca_bt, sep, act_bt:
            bt_box.pack_end(w)
            w.show()

        layout.content_set("polkit.swallow.buttons", bt_box)

        # === KEYBOARD HANDLER ===
        def key_handler(obj, src, ev_type, event, *args, **kwargs):
            if not ev_type == EVAS_CALLBACK_KEY_UP:
                return
            elif event.key == "Up":
                name_sel.display_prev()
            elif event.key == "Down":
                name_sel.display_next()
            elif event.key == "Return" and not self.authenticating:
                log.debug("Handling key Return")
                ok_cb(obj)
            elif event.key == "Escape":
                log.debug("Handling key Escape")
                cancel_cb(obj)

        layout.elm_event_callback_add(key_handler)

        # === ICON ===
        app_ic = Icon(layout)

        if icon_name:
            try:
                app_ic.standard = icon_name
            except Exception:
                log.debug("Icon not found: %s" % (icon_name))
                try:
                    app_ic.standard = "gtk-dialog-authentication"
                except Exception:
                    log.debug("and stock authentication icon not found")
        else:
            try:
                app_ic.standard = "gtk-dialog-authentication"
            except Exception:
                log.debug("Could not find stock authentication icon")

        app_ic.size_hint_min = 32, 32
        layout.content_set("polkit.swallow.info", app_ic)

        layout.show()
        layout.focus = True
        self.show()

    def ask_password(self, func, exe_obj):
        log.debug("Displaying password entry")
        self.pw_entry.send_func = func
        self.pw_entry.exe_obj = exe_obj
        self.pw_visible = True
        self.layout.signal_emit("polkit,password,visible", "polkit")

    def show_info(self, msg):
        log.debug("Displaying text info")
        self.layout.part_text_set("polkit.text.info", msg)
        if "fingerprint" in msg:
            try:
                ic = self.layout.content_get("polkit.swallow.info")
                ic.standard = "scanner"
            except Exception as e:
                log.debug(e)

    def show_error(self, msg):
        log.debug("Displaying error message")
        self.layout.part_text_set("polkit.text.info", msg)

    def auth_success(self):
        log.debug("Sending signal polkit,login,success")
        self.layout.signal_emit("polkit,login,success", "polkit")
        self.layout.part_text_set(
            "polkit.text.info", _("Authentication successful!")
            )
        # TODO: Display a desktop notification here?

    def auth_failure(self):
        log.debug("Senging signal polkit,login,fail")
        self.layout.signal_emit("polkit,login,fail", "polkit")
        self.layout.part_text_set(
            "polkit.text.info", _("Authentication failed!")
            )
        # TODO: Display a desktop notification here?

    def resend_signals(self, *args):
        if self.hiding:
            self.layout.signal_emit("polkit,hide", "polkit")
        else:
            self.layout.signal_emit("polkit,visible", "polkit")
        if self.pw_visible:
            self.layout.signal_emit("polkit,password,hidden", "polkit")
        else:
            self.layout.signal_emit("polkit,password,visible", "polkit")


class PasswordEntry(Entry):

    send_func = None
    exe_obj = None

    def __init__(self, parent):
        Entry.__init__(
            self, parent, single_line=True, size_hint_align=FILL_BOTH,
            password=True
            )

        def ok_cb(obj):
            self.disabled = True
            self.send_func(self.exe_obj, self.entry)
            parent.signal_emit("polkit,authenticating", "polkit")

        def cancel_cb(obj):
            parent.top_widget.authenticating = False
            parent.focus = False
            parent.top_widget.pw_visible = False
            parent.signal_emit("polkit,password,hidden", "polkit")
            self.exe_obj.terminate()

        def key_handler(obj, src, ev_type, event, *args, **kwargs):
            if not ev_type == EVAS_CALLBACK_KEY_UP:
                return
            if event.key == "Return":
                log.debug("Handling key Return in pw entry")
                ok_cb(obj)
                return True
            elif event.key == "Escape":
                log.debug("Handling key Escape in pw entry")
                cancel_cb(obj)
                return True

        self.elm_event_callback_add(key_handler)


class Details(InnerWindow):
    def __init__(self, parent, action_details, process_details):

        InnerWindow.__init__(self, parent)

        box = Box(self, size_hint_weight=EXPAND_BOTH)
        self.content = box

        gl = Genlist(
            box, homogeneous=True, mode=ELM_LIST_COMPRESS,
            size_hint_weight=EXPAND_BOTH, size_hint_align=FILL_BOTH
            )

        def group_text_get(obj, part, item_data):
            return item_data

        gl_gic = GenlistItemClass(text_get_func=group_text_get)
        gl_ic = DetailsItemClass(item_style="polkit_action")

        if action_details is None:
            # NOTE: Action here means the (system/root/etc.) action we are
            #       authenticating for. We are trying to query detailed
            #       information but were unable to get them for some
            #       reason.
            log.info(_("Action details not available"))
        else:
            action_id, description, message, vendor_name, vendor_url, \
                icon_name, implicit_any, implicit_inactive, implicit_active, \
                annotations \
                = action_details

            titles = (
                _("Action ID"),
                _("Description"),
                _("Message"),
                # NOTE: Provider of the action
                _("Action vendor name"),
                # NOTE: Url for the provider of an action
                _("Action vendor url"),
                # _("Icon name"),
                # _("Implicit authorization for any subject"),
                # _("Implicit authorization for inactive user sessions"),
                # _("Implicit authorization for active user sessions"),
                # _("Annotations")
                )

            # implicit_authorization_enum = (
            #     _("Not authorized"),
            #     _("Authentication required"),
            #     _("Administrator authentication required"),
            #     _("Authentication required, retained if obtained"),
            #     _("Administrator authentication required, retained if obtained"),
            #     _("Authorized")
            #     )

            title_i = GenlistItem(
                gl_gic, _("Action"), None, ELM_GENLIST_ITEM_GROUP
                ).append_to(gl)

            for i, value in enumerate(action_details):
                try:
                    if isinstance(value, dbus.Struct):
                        value = " ".join(value)
                    elif isinstance(value, dbus.Dictionary):
                        tmp = []
                        for k, v in value.items():
                            tmp.append(": ".join((k, v)))
                        value = "; ".join(tmp)
                    elif isinstance(value, float):
                        value = str(datetime.fromtimestamp(round(value)))
                    # elif isinstance(value, dbus.UInt32):
                    #     value = implicit_authorization_enum[value]
                except Exception as e:
                    log.debug(e)
                else:
                    try:
                        header = titles[i]
                    except IndexError:
                        continue
                    else:
                        desc = value
                        GenlistItem(
                            gl_ic, (header, desc), None, ELM_GENLIST_ITEM_NONE
                            ).append_to(gl)

        try:
            import psutil
            if not StrictVersion(psutil.__version__) >= StrictVersion("2.0.0"):
                raise ImportError
        except Exception:
            log.info(
                # NOTE: psutil is a Python package, leave the word untranslated
                _(
                    "Process information not available without the package "
                    "psutil, version >= 2.0.0."
                    )
                )
        else:
            from psutil import Process
            for desc, pid in process_details.items():
                titles = {
                    "polkit.caller-pid": _("Caller process"),
                    "polkit.subject-pid": _("Subject process")
                    }
                title = titles.get(desc, desc)
                title_i = GenlistItem(
                    gl_gic, title, None, ELM_GENLIST_ITEM_GROUP
                    ).append_to(gl)
                p = Process(int(pid))
                for row in (
                    # NOTE: Name of a process
                    (_("Name"), p.name),
                    # NOTE: Process executable as an absolute path
                    (_("Executable path"), p.exe),
                    (_("Working directory"), p.cwd),
                    (_("Command line"), p.cmdline),
                    (_("Username"), p.username),
                    (_("Created"), p.create_time),
                    # NOTE: As in /dev/pts/*
                    (_("Terminal"), p.terminal)
                        ):
                    try:
                        key, v = row
                        value = v()
                        if isinstance(value, list):
                            value = " ".join(value)
                        elif isinstance(value, float):
                            value = str(datetime.fromtimestamp(round(value)))
                    except Exception as e:
                        log.debug(e)
                    else:
                        header = row[0]
                        desc = value
                        GenlistItem(
                            gl_ic, (header, desc), title_i,
                            ELM_GENLIST_ITEM_NONE
                            ).append_to(gl)

        box.pack_end(gl)
        gl.show()

        close_bt = Button(box, text=_("Close"))
        close_bt.callback_clicked_add(lambda x: self.delete())
        close_bt.show()
        box.pack_end(close_bt)

        box.show()

        self.activate()


class TrackingHoverselItem(HoverselItem):
    def delete(self):
        sel_i = self.widget.displayed_item
        if sel_i == self:
            self.widget.displayed_item = None
        HoverselItem.delete(self)


class TrackingHoversel(Hoversel):

    displayed_item = None

    def __init__(self, *args, **kwargs):
        Hoversel.__init__(self, *args, **kwargs)

        def selected_cb(obj, item):
            self.displayed_item = item

        self.callback_selected_add(selected_cb)

    def clear(self):
        self.displayed_item = None
        Hoversel.clear(self)

    def display_item(self, item):
        self.displayed_item = item
        self.text = item.text

    def display_next(self):
        if not self.items:
            return
        if self.displayed_item is None:
            self.displayed_item = self.items[0]
        elif len(self.items) < 2:
            return
        else:
            i = self.items.index(self.displayed_item)
            self.displayed_item = self.items[i+1]
        self.text = self.displayed_item.text

    def display_prev(self):
        if not self.items:
            return
        if self.displayed_item is None:
            self.displayed_item = self.items[len(self.items)-1]
        elif len(self.items) < 2:
            return
        else:
            i = self.items.index(self.displayed_item)
            self.displayed_item = self.items[i-1]
        self.text = self.displayed_item.text

dbo = EFLPolkitAuthAgent()
dbo.register()

elementary.run()

dbo.unregister()
elementary.shutdown()
