/*
 *      lxrandr.c - Easy-to-use XRandR GUI frontend for LXDE project
 *
 *      Copyright (C) 2008 Hong Jen Yee(PCMan) <pcman.tw@gmail.com>
 *      Copyright (C) 2011 Julien Lavergne <julien.lavergne@gmail.com>
 *      Copyright (C) 2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
 *      Copyright (C) 2021,2024-2025 Ingo Brückl
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <gdk/gdkx.h>

#include <locale.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef enum
{
    PLACEMENT_DEFAULT,
    PLACEMENT_RIGHT,
    PLACEMENT_ABOVE,
    PLACEMENT_LEFT,
    PLACEMENT_BELOW
} MonitorPlacement;

typedef struct _Monitor
{
    char* name;
    GSList* mode_lines;
    short active_mode;
    short active_rate;
    short pref_mode;
    short pref_rate;
    short try_mode;
    short try_rate;
    MonitorPlacement placement;
    MonitorPlacement try_placement;

    GtkCheckButton* enable;
#if GTK_CHECK_VERSION(2, 24, 0)
    GtkComboBoxText* pos_combo;
    GtkComboBoxText* res_combo;
    GtkComboBoxText* rate_combo;
#else
    GtkComboBox* pos_combo;
    GtkComboBox* res_combo;
    GtkComboBox* rate_combo;
#endif
}Monitor;

static GSList* monitors = NULL;
static Monitor* LVDS = NULL;

static GtkWidget* dlg = NULL;
static guint timer;

/* Disable, not used
static void monitor_free( Monitor* m )
{
    g_free( m->name );
    g_slist_free( m->mode_lines );
    g_free( m );
}
*/

static const char* get_human_readable_name( Monitor* m )
{
    if( m == LVDS )
        return _("Laptop LCD Monitor");
    else if( g_str_has_prefix( m->name, "VGA" ) || g_str_has_prefix( m->name, "Analog" ) )
        return LVDS ? _("External VGA Monitor") : _("VGA Monitor");
    else if( g_str_has_prefix( m->name, "DVI" ) || g_str_has_prefix(m->name, "TMDS") || g_str_has_prefix(m->name, "Digital") || g_str_has_prefix(m->name, "LVDS") )
        return LVDS ? _("External DVI Monitor") : _("DVI Monitor");
    else if( g_str_has_prefix( m->name, "TV" ) || g_str_has_prefix(m->name, "S-Video") )
        return _("TV");
    else if( strcmp( m->name, "default" ) == 0 )
        return _( "Default Monitor");

    return m->name;
}

