/*============================================================================
Copyright (c) 2018-2025 Raspberry Pi
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
============================================================================*/

#include <locale.h>
#include <glib/gi18n.h>

#ifdef LXPLUG
#include "plugin.h"
#else
#include "lxutils.h"
#endif

#include "ejecter.h"

/*----------------------------------------------------------------------------*/
/* Typedefs and macros                                                        */
/*----------------------------------------------------------------------------*/

#define DEBUG_ON
#ifdef DEBUG_ON
#define DEBUG(fmt,args...) if(getenv("DEBUG_EJ"))g_message("ej: " fmt,##args)
#else
#define DEBUG(fmt,args...)
#endif

#define HIDE_TIME_MS 5000

typedef struct {
    EjecterPlugin *ej;
    GDrive *drv;
} CallbackData;

typedef struct {
    GDrive *drv;
    int seq;
} EjectList;

/*----------------------------------------------------------------------------*/
/* Global data                                                                */
/*----------------------------------------------------------------------------*/

conf_table_t conf_table[3] = {
    {CONF_TYPE_BOOL, "autohide",    N_("Hide icon when no devices"),    NULL,   "true"  },
    {CONF_TYPE_BOOL, "automount",   N_("Automount removable devices"),  NULL,   "true"  },
    {CONF_TYPE_NONE,  NULL,         NULL,                               NULL,   NULL    }
};

/*----------------------------------------------------------------------------*/
/* Prototypes                                                                 */
/*----------------------------------------------------------------------------*/

static void log_eject (EjecterPlugin *ej, GDrive *drive);
static gboolean was_ejected (EjecterPlugin *ej, GDrive *drive);
static void log_mount (EjecterPlugin *ej, GMount *mount);
static void log_init_mounts (EjecterPlugin *ej);
static gboolean was_mounted (EjecterPlugin *ej, GDrive *drive);
static void add_seq_for_drive (EjecterPlugin *ej, GDrive *drive, int seq);
static void handle_mount_in (GtkWidget *, GMount *mount, gpointer data);
static void handle_mount_out (GtkWidget *, GMount *mount, gpointer data);
static void handle_mount_pre (GtkWidget *, GMount *mount, gpointer data);
static void handle_volume_in (GtkWidget *, GVolume *vol, gpointer data);
static void handle_volume_out (GtkWidget *, GVolume *vol, gpointer data);
static void handle_drive_in (GtkWidget *, GDrive *drive, gpointer data);
static void handle_drive_out (GtkWidget *, GDrive *drive, gpointer data);
static void handle_eject_clicked (GtkWidget *widget, gpointer ptr);
static void eject_done (GObject *source_object, GAsyncResult *res, gpointer ptr);
static void stop_done (GObject *source_object, GAsyncResult *res, gpointer ptr);
static void vol_unmount_done (GObject *source_object, GAsyncResult *res, gpointer ptr);
static void vol_eject_done (GObject *source_object, GAsyncResult *res, gpointer ptr);
static gboolean is_drive_mounted (GDrive *d);
static void update_icon (EjecterPlugin *ej);
static void show_menu (EjecterPlugin *ej);
static void hide_menu (EjecterPlugin *ej);
static GtkWidget *create_menuitem (EjecterPlugin *ej, GDrive *d);
static void ejecter_button_clicked (GtkWidget *, EjecterPlugin * ej);

/*----------------------------------------------------------------------------*/
/* Function definitions                                                       */
/*----------------------------------------------------------------------------*/

static void log_eject (EjecterPlugin *ej, GDrive *drive)
{
    EjectList *el;
    el = g_new (EjectList, 1);
    el->drv = drive;
    el->seq = -1;
    ej->ejdrives = g_list_append (ej->ejdrives, el);
}

static gboolean was_ejected (EjecterPlugin *ej, GDrive *drive)
{
    GList *l;
    gboolean ejected = FALSE;
    for (l = ej->ejdrives; l != NULL; l = l->next)
    {
        EjectList *el = (EjectList *) l->data;
        if (el->drv == drive)
        {
            ejected = TRUE;
            if (el->seq != -1) wrap_notify_clear (el->seq);
            ej->ejdrives = g_list_remove (ej->ejdrives, el);
            g_free (el);
        }
    }
    return ejected;
}

