#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# gcdemu: Gtk+ CDEmu GUI
# Copyright (C) 2006-2012 Rok Mandeljc
#
# 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 os
import subprocess
import weakref

# gettext
from gettext import gettext as _
from gettext import bindtextdomain, textdomain

# Gtk, GObject, Notify, etc.
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import GdkPixbuf
from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import Notify

# *** Globals ***
app_name = "gcdemu"
app_version = "1.5.0"
supported_daemon_interface_version = 3

# I18n
bindtextdomain(app_name)
textdomain(app_name)


################################################################################
#                   CDEmuDaemonProxy: Daemon proxy object                      #
################################################################################
class CDEmuDaemonProxy (GObject.GObject):
    _name = 'net.sf.cdemu.CDEMUD_Daemon'
    _object_path = '/CDEMUD_Daemon'
    
    def __init__ (self):
        super(CDEmuDaemonProxy, self).__init__()

        self.watcher_id = -1
        self.proxy_handler_ids = []
        self.proxy = None

    def cleanup (self):
        # Cancel watcher
        if self.watcher_id is not -1:
            Gio.bus_unwatch_name(self.watcher_id)
            self.watcher_id = -1

        # Clean up the proxy
        if self.proxy:
            for handler in self.proxy_handler_ids:
                self.proxy.disconnect(handler)
            self.proxy_handler_ids = []
            self.proxy = None

    def connect_to_bus (self, use_system, autostart_daemon):
        # Cleanup
        self.cleanup()
        
        # Get the bus
        if use_system:
            bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
        else:
            bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
        
        # Create proxy on our interface
        self.proxy = Gio.DBusProxy.new_sync(bus, 0, None, self._name, self._object_path, 'net.sf.cdemu.CDEMUD_Daemon', None)

        # Connect signal: g-signal
        handler_id = self.proxy.connect("g-signal", self.on_g_signal)
        self.proxy_handler_ids.append(handler_id)

        # Set up name watch; we do not try to autostart the daemon here
        self.watcher_id = Gio.bus_watch_name_on_connection(bus, self._name, 0, lambda c, n, no: self.emit("DaemonStarted"), lambda c, n: self.emit("DaemonStopped"))

        # Try to autostart daemon by pinging it
        if autostart_daemon:
            bus.call_sync(self._name, self._object_path, "org.freedesktop.DBus.Peer", "Ping", None, None, 0, -1, None)


    def on_g_signal (self, proxy, sender, signal, params):
        if signal == "DeviceMappingsReady":
            self.emit(signal)
        elif signal == "DeviceStatusChanged":
            self.emit(signal, params[0])
        elif signal == "DeviceOptionChanged":
            self.emit(signal, params[0], params[1])
        else:
            print "Unknown signal: ", sender, signal, params


    def GetDaemonVersion (self):
        return self.proxy.GetDaemonVersion()
        
    def GetLibraryVersion (self):
        return self.proxy.GetLibraryVersion()
        
    def GetDaemonInterfaceVersion (self):
        return self.proxy.GetDaemonInterfaceVersion()
        
    def EnumDaemonDebugMasks (self):
        return self.proxy.EnumDaemonDebugMasks()
        
    def EnumLibraryDebugMasks (self):
        return self.proxy.EnumLibraryDebugMasks()
        
    def EnumSupportedParsers (self):
        return self.proxy.EnumSupportedParsers()
        
    def EnumSupportedFragments (self):
        return self.proxy.EnumSupportedFragments()


    def GetNumberOfDevices (self):
        return self.proxy.GetNumberOfDevices()


    def DeviceGetMapping (self, device_number):
        return self.proxy.DeviceGetMapping('(i)', device_number)

    def DeviceGetStatus (self, device_number):
        return self.proxy.DeviceGetStatus('(i)', device_number)

    def DeviceLoad (self, device_number, file_names, parameters):
        return self.proxy.DeviceLoad('(iasa{sv})', device_number, file_names, parameters)

    def DeviceUnload (self, device_number):
        return self.proxy.DeviceUnload('(i)', device_number)

    def DeviceGetOption (self, device_number, option_name):
        return self.proxy.DeviceGetOption('(is)', device_number, option_name)

    def DeviceSetOption (self, device_number, option_name, option_value):
        return self.proxy.DeviceSetOption('(isv)', device_number, option_name, option_value)
 

GObject.type_register(CDEmuDaemonProxy)
GObject.signal_new("DaemonStarted", CDEmuDaemonProxy,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("DaemonStopped", CDEmuDaemonProxy,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())


GObject.signal_new("DeviceMappingsReady", CDEmuDaemonProxy,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())

GObject.signal_new("DeviceStatusChanged", CDEmuDaemonProxy,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_INT, ))

GObject.signal_new("DeviceOptionChanged", CDEmuDaemonProxy,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_INT, GObject.TYPE_STRING ))
    

