#!/usr/bin/env python3

# Copyright (C) 2020 Konstantin Unruh <freaxmate@protonmail.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 <https://www.gnu.org/licenses/>.

import argparse
import distro
import errno
import gettext
import getpass
import gi
import glob
import mmap
import os
import psutil
import setproctitle
import shutil
import signal
import subprocess
import sysconfig
import string
import sys
import time

from subprocess import DEVNULL, PIPE

from gi.repository import GLib

gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
from gi.repository import GdkPixbuf

gi.require_version('GdkX11', '3.0')
from gi.repository import GdkX11
from gi.repository import Gio
from gi.repository import GObject

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

gi.require_version('Notify', '0.7')
from gi.repository import Notify

# Workaround introspection bug, gnome bug 622084
signal.signal(signal.SIGINT, signal.SIG_DFL)

__VERSION__ = '0.0.1'

__TRADITIONAL_BUTTONS__ = ":minimize,maximize,close"
__CONTEMPORARY_BUTTONS__ = "close,minimize,maximize:"
__MENU_TRADITIONAL_BUTTONS__ = "menu:minimize,maximize,close"
__MENU_CONTEMPORARY_BUTTONS__ = "close,minimize,maximize:menu"

# i18n
gettext.install('mate-layouts', os.path.join('/','usr','share','locale'))

class LayoutItem(Gtk.Box):
    def __init__(self, mate_layouts_object,  image, display_name, code_name):
        super().__init__(homogeneous=False, spacing=0)
        self.image = image
        self.display_name = Gtk.Label(label="<b>"+display_name+"</b>")
        self.display_name.set_use_markup(True)
        self.code_name = code_name
        self.event_box = Gtk.EventBox()
        self.box = Gtk.Box(spacing=12)
        self.box.set_orientation(Gtk.Orientation.VERTICAL)
        self.box.pack_start(self.image, True, True, 0)
        self.box.pack_start(self.display_name, True, True, 0)
        self.event_box.add(self.box)
        self.event_box.connect("enter-notify-event", MateLayouts.mouse_enter_layout_item, mate_layouts_object)
        self.event_box.connect("leave-notify-event", MateLayouts.mouse_leave_layout_item, mate_layouts_object)
        self.pack_start(self.event_box, True, True, 0)
        self.show_all()