static void log_mount (EjecterPlugin *ej, GMount *mount)
{
    GList *l;
    GDrive *drv, *drive = g_mount_get_drive (mount);
    for (l = ej->mdrives; l != NULL; l = l->next)
    {
        drv = (GDrive *) l->data;
        if (drv == drive)
        {
            g_object_unref (drive);
            return;
        }
    }

    ej->mdrives = g_list_append (ej->mdrives, drive);
    DEBUG ("MOUNTED DRIVE %s", g_drive_get_name (drive));
    g_object_unref (drive);
}

static void log_init_mounts (EjecterPlugin *ej)
{
    ej->mdrives = NULL;
    GList *l, *mnts = g_volume_monitor_get_mounts (ej->monitor);
    for (l = mnts; l != NULL; l = l->next)
        log_mount (ej, (GMount *) l->data);
    g_list_free_full (mnts, g_object_unref);
}

static gboolean was_mounted (EjecterPlugin *ej, GDrive *drive)
{
    GList *l;
    GDrive *drv;
    for (l = ej->mdrives; l != NULL; l = l->next)
    {
        drv = (GDrive *) l->data;
        if (drv == drive)
        {
            ej->mdrives = g_list_remove (ej->mdrives, drv);
            return TRUE;
        }
    }
    return FALSE;
}

static void add_seq_for_drive (EjecterPlugin *ej, GDrive *drive, int seq)
{
    GList *l;
    for (l = ej->ejdrives; l != NULL; l = l->next)
    {
        EjectList *el = (EjectList *) l->data;
        if (el->drv == drive)
        {
            el->seq = seq;
            return;
        }
    }
}

static void handle_mount_in (GtkWidget *, GMount *mount, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    DEBUG ("MOUNT ADDED %s", g_mount_get_name (mount));

    log_mount (ej, mount);
    if (ej->menu && gtk_widget_get_visible (ej->menu)) show_menu (ej);
    update_icon (ej);
}

static void handle_mount_out (GtkWidget *, GMount *mount, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    DEBUG ("MOUNT REMOVED %s", g_mount_get_name (mount));

    if (ej->menu && gtk_widget_get_visible (ej->menu)) show_menu (ej);
    update_icon (ej);
}

static void handle_mount_pre (GtkWidget *, GMount *mount, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    DEBUG ("MOUNT PREUNMOUNT %s", g_mount_get_name (mount));
    log_eject (ej, g_mount_get_drive (mount));
}

static void mount_done (GVolume *vol, GAsyncResult *res, gpointer)
{
    if (g_volume_mount_finish (vol, res, NULL))
    {
#ifndef LXPLUG
        GDrive *drv = g_volume_get_drive (vol);
        GMount *mnt = g_volume_get_mount (vol);
        GFile *root = g_mount_get_root (mnt);
        char *name = g_drive_get_name (drv);
        char *msg = g_strdup_printf (_("Removable drive %s connected"), name);
        char *path = g_file_get_path (root);

        GNotification *not = g_notification_new (msg);
        g_notification_add_button_with_target (not, _("Open"), "app.open-mount", "s", path);
        g_application_send_notification (g_application_get_default (), name, not);
        g_object_unref (not);

        g_free (path);
        g_free (msg);
        g_free (name);
        g_object_unref (root);
        g_object_unref (mnt);
        g_object_unref (drv);
#endif
    }
}

#ifndef LXPLUG
static gboolean open_mount (GSimpleAction *, GVariant *param, gpointer)
{
    char *cmd = g_strdup_printf ("pcmanfm %s &", g_variant_get_string (param, NULL));
    system (cmd);
    g_free (cmd);
    return FALSE;
}
#endif

