#! /usr/bin/python3

#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#
#                                                                             #
# ndisgtk - A GTK frontend for the ndiswrapper wireless driver tool           #
#                                                                             #
# Copyright (C) 2005, Sam Pohlenz <retrix@internode.on.net>                   #
# Copyright (C) 2007-2008 Julian Andres Klode <jak@jak-linux.org>             #
#                                                                             #
# 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. #
#                                                                             #
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#

import sys
import os
import subprocess
import re


def getoutput(*cmd):
    '''Like commands.getoutput, but uses subprocess'''
    myproc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                              universal_newlines=True)
    value  =  myproc.stdout.read()
    retcode = myproc.wait()
    return value, retcode

# Attempt to load GTK bindings
try:
    from gi.repository import Gtk
    from gi.repository import Gdk
    from gi.repository import GdkPixbuf
    from gi.repository import GObject
except ImportError:
    print("Failed to load GTK bindings. Please check your Gnome installation.")
    sys.exit(1)

# Internationalization
import locale
import gettext
locale.setlocale(locale.LC_ALL, "")
locale.bindtextdomain("ndisgtk", "/usr/share/locale")
gettext.bindtextdomain("ndisgtk", "/usr/share/locale")
gettext.textdomain("ndisgtk")
gettext.install("ndisgtk", "/usr/share/locale")

# Data directory
DATA_DIR = "/usr/share/ndisgtk"

def error_dialog(message, parent = None):
    """
    Displays an error message.
    """

    dialog = Gtk.MessageDialog(parent = parent, type = Gtk.MessageType.ERROR, buttons = Gtk.ButtonsType.OK, flags = Gtk.DialogFlags.MODAL)
    dialog.set_markup(message)

    result = dialog.run()
    dialog.destroy()

