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