static void handle_volume_in (GtkWidget *, GVolume *vol, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    DEBUG ("VOLUME ADDED %s", g_volume_get_name (vol));

    if (ej->automount && g_volume_should_automount (vol) && g_volume_can_mount (vol) && !g_volume_get_mount (vol))
        g_volume_mount (vol, 0, NULL, NULL, (GAsyncReadyCallback) mount_done, NULL);

    if (ej->menu && gtk_widget_get_visible (ej->menu)) show_menu (ej);
    update_icon (ej);
}

static void handle_volume_out (GtkWidget *, GVolume *vol, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    DEBUG ("VOLUME REMOVED %s", g_volume_get_name (vol));

    if (ej->menu && gtk_widget_get_visible (ej->menu)) show_menu (ej);
    update_icon (ej);
}

static void handle_drive_in (GtkWidget *, GDrive *drive, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    DEBUG ("DRIVE ADDED %s", g_drive_get_name (drive));

    if (ej->menu && gtk_widget_get_visible (ej->menu)) show_menu (ej);
    update_icon (ej);
}

static void handle_drive_out (GtkWidget *, GDrive *drive, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    DEBUG ("DRIVE REMOVED %s", g_drive_get_name (drive));

    if (was_mounted (ej, drive) && !was_ejected (ej, drive))
    {
        // filter Pico loader
        char *name = g_drive_get_name (drive);
        if (strncmp (name, "RPI RP2", 7))
        {
#ifndef LXPLUG
            g_application_withdraw_notification (g_application_get_default (), name);
#endif
            wrap_notify (ej->panel, _("Drive was removed without ejecting\nPlease use menu to eject before removal"));
        }
        g_free (name);
    }

    if (ej->menu && gtk_widget_get_visible (ej->menu)) show_menu (ej);
    update_icon (ej);
}

static void handle_eject_clicked (GtkWidget *, gpointer data)
{
    CallbackData *dt = (CallbackData *) data;
    EjecterPlugin *ej = dt->ej;
    GDrive *drv = dt->drv;
    char *id = g_drive_get_identifier (drv, G_DRIVE_IDENTIFIER_KIND_UNIX_DEVICE);
    DEBUG ("EJECT %s", g_drive_get_name (drv));

    // this should probably be replaced with a proper less hacky test at some point...
    if (g_drive_is_media_removable (drv) && !strstr (id, "mmcblk0"))
    {
        DEBUG ("EJECTING DRIVE");
        g_drive_eject_with_operation (drv, G_MOUNT_UNMOUNT_NONE, NULL, NULL, eject_done, ej);
    }
    else if (g_drive_can_stop (drv))
    {
        DEBUG ("STOPPING DRIVE");
        g_drive_stop (drv, G_MOUNT_UNMOUNT_NONE, NULL, NULL, stop_done, ej);
    }
    else
    {
        DEBUG ("EJECTING VOLUMES");

        GList *vols, *iter;
        GVolume *v;
        vols = g_drive_get_volumes (drv);
        for (iter = vols; iter != NULL; iter = g_list_next (iter))
        {
            v = (GVolume *) iter->data;
            GMount *mnt = g_volume_get_mount (v);
            if (mnt)
            {
                CallbackData *dtn = g_new0 (CallbackData, 1);
                dtn->ej = ej;
                dtn->drv = drv;
                if (g_mount_can_eject (mnt))
                {
                    DEBUG ("EJECTING VOLUME");
                    g_mount_eject_with_operation (mnt, G_MOUNT_UNMOUNT_NONE, NULL, NULL, vol_eject_done, dtn);
                }
                else if (g_mount_can_unmount (mnt))
                {
                    DEBUG ("UNMOUNTING VOLUME");
                    g_mount_unmount_with_operation (mnt, G_MOUNT_UNMOUNT_NONE, NULL, NULL, vol_unmount_done, dtn);
                }
                else
                {
                    DEBUG ("CANNOT EJECT OR UNMOUNT");
                }
                g_object_unref (mnt);
                break;
            }
            if (mnt) g_object_unref (mnt);
        }
        g_list_free_full (vols, g_object_unref);
    }
    g_free (id);
}

static void eject_done (GObject *source_object, GAsyncResult *res, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    GDrive *drv = (GDrive *) source_object;
    char *buffer, *name;
    GError *err = NULL;

    name = g_drive_get_name (drv);
    g_drive_eject_with_operation_finish (drv, res, &err);

    if (err == NULL)
    {
        DEBUG ("EJECT COMPLETE");
#ifndef LXPLUG
        g_application_withdraw_notification (g_application_get_default (), name);
#endif
        buffer = g_strdup_printf (_("%s has been ejected\nIt is now safe to remove the device"), name);
        add_seq_for_drive (ej, drv, wrap_notify (ej->panel, buffer));
    }
    else
    {
        DEBUG ("EJECT FAILED");
        buffer = g_strdup_printf (_("Failed to eject %s\n%s"), name, err->message);
        wrap_notify (ej->panel, buffer);
    }
    g_free (name);
    g_free (buffer);
}

static void stop_done (GObject *source_object, GAsyncResult *res, gpointer data)
{
    EjecterPlugin *ej = (EjecterPlugin *) data;
    GDrive *drv = (GDrive *) source_object;
    char *buffer, *name;
    GError *err = NULL;

    name = g_drive_get_name (drv);
    g_drive_stop_finish (drv, res, &err);

    if (err == NULL)
    {
        DEBUG ("STOP COMPLETE");
#ifndef LXPLUG
        g_application_withdraw_notification (g_application_get_default (), name);
#endif
        buffer = g_strdup_printf (_("%s has been stopped\nIt is now safe to remove the device"), name);
    }
    else
    {
        DEBUG ("STOP FAILED");
        buffer = g_strdup_printf (_("Failed to stop %s\n%s"), name, err->message);
    }
    wrap_notify (ej->panel, buffer);
    g_free (name);
    g_free (buffer);
}

static void vol_eject_done (GObject *source_object, GAsyncResult *res, gpointer data)
{
    CallbackData *dt = (CallbackData *) data;
    EjecterPlugin *ej = dt->ej;
    GMount *mnt = (GMount *) source_object;
    GDrive *drv = dt->drv;
    char *buffer, *name = g_drive_get_name (drv);
    GError *err = NULL;

    g_mount_eject_with_operation_finish (mnt, res, &err);

    if (err == NULL)
    {
        DEBUG ("VOL EJECT COMPLETE");
#ifndef LXPLUG
        g_application_withdraw_notification (g_application_get_default (), name);
#endif
        buffer = g_strdup_printf (_("%s has been ejected\nIt is now safe to remove the device"), name);
        wrap_notify (ej->panel, buffer);
        add_seq_for_drive (ej, drv, wrap_notify (ej->panel, buffer));
    }
    else
    {
        DEBUG ("VOL EJECT FAILED");
        buffer = g_strdup_printf (_("Failed to eject %s\n%s"), name, err->message);
        wrap_notify (ej->panel, buffer);
    }
    g_free (name);
    g_free (buffer);
    g_free (dt);
}

static void vol_unmount_done (GObject *source_object, GAsyncResult *res, gpointer data)
{
    CallbackData *dt = (CallbackData *) data;
    EjecterPlugin *ej = dt->ej;
    GMount *mnt = (GMount *) source_object;
    GDrive *drv = dt->drv;
    char *buffer, *name = g_drive_get_name (drv);
    GError *err = NULL;

    g_mount_unmount_with_operation_finish (mnt, res, &err);

    if (err == NULL)
    {
        DEBUG ("VOL UNMOUNT COMPLETE");
#ifndef LXPLUG
        g_application_withdraw_notification (g_application_get_default (), name);
#endif
        buffer = g_strdup_printf (_("%s has been unmounted\nIt is now safe to remove the device"), name);
        wrap_notify (ej->panel, buffer);
        add_seq_for_drive (ej, drv, wrap_notify (ej->panel, buffer));
    }
    else
    {
        DEBUG ("VOL UNMOUNT FAILED");
        buffer = g_strdup_printf (_("Failed to unmount %s\n%s"), name, err->message);
        wrap_notify (ej->panel, buffer);
    }
    g_free (name);
    g_free (buffer);
    g_free (dt);
}

/* Ejecter functions */

