1 /*
2  *      lxrandr.c - Easy-to-use XRandR GUI frontend for LXDE project
3  *
4  *      Copyright (C) 2008 Hong Jen Yee(PCMan) <pcman.tw@gmail.com>
5  *      Copyright (C) 2011 Julien Lavergne <julien.lavergne@gmail.com>
6  *      Copyright (C) 2014 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
7  *
8  *      This program is free software; you can redistribute it and/or modify
9  *      it under the terms of the GNU General Public License as published by
10  *      the Free Software Foundation; either version 2 of the License, or
11  *      (at your option) any later version.
12  *
13  *      This program is distributed in the hope that it will be useful,
14  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *      GNU General Public License for more details.
17  *
18  *      You should have received a copy of the GNU General Public License
19  *      along with this program; if not, write to the Free Software
20  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21  *      MA 02110-1301, USA.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30 
31 #include <locale.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <stdlib.h>
35 
36 typedef enum
37 {
38     PLACEMENT_DEFAULT,
39     PLACEMENT_RIGHT,
40     PLACEMENT_ABOVE,
41     PLACEMENT_LEFT,
42     PLACEMENT_BELOW
43 } MonitorPlacement;
44 
45 typedef struct _Monitor
46 {
47     char* name;
48     GSList* mode_lines;
49     short active_mode;
50     short active_rate;
51     short pref_mode;
52     short pref_rate;
53     short try_mode;
54     short try_rate;
55     MonitorPlacement placement;
56     MonitorPlacement try_placement;
57 
58     GtkCheckButton* enable;
59 #if GTK_CHECK_VERSION(2, 24, 0)
60     GtkComboBoxText* pos_combo;
61     GtkComboBoxText* res_combo;
62     GtkComboBoxText* rate_combo;
63 #else
64     GtkComboBox* pos_combo;
65     GtkComboBox* res_combo;
66     GtkComboBox* rate_combo;
67 #endif
68 }Monitor;
69 
70 static GSList* monitors = NULL;
71 static Monitor* LVDS = NULL;
72 
73 static GtkWidget* dlg = NULL;
74 
75 /* Disable, not used
76 static void monitor_free( Monitor* m )
77 {
78     g_free( m->name );
79     g_slist_free( m->mode_lines );
80     g_free( m );
81 }
82 */
83 
get_human_readable_name(Monitor * m)84 static const char* get_human_readable_name( Monitor* m )
85 {
86     if( m == LVDS )
87         return _("Laptop LCD Monitor");
88     else if( g_str_has_prefix( m->name, "VGA" ) || g_str_has_prefix( m->name, "Analog" ) )
89         return LVDS ? _("External VGA Monitor") : _("VGA Monitor");
90     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") )
91         return LVDS ? _("External DVI Monitor") : _("DVI Monitor");
92     else if( g_str_has_prefix( m->name, "TV" ) || g_str_has_prefix(m->name, "S-Video") )
93         return _("TV");
94     else if( strcmp( m->name, "default" ) == 0 )
95         return _( "Default Monitor");
96 
97     return m->name;
98 }
99 
get_xrandr_info()100 static gboolean get_xrandr_info()
101 {
102     GRegex* regex;
103     GMatchInfo* match;
104     int status;
105     char* output = NULL;
106     char* ori_locale;
107 
108     ori_locale = g_strdup( setlocale(LC_ALL, "") );
109 
110     // set locale to "C" temporarily to guarantee English output of xrandr
111     setlocale(LC_ALL, "C");
112 
113     if( ! g_spawn_command_line_sync( "xrandr", &output, NULL, &status, NULL ) || status )
114     {
115         g_free( output );
116         setlocale( LC_ALL, ori_locale );
117         g_free( ori_locale );
118         return FALSE;
119     }
120 
121     regex = g_regex_new( "\n([-\\.a-zA-Z]+[-\\.0-9]*) +connected ([^(\n ]*)[^\n]*"
122                          "((\n +[0-9]+x[0-9]+[^\n]+)+)",
123                          0, 0, NULL );
124     if( g_regex_match( regex, output, 0, &match ) )
125     {
126         do {
127             Monitor* m = g_new0( Monitor, 1 );
128             char *modes = g_match_info_fetch( match, 3 );
129             char *coords = g_match_info_fetch(match, 2);
130             char **lines, **line;
131             char *ptr;
132             int imode = 0, x = -1, y = -1;
133 
134             m->active_mode = m->active_rate = -1;
135             m->pref_mode = m->pref_rate = -1;
136             m->name = g_match_info_fetch( match, 1 );
137             ptr = strchr(coords, '+');
138             if (ptr != NULL)
139             {
140                 ptr++;
141                 x = strtol(ptr, &ptr, 10);
142                 if (*ptr++ == '+')
143                     y = strtol(ptr, &ptr, 10);
144             }
145             /* g_debug("name '%s' coords '%s'=>%d:%d modes%.20s...",m->name,coords,x,y,&modes[1]); */
146             if (x < 0 || y < 0 || (x == 0 && y == 0))
147                 m->placement = PLACEMENT_DEFAULT;
148             else if (x == 0)
149                 m->placement = PLACEMENT_BELOW;
150             else if (y == 0)
151                 m->placement = PLACEMENT_RIGHT;
152 
153             // check if this is the built-in LCD of laptop
154             if (! LVDS && (g_str_has_prefix(m->name, "LVDS") ||
155                            g_str_has_prefix(m->name, "PANEL")))
156                 LVDS = m;
157 
158             lines = g_strsplit( modes, "\n", -1 );
159             for( line = lines; *line; ++line )
160             {
161                 char* str = strtok( *line, " " );
162                 int irate = 0;
163                 GPtrArray* strv;
164                 if( ! str )
165                     continue;
166                 strv = g_ptr_array_sized_new(8);
167                 g_ptr_array_add( strv, g_strdup(str) );
168                 while ((str = strtok( NULL, " ")))
169                 {
170                     if( *str )
171                     {
172                         char *star = NULL, *plus = NULL;
173                         str = g_strdup( str );
174 
175                         // sometimes, + goes after a space
176                         if( 0 == strcmp( str, "+" ) )
177                             --irate;
178                         else
179                             g_ptr_array_add( strv, str );
180 
181                         if ((star = strchr( str, '*' )))
182                         {
183                             m->active_mode = imode;
184                             m->active_rate = irate;
185                         }
186                         if ((plus = strchr( str, '+' )))
187                         {
188                             m->pref_mode = imode;
189                             m->pref_rate = irate;
190                         }
191                         if( star )
192                             *star = '\0';
193                         if( plus )
194                             *plus = '\0';
195                         ++irate;
196                     }
197                 }
198                 g_ptr_array_add( strv, NULL );
199                 m->mode_lines = g_slist_append( m->mode_lines, g_ptr_array_free( strv, FALSE ) );
200                 ++imode;
201             }
202             g_strfreev( lines );
203             g_free( modes );
204             g_free(coords);
205             monitors = g_slist_prepend( monitors, m );
206         }while( g_match_info_next( match, NULL ) );
207 
208         g_match_info_free( match );
209     }
210     g_regex_unref( regex );
211 
212     // restore the original locale
213     setlocale( LC_ALL, ori_locale );
214     g_free( ori_locale );
215 
216     // Handle the case actually no monitor is added
217     if (! monitors)
218     {
219         return FALSE;
220     }
221 
222     return TRUE;
223 }
224 
on_enable_toggled(GtkToggleButton * tb,Monitor * m)225 static void on_enable_toggled(GtkToggleButton *tb, Monitor* m)
226 {
227     GSList *l;
228     Monitor *fixed = LVDS ? LVDS : monitors->data;
229     int i;
230     gboolean can_position;
231 
232     for (l = monitors, i = 0; l; l = l->next)
233         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->enable)))
234             i++;
235     can_position = (i > 1);
236 
237     if (monitors->next != NULL) for (l = monitors; l; l = l->next)
238     {
239         Monitor *m = (Monitor*)l->data;
240         gtk_widget_set_sensitive(GTK_WIDGET(m->pos_combo), can_position && (m != fixed));
241         if (!can_position || m == fixed)
242             gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), 0);
243     }
244 }
245 
on_res_sel_changed(GtkComboBox * cb,Monitor * m)246 static void on_res_sel_changed( GtkComboBox* cb, Monitor* m )
247 {
248     char** rate;
249     int sel = gtk_combo_box_get_active( cb );
250     char** mode_line = g_slist_nth_data( m->mode_lines, sel - 1 );
251 #if GTK_CHECK_VERSION(2, 24, 0)
252     gtk_list_store_clear( GTK_LIST_STORE(gtk_combo_box_get_model( GTK_COMBO_BOX(m->rate_combo) )) );
253     gtk_combo_box_text_append_text( m->rate_combo, _("Auto") );
254     if( sel >= 0 && mode_line && *mode_line )
255     {
256         for( rate = mode_line + 1; *rate; ++rate )
257         {
258             gtk_combo_box_text_append_text( m->rate_combo, *rate );
259         }
260     }
261     gtk_combo_box_set_active( GTK_COMBO_BOX(m->rate_combo), 0 );
262 #else
263     gtk_list_store_clear( GTK_LIST_STORE(gtk_combo_box_get_model(m->rate_combo )) );
264     gtk_combo_box_append_text( m->rate_combo, _("Auto") );
265     if( sel >= 0 && mode_line && *mode_line )
266     {
267         for( rate = mode_line + 1; *rate; ++rate )
268         {
269             gtk_combo_box_append_text( m->rate_combo, *rate );
270         }
271     }
272     gtk_combo_box_set_active( m->rate_combo, 0 );
273 #endif
274 }
275 
276 /*Disable, not used
277 static void open_url( GtkDialog* dlg, const char* url, gpointer data )
278 {
279     FIXME
280 }
281 */
282 
on_about(GtkButton * btn,gpointer parent)283 static void on_about( GtkButton* btn, gpointer parent )
284 {
285     GtkWidget * about_dlg;
286     const gchar *authors[] =
287     {
288         "洪任諭 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>",
289         NULL
290     };
291     /* TRANSLATORS: Replace mw string with your names, one name per line. */
292     gchar *translators = _( "translator-credits" );
293 
294 //    gtk_about_dialog_set_url_hook( open_url, NULL, NULL);
295 
296     about_dlg = gtk_about_dialog_new ();
297 
298     gtk_container_set_border_width ( ( GtkContainer*)about_dlg , 2 );
299     gtk_about_dialog_set_version ( (GtkAboutDialog*)about_dlg, VERSION );
300     gtk_about_dialog_set_program_name ( (GtkAboutDialog*)about_dlg, _( "LXRandR" ) );
301     //gtk_about_dialog_set_logo( (GtkAboutDialog*)about_dlg, gdk_pixbuf_new_from_file(  PACKAGE_DATA_DIR"/pixmaps/lxrandr.png", NULL ) );
302     gtk_about_dialog_set_logo_icon_name( (GtkAboutDialog*)about_dlg, "video-display" );
303     gtk_about_dialog_set_copyright ( (GtkAboutDialog*)about_dlg, _( "Copyright (C) 2008-2014" ) );
304     gtk_about_dialog_set_comments ( (GtkAboutDialog*)about_dlg, _( "Monitor configuration tool for LXDE" ) );
305     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." );
306     gtk_about_dialog_set_website ( (GtkAboutDialog*)about_dlg, "http://lxde.org/" );
307     gtk_about_dialog_set_authors ( (GtkAboutDialog*)about_dlg, authors );
308     gtk_about_dialog_set_translator_credits ( (GtkAboutDialog*)about_dlg, translators );
309 
310     gtk_window_set_transient_for( (GtkWindow*)about_dlg, (GtkWindow*)parent );
311     gtk_dialog_run( ( GtkDialog*)about_dlg );
312     gtk_widget_destroy( about_dlg );
313 }
314 
315 
prepare_try_values_from_GUI()316 static void prepare_try_values_from_GUI()
317 {
318     GSList *l;
319 
320     for (l = monitors; l; l = l->next)
321     {
322         Monitor *m = (Monitor*)l->data;
323         if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m->enable)))
324             m->try_mode = -1;
325         else
326 #if GTK_CHECK_VERSION(2, 24, 0)
327             m->try_mode = gtk_combo_box_get_active(GTK_COMBO_BOX(m->res_combo));
328         m->try_rate = gtk_combo_box_get_active(GTK_COMBO_BOX(m->rate_combo));
329         if (m->pos_combo)
330             m->try_placement = gtk_combo_box_get_active(GTK_COMBO_BOX(m->pos_combo));
331 #else
332             m->try_mode = gtk_combo_box_get_active(m->res_combo);
333         m->try_rate = gtk_combo_box_get_active(m->rate_combo);
334         if (m->pos_combo)
335             m->try_placement = gtk_combo_box_get_active(m->pos_combo);
336 #endif
337         /* g_debug("mode %d rate %d placement %d",m->try_mode,m->try_rate,m->try_placement); */
338     }
339 }
340 
get_command_xrandr_info()341 static GString* get_command_xrandr_info()
342 {
343 
344     GSList* l;
345     GString *cmd = g_string_sized_new( 1024 );
346 
347     g_string_assign( cmd, "sh -c 'xrandr" );
348 
349     for( l = monitors; l; l = l->next )
350     {
351         Monitor* m = (Monitor*)l->data;
352         g_string_append( cmd, " --output " );
353         g_string_append( cmd, m->name );
354 
355         // if the monitor is turned on
356         if (m->try_mode >= 0)
357         {
358             if( m->try_mode < 1 ) // auto resolution
359             {
360                 g_string_append( cmd, " --auto" );
361             }
362             else
363             {
364                 GtkTreeModel *model;
365                 GtkTreePath *path;
366                 gchar *text;
367                 GtkTreeIter iter;
368                 gint text_column;
369 
370                 g_string_append( cmd, " --mode " );
371                 model = gtk_combo_box_get_model(GTK_COMBO_BOX(m->res_combo));
372                 text_column = gtk_combo_box_get_entry_text_column(GTK_COMBO_BOX(m->res_combo));
373                 path = gtk_tree_path_new_from_indices(m->try_mode, -1);
374                 gtk_tree_model_get_iter(model, &iter, path);
375                 gtk_tree_model_get(model, &iter, text_column, &text, -1);
376                 gtk_tree_path_free(path);
377                 g_string_append(cmd, text);
378                 g_free(text);
379                 if( m->try_rate >= 1 ) // not auto refresh rate
380                 {
381                     g_string_append( cmd, " --rate " );
382                     model = gtk_combo_box_get_model(GTK_COMBO_BOX(m->rate_combo));
383                     text_column = gtk_combo_box_get_entry_text_column(GTK_COMBO_BOX(m->rate_combo));
384                     path = gtk_tree_path_new_from_indices(m->try_rate, -1);
385                     gtk_tree_model_get_iter(model, &iter, path);
386                     gtk_tree_model_get(model, &iter, text_column, &text, -1);
387                     gtk_tree_path_free(path);
388                     g_string_append(cmd, text);
389                     g_free(text);
390                 }
391             }
392             if (m == LVDS) ; /* it's fixed, no positioning */
393             else if (LVDS != NULL)
394             {
395                 switch (m->try_placement)
396                 {
397                 case PLACEMENT_RIGHT:
398                     g_string_append(cmd, " --right-of ");
399                     break;
400                 case PLACEMENT_ABOVE:
401                     g_string_append(cmd, " --above ");
402                     break;
403                 case PLACEMENT_LEFT:
404                     g_string_append(cmd, " --left-of ");
405                     break;
406                 case PLACEMENT_BELOW:
407                     g_string_append(cmd, " --below ");
408                     break;
409                 case PLACEMENT_DEFAULT:
410                     g_string_append(cmd, " --same-as ");
411                 }
412                 g_string_append(cmd, LVDS->name);
413             }
414             else if (l != monitors)
415             {
416                 Monitor *first = (Monitor*)monitors->data;
417                 /* not notebook */
418                 switch (m->try_placement)
419                 {
420                 case PLACEMENT_RIGHT:
421                     g_string_append(cmd, " --right-of ");
422                     break;
423                 case PLACEMENT_ABOVE:
424                     g_string_append(cmd, " --above ");
425                     break;
426                 case PLACEMENT_LEFT:
427                     g_string_append(cmd, " --left-of ");
428                     break;
429                 case PLACEMENT_BELOW:
430                     g_string_append(cmd, " --below ");
431                     break;
432                 case PLACEMENT_DEFAULT:
433                     g_string_append(cmd, " --same-as ");
434                 }
435                 g_string_append(cmd, first->name);
436             }
437 
438             /* g_string_append( cmd, "" ); */
439 
440         }
441         else    // turn off
442             g_string_append( cmd, " --off" );
443     }
444     g_string_append_c(cmd, '\'');
445 
446     return cmd;
447 
448 }
449 
save_configuration()450 static void save_configuration()
451 {
452 
453     char* dirname;
454     const char grp[] = "Desktop Entry";
455     GKeyFile* kf;
456 
457     char* file, *data;
458     gsize len;
459 
460     GString *cmd;
461 
462     prepare_try_values_from_GUI();
463     cmd = get_command_xrandr_info();
464 
465     /* create user autostart dir */
466     dirname = g_build_filename(g_get_user_config_dir(), "autostart", NULL);
467     g_mkdir_with_parents(dirname, 0700);
468     g_free(dirname);
469 
470     kf = g_key_file_new();
471 
472     g_key_file_set_string( kf, grp, "Type", "Application" );
473     g_key_file_set_string( kf, grp, "Name", _("LXRandR autostart") );
474     g_key_file_set_string( kf, grp, "Comment", _("Start xrandr with settings done in LXRandR") );
475     g_key_file_set_string( kf, grp, "Exec", cmd->str );
476     g_key_file_set_string( kf, grp, "OnlyShowIn", "LXDE" );
477 
478     data = g_key_file_to_data(kf, &len, NULL);
479     file = g_build_filename(  g_get_user_config_dir(),
480                               "autostart",
481                               "lxrandr-autostart.desktop",
482                               NULL );
483     /* save it to user-specific autostart dir */
484     g_debug("save to: %s", file);
485     g_file_set_contents(file, data, len, NULL);
486     g_key_file_free (kf);
487     g_free(file);
488     g_free(data);
489     g_string_free(cmd, TRUE);
490 }
491 
cancel_confirmation(gpointer data)492 static gboolean cancel_confirmation(gpointer data)
493 {
494     if (!g_source_is_destroyed(g_main_current_source()))
495         gtk_dialog_response(data, GTK_RESPONSE_CANCEL);
496     return FALSE;
497 }
498 
set_xrandr_info()499 static void set_xrandr_info()
500 {
501     GString *cmd;
502     GSList *l;
503 
504     prepare_try_values_from_GUI();
505     cmd = get_command_xrandr_info();
506     /* g_debug("trying: %s", cmd->str); */
507 
508     if (g_spawn_command_line_sync( cmd->str, NULL, NULL, NULL, NULL ))
509     {
510         /* open a dialog box and wait 15 seconds */
511         GtkWidget *confirmation;
512         guint timer;
513         int responce;
514 
515         confirmation = gtk_message_dialog_new(GTK_WINDOW(dlg), GTK_DIALOG_MODAL,
516                                               GTK_MESSAGE_QUESTION,
517                                               GTK_BUTTONS_NONE,
518                                               _("Is everything OK? Confirm within 15 seconds,"
519                                                 " otherwise previous state will be restored."));
520         gtk_dialog_add_buttons(GTK_DIALOG(confirmation),
521                                _("_OK"), GTK_RESPONSE_ACCEPT,
522                                _("_Abort"), GTK_RESPONSE_CANCEL,
523                                NULL);
524         gtk_dialog_set_default_response(GTK_DIALOG(confirmation), GTK_RESPONSE_CANCEL);
525         timer = gdk_threads_add_timeout(15000, cancel_confirmation, confirmation);
526         responce = gtk_dialog_run(GTK_DIALOG(confirmation));
527         g_source_remove(timer);
528         gtk_widget_destroy(confirmation);
529         /* if not confirmed then set GUI to fallback values, reset xrandr,
530            then restore all GUI again */
531         if (responce == GTK_RESPONSE_ACCEPT)
532         {
533             for (l = monitors; l; l = l->next)
534             {
535                 Monitor *m = (Monitor*)l->data;
536 
537                 m->active_mode = m->try_mode;
538                 m->active_rate = m->try_rate;
539                 m->placement = m->try_placement;
540             }
541         }
542         else
543         {
544             for (l = monitors; l; l = l->next)
545             {
546                 Monitor *m = (Monitor*)l->data;
547 
548                 m->try_mode = m->active_mode;
549                 m->try_rate = m->active_rate;
550                 m->try_placement = m->placement;
551             }
552             g_string_free(cmd, TRUE);
553             cmd = get_command_xrandr_info();
554             /* g_debug("recovering: %s", cmd->str); */
555             g_spawn_command_line_sync(cmd->str, NULL, NULL, NULL, NULL);
556         }
557     }
558 
559     g_string_free( cmd, TRUE );
560 }
561 
choose_max_resolution(Monitor * m)562 static void choose_max_resolution( Monitor* m )
563 {
564 #if GTK_CHECK_VERSION(2, 24, 0)
565     if( gtk_tree_model_iter_n_children( gtk_combo_box_get_model(GTK_COMBO_BOX(m->res_combo)), NULL ) > 1 )
566         gtk_combo_box_set_active( GTK_COMBO_BOX(m->res_combo), 1 );
567     if (m->pos_combo)
568         gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), PLACEMENT_DEFAULT);
569 #else
570     if( gtk_tree_model_iter_n_children( gtk_combo_box_get_model(m->res_combo), NULL ) > 1 )
571         gtk_combo_box_set_active( m->res_combo, 1 );
572     if (m->pos_combo)
573         gtk_combo_box_set_active(m->pos_combo, PLACEMENT_DEFAULT);
574 #endif
575 }
576 
on_quick_option(GtkButton * btn,gpointer data)577 static void on_quick_option( GtkButton* btn, gpointer data )
578 {
579     GSList* l;
580     int option = GPOINTER_TO_INT(data);
581     switch( option )
582     {
583     case 1: // turn on both
584         for( l = monitors; l; l = l->next )
585         {
586             Monitor* m = (Monitor*)l->data;
587             choose_max_resolution( m );
588             gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), TRUE );
589         }
590         break;
591     case 2: // external monitor only
592         for( l = monitors; l; l = l->next )
593         {
594             Monitor* m = (Monitor*)l->data;
595             choose_max_resolution( m );
596             gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), m != LVDS );
597         }
598         break;
599     case 3: // laptop panel - LVDS only
600         for( l = monitors; l; l = l->next )
601         {
602             Monitor* m = (Monitor*)l->data;
603             choose_max_resolution( m );
604             gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), m == LVDS );
605         }
606         break;
607     case 4: // external right of LVDS
608         for( l = monitors; l; l = l->next )
609         {
610             Monitor* m = (Monitor*)l->data;
611             choose_max_resolution( m );
612             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m->enable), TRUE);
613             if (m != LVDS)
614                 gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), PLACEMENT_RIGHT);
615         }
616         break;
617     case 5: // external above of LVDS
618         for( l = monitors; l; l = l->next )
619         {
620             Monitor* m = (Monitor*)l->data;
621             choose_max_resolution( m );
622             gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m->enable), TRUE);
623             if (m != LVDS)
624                 gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), PLACEMENT_ABOVE);
625         }
626         break;
627     default:
628         return;
629     }
630 //    gtk_dialog_response( GTK_DIALOG(dlg), GTK_RESPONSE_OK );
631 //    set_xrandr_info();
632 }
633 
on_response(GtkDialog * dialog,int response,gpointer user_data)634 static void on_response( GtkDialog* dialog, int response, gpointer user_data )
635 {
636     if( response == GTK_RESPONSE_OK )
637     {
638         GtkWidget* msg;
639         GSList* l;
640         for( l = monitors; l; l = l->next )
641         {
642             Monitor* m = (Monitor*)l->data;
643             if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(m->enable) ) )
644                 break;
645         }
646         if (l != NULL)
647             set_xrandr_info();
648         else
649         {
650             msg = gtk_message_dialog_new( GTK_WINDOW(dialog), 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
651                                          _("You cannot turn off all monitors. "
652                                            "Otherwise, you will not be able to "
653                                            "turn them on again since this tool "
654                                            "is not accessible without monitor.") );
655             gtk_dialog_run( GTK_DIALOG(msg) );
656             gtk_widget_destroy( msg );
657         }
658 
659         // block the response
660         g_signal_stop_emission_by_name( dialog, "response" );
661     }
662     else if (response == GTK_RESPONSE_ACCEPT)
663     {
664         GtkWidget* msg;
665 
666         save_configuration();
667 
668         msg = gtk_message_dialog_new( GTK_WINDOW(dialog),
669                                       0,
670                                       GTK_MESSAGE_INFO,
671                                       GTK_BUTTONS_OK,
672                                       _("Configuration Saved") );
673         gtk_dialog_run( GTK_DIALOG(msg) );
674         gtk_widget_destroy( msg );
675     }
676 }
677 
main(int argc,char ** argv)678 int main(int argc, char** argv)
679 {
680     GtkWidget *notebook, *vbox, *frame, *label, *hbox, *check, *btn;
681     GSList* l;
682     Monitor *fixed;
683     int i;
684     gboolean can_position;
685 
686 #ifdef ENABLE_NLS
687     bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR );
688     bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" );
689     textdomain ( GETTEXT_PACKAGE );
690 #endif
691 
692     gtk_init( &argc, &argv );
693 
694     if( ! get_xrandr_info() )
695     {
696         dlg = gtk_message_dialog_new( NULL,
697                                       0,
698                                       GTK_MESSAGE_ERROR,
699                                       GTK_BUTTONS_OK,
700                                       _("Unable to get monitor information!"));
701         gtk_dialog_run( (GtkDialog*)dlg );
702         gtk_widget_destroy( dlg );
703         return 1;
704     }
705 
706     dlg = gtk_dialog_new_with_buttons( _("Display Settings"), NULL,
707                                        GTK_DIALOG_MODAL,
708                                        GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
709                                        GTK_STOCK_APPLY, GTK_RESPONSE_OK,
710                                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL );
711     g_signal_connect( dlg, "response", G_CALLBACK(on_response), NULL );
712     gtk_container_set_border_width( GTK_CONTAINER(dlg), 8 );
713     gtk_dialog_set_alternative_button_order( GTK_DIALOG(dlg), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1 );
714 
715     /* Set icon name for main (dlg) window so it displays in the panel. */
716     gtk_window_set_icon_name(GTK_WINDOW(dlg), "video-display");
717 
718     btn = gtk_button_new_from_stock( GTK_STOCK_ABOUT );
719 #if GTK_CHECK_VERSION(2,14,0)
720     gtk_box_pack_start(GTK_BOX(gtk_dialog_get_action_area( GTK_DIALOG(dlg))), btn, FALSE, TRUE, 0 );
721     gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(gtk_dialog_get_action_area( GTK_DIALOG(dlg))), btn, TRUE );
722 #else
723     gtk_box_pack_start( GTK_BOX(GTK_DIALOG(dlg)->action_area), btn, FALSE, TRUE, 0 );
724     gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(GTK_DIALOG(dlg)->action_area), btn, TRUE );
725 #endif
726     g_signal_connect( btn, "clicked", G_CALLBACK(on_about), dlg );
727 
728     notebook = gtk_notebook_new();
729 #if GTK_CHECK_VERSION(2,14,0)
730     gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dlg))), notebook, TRUE, TRUE, 2 );
731 #else
732     gtk_box_pack_start( GTK_BOX( GTK_DIALOG(dlg)->vbox ), notebook, TRUE, TRUE, 2 );
733 #endif
734 
735     // If this is a laptop and there is an external monitor, offer quick options
736     if( LVDS && g_slist_length( monitors ) == 2 )
737     {
738         vbox = gtk_vbox_new( FALSE, 4 );
739         gtk_container_set_border_width( GTK_CONTAINER(vbox), 8 );
740 
741         btn = gtk_radio_button_new_with_label(NULL, _("Show the same screen on both laptop LCD and external monitor"));
742         g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(1) );
743         gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);
744 
745         btn = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(btn),
746                         _("Turn off laptop LCD and use external monitor only"));
747         g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(2) );
748         gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);
749 
750         btn = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(btn),
751                         _("Turn off external monitor and use laptop LCD only"));
752         g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(3) );
753         gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);
754 
755         btn = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(btn),
756                         _("Place external monitor to the right of laptop LCD"));
757         g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(4) );
758         gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);
759 
760         btn = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(btn),
761                         _("Place external monitor above of laptop LCD"));
762         g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(5) );
763         gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);
764 
765         gtk_notebook_append_page( GTK_NOTEBOOK(notebook), vbox, gtk_label_new( _("Quick Options") ) );
766     }
767     else
768     {
769         gtk_notebook_set_show_tabs( GTK_NOTEBOOK(notebook), FALSE );
770     }
771 
772     vbox = gtk_vbox_new( FALSE, 4 );
773     gtk_container_set_border_width( GTK_CONTAINER(vbox), 8 );
774     gtk_notebook_append_page( GTK_NOTEBOOK(notebook), vbox, gtk_label_new(_("Advanced")) );
775 
776     label = gtk_label_new("");
777     gtk_misc_set_alignment( GTK_MISC(label), 0.0, 0.5 );
778     gtk_label_set_markup( GTK_LABEL(label), ngettext( "The following monitor is detected:",
779                                     "The following monitors are detected:",
780                                     g_slist_length(monitors) ) );
781     gtk_box_pack_start( GTK_BOX(vbox), label, FALSE, TRUE, 2 );
782 
783     for (l = monitors, i = 0; l; l = l->next)
784         if (((Monitor*)l->data)->active_mode >= 0)
785             i++;
786     can_position = (i > 1);
787 
788     /* correct placements */
789     fixed = LVDS ? LVDS : monitors->data;
790     if (fixed->placement != PLACEMENT_DEFAULT)
791     {
792         for (l = monitors, i = 0; l; l = l->next)
793         {
794             Monitor *m = (Monitor*)l->data;
795 
796             if (m->placement != PLACEMENT_DEFAULT)
797                 continue; /* FIXME: how to handle corners? */
798             switch (fixed->placement)
799             {
800             case PLACEMENT_LEFT:
801                 m->placement = PLACEMENT_RIGHT;
802                 break;
803             case PLACEMENT_RIGHT:
804                 m->placement = PLACEMENT_LEFT;
805                 break;
806             case PLACEMENT_ABOVE:
807                 m->placement = PLACEMENT_BELOW;
808                 break;
809             case PLACEMENT_BELOW:
810                 m->placement = PLACEMENT_ABOVE;
811                 break;
812             case PLACEMENT_DEFAULT: ;
813             }
814         }
815         fixed->placement = PLACEMENT_DEFAULT;
816     }
817 
818     for( l = monitors, i = 0; l; l = l->next, ++i )
819     {
820         Monitor* m = (Monitor*)l->data;
821         GSList* mode_line;
822 
823         frame = gtk_frame_new( get_human_readable_name(m) );
824         gtk_box_pack_start( GTK_BOX(vbox), frame, FALSE, TRUE, 2 );
825 
826         hbox = gtk_hbox_new( FALSE, 4 );
827         gtk_container_set_border_width( GTK_CONTAINER(hbox), 4 );
828         gtk_container_add( GTK_CONTAINER(frame), hbox );
829 
830         check = gtk_check_button_new_with_label( _("Turn On") );
831         m->enable = GTK_CHECK_BUTTON(check);
832         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), m->active_mode >= 0);
833 
834         // turn off screen is not allowed since there should be at least one monitor available.
835         if( g_slist_length( monitors ) == 1 )
836             gtk_widget_set_sensitive( GTK_WIDGET(m->enable), FALSE );
837         else
838             g_signal_connect(m->enable, "toggled", G_CALLBACK(on_enable_toggled), m);
839 
840         gtk_box_pack_start( GTK_BOX(hbox), check, FALSE, TRUE, 6 );
841 
842         if (monitors->next != NULL) /* g_slist_length(monitors) > 0 */
843         {
844             label = gtk_label_new(_("Position:"));
845             gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 2);
846 #if GTK_CHECK_VERSION(2, 24, 0)
847             m->pos_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
848             gtk_combo_box_text_append_text(m->pos_combo, _("Default"));
849             gtk_combo_box_text_append_text(m->pos_combo, _("On right"));
850             gtk_combo_box_text_append_text(m->pos_combo, _("Above"));
851             gtk_combo_box_text_append_text(m->pos_combo, _("On left"));
852             gtk_combo_box_text_append_text(m->pos_combo, _("Below"));
853             gtk_combo_box_set_active(GTK_COMBO_BOX(m->pos_combo), m->placement);
854 #else
855             m->pos_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
856             gtk_combo_box_append_text(m->res_combo, _("Default"));
857             gtk_combo_box_append_text(m->res_combo, _("On right"));
858             gtk_combo_box_append_text(m->res_combo, _("Above"));
859             gtk_combo_box_append_text(m->pos_combo, _("On left"));
860             gtk_combo_box_append_text(m->pos_combo, _("Below"));
861             gtk_combo_box_set_active(m->pos_combo, m->placement);
862 #endif
863             if (m == fixed || !can_position || m->active_mode < 0)
864                 gtk_widget_set_sensitive(GTK_WIDGET(m->pos_combo), FALSE);
865             gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(m->pos_combo), FALSE, TRUE, 2);
866         }
867 
868         label = gtk_label_new( _("Resolution:") );
869         gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 2 );
870 
871 #if GTK_CHECK_VERSION(2, 24, 0)
872         m->res_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
873 #else
874         m->res_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
875 #endif
876         g_signal_connect( m->res_combo, "changed", G_CALLBACK(on_res_sel_changed), m );
877         gtk_box_pack_start( GTK_BOX(hbox), GTK_WIDGET(m->res_combo), FALSE, TRUE, 2 );
878 
879         label = gtk_label_new( _("Refresh Rate:") );
880         gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 2 );
881 
882 #if GTK_CHECK_VERSION(2, 24, 0)
883         m->rate_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
884 #else
885         m->rate_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
886 #endif
887         gtk_box_pack_start( GTK_BOX(hbox), GTK_WIDGET(m->rate_combo), FALSE, TRUE, 2 );
888 
889 #if GTK_CHECK_VERSION(2, 24, 0)
890         gtk_combo_box_text_append_text( m->res_combo, _("Auto") );
891 #else
892         gtk_combo_box_append_text( m->res_combo, _("Auto") );
893 #endif
894         for( mode_line = m->mode_lines; mode_line; mode_line = mode_line->next )
895         {
896             char** strv = (char**)mode_line->data;
897 #if GTK_CHECK_VERSION(2, 24, 0)
898             gtk_combo_box_text_append_text( m->res_combo, strv[0] );
899 #else
900             gtk_combo_box_append_text( m->res_combo, strv[0] );
901 #endif
902         }
903 
904         m->active_rate++;
905 #if GTK_CHECK_VERSION(2, 24, 0)
906         gtk_combo_box_set_active( GTK_COMBO_BOX(m->res_combo), m->active_mode + 1 );
907         gtk_combo_box_set_active( GTK_COMBO_BOX(m->rate_combo), m->active_rate );
908 #else
909         gtk_combo_box_set_active( m->res_combo, m->active_mode + 1 );
910         gtk_combo_box_set_active( m->rate_combo, m->active_rate );
911 #endif
912         if (m->active_mode >= 0)
913             m->active_mode++; /* let it stay -1 for inactive button */
914     }
915 
916     gtk_widget_show_all( dlg );
917 
918     gtk_dialog_run((GtkDialog*)dlg);
919 
920     gtk_widget_destroy( dlg );
921 
922     return 0;
923 }
924