static gboolean get_xrandr_info()
{
    GRegex* regex;
    GMatchInfo* match;
    int status, i;
    char* output = NULL;
    char* ori_locale;

    const guint64 known_ratios[][2] =
    {
         5,  4,
         4,  3,
         3,  2,
        16, 10,
        16,  9,
        21,  9,
        32,  9,
         0,  0
    };

    ori_locale = g_strdup( setlocale(LC_ALL, "") );

    // set locale to "C" temporarily to guarantee English output of xrandr
    setlocale(LC_ALL, "C");

    if( ! g_spawn_command_line_sync( "xrandr", &output, NULL, &status, NULL ) || status )
    {
        g_free( output );
        setlocale( LC_ALL, ori_locale );
        g_free( ori_locale );
        return FALSE;
    }

    regex = g_regex_new( "\n([-.a-zA-Z]+[-.0-9]*) +connected ([^(\n ]*)[^\n]*"
                         "((\n +[0-9]+x[0-9]+[^\n]+)+)",
                         0, 0, NULL );
    if( g_regex_match( regex, output, 0, &match ) )
    {
        do {
            Monitor* m = g_new0( Monitor, 1 );
            char *modes = g_match_info_fetch( match, 3 );
            char *coords = g_match_info_fetch(match, 2);
            char **lines, **line;
            char *ptr;
            int imode = 0, x = -1, y = -1;

            m->active_mode = m->active_rate = -1;
            m->pref_mode = m->pref_rate = -1;
            m->name = g_match_info_fetch( match, 1 );
            ptr = strchr(coords, '+');
            if (ptr != NULL)
            {
                ptr++;
                x = strtol(ptr, &ptr, 10);
                if (*ptr++ == '+')
                    y = strtol(ptr, &ptr, 10);
            }
            /* g_debug("name '%s' coords '%s'=>%d:%d modes%.20s...",m->name,coords,x,y,&modes[1]); */
            if (x < 0 || y < 0 || (x == 0 && y == 0))
                m->placement = PLACEMENT_DEFAULT;
            else if (x == 0)
                m->placement = PLACEMENT_BELOW;
            else if (y == 0)
                m->placement = PLACEMENT_RIGHT;

            // check if this is the built-in LCD of laptop
            if (! LVDS && (g_str_has_prefix(m->name, "eDP") ||
                           g_str_has_prefix(m->name, "LVDS") ||
                           g_str_has_prefix(m->name, "PANEL")))
                LVDS = m;

            lines = g_strsplit( modes, "\n", -1 );
            for( line = lines; *line; ++line )
            {
                char* str = strtok( *line, " " );
                int irate = 0;
                GPtrArray* strv;
                if( ! str )
                    continue;

                // aspect ratio

                gchar *endptr;
                guint64 width = g_ascii_strtoull(str, &endptr, 10);
                guint64 height = 0;

                if (endptr && *endptr)
                    height = g_ascii_strtoull(endptr + 1, NULL, 10);

                if (height == 0)
                    height = 1;

                // aspect ratio via greatest common divisor
                guint64 a = width;
                guint64 b = height;
                while (b != 0)
                {
                    guint64 tmp = a % b;
                    a = b;
                    b = tmp;
                }
                width = width / a;
                height = height / a;

                // 16:10 is more common than 8:5
                if (width == 8 && height == 5)
                {
                    width = 16;
                    height = 10;
                }

                // check for exact or approximate ratio

                gboolean exact = TRUE;

                for (i = 0; known_ratios[i][0]; i++)
                {
                    if (width == known_ratios[i][0] && height == known_ratios[i][1])
                        break;
                    else if (ABS((float) width / (float) height - (float) known_ratios[i][0] / (float) known_ratios[i][1]) <= 0.01f)
                    {
                        width = known_ratios[i][0];
                        height = known_ratios[i][1];
                        exact = FALSE;
                        break;
                    }
                }

                strv = g_ptr_array_sized_new(8);
                g_ptr_array_add(strv, g_strdup_printf("%s (%s%" G_GUINT64_FORMAT ":%" G_GUINT64_FORMAT ")", str, exact ? "" : "≈", width, height));

                // frequencies

                while ((str = strtok( NULL, " ")))
                {
                    if( *str )
                    {
                        char *star = NULL, *plus = NULL;
                        str = g_strdup( str );

                        // sometimes, + goes after a space
                        if( 0 == strcmp( str, "+" ) )
                            --irate;
                        else
                            g_ptr_array_add( strv, str );

                        if ((star = strchr( str, '*' )))
                        {
                            m->active_mode = imode;
                            m->active_rate = irate;
                        }
                        if ((plus = strchr( str, '+' )))
                        {
                            m->pref_mode = imode;
                            m->pref_rate = irate;
                        }
                        if( star )
                            *star = '\0';
                        if( plus )
                            *plus = '\0';
                        ++irate;
                    }
                }
                g_ptr_array_add( strv, NULL );
                m->mode_lines = g_slist_append( m->mode_lines, g_ptr_array_free( strv, FALSE ) );
                ++imode;
            }
            g_strfreev( lines );
            g_free( modes );
            g_free(coords);
            monitors = g_slist_prepend( monitors, m );
        }while( g_match_info_next( match, NULL ) );

        g_match_info_free( match );
    }
    g_regex_unref( regex );

    // restore the original locale
    setlocale( LC_ALL, ori_locale );
    g_free( ori_locale );

    // Handle the case actually no monitor is added
    if (! monitors)
    {
        return FALSE;
    }

    return TRUE;
}

static void on_enable_toggled(GtkToggleButton *tb, Monitor* m)
{
    GSList *l;
    Monitor *fixed = LVDS ? LVDS : monitors->data;
    int i;
    gboolean can_position;

    for (l = monitors, i = 0; l; l = l->next)
        if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->enable)))
            i++;
    can_position = (i > 1);

    if (monitors->next != NULL) for (l = monitors; l; l = l->next)
    {
        Monitor *m = (Monitor*)l->data;
        gtk_widget_set_sensitive(GTK_WIDGET(m->pos_combo), can_position && (m != fixed));
        if (!can_position || m == fixed)
            gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), 0);
    }
}