static gboolean is_drive_mounted (GDrive *d)
{
    GList *viter, *vols = g_drive_get_volumes (d);
    gboolean res = FALSE;

    for (viter = vols; viter != NULL; viter = g_list_next (viter))
    {
        GMount *mnt = g_volume_get_mount ((GVolume *) viter->data);
        if (mnt != NULL)
        {
            res = TRUE;
            g_object_unref (mnt);
        }
    }
    g_list_free_full (vols, g_object_unref);
    return res;
}

static void update_icon (EjecterPlugin *ej)
{
    if (ej->autohide)
    {
        /* loop through all devices, checking for mounted volumes */
        GList *driter, *drives = g_volume_monitor_get_connected_drives (ej->monitor);

        gtk_widget_hide (ej->plugin);
        gtk_widget_set_sensitive (ej->plugin, FALSE);
        for (driter = drives; driter != NULL; driter = g_list_next (driter))
        {
            GDrive *drv = (GDrive *) driter->data;
            if (is_drive_mounted (drv))
            {
                gtk_widget_show_all (ej->plugin);
                gtk_widget_set_sensitive (ej->plugin, TRUE);
            }
        }
        g_list_free_full (drives, g_object_unref);
    }
    else
    {
        gtk_widget_show_all (ej->plugin);
        gtk_widget_set_sensitive (ej->plugin, TRUE);
    }
}

static void show_menu (EjecterPlugin *ej)
{
    hide_menu (ej);

    ej->menu = gtk_menu_new ();
    gtk_menu_set_reserve_toggle_size (GTK_MENU (ej->menu), FALSE);

    /* loop through all devices, creating menu items for them */
    GList *driter, *drives = g_volume_monitor_get_connected_drives (ej->monitor);
    int count = 0;

    for (driter = drives; driter != NULL; driter = g_list_next (driter))
    {
        GDrive *drv = (GDrive *) driter->data;
        if (is_drive_mounted (drv))
        {
            GtkWidget *item = create_menuitem (ej, drv);
            CallbackData *dt = g_new0 (CallbackData, 1);
            dt->ej = ej;
            dt->drv = drv;
            g_signal_connect (item, "activate", G_CALLBACK (handle_eject_clicked), dt);
            gtk_menu_shell_append (GTK_MENU_SHELL (ej->menu), item);
            count++;
        }
    }
    g_list_free_full (drives, g_object_unref);

    if (count)
    {
        gtk_widget_show_all (ej->menu);
        wrap_show_menu (ej->plugin, ej->menu);
    }
}

static void hide_menu (EjecterPlugin *ej)
{
    if (ej->menu)
    {
        gtk_menu_popdown (GTK_MENU (ej->menu));
        gtk_widget_destroy (ej->menu);
        ej->menu = NULL;
    }
}

static GtkWidget *create_menuitem (EjecterPlugin *ej, GDrive *d)
{
    char buffer[1024], *name, *vname;
    GList *vols, *iter;
    GVolume *v;
    GIcon *ic = NULL;
    GtkWidget *item, *icon, *eject;

    vols = g_drive_get_volumes (d);
    name = g_drive_get_name (d);

    sprintf (buffer, "%s (", name);
    gboolean first = TRUE;
    for (iter = vols; iter != NULL; iter = g_list_next (iter))
    {
        v = (GVolume *) iter->data;
        vname = g_volume_get_name (v);
        if (vname)
        {
            if (first)
            {
                ic = g_volume_get_icon (v);
                first = FALSE;
            }
            else strcat (buffer, ", ");
            strcat (buffer, vname);
            g_free (vname);
        }
    }
    g_list_free_full (vols, g_object_unref);

    strcat (buffer, ")");
    g_free (name);

    if (!ic) ic = g_drive_get_icon (d);
    icon = gtk_image_new_from_gicon (ic, wrap_icon_size (ej) >= 32 ? GTK_ICON_SIZE_LARGE_TOOLBAR : GTK_ICON_SIZE_BUTTON);
    g_object_unref (ic);

    item = wrap_new_menu_item (ej, buffer, 40, NULL);
    lxpanel_plugin_update_menu_icon (item, icon);

    eject = gtk_image_new ();
    wrap_set_menu_icon (ej, eject, "media-eject");
    lxpanel_plugin_append_menu_icon (item, eject);

    gtk_widget_show_all (item);

    return item;
}