class MateLayouts:
    system_installed_panel_layouts = []
    layout_list = []

    ############################
    ## Check available options
    ############################

    def check_panel_features(self):
        # Determine what panel features are available
        self.indicators_available = False
        self.mate_dock_available = False
        self.mate_menu_available = False
        self.mint_menu_available = False
        self.volume_status_icon_enabled = False
        self.brisk_menu_available = False
        self.appmenu_applet_available = False

        if os.path.exists('/usr/lib/ayatana-indicators3/7/libayatana-application.so') and \
            os.path.exists('/usr/share/mate-panel/applets/org.mate.applets.Indicator.mate-panel-applet'):
                self.indicators_available = True

        if os.path.exists('/usr/lib/mate-applets/mate-dock-applet/dock.py'):
            self.mate_dock_available = True

        if os.path.exists('/usr/lib/mate-menu/mate-menu.py'):
            self.mate_menu_available = True

        if os.path.exists('/usr/lib/' + self.multiarch + '/brisk-menu/brisk-menu') or \
            os.path.exists('/usr/lib/brisk-menu/brisk-menu') or \
            os.path.exists('/usr/lib/brisk-menu') or \
             os.path.exists('/usr/libexec/brisk-menu'):
             if os.path.exists('/usr/share/mate-panel/applets/com.solus_project.brisk.BriskMenu.mate-panel-applet'):
              self.brisk_menu_available = True

        if (os.path.exists('/usr/lib/' + self.multiarch + '/mate-panel/libappmenu-mate.so') or \
            os.path.exists('/usr/lib/mate-panel/libappmenu-mate.so')) and \
            os.path.exists('/usr/share/mate-panel/applets/org.vala-panel.appmenu.mate-panel-applet'):
            self.appmenu_applet_available = True

        if os.path.exists('/usr/lib/linuxmint/mintMenu/mintMenu.py'):
            self.mint_menu_available = True

    def make_list_of_panel_layouts(self):
        for layout_item in self.layout_list:
           layout_item.get_parent().destroy()
           Gtk.Widget.destroy(layout_item)
        self.layout_list.clear()
        layouts = ['contemporary', 'eleven', 'eleven-no-indicators', 'familiar', 'fedora',
                   'default', 'linuxmint', 'mageia', 'manjaro', 'mutiny', 'mutiny-no-indicators',
                   'netbook', 'netbook-no-indicators', 'opensuse', 'pantheon', 'redmond',
                   'redmond-no-indicators', 'solus', 'ubuntu-mate', 'gnome2']

        layout_thumbnails = []

        for layout in layouts:
           if layout not in layout_thumbnails:
              image = Gtk.Image()
              if self.get_right_path('layout-previews/' + layout + '-symbolic.svg'):
                 image.set_from_file(self.get_right_path('layout-previews/' + layout + '-symbolic.svg'))
              else:
                 image.set_from_icon_name('image-missing', Gtk.IconSize.DIALOG)
              layout_thumbnails.append(image)

        if self.dock is not None and \
           self.appmenu_applet_available and \
           self.brisk_menu_available and \
           self.indicators_available:
            if self.panel_layout_exists('eleven'):
                self.add_to_panel_list(self.layout_list, "Cupertino", "eleven", layout_thumbnails[2])

        if self.panel_layout_exists('manjaro') and \
           self.brisk_menu_available and \
           self.indicators_available:
            self.add_to_panel_list(self.layout_list, "Manjaro", "manjaro", layout_thumbnails[8])

        if self.panel_layout_exists('mutiny') and \
           self.mate_dock_available and \
           self.appmenu_applet_available and \
           self.indicators_available:
            self.add_to_panel_list(self.layout_list, "Mutiny", "mutiny", layout_thumbnails[10])

        if self.panel_layout_exists('redmond') and \
           self.mate_menu_available and \
           self.indicators_available:
            self.add_to_panel_list(self.layout_list, "Redmond", "redmond", layout_thumbnails[16])

        if self.panel_layout_exists('gnome2') and \
           self.indicators_available:
            self.add_to_panel_list(self.layout_list, "GNOME 2", "gnome2", layout_thumbnails[19])


        print("System installed layouts: ")
        print(self.system_installed_panel_layouts)

        # Add any saved panel layouts to the start.
        layouts = os.path.join('/','usr','share','mate-panel','layouts','*-tweak.layout')
        for layout in glob.glob(layouts):
            list_entry = self.get_custom_panel_list_entry(layout)
            image = Gtk.Image()
            if self.get_right_path('layout-previews/custom-symbolic.svg'):
               image.set_from_file(self.get_right_path('layout-previews/custom-symbolic.svg'))
            else:
               image.set_from_icon_name('image-missing', 128)
            self.layout_list.insert(0, LayoutItem(self, image, list_entry['displayname'], list_entry['codename']))

        self.update_layout_view()

    def check_appmenu(self):
        self.appmenu_available = False
        if self.schema_has_key('org.mate.interface', 'gtk-shell-shows-menubar') \
            and self.schema_has_key('org.appmenu.gtk-module','blacklist'):
            self.appmenu_available = True

    def check_dock_features(self):
        # Order matters. Plank is preferred.
        if self.find_on_path('plank') and \
            os.path.exists(os.path.join('/','usr','share','applications', 'plank.desktop')):
            self.dock = 'plank'
        else:
            self.dock = None

        if self.dock is not None and self.get_string('org.mate.session.required-components', None, 'dock'):
            self.dock_enabled = True
        else:
            self.dock_enabled = False

    def panel_layout_uses(self, applet, panel_layout):
        try:
            with open(os.path.join('/','usr','share','mate-panel','layouts', panel_layout + '.layout'), 'rb', 0) as layout, \
                mmap.mmap(layout.fileno(), 0, access=mmap.ACCESS_READ) as data:
                if data.find(applet.encode('utf-8')) != -1:
                    return True
            return False

        except:
            return False

    def get_panel_layout_section_settings(self, panel_layout, section, target_keys):
        settings = self.get_panel_layout_section_all_settings(panel_layout, section)

        if len(settings.keys()) > 0 and len(target_keys) > 0:
            section_keys = set(settings.keys())
            ignore_keys = list(section_keys - set(target_keys))

            for key in ignore_keys:
                del settings[key]

        # need to make sure that our targets, if any, at least
        # exist in the dictionary to avoid exception
        for key in target_keys:
            if key not in settings:
                settings[key] = None

        return settings

    def get_panel_layout_section_all_settings(self, panel_layout, section):
        settings = {}
        if self.panel_layout_uses(section, panel_layout):
            try:
                with open(os.path.join('/','usr','share','mate-panel','layouts', panel_layout + '.layout'), 'rb', 0) as layout, \
                    mmap.mmap(layout.fileno(), 0, access=mmap.ACCESS_READ) as data:

                    section_pos = data.find(section.encode('utf-8'))
                    if section_pos != -1:
                        data.seek(section_pos)
                        print("Found section %s" % section)

                        done = False
                        while not done:
                            line = data.readline().strip().decode('utf-8')
                            done = (not line or line.isspace())

                            if not done:
                                if line.find("=") > 0:
                                    setting = line.split("=", 1)
                                    settings[setting[0]] = setting[1]

            except:
                print("ERROR!! Reading file %s" % panel_layout + '.layout', sys.exc_info()[0])

        return settings

    def is_panel_layout_name_special(self, panel_layout, name_tags):
        # Legacy versions used the begining of the file name to determine
        # if the specified layout was special in some way. This is the
        # same check, but less verbose for callers

        for tag in name_tags:
            if panel_layout.startswith(tag):
                return True

        return False

    ##################
    ## Modify panel
    ##################

    def replace_panel_layout(self, new_layout, called_from_api=False):
        leds_enabled = self.get_bool('org.mate.peripherals-keyboard-xkb.general', None, 'duplicate-leds')
        icon_size = self.get_string('org.mate.panel.menubar', None, 'icon-size')
        item_icon_size = self.get_string('org.mate.panel.menubar', None, 'item-icon-size')

        print('Switching to: ' + new_layout)

        if not called_from_api:
            self.update_panel_layout_ui(new_layout)

        # Change Brisk Menu label-visible state when changing to/from Cupertino, Mutiny and Netbook.
        if self.brisk_menu_available:
            label_visible = self.get_bool('com.solus-project.brisk-menu', None, 'label-visible')
            if (new_layout.startswith('eleven') or new_layout.startswith('mutiny') or new_layout.startswith('netbook')):
                self.set_bool('com.solus-project.brisk-menu', None, 'label-visible', False)
            # If we're switching from Cupertino, Mutiny or Netbook and Brisk Menu label-visible is disabled then enable it.
            elif (self.current_layout.startswith('eleven') or self.current_layout.startswith('mutiny') or self.current_layout.startswith('netbook')) and not label_visible:
                self.set_bool('com.solus-project.brisk-menu', None, 'label-visible', True)

        # Change Brisk Menu window-type when changing to/from Mutiny or Cupertino.
        if self.brisk_menu_available and self.schema_has_key('com.solus-project.brisk-menu', 'window-type'):
            if new_layout.startswith('mutiny') or new_layout.startswith('eleven'):
                # If we're switching to Mutiny or Cupertino, and Brisk Menu change the window-type to dash.
                # FIXME! Do not hardcode this.
                self.set_enum('com.solus-project.brisk-menu', None, 'window-type', 2)
            elif self.current_layout.startswith('mutiny') or self.current_layout.startswith('eleven'):
                # If we're switching from Mutiny or Cupertino, and Brisk Menu change the window-type to classic.
                # FIXME! Do not hardcode this.
                self.set_enum('com.solus-project.brisk-menu', None, 'window-type', 1)

        # If we're switching to Cupertino or Mutiny to move window
        # controls to the left and enable Global Menu.
        # If there is a custom setting, use that first
        layout_name_is_special = self.is_panel_layout_name_special(new_layout, ['eleven','mutiny','contemporary'])

        custom_settings = self.get_panel_layout_section_settings(new_layout, 'Customsetting windowcontrollayout', \
                                                        ['mate-general','mate-interface','gnome-wm-preferences'])

        self.set_string("org.mate.Marco.general", None, "button-layout", \
                        custom_settings.get('mate-general') or \
                        (__CONTEMPORARY_BUTTONS__ if layout_name_is_special else __TRADITIONAL_BUTTONS__))
        self.set_string("org.mate.interface", None, "gtk-decoration-layout", \
                        custom_settings.get('mate-interface') or \
                        (__CONTEMPORARY_BUTTONS__ if layout_name_is_special else __TRADITIONAL_BUTTONS__))
        self.set_string("org.gnome.desktop.wm.preferences", None, "button-layout", \
                        custom_settings.get('gnome-wm-preferences') or \
                        (__CONTEMPORARY_BUTTONS__ if layout_name_is_special else __TRADITIONAL_BUTTONS__))

        # If we're switching away from a layout that uses the AppMenu applet
        # terminate the registrar.
        if self.panel_layout_uses('AppmenuApplet', self.current_layout) and \
           self.process_running('appmenu-registrar'):
            self.kill_process('appmenu-registrar')

        if leds_enabled:
            self.set_bool('org.mate.peripherals-keyboard-xkb.general', None, 'duplicate-leds', False)

        # Reset panel configuration to defaults.
        self.reset_dconf_path('/org/mate/panel/objects/')
        self.reset_dconf_path('/org/mate/panel/toplevels/')

        # Get the icon sizes now the new layout has been applied.
        new_icon_size = self.get_string('org.mate.panel.menubar', None, 'icon-size')
        new_item_icon_size = self.get_string('org.mate.panel.menubar', None, 'item-icon-size')

        # If the new icon sizes are default and the icon sizes were
        # previously set then restore the previous icon sizes.
        if (new_icon_size == 'Default') and (icon_size != 'Default'):
            print('Change icon-size')
            self.set_string('org.mate.panel.menubar', None, 'icon-size', icon_size)

        if (new_item_icon_size == 'Default') and (item_icon_size != 'Default'):
            print('Change item-icon-size')
            self.set_string('org.mate.panel.menubar', None, 'item-icon-size', item_icon_size)

        # If we have a custom panel layout just replace the dconf dump.
        if os.path.exists(os.path.join('/','usr','share','mate-panel','layouts', new_layout + '.panel')):
            print('Loading additional panel configuration for ' + new_layout)
            cmd = 'dconf load /org/mate/panel/ < /usr/share/mate-panel/layouts/' + new_layout + '.panel'
            subprocess.call(cmd, shell=True, stdout=DEVNULL, stderr=DEVNULL)

        # Determine if the dock should be enabled
        if (os.path.exists(os.path.join('/','usr','share','mate-panel','layouts', new_layout + '.dock')) and self.dock is not None) or \
           self.is_panel_layout_name_special(new_layout, ['eleven', 'contemporary']):
            print('Found dock hint for ' + new_layout)
            self.enable_dock()
        else:
            self.disable_dock()

        # Set the new layout
        subprocess.call(['mate-panel', '--reset', '--layout', new_layout], stdout=DEVNULL, stderr=DEVNULL)

        if leds_enabled:
            time.sleep(1)
            self.set_bool('org.mate.peripherals-keyboard-xkb.general', None, 'duplicate-leds', True)

        self.current_layout = new_layout



    def delete_panel(self, widget):
        # Make certain we don't delete a system installed layout
        if 'tweak' in self.current_layout:
            # Get default layout
            settings = Gio.Settings.new('org.mate.panel')
            default_layout = GLib.Variant.get_string(settings.get_default_value('default-layout'))
            old_layout = self.current_layout

            if self.confirm_dialog():
                delete = subprocess.call(['pkexec', self.mate_layouts_helper, 'delete', old_layout], stdout=DEVNULL, stderr=DEVNULL)
                Notify.init(_('MATE Layouts'))
                delete_panel_notify=Notify.Notification.new (_('Panel Layout Deleted'),_('Your panel layout has been deleted: ') + old_layout.replace('-tweak','') , 'dialog-information')
                delete_panel_notify.show()

                self.make_list_of_panel_layouts()
                self.replace_panel_layout(default_layout)

                source = Gio.SettingsSchemaSource.get_default()

    def save_panel(self, widget):
        # layout name dialog
        layoutname = self.layout_name_dialog()
        if layoutname is not None:
            layoutnameprefix = self.get_custom_layout_file_prefix(self.current_layout)
            print(layoutnameprefix, " ", layoutname)
            layoutname = layoutnameprefix + layoutname + '-tweak'
            print('Saving ' + layoutname)
            if self.panel_layout_exists(layoutname):
                print('Layout exists. Ignoring that for now and over writting it.')

            backup = subprocess.call([self.mate_layouts_helper, 'backup', layoutname], stdout=DEVNULL, stderr=DEVNULL)
            if self.dock_enabled:
                dock = subprocess.call([self.mate_layouts_helper, 'dock', layoutname], stdout=DEVNULL, stderr=DEVNULL)
            install = subprocess.call(['pkexec', self.mate_layouts_helper, 'install', layoutname], stdout=DEVNULL, stderr=DEVNULL)
            Notify.init(_('MATE Layouts'))
            save_panel_notify = Notify.Notification.new (_('Panel Layout Saved'),_('Your panel layout has been saved as ') + layoutname.replace('-tweak',''), 'dialog-information')
            save_panel_notify.show()

            # Update the currently selected layout.
            self.make_list_of_panel_layouts()

    def export_panel(self, widget):
        filename = self.filechooser_dialog_export()
        if filename == "":
            return
        backup = subprocess.call([self.mate_layouts_helper, 'backup', self.current_layout], stdout=DEVNULL, stderr=DEVNULL)
        if self.dock_enabled:
            dock = subprocess.call([self.mate_layouts_helper, 'dock', self.current_layout], stdout=DEVNULL, stderr=DEVNULL)
        install = subprocess.call(['pkexec', self.mate_layouts_helper, 'export', self.current_layout, filename], stdout=DEVNULL, stderr=DEVNULL)
        Notify.init(_('MATE Layouts'))
        export_panel_notify = Notify.Notification.new (_('Panel Layout Exported'),_('Your panel layout has been exported as ') + self.current_layout.replace('-tweak',''), 'dialog-information')
        export_panel_notify.show()

    def import_panel(self, widget):
        filepath = self.filechooser_dialog_import()
        if filepath == "":
            return
        import_call = subprocess.call([self.mate_layouts_helper, 'import', filepath.rsplit(".")[0]], stdout=DEVNULL, stderr=DEVNULL)
        install = subprocess.call(['pkexec', self.mate_layouts_helper, 'install', os.path.basename(filepath).rsplit(".")[0]], stdout=DEVNULL, stderr=DEVNULL)
        Notify.init(_('MATE Layouts'))
        export_panel_notify = Notify.Notification.new (_('Panel Layout Imported'),_('Your panel layout has been imported as ') + self.current_layout.replace('-tweak',''), 'dialog-information')
        export_panel_notify.show()
        # Update the currently selected layout.
        self.make_list_of_panel_layouts()


    ######################
    ## Gsettings methods
    ######################
    def schema_has_key(self, schema, key):
        source = Gio.SettingsSchemaSource.get_default()
        if source.lookup(schema, True):
            settings=Gio.Settings.new(schema)
            schema = settings.get_property('settings-schema')
            return schema.has_key(key)
        else:
            return False

    def set_string(self, schema, path, key, value):
        if path:
            settings = Gio.Settings.new_with_path(schema, path)
        else:
            settings = Gio.Settings.new(schema)
        try:
            settings.set_string(key, value)
        except:
            print('Unable set ' + key + ' with ' + value + ' in ' + schema)
            pass

    def get_string(self, schema, path, key):
        if path:
            settings = Gio.Settings.new_with_path(schema, path)
        else:
            settings = Gio.Settings.new(schema)
        return settings.get_string(key)

    def get_int(self, schema, path, key):
        if path:
            settings = Gio.Settings.new_with_path(schema, path)
        else:
            settings = Gio.Settings.new(schema)
        return settings.get_int(key)

    def set_int(self, schema, path, key, value):
        if path:
            settings = Gio.Settings.new_with_path(schema, path)
        else:
            settings = Gio.Settings.new(schema)
        settings.set_int(key, value)

    def get_enum(self, schema, path, key):
        if path:
            settings = Gio.Settings.new_with_path(schema, path)
        else:
            settings = Gio.Settings.new(schema)
        return settings.get_enum(key)

    def set_enum(self, schema, path, key, value):
        if path:
            settings = Gio.Settings.new_with_path(schema, path)
        else:
            settings = Gio.Settings.new(schema)
        settings.set_enum(key, value)

    def get_bool(self, schema, path, key):
        if path:
            settings = Gio.Settings.new_with_path(schema, path)
        else:
            settings = Gio.Settings.new(schema)
        return settings.get_boolean(key)

    def set_bool(self, schema, path, key, value):
        if path:
            settings = Gio.Settings.new_with_path(schema, path)
        else:
            settings = Gio.Settings.new(schema)
        settings.set_boolean(key, value)

    def reset_dconf_path(self, path):
        subprocess.call(['dconf', 'reset', '-f', path], stdout=DEVNULL, stderr=DEVNULL)

    def set_dconf_value(self, path, value):
        subprocess.call(['dconf', 'write', path, value], stdout=DEVNULL, stderr=DEVNULL)

    def get_dconf_value(self, path):
        dconf = subprocess.Popen(['dconf', 'read', path], stdin=PIPE, stdout=PIPE, stderr=PIPE)
        dconf_output, dconf_error = dconf.communicate()
        return dconf_output.decode().strip()


    def process_running(self, name):
        uid = os.getuid()
        for process in psutil.process_iter():
            try:
                proc = process.as_dict(attrs=['name', 'uids'])
            except psutil.NoSuchProcess:
                pass
            else:
                if name == proc['name'] and uid == proc['uids'].real:
                    return True
        return False

    def kill_process(self, name):
        uid = os.getuid()
        for process in psutil.process_iter():
            try:
                proc = process.as_dict(attrs=['name', 'pid', 'uids'])
            except psutil.NoSuchProcess:
                pass
            else:
                if name == proc['name'] and uid == proc['uids'].real:
                    try:
                        target = psutil.Process(proc['pid'])
                        target.kill()
                    except psutil.NoSuchProcess:
                        pass

    def find_on_path(self, command):
        """Is command on the executable search path?"""
        if 'PATH' not in os.environ:
            return False
        path = os.environ['PATH']
        for element in path.split(os.pathsep):
            if not element:
                continue
            filename = os.path.join(element, command)
            if os.path.isfile(filename) and os.access(filename, os.X_OK):
                return True
        return False

    def mkdir_p(self, path):
        try:
            os.makedirs(path)
        except OSError as exc: # Python >2.5
            if exc.errno == errno.EEXIST and os.path.isdir(path):
                pass
            else:
                raise

    def create_autostart(self, filename, content):
        # Create a local autostart only if it is missing from the system path.
        if not os.path.exists(os.path.join('/','usr','share','mate','autostart',filename)):
            config_dir = GLib.get_user_config_dir()
            self.mkdir_p(os.path.join(config_dir, 'autostart/'))
            if not os.path.exists(os.path.join(config_dir, 'autostart',filename)):
                with open(os.path.join(config_dir, 'autostart', filename),'w') as autostart:
                    autostart.write(content)

    def autostart_exists(self, filename):
        config_dir = GLib.get_user_config_dir()
        if os.path.exists('/etc/xdg/autostart/' + filename) or \
            os.path.exists(os.path.join(config_dir, 'autostart/') + filename):
            return True
        else:
            return False

    def remove_autostart(self, filename):
        config_dir = GLib.get_user_config_dir()
        autostart_file = os.path.join(config_dir, 'autostart', filename)
        if os.path.exists(autostart_file):
            os.remove(autostart_file)

        # Make sure any system desktop files are also removed
        subprocess.call(['pkexec', self.mate_layouts_helper, 'autostop', filename], stdout=DEVNULL, stderr=DEVNULL)


    def enable_dock(self):
        self.set_string('org.mate.session.required-components', None, 'dock', self.dock)
        if self.dock:
            self.remove_autostart(self.dock + '.desktop')

        # Launch the dock, if it is not already enabled.
        print("dock_enabled: ", self.dock_enabled)
        if not self.dock_enabled:
            pid = subprocess.Popen([self.dock], stdout=DEVNULL, stderr=DEVNULL).pid
            self.dock_enabled = True

    def disable_dock(self):
        self.set_string('org.mate.session.required-components', None, 'dock', '')
        if self.dock:
            self.remove_autostart(self.dock + '.desktop')
        self.kill_process(self.dock)
        self.dock_enabled = False


    def update_panel_layout_ui(self, panel_layout):
        if 'tweak' in panel_layout:
            self.builder.get_object('button_delete_panel').props.sensitive = True
        else:
            self.builder.get_object('button_delete_panel').props.sensitive = False

    def reload_panel(self):
        pid = subprocess.Popen(['mate-panel', '--replace'], stdout=DEVNULL, stderr=DEVNULL).pid


    ######################
    ## Dialogs
    ######################

    def confirm_dialog(self):
        dialog = self.builder.get_object("confirm_dialog")
        dialog.show_all()
        response = dialog.run()
        dialog.hide()

        if (response == Gtk.ResponseType.OK):
            return True

        return False

    def layout_name_dialog(self):
        dialog = self.builder.get_object("layout_name_dialog")
        dialog.show_all()
        response = dialog.run()
        name = self.builder.get_object("layout_name_dialog_entry").get_text().strip().replace(' ', '-')
        dialog.hide()

        if (response == Gtk.ResponseType.OK) and (name != ''):
            return name
        else:
            return None

    def filechooser_dialog_export(self):
        res = self.builder.get_object("filechooser_dialog_export").run()
        if res == Gtk.ResponseType.OK:
            self.builder.get_object("filechooser_dialog_export").hide()
            return self.builder.get_object("filechooser_dialog_export").get_filename()
        else:
            self.builder.get_object("filechooser_dialog_export").hide()
            return ""

    def filechooser_dialog_import(self):
        res = self.builder.get_object("filechooser_dialog_import").run()
        if res == Gtk.ResponseType.OK:
            self.builder.get_object("filechooser_dialog_import").hide()
            return self.builder.get_object("filechooser_dialog_import").get_filename()
        else:
            self.builder.get_object("filechooser_dialog_import").hide()
            return ""



    #############################
    ## frontend helper functions
    #############################

    def update_layout_view(self):
        for layout_item in self.layout_list:
            self.builder.get_object("layout_view").add(layout_item)
        current_layout_item = self.get_layout_item_by_layout_code_name(self.current_layout)
        if current_layout_item != None:
            self.builder.get_object("layout_view").select_child(current_layout_item.get_parent())
        self.builder.get_object("layout_view").show_all()
        self.builder.get_object("layout_view").grab_focus()

    def on_layout_view_item_activated(self, layout_view, layout_item):
        self.update_panel_layout_ui(layout_item.get_child().code_name)
        self.replace_panel_layout(layout_item.get_child().code_name)

    def mouse_enter_layout_item(layout_item, event, mate_layouts_object):
        if mate_layouts_object.get_right_path('layout-previews/' + layout_item.get_parent().code_name + '-logo' + '-symbolic.svg') != '':
           layout_item.get_parent().image.set_from_file(mate_layouts_object.get_right_path('layout-previews/' + layout_item.get_parent().code_name + '-logo' + '-symbolic.svg'))
        elif layout_item.get_parent().code_name.endswith('tweak'):
           layout_item.get_parent().image.set_from_file(mate_layouts_object.get_right_path('layout-previews/custom-logo-symbolic.svg'))

    def mouse_leave_layout_item(layout_item, event, mate_layouts_object):
        if mate_layouts_object.get_right_path('layout-previews/' + layout_item.get_parent().code_name + '-symbolic.svg') != '':
           layout_item.get_parent().image.set_from_file(mate_layouts_object.get_right_path('layout-previews/' + layout_item.get_parent().code_name + '-symbolic.svg'))
        elif layout_item.get_parent().code_name.endswith('tweak'):
           layout_item.get_parent().image.set_from_file(mate_layouts_object.get_right_path('layout-previews/custom-symbolic.svg'))


    #############################
    ## backend helper functions
    #############################

    def get_panel_layout(self):
        self.current_layout = self.get_string('org.mate.panel', None, 'default-layout')

    def init_panel_features(self):
        self.get_panel_layout()
        print ('Current layout: ' + self.current_layout)
        self.update_panel_layout_ui(self.current_layout)

    def panel_layout_exists(self, panel_layout):
        return os.path.exists('/usr/share/mate-panel/layouts/' + panel_layout + '.layout')


    def add_to_panel_list(self, panel_list_store, item_display_name, item_code_name, item_thumbnail):
       self.layout_list.append(LayoutItem(self, item_thumbnail, _(item_display_name), item_code_name))
       if not item_code_name in self.system_installed_panel_layouts:
          self.system_installed_panel_layouts.append(item_code_name)

    def get_custom_panel_list_entry(self, layout_full_name):
        layout_code_name = layout_full_name.replace('.layout','').replace('/usr/share/mate-panel/layouts/', '')
        layout_display_name = layout_code_name

        custom_layout_prefix = self.get_custom_layout_file_prefix(layout_code_name)

        if custom_layout_prefix != '':
            layout_display_name = layout_code_name.replace(custom_layout_prefix, '', 1)

        layout_display_name = layout_display_name.replace('-tweak', '')

        result = {'displayname': _('Custom: ') + layout_display_name,
                  'codename': layout_code_name}

        return result

    def get_custom_layout_file_prefix(self, layout_code_name):
        result = ''

        for layout_template in self.system_installed_panel_layouts:
            if self.is_panel_layout_name_special(layout_code_name, [layout_template]):
                result = layout_template + '-'

        return result

    def get_right_path(self, suffix):
        if os.path.exists('./data/' + suffix):
           return './data/' + suffix
        elif os.path.exists('../data/' + suffix):
           return '../data/' + suffix
        elif os.path.exists('/usr/share/' + suffix):
           return '/usr/share/' + suffix
        elif os.path.exists('/usr/share/mate-layouts/' + suffix):
           return '/usr/share/mate-layouts/' + suffix
        elif os.path.exists('/usr/local/share/' + suffix):
           return '/usr/local/share/' + suffix
        elif os.path.exists('/usr/lib/' + suffix):
           return '/usr/lib/' + suffix
        elif os.path.exists('/usr/libexec/' + suffix):
           return '/usr/libexec/' + suffix
        elif os.path.exists('/bin/' + suffix):
           return '/bin/' + suffix
        else:
           return ''

    def get_layout_item_by_layout_code_name(self, layout_code_name):
       for layout_item in self.layout_list:
          if layout_item.code_name == layout_code_name:
             return layout_item
       return None


    #############################
    ## init function
    #############################

    def __init__(self, called_from_api=False):
        self.multiarch = sysconfig.get_config_var('MULTIARCH')
        self.current_layout = ''
        self.check_dock_features()
        self.check_panel_features()
        self.check_appmenu()
        self.mate_layouts_helper = ''

        # load helper script
        if os.path.exists('mate-layouts-helper'):
            self.mate_layouts_helper = 'mate-layouts-helper'
        elif os.path.exists('/usr/lib/mate-layouts/mate-layouts-helper'):
            self.mate_layouts_helper = '/usr/lib/mate-layouts/mate-layouts-helper'
        if self.mate_layouts_helper == '':
           print('ERROR! Unable to find \'mate-layouts-helper\' script.')

        # Load the Glade UI file
        self.builder = Gtk.Builder()
        if os.path.exists('../data/org.github.FreaxMATE.mate-layouts.ui'):
            print('Development mode.')
            self.builder.add_from_file('../data/org.github.FreaxMATE.mate-layouts.ui')
        elif os.path.exists('/usr/share/mate-layouts//org.github.FreaxMATE.mate-layouts.ui'):
            self.builder.add_from_file('/usr/share/mate-layouts//org.github.FreaxMATE.mate-layouts.ui')
        else:
            print("ERROR! Unable to find glade file.")

        self.window = self.builder.get_object( "main_window" )
        self.builder.get_object("button_save_panel").connect("clicked", self.save_panel)
        self.builder.get_object("button_delete_panel").connect("clicked", self.delete_panel)
        self.builder.get_object("button_export_panel").connect("clicked", self.export_panel)
        self.builder.get_object("button_import_panel").connect("clicked", self.import_panel)
        self.builder.get_object("layout_view").connect("child-activated", self.on_layout_view_item_activated)

        # Panel layouts
        self.init_panel_features()
        self.make_list_of_panel_layouts()

        if not called_from_api:
            response = self.window.run()
            if response == Gtk.ResponseType.CLOSE:
                self.window.destroy()

if __name__ == "__main__":
    setproctitle.setproctitle('mate-layouts')

    parser = argparse.ArgumentParser()
    parser.add_argument('--layout', help="Switch to a panel layout")
    parser.add_argument('--get-layout', action='store_true', help="Get the current panel layout")
    args = parser.parse_args()

    # If we've been given a layout then attempt to switch layouts.
    if args.layout:
        ml = MateLayouts(True)
        if ml.panel_layout_exists(args.layout):
            ml.replace_panel_layout(args.layout, True)
        else:
            print("ERROR! Unable to find layout: " + args.layout)
    elif args.get_layout:
        ml = MateLayouts()
    else:
        MateLayouts()