static void on_res_sel_changed( GtkComboBox* cb, Monitor* m )
{
    char** rate;
    int sel = gtk_combo_box_get_active( cb );
    char** mode_line = g_slist_nth_data( m->mode_lines, sel - 1 );
#if GTK_CHECK_VERSION(2, 24, 0)
    gtk_list_store_clear( GTK_LIST_STORE(gtk_combo_box_get_model( GTK_COMBO_BOX(m->rate_combo) )) );
    gtk_combo_box_text_append_text( m->rate_combo, _("Auto") );
    if( sel >= 0 && mode_line && *mode_line )
    {
        for( rate = mode_line + 1; *rate; ++rate )
        {
            gtk_combo_box_text_append_text( m->rate_combo, *rate );
        }
    }
    gtk_combo_box_set_active( GTK_COMBO_BOX(m->rate_combo), 0 );
#else
    gtk_list_store_clear( GTK_LIST_STORE(gtk_combo_box_get_model(m->rate_combo )) );
    gtk_combo_box_append_text( m->rate_combo, _("Auto") );
    if( sel >= 0 && mode_line && *mode_line )
    {
        for( rate = mode_line + 1; *rate; ++rate )
        {
            gtk_combo_box_append_text( m->rate_combo, *rate );
        }
    }
    gtk_combo_box_set_active( m->rate_combo, 0 );
#endif
}

/*Disable, not used
static void open_url( GtkDialog* dlg, const char* url, gpointer data )
{
    FIXME
}
*/

static void on_about( GtkButton* btn, gpointer parent )
{
    GtkWidget * about_dlg;
    const gchar *authors[] =
    {
        "洪任諭 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>",
        NULL
    };
    /* TRANSLATORS: Replace mw string with your names, one name per line. */
    gchar *translators = _( "translator-credits" );

//    gtk_about_dialog_set_url_hook( open_url, NULL, NULL);

    about_dlg = gtk_about_dialog_new ();

    gtk_container_set_border_width ( ( GtkContainer*)about_dlg , 2 );
    gtk_about_dialog_set_version ( (GtkAboutDialog*)about_dlg, VERSION );
#if GTK_CHECK_VERSION(2, 12, 0)
    gtk_about_dialog_set_program_name ( (GtkAboutDialog*)about_dlg, _( "LXRandR" ) );
#else
    gtk_about_dialog_set_name ( (GtkAboutDialog*)about_dlg, _( "LXRandR" ) );
#endif
    //gtk_about_dialog_set_logo( (GtkAboutDialog*)about_dlg, gdk_pixbuf_new_from_file(  PACKAGE_DATA_DIR"/pixmaps/lxrandr.png", NULL ) );
    gtk_about_dialog_set_logo_icon_name( (GtkAboutDialog*)about_dlg, "video-display" );
    gtk_about_dialog_set_copyright ( (GtkAboutDialog*)about_dlg, _( "Copyright (C) 2008-2025" ) );
    gtk_about_dialog_set_comments ( (GtkAboutDialog*)about_dlg, _( "Monitor configuration tool for LXDE" ) );
    gtk_about_dialog_set_license ( (GtkAboutDialog*)about_dlg, "This program is free software; you can redistribute it and/or\nmodify it under the terms of the GNU General Public License\nas published by the Free Software Foundation; either version 2\nof the License, or (at your option) any later version.\n\nmw program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with mw program; if not, write to the Free Software\nFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA." );
    gtk_about_dialog_set_website ( (GtkAboutDialog*)about_dlg, "http://lxde.org/" );
    gtk_about_dialog_set_authors ( (GtkAboutDialog*)about_dlg, authors );
    gtk_about_dialog_set_translator_credits ( (GtkAboutDialog*)about_dlg, translators );

    gtk_window_set_transient_for( (GtkWindow*)about_dlg, (GtkWindow*)parent );
    gtk_dialog_run( ( GtkDialog*)about_dlg );
    gtk_widget_destroy( about_dlg );
}


static void prepare_try_values_from_GUI()
{
    GSList *l;

    for (l = monitors; l; l = l->next)
    {
        Monitor *m = (Monitor*)l->data;
        if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->enable)))
            m->try_mode = -1;
        else
#if GTK_CHECK_VERSION(2, 24, 0)
            m->try_mode = gtk_combo_box_get_active(GTK_COMBO_BOX(m->res_combo));
        m->try_rate = gtk_combo_box_get_active(GTK_COMBO_BOX(m->rate_combo));
        if (m->pos_combo)
            m->try_placement = gtk_combo_box_get_active(GTK_COMBO_BOX(m->pos_combo));