/*----------------------------------------------------------------------------*/
/* wf-panel plugin functions                                                  */
/*----------------------------------------------------------------------------*/

/* Handler for button click */
static void ejecter_button_clicked (GtkWidget *, EjecterPlugin * ej)
{
    CHECK_LONGPRESS
    show_menu (ej);
}

/* Handler for system config changed message from panel */
void ejecter_update_display (EjecterPlugin * ej)
{
    wrap_set_taskbar_icon (ej, ej->tray_icon, "plugin-eject");
    update_icon (ej);
}

void ejecter_set_values (EjecterPlugin * ej)
{
    conf_table[0].value = (void *) &ej->autohide;
    conf_table[1].value = (void *) &ej->automount;
}

/* Handler for control message */
gboolean ejecter_control_msg (EjecterPlugin *ej, const char *cmd)
{
    DEBUG ("Eject command device %s\n", cmd);

    /* Loop through all drives until we find the one matching the supplied device */
    GList *iter, *drives = g_volume_monitor_get_connected_drives (ej->monitor);
    for (iter = drives; iter != NULL; iter = g_list_next (iter))
    {
        GDrive *d = iter->data;
        char *id = g_drive_get_identifier (d, "unix-device");

        if (!g_strcmp0 (id, cmd)) 
        {
            DEBUG ("EXTERNAL EJECT %s", g_drive_get_name (d));
            log_eject (ej, d);
        }
        g_free (id);
    }
    g_list_free_full (drives, g_object_unref);
    return TRUE;
}

void ejecter_init (EjecterPlugin *ej)
{
    setlocale (LC_ALL, "");
    bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");

    /* Allocate icon as a child of top level */
    ej->tray_icon = gtk_image_new ();
    gtk_container_add (GTK_CONTAINER (ej->plugin), ej->tray_icon);
    wrap_set_taskbar_icon (ej, ej->tray_icon, "plugin-eject");
    gtk_widget_set_tooltip_text (ej->tray_icon, _("Select a drive in menu to eject safely"));

    /* Set up button */
    gtk_button_set_relief (GTK_BUTTON (ej->plugin), GTK_RELIEF_NONE);
#ifndef LXPLUG
    g_signal_connect (ej->plugin, "clicked", G_CALLBACK (ejecter_button_clicked), ej);
    ej->gesture = add_long_press (ej->plugin, NULL, NULL);
#endif

    /* Set up variables */
    ej->popup = NULL;
    ej->menu = NULL;
    ej->hide_timer = 0;

    /* Get volume monitor and connect to events */
    ej->monitor = g_volume_monitor_get ();
    ej->handlers[0] = g_signal_connect (ej->monitor, "volume-added", G_CALLBACK (handle_volume_in), ej);
    ej->handlers[1] = g_signal_connect (ej->monitor, "volume-removed", G_CALLBACK (handle_volume_out), ej);
    ej->handlers[2] = g_signal_connect (ej->monitor, "mount-added", G_CALLBACK (handle_mount_in), ej);
    ej->handlers[3] = g_signal_connect (ej->monitor, "mount-removed", G_CALLBACK (handle_mount_out), ej);
    ej->handlers[4] = g_signal_connect (ej->monitor, "mount-pre-unmount", G_CALLBACK (handle_mount_pre), ej);
    ej->handlers[5] = g_signal_connect (ej->monitor, "drive-connected", G_CALLBACK (handle_drive_in), ej);
    ej->handlers[6] = g_signal_connect (ej->monitor, "drive-disconnected", G_CALLBACK (handle_drive_out), ej);

    /* try to automount all volumes */
    GList *vols, *l;
    vols = g_volume_monitor_get_volumes (ej->monitor);
    for (l = vols; l; l = l->next)
    {
        GVolume* vol = G_VOLUME (l->data);
        if (ej->automount && g_volume_should_automount (vol) && g_volume_can_mount (vol) && !g_volume_get_mount (vol))
            g_volume_mount (vol, 0, NULL, NULL, NULL, NULL);
        g_object_unref (vol);
    }
    g_list_free (vols);

    log_init_mounts (ej);

#ifndef LXPLUG
    GSimpleAction *act = g_simple_action_new_stateful ("open-mount", G_VARIANT_TYPE ("s"), g_variant_new_string (""));
    g_signal_connect (act, "activate", G_CALLBACK (open_mount), NULL);
    g_action_map_add_action (G_ACTION_MAP (g_application_get_default ()), G_ACTION (act));
#endif
}

