Merging upstream version 0.1.1.
[debian/lxrandr.git] / src / lxrandr.c
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 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
28
29 #include <locale.h>
30 #include <stdio.h>
31 #include <string.h>
32
33 typedef struct _Monitor
34 {
35 char* name;
36 GSList* mode_lines;
37 short active_mode;
38 short active_rate;
39 short pref_mode;
40 short pref_rate;
41
42 GtkCheckButton* enable;
43 GtkComboBox* res_combo;
44 GtkComboBox* rate_combo;
45 }Monitor;
46
47 static GSList* monitors = NULL;
48 static Monitor* LVDS = NULL;
49
50 static GtkWidget* dlg = NULL;
51
52 static void monitor_free( Monitor* m )
53 {
54 g_free( m->name );
55 g_slist_free( m->mode_lines );
56 g_free( m );
57 }
58
59 static const char* get_human_readable_name( Monitor* m )
60 {
61 if( m == LVDS )
62 return _("Laptop LCD Monitor");
63 else if( g_str_has_prefix( m->name, "VGA" ) || g_str_has_prefix( m->name, "Analog" ) )
64 return _( LVDS ? "External VGA Monitor" : "VGA Monitor");
65 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") )
66 return _( LVDS ? "External DVI Monitor" : "DVI Monitor");
67 else if( g_str_has_prefix( m->name, "TV" ) || g_str_has_prefix(m->name, "S-Video") )
68 return _("TV");
69 else if( strcmp( m->name, "default" ) == 0 )
70 return _( "Default Monitor");
71
72 return m->name;
73 }
74
75 static gboolean get_xrandr_info()
76 {
77 GRegex* regex;
78 GMatchInfo* match;
79 int status;
80 char* output = NULL;
81 char* ori_locale;
82
83 ori_locale = g_strdup( setlocale(LC_ALL, "") );
84
85 // set locale to "C" temporarily to guarantee English output of xrandr
86 setlocale(LC_ALL, "C");
87
88 if( ! g_spawn_command_line_sync( "xrandr", &output, NULL, &status, NULL ) || status )
89 {
90 g_free( output );
91 setlocale( LC_ALL, ori_locale );
92 g_free( ori_locale );
93 return FALSE;
94 }
95
96 regex = g_regex_new( "([a-zA-Z]+[-0-9]*) +connected .*((\n +[0-9]+x[0-9]+[^\n]+)+)",
97 0, 0, NULL );
98 if( g_regex_match( regex, output, 0, &match ) )
99 {
100 do {
101 Monitor* m = g_new0( Monitor, 1 );
102 char *modes = g_match_info_fetch( match, 2 );
103 char **lines, **line;
104 int imode = 0;
105
106 m->active_mode = m->active_rate = -1;
107 m->pref_mode = m->pref_rate = -1;
108 m->name = g_match_info_fetch( match, 1 );
109
110 // check if this is the built-in LCD of laptop
111 if( ! LVDS && (strcmp( m->name, "LVDS" ) == 0 || strcmp( m->name, "PANEL" ) == 0) )
112 LVDS = m;
113
114 lines = g_strsplit( modes, "\n", -1 );
115 for( line = lines; *line; ++line )
116 {
117 char* str = strtok( *line, " " );
118 int irate = 0;
119 GPtrArray* strv;
120 if( ! str )
121 continue;
122 strv = g_ptr_array_sized_new(8);
123 g_ptr_array_add( strv, g_strdup(str) );
124 while( str = strtok( NULL, " ") )
125 {
126 if( *str )
127 {
128 char *star, *plus;
129 str = g_strdup( str );
130
131 // sometimes, + goes after a space
132 if( 0 == strcmp( str, "+" ) )
133 --irate;
134 else
135 g_ptr_array_add( strv, str );
136
137 if( star = strchr( str, '*' ) )
138 {
139 m->active_mode = imode;
140 m->active_rate = irate;
141 }
142 if( plus = strchr( str, '+' ) )
143 {
144 m->pref_mode = imode;
145 m->pref_rate = irate;
146 }
147 if( star )
148 *star = '\0';
149 if( plus )
150 *plus = '\0';
151 ++irate;
152 }
153 }
154 g_ptr_array_add( strv, NULL );
155 m->mode_lines = g_slist_append( m->mode_lines, g_ptr_array_free( strv, FALSE ) );
156 ++imode;
157 }
158 g_strfreev( lines );
159 g_free( modes );
160 monitors = g_slist_prepend( monitors, m );
161 }while( g_match_info_next( match, NULL ) );
162
163 g_match_info_free( match );
164 }
165 g_regex_unref( regex );
166
167 // restore the original locale
168 setlocale( LC_ALL, ori_locale );
169 g_free( ori_locale );
170
171 return TRUE;
172 }
173
174 static void on_res_sel_changed( GtkComboBox* cb, Monitor* m )
175 {
176 char** rate;
177 int sel = gtk_combo_box_get_active( cb );
178 char** mode_line = g_slist_nth_data( m->mode_lines, sel - 1 );
179 gtk_list_store_clear( GTK_LIST_STORE(gtk_combo_box_get_model( m->rate_combo )) );
180
181 gtk_combo_box_append_text( m->rate_combo, _("Auto") );
182 if( sel >= 0 && mode_line && *mode_line )
183 {
184 for( rate = mode_line + 1; *rate; ++rate )
185 {
186 gtk_combo_box_append_text( m->rate_combo, *rate );
187 }
188 }
189 gtk_combo_box_set_active( m->rate_combo, 0 );
190 }
191
192 static void open_url( GtkDialog* dlg, const char* url, gpointer data )
193 {
194 /* FIXME: */
195 }
196
197 static void on_about( GtkButton* btn, gpointer parent )
198 {
199 GtkWidget * about_dlg;
200 const gchar *authors[] =
201 {
202 "洪任諭 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>",
203 NULL
204 };
205 /* TRANSLATORS: Replace mw string with your names, one name per line. */
206 gchar *translators = _( "translator-credits" );
207
208 // gtk_about_dialog_set_url_hook( open_url, NULL, NULL);
209
210 about_dlg = gtk_about_dialog_new ();
211
212 gtk_container_set_border_width ( ( GtkContainer*)about_dlg , 2 );
213 gtk_about_dialog_set_version ( (GtkAboutDialog*)about_dlg, VERSION );
214 gtk_about_dialog_set_name ( (GtkAboutDialog*)about_dlg, _( "LXRandR" ) );
215 //gtk_about_dialog_set_logo( (GtkAboutDialog*)about_dlg, gdk_pixbuf_new_from_file( PACKAGE_DATA_DIR"/pixmaps/lxrandr.png", NULL ) );
216 gtk_about_dialog_set_copyright ( (GtkAboutDialog*)about_dlg, _( "Copyright (C) 2008" ) );
217 gtk_about_dialog_set_comments ( (GtkAboutDialog*)about_dlg, _( "Monitor configuration tool for LXDE" ) );
218 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." );
219 gtk_about_dialog_set_website ( (GtkAboutDialog*)about_dlg, "http://lxde.org/" );
220 gtk_about_dialog_set_authors ( (GtkAboutDialog*)about_dlg, authors );
221 gtk_about_dialog_set_translator_credits ( (GtkAboutDialog*)about_dlg, translators );
222
223 gtk_window_set_transient_for( (GtkWindow*)about_dlg, (GtkWindow*)parent );
224 gtk_dialog_run( ( GtkDialog*)about_dlg );
225 gtk_widget_destroy( about_dlg );
226 }
227
228 static void set_xrandr_info()
229 {
230 GSList* l;
231 GString *cmd = g_string_sized_new( 1024 );
232
233 g_string_assign( cmd, "xrandr" );
234
235 for( l = monitors; l; l = l->next )
236 {
237 Monitor* m = (Monitor*)l->data;
238 g_string_append( cmd, " --output " );
239 g_string_append( cmd, m->name );
240 g_string_append_c( cmd, ' ' );
241
242 // if the monitor is turned on
243 if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(m->enable) ) )
244 {
245 int sel_res = gtk_combo_box_get_active( m->res_combo );
246 int sel_rate = gtk_combo_box_get_active( m->rate_combo );
247
248 if( sel_res < 1 ) // auto resolution
249 {
250 g_string_append( cmd, "--auto" );
251 }
252 else
253 {
254 g_string_append( cmd, "--mode " );
255 ++sel_res; // the fist item in the combo box is "Auto", indecis of resolutions are 1, 2, 3...
256 g_string_append( cmd, gtk_combo_box_get_active_text(m->res_combo) );
257
258 if( sel_rate >= 1 ) // not auto refresh rate
259 {
260 g_string_append( cmd, " --rate " );
261 g_string_append( cmd, gtk_combo_box_get_active_text(m->rate_combo) );
262 }
263 }
264
265 g_string_append( cmd, "" );
266
267 }
268 else // turn off
269 g_string_append( cmd, "--off" );
270 }
271
272 g_spawn_command_line_sync( cmd->str, NULL, NULL, NULL, NULL );
273 g_string_free( cmd, TRUE );
274 }
275
276 static void choose_max_resolution( Monitor* m )
277 {
278 if( gtk_tree_model_iter_n_children( gtk_combo_box_get_model(m->res_combo), NULL ) > 1 )
279 gtk_combo_box_set_active( m->res_combo, 1 );
280 }
281
282 static void on_quick_option( GtkButton* btn, gpointer data )
283 {
284 GSList* l;
285 int option = GPOINTER_TO_INT(data);
286 switch( option )
287 {
288 case 1: // turn on both
289 for( l = monitors; l; l = l->next )
290 {
291 Monitor* m = (Monitor*)l->data;
292 choose_max_resolution( m );
293 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), TRUE );
294 }
295 break;
296 case 2: // external monitor only
297 for( l = monitors; l; l = l->next )
298 {
299 Monitor* m = (Monitor*)l->data;
300 choose_max_resolution( m );
301 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), m != LVDS );
302 }
303 break;
304 case 3: // laptop panel - LVDS only
305 for( l = monitors; l; l = l->next )
306 {
307 Monitor* m = (Monitor*)l->data;
308 choose_max_resolution( m );
309 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(m->enable), m == LVDS );
310 }
311 break;
312 default:
313 return;
314 }
315 gtk_dialog_response( GTK_DIALOG(dlg), GTK_RESPONSE_OK );
316 // set_xrandr_info();
317 }
318
319 static void on_response( GtkDialog* dialog, int response, gpointer user_data )
320 {
321 if( response == GTK_RESPONSE_OK )
322 {
323 GtkWidget* msg;
324 GSList* l;
325 for( l = monitors; l; l = l->next )
326 {
327 Monitor* m = (Monitor*)l->data;
328 if( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(m->enable) ) )
329 return;
330 }
331
332 msg = gtk_message_dialog_new( GTK_WINDOW(dialog), 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
333 _("You cannot turn off all monitors. Otherwise, you will not be able to turn them on again since this tool is not accessable whithout monitor.") );
334 gtk_dialog_run( GTK_DIALOG(msg) );
335 gtk_widget_destroy( msg );
336
337 // block the response
338 g_signal_stop_emission_by_name( dialog, "response" );
339 }
340 }
341
342 int main(int argc, char** argv)
343 {
344 GtkWidget *notebook, *vbox, *frame, *label, *hbox, *check, *btn;
345 GSList* l;
346
347 #ifdef ENABLE_NLS
348 bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR );
349 bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" );
350 textdomain ( GETTEXT_PACKAGE );
351 #endif
352
353 gtk_init( &argc, &argv );
354
355 if( ! get_xrandr_info() )
356 {
357 dlg = gtk_message_dialog_new( NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
358 _("Unable to get monitor information!"), NULL );
359 gtk_dialog_run( (GtkDialog*)dlg );
360 gtk_widget_destroy( dlg );
361 return 1;
362 }
363
364 dlg = gtk_dialog_new_with_buttons( _("Display Settings"), NULL,
365 GTK_DIALOG_NO_SEPARATOR,
366 GTK_STOCK_OK, GTK_RESPONSE_OK,
367 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL );
368 g_signal_connect( dlg, "response", G_CALLBACK(on_response), NULL );
369 gtk_container_set_border_width( GTK_CONTAINER(dlg), 8 );
370 gtk_dialog_set_alternative_button_order( GTK_DIALOG(dlg), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1 );
371
372 btn = gtk_button_new_from_stock( GTK_STOCK_ABOUT );
373 gtk_box_pack_start( GTK_BOX(GTK_DIALOG(dlg)->action_area), btn, FALSE, TRUE, 0 );
374 gtk_button_box_set_child_secondary( GTK_BUTTON_BOX(GTK_DIALOG(dlg)->action_area), btn, TRUE );
375 g_signal_connect( btn, "clicked", G_CALLBACK(on_about), dlg );
376
377 notebook = gtk_notebook_new();
378 gtk_box_pack_start( GTK_BOX( GTK_DIALOG(dlg)->vbox ), notebook, TRUE, TRUE, 2 );
379
380 // If this is a laptop and there is an external monitor, offer quick options
381 if( LVDS && g_slist_length( monitors ) == 2 )
382 {
383 vbox = gtk_vbox_new( FALSE, 4 );
384 gtk_container_set_border_width( GTK_CONTAINER(vbox), 8 );
385
386 btn = gtk_button_new_with_label( _("Show the same screen on both laptop LCD and external monitor") );
387 g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(1) );
388 gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);
389
390 btn = gtk_button_new_with_label( _("Turn off laptop LCD and use external monitor only") );
391 g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(2) );
392 gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);
393
394 btn = gtk_button_new_with_label( _("Turn off external monitor and use laptop LCD only") );
395 g_signal_connect( btn, "clicked", G_CALLBACK(on_quick_option), GINT_TO_POINTER(3) );
396 gtk_box_pack_start( GTK_BOX(vbox), btn, FALSE, TRUE , 4);
397
398 gtk_notebook_append_page( GTK_NOTEBOOK(notebook), vbox, gtk_label_new( _("Quick Options") ) );
399 }
400 else
401 {
402 gtk_notebook_set_show_tabs( GTK_NOTEBOOK(notebook), FALSE );
403 }
404
405 vbox = gtk_vbox_new( FALSE, 4 );
406 gtk_container_set_border_width( GTK_CONTAINER(vbox), 8 );
407 gtk_notebook_append_page( GTK_NOTEBOOK(notebook), vbox, gtk_label_new(_("Advanced")) );
408
409 label = gtk_label_new("");
410 gtk_misc_set_alignment( GTK_MISC(label), 0.0, 0.5 );
411 gtk_label_set_markup( GTK_LABEL(label), ngettext( "The following monitor is detected:",
412 "The following monitors are detected:",
413 g_slist_length(monitors) ) );
414 gtk_box_pack_start( GTK_BOX(vbox), label, FALSE, TRUE, 2 );
415
416 int i;
417 for( l = monitors, i = 0; l; l = l->next, ++i )
418 {
419 Monitor* m = (Monitor*)l->data;
420 GSList* mode_line;
421
422 frame = gtk_frame_new( get_human_readable_name(m) );
423 gtk_box_pack_start( GTK_BOX(vbox), frame, FALSE, TRUE, 2 );
424
425 hbox = gtk_hbox_new( FALSE, 4 );
426 gtk_container_set_border_width( GTK_CONTAINER(hbox), 4 );
427 gtk_container_add( GTK_CONTAINER(frame), hbox );
428
429 check = gtk_check_button_new_with_label( _("Turn On") );
430 m->enable = GTK_CHECK_BUTTON(check);
431
432 // turn off screen is not allowed since there should be at least one monitor available.
433 if( g_slist_length( monitors ) == 1 )
434 gtk_widget_set_sensitive( GTK_WIDGET(m->enable), FALSE );
435
436 gtk_box_pack_start( GTK_BOX(hbox), check, FALSE, TRUE, 6 );
437 if( m->active_mode >= 0 )
438 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(check), TRUE );
439
440 label = gtk_label_new( _("Resolution:") );
441 gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 2 );
442
443 m->res_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
444 g_signal_connect( m->res_combo, "changed", G_CALLBACK(on_res_sel_changed), m );
445 gtk_box_pack_start( GTK_BOX(hbox), GTK_WIDGET(m->res_combo), FALSE, TRUE, 2 );
446
447 label = gtk_label_new( _("Refresh Rate:") );
448 gtk_box_pack_start( GTK_BOX(hbox), label, FALSE, TRUE, 2 );
449
450 m->rate_combo = GTK_COMBO_BOX(gtk_combo_box_new_text());
451 gtk_box_pack_start( GTK_BOX(hbox), GTK_WIDGET(m->rate_combo), FALSE, TRUE, 2 );
452
453 gtk_combo_box_append_text( m->res_combo, _("Auto") );
454 for( mode_line = m->mode_lines; mode_line; mode_line = mode_line->next )
455 {
456 char** strv = (char**)mode_line->data;
457 gtk_combo_box_append_text( m->res_combo, strv[0] );
458 }
459
460 gtk_combo_box_set_active( m->res_combo, m->active_mode + 1 );
461 gtk_combo_box_set_active( m->rate_combo, m->active_rate + 1 );
462 }
463
464 gtk_widget_show_all( dlg );
465
466 if( gtk_dialog_run( (GtkDialog*)dlg ) == GTK_RESPONSE_OK )
467 set_xrandr_info();
468
469 gtk_widget_destroy( dlg );
470
471 return 0;
472 }