#else
            m->try_mode = gtk_combo_box_get_active(m->res_combo);
        m->try_rate = gtk_combo_box_get_active(m->rate_combo);
        if (m->pos_combo)
            m->try_placement = gtk_combo_box_get_active(m->pos_combo);
#endif
        /* g_debug("mode %d rate %d placement %d",m->try_mode,m->try_rate,m->try_placement); */
    }
}

static GString* get_command_xrandr_info()
{

    GSList* l;
    GString *cmd = g_string_sized_new( 1024 );

    g_string_assign( cmd, "sh -c 'xrandr" );

    for( l = monitors; l; l = l->next )
    {
        Monitor* m = (Monitor*)l->data;
        g_string_append( cmd, " --output " );
        g_string_append( cmd, m->name );

        // if the monitor is turned on
        if (m->try_mode >= 0)
        {
            if( m->try_mode < 1 ) // auto resolution
            {
                g_string_append( cmd, " --auto" );
            }
            else
            {
                GtkTreeModel *model;
                GtkTreePath *path;
                gchar *text;
                GtkTreeIter iter;
                gint text_column;

                g_string_append( cmd, " --mode " );
                model = gtk_combo_box_get_model(GTK_COMBO_BOX(m->res_combo));
                text_column = gtk_combo_box_get_entry_text_column(GTK_COMBO_BOX(m->res_combo));
                path = gtk_tree_path_new_from_indices(m->try_mode, -1);
                gtk_tree_model_get_iter(model, &iter, path);
                gtk_tree_model_get(model, &iter, text_column, &text, -1);
                gtk_tree_path_free(path);

                // resolution and aspect ratio are separated by a space
                gchar *delim = g_strstr_len(text, -1, " ");
                if (delim)
                    *delim = 0;

                g_string_append(cmd, text);
                g_free(text);

                if( m->try_rate >= 1 ) // not auto refresh rate
                {
                    g_string_append( cmd, " --rate " );
                    model = gtk_combo_box_get_model(GTK_COMBO_BOX(m->rate_combo));
                    text_column = gtk_combo_box_get_entry_text_column(GTK_COMBO_BOX(m->rate_combo));
                    path = gtk_tree_path_new_from_indices(m->try_rate, -1);
                    gtk_tree_model_get_iter(model, &iter, path);
                    gtk_tree_model_get(model, &iter, text_column, &text, -1);
                    gtk_tree_path_free(path);
                    g_string_append(cmd, text);
                    g_free(text);
                }
            }
            if (m == LVDS) ; /* it's fixed, no positioning */
            else if (LVDS != NULL)
            {
                switch (m->try_placement)
                {
                case PLACEMENT_RIGHT:
                    g_string_append(cmd, " --right-of ");
                    break;
                case PLACEMENT_ABOVE:
                    g_string_append(cmd, " --above ");
                    break;
                case PLACEMENT_LEFT:
                    g_string_append(cmd, " --left-of ");
                    break;
                case PLACEMENT_BELOW:
                    g_string_append(cmd, " --below ");
                    break;
                case PLACEMENT_DEFAULT:
                    g_string_append(cmd, " --same-as ");
                }
                g_string_append(cmd, LVDS->name);
            }
            else if (l != monitors)
            {
                Monitor *first = (Monitor*)monitors->data;
                /* not notebook */
                switch (m->try_placement)
                {
                case PLACEMENT_RIGHT:
                    g_string_append(cmd, " --right-of ");
                    break;
                case PLACEMENT_ABOVE:
                    g_string_append(cmd, " --above ");
                    break;
                case PLACEMENT_LEFT:
                    g_string_append(cmd, " --left-of ");
                    break;
                case PLACEMENT_BELOW:
                    g_string_append(cmd, " --below ");
                    break;
                case PLACEMENT_DEFAULT:
                    g_string_append(cmd, " --same-as ");
                }
                g_string_append(cmd, first->name);
            }

            /* g_string_append( cmd, "" ); */

        }
        else    // turn off
            g_string_append( cmd, " --off" );
    }
    g_string_append_c(cmd, '\'');

    return cmd;

}