################################################################################
#                       CDEmu: D-BUS interface object                          #
################################################################################
class CDEmu (GObject.GObject):
    def __init__ (self):
        super(CDEmu, self).__init__()

        self.daemon_proxy = CDEmuDaemonProxy()
        self.daemon_proxy.connect("DaemonStarted", lambda o: self.on_daemon_started())
        self.daemon_proxy.connect("DaemonStopped", lambda o: self.on_daemon_stopped())
        
        self.initial = True
        self.devices = []

    def cleanup (self):
        # Cleanup the devices
        self.devices = []


    def on_daemon_started (self):
        if not self.initial:
            self.emit("daemon-started")

        # Establish connection to daemon
        self.establish_connection()

        # Reset the initial run flag
        self.initial = False
            
    def on_daemon_stopped (self):
        if not self.initial:
            self.emit("daemon-stopped")

        # Cleanup
        self.emit("connection-lost")
        self.cleanup()

        # Reset the initial run flag
        self.initial = False

    def connect_to_bus (self, use_system, autostart_daemon):
        # Disconnect
        self.emit("connection-lost")

        # Cleanup
        self.cleanup()

        # Connect
        self.initial = True
        try:
            self.daemon_proxy.connect_to_bus(use_system, autostart_daemon)
        except GLib.GError as e:
            self.emit("error", _("Daemon autostart error"), _("Daemon autostart failed. Error:\n%s") % (e))
            

    def establish_connection (self):
        # Get the daemon interface version
        self.interface_version = self.daemon_proxy.GetDaemonInterfaceVersion()

        # Validate the daemon interface version
        if self.interface_version != supported_daemon_interface_version:
            self.emit("error", _("Incompatible daemon interface"), _("CDEmu daemon interface version %i detected, but version %i is required!") % (self.interface_version, supported_daemon_interface_version))
            # Cleanup
            self.cleanup()
            return

        # Get daemon version
        self.daemon_version = self.daemon_proxy.GetDaemonVersion()

        # Get library version
        self.library_version = self.daemon_proxy.GetLibraryVersion()

        # Get daemon debug masks
        self.daemon_debug_masks = self.daemon_proxy.EnumDaemonDebugMasks()

        # Get library debug masks
        self.library_debug_masks = self.daemon_proxy.EnumLibraryDebugMasks()

        # Get supported parsers
        self.supported_parsers = self.daemon_proxy.EnumSupportedParsers()

        # Get number of devices
        num_devices = self.daemon_proxy.GetNumberOfDevices()

        # Create devices
        for i in range(0, num_devices):
            self.devices.append(CDEmuDevice(i, self.daemon_proxy))

        self.daemon_proxy.connect("DeviceMappingsReady", lambda p: self.on_device_mappings_ready())

        # Emit signal
        self.emit("connection-established", num_devices)

    def on_device_mappings_ready (self):
        for device in self.devices:
            device.update_device_mapping()