void ejecter_destructor (gpointer user_data)
{
    EjecterPlugin *ej = (EjecterPlugin *) user_data;
    int i;

#ifndef LXPLUG
    if (ej->gesture) g_object_unref (ej->gesture);
#endif

    for (i = 0; i < 7; i++) g_signal_handler_disconnect (ej->monitor, ej->handlers[i]);
    g_object_unref (ej->monitor);

    g_free (ej);
}

/*----------------------------------------------------------------------------*/
/* LXPanel plugin functions                                                   */
/*----------------------------------------------------------------------------*/
#ifdef LXPLUG

/* Constructor */
static GtkWidget *ejecter_constructor (LXPanel *panel, config_setting_t *settings)
{
    /* Allocate and initialize plugin context */
    EjecterPlugin *ej = g_new0 (EjecterPlugin, 1);

    /* Allocate top level widget and set into plugin widget pointer. */
    ej->panel = panel;
    ej->settings = settings;
    ej->plugin = gtk_button_new ();
    lxpanel_plugin_set_data (ej->plugin, ej, ejecter_destructor);

    /* Read config */
    ejecter_set_values (ej);
    lxplug_read_settings (ej->settings, conf_table);

    ejecter_init (ej);

    return ej->plugin;
}

/* Handler for button press */
static gboolean ejecter_button_press_event (GtkWidget *widget, GdkEventButton *event, LXPanel *)
{
    EjecterPlugin *ej = lxpanel_plugin_get_data (widget);
    if (event->button == 1)
    {
        ejecter_button_clicked (widget, ej);
        return TRUE;
    }
    else return FALSE;
}

/* Handler for system config changed message from panel */
static void ejecter_configuration_changed (LXPanel *, GtkWidget *plugin)
{
    EjecterPlugin *ej = lxpanel_plugin_get_data (plugin);
    ejecter_update_display (ej);
}

/* Handler for control message */
static gboolean ejecter_control (GtkWidget *plugin, const char *cmd)
{
    EjecterPlugin *ej = lxpanel_plugin_get_data (plugin);
    return ejecter_control_msg (ej, cmd);
}

/* Apply changes from config dialog */
static gboolean ejecter_apply_configuration (gpointer user_data)
{
    EjecterPlugin *ej = lxpanel_plugin_get_data (GTK_WIDGET (user_data));

    lxplug_write_settings (ej->settings, conf_table);

    ejecter_update_display (ej);
    return FALSE;
}

/* Display configuration dialog */
static GtkWidget *ejecter_configure (LXPanel *panel, GtkWidget *plugin)
{
    return lxpanel_generic_config_dlg_new (_(PLUGIN_TITLE), panel,
        ejecter_apply_configuration, plugin,
        conf_table);
}

int module_lxpanel_gtk_version = 1;
char module_name[] = PLUGIN_NAME;

/* Plugin descriptor */
LXPanelPluginInit fm_module_init_lxpanel_gtk = {
    .name = PLUGIN_TITLE,
    .description = N_("Ejects mounted drives"),
    .new_instance = ejecter_constructor,
    .reconfigure = ejecter_configuration_changed,
    .button_press_event = ejecter_button_press_event,
    .config = ejecter_configure,
    .control = ejecter_control,
    .gettext_package = GETTEXT_PACKAGE
};
#endif

/* End of file */
/*----------------------------------------------------------------------------*/