static void save_configuration()
{

    char* dirname;
    const char grp[] = "Desktop Entry";
    GKeyFile* kf;

    char* file, *data;
    gsize len;

    GString *cmd;

    prepare_try_values_from_GUI();
    cmd = get_command_xrandr_info();

    /* create user autostart dir */
    dirname = g_build_filename(g_get_user_config_dir(), "autostart", NULL);
    g_mkdir_with_parents(dirname, 0700);
    g_free(dirname);

    kf = g_key_file_new();

    g_key_file_set_string( kf, grp, "Type", "Application" );
    g_key_file_set_string( kf, grp, "Name", _("LXRandR autostart") );
    g_key_file_set_string( kf, grp, "Comment", _("Start xrandr with settings done in LXRandR") );
    g_key_file_set_string( kf, grp, "Exec", cmd->str );
    g_key_file_set_string( kf, grp, "NotShowIn", "GNOME;KDE;XFCE;MATE;Budgie;COSMIC;Enlightenment;LXQt;X-Cinnamon;" );

    data = g_key_file_to_data(kf, &len, NULL);
    file = g_build_filename(  g_get_user_config_dir(),
                              "autostart",
                              "lxrandr-autostart.desktop",
                              NULL );
    /* save it to user-specific autostart dir */
    g_debug("save to: %s", file);
    g_file_set_contents(file, data, len, NULL);
    g_key_file_free (kf);
    g_free(file);
    g_free(data);
    g_string_free(cmd, TRUE);
}

static gboolean cancel_confirmation(gpointer data)
{
    if (!g_source_is_destroyed(g_main_current_source()))
        gtk_dialog_response(data, GTK_RESPONSE_CANCEL);

    timer = 0;

    return FALSE;
}

static void set_xrandr_info()
{
    GString *cmd;
    GSList *l;

    prepare_try_values_from_GUI();
    cmd = get_command_xrandr_info();
    /* g_debug("trying: %s", cmd->str); */

    if (g_spawn_command_line_sync( cmd->str, NULL, NULL, NULL, NULL ))
    {
        /* open a dialog box and wait 15 seconds */
        GtkWidget *confirmation;
        int responce;

        confirmation = gtk_message_dialog_new(GTK_WINDOW(dlg), GTK_DIALOG_MODAL,
                                              GTK_MESSAGE_QUESTION,
                                              GTK_BUTTONS_NONE,
                                              _("Is everything OK? Confirm within 15 seconds,"
                                                " otherwise previous state will be restored."));
        gtk_dialog_add_buttons(GTK_DIALOG(confirmation),
                               _("_OK"), GTK_RESPONSE_ACCEPT,
                               _("_Abort"), GTK_RESPONSE_CANCEL,
                               NULL);
        gtk_dialog_set_default_response(GTK_DIALOG(confirmation), GTK_RESPONSE_CANCEL);
        timer = gdk_threads_add_timeout(15000, cancel_confirmation, confirmation);
        responce = gtk_dialog_run(GTK_DIALOG(confirmation));
        if (timer)
            g_source_remove(timer);
        gtk_widget_destroy(confirmation);
        /* if not confirmed then set GUI to fallback values, reset xrandr,
           then restore all GUI again */
        if (responce == GTK_RESPONSE_ACCEPT)
        {
            for (l = monitors; l; l = l->next)
            {
                Monitor *m = (Monitor*)l->data;

                m->active_mode = m->try_mode;
                m->active_rate = m->try_rate;
                m->placement = m->try_placement;
            }
        }
        else
        {
            for (l = monitors; l; l = l->next)
            {
                Monitor *m = (Monitor*)l->data;

                m->try_mode = m->active_mode;
                m->try_rate = m->active_rate;
                m->try_placement = m->placement;
            }
            g_string_free(cmd, TRUE);
            cmd = get_command_xrandr_info();
            /* g_debug("recovering: %s", cmd->str); */
            g_spawn_command_line_sync(cmd->str, NULL, NULL, NULL, NULL);
        }
    }

    g_string_free( cmd, TRUE );
}

static void choose_max_resolution( Monitor* m )
{
#if GTK_CHECK_VERSION(2, 24, 0)
    if( gtk_tree_model_iter_n_children( gtk_combo_box_get_model(GTK_COMBO_BOX(m->res_combo)), NULL ) > 1 )
        gtk_combo_box_set_active( GTK_COMBO_BOX(m->res_combo), 1 );
    if (m->pos_combo)
        gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), PLACEMENT_DEFAULT);
#else
    if( gtk_tree_model_iter_n_children( gtk_combo_box_get_model(m->res_combo), NULL ) > 1 )
        gtk_combo_box_set_active( m->res_combo, 1 );
    if (m->pos_combo)
        gtk_combo_box_set_active(m->pos_combo, PLACEMENT_DEFAULT);
#endif
}

