#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# 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.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""Restricted Modules Manager.

Usage:
  --update          Update the restricted modules list
  --check           Check for newly used restricted modules
  --enable MODULE   Enable the named module
  --disable MODULE  Disable the named module
  --check-composite Check if there is a graphics driver available that supports
                    composite and offer to enable it

Without arguments, immediately loads the restricted modules manager
window.
"""

__copyright__ = "Copyright © 2007 Canonical Ltd."
__author__    = "Scott James Remnant <scott@ubuntu.com>"


import os
import sys
import gettext
from optparse import OptionParser

import pygtk
pygtk.require("2.0")

import gobject
import gtk
import gtk.glade
import pynotify

import RestrictedManager
from RestrictedManager.core import *

from gettext import gettext as _

class ManagerWindow(object):
    def __init__(self, xml, handlers):
        self.xml = xml

        gtk.window_set_default_icon_name("restricted-manager")
        self.dialog = self.xml.get_widget("dialog_manager")
        self.dialog.connect("delete_event", self.on_dialog_delete_event)

        self.treeview = self.xml.get_widget("treeview_drivers")
        self.treeview.get_selection().set_mode(gtk.SELECTION_NONE)

        text_renderer = gtk.CellRendererText()
        col1 = gtk.TreeViewColumn(_("Driver"))
        col1.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col1.set_expand(True)
        col1.pack_start(text_renderer, True)
        col1.set_attributes(text_renderer, text=3)
        self.treeview.append_column(col1)

        toggle_renderer = gtk.CellRendererToggle()
        toggle_renderer.set_property("activatable", True)
        toggle_renderer.connect("toggled", self.on_renderer_toggled)
        col2 = gtk.TreeViewColumn(_("Enabled"))
        col2.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col2.set_expand(False)
        col2.pack_start(toggle_renderer, True)
        col2.set_attributes(toggle_renderer, active=4)
        self.treeview.append_column(col2)

        pixbuf_renderer = gtk.CellRendererPixbuf()
        text_renderer = gtk.CellRendererText()
        col3 = gtk.TreeViewColumn(_("Status"))
        col3.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col3.set_expand(False)
        col3.pack_start(pixbuf_renderer, False)
        col3.pack_end(text_renderer, True)
        col3.set_attributes(pixbuf_renderer, pixbuf=5)
        col3.set_attributes(text_renderer, text=6)
        self.treeview.append_column(col3)

        self.button_close = self.xml.get_widget("button_close")
        self.button_close.connect("clicked", self.on_button_close_clicked)

        self.handlers = handlers
        self.reset_model()

        # to remove the selection outline around the first item of the tree
        self.treeview.grab_focus()

        self.dialog.show()

    def reset_model(self):
        self.model = gtk.ListStore(gobject.TYPE_PYOBJECT,
                                   gobject.TYPE_PYOBJECT,
                                   gobject.TYPE_PYOBJECT,
                                   str, bool, gtk.gdk.Pixbuf, str)

        theme = gtk.icon_theme_get_default()
        handlers = sorted(self.handlers.values(), key=lambda x: x.description)
        for handler in handlers:
            is_enabled = handler.is_enabled()
            is_loaded = handler.is_loaded()
            if (is_enabled != is_loaded) and handler.is_changed():
                status, icon = _("Needs computer restart"), "reboot-notifier"
            elif is_loaded:
                status, icon = _("In use"), gtk.STOCK_YES
            else:
                status, icon = _("Not in use"), gtk.STOCK_NO
            try:
                pixbuf = theme.load_icon(icon, 16, gtk.ICON_LOOKUP_USE_BUILTIN)
            except:
                # do not fail if icon is not found, just create a 1px pixbuf
                pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 1, 1)
            self.model.append([ handler, is_enabled, is_loaded,
                                handler.description, is_enabled, pixbuf,
                                status ])

        self.treeview.set_model(self.model)

    def on_dialog_delete_event(self, *args):
       gtk.main_quit()

    def on_renderer_toggled(self, widget, path, *args):
        self.action(path)

    def action(self, path):
        handler = self.model[path][0]
        is_enabled = self.model[path][1]
        is_loaded = self.model[path][2]

        ch = handler.can_change()

        if not ch:
            dlg = ConfirmDialog(self.xml, handler, is_enabled, self.dialog)
            if dlg.run() != gtk.RESPONSE_OK:
                return

            self.treeview.set_sensitive(False)
            if is_enabled:
                handler.disable()

                if handler.is_loaded():
                    notify_reboot_required()
            else:
                handler.enable()
            self.treeview.set_sensitive(True)
        else:
            md = gtk.MessageDialog(self.dialog, type=gtk.MESSAGE_ERROR,
                        buttons=gtk.BUTTONS_CLOSE, message_format=ch)
            md.run()
            md.hide()
            return

        self.reset_model()
        self.treeview.grab_focus()

    def on_button_close_clicked(self, *args):
        gtk.main_quit()


class ConfirmDialog(object):
    def __init__(self, xml, handler, is_enabled, parent=None):
        self.xml = xml
        self.handler = handler
        self.is_enabled = is_enabled

        self.dialog = self.xml.get_widget("dialog_confirm")
        self.dialog.set_title("")
        if parent is not None:
            self.dialog.set_transient_for(parent)
        self.xml.get_widget("button_cancel").grab_focus()

        if self.is_enabled:
            frob = _("Disable the driver?")
            self.xml.get_widget("label_ok").set_label(_("_Disable Driver"))
        else:
            frob = _("Enable the driver?")
            self.xml.get_widget("label_ok").set_label(_("_Enable Driver"))

        label = self.xml.get_widget("label_text")
        label.set_label("<span weight=\"bold\" size=\"larger\">"
                        "%s"
                        "</span>\n\n"
                        "<span weight=\"bold\">%s</span>\n\n"
                        "%s" % (frob, handler.description, handler.rationale))

    def run(self):
        ret = self.dialog.run()
        self.dialog.hide()
        return ret


class Notification(object):
    def __init__(self):
        self.trayicon = gtk.status_icon_new_from_icon_name("restricted-manager")
        self.trayicon.connect("activate", self.open_manager)
        self.trayicon.set_tooltip(_("New Restricted Drivers in use"))

        gobject.timeout_add (500, self.show_notify)

    def show_notify(self):
        pynotify.init("restricted-manager")

        notify = pynotify.Notification(_("New restricted drivers in use"),
           "<span weight=\"bold\">%s</span>\n\n%s" % (
                _("In order for this computer to function properly, Ubuntu is "
                  "using driver software that cannot be supported"),
                _("Click on this icon to open the Restricted Drivers control "
                  "panel.")), "restricted-manager")
        notify.set_urgency(pynotify.URGENCY_NORMAL)
        notify.attach_to_status_icon(self.trayicon)
        notify.show()

    def open_manager(self, *args):
        runme_as_root()

def runme_as_root(extra_argv = []):
    """Execute myself through gksu."""

    argv = ["gksu", "-D", _("Restricted Drivers manager"),
              "--", sys.argv[0]] + extra_argv
    os.execvp("gksu", argv)

def gtk_idle():
    while gtk.events_pending():
        gtk.main_iteration(False)

def main(args, opts):
    try:
        xml = gtk.glade.XML("/usr/share/restricted-manager/manager.glade")
    except RuntimeError:
        glade_filename = os.path.join(os.path.dirname(RestrictedManager.__file__),
                                      "manager.glade")
        xml = gtk.glade.XML(glade_filename)

    RestrictedManager.core.package_install_idle_function = gtk_idle

    # set the title label here instead of the glade file, so that it can be
    # translated more easily
    xml.get_widget("label_heading").set_label('<span weight="bold">%s</span>\n\n%s' % (
        _("In order for this computer to function properly, Ubuntu may be using driver software that cannot be supported."),
        _("Because the software is proprietary, it cannot easily be changed to fix any future problems.")
    ))

    if not am_admin():
        print >>sys.stderr, _("Must be run as member of admin group")
        return 1

    if opts.update:
        load_restricted_list(force=True)
    elif opts.list:
        handlers = get_handlers()
        for handler in handlers.values():
            print handler.name
    elif opts.check:
        handlers = get_handlers()
        used = load_used_list()
        new = []

        for handler in handlers.values():
            if handler.name in used:
                continue
            elif handler.is_loaded():
                new.append(handler.name)

        if new:
            used.extend(new)
            save_used_list(used)

            Notification()
            gtk.main()
    elif opts.check_composite:
        handlers = get_handlers()
        try:
            handler = handlers["nvidia"]
            if handler.is_enabled():
                print "nvidia restricted driver is already enabled"
                return 1
        except KeyError:
            print "nvidia hardware not available"
            return 1

        runme_as_root(["--enable", "nvidia"])

    elif os.getuid() > 0:
        print >>sys.stderr, _("Must be run as root")
        return 1
    elif opts.enable:
        handlers = get_handlers()
        try:
            handler = handlers[opts.enable]
        except KeyError:
            print >>sys.stderr, _("Unknown module: %s") % opts.enable
            return 1

        if handler.is_enabled():
            print >>sys.stderr, _("%s is already enabled") % opts.enable
            return 1

        ch = handler.can_change()
        if ch:
            md = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
                        buttons=gtk.BUTTONS_CLOSE, message_format=ch)
            md.run()
            md.hide()
            return

        if ConfirmDialog(xml, handler, False).run() != gtk.RESPONSE_OK:
            return

        handler.enable()
    elif opts.disable:
        handlers = get_handlers()
        try:
            handler = handlers[opts.disable]
        except KeyError:
            print >>sys.stderr, "Unknown module: %s" % opts.disable
            return 1

        if not handler.is_enabled():
            print >>sys.stderr, "%s is already disabled" % opts.disable
            return 1

        ch = handler.can_change()
        if ch:
            md = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
                        buttons=gtk.BUTTONS_CLOSE, message_format=ch)
            md.run()
            md.hide()
            return

        if ConfirmDialog(xml, handler, True).run() != gtk.RESPONSE_OK:
            return

        handler.disable()
    else:
        # check whether we have restricted modules installed
        lrm = "linux-restricted-modules-" + os.uname()[2]
        if not DefaultHandler.package_installed(lrm):
            md = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
                        buttons=gtk.BUTTONS_CLOSE, message_format=_(
                        "You need to install the package\n\n  %s\n\nfor this program to work.") % lrm)
            md.run()
            md.hide()
            return 0

        handlers = get_handlers()

        if not handlers:
            md = gtk.MessageDialog(type=gtk.MESSAGE_INFO,
                        buttons=gtk.BUTTONS_CLOSE, message_format=_(
                        "Your hardware does not need any restricted drivers."))
            md.run()
            md.hide()
            return 0

        ManagerWindow(xml, handlers)
        RestrictedManager.core.package_install_xid = \
            xml.get_widget("dialog_manager").window.xid

        gtk.main()

def get_opt_args():
    parser = OptionParser()
    parser.add_option ("-u", "--update", action="store_true",
                       dest="update", default=False,
                       help=_("Update restricted modules list"))
    parser.add_option ("-c", "--check", action="store_true",
                       dest="check", default=False,
                       help=_("Check for newly used restricted modules"))
    parser.add_option ("-l", "--list", action="store_true",
                       dest="list", default=False,
                       help=_("List available restricted modules"))
    parser.add_option ("-C", "--check-composite", action="store_true",
                       dest="check_composite", default=False,
                       help=_("Check if there is a graphics driver available that supports composite and offer to enable it"))
    parser.add_option ("-e", "--enable", type="string",
                       dest="enable", default="", metavar="MODULE",
                       help=_("Enable the named module"))
    parser.add_option ("-d", "--disable", type="string",
                       dest="disable", default="", metavar="MODULE",
                       help=_("Disable the named module"))
    return parser.parse_args()

if __name__ == "__main__":
    gettext.textdomain("restricted-manager")
    gtk.glade.textdomain("restricted-manager")
    #gettext.bindtextdomain("restricted-manager", "po/mo")
    (opts, args) = get_opt_args()

    sys.exit(main(args, opts))