class NdisGTK:
    """
    Main application class.
    """

    def __init__(self, kde=False):
        """
        Initializes the application.
        """

        # Setup glade and signals
        self.signals = { "gtk_main_quit": Gtk.main_quit,
                         "on_install_driver_clicked": self.install_driver_open,
                         "on_remove_driver_clicked": self.remove_driver,
                         "on_driver_list_cursor_changed": self.cursor_changed,
                         "install_dialog_close": self.install_dialog_close,
                         "install_button_clicked": self.install_driver,
                         "network_button_clicked": self.config_network,
                         "help_button_clicked": self.show_help,
                         "drag_motion": self.drag_motion,
                         "drag_data_received": self.drag_data_received }

        self.builder = Gtk.Builder()
        self.builder.set_translation_domain("ndisgtk")
        self.builder.add_from_file(DATA_DIR + "/ndisgtk.ui")
        self.builder.connect_signals(self.signals)

        # Get handle to window
        self.window = self.builder.get_object("ndiswrapper_main")

        # Load icon
        icon_theme = Gtk.IconTheme.get_default()
        self.wifi_icon = icon_theme.load_icon('ndisgtk', 48, 0)
        self.wifi_error_icon = icon_theme.load_icon('ndisgtk-error', 48, 0)
        self.window.set_icon(self.wifi_icon)

        # Get handle to 'Remove Driver' button
        self.remove_driver = self.builder.get_object("remove_driver")

        # Get handle to 'Install Driver' dialog
        self.install_dialog = self.builder.get_object("install_dialog")
        self.install_dialog.set_transient_for(self.window)

        # Get handle to file chooser
        self.file_chooser = self.builder.get_object("filechooser")

        # Enable drag-and-drop
        self.window.drag_dest_set(Gtk.DestDefaults.DROP, [Gtk.TargetEntry.new("text/plain", 0, 80)], Gdk.DragAction.COPY)

        # Setup driver list
        self.setup_driver_list()

        # Use KDE network admin?
        self.kde = kde

        Gtk.main()

    def setup_driver_list(self):
        """
        Sets up the driver list and list widget.
        """

        # Initialize lists
        self.driver_list = []
        self.driver_list_store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
        self.driver_list_widget = self.builder.get_object("driver_list")

        # Set up columns
        first = Gtk.TreeViewColumn("icon", Gtk.CellRendererPixbuf(), pixbuf = 0)
        second = Gtk.TreeViewColumn("desc", Gtk.CellRendererText(), markup = 1)

        self.driver_list_widget.append_column(first)
        self.driver_list_widget.append_column(second)

        # Set list model for widget
        self.driver_list_widget.set_model(self.driver_list_store)

        # Load the list of drivers
        self.get_driver_list()

    def get_driver_list(self):
        """
        Gets the list of drivers from ndiswrapper.
        """

        # Clear driver list
        self.driver_list_store.clear()
        self.driver_list = []

        # Run the ndiswrapper list command
        output,retcode = getoutput("ndiswrapper", "-l")

        # Ignore warnings from modprobe.
        output_lines = []
        for line in output.splitlines():
            if not line.startswith("WARNING: All config files need .conf"):
                output_lines.append(line)
        output = '\n'.join(output_lines)

        if "WARNING" in output:
            error_dialog(_("Unable to see if hardware is present."), self.window)

        if ": driver" in output or ": invalid" in output:
            # Newer ndiswrapper versions
            # Drivers found
            output = output.splitlines()

            for i in range(0, len(output)):
                line = output[i]
                if len(output) > i+1: line2 = output[i+1]
                else: line2=""

                if "present" in line2: hardware_present = _("Yes")
                else: hardware_present = _("No")

                # Get driver name
                p = re.compile(".*:")						# match up to first tab
                driver_name = p.search(line).group()[:-1].strip()	# strip trailing space

                # Add to list
                if "installed" in line:
                    self.driver_list.append(driver_name)
                    self.driver_list_store.append([self.wifi_icon,
                        _("<b>%s</b>\nHardware present: %s") % (driver_name, hardware_present)])
                elif "invalid" in line:
                    self.driver_list.append(driver_name)
                    self.driver_list_store.append([self.wifi_error_icon,
                        _("<b>%s</b>\nInvalid Driver!") % driver_name])

        elif "installed" in output or "Installed" in output or "invalid" in output:
            # Drivers found
            output = output.splitlines()
            for i in range(1, len(output)):
                line = output[i]

                if "hardware" in line: hardware_present = _("Yes")
                else: hardware_present = _("No")

                # Get driver name
                p = re.compile(".*\\t")						# match up to first tab
                driver_name = p.search(line).group()[:-1].strip()	# strip trailing space

                # Add to list

                if "installed" in line:
                    self.driver_list.append(driver_name)
                    self.driver_list_store.append([self.wifi_icon,
                        _("<b>%s</b>\nHardware present: %s") % (driver_name, hardware_present)])
                elif "invalid" in line:
                    self.driver_list.append(driver_name)
                    self.driver_list_store.append([self.wifi_icon,
                        _("<b>%s</b>\nInvalid Driver!") % driver_name])
        else:
            # No drivers installed
            pass

    def drag_motion(self, window, context, x, y, time):
        """
        Called whenever a drag motion is made.
        """

        context.drag_status(Gtk.gdk.ACTION_COPY, time)
        return True

    def drag_data_received(self, window, context, x, y, selection, info, timestamp):
        """
        Called when a file is dragged onto the main window.
        """

        file = selection.get_text().strip()

        if file.startswith("file://"):
            if file.endswith(".inf"):
                self.file_chooser.set_uri(file)
                self.install_driver_open()
            else:
                error_dialog(_("Please drag an '.inf' file instead."), self.window)

        return True

    def show_help(self, *args):
        """
        Displays the help window.
        Called when the 'Help' button is clicked.
        """

        # TODO: Implement
        error_dialog("Help", self.window)

    def config_network(self, *args):
        """
        Opens the network configuration tool.
        """
        # Run the command under the normal user.
        uid = int(os.getenv("SUDO_UID", 0))
        if uid == 0:
            import pwd
            user = os.getenv("USERNAME", pwd.getpwuid(0).pw_name)
            uid = pwd.getpwnam(user).pw_uid

        if "XAUTHORITY" in os.environ:
            # Create a new Xauthority for running the configuration tool.
            import shutil, tempfile
            (xauth_fd, xauth_name) = tempfile.mkstemp('.ndisgtk', 'xauth-')
            os.close(xauth_fd)
            shutil.copyfile(os.getenv("XAUTHORITY"), xauth_name)
            os.chown(xauth_name, uid, -1)

        fork = os.fork()
        if fork == 0:
            # Set the new Xauthority, switch user and run the command
            os.environ["XAUTHORITY"] = xauth_name
            os.setuid(uid)
            try:
                if self.kde:
                    subprocess.call(["kcmshell", "kcm_knetworkconfmodule"])
                else:
                    subprocess.call(["nm-connection-editor"])
            except Exception:
                try:
                    e = sys.exc_info()[1]
                    os._exit(e.errno)
                except AttributeError:
                    os._exit(1)
            os._exit(0)

        GObject.child_watch_add(fork, self.on_config_network_exited,
                                xauth_name)

    def on_config_network_exited(self, pid, status, xauth_name=None):
        """Called when the network configuration has been called"""
        errno = os.WEXITSTATUS(status)
        if errno == 2:
            error_dialog(_("Could not find a network configuration tool."))
        elif errno:
            print >> sys.stderr, "Configuration failed:", os.strerror(errno)
        os.unlink(xauth_name)

    def install_driver_open(self, *args):
        """
        Opens the install driver dialog.
        """

        self.install_dialog.show()

    def install_dialog_close(self, *args):
        """
        Closes the install driver dialog.
        """

        self.install_dialog.hide()
        return True;

    def install_driver(self, *args):
        """
        Installs a selected wireless driver.
        Called when the install dialog's 'Install Driver' button is clicked.
        """

        inf_file = self.file_chooser.get_filename()

        if inf_file == None:
            error_dialog(_("No file selected."), self.install_dialog)
        elif not inf_file.lower().endswith(".inf"):
            error_dialog(_("Not a valid driver .inf file."), self.install_dialog)
        else:
            # Attempt to install driver
            output, retcode = getoutput("ndiswrapper", "-i", inf_file)

            # Attempt to detect errors
            if "already" in output:
                #driver_name = output.split()[0]
                error_dialog(_("Driver is already installed."), self.install_dialog)
            elif retcode != 0:
                error_dialog(_("Error while installing.") , self.install_dialog)
            else:
                # Assume driver installed successfully. Set up and reload module
                subprocess.call(["ndiswrapper",  "-ma"])
                subprocess.call(["modprobe", "-r", "ndiswrapper"])
                out, ret = getoutput("modprobe", "ndiswrapper")
                if ret != 0:
                    error_text = _("Module could not be loaded. Error was:\n\n<i>%s</i>\n")
                    if "not found" in out:
                        error_text += _("Is the ndiswrapper module installed?")
                    error_dialog(error_text % out, self.install_dialog)

                self.get_driver_list()
                self.install_dialog_close()

    def remove_driver(self, *args):
        """
        Removes a driver after asking for confirmation.
        Called when the 'Remove Driver' button is clicked.
        """

        # Get the first selected driver
        if not self.driver_list_widget.get_cursor()[0]:
            return
        cursor = self.driver_list_widget.get_cursor()[0][0]
        driver_name = self.driver_list[cursor]

        # Get confirmation
        confirm = Gtk.MessageDialog(type = Gtk.MessageType.WARNING, buttons = Gtk.ButtonsType.YES_NO)
        confirm.set_markup(_("Are you sure you want to remove the <b>%s</b> driver?") % driver_name)
        result = confirm.run()

        if result == Gtk.ResponseType.YES:
            # Remove driver
            output,retcode = getoutput("ndiswrapper", "-e", driver_name)

            # Reload driver list
            self.get_driver_list()

        # Destroy the confirmation dialog
        confirm.destroy()

    def cursor_changed(self, *args):
        """
        Called when the currently selected driver changes.
        """

        # Allow the 'Remove Driver' button to be clicked
        self.remove_driver.set_sensitive(True)


if __name__ == '__main__':
    # Check for root privileges
    if os.getuid() != 0:
        error_dialog(_("Root or sudo privileges required!"))
        sys.exit(1)

    # Parse options and load GUI
    if "--kde" in sys.argv:
        NdisGTK(kde=True)
    else:
        NdisGTK()