static void on_quick_option( GtkButton* btn, gpointer data )
{
    GSList* l;
    int option = GPOINTER_TO_INT(data);
    switch( option )
    {
    case 1: // turn on both
        for( l = monitors; l; l = l->next )
        {
            Monitor* m = (Monitor*)l->data;
            choose_max_resolution( m );
            gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), TRUE );
        }
        break;
    case 2: // external monitor only
        for( l = monitors; l; l = l->next )
        {
            Monitor* m = (Monitor*)l->data;
            choose_max_resolution( m );
            gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), m != LVDS );
        }
        break;
    case 3: // laptop panel - LVDS only
        for( l = monitors; l; l = l->next )
        {
            Monitor* m = (Monitor*)l->data;
            choose_max_resolution( m );
            gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), m == LVDS );
        }
        break;
    case 4: // external right of LVDS
        for( l = monitors; l; l = l->next )
        {
            Monitor* m = (Monitor*)l->data;
            choose_max_resolution( m );
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m->enable), TRUE);
            if (m != LVDS)
                gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), PLACEMENT_RIGHT);
        }
        break;
    case 5: // external above of LVDS
        for( l = monitors; l; l = l->next )
        {
            Monitor* m = (Monitor*)l->data;
            choose_max_resolution( m );
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m->enable), TRUE);
            if (m != LVDS)
                gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), PLACEMENT_ABOVE);
        }
        break;
    default:
        return;
    }
//    gtk_dialog_response( GTK_DIALOG(dlg), GTK_RESPONSE_OK );
//    set_xrandr_info();
}

static void on_response( GtkDialog* dialog, int response, gpointer user_data )
{
    if( response == GTK_RESPONSE_OK )
    {
        GtkWidget* msg;
        GSList* l;
        for( l = monitors; l; l = l->next )
        {
            Monitor* m = (Monitor*)l->data;
            if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(m->enable) ) )
                break;
        }
        if (l != NULL)
            set_xrandr_info();
        else
        {
            msg = gtk_message_dialog_new( GTK_WINDOW(dialog), 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
                                         _("You cannot turn off all monitors. "
                                           "Otherwise, you will not be able to "
                                           "turn them on again since this tool "
                                           "is not accessible without monitor.") );
            gtk_dialog_run( GTK_DIALOG(msg) );
            gtk_widget_destroy( msg );
        }

        // block the response
        g_signal_stop_emission_by_name( dialog, "response" );
    }
    else if (response == GTK_RESPONSE_ACCEPT)
    {
        GtkWidget* msg;

        save_configuration();

        msg = gtk_message_dialog_new( GTK_WINDOW(dialog),
                                      0,
                                      GTK_MESSAGE_INFO,
                                      GTK_BUTTONS_OK,
                                      _("Configuration Saved") );
        gtk_dialog_run( GTK_DIALOG(msg) );
        gtk_widget_destroy( msg );
    }
}

int main(int argc, char** argv)
{
    GtkWidget *notebook, *vbox, *frame, *label, *hbox, *check, *btn;
    GSList* l;
    Monitor *fixed;
    int i;
    gboolean can_position;

#ifdef ENABLE_NLS
    bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR );
    bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" );
    textdomain ( GETTEXT_PACKAGE );
#endif

    gtk_init( &argc, &argv );

#if GTK_CHECK_VERSION(3, 0, 0)
    /* check if not running under X11 */
    if (!GDK_IS_X11_DISPLAY(gdk_display_get_default()))
    {
        GtkWidget *dialog = gtk_message_dialog_new(
            NULL,
            GTK_DIALOG_MODAL,
            GTK_MESSAGE_ERROR,
            GTK_BUTTONS_OK,
            _("This tool can be used only under X11.")
        );

        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
        return 1;
    }
#endif

    if( ! get_xrandr_info() )
    {
        dlg = gtk_message_dialog_new( NULL,
                                      0,
                                      GTK_MESSAGE_ERROR,
                                      GTK_BUTTONS_OK,
                                      _("Unable to get monitor information!"));
        gtk_dialog_run( (GtkDialog*)dlg );
        gtk_widget_destroy( dlg );
        return 1;
    }

    dlg = gtk_dialog_new_with_buttons( _("Display Settings"), NULL,
                                       GTK_DIALOG_MODAL,
                                       GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
                                       GTK_STOCK_APPLY, GTK_RESPONSE_OK,
                                       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL );
    g_signal_connect( dlg, "response", G_CALLBACK(on_response), NULL );
    gtk_container_set_border_width( GTK_CONTAINER(dlg), 8 );
    gtk_dialog_set_alternative_button_order( GTK_DIALOG(dlg), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1 );

    /* Set icon name for main (dlg) window so it displays in the panel. */
    gtk_window_set_icon_name(GTK_WINDOW(dlg), "video-display");

    btn = gtk_button_new_from_stock( GTK_STOCK_ABOUT );