GObject.type_register(CDEmu)
GObject.signal_new("error", CDEmu,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_STRING, GObject.TYPE_STRING, ))
GObject.signal_new("daemon-started", CDEmu,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("daemon-stopped", CDEmu,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("connection-established", CDEmu,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_INT, ))
GObject.signal_new("connection-lost", CDEmu,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())


################################################################################
#                     CDEmuDevice: device representation                       #
################################################################################
class CDEmuNeedPassword (Exception):
    def __init__ (self):
        pass

class CDEmuDevice (GObject.GObject):
    def __init__ (self, d, proxy):
        super(CDEmuDevice, self).__init__()

        self.dbus_signals = []

        # Device number
        self.number = d

        # Daemon proxy
        self.proxy = proxy

        # Device status
        [ self.loaded, self.filenames ] = self.proxy.DeviceGetStatus(self.number)

        # Device mapping
        [ self.sr_path, self.sg_path ] = self.proxy.DeviceGetMapping(self.number)

        # Get device options
        self.dpm_emulation = self.proxy.DeviceGetOption(self.number, "dpm-emulation")
        self.tr_emulation = self.proxy.DeviceGetOption(self.number, "tr-emulation")
        self.device_id = self.proxy.DeviceGetOption(self.number, "device-id")
        self.daemon_debug_mask = self.proxy.DeviceGetOption(self.number, "daemon-debug-mask")
        self.library_debug_mask = self.proxy.DeviceGetOption(self.number, "library-debug-mask")

        # Note: we connect the following using weakref, otherwise we get circular references
        # and devices never get destroyed
        self.proxy.connect("DeviceStatusChanged", lambda p, d, obj=weakref.ref(self): obj().on_status_changed(d) if obj() else None)
        self.proxy.connect("DeviceOptionChanged", lambda p, d, o, obj=weakref.ref(self): obj().on_option_changed(d, o) if obj() else None)


    def on_status_changed (self, d):
        if d != self.number:
            return

        [ self.loaded, self.filenames ] = self.proxy.DeviceGetStatus(self.number)
        self.emit("status-changed")

    def on_option_changed (self, d, option):
        if d != self.number:
            return

        if option == "device-id":
            self.device_id = self.proxy.DeviceGetOption(self.number, "device-id")
            self.emit("device-id-changed")
        elif option == "dpm-emulation":
            self.dpm_emulation = self.proxy.DeviceGetOption(self.number, "dpm-emulation")
            self.emit("dpm-emulation-changed")
        elif option == "tr-emulation":
            self.tr_emulation = self.proxy.DeviceGetOption(self.number, "tr-emulation")
            self.emit("tr-emulation-changed")
        elif option == "daemon-debug-mask":
            self.daemon_debug_mask = self.proxy.DeviceGetOption(self.number, "daemon-debug-mask")
            self.emit("daemon-debug-mask-changed")
        elif option == "library-debug-mask":
            self.library_debug_mask = self.proxy.DeviceGetOption(self.number, "library-debug-mask")
            self.emit("library-debug-mask-changed")
        else:
            print ("Unknown option: %s!" % option)

    def unload_device (self):
        try:
            self.proxy.DeviceUnload(self.number)
        except GLib.GError as e:
            self.emit("error", _("Failed to unload device #%02d:\n%s") % (self.number, e))


    def load_device (self, filenames, params = {}):
        try:
            self.proxy.DeviceLoad(self.number, filenames, params)
        except GLib.GError as e:
            if "net.sf.cdemu.CDEMUD_Daemon.libMirage.NeedPassword" in str(e):
                # Need password, raise proper exception...
                raise CDEmuNeedPassword()
            else:
                self.emit("error", _("Failed to load image %s to device #%02d:\n%s") % (";".join(filenames), self.number, e))


    def set_device_id (self, value):
        if self.device_id == value:
            return

        try:
            self.proxy.DeviceSetOption(self.number, "device-id", GLib.Variant('(ssss)', value))
        except GLib.GError as e:
            self.emit("error", _("Failed to set device ID for device #%02d to %s:\n%s") % (self.number, value, e))

    def set_dpm_emulation (self, value):
        if self.dpm_emulation == value:
            return

        try:
            self.proxy.DeviceSetOption(self.number, "dpm-emulation", GLib.Variant('b', value))
        except GLib.GError as e:
            self.emit("error", _("Failed to set DPM emulation for device #%02d to %i:\n%s") % (self.number, value, e))


    def set_tr_emulation (self, value):
        if self.tr_emulation == value:
            return

        try:
            self.proxy.DeviceSetOption(self.number, "tr-emulation", GLib.Variant('b', value))
        except GLib.GError as e:
            self.emit("error", _("Failed to set TR emulation for device #%02d to %i:\n%s") % (self.number, value, e))

    def set_daemon_debug_mask (self, value):
        if self.daemon_debug_mask == value:
            return

        try:
            self.proxy.DeviceSetOption(self.number, "daemon-debug-mask", GLib.Variant('i', value))
        except GLib.GError as e:
            self.emit("error", _("Failed to set daemon debug mask for device #%02d to 0x%X:\n%s") % (self.number, value, e))

    def set_library_debug_mask (self, value):
        if self.library_debug_mask == value:
            return

        try:
            self.proxy.DeviceSetOption(self.number, "library-debug-mask", GLib.Variant('i', value))
        except GLib.GError as e:
            self.emit("error", _("Failed to set library debug mask for device #%02d to 0x%X:\n%s") % (self.number, value, e))

    def update_device_mapping (self):
        [ self.sr_path, self.sg_path ] = self.proxy.DeviceGetMapping(self.number)
        self.emit("mapping-ready")


GObject.type_register(CDEmuDevice)
GObject.signal_new("mapping-ready", CDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("status-changed", CDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("device-id-changed", CDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("dpm-emulation-changed", CDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("tr-emulation-changed", CDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("daemon-debug-mask-changed", CDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("library-debug-mask-changed", CDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())
GObject.signal_new("error", CDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_STRING, ))


################################################################################
#                                   gCDEmu                                     #
################################################################################
class gCDEmu (Gtk.StatusIcon):
    def load_icon (self):
        # Get icon name from settings
        icon_name = self.settings.get_string("icon-name")
        if icon_name == None:
            icon_name = "gcdemu"

        # Lookup icon in standard paths
        icon_info = Gtk.IconTheme().lookup_icon(icon_name, 0, 0)
        if icon_info == None:
            print ("Pixmap '%s' not found in standard pixmap paths!" % (icon_name))
            self.icon_on = None
            self.icon_off = None
            return
        icon_filename = icon_info.get_filename()

        # "On" icon
        self.icon_on = GdkPixbuf.Pixbuf.new_from_file(icon_filename)
        # "Off" icon - desaturated
        self.icon_off = self.icon_on.copy()
        self.icon_off.saturate_and_pixelate(self.icon_off, 0.00, False)

    def load_logo (self):
        # Lookup "icon" in standard paths
        icon_info = Gtk.IconTheme().lookup_icon("gcdemu", 0, 0)
        if icon_info == None:
            print ("Pixmap '%s' not found in standard pixmap paths!" % ("gcdemu"))
            self.logo = None
            return
        logo_filename = icon_info.get_filename()

        # Load
        self.logo = GdkPixbuf.Pixbuf.new_from_file_at_size(logo_filename, 156, 156)

    def update_icon (self):
        # If icon is invalid, use standard "image-missing" as fallback
        if self.icon_on == None:
            self.set_from_icon_name("image-missing")
        else:
            # Which icon to use
            if self.connected:
                self.set_from_pixbuf(self.icon_on)
            else:
                self.set_from_pixbuf(self.icon_off)

        # Show/hide icon
        if self.icon_policy == "always":
            visible = True
        elif self.icon_policy == "never":
            visible = False
        elif self.icon_policy in [ "when_connected", "when-connected" ]:
            if self.connected:
                visible = True
            else:
                visible = False
        else:
            # Fix invalid setting...
            print ("Unknown icon policy '%s'! Using 'always' by default!" % (self.icon_policy))
            self.icon_policy = "always"
            visible = True
        self.set_visible(visible)

    def cleanup (self):
        # Default state: disconnected
        self.connected = False
        self.update_icon()

        # Clean signals handlers
        for signal in self.signals:
            signal.remove()

        # Clean devices menu
        items = self.devices_menu.get_children()
        # Note: first two entries are "Devices" and the separator...
        for i in range(2, len(items)):
            self.devices_menu.remove(items[i])

        # Free devices
        for device in self.devices:
            device.cleanup()
        self.devices = []

    def connect_to_daemon (self):
        # Cleanup
        self.cleanup()

        # Establish connection
        self.cdemu.connect_to_daemon(self.use_system_bus)


    def __init__ (self):
        super(gCDEmu, self).__init__()

        self.connected = False
        self.devices = []

        self.signals = []

        # *** Configuration ***
        self.settings = Gio.Settings("apps.gcdemu")
        
        # Watch over our settings
        self.settings.connect("changed", lambda s, k: self.on_settings_changed(k))

        # Notifications
        if Notify.init(app_name):
            self.show_notifications = self.settings.get_boolean("show-notifications")
        else:
            # Disable because pynotify initialization failed
            self.show_notifications = False
        
        # Which bus to use
        self.use_system_bus = self.settings.get_boolean("use-system-bus")

        # Daemon autostart
        self.daemon_autostart = self.settings.get_boolean("daemon-autostart")

        # Icon policy
        self.icon_policy = self.settings.get_string("icon-policy")

        # To load the icon, we pretend that icon path g-conf key has changed and
        # let the callback do the rest for us...
        self.on_settings_changed("icon-name")

        # Load logo
        self.load_logo()

        # *** The About dialog ***
        self.about = Gtk.AboutDialog()
        self.about.set_name(app_name)
        self.about.set_version(app_version)
        self.about.set_copyright("Copyright (C) 2006-2012 Rok Mandeljc")
        self.about.set_comments(_("A GUI for controlling CDEmu devices."))
        self.about.set_website("http://cdemu.sf.net")
        self.about.set_website_label(_("The CDEmu project website"))
        self.about.set_authors([ "Rok Mandeljc <rok.mandeljc@gmail.com>" ])
        self.about.set_artists([ "Rômulo Fernandes <abra185@gmail.com>" ])
        self.about.set_translator_credits(_("translator-credits"))
        self.about.set_logo(self.logo)

        # *** The right-click popup menu ***
        self.menu = Gtk.Menu()

        # Use system bus
        item = Gtk.CheckMenuItem.new_with_mnemonic(_("Use _system bus"))
        item.set_active(self.use_system_bus)
        item.connect("toggled", self.on_use_system_bus_toggled)
        self.menu.append(item)
        self.item_use_system_bus = item

        # Show notifications
        item = Gtk.CheckMenuItem.new_with_mnemonic(_("Show _notifications"))
        item.set_active(self.show_notifications)
        item.connect("toggled", self.on_show_notifications_toggled)
        self.menu.append(item)
        self.item_show_notifications = item

        # Separator
        self.menu.append(Gtk.SeparatorMenuItem())

        # About
        item = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_ABOUT, None)
        item.connect("activate", self.on_about_activate)
        self.menu.append(item)

        # Separator
        self.menu.append(Gtk.SeparatorMenuItem())

        # Quit
        item = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_QUIT, None)
        item.connect("activate", Gtk.main_quit)
        self.menu.append(item)

        self.menu.show_all()

        # *** Devices menu ***
        self.devices_menu = Gtk.Menu()

        item = Gtk.MenuItem.new_with_label(_("Devices"))
        item.set_sensitive(False)
        self.devices_menu.append(item);

        self.devices_menu.append(Gtk.SeparatorMenuItem())

        self.devices_menu.show_all()

        # *** The status icon ***
        self.set_tooltip_text('gCDEmu')
        self.set_visible(True)
        self.connect('activate', self.on_activate)
        self.connect('popup-menu', self.on_popup_menu)

        # *** Create the CDEmu object ***
        self.cdemu = CDEmu()
        self.cdemu.connect("daemon-started", self.on_daemon_started)
        self.cdemu.connect("daemon-stopped", self.on_daemon_stopped)
        self.cdemu.connect("connection-established", self.on_connection_established)
        self.cdemu.connect("connection-lost", self.on_connection_lost)
        self.cdemu.connect("error", lambda c, t, m: self.show_error(t, m))
        # Connect to daemon
        self.cdemu.connect_to_bus(self.use_system_bus, self.daemon_autostart)

        # *** Main loop ***
        Gtk.main()


    def on_activate (self, status_icon):
        # Popup the devices menu (simulate the 1st button click)
        self.devices_menu.popup(None, None, status_icon.position_menu, self, 1, Gtk.get_current_event_time())

    def on_popup_menu (self, status_icon, button, activate_time):
        # Popup the menu
        self.menu.popup(None, None, status_icon.position_menu, self, button, activate_time)

    def on_settings_changed (self, key):
        if key == "use-system-bus":
            self.use_system_bus = self.settings.get_boolean(key)
            print ("New setting: use system bus: %d" % self.use_system_bus)
            self.item_use_system_bus.set_active(self.use_system_bus)
            self.cdemu.connect_to_bus(self.use_system_bus, self.daemon_autostart)
        elif key == "show-notifications":
            self.show_notifications = self.settings.get_boolean(key)
            self.item_show_notifications.set_active(self.show_notifications)
            print ("New setting: show notifications: %d" % self.show_notifications)
        elif key == "icon-name":
            self.load_icon()
            self.update_icon()
        elif key == "daemon-autostart":
            self.daemon_autostart = self.settings.get_boolean(key)
            print ("New setting: daemon autostart: %d" % self.daemon_autostart)
        elif key == "icon-policy":
            self.icon_policy = self.settings.get_string(key)
            self.update_icon()
        else:
            print ("Unknown setting key: %s!" % key)

    def on_about_activate (self, menuitem):
        self.about.run()
        self.about.hide()

    def on_use_system_bus_toggled (self, checkmenuitem):
        self.settings.set_boolean("use-system-bus", checkmenuitem.get_active())
        return

    def on_show_notifications_toggled (self, checkmenuitem):
        self.settings.set_boolean("show-notifications", checkmenuitem.get_active())
        return


    def on_connection_established (self, cdemu, num_devices):
        self.connected = True
        self.update_icon()

        # Create devices GUI
        for i in range(0, num_devices):
            # Create device dialog
            device = gCDEmuDevice(self.cdemu, i)
            self.devices.append(device)
            # Add device's menu item to the devices menu
            self.devices_menu.add(device.menu_item)
            # Connect signal
            device.connect("device-notification", self.on_device_notification)

    def on_connection_lost (self, cdemu):
        self.cleanup()

    def on_daemon_started (self, cdemu):
        self.show_notification(_("Daemon started"), _("CDEmu daemon has been started."), "dialog-information")

    def on_daemon_stopped (self, cdemu):
        self.show_notification(_("Daemon stopped"), _("CDEmu daemon has been stopped."), "dialog-information")

    def on_device_notification (self, device, summary, body):
        self.show_notification(summary, body, "dialog-information")


    def show_notification (self, summary, body, icon = None):
        if self.show_notifications:
            n = Notify.Notification.new(summary, body, icon)
            n.show()

    def show_error (self, title, message):
         # Show error dialog
        message = Gtk.MessageDialog(None, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, message)
        message.set_title(title)
        message.run()
        message.destroy()

        return

GObject.type_register(gCDEmu)


################################################################################
#                                gCDEmuDevice                                  #
################################################################################
class gCDEmuDevice (GObject.GObject):
    def connect_signal (self, obj, sig, callback):
        signal = obj.connect(sig, callback)
        self.signals.append((obj, signal))


    def __init__ (self, cdemu, d):
        super(gCDEmuDevice, self).__init__()

        self.signals = []

        self.number = d
        self.device = cdemu.devices[d]

        # Create menu item
        self.menu_item = Gtk.MenuItem()
        self.menu_item.show()
        self.connect_signal(self.menu_item, "button-press-event", self.on_menu_item_button_press_event)

        # Create file chooser dialog
        self.file_chooser = gCDEMuFileChooserDialog(cdemu.supported_parsers)

        # Create password dialog
        self.password_dialog = gCDEMuPasswordDialog()

        # *** GUI ***
        # Create dialog
        self.dialog = Gtk.Dialog("Device #%02d properties" % d, None, 0, (Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE))
        self.dialog.set_border_width(5)
        self.dialog.vbox.set_border_width(5)

        # Label
        label = Gtk.Label(label="<b><big>" + (_("Device #%02d") % (self.number)) + "</big></b>")
        label.show()
        label.set_use_markup(True)
        self.dialog.vbox.pack_start(label, expand=False, fill=False, padding=0)

        # Notebook
        notebook = Gtk.Notebook()
        notebook.show()
        notebook.set_tab_pos(Gtk.PositionType.LEFT)
        notebook.set_border_width(5)
        self.dialog.vbox.pack_start(notebook, expand=True, fill=True, padding=0)

        # Page: status
        self.page_status = gCDEmuDeviceStatusPage()
        self.page_status.set_device_mapping(self.device.sg_path, self.device.sr_path)
        notebook.append_page(self.page_status, Gtk.Label(label=_("Status")))

        self.connect_signal(self.page_status, "load-unload-device", lambda w: self.load_unload_device())

        # Page: options
        self.page_options = gCDEmuDeviceOptionsPage()
        notebook.append_page(self.page_options, Gtk.Label(label=_("Options")))
        self.connect_signal(self.page_options, "device-id-changed", lambda w, id: self.device.set_device_id(id))
        self.connect_signal(self.page_options, "dpm-emulation-changed", lambda w, v: self.device.set_dpm_emulation(v))
        self.connect_signal(self.page_options, "tr-emulation-changed", lambda w, v: self.device.set_tr_emulation(v))

        # Page: daemon debug mask
        self.page_daemon = gCDEmuDeviceDebugMaskPage(_("Daemon debug mask"), cdemu.daemon_debug_masks)
        notebook.append_page(self.page_daemon, Gtk.Label(label=_("Daemon")))
        self.connect_signal(self.page_daemon, "debug-mask-changed", lambda w, v: self.device.set_daemon_debug_mask(v))

        # Page: library debug mask
        self.page_library = gCDEmuDeviceDebugMaskPage(_("Library debug mask"), cdemu.library_debug_masks)
        notebook.append_page(self.page_library, Gtk.Label(label=_("Library")))
        self.connect_signal(self.page_library, "debug-mask-changed", lambda w, v: self.device.set_library_debug_mask(v))

        # Connect device signals
        self.connect_signal(self.device, "mapping-ready", lambda w: self.page_status.set_device_mapping(self.device.sg_path, self.device.sr_path))
        self.connect_signal(self.device, "status-changed", lambda w: self.update_status(True))
        self.connect_signal(self.device, "device-id-changed", lambda w: self.update_device_id(True))
        self.connect_signal(self.device, "dpm-emulation-changed", lambda w: self.update_dpm_emulation(True))
        self.connect_signal(self.device, "tr-emulation-changed", lambda w: self.update_tr_emulation(True))
        self.connect_signal(self.device, "daemon-debug-mask-changed", lambda w: self.update_daemon_debug_mask(True))
        self.connect_signal(self.device, "library-debug-mask-changed", lambda w: self.update_library_debug_mask(True))
        self.connect_signal(self.device, "error", lambda w, t: self.show_error(t));

        # Some pages require refresh when they are shown...
        self.connect_signal(notebook, "switch-page", lambda n, p, pp: self.refresh_pages())
        self.connect_signal(self.dialog, "show", lambda w: self.refresh_pages())

        # Manually perform the initial update...
        self.update_status(False)
        self.update_device_id(False)
        self.update_dpm_emulation(False)
        self.update_tr_emulation(False)
        self.update_daemon_debug_mask(False)
        self.update_library_debug_mask(False)


    def cleanup (self):
        # Release reference to device
        self.device = None

        # Cleanup signal handlers
        for (obj, handler) in self.signals:
            obj.disconnect(handler)

        # Explicitly destroy dialogs, because they might still be running
        self.dialog.destroy()
        self.file_chooser.destroy()
        self.menu_item.destroy()
        self.password_dialog.destroy()

    def update_status (self, notify = False):
        # Menu item
        if self.device.loaded:
            images = os.path.basename(self.device.filenames[0]) # Make it short
            if len(self.device.filenames) > 1:
                images += ", ..." # Indicate there's more than one file
            str = "%s #%02d: %s" % (_("Device"), self.device.number, images)
        else:
            str = "%s #%02d: %s" % (_("Device"), self.device.number, _("Empty"))
        self.menu_item.set_label(str)

        # Status page in the device dialog
        self.page_status.set_status(self.device.loaded, self.device.filenames)

        # Notification
        if notify:
            if self.device.loaded:
                self.emit("device-notification", _("Device status change"), _("Device #%02d has been loaded.") % (self.device.number))
            else:
                self.emit("device-notification", _("Device status change"), _("Device #%02d has been emptied.") % (self.device.number))

    def update_device_id (self, notify = False):
        self.page_options.set_device_id(self.device.device_id)
        if notify:
            self.emit("device-notification", _("Device option change"), _("Device #%02d has been changed its device ID:\n  Vendor ID: '%s'\n  Product ID: '%s'\n  Revision: '%s'\n  Vendor-specific: '%s'") % (self.device.number, self.device.device_id[0], self.device.device_id[1], self.device.device_id[2], self.device.device_id[3]))

    def update_dpm_emulation (self, notify = False):
        self.page_options.set_dpm_emulation(self.device.dpm_emulation)
        if notify:
            self.emit("device-notification", _("Device option change"), _("Device #%02d has been changed its DPM emulation option. New value: %s") % (self.device.number, self.device.dpm_emulation))

    def update_tr_emulation (self, notify = False):
        self.page_options.set_tr_emulation(self.device.tr_emulation)
        if notify:
            self.emit("device-notification", _("Device option change"), _("Device #%02d has been changed its TR emulation option. New value: %s") % (self.device.number, self.device.tr_emulation))

    def update_daemon_debug_mask (self, notify = False):
        self.page_daemon.set_debug_mask(self.device.daemon_debug_mask)
        if notify:
            self.emit("device-notification", _("Device option change"), _("Device #%02d has been changed its daemon debug mask. New value: 0x%X") % (self.device.number, self.device.daemon_debug_mask))

    def update_library_debug_mask (self, notify = False):
        self.page_library.set_debug_mask(self.device.library_debug_mask)
        if notify:
            self.emit("device-notification", _("Device option change"), _("Device #%02d has been changed its library debug mask. New value: 0x%X") % (self.device.number, self.device.library_debug_mask))


    def on_menu_item_button_press_event (self, widget, event):
        if event.button == 1:
            # Left click: quick load/unload
            self.load_unload_device()
        else:
            # Right click: device dialog
            self.dialog.present() # Make sure the window is always on top
            self.dialog.run()
            self.dialog.hide()

    def load_unload_device (self):
        if self.device.loaded:
            self.device.unload_device()
        else:
            result = self.file_chooser.run()
            self.file_chooser.hide()
            if result == Gtk.ResponseType.ACCEPT:
                # Get filename and parameters
                filenames = self.file_chooser.get_filenames()
                parameters = self.file_chooser.get_parameters()
                
                # Try loading
                try:
                    self.device.load_device(filenames, parameters)
                except CDEmuNeedPassword as c:
                    # Get password
                    result = self.password_dialog.run()
                    self.password_dialog.hide()
                    if result == Gtk.ResponseType.OK:
                        password = self.password_dialog.get_password()
                        # Try loading with overriden password
                        parameters["password"] = GLib.Variant("s", password)
                        self.device.load_device(filenames, parameters)

    def refresh_pages (self):
        # Some pages have fields that can be altered without applying the
        # changed. We want to reset those changes when a page is changed
        # or when the dialog is shown...
        self.update_device_id(False)
        self.update_daemon_debug_mask(False)
        self.update_library_debug_mask(False)


    def show_error (self, text):
         # Show error dialog
        message = Gtk.MessageDialog(None, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, text)
        message.set_title(_("Device error"))
        message.run()
        message.destroy()

        return

GObject.type_register(gCDEmuDevice)
GObject.signal_new("device-notification", gCDEmuDevice,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_STRING, GObject.TYPE_STRING))


################################################################################
#                 gCDEmuDeviceStatusPage: device status page                   #
################################################################################
class gCDEmuDeviceStatusPage (Gtk.VBox):
    def __init__ (self):
        super(gCDEmuDeviceStatusPage, self).__init__()

        # Frame: status
        frame = Gtk.Frame.new(_("Status"))
        frame.set_label_align(0.50, 0.50)
        frame.show()
        frame.set_border_width(2)
        self.pack_start(frame, expand=False, fill=False, padding=0)

        table = Gtk.Table()
        table.show()
        frame.add(table)
        table.set_border_width(5)
        table.set_row_spacings(5)

        label = Gtk.Label(label=_("Loaded: "))
        label.show()
        table.attach(label, 0, 1, 0, 1, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        label = Gtk.Label()
        label.show()
        table.attach(label, 1, 2, 0, 1, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)
        self.label_loaded = label

        label = Gtk.Label(label=_("File name(s): "))
        label.show()
        table.attach(label, 0, 1, 1, 2, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        label = Gtk.Label()
        label.show()
        table.attach(label, 1, 2, 1, 2, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)
        self.label_filename = label

        separator = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL)
        separator.show()
        table.attach(separator, 0, 2, 2, 3, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        button = Gtk.Button(_("Load"))
        button.show()
        table.attach(button, 0, 2, 3, 4, Gtk.AttachOptions.EXPAND, 0)
        button.connect("clicked", self.on_load_device_clicked)
        self.button_load = button

        # *** Frame: mapping ***
        frame = Gtk.Frame.new(_("Device mapping"))
        frame.set_label_align(0.50, 0.50)
        frame.show()
        frame.set_border_width(2)
        self.pack_start(frame, expand=False, fill=False, padding=5)

        table = Gtk.Table()
        table.show()
        frame.add(table)
        table.set_border_width(5)
        table.set_row_spacings(2)

        label = Gtk.Label(label=_("SCSI CD-ROM device: "))
        label.show()
        table.attach(label, 0, 1, 0, 1, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        label = Gtk.Label()
        label.show()
        table.attach(label, 1, 2, 0, 1, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)
        self.label_dev_sr = label

        label = Gtk.Label(label=_("SCSI generic device: "))
        label.show()
        table.attach(label, 0, 1, 1, 2, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        label = Gtk.Label()
        label.show()
        table.attach(label, 1, 2, 1, 2, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)
        self.label_dev_sg = label

        self.show_all()


    def set_device_mapping (self, sg_path, sr_path):
        self.label_dev_sr.set_text(sr_path)
        self.label_dev_sg.set_text(sg_path)

    def set_status (self, loaded, filenames):
        if loaded:
            tmp_list = []
            for filename in filenames:
                tmp_list.append(os.path.basename(filename))
            text = "\n".join(tmp_list)

            self.label_loaded.set_label(_("Yes"))
            self.label_filename.set_label(text)
            self.button_load.set_label(_("Unload"))
        else:
            self.label_loaded.set_label(_("No"))
            self.label_filename.set_label("")
            self.button_load.set_label(_("Load"))


    def on_load_device_clicked (self, button):
        self.emit("load-unload-device")

GObject.type_register(gCDEmuDeviceStatusPage)
GObject.signal_new("load-unload-device", gCDEmuDeviceStatusPage,
    GObject.SignalFlags.RUN_FIRST,
    None,
    ())


################################################################################
#                gCDEmuDeviceOptionsPage: device options page                  #
################################################################################
class gCDEmuDeviceOptionsPage (Gtk.VBox):
    def __init__ (self):
        super(gCDEmuDeviceOptionsPage, self).__init__()

        # Device ID
        frame = Gtk.Frame.new(_("Device ID"))
        frame.set_label_align(0.50, 0.50)
        frame.show()
        frame.set_border_width(2)
        self.pack_start(frame, expand=False, fill=False, padding=0)

        table = Gtk.Table()
        table.show()
        frame.add(table)
        table.set_border_width(5)
        table.set_row_spacings(5)

        label = Gtk.Label(label=_("Vendor ID: "))
        label.show()
        table.attach(label, 0, 1, 0, 1, 0, 0)

        entry = Gtk.Entry()
        entry.show()
        entry.set_max_length(8)
        table.attach(entry, 1, 2, 0, 1, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)
        self.entry_vendor_id = entry

        label = Gtk.Label(label=_("Product ID: "))
        label.show()
        table.attach(label, 0, 1, 1, 2, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        entry = Gtk.Entry()
        entry.show()
        entry.set_max_length(16)
        table.attach(entry, 1, 2, 1, 2, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)
        self.entry_product_id = entry

        label = Gtk.Label(label=_("Revision: "))
        label.show()
        table.attach(label, 0, 1, 2, 3, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        entry = Gtk.Entry()
        entry.show()
        entry.set_max_length(4)
        table.attach(entry, 1, 2, 2, 3, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)
        self.entry_revision = entry

        label = Gtk.Label(label=_("Vendor-specific: "))
        label.show()
        table.attach(label, 0, 1, 3, 4, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        entry = Gtk.Entry()
        entry.show()
        entry.set_max_length(20)
        table.attach(entry, 1, 2, 3, 4, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)
        self.entry_vendor_specific = entry

        separator = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL)
        separator.show()
        table.attach(separator, 0, 2, 4, 5, Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND, 0)

        button = Gtk.Button(_("Set device ID"))
        button.show()
        table.attach(button, 0, 2, 5, 6, Gtk.AttachOptions.EXPAND, 0)
        button.connect("clicked", self.on_set_device_id_clicked)

        # DPM emulation
        checkbutton = Gtk.CheckButton(_("DPM emulation"), False)
        checkbutton.show()
        checkbutton.connect("toggled", self.on_set_dpm_emulation_toggled)
        self.pack_start(checkbutton, expand=False, fill=False, padding=0)
        self.checkbutton_dpm = checkbutton

        # Transfer rate emulation
        checkbutton = Gtk.CheckButton(_("Transfer rate emulation"), False)
        checkbutton.show()
        checkbutton.connect("toggled", self.on_set_tr_emulation_toggled)
        self.pack_start(checkbutton, expand=False, fill=False, padding=0)
        self.checkbutton_tr = checkbutton

        self.show_all()


    def set_device_id (self, device_id):
        self.entry_vendor_id.set_text(device_id[0])
        self.entry_product_id.set_text(device_id[1])
        self.entry_revision.set_text(device_id[2])
        self.entry_vendor_specific.set_text(device_id[3])

    def on_set_device_id_clicked (self, button):
        id = ( self.entry_vendor_id.get_text(),
               self.entry_product_id.get_text(),
               self.entry_revision.get_text(),
               self.entry_vendor_specific.get_text() )
        self.emit("device-id-changed", id)

    def set_dpm_emulation (self, value):
        self.checkbutton_dpm.set_active(value)

    def on_set_dpm_emulation_toggled (self, togglebutton):
        self.emit("dpm-emulation-changed", togglebutton.get_active())

    def set_tr_emulation (self, value):
        self.checkbutton_tr.set_active(value)

    def on_set_tr_emulation_toggled (self, togglebutton):
        self.emit("tr-emulation-changed", togglebutton.get_active())

GObject.type_register(gCDEmuDeviceOptionsPage)
GObject.signal_new("device-id-changed", gCDEmuDeviceOptionsPage,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_PYOBJECT,))
GObject.signal_new("dpm-emulation-changed", gCDEmuDeviceOptionsPage,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_BOOLEAN,))
GObject.signal_new("tr-emulation-changed", gCDEmuDeviceOptionsPage,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_BOOLEAN,))


################################################################################
#                 gCDEmuDeviceDebugMaskPage: debug mask page                   #
################################################################################
class gCDEmuDeviceDebugMaskPage (Gtk.Frame):
    def __init__ (self, name, masks_list):
        super(gCDEmuDeviceDebugMaskPage, self).__init__()
        self.set_label(name)
        self.entries = []

        vbox = Gtk.VBox()
        vbox.set_border_width(5)
        vbox.set_spacing(2)
        self.add(vbox)

        # Create checkboxes
        for mask in masks_list:
            checkbutton = Gtk.CheckButton(mask[0], False)
            checkbutton.weight = mask[1]
            vbox.pack_start(checkbutton, expand=False, fill=False, padding=0)
            self.entries.append(checkbutton)

        # Separator
        vbox.pack_start(Gtk.Separator.new(Gtk.Orientation.HORIZONTAL), expand=False, fill=False, padding=5)

        # Button
        button = Gtk.Button(_("Set debug mask"))
        button.show()
        vbox.pack_start(button, expand=False, fill=False, padding=0)
        button.connect("clicked", self.on_set_debug_mask_clicked)

        self.show_all()


    def set_debug_mask (self, value):
        for entry in self.entries:
            entry.set_active(value & entry.weight)

    def on_set_debug_mask_clicked (self, button):
        # Get value
        value = 0
        for entry in self.entries:
            value |= entry.weight * entry.get_active()

        self.emit("debug-mask-changed", value)

GObject.type_register(gCDEmuDeviceDebugMaskPage)
GObject.signal_new("debug-mask-changed", gCDEmuDeviceDebugMaskPage,
    GObject.SignalFlags.RUN_FIRST,
    None,
    (GObject.TYPE_UINT,))


################################################################################
#                gCDEMuFileChooserDialog: file chooser dialog                  #
################################################################################
class gCDEMuFileChooserDialog (Gtk.FileChooserDialog):
    def __init__ (self, parsers):
        super(gCDEMuFileChooserDialog, self).__init__(_("Open file"), None, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
        self.set_select_multiple(True)

        # Set filters based on supported parser information
        filter = Gtk.FileFilter()
        filter.set_name(_("All files"))
        filter.add_pattern("*")
        self.add_filter(filter)

        all_images = Gtk.FileFilter()
        all_images.set_name(_("All image files"))
        self.add_filter(all_images)

        for parser in parsers:
            filter = Gtk.FileFilter()

            filter.set_name(parser[2])
            filter.add_mime_type(parser[3])

            all_images.add_mime_type(parser[3])

            self.add_filter(filter)

        # Extra options
        widget = self.create_extra_options_widget()
        self.set_extra_widget(widget)

    def run (self):
        # Clear parameters
        self.combobox_encoding.set_active(0)
        self.entry_password.set_text("")
        self.check_button_css.set_active(False)

        # Run the dialog
        return Gtk.FileChooserDialog.run(self)

    def get_parameters (self):
        parameters = {}
        
        # Encoding
        i = self.combobox_encoding.get_active_iter()
        encoding = self.combobox_encoding.get_model().get_value(i, 1)
        if encoding:
            parameters["encoding"] = GLib.Variant("s", encoding)

        # Password
        password = self.entry_password.get_text()
        if password:
            parameters["password"] = GLib.Variant("s", password)

        # CSS-encrypted DVD flag
        if self.check_button_css.get_active():
            parameters["dvd-report-css"] = GLib.Variant("b", True)

        return parameters                    

    def create_extra_options_widget (self):
        # Expander and VBox
        expander = Gtk.Expander()
        expander.set_label(_("Extra Options"))

        vbox = Gtk.VBox(False, 5)
        expander.add(vbox)


        # *** Encoding ***
        hbox = Gtk.HBox(False, 5)
        vbox.pack_start(hbox, expand=True, fill=True, padding=0)

        # Label
        label = Gtk.Label(label=_("Encoding: "))
        hbox.pack_start(label, expand=False, fill=False, padding=0)

        # ComboBoxEntry
        liststore = self.get_encodings_list()
        combobox = Gtk.ComboBox.new_with_model_and_entry(liststore)
        combobox.set_entry_text_column(0)
        hbox.pack_start(combobox, expand=False, fill=False, padding=0)
        self.combobox_encoding = combobox
        
        # Autocompletion
        completion = Gtk.EntryCompletion()
        completion.set_model(liststore)
        completion.set_text_column(0)
        combobox.get_child().set_completion(completion)

        
        # *** Password ***
        hbox = Gtk.HBox(False, 5)
        vbox.pack_start(hbox, expand=True, fill=True, padding=0)

        # Label
        label = Gtk.Label(label=_("Password: "))
        hbox.pack_start(label, expand=False, fill=False, padding=0)

        # Entry
        entry = Gtk.Entry()
        entry.set_visibility(False)
        hbox.pack_start(entry, expand=False, fill=False, padding=0)
        self.entry_password = entry


        # *** CSS-encrypted DVD flag ***
        check = Gtk.CheckButton(_("Raw image of CSS-encrypted DVD"))
        vbox.pack_start(check, expand=True, fill=True, padding=0)

        self.check_button_css = check


        expander.show_all()

        return expander

    def get_encodings_list (self):
        # Get list of encodings supported by iconv (since that's what
        # GLib and consequently CDEmu/libMirage uses)
        encodings = []
        try:
            cmd = [ 'iconv' , '--list']
            iconv = subprocess.Popen(cmd, env = {'LANG' : 'C'}, stdout=subprocess.PIPE, stdin=open(os.devnull, 'w+'), stderr=open(os.devnull, 'w+'))
            encodings = [ line.strip('/').lower() for line in iconv.communicate()[0].splitlines() ]
        except OSError:
            # Probably due to iconv missing; not a big deal, we just won't
            # display encodings list
            pass

        # Create ListStore
        liststore = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)
        liststore.append( ("Autodetect", "") )
        for encoding in encodings:
            liststore.append( (encoding, encoding) )
        
        return liststore
        


################################################################################
#                 gCDEMuPasswordDialog: file chooser dialog                    #
################################################################################
class gCDEMuPasswordDialog (Gtk.Dialog):
    def __init__ (self):
        super(gCDEMuPasswordDialog, self).__init__(_("Enter password"), None, 0, (Gtk.STOCK_OK, Gtk.ResponseType.OK, Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT))
        self.set_default_response(Gtk.ResponseType.OK)

        self.vbox.set_spacing(5)

        label = Gtk.Label(label=_("The image you are trying to load is encrypted."))
        self.vbox.pack_start(label, expand=True, fill=True, padding=0)

        # Create the text input field
        entry = Gtk.Entry()
        entry.connect("activate", lambda w: self.response(Gtk.ResponseType.OK)) # Allow the user to press enter to do OK
        entry.set_visibility(False)
        self.entry = entry

        # Create a horizontal box to pack the entry and a label
        hbox = Gtk.HBox(False, 5)
        hbox.pack_start(Gtk.Label(_("Password: ")), expand=False, fill=False, padding=0)
        hbox.pack_start(entry, expand=True, fill=True, padding=0)
        self.vbox.pack_start(hbox, expand=False, fill=False, padding=0)

        self.vbox.show_all()

    def get_password (self):
        return self.entry.get_text()

    def run (self):
        # Clear the password field
        self.entry.set_text("")

        # Chain to parent
        return super(gCDEMuPasswordDialog, self).run()


################################################################################
#                                    Main                                      #
################################################################################
if __name__ == "__main__":
    app = gCDEmu()