#if GTK_CHECK_VERSION(2,14,0)
    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_action_area( GTK_DIALOG(dlg))), btn, FALSE, TRUE, 0 );
    gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(gtk_dialog_get_action_area( GTK_DIALOG(dlg))), btn, TRUE );
#else
    gtk_box_pack_start( GTK_BOX(GTK_DIALOG(dlg)->action_area), btn, FALSE, TRUE, 0 );
    gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(GTK_DIALOG(dlg)->action_area), btn, TRUE );
#endif
    g_signal_connect( btn, "clicked", G_CALLBACK(on_about), dlg );

    notebook = gtk_notebook_new();
#if GTK_CHECK_VERSION(2,14,0)
    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), notebook, TRUE, TRUE, 2 );
#else
    gtk_box_pack_start( GTK_BOX( GTK_DIALOG(dlg)->vbox ), notebook, TRUE, TRUE, 2 );
#endif

    // If this is a laptop and there is an external monitor, offer quick options
    if( LVDS && g_slist_length( monitors ) == 2 )
    {
        vbox = gtk_vbox_new( FALSE, 4 );
        gtk_container_set_border_width( GTK_CONTAINER(vbox), 8 );

        btn = gtk_radio_button_new_with_label(NULL, _("Show the same screen on both laptop LCD and external monitor"));
        g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(1) );
        gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);

        btn = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(btn),
                        _("Turn off laptop LCD and use external monitor only"));
        g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(2) );
        gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);

        btn = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(btn),
                        _("Turn off external monitor and use laptop LCD only"));
        g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(3) );
        gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);

        btn = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(btn),
                        _("Place external monitor to the right of laptop LCD"));
        g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(4) );
        gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);

        btn = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(btn),
                        _("Place external monitor above of laptop LCD"));
        g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(5) );
        gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);

        gtk_notebook_append_page( GTK_NOTEBOOK(notebook), vbox, gtk_label_new( _("Quick Options") ) );
    }
    else
    {
        gtk_notebook_set_show_tabs( GTK_NOTEBOOK(notebook), FALSE );
    }

    vbox = gtk_vbox_new( FALSE, 4 );
    gtk_container_set_border_width( GTK_CONTAINER(vbox), 8 );
    gtk_notebook_append_page( GTK_NOTEBOOK(notebook), vbox, gtk_label_new(_("Advanced")) );

    label = gtk_label_new("");
    gtk_misc_set_alignment( GTK_MISC(label), 0.0, 0.5 );
    gtk_label_set_markup( GTK_LABEL(label), ngettext( "The following monitor is detected:",
                                    "The following monitors are detected:",
                                    g_slist_length(monitors) ) );
    gtk_box_pack_start( GTK_BOX(vbox), label, FALSE, TRUE, 2 );

    for (l = monitors, i = 0; l; l = l->next)
        if (((Monitor*)l->data)->active_mode >= 0)
            i++;
    can_position = (i > 1);

    /* correct placements */
    fixed = LVDS ? LVDS : monitors->data;
    if (fixed->placement != PLACEMENT_DEFAULT)
    {
        for (l = monitors, i = 0; l; l = l->next)
        {
            Monitor *m = (Monitor*)l->data;

            if (m->placement != PLACEMENT_DEFAULT)
                continue; /* FIXME: how to handle corners? */
            switch (fixed->placement)
            {
            case PLACEMENT_LEFT:
                m->placement = PLACEMENT_RIGHT;
                break;
            case PLACEMENT_RIGHT:
                m->placement = PLACEMENT_LEFT;
                break;
            case PLACEMENT_ABOVE:
                m->placement = PLACEMENT_BELOW;
                break;
            case PLACEMENT_BELOW:
                m->placement = PLACEMENT_ABOVE;
                break;
            case PLACEMENT_DEFAULT: ;
            }
        }
        fixed->placement = PLACEMENT_DEFAULT;
    }

    for( l = monitors, i = 0; l; l = l->next, ++i )
    {
        Monitor* m = (Monitor*)l->data;
        GSList* mode_line;

        frame = gtk_frame_new( get_human_readable_name(m) );
        gtk_box_pack_start( GTK_BOX(vbox), frame, FALSE, TRUE, 2 );

        hbox = gtk_hbox_new( FALSE, 4 );
        gtk_container_set_border_width( GTK_CONTAINER(hbox), 4 );
        gtk_container_add( GTK_CONTAINER(frame), hbox );

        check = gtk_check_button_new_with_label( _("Turn On") );
        m->enable = GTK_CHECK_BUTTON(check);
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), m->active_mode >= 0);

        // turn off screen is not allowed since there should be at least one monitor available.
        if( g_slist_length( monitors ) == 1 )
            gtk_widget_set_sensitive( GTK_WIDGET(m->enable), FALSE );
        else
            g_signal_connect(m->enable, "toggled", G_CALLBACK(on_enable_toggled), m);

        gtk_box_pack_start( GTK_BOX(hbox), check, FALSE, TRUE, 6 );

        if (monitors->next != NULL) /* g_slist_length(monitors) > 0 */
        {
            label = gtk_label_new(_("Position:"));
            gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 2);
#if GTK_CHECK_VERSION(2, 24, 0)
            m->pos_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
            gtk_combo_box_text_append_text(m->pos_combo, _("Default"));
            gtk_combo_box_text_append_text(m->pos_combo, _("On right"));
            gtk_combo_box_text_append_text(m->pos_combo, _("Above"));
            gtk_combo_box_text_append_text(m->pos_combo, _("On left"));
            gtk_combo_box_text_append_text(m->pos_combo, _("Below"));
            gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), m->placement);
#else
            m->pos_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
            gtk_combo_box_append_text(m->res_combo, _("Default"));
            gtk_combo_box_append_text(m->res_combo, _("On right"));
            gtk_combo_box_append_text(m->res_combo, _("Above"));
            gtk_combo_box_append_text(m->pos_combo, _("On left"));
            gtk_combo_box_append_text(m->pos_combo, _("Below"));
            gtk_combo_box_set_active(m->pos_combo, m->placement);
#endif
            if (m == fixed || !can_position || m->active_mode < 0)
                gtk_widget_set_sensitive(GTK_WIDGET(m->pos_combo), FALSE);
            gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(m->pos_combo), FALSE, TRUE, 2);
        }

        label = gtk_label_new( _("Resolution:") );
        gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 2 );

#if GTK_CHECK_VERSION(2, 24, 0)
        m->res_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
#else
        m->res_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
#endif
        g_signal_connect( m->res_combo, "changed", G_CALLBACK(on_res_sel_changed), m );
        gtk_box_pack_start( GTK_BOX(hbox), GTK_WIDGET(m->res_combo), FALSE, TRUE, 2 );

        label = gtk_label_new( _("Refresh Rate:") );
        gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 2 );

#if GTK_CHECK_VERSION(2, 24, 0)
        m->rate_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
#else
        m->rate_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
#endif
        gtk_box_pack_start( GTK_BOX(hbox), GTK_WIDGET(m->rate_combo), FALSE, TRUE, 2 );

#if GTK_CHECK_VERSION(2, 24, 0)
        gtk_combo_box_text_append_text( m->res_combo, _("Auto") );
#else
        gtk_combo_box_append_text( m->res_combo, _("Auto") );
#endif
        for( mode_line = m->mode_lines; mode_line; mode_line = mode_line->next )
        {
            char** strv = (char**)mode_line->data;
#if GTK_CHECK_VERSION(2, 24, 0)
            gtk_combo_box_text_append_text( m->res_combo, strv[0] );
#else
            gtk_combo_box_append_text( m->res_combo, strv[0] );
#endif
        }

        m->active_rate++;
#if GTK_CHECK_VERSION(2, 24, 0)
        gtk_combo_box_set_active( GTK_COMBO_BOX(m->res_combo), m->active_mode + 1 );
        gtk_combo_box_set_active( GTK_COMBO_BOX(m->rate_combo), m->active_rate );
#else
        gtk_combo_box_set_active( m->res_combo, m->active_mode + 1 );
        gtk_combo_box_set_active( m->rate_combo, m->active_rate );
#endif
        if (m->active_mode >= 0)
            m->active_mode++; /* let it stay -1 for inactive button */
    }

    gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
    gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);

    gtk_widget_show_all( dlg );

    gtk_dialog_run((GtkDialog*)dlg);

    gtk_widget_destroy( dlg );

    return 0;
}
