From 20d518389a6d2e2cac761d62890c8321397d292a Mon Sep 17 00:00:00 2001 From: Marty Jack <martyj@linux.local> Date: Mon, 29 Mar 2010 16:50:48 -0400 Subject: [PATCH 09/60] Major rework - -e and --command now consume arguments to the end of the line (--command= is unchanged) - Preference dialog now GtkBuilder based, must have external XML file to operate successfully - 2973537 Failure to either connect or bind to the communication socket now handled gracefully; the LXTerminal will run standalone - Terminal failed to resize when font changed from larger to smaller - Failed to honor --working-directory with --command - Configuration now applies immediately - User preference for cursor shape - User preference for audible bell - 2977944 User preference for allow bold - 2842542 User preference for hide Close button - 2842542 Middle click on tab closes it; consistent with other applications such as Firefox - 2845142 User can edit tab labels --- AUTHORS | 1 + configure.ac | 2 +- data/Makefile.am | 15 + data/lxterminal-preferences.glade | 512 ++++++++++ data/lxterminal.conf.in | 1 + man/lxterminal.xml | 30 +- src/Makefile.am | 7 +- src/lxterminal.c | 2017 ++++++++++++++++++++----------------- src/lxterminal.h | 111 ++- src/preferences.c | 526 ++++------ src/preferences.h | 78 +- src/setting.c | 208 ++-- src/setting.h | 58 +- src/tab.c | 112 +-- src/tab.h | 29 +- src/unixsocket.c | 361 ++++--- src/unixsocket.h | 22 +- 17 files changed, 2361 insertions(+), 1729 deletions(-) create mode 100644 data/lxterminal-preferences.glade diff --git a/AUTHORS b/AUTHORS index 975d44a..e976153 100644 --- a/AUTHORS +++ b/AUTHORS @@ -4,6 +4,7 @@ Alessandro Pellizzari <alex_shu@users.sourceforge.net> Micahel <metsger@users.sourceforge.net> Yotam Medini <yotam.medini@gmail.com> Liu Yubao <yubao.liu@gmail.com> +Marty Jack <martyj19@comcast.net> Icon: Taken from "nuoveXT 2" icon theme created by Alexandre Moore (saki) diff --git a/configure.ac b/configure.ac index d3942c2..120deea 100644 --- a/configure.ac +++ b/configure.ac @@ -21,7 +21,7 @@ AC_ARG_ENABLE(man, ) # Checks for libraries. PKG_CHECK_MODULES(glib, [glib-2.0 >= 2.6.0]) -PKG_CHECK_MODULES(gtk, [gtk+-2.0 >= 2.6.0]) +PKG_CHECK_MODULES(gtk, [gtk+-2.0 >= 2.12.0]) PKG_CHECK_MODULES(vte, [vte >= 0.17.1]) CFLAGS="$CFLAGS $glib_CFLAGS $gtk_CFLAGS $vte_CFLAGS" LIBS="$LIBS $glib_LIBS $gtk_LIBS $vte_LIBS" diff --git a/data/Makefile.am b/data/Makefile.am index a0eacd4..046918a 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -1,6 +1,20 @@ default_config_DATA = lxterminal.conf default_configdir = $(datadir)/lxterminal +uidir=$(datadir)/lxterminal +ui_in_files= \ + lxterminal-preferences.glade + +ui_DATA = $(ui_in_files:.glade=.ui) + +# Generate GtkBuilder UI files from Glade files +%.ui: %.glade + cp $< $@ + $(top_builddir)/src/xml-purge $@ + +CLEANFILES = \ + $(ui_DATA) + lxterminal.desktop_in_files = lxterminal.desktop.in lxterminal.desktop.in_DATA = $(lxterminal.desktop_in_files:.desktop.in=.desktop) @INTLTOOL_DESKTOP_RULE@ @@ -13,4 +27,5 @@ EXTRA_DIST = \ lxterminal.conf.in \ $(lxterminal_images_DATA) \ $(lxterminal.desktop.in_DATA) \ + $(ui_DATA) \ $(default_config_DATA) diff --git a/data/lxterminal-preferences.glade b/data/lxterminal-preferences.glade new file mode 100644 index 0000000..6b9ebaf --- /dev/null +++ b/data/lxterminal-preferences.glade @@ -0,0 +1,512 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 2.12 --> + <!-- interface-naming-policy project-wide --> + <object class="GtkListStore" id="liststore1"> + <columns> + <!-- column-name gchararray1 --> + <column type="gchararray"/> + </columns> + <data> + <row> + <col id="0" translatable="yes">Top</col> + </row> + <row> + <col id="0" translatable="yes">Bottom</col> + </row> + <row> + <col id="0" translatable="yes">Left</col> + </row> + <row> + <col id="0" translatable="yes">Right</col> + </row> + </data> + </object> + <object class="GtkDialog" id="lxterminal_preferences"> + <property name="border_width">5</property> + <property name="type_hint">normal</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <object class="GtkNotebook" id="notebook1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="n_rows">8</property> + <property name="n_columns">2</property> + <property name="column_spacing">5</property> + <property name="row_spacing">3</property> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Terminal font</property> + </object> + </child> + <child> + <object class="GtkFontButton" id="terminal_font"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Background</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="background_color"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_alpha">True</property> + <property name="color">#000000000000</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="foreground_color"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="color">#000000000000</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Foreground</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Allow bold font</property> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="allow_bold"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Cursor blink</property> + </object> + <packing> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="cursor_blink"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Cursor style</property> + </object> + <packing> + <property name="top_attach">5</property> + <property name="bottom_attach">7</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="cursor_style_block"> + <property name="label" translatable="yes">Block</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="cursor_style_underline"> + <property name="label" translatable="yes">Underline</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + <property name="group">cursor_style_block</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">6</property> + <property name="bottom_attach">7</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label17"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Audible bell</property> + </object> + <packing> + <property name="top_attach">7</property> + <property name="bottom_attach">8</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="audible_bell"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">7</property> + <property name="bottom_attach">8</property> + </packing> + </child> + </object> + </child> + <child type="tab"> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">Style</property> + </object> + <packing> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table2"> + <property name="visible">True</property> + <property name="n_rows">5</property> + <property name="n_columns">2</property> + <property name="column_spacing">5</property> + <property name="row_spacing">3</property> + <child> + <object class="GtkLabel" id="label9"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Tab panel position</property> + </object> + </child> + <child> + <object class="GtkLabel" id="label10"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Scrollback lines</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label11"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Hide scroll bar</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Hide menu bar</property> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="hide_menu_bar"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="tab_position"> + <property name="visible">True</property> + <property name="model">liststore1</property> + <child> + <object class="GtkCellRendererText" id="cellrenderertext2"/> + <attributes> + <attribute name="text">0</attribute> + </attributes> + </child> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="y_options">GTK_SHRINK</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="scrollback_lines"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">•</property> + <property name="adjustment">scrollback_lines_adjustment</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="hide_scroll_bar"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label16"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Hide Close buttons</property> + </object> + <packing> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="hide_close_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes">Display</property> + </object> + <packing> + <property name="position">1</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table3"> + <property name="visible">True</property> + <property name="n_rows">2</property> + <property name="n_columns">2</property> + <property name="column_spacing">5</property> + <property name="row_spacing">3</property> + <child> + <object class="GtkLabel" id="label14"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Select-by-word characters</property> + </object> + </child> + <child> + <object class="GtkLabel" id="label15"> + <property name="visible">True</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Disable menu shortcut key (F10 by default)</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="disable_f10"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="select_by_word"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="invisible_char">•</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + </packing> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Advanced</property> + </object> + <packing> + <property name="position">2</property> + <property name="tab_fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="Cancel_button"> + <property name="label" translatable="yes">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="OK_button"> + <property name="label" translatable="yes">gtk-ok</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-6">Cancel_button</action-widget> + <action-widget response="-5">OK_button</action-widget> + </action-widgets> + </object> + <object class="GtkAdjustment" id="scrollback_lines_adjustment"> + <property name="value">1000</property> + <property name="upper">100000</property> + <property name="step_increment">10</property> + <property name="page_increment">10</property> + </object> +</interface> diff --git a/data/lxterminal.conf.in b/data/lxterminal.conf.in index 74ec5cb..2621a08 100644 --- a/data/lxterminal.conf.in +++ b/data/lxterminal.conf.in @@ -1,3 +1,4 @@ [general] fontname=Monospace 10 selchars=-A-Za-z0-9,./?%&#:_ +scrollback=1000 diff --git a/man/lxterminal.xml b/man/lxterminal.xml index c2124eb..e0977cf 100644 --- a/man/lxterminal.xml +++ b/man/lxterminal.xml @@ -29,8 +29,7 @@ <para><command>lxterminal</command> is a program that provides a terminal emulator - for the desktop, usually for LXDE. It is a lightweight GTK+ 2.x based desktop - panel. + for the desktop, usually for LXDE. </para> </refsect1> @@ -44,19 +43,11 @@ <variablelist> <varlistentry> <term> <option>-e <replaceable>STRING</replaceable></option> <option>--command=<replaceable>STRING</replaceable></option> + <option>--command <replaceable>STRING</replaceable></option> + </term> - <listitem> <para>Execute the argument to this option inside the terminal.</para> - </listitem> - </varlistentry> - <varlistentry> <term> <option>-t <replaceable>STRING</replaceable></option> - <option>--title=<replaceable>STRING</replaceable></option> - </term> - <listitem> <para>Set the terminal's title.</para> - </listitem> - </varlistentry> - <varlistentry> <term> <option>--working-directory=<replaceable>DIRECTORY</replaceable></option> - </term> - <listitem> <para>Set the terminal's working directory.</para> + <listitem> <para>This option specifies the program (and its command line arguments) to be run in the terminal. +Except in the <option>--command=</option> form, this must be the last option on the command line.</para> </listitem> </varlistentry> <varlistentry> <term> <option>--geometry=<replaceable>CHARACTERS</replaceable>x<replaceable>LINES</replaceable></option> @@ -70,6 +61,17 @@ <listitem> <para>Executes login shell.</para> </listitem> </varlistentry> + <varlistentry> <term> <option>-t <replaceable>STRING</replaceable></option> + <option>--title=<replaceable>STRING</replaceable></option> + </term> + <listitem> <para>Set the terminal's title.</para> + </listitem> + </varlistentry> + <varlistentry> <term> <option>--working-directory=<replaceable>DIRECTORY</replaceable></option> + </term> + <listitem> <para>Set the terminal's working directory.</para> + </listitem> + </varlistentry> </variablelist> </refsect1> diff --git a/src/Makefile.am b/src/Makefile.am index ca2b3db..af9efd4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,8 +15,6 @@ lxterminal_SOURCES = \ unixsocket.c \ setting.h \ setting.c \ - tab.h \ - tab.c \ lxterminal.h \ lxterminal.c \ preferences.h \ @@ -25,3 +23,8 @@ lxterminal_SOURCES = \ lxterminal_LDADD = \ $(PACKAGE_LIBS) \ $(INTLLIBS) + +noinst_PROGRAMS=xml-purge +xml_purge_SOURCES=xml-purge.c +xml_purge_CFLAGS=@CFLAGS@ +xml_purge_LDADD=@LIBS@ diff --git a/src/lxterminal.c b/src/lxterminal.c index 37e0253..60712bb 100644 --- a/src/lxterminal.c +++ b/src/lxterminal.c @@ -1,5 +1,6 @@ -/* +/** * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details. * * 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 @@ -18,7 +19,7 @@ */ #ifdef HAVE_CONFIG_H - #include <config.h> +#include <config.h> #endif #include <X11/Xutil.h> @@ -29,14 +30,14 @@ #include <vte/vte.h> #include <langinfo.h> #include <locale.h> +#include <sys/stat.h> #include "lxterminal.h" #include "setting.h" -#include "tab.h" #include "preferences.h" #include "unixsocket.h" -/* Linux color for palette */ +/* Linux color for palette. */ static const GdkColor linux_color[16] = { { 0, 0x0000, 0x0000, 0x0000 }, @@ -57,1100 +58,1272 @@ static const GdkColor linux_color[16] = { 0, 0xffff, 0xffff, 0xffff } }; -static void terminal_childexit(VteTerminal *vte, Term *term); - -static Term *terminal_new(LXTerminal *terminal, const gchar *label, const gchar *pwd, gchar **env, const gchar *exec); -static void terminal_free(Term* term); - -/* menu accel saved when the user disables it */ -static char *saved_menu_accel = NULL; - -static gchar helpmsg[] = { +/* X accessor. */ +static void gdk_window_get_geometry_hints(GdkWindow * window, GdkGeometry * geometry, GdkWindowHints * geometry_mask); + +/* Utilities. */ +static void terminal_get_padding(Term * term, GtkBorder * border); +static void terminal_geometry_restore(Term * term); +static void terminal_tab_set_position(GtkWidget * notebook, gint tab_position); + +/* Menu and accelerator event handlers. */ +static void terminal_initialize_switch_tab_accelerator(Term * term); +static void terminal_switch_tab_accelerator(Term * term); +static void terminal_new_window_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_new_window_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_new_tab_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_new_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_close_tab_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_close_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_copy_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_copy_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_paste_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_paste_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_name_tab_response_event(GtkWidget * dialog, gint response, LXTerminal * terminal); +static void terminal_name_tab_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_name_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_previous_tab_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_previous_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_next_tab_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_next_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_move_tab_execute(LXTerminal * terminal, gint direction); +static void terminal_move_tab_left_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_move_tab_left_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_move_tab_right_activate_event(GtkAction * action, LXTerminal * terminal); +static void terminal_move_tab_right_accelerator(LXTerminal * terminal, guint action, GtkWidget * item); +static void terminal_about_activate_event(GtkAction * action, LXTerminal * terminal); + +/* Window creation, destruction, and control. */ +static gboolean terminal_window_size_request_event(GtkWidget * widget, GtkRequisition * requisition, LXTerminal * terminal); +static void terminal_window_set_fixed_size(LXTerminal * terminal); +static void terminal_switch_page_event(GtkNotebook * notebook, GtkNotebookPage * page, guint num, LXTerminal * terminal); +static void terminal_window_title_changed_event(GtkWidget * vte, Term * term); +static void terminal_window_exit(LXTerminal * terminal, GObject * where_the_object_was); +static void terminal_child_exited_event(VteTerminal * vte, Term * term); +static gboolean terminal_tab_button_press_event(GtkWidget * widget, GdkEventButton * event, Term * term); +static gboolean terminal_vte_button_press_event(VteTerminal * vte, GdkEventButton * event, Term * term); +static void terminal_settings_apply_to_term(LXTerminal * terminal, Term * term); +static Term * terminal_new(LXTerminal * terminal, const gchar * label, const gchar * pwd, gchar * * env, const gchar * exec); +static void terminal_free(Term * term); +static void terminal_menubar_initialize(LXTerminal * terminal); +static void terminal_accelerator_initialize(LXTerminal * terminal); +static void terminal_menu_accelerator_update(LXTerminal * terminal); +static void terminal_settings_apply(LXTerminal * terminal); + +/* Menu accelerator saved when the user disables it. */ +static char * saved_menu_accelerator = NULL; + +/* Help when user enters an invalid command. */ +static gchar usage_display[] = { "Usage:\n" " lxterminal [Options...] - LXTerminal is a terminal emulator\n\n" "Options:\n" " -e, --command=STRING Execute the argument to this option inside the terminal\n" - " -t, -T, --title=STRING Set the terminal's title\n" + " --geometry=COLUMNSxROWS Set the terminal's size\n" + " -l, --loginshell Execute login shell\n" + " -t, -T, --title=STRING Set the terminal's title\n" " --working-directory=DIRECTORY Set the terminal's working directory\n" - " --geometry=GEOMETRY X geometry specification (see \"X\" man page), can be specified once per window to be opened.\n" - " -l, --loginshell Executes login shell.\n" }; +/* Descriptors for menu top level. */ static GtkActionEntry menus[] = { { "File", NULL, N_("_File") }, { "Edit", NULL, N_("_Edit") }, -// { "View", NULL, N_("_View") }, { "Tabs", NULL, N_("_Tabs") }, { "Help", NULL, N_("_Help") } }; #define MENUBAR_MENU_COUNT G_N_ELEMENTS(menus) -static void -gdk_window_get_geometry_hints(GdkWindow *window, - GdkGeometry *geometry, - GdkWindowHints *geom_mask) +/* Descriptors for menu bar items. */ +static GtkActionEntry menu_items[] = { - XSizeHints size_hints; - glong junk_size_mask = 0; - - g_return_if_fail (GDK_IS_WINDOW (window)); - g_return_if_fail (geometry != NULL); - g_return_if_fail (geom_mask != NULL); - - *geom_mask = 0; - - if (GDK_WINDOW_DESTROYED (window)) - return; - - if (!XGetWMNormalHints (GDK_WINDOW_XDISPLAY (window), - GDK_WINDOW_XID (window), - &size_hints, - &junk_size_mask)) - return; - - if (size_hints.flags & PMinSize) { - *geom_mask |= GDK_HINT_MIN_SIZE; - geometry->min_width = size_hints.min_width; - geometry->min_height = size_hints.min_height; - } - - if (size_hints.flags & PMaxSize) { - *geom_mask |= GDK_HINT_MAX_SIZE; - geometry->max_width = MAX (size_hints.max_width, 1); - geometry->max_height = MAX (size_hints.max_height, 1); - } - - if (size_hints.flags & PResizeInc) { - *geom_mask |= GDK_HINT_RESIZE_INC; - geometry->width_inc = size_hints.width_inc; - geometry->height_inc = size_hints.height_inc; - } - - if (size_hints.flags & PAspect) { - *geom_mask |= GDK_HINT_ASPECT; - geometry->min_aspect = (gdouble) size_hints.min_aspect.x / (gdouble) size_hints.min_aspect.y; - geometry->max_aspect = (gdouble) size_hints.max_aspect.x / (gdouble) size_hints.max_aspect.y; - } - - if (size_hints.flags & PWinGravity) { - *geom_mask |= GDK_HINT_WIN_GRAVITY; - geometry->win_gravity = size_hints.win_gravity; - } -} + { "File_NewWindow", GTK_STOCK_ADD, N_("New _Window"), NEW_WINDOW_ACCEL, "New Window", G_CALLBACK(terminal_new_window_activate_event) }, + { "File_NewTab", GTK_STOCK_ADD, N_("New _Tab"), NEW_TAB_ACCEL, "New Tab", G_CALLBACK(terminal_new_tab_activate_event) }, + { "File_Sep1", NULL, "Sep" }, + { "File_CloseTab", GTK_STOCK_CLOSE, N_("_Close Tab"), CLOSE_TAB_ACCEL, "Close Tab", G_CALLBACK(terminal_close_tab_activate_event) }, + { "File_Quit", GTK_STOCK_QUIT, N_("_Quit"), QUIT_ACCEL, "Quit", G_CALLBACK(gtk_main_quit) }, + { "Edit_Copy", GTK_STOCK_COPY, N_("Cop_y"), COPY_ACCEL, "Copy", G_CALLBACK(terminal_copy_activate_event) }, + { "Edit_Paste", GTK_STOCK_PASTE, N_("_Paste"), PASTE_ACCEL, "Paste", G_CALLBACK(terminal_paste_activate_event) }, + { "Edit_Sep1", NULL, "Sep" }, + { "Edit_Preferences", GTK_STOCK_EXECUTE, N_("Preference_s"), NULL, "Preferences", G_CALLBACK(terminal_preferences_dialog) }, + { "Tabs_NameTab", GTK_STOCK_INFO, N_("Na_me Tab"), NAME_TAB_ACCEL, "Name Tab", G_CALLBACK(terminal_name_tab_activate_event) }, + { "Tabs_PreviousTab", GTK_STOCK_GO_BACK, N_("Pre_vious Tab"), PREVIOUS_TAB_ACCEL, "Previous Tab", G_CALLBACK(terminal_previous_tab_activate_event) }, + { "Tabs_NextTab", GTK_STOCK_GO_FORWARD, N_("Ne_xt Tab"), NEXT_TAB_ACCEL, "Next Tab", G_CALLBACK(terminal_next_tab_activate_event) }, + { "Tabs_MoveTabLeft", NULL, N_("Move Tab _Left"), MOVE_TAB_LEFT_ACCEL, "Move Tab Left", G_CALLBACK(terminal_move_tab_left_activate_event) }, + { "Tabs_MoveTabRight", NULL, N_("Move Tab _Right"), MOVE_TAB_RIGHT_ACCEL, "Move Tab Right", G_CALLBACK(terminal_move_tab_right_activate_event) }, + { "Help_About", GTK_STOCK_ABOUT, N_("_About"), NULL, "About", G_CALLBACK(terminal_about_activate_event) } +}; +#define MENUBAR_MENUITEM_COUNT G_N_ELEMENTS(menu_items) -static gboolean terminal_window_resize(GtkWidget *widget, GtkRequisition *requisition, LXTerminal *terminal) +/* Descriptors for popup menu items, accessed via right click on the terminal. */ +static GtkActionEntry vte_menu_items[] = { - Term *term; - GdkGeometry hints; - gint xpad; - gint ypad; - gint i; - - if (!terminal->fixedsize) { - return FALSE; - } - - terminal->fixedsize = FALSE; - - /* getting window size by fixed */ - term = g_ptr_array_index(terminal->terms, 0); - vte_terminal_get_padding(VTE_TERMINAL(term->vte), &xpad, &ypad); - hints.width_inc = VTE_TERMINAL(term->vte)->char_width; - hints.height_inc = VTE_TERMINAL(term->vte)->char_height; - hints.base_width = xpad; - hints.base_height = ypad; - hints.min_width = hints.base_width + hints.width_inc * 4; - hints.min_height = hints.base_height + hints.height_inc * 2; - - /* allow resizing by user */ - for (i=0;i<terminal->terms->len;i++) { - term = g_ptr_array_index(terminal->terms, i); - - gtk_window_set_geometry_hints(GTK_WINDOW(terminal->mainw), - term->vte, - &hints, - GDK_HINT_RESIZE_INC - | GDK_HINT_MIN_SIZE - | GDK_HINT_BASE_SIZE); - } - - /* setting fixed size */ - gtk_window_resize(GTK_WINDOW(terminal->mainw), requisition->width, requisition->height); - - return FALSE; -} + { "VTEMenu", NULL, "VTEMenu" }, + { "NewWindow", GTK_STOCK_ADD, N_("New _Window"), NULL, "New Window", G_CALLBACK(terminal_new_window_activate_event) }, + { "NewTab", GTK_STOCK_ADD, N_("New _Tab"), NULL, "New Tab", G_CALLBACK(terminal_new_tab_activate_event) }, + { "Sep1", NULL, "Sep" }, + { "Copy", GTK_STOCK_COPY, N_("Cop_y"), NULL, "Copy", G_CALLBACK(terminal_copy_activate_event) }, + { "Paste", GTK_STOCK_PASTE, N_("_Paste"), NULL, "Paste", G_CALLBACK(terminal_paste_activate_event) }, + { "Sep2", NULL, "Sep" }, + { "Preferences", GTK_STOCK_EXECUTE, N_("Preference_s"), NULL, "Preferences", G_CALLBACK(terminal_preferences_dialog) }, + { "Sep3", NULL, "Sep" }, + { "NameTab", GTK_STOCK_INFO, N_("Na_me Tab"), NULL, "Name Tab", G_CALLBACK(terminal_name_tab_activate_event) }, + { "PreviousTab", GTK_STOCK_GO_BACK, N_("Pre_vious Tab"), NULL, "Previous Tab", G_CALLBACK(terminal_previous_tab_activate_event) }, + { "NextTab", GTK_STOCK_GO_FORWARD, N_("Ne_xt Tab"), NULL, "Next Tab", G_CALLBACK(terminal_next_tab_activate_event) }, + { "Tabs_MoveTabLeft", NULL, N_("Move Tab _Left"), NULL, "Move Tab Left", G_CALLBACK(terminal_move_tab_left_activate_event) }, + { "Tabs_MoveTabRight", NULL, N_("Move Tab _Right"), NULL, "Move Tab Right", G_CALLBACK(terminal_move_tab_right_activate_event) }, + { "CloseTab", GTK_STOCK_CLOSE, N_("_Close Tab"), NULL, "Close Tab", G_CALLBACK(terminal_close_tab_activate_event) } +}; +#define VTE_MENUITEM_COUNT G_N_ELEMENTS(vte_menu_items) -static void terminal_window_set_fixed_size(LXTerminal *terminal) +/* Copied out of static function in gdk_window. + * A wrapper around XGetWMNormalHints. */ +static void gdk_window_get_geometry_hints(GdkWindow * window, GdkGeometry * geometry, GdkWindowHints * geometry_mask) { - Term *term; - gint i; + g_return_if_fail(GDK_IS_WINDOW (window)); + g_return_if_fail(geometry != NULL); + g_return_if_fail(geometry_mask != NULL); - terminal->fixedsize = TRUE; + *geometry_mask = 0; - for (i=0;i<terminal->terms->len;i++) { - term = g_ptr_array_index(terminal->terms, i); + if (GDK_WINDOW_DESTROYED(window)) + return; - gtk_window_set_geometry_hints(GTK_WINDOW(terminal->mainw), - term->vte, - &terminal->geometry, - terminal->geom_mask); - } -} + XSizeHints size_hints; + glong junk_size_mask = 0; + if ( ! XGetWMNormalHints(GDK_WINDOW_XDISPLAY(window), GDK_WINDOW_XID(window), &size_hints, &junk_size_mask)) + return; -static gboolean term_switchtab(Term *term) -{ - LXTerminal *terminal = term->parent; - gtk_notebook_set_current_page(GTK_NOTEBOOK(terminal->notebook), term->index); - return TRUE; -} + if (size_hints.flags & PMinSize) + { + *geometry_mask |= GDK_HINT_MIN_SIZE; + geometry->min_width = size_hints.min_width; + geometry->min_height = size_hints.min_height; + } -static gboolean terminal_copy(GtkAction *action, gpointer data) -{ - LXTerminal *terminal = (LXTerminal *)data; - Term *term; + if (size_hints.flags & PMaxSize) + { + *geometry_mask |= GDK_HINT_MAX_SIZE; + geometry->max_width = MAX (size_hints.max_width, 1); + geometry->max_height = MAX (size_hints.max_height, 1); + } - /* getting current vte */ - term = g_ptr_array_index(terminal->terms, gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook))); + if (size_hints.flags & PResizeInc) + { + *geometry_mask |= GDK_HINT_RESIZE_INC; + geometry->width_inc = size_hints.width_inc; + geometry->height_inc = size_hints.height_inc; + } - /* copy from vte */ - vte_terminal_copy_clipboard(VTE_TERMINAL(term->vte)); + if (size_hints.flags & PAspect) + { + *geometry_mask |= GDK_HINT_ASPECT; + geometry->min_aspect = (gdouble) size_hints.min_aspect.x / (gdouble) size_hints.min_aspect.y; + geometry->max_aspect = (gdouble) size_hints.max_aspect.x / (gdouble) size_hints.max_aspect.y; + } - return TRUE; + if (size_hints.flags & PWinGravity) + { + *geometry_mask |= GDK_HINT_WIN_GRAVITY; + geometry->win_gravity = size_hints.win_gravity; + } } -static gboolean terminal_copy_accel(gpointer data, guint action, GtkWidget *item) +/* Accessor for border values from VteTerminal. */ +static GtkBorder * terminal_get_border(Term * term) { - return terminal_copy(NULL, data); +#if VTE_CHECK_VERSION(0, 24, 0) + /* Style property, new in 0.24.0, replaces the function below. */ + GtkBorder * border; + gtk_widget_style_get(term->vte, "inner-border", &border, NULL); + return gtk_border_copy(border); +#else + /* Deprecated function produces a warning. */ + GtkBorder * border = gtk_border_new(); + vte_terminal_get_padding(VTE_TERMINAL(term->vte), &border->left, &border->top); + return border; +#endif } -static gboolean terminal_paste(GtkAction *action, gpointer data) +/* Restore the terminal geometry after a font size change or hiding the tab bar. */ +static void terminal_geometry_restore(Term * term) { - LXTerminal *terminal = (LXTerminal *)data; - Term *term; - - /* getting current vte */ - term = g_ptr_array_index(terminal->terms, gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook))); - - /* copy from vte */ - vte_terminal_paste_clipboard(VTE_TERMINAL(term->vte)); + /* Set fixed VTE size. */ + terminal_window_set_fixed_size(term->parent); + + /* Recover the window size. */ + GtkBorder * border = terminal_get_border(term); + vte_terminal_set_size(VTE_TERMINAL(term->vte), + vte_terminal_get_column_count(VTE_TERMINAL(term->vte)), + vte_terminal_get_row_count(VTE_TERMINAL(term->vte))); + gtk_window_resize(GTK_WINDOW(term->parent->window), + border->left + VTE_TERMINAL(term->vte)->char_width, + border->top + VTE_TERMINAL(term->vte)->char_height); + gtk_border_free(border); +} - return TRUE; +/* Set the position of the tabs on the main window. */ +static void terminal_tab_set_position(GtkWidget * notebook, gint tab_position) +{ + switch (tab_position) + { + case 0: + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP); + break; + case 1: + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_BOTTOM); + break; + case 2: + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_LEFT); + break; + default: + gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_RIGHT); + break; + } } -static gboolean terminal_paste_accel(gpointer data, guint action, GtkWidget *item) +/* Initialize the <ALT> n accelerators, where n is a digit. + * These switch to the tab selected by the digit, if it exists. */ +static void terminal_initialize_switch_tab_accelerator(Term * term) { - return terminal_paste(NULL, data); + if ((term->index + 1) < 10) + { + /* Formulate the accelerator name. */ + char switch_tab_accel[1 + 3 + 1 + 1 + 1]; /* "<ALT>n" */ + sprintf(switch_tab_accel, "<ALT>%d", term->index + 1); + + /* Parse the accelerator name. */ + guint key; + GdkModifierType mods; + gtk_accelerator_parse(switch_tab_accel, &key, &mods); + + /* Define the accelerator. */ + term->closure = g_cclosure_new_swap(G_CALLBACK(terminal_switch_tab_accelerator), term, NULL); + gtk_accel_group_connect(term->parent->accel_group, key, mods, GTK_ACCEL_LOCKED, term->closure); + } } -static void terminal_nexttab(GtkAction *action, gpointer data) +/* Handler for accelerator <ALT> n, where n is a digit. + * Switch to the tab selected by the digit, if it exists. */ +static void terminal_switch_tab_accelerator(Term * term) { - LXTerminal *terminal = (LXTerminal *)data; + LXTerminal * terminal = term->parent; + if (term->index < gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook))) + gtk_notebook_set_current_page(GTK_NOTEBOOK(terminal->notebook), term->index); +} - /* cycle */ - if (gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook))==gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook))-1) - gtk_notebook_set_current_page(GTK_NOTEBOOK(terminal->notebook), 0); - else - gtk_notebook_next_page(GTK_NOTEBOOK(terminal->notebook)); +/* Handler for "activate" signal on File/New Window menu item. + * Open a new window. */ +static void terminal_new_window_activate_event(GtkAction * action, LXTerminal * terminal) +{ + CommandArguments arguments; + memset(&arguments, 0, sizeof(arguments)); + lxterminal_initialize(terminal->parent, &arguments, terminal->setting); } -static void terminal_nexttab_accel(gpointer data, guint action, GtkWidget *item) +/* Handler for accelerator <SHIFT><CTRL> N. Open a new window. */ +static void terminal_new_window_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) { - terminal_nexttab(NULL, data); + terminal_new_window_activate_event(NULL, terminal); } -static void terminal_prevtab(GtkAction *action, gpointer data) +/* Handler for "activate" signal on File/New Tab menu item. + * Open a new tab. */ +static void terminal_new_tab_activate_event(GtkAction * action, LXTerminal * terminal) { - LXTerminal *terminal = (LXTerminal *)data; + gchar * proc_cwd = NULL; + +#ifdef __linux + /* Try to get the working directory from /proc. */ + gint current = gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook)); + if (current != -1) + { + /* Search for the Term structure corresponding to the current tab. */ + int i; + for (i = 0; i < terminal->terms->len; i += 1) + { + Term * term = g_ptr_array_index(terminal->terms, i); + if (term->index == current) + { + /* Get the working directory corresponding to the process ID. */ + gchar proc_cwd_link[PATH_MAX]; + g_snprintf(proc_cwd_link, PATH_MAX, "/proc/%d/cwd", term->pid); + proc_cwd = g_file_read_link(proc_cwd_link, NULL); + break; + } + } + + } +#endif - /* cycle */ - if (gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook))==0) - gtk_notebook_set_current_page(GTK_NOTEBOOK(terminal->notebook), -1); - else - gtk_notebook_prev_page(GTK_NOTEBOOK(terminal->notebook)); + /* Propagate the working directory of the current tab to the new tab. + * If the working directory was determined above, use it; otherwise default to the working directory of the process. + * Create the new terminal. */ + if (proc_cwd == NULL) + proc_cwd = g_get_current_dir(); + Term * term = terminal_new(terminal, _("LXTerminal"), proc_cwd, NULL, NULL); + g_free(proc_cwd); + + /* Add a tab to the notebook and the "terms" array. */ + gtk_notebook_append_page(GTK_NOTEBOOK(terminal->notebook), term->box, term->tab); + term->index = gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook)) - 1; + g_ptr_array_add(terminal->terms, term); + + /* Redraw the terminal. */ + gtk_widget_queue_draw(term->vte); + + /* Make the new terminal's tab current and, if we went from one to more than one tab, + * turn the tab display on. */ + gtk_notebook_set_current_page(GTK_NOTEBOOK(term->parent->notebook), term->index); + if (term->index > 0) + { + terminal_window_set_fixed_size(terminal); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(term->parent->notebook), TRUE); + } + terminal_initialize_switch_tab_accelerator(term); } -static void terminal_prevtab_accel(gpointer data, guint action, GtkWidget *item) +/* Handler for accelerator <SHIFT><CTRL> T. Open a new tab. */ +static void terminal_new_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) { - terminal_prevtab(NULL, data); + terminal_new_tab_activate_event(NULL, terminal); } -/* direction: -1=left 1=right */ -static void terminal_movetab(GtkAction *action, gpointer data, gint direction) +/* Handler for "activate" signal on File/Close Tab menu item. + * Close the current tab. */ +static void terminal_close_tab_activate_event(GtkAction * action, LXTerminal * terminal) { - LXTerminal *terminal = (LXTerminal *)data; - GtkNotebook *notebook = GTK_NOTEBOOK(terminal->notebook); - gint curPageNum = gtk_notebook_get_current_page( notebook ); - - gtk_notebook_reorder_child( - notebook, - gtk_notebook_get_nth_page(notebook, curPageNum), - curPageNum + direction - ); + Term * term = g_ptr_array_index(terminal->terms, gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook))); + terminal_child_exited_event(VTE_TERMINAL(term->vte), term); } -static void terminal_movetableft(GtkAction *action, gpointer data) +/* Handler for accelerator <SHIFT><CTRL> W. Close the current tab. */ +static void terminal_close_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) { - terminal_movetab(action, data, -1); + terminal_close_tab_activate_event(NULL, terminal); } -static void terminal_movetableft_accel(gpointer data, guint action, GtkWidget *item) +/* Handler for "activate" signal on Edit/Copy menu item. + * Copy to the clipboard. */ +static void terminal_copy_activate_event(GtkAction * action, LXTerminal * terminal) { - terminal_movetableft(NULL, data); + Term * term = g_ptr_array_index(terminal->terms, gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook))); + vte_terminal_copy_clipboard(VTE_TERMINAL(term->vte)); } -static void terminal_movetabright(GtkAction *action, gpointer data) +/* Handler for accelerator <CTRL><SHIFT> C. Copy to the clipboard. */ +static void terminal_copy_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) { - terminal_movetab(action, data, 1); + terminal_copy_activate_event(NULL, terminal); } -static void terminal_movetabright_accel(gpointer data, guint action, GtkWidget *item) +/* Handler for "activate" signal on Edit/Paste menu item. + * Paste from the clipboard. */ +static void terminal_paste_activate_event(GtkAction * action, LXTerminal * terminal) { - terminal_movetabright(NULL, data); + Term * term = g_ptr_array_index(terminal->terms, gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook))); + vte_terminal_paste_clipboard(VTE_TERMINAL(term->vte)); } -static void terminal_closetab(GtkAction *action, gpointer data) +/* Handler for accelerator <CTRL><SHIFT> V. Paste from the clipboard. */ +static void terminal_paste_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) { - LXTerminal *terminal = (LXTerminal *)data; - Term *term; - - /* getting current vte */ - term = g_ptr_array_index(terminal->terms, gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook))); - - /* release child */ - terminal_childexit(VTE_TERMINAL(term->vte), term); + terminal_paste_activate_event(NULL, terminal); } -static void terminal_closetab_accel(gpointer data, guint action, GtkWidget *item) +/* Handler for "response" signal on Name Tab dialog. */ +static void terminal_name_tab_response_event(GtkWidget * dialog, gint response, LXTerminal * terminal) { - terminal_closetab(NULL, data); + if (response == GTK_RESPONSE_OK) + { + GtkWidget * dialog_item = g_object_get_data(G_OBJECT(dialog), "entry"); + + gint current = gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook)); + if (current != -1) + { + /* Search for the Term structure corresponding to the current tab. */ + int i; + for (i = 0; i < terminal->terms->len; i += 1) + { + Term * term = g_ptr_array_index(terminal->terms, i); + if (term->index == current) + { + /* If Term structure found, set the tab's label and mark it so we will never overwrite it. */ + term->user_specified_label = TRUE; + gtk_label_set_text(GTK_LABEL(term->label), g_strdup(gtk_entry_get_text(GTK_ENTRY(dialog_item)))); + break; + } + } + } + } + + /* Dismiss dialog. */ + gtk_widget_destroy(dialog); } -static void terminal_newwindow(GtkAction *action, gpointer data) +/* Handler for "activate" signal on Tabs/Name Tab menu item. + * Put up a dialog to get a user specified name for the tab. */ +static void terminal_name_tab_activate_event(GtkAction * action, LXTerminal * terminal) { - LXTerminal *terminal = (LXTerminal *)data; - - lxterminal_init(terminal->parent, 0, NULL, terminal->setting); + GtkWidget * dialog = gtk_dialog_new_with_buttons( + _("Name Tab"), + GTK_WINDOW(terminal->window), + 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + gtk_window_set_icon_from_file(GTK_WINDOW(dialog), PACKAGE_DATA_DIR "/pixmaps/lxterminal.png", NULL); + g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(terminal_name_tab_response_event), terminal); + GtkWidget * dialog_item = gtk_entry_new(); + g_object_set_data(G_OBJECT(dialog), "entry", (gpointer) dialog_item); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), dialog_item, FALSE, FALSE, 2); + gint current = gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook)); + if (current != -1) + { + /* Search for the Term structure corresponding to the current tab. */ + int i; + for (i = 0; i < terminal->terms->len; i += 1) + { + Term * term = g_ptr_array_index(terminal->terms, i); + if (term->index == current) + { + gtk_entry_set_text(GTK_ENTRY(dialog_item), gtk_label_get_text(GTK_LABEL(term->label))); + break; + } + } + } + gtk_widget_show_all(dialog); } -static void terminal_newwindow_accel(gpointer data, guint action, GtkWidget *item) +/* Handler for accelerator <CTRL><SHIFT> R. Name the tab. */ +static void terminal_name_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) { - terminal_newwindow(NULL, data); + terminal_name_tab_activate_event(NULL, terminal); } -static void term_set_swicth_accel(Term *term) +/* Handler for "activate" signal on Tabs/Previous Tab menu item. + * Cycle through tabs in the reverse direction. */ +static void terminal_previous_tab_activate_event(GtkAction * action, LXTerminal * terminal) { - if (term->index + 1 < 10) { - char switch_tab_accel[1 + 3 + 1 + 1 + 1]; /* "<ALT>n" */ - guint key; - GdkModifierType mods; - GtkAccelGroup *accel_group = term->parent->menubar->accel_group; - - sprintf(switch_tab_accel, "<ALT>%d", term->index + 1); - gtk_accelerator_parse(switch_tab_accel, &key, &mods); - term->closure = g_cclosure_new_swap(G_CALLBACK(term_switchtab), term, NULL); - gtk_accel_group_connect(accel_group, key, mods, GTK_ACCEL_LOCKED, - term->closure); - } + /* Cycle through tabs. */ + if (gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook)) == 0) + gtk_notebook_set_current_page(GTK_NOTEBOOK(terminal->notebook), -1); + else + gtk_notebook_prev_page(GTK_NOTEBOOK(terminal->notebook)); } -static void terminal_newtab(GtkWidget *widget, gpointer data) +/* Handler for accelerator <CTRL><PAGE UP>. Cycle through tabs in the reverse direction. */ +static void terminal_previous_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) { - LXTerminal *terminal = (LXTerminal *)data; - Term *term; - -#ifdef __linux - gchar cwd[PATH_MAX]; - - gint current = gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook)); - if (current != -1) { - gchar proc_cwd_link[PATH_MAX]; - gchar *proc_cwd; - gint i; - for (i=0;i<terminal->terms->len;i++) { - term = g_ptr_array_index(terminal->terms, i); - if (term->index == current) - break; - } - - g_message("%d", term->pid); - g_snprintf(proc_cwd_link, PATH_MAX, "/proc/%d/cwd", term->pid); - proc_cwd = g_file_read_link(proc_cwd_link, NULL); - if (proc_cwd) { - g_strlcpy(cwd, proc_cwd, PATH_MAX); - g_free(proc_cwd); - } else { - g_strlcpy(cwd, g_get_current_dir(), PATH_MAX); - } - } else { - g_strlcpy(cwd, g_get_current_dir(), PATH_MAX); - } - - term = terminal_new(terminal, _("LXTerminal"), cwd, NULL, NULL); -#else - term = terminal_new(terminal, _("LXTerminal"), g_get_current_dir(), NULL, NULL); -#endif + terminal_previous_tab_activate_event(NULL, terminal); +} - /* add page to notebook */ - gtk_notebook_append_page(GTK_NOTEBOOK(terminal->notebook), term->box, term->label->main); - term->index = gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook)) - 1; - g_ptr_array_add(terminal->terms, term); +/* Handler for "activate" signal on Tabs/Next Tab menu item. + * Cycle through tabs in the forward direction. */ +static void terminal_next_tab_activate_event(GtkAction * action, LXTerminal * terminal) +{ + /* Cycle through tabs. */ + if (gtk_notebook_get_current_page(GTK_NOTEBOOK(terminal->notebook)) == gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook)) - 1) + gtk_notebook_set_current_page(GTK_NOTEBOOK(terminal->notebook), 0); + else + gtk_notebook_next_page(GTK_NOTEBOOK(terminal->notebook)); +} - /* push VTE to queue */ - gtk_widget_queue_draw(term->vte); +/* Handler for accelerator <CTRL><PAGE DOWN>. Cycle through tabs in the forward direction. */ +static void terminal_next_tab_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) +{ + terminal_next_tab_activate_event(NULL, terminal); +} - gtk_notebook_set_current_page(GTK_NOTEBOOK(term->parent->notebook), term->index); +/* Helper for move tab left and right. */ +static void terminal_move_tab_execute(LXTerminal * terminal, gint direction) +{ + GtkNotebook * notebook = GTK_NOTEBOOK(terminal->notebook); + gint current_page_number = gtk_notebook_get_current_page(notebook); + gtk_notebook_reorder_child(notebook, gtk_notebook_get_nth_page(notebook, current_page_number), current_page_number + direction); +} - if (term->index > 0) { - /* fixed VTE size */ - terminal_window_set_fixed_size(terminal); +/* Handler for "activate" signal on Tabs/Move Tab Left menu item. + * Move the tab one position in the reverse direction. */ +static void terminal_move_tab_left_activate_event(GtkAction * action, LXTerminal * terminal) +{ + terminal_move_tab_execute(terminal, -1); +} - /* show tab */ - gtk_notebook_set_show_tabs(GTK_NOTEBOOK(term->parent->notebook), TRUE); - } - term_set_swicth_accel(term); +/* Handler for accelerator <SHIFT><CTRL><PAGE UP>. Move the tab one position in the reverse direction. */ +static void terminal_move_tab_left_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) +{ + terminal_move_tab_execute(terminal, -1); } -static void terminal_newtab_accel(gpointer data, guint action, GtkWidget *item) +/* Handler for "activate" signal on Tabs/Move Tab Right menu item. + * Move the tab one position in the forward direction. */ +static void terminal_move_tab_right_activate_event(GtkAction * action, LXTerminal * terminal) { - terminal_newtab(NULL, data); + terminal_move_tab_execute(terminal, 1); } -static void open_url( GtkAboutDialog* dlg, const gchar* url, gpointer data ) +/* Handler for accelerator <SHIFT><CTRL><PAGE DOWN>. Move the tab one position in the forward direction. */ +static void terminal_move_tab_right_accelerator(LXTerminal * terminal, guint action, GtkWidget * item) { - /* FIXME: */ + terminal_move_tab_execute(terminal, 1); } -static void terminal_about(GtkAction *action, gpointer data) +/* Handler for "activate" signal on Help/About menu item. */ +static void terminal_about_activate_event(GtkAction * action, LXTerminal * terminal) { - GtkWidget * about_dlg; - const gchar *authors[] = + const gchar * authors[] = { "Fred Chien <cfsghost@gmail.com>", + "Marty Jack <martyj19@comcast.net>", NULL }; /* TRANSLATORS: Replace this 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 ); - gtk_about_dialog_set_name ( (GtkAboutDialog*)about_dlg, _( "LXTerminal" ) ); - gtk_about_dialog_set_logo( (GtkAboutDialog*)about_dlg, gdk_pixbuf_new_from_file( PACKAGE_DATA_DIR"/pixmaps/lxterminal.png", NULL ) ); - gtk_about_dialog_set_copyright ( (GtkAboutDialog*)about_dlg, _( "Copyright (C) 2008" ) ); - gtk_about_dialog_set_comments ( (GtkAboutDialog*)about_dlg, _( "Terminal emulator for LXDE project" ) ); - 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\nThis 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 this 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_dialog_run( ( GtkDialog*)about_dlg ); - gtk_widget_destroy( about_dlg ); + gchar * translators = _("translator-credits"); + + /* Create and initialize the dialog. */ + GtkWidget * about_dlg = gtk_about_dialog_new(); + gtk_about_dialog_set_url_hook(NULL, NULL, NULL); + gtk_container_set_border_width(GTK_CONTAINER(about_dlg), 2); + gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about_dlg), VERSION); + gtk_about_dialog_set_name (GTK_ABOUT_DIALOG(about_dlg), _("LXTerminal")); + gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about_dlg), gdk_pixbuf_new_from_file(PACKAGE_DATA_DIR "/pixmaps/lxterminal.png", NULL)); + gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(about_dlg), _("Copyright (C) 2008-2010")); + gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(about_dlg), _("Terminal emulator for LXDE project")); + gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(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\nThis 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 this 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(GTK_ABOUT_DIALOG(about_dlg), "http://lxde.org/"); + gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(about_dlg), authors); + gtk_about_dialog_set_translator_credits(GTK_ABOUT_DIALOG(about_dlg), translators); + + /* Display the dialog, wait for the user to click OK, and dismiss the dialog. */ + gtk_dialog_run(GTK_DIALOG(about_dlg)); + gtk_widget_destroy(about_dlg); } -static GtkActionEntry menu_items[] = +/* Handler for "size-request" signal on the top level window. */ +static gboolean terminal_window_size_request_event(GtkWidget * widget, GtkRequisition * requisition, LXTerminal * terminal) { - { "File_NewWindow", GTK_STOCK_ADD, N_("New _Window"), NEW_WINDOW_ACCEL, "New Window", G_CALLBACK(terminal_newwindow)}, - { "File_NewTab", GTK_STOCK_ADD, N_("New _Tab"), NEW_TAB_ACCEL, "New Tab", G_CALLBACK(terminal_newtab)}, - { "File_Sep1", NULL, "Sep" }, - { "File_CloseTab", GTK_STOCK_CLOSE, N_("_Close Tab"), CLOSE_TAB_ACCEL, "Close Tab", G_CALLBACK(terminal_closetab)}, - { "File_Quit", GTK_STOCK_QUIT, N_("_Quit"), QUIT_ACCEL, "Quit", G_CALLBACK(gtk_main_quit)}, - { "Edit_Copy", GTK_STOCK_COPY, N_("Cop_y"), COPY_ACCEL, "Copy", G_CALLBACK(terminal_copy)}, - { "Edit_Paste", GTK_STOCK_PASTE, N_("_Paste"), PASTE_ACCEL, "Paste", G_CALLBACK(terminal_paste)}, - { "Edit_Sep1", NULL, "Sep" }, - { "Edit_Preferences", GTK_STOCK_EXECUTE, N_("Preference_s"), NULL, "Preferences", G_CALLBACK(lxterminal_preferences_dialog)}, -// { "View_CharacterEncoding", NULL, N_("_Character Encoding"), NULL, "Character Encoding", NULL}, - { "Tabs_PreviousTab", GTK_STOCK_GO_BACK, N_("Pre_vious Tab"), PREVIOUS_TAB_ACCEL, "Previous Tab", G_CALLBACK(terminal_prevtab)}, - { "Tabs_NextTab", GTK_STOCK_GO_FORWARD, N_("Ne_xt Tab"), NEXT_TAB_ACCEL, "Next Tab", G_CALLBACK(terminal_nexttab)}, - { "Tabs_MoveTabLeft", NULL, N_("Move Tab _Left"), MOVE_TAB_LEFT_ACCEL, "Move Tab Left", G_CALLBACK(terminal_movetableft)}, - { "Tabs_MoveTabRight", NULL, N_("Move Tab _Right"), MOVE_TAB_RIGHT_ACCEL, "Move Tab Right", G_CALLBACK(terminal_movetabright)}, - { "Help_About", GTK_STOCK_ABOUT, N_("_About"), NULL, "About", G_CALLBACK(terminal_about)} -}; -#define MENUBAR_MENUITEM_COUNT G_N_ELEMENTS(menu_items) + /* Only do this once. */ + if (terminal->fixed_size) + { + /* No longer fixed size. */ + terminal->fixed_size = FALSE; + + /* Initialize geometry hints structure. */ + Term * term = g_ptr_array_index(terminal->terms, 0); + GtkBorder * border = terminal_get_border(term); + GdkGeometry hints; + hints.width_inc = VTE_TERMINAL(term->vte)->char_width; + hints.height_inc = VTE_TERMINAL(term->vte)->char_height; + hints.base_width = border->left; + hints.base_height = border->top; + hints.min_width = hints.base_width + hints.width_inc * 4; + hints.min_height = hints.base_height + hints.height_inc * 2; + gtk_border_free(border); + + /* Set hints into all terminals. This makes sure we resize on character cell boundaries. */ + int i; + for (i = 0; i < terminal->terms->len; i += 1) + { + Term * term = g_ptr_array_index(terminal->terms, i); + gtk_window_set_geometry_hints(GTK_WINDOW(terminal->window), + term->vte, + &hints, + GDK_HINT_RESIZE_INC | GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE); + } + + /* Resize the window. */ + gtk_window_resize(GTK_WINDOW(terminal->window), requisition->width, requisition->height); + } + return FALSE; +} -static GtkActionEntry vte_menu_items[] = +/* Set geometry hints for the fixed size case. */ +static void terminal_window_set_fixed_size(LXTerminal * terminal) { - { "VTEMenu", NULL, "VTEMenu" }, - { "NewWindow", GTK_STOCK_ADD, N_("New _Window"), NULL, "New Window", G_CALLBACK(terminal_newwindow)}, - { "NewTab", GTK_STOCK_ADD, N_("New _Tab"), NULL, "New Tab", G_CALLBACK(terminal_newtab)}, - { "Sep1", NULL, "Sep" }, - { "Copy", GTK_STOCK_COPY, N_("Cop_y"), NULL, "Copy", G_CALLBACK(terminal_copy)}, - { "Paste", GTK_STOCK_PASTE, N_("_Paste"), NULL, "Paste", G_CALLBACK(terminal_paste)}, - { "Sep2", NULL, "Sep" }, - { "Preferences", GTK_STOCK_EXECUTE, N_("Preference_s"), NULL, "Preferences", G_CALLBACK(lxterminal_preferences_dialog)}, - { "Sep3", NULL, "Sep" }, - { "PreviousTab", GTK_STOCK_GO_BACK, N_("Pre_vious Tab"), NULL, "Previous Tab", G_CALLBACK(terminal_prevtab)}, - { "NextTab", GTK_STOCK_GO_FORWARD, N_("Ne_xt Tab"), NULL, "Next Tab", G_CALLBACK(terminal_nexttab)}, - { "Tabs_MoveTabLeft", NULL, N_("Move Tab _Left"), NULL, "Move Tab Left", G_CALLBACK(terminal_prevtab)}, - { "Tabs_MoveTabRight", NULL, N_("Move Tab _Right"), NULL, "Move Tab Right", G_CALLBACK(terminal_nexttab)}, - { "CloseTab", GTK_STOCK_CLOSE, N_("_Close Tab"), NULL, "Close Tab", G_CALLBACK(terminal_closetab)} -}; -#define VTE_MENUITEM_COUNT G_N_ELEMENTS(vte_menu_items) + terminal->fixed_size = TRUE; + int i; + for (i = 0; i < terminal->terms->len; i += 1) + { + Term * term = g_ptr_array_index(terminal->terms, i); + gtk_window_set_geometry_hints(GTK_WINDOW(terminal->window), + term->vte, + &terminal->geometry, + terminal->geometry_mask); + } +} -static void terminal_switch_tab(GtkNotebook *notebook, GtkNotebookPage *page, guint num, gpointer data) +/* Handler for "switch-page" event on the tab notebook. */ +static void terminal_switch_page_event(GtkNotebook * notebook, GtkNotebookPage * page, guint num, LXTerminal * terminal) { - const gchar *title; - Term *term; - LXTerminal *terminal = (LXTerminal *)data; - - if (terminal->terms->len <= num) - return; - - term = g_ptr_array_index(terminal->terms, num); - - /* if title of VTE is NULL */ - if ((title = vte_terminal_get_window_title(VTE_TERMINAL(term->vte))) == NULL) - gtk_window_set_title(GTK_WINDOW(terminal->mainw), _("LXTerminal")); - else - gtk_window_set_title(GTK_WINDOW(terminal->mainw), title); + if (terminal->terms->len > num) + { + /* Propagate the title to the toplevel window. */ + Term * term = g_ptr_array_index(terminal->terms, num); + const gchar * title = vte_terminal_get_window_title(VTE_TERMINAL(term->vte)); + gtk_window_set_title(GTK_WINDOW(terminal->window), ((title != NULL) ? title : _("LXTerminal"))); + } } -static void terminal_title_changed(VteTerminal *vte, Term *term) +/* Handler for "window-title-changed" signal on a Term. */ +static void terminal_window_title_changed_event(GtkWidget * vte, Term * term) { - /* setting label */ - lxterminal_tab_label_set_text(term->label, vte_terminal_get_window_title(VTE_TERMINAL(vte))); - lxterminal_tab_label_set_tooltip_text(term->label, vte_terminal_get_window_title(VTE_TERMINAL(vte))); - - /* setting window title */ - gtk_window_set_title(GTK_WINDOW(term->parent->mainw), vte_terminal_get_window_title(VTE_TERMINAL(vte))); + /* Copy the VTE data out into the tab and the window title, unless the user edited the tab label. */ + if ( ! term->user_specified_label) + { + gtk_label_set_text(GTK_LABEL(term->label), vte_terminal_get_window_title(VTE_TERMINAL(vte))); + gtk_widget_set_tooltip_text(term->label, vte_terminal_get_window_title(VTE_TERMINAL(vte))); + } + gtk_window_set_title(GTK_WINDOW(term->parent->window), vte_terminal_get_window_title(VTE_TERMINAL(vte))); } -static void terminal_windowexit(gpointer terminal_p, GObject *where_the_object_was) +/* Weak-notify callback for LXTerminal object. */ +static void terminal_window_exit(LXTerminal * terminal, GObject * where_the_object_was) { - LXTerminal * terminal = (LXTerminal *) terminal_p; + /* If last window, exit main loop. */ + if (terminal->parent->windows->len == 1) + gtk_main_quit(); + + else + { + /* Remove the element and decrease the index number of each succeeding element. */ + g_ptr_array_remove_index(terminal->parent->windows, terminal->index); int i; + for (i = terminal->index; i < terminal->parent->windows->len; i += 1) + { + LXTerminal * t = g_ptr_array_index(terminal->parent->windows, i); + t->index -= 1; + } + } +} - if (terminal->parent->windows->len==1) { - gtk_main_quit(); - } else { - g_ptr_array_remove_index(terminal->parent->windows, terminal->index); +/* Handler for "child-exited" signal on VTE. + * Also handler for "activate" signal on Close button of tab and File/Close Tab menu item and accelerator. */ +static void terminal_child_exited_event(VteTerminal * vte, Term * term) +{ + LXTerminal * terminal = term->parent; - /* decreasing index number after the window be removed */ - for (i=terminal->index;i<terminal->parent->windows->len;i++) { - LXTerminal *t = g_ptr_array_index(terminal->parent->windows, i); - t->index--; - } + /* Last tab being deleted. Deallocate memory and close the window. */ + if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook)) == 1) + { + g_ptr_array_free(terminal->terms, TRUE); + gtk_widget_destroy(terminal->window); + } - } + /* Not last tab being deleted. */ + else + { + /* Remove the element and decrease the index number of each succeeding element. */ + g_ptr_array_remove_index(terminal->terms, term->index); + int i; + for (i = term->index; i < terminal->terms->len; i++) + { + Term * t = g_ptr_array_index(terminal->terms, i); + t->index -= 1; + } + + /* Delete the tab and free the Term structure. */ + gtk_notebook_remove_page(GTK_NOTEBOOK(terminal->notebook), term->index); + terminal_free(term); + + /* If only one page is left, hide the tab and correct the geometry. */ + if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook)) == 1) + { + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(terminal->notebook), FALSE); + terminal_geometry_restore(g_ptr_array_index(terminal->terms, 0)); + } + } } -static void terminal_childexit(VteTerminal *vte, Term *term) +/* Handler for "button-press-event" signal on a notebook tab. */ +static gboolean terminal_tab_button_press_event(GtkWidget * widget, GdkEventButton * event, Term * term) { - int i; - LXTerminal *terminal = term->parent; - - if(gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook))==1) { - /* release */ - g_ptr_array_free(terminal->terms, TRUE); - - /* quit */ - gtk_widget_destroy(terminal->mainw); - } else { - /* remove page */ - g_ptr_array_remove_index(terminal->terms, term->index); - - /* decreasing index number after the page be removed */ - for (i=term->index;i<terminal->terms->len;i++) { - Term *t = g_ptr_array_index(terminal->terms, i); - t->index--; - } - - /* release */ - gtk_notebook_remove_page(GTK_NOTEBOOK(terminal->notebook), term->index); - terminal_free(term); - - /* if only one page, hide tab */ - if (gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook)) == 1) { - gint xpad; - gint ypad; - gint cols; - gint rows; - - /* get original info of VTE */ - Term *t = g_ptr_array_index(terminal->terms, 0); - vte_terminal_get_padding(VTE_TERMINAL(t->vte), &xpad, &ypad); - cols = vte_terminal_get_column_count(VTE_TERMINAL(t->vte)); - rows = vte_terminal_get_row_count(VTE_TERMINAL(t->vte)); - - /* fixed VTE size */ - terminal_window_set_fixed_size(terminal); - - /* hide tab */ - gtk_notebook_set_show_tabs(GTK_NOTEBOOK(terminal->notebook), FALSE); - - /* recovery window size */ - vte_terminal_set_size(VTE_TERMINAL(t->vte), cols, rows); - gtk_window_resize(GTK_WINDOW(terminal->mainw), - xpad + VTE_TERMINAL(t->vte)->char_width, - ypad + VTE_TERMINAL(t->vte)->char_height); - } - } + if (event->button == 2) + { + /* Middle click closes the tab. */ + terminal_child_exited_event(NULL, term); + return TRUE; + } + return FALSE; } -static gboolean terminal_vte_button_press(VteTerminal *vte, GdkEventButton *event, gpointer data) +/* Handler for "button-press-event" signal on VTE. */ +static gboolean terminal_vte_button_press_event(VteTerminal * vte, GdkEventButton * event, Term * term) { - LXTerminal *terminal = (LXTerminal *)data; - GtkWidget *menu_item; - GtkActionGroup *action_group; - GtkUIManager *manager; - guint merge_id; - gint i; - - /* right-click */ - if (event->button == 3) { - /* initializing UI manager */ - manager = gtk_ui_manager_new(); - action_group = gtk_action_group_new("VTEMenu"); - gtk_action_group_set_translation_domain(action_group, GETTEXT_PACKAGE); - gtk_action_group_add_actions(action_group, vte_menu_items, VTE_MENUITEM_COUNT, terminal); - gtk_ui_manager_insert_action_group(manager, action_group, 0); - - merge_id = gtk_ui_manager_new_merge_id(manager); - - gtk_ui_manager_add_ui(manager, merge_id, "/", "VTEMenu", NULL, GTK_UI_MANAGER_POPUP, FALSE); - for (i=1;i<VTE_MENUITEM_COUNT;i++) { - if (strcmp(vte_menu_items[i].label, "Sep")==0) - gtk_ui_manager_add_ui(manager, merge_id, "/VTEMenu", vte_menu_items[i].name, NULL, GTK_UI_MANAGER_SEPARATOR, FALSE); - else - gtk_ui_manager_add_ui(manager, merge_id, "/VTEMenu", vte_menu_items[i].name, vte_menu_items[i].name, GTK_UI_MANAGER_MENUITEM, FALSE); - } - - gtk_menu_popup(GTK_MENU(gtk_ui_manager_get_widget(manager, "/VTEMenu")), NULL, NULL, NULL, NULL, event->button, event->time); - } else if (event->button == 1) { /* left click */ - /* steal from tilda-0.09.6/src/tilda_terminal.c:743 */ - gint tag; - gint xpad, ypad; - gchar* match; - gchar* cmd; - gboolean ret = FALSE; - - vte_terminal_get_padding(vte, &xpad, &ypad); - match = vte_terminal_match_check(vte, - (event->x - xpad) / vte->char_width, - (event->y - ypad) / vte->char_height, - &tag); - - /* Check if we can launch a web browser, and do so if possible */ - if ((event->state & GDK_CONTROL_MASK) && match != NULL) { -#if DEBUG - g_print("Got a Ctrl+Left Click -- Matched: `%s' (%d)\n", match, tag); -#endif - cmd = g_strdup_printf("%s %s", "xdg-open", match); -#if DEBUG - g_print("Launching command: `%s'\n", cmd); -#endif - ret = g_spawn_command_line_async(cmd, NULL); - - /* Check that the command launched */ - if (!ret) { - g_printerr(_("Failed to launch the web browser. The command was `%s'\n"), cmd); - } - - g_free(cmd); - } - - /* Always free match if it is non NULL */ - if (match) - g_free(match); - } - -#if 0 - GtkItemFactory *item_factory; - - /* right-click */ - if (event->button == 3) { - item_factory = gtk_item_factory_new(GTK_TYPE_MENU, "<main>", NULL); - gtk_item_factory_set_translate_func(item_factory, gettext, NULL, NULL); - gtk_item_factory_create_items(item_factory, sizeof(vte_menu_items) / sizeof(vte_menu_items[0]), vte_menu_items, data); - gtk_menu_popup(GTK_MENU(gtk_item_factory_get_widget(item_factory, "<main>")), NULL, NULL, NULL, NULL, event->button, event->time); - } -#endif - return FALSE; + /* Right-click. */ + if (event->button == 3) + { + /* Generate popup menu. */ + GtkUIManager * manager = gtk_ui_manager_new(); + GtkActionGroup * action_group = gtk_action_group_new("VTEMenu"); + gtk_action_group_set_translation_domain(action_group, GETTEXT_PACKAGE); + gtk_action_group_add_actions(action_group, vte_menu_items, VTE_MENUITEM_COUNT, term->parent); + gtk_ui_manager_insert_action_group(manager, action_group, 0); + + guint merge_id = gtk_ui_manager_new_merge_id(manager); + gtk_ui_manager_add_ui(manager, merge_id, "/", "VTEMenu", NULL, GTK_UI_MANAGER_POPUP, FALSE); + + int i; + for (i = 1; i < VTE_MENUITEM_COUNT; i += 1) + { + if (strcmp(vte_menu_items[i].label, "Sep") == 0) + gtk_ui_manager_add_ui(manager, merge_id, "/VTEMenu", vte_menu_items[i].name, NULL, GTK_UI_MANAGER_SEPARATOR, FALSE); + else gtk_ui_manager_add_ui(manager, merge_id, "/VTEMenu", vte_menu_items[i].name, vte_menu_items[i].name, GTK_UI_MANAGER_MENUITEM, FALSE); + } + gtk_menu_popup(GTK_MENU(gtk_ui_manager_get_widget(manager, "/VTEMenu")), NULL, NULL, NULL, NULL, event->button, event->time); + } + + /* Control left click. */ + else if ((event->button == 1) && (event->state & GDK_CONTROL_MASK)) + { + /* steal from tilda-0.09.6/src/tilda_terminal.c:743 + * See if the terminal has matched the regular expression. */ + GtkBorder * border = terminal_get_border(term); + gint tag; + gchar * match = vte_terminal_match_check(vte, + (event->x - border->left) / vte->char_width, + (event->y - border->top) / vte->char_height, + &tag); + gtk_border_free(border); + + /* Launch xdg-open with the match string. */ + if (match != NULL) + { + gchar * cmd = g_strdup_printf("xdg-open %s", match); + if ( ! g_spawn_command_line_async(cmd, NULL)) + g_warning("Failed to launch xdg-open. The command was `%s'\n", cmd); + g_free(cmd); + g_free(match); + } + } + return FALSE; } -void terminal_term_setting_update(Term *term, LXTerminal *terminal){ - vte_terminal_reset((VteTerminal *)term->vte, FALSE, FALSE); - - vte_terminal_set_font_from_string((VteTerminal *)term->vte, terminal->setting->fontname); - vte_terminal_set_word_chars((VteTerminal *)term->vte, terminal->setting->selchars); - vte_terminal_set_scrollback_lines((VteTerminal *)term->vte, terminal->setting->scrollback); - - vte_terminal_set_cursor_blink_mode((VteTerminal *)term->vte, terminal->setting->cursorblinks?VTE_CURSOR_BLINK_ON:VTE_CURSOR_BLINK_OFF); - - /* background transparency */ - if( terminal->rgba ){ - /* dirty hack - vte_terminal_queue_background_update - * doesn't run without changing background */ - vte_terminal_set_color_background((VteTerminal *)term->vte, &terminal->foreground); - vte_terminal_set_background_transparent((VteTerminal *)term->vte, FALSE); - vte_terminal_set_opacity((VteTerminal *)term->vte, terminal->setting->bgalpha); - } else { - vte_terminal_set_background_transparent((VteTerminal *)term->vte, terminal->setting->bgalpha == 65535 ? FALSE : TRUE); - vte_terminal_set_background_saturation((VteTerminal *)term->vte, 1-((double)terminal->setting->bgalpha/65535)); - } - - vte_terminal_set_colors((VteTerminal *)term->vte, &terminal->foreground, &terminal->background, &linux_color[0], 16); - - /* update scrollbar */ - if( terminal->setting->hidescrollbar ) - gtk_widget_hide( term->scrollbar ); - else - gtk_widget_show( term->scrollbar ); +/* Apply new settings in an LXTerminal to its tab Term. */ +static void terminal_settings_apply_to_term(LXTerminal * terminal, Term * term) +{ + Setting * setting = terminal->setting; + + /* Terminal properties. */ + vte_terminal_reset(VTE_TERMINAL(term->vte), FALSE, FALSE); + vte_terminal_set_font_from_string(VTE_TERMINAL(term->vte), setting->font_name); + vte_terminal_set_word_chars(VTE_TERMINAL(term->vte), setting->word_selection_characters); + vte_terminal_set_scrollback_lines(VTE_TERMINAL(term->vte), setting->scrollback); + vte_terminal_set_allow_bold(VTE_TERMINAL(term->vte), ! setting->disallow_bold); + vte_terminal_set_cursor_blink_mode(VTE_TERMINAL(term->vte), ((setting->cursor_blink) ? VTE_CURSOR_BLINK_ON : VTE_CURSOR_BLINK_OFF)); + vte_terminal_set_cursor_shape(VTE_TERMINAL(term->vte), ((setting->cursor_underline) ? VTE_CURSOR_SHAPE_UNDERLINE : VTE_CURSOR_SHAPE_BLOCK)); + vte_terminal_set_audible_bell(VTE_TERMINAL(term->vte), setting->audible_bell); + + /* Background and foreground colors. */ + if (terminal->rgba) + { + /* vte_terminal_queue_background_update doesn't run without changing background. */ + vte_terminal_set_color_background(VTE_TERMINAL(term->vte), &setting->foreground_color); + vte_terminal_set_background_transparent(VTE_TERMINAL(term->vte), FALSE); + vte_terminal_set_opacity(VTE_TERMINAL(term->vte), setting->background_alpha); + } + else + { + vte_terminal_set_background_transparent(VTE_TERMINAL(term->vte), setting->background_alpha == 65535 ? FALSE : TRUE); + vte_terminal_set_background_saturation(VTE_TERMINAL(term->vte), 1 - ((double) setting->background_alpha / 65535)); + } + vte_terminal_set_colors(VTE_TERMINAL(term->vte), &setting->foreground_color, &setting->background_color, &linux_color[0], 16); + + /* Hide or show scrollbar. */ + if (setting->hide_scroll_bar) + gtk_widget_hide(term->scrollbar); + else gtk_widget_show(term->scrollbar); + + /* Hide or show Close button. */ + if (setting->hide_close_button) + gtk_widget_hide(term->close_button); + else gtk_widget_show(term->close_button); + + /* If terminal geometry changed, react to it. */ + if (setting->geometry_change) + terminal_geometry_restore(term); } -static Term *terminal_new(LXTerminal *terminal, const gchar *label, const gchar *pwd, gchar **env, const gchar *exec) +/* Create a new terminal. */ +static Term * terminal_new(LXTerminal * terminal, const gchar * label, const gchar * pwd, gchar * * env, const gchar * exec) { - gint ret; - GRegex *dingus1, *dingus2; - Term *term; - - /* create terminal */ - term = g_new0(Term, 1); - term->parent = terminal; - term->vte = vte_terminal_new(); - term->box = gtk_hbox_new(FALSE, 0); - term->scrollbar = gtk_vscrollbar_new(NULL); - gtk_box_pack_start(GTK_BOX(term->box), term->vte, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(term->box), term->scrollbar, FALSE, TRUE, 0); - - /* setting terminal */ - vte_terminal_set_emulation((VteTerminal *)term->vte, "xterm"); - - /* Set encoding from locale. */ - setlocale(LC_ALL, ""); - vte_terminal_set_encoding((VteTerminal *)term->vte, nl_langinfo(CODESET)); - - /* fixing bugs for specific environment */ - vte_terminal_set_backspace_binding((VteTerminal *)term->vte, VTE_ERASE_ASCII_DELETE); - vte_terminal_set_delete_binding((VteTerminal *)term->vte, VTE_ERASE_DELETE_SEQUENCE); - - /* disable beeping */ - vte_terminal_set_audible_bell((VteTerminal *)term->vte, FALSE); - - if (!gdk_color_parse(terminal->setting->bgcolor, &terminal->background)) { - terminal->background = (GdkColor){ 0, 0, 0, 0 }; - printf("Bad bgcolor string in config: %s\n", terminal->setting->bgcolor); - } - - if (!gdk_color_parse(terminal->setting->fgcolor, &terminal->foreground)) { - terminal->foreground = (GdkColor){ 0, 0xaaaa, 0xaaaa, 0xaaaa}; - printf("Bad fgcolor string in config: %s\n", terminal->setting->fgcolor); - } - - /* steal from tilda-0.09.6/src/tilda_terminal.c:145 */ - /* Match URL's, etc */ - dingus1 = g_regex_new(DINGUS1, G_REGEX_OPTIMIZE, 0, NULL); - dingus2 = g_regex_new(DINGUS2, G_REGEX_OPTIMIZE, 0, NULL); - ret = vte_terminal_match_add_gregex((VteTerminal *)term->vte, dingus1, 0); - vte_terminal_match_set_cursor_type((VteTerminal *)term->vte, ret, GDK_HAND2); - ret = vte_terminal_match_add_gregex((VteTerminal *)term->vte, dingus2, 0); - vte_terminal_match_set_cursor_type((VteTerminal *)term->vte, ret, GDK_HAND2); - g_regex_unref(dingus1); - g_regex_unref(dingus2); - - /* create label for tab */ - if (label) - term->label = lxterminal_tab_label_new(label); - else - term->label = lxterminal_tab_label_new(pwd); - - lxterminal_tab_label_close_button_clicked(G_CALLBACK(terminal_childexit), term); - - /* setting scrollbar */ - gtk_range_set_adjustment(GTK_RANGE(term->scrollbar), VTE_TERMINAL(term->vte)->adjustment); - - /* terminal fork */ - if (exec) { - gchar **command; - g_shell_parse_argv(exec, NULL, &command, NULL); - term->pid = vte_terminal_fork_command(VTE_TERMINAL(term->vte), (const char *)*(command), command, env, "/tmp", FALSE, TRUE, TRUE); - g_strfreev(command); - } else { - term->pid = vte_terminal_fork_command(VTE_TERMINAL(term->vte), NULL, NULL, env, pwd, FALSE, TRUE, TRUE); - } - - /* signal handler */ - g_signal_connect(term->vte, "child-exited", G_CALLBACK(terminal_childexit), term); - g_signal_connect(term->vte, "window-title-changed", G_CALLBACK(terminal_title_changed), term); - g_signal_connect(term->vte, "button-press-event", G_CALLBACK(terminal_vte_button_press), terminal); - - gtk_widget_show_all(term->box); - - /* apply other settings */ - terminal_term_setting_update(term, terminal); - - return term; + /* Create and initialize Term structure for new terminal. */ + Term * term = g_new0(Term, 1); + term->parent = terminal; + + /* Create a VTE and a vertical scrollbar, and place them inside a horizontal box. */ + term->vte = vte_terminal_new(); + term->box = gtk_hbox_new(FALSE, 0); + term->scrollbar = gtk_vscrollbar_new(NULL); + gtk_box_pack_start(GTK_BOX(term->box), term->vte, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(term->box), term->scrollbar, FALSE, TRUE, 0); + + /* Set up the VTE. */ + setlocale(LC_ALL, ""); + vte_terminal_set_emulation(VTE_TERMINAL(term->vte), "xterm"); + vte_terminal_set_encoding(VTE_TERMINAL(term->vte), nl_langinfo(CODESET)); + vte_terminal_set_backspace_binding(VTE_TERMINAL(term->vte), VTE_ERASE_ASCII_DELETE); + vte_terminal_set_delete_binding(VTE_TERMINAL(term->vte), VTE_ERASE_DELETE_SEQUENCE); + + /* steal from tilda-0.09.6/src/tilda_terminal.c:145 */ + /* Match URL's, etc. */ + GRegex * dingus1 = g_regex_new(DINGUS1, G_REGEX_OPTIMIZE, 0, NULL); + GRegex * dingus2 = g_regex_new(DINGUS2, G_REGEX_OPTIMIZE, 0, NULL); + gint ret = vte_terminal_match_add_gregex(VTE_TERMINAL(term->vte), dingus1, 0); + vte_terminal_match_set_cursor_type(VTE_TERMINAL(term->vte), ret, GDK_HAND2); + ret = vte_terminal_match_add_gregex(VTE_TERMINAL(term->vte), dingus2, 0); + vte_terminal_match_set_cursor_type(VTE_TERMINAL(term->vte), ret, GDK_HAND2); + g_regex_unref(dingus1); + g_regex_unref(dingus2); + + /* Create a horizontal box inside an event box as the toplevel for the tab label. */ + term->tab = gtk_event_box_new(); + gtk_widget_set_events(term->tab, GDK_BUTTON_PRESS_MASK); + GtkWidget * hbox = gtk_hbox_new(FALSE, 4); + gtk_container_add(GTK_CONTAINER(term->tab), hbox); + + /* Create the Close button. */ + term->close_button = gtk_button_new(); + gtk_button_set_relief(GTK_BUTTON(term->close_button), GTK_RELIEF_NONE); + gtk_button_set_focus_on_click(GTK_BUTTON(term->close_button), FALSE); + gtk_container_add(GTK_CONTAINER(term->close_button), gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU)); + + /* Make the button as small as possible. */ + GtkRcStyle * rcstyle = gtk_rc_style_new(); + rcstyle->xthickness = rcstyle->ythickness = 0; + gtk_widget_modify_style(term->close_button, rcstyle); + gtk_rc_style_unref(rcstyle), + + /* Create the label. */ + term->label = gtk_label_new((label != NULL) ? label : pwd); + gtk_widget_set_size_request(GTK_WIDGET(term->label), 100, -1); + gtk_label_set_ellipsize(GTK_LABEL(term->label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment(GTK_MISC(term->label), 0.0, 0.5); + gtk_misc_set_padding(GTK_MISC(term->label), 0, 0); + + /* Pack everything and show the widget. */ + gtk_box_pack_start(GTK_BOX(hbox), term->label, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(hbox), term->close_button, FALSE, FALSE, 0); + gtk_widget_show_all(term->tab); + + /* Set up scrollbar. */ + gtk_range_set_adjustment(GTK_RANGE(term->scrollbar), VTE_TERMINAL(term->vte)->adjustment); + + /* Fork the process that will have the VTE as its controlling terminal. */ + if (exec != NULL) + { + gchar * * command; + g_shell_parse_argv(exec, NULL, &command, NULL); + term->pid = vte_terminal_fork_command(VTE_TERMINAL(term->vte), (const char *) command[0], command, env, pwd, FALSE, TRUE, TRUE); + g_strfreev(command); + } + else + term->pid = vte_terminal_fork_command(VTE_TERMINAL(term->vte), NULL, NULL, env, pwd, FALSE, TRUE, TRUE); + + /* Connect signals. */ + g_signal_connect(G_OBJECT(term->tab), "button-press-event", G_CALLBACK(terminal_tab_button_press_event), term); + g_signal_connect(G_OBJECT(term->close_button), "clicked", G_CALLBACK(terminal_child_exited_event), term); + g_signal_connect(G_OBJECT(term->vte), "button-press-event", G_CALLBACK(terminal_vte_button_press_event), term); + g_signal_connect(G_OBJECT(term->vte), "child-exited", G_CALLBACK(terminal_child_exited_event), term); + g_signal_connect(G_OBJECT(term->vte), "window-title-changed", G_CALLBACK(terminal_window_title_changed_event), term); + + /* Show the widget and return. */ + gtk_widget_show_all(term->box); + + /* Apply user preferences. */ + terminal_settings_apply_to_term(terminal, term); + return term; } -void terminal_free(Term* term) +/* Deallocate a Term structure. */ +static void terminal_free(Term * term) { - GtkAccelGroup *accel_group = term->parent->menubar->accel_group; - gtk_accel_group_disconnect(accel_group, term->closure); + gtk_accel_group_disconnect(term->parent->accel_group, term->closure); g_free(term); } -static Menu *menubar_init(LXTerminal *terminal) +/* Initialize the menu bar. */ +static void terminal_menubar_initialize(LXTerminal * terminal) { - Menu *menubar; - GtkWidget *menu_item; - GtkActionGroup *action_group; - GtkAction *encoding_action; - GtkUIManager *manager; - guint merge_id; - gint i; - gchar *path, *path_ptr; - - /* initializing menu */ - menubar = g_new0(Menu, 1); - - /* initializing encoding Action */ -// encoding_action = encoding_action_new("CharacterEncoding"); - - /* initializing UI manager */ - manager = gtk_ui_manager_new(); - action_group = gtk_action_group_new("MenuBar"); - gtk_action_group_set_translation_domain(action_group, GETTEXT_PACKAGE); - gtk_action_group_add_actions(action_group, menus, MENUBAR_MENU_COUNT, terminal); - gtk_action_group_add_actions(action_group, menu_items, MENUBAR_MENUITEM_COUNT, terminal); -// gtk_action_group_add_action(action_group, encoding_action); - gtk_ui_manager_insert_action_group(manager, action_group, 0); - - merge_id = gtk_ui_manager_new_merge_id(manager); - - gtk_ui_manager_add_ui(manager, merge_id, "/", "MenuBar", NULL, GTK_UI_MANAGER_MENUBAR, FALSE); - - /* menus */ - for (i=0;i<MENUBAR_MENU_COUNT;i++) { - path = g_strdup_printf("/MenuBar/%s", menus[i].name); - for (path_ptr=path;*path_ptr!='\0';path_ptr++) { - if (*path_ptr=='_') - *path_ptr = '/'; - } - - path_ptr = g_path_get_dirname(path); - gtk_ui_manager_add_ui(manager, merge_id, path_ptr, menus[i].name, menus[i].name, GTK_UI_MANAGER_MENU, FALSE); - g_free(path); - g_free(path_ptr); - } - - /* items */ - for (i=0;i<MENUBAR_MENUITEM_COUNT;i++) { - path = g_strdup_printf("/MenuBar/%s", menu_items[i].name); - for (path_ptr=path;*path_ptr!='\0';path_ptr++) { - if (*path_ptr=='_') - *path_ptr = '/'; - } - - path_ptr = g_path_get_dirname(path); - - if (strcmp(menu_items[i].label, "Sep")==0) { - gtk_ui_manager_add_ui(manager, merge_id, path_ptr, menu_items[i].name, NULL, GTK_UI_MANAGER_SEPARATOR, FALSE); -#if 0 - /* Encoding */ - } else if (strcmp(menu_items[i].name, "View_CharacterEncoding")==0) { - gtk_ui_manager_add_ui(manager, merge_id, path_ptr, menu_items[i].name, "CharacterEncoding", GTK_UI_MANAGER_MENUITEM, FALSE); -#endif - } else { - gtk_ui_manager_add_ui(manager, merge_id, path_ptr, menu_items[i].name, menu_items[i].name, GTK_UI_MANAGER_MENUITEM, FALSE); - } - - g_free(path); - g_free(path_ptr); - } - - menubar->menu = gtk_ui_manager_get_widget(manager, "/MenuBar"); - - return menubar; + /* Initialize UI manager. */ + GtkUIManager * manager = gtk_ui_manager_new(); + GtkActionGroup * action_group = gtk_action_group_new("MenuBar"); + gtk_action_group_set_translation_domain(action_group, GETTEXT_PACKAGE); + gtk_action_group_add_actions(action_group, menus, MENUBAR_MENU_COUNT, terminal); + gtk_action_group_add_actions(action_group, menu_items, MENUBAR_MENUITEM_COUNT, terminal); + gtk_ui_manager_insert_action_group(manager, action_group, 0); + + guint merge_id = gtk_ui_manager_new_merge_id(manager); + gtk_ui_manager_add_ui(manager, merge_id, "/", "MenuBar", NULL, GTK_UI_MANAGER_MENUBAR, FALSE); + + /* Menus. */ + int i; + for (i = 0; i < MENUBAR_MENU_COUNT; i += 1) + { + gchar * path = g_strdup_printf("/MenuBar/%s", menus[i].name); + gchar * path_ptr; + for (path_ptr = path; *path_ptr != '\0'; path_ptr += 1) + { + if (*path_ptr == '_') + *path_ptr = '/'; + } + path_ptr = g_path_get_dirname(path); + gtk_ui_manager_add_ui(manager, merge_id, path_ptr, menus[i].name, menus[i].name, GTK_UI_MANAGER_MENU, FALSE); + g_free(path); + g_free(path_ptr); + } + + /* Items. */ + for (i = 0; i < MENUBAR_MENUITEM_COUNT; i += 1) + { + gchar * path = g_strdup_printf("/MenuBar/%s", menu_items[i].name); + gchar * path_ptr; + for (path_ptr = path; *path_ptr != '\0'; path_ptr += 1) + { + if (*path_ptr == '_') + *path_ptr = '/'; + } + path_ptr = g_path_get_dirname(path); + if (strcmp(menu_items[i].label, "Sep") == 0) + gtk_ui_manager_add_ui(manager, merge_id, path_ptr, menu_items[i].name, NULL, GTK_UI_MANAGER_SEPARATOR, FALSE); + else gtk_ui_manager_add_ui(manager, merge_id, path_ptr, menu_items[i].name, menu_items[i].name, GTK_UI_MANAGER_MENUITEM, FALSE); + + g_free(path); + g_free(path_ptr); + } + + terminal->menu = gtk_ui_manager_get_widget(manager, "/MenuBar"); } -static void lxterminal_accelerator_init(LXTerminal *terminal) +/* Allocate, initialize, and connect up an accelerator group for the accelerators on a new terminal. */ +static void terminal_accelerator_initialize(LXTerminal * terminal) { - guint key; - GdkModifierType mods; + guint key; + GdkModifierType mods; + + terminal->accel_group = gtk_accel_group_new(); - terminal->menubar->accel_group = gtk_accel_group_new(); + gtk_accelerator_parse(NEW_WINDOW_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_new_window_accelerator), terminal, NULL)); - gtk_accelerator_parse(NEW_WINDOW_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_newwindow_accel), terminal, NULL)); + gtk_accelerator_parse(QUIT_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(gtk_main_quit), NULL, NULL)); - gtk_accelerator_parse(QUIT_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(gtk_main_quit), NULL, NULL)); + gtk_accelerator_parse(NEW_TAB_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_new_tab_accelerator), terminal, NULL)); - gtk_accelerator_parse(NEW_TAB_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_newtab_accel), terminal, NULL)); + gtk_accelerator_parse(CLOSE_TAB_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_close_tab_accelerator), terminal, NULL)); - gtk_accelerator_parse(CLOSE_TAB_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_closetab_accel), terminal, NULL)); + gtk_accelerator_parse(COPY_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_copy_accelerator), terminal, NULL)); - gtk_accelerator_parse(COPY_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_copy_accel), terminal, NULL)); + gtk_accelerator_parse(PASTE_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_paste_accelerator), terminal, NULL)); - gtk_accelerator_parse(PASTE_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_paste_accel), terminal, NULL)); + gtk_accelerator_parse(NAME_TAB_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_name_tab_accelerator), terminal, NULL)); - gtk_accelerator_parse(NEXT_TAB_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_nexttab_accel), terminal, NULL)); + gtk_accelerator_parse(PREVIOUS_TAB_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_previous_tab_accelerator), terminal, NULL)); - gtk_accelerator_parse(PREVIOUS_TAB_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_prevtab_accel), terminal, NULL)); + gtk_accelerator_parse(NEXT_TAB_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_next_tab_accelerator), terminal, NULL)); - gtk_accelerator_parse(MOVE_TAB_LEFT_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_movetableft_accel), terminal, NULL)); + gtk_accelerator_parse(MOVE_TAB_LEFT_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_move_tab_left_accelerator), terminal, NULL)); - gtk_accelerator_parse(MOVE_TAB_RIGHT_ACCEL, &key, &mods); - gtk_accel_group_connect(terminal->menubar->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_movetabright_accel), terminal, NULL)); + gtk_accelerator_parse(MOVE_TAB_RIGHT_ACCEL, &key, &mods); + gtk_accel_group_connect(terminal->accel_group, key, mods, GTK_ACCEL_LOCKED, g_cclosure_new_swap(G_CALLBACK(terminal_move_tab_right_accelerator), terminal, NULL)); - gtk_window_add_accel_group(GTK_WINDOW(terminal->mainw), terminal->menubar->accel_group); + gtk_window_add_accel_group(GTK_WINDOW(terminal->window), terminal->accel_group); } -void lxterminal_menuaccel_update(LXTerminal *terminal) +/* Update the accelerator that brings up the menu. + * We have a user preference as to whether F10 (or a style-supplied alternate) is used for this purpose. + * Technique taken from gnome-terminal. */ +static void terminal_menu_accelerator_update(LXTerminal * terminal) { - /* update F10 status */ - /* hack took from gnome-terminal */ - - if (saved_menu_accel == NULL) { - g_object_get (G_OBJECT (gtk_settings_get_default ()), - "gtk-menu-bar-accel", - &saved_menu_accel, - NULL); - /* FIXME if gtkrc is reparsed we don't catch on, - * I guess. - */ - } - - - if (terminal->setting->disablef10) { - gtk_settings_set_string_property (gtk_settings_get_default(), - "gtk-menu-bar-accel", - /* no one will ever press this ;-) */ - "<Shift><Control><Mod1><Mod2><Mod3><Mod4><Mod5>F10", - "lxterminal"); - } else { - gtk_settings_set_string_property (gtk_settings_get_default(), - "gtk-menu-bar-accel", - saved_menu_accel, - "lxterminal"); - } + /* Ensure that saved_menu_accelerator is initialized. */ + if (saved_menu_accelerator == NULL) + g_object_get(G_OBJECT(gtk_settings_get_default()), "gtk-menu-bar-accel", &saved_menu_accelerator, NULL); + + /* If F10 is disabled, set the accelerator to a key combination that is not F10 and unguessable. */ + gtk_settings_set_string_property( + gtk_settings_get_default(), + "gtk-menu-bar-accel", + ((terminal->setting->disable_f10) ? "<Shift><Control><Mod1><Mod2><Mod3><Mod4><Mod5>F10" : saved_menu_accelerator), + "lxterminal"); } -void terminal_setting_update(LXTerminal *terminal) +/* Process the argument vector into the CommandArguments structure. + * This is called from the main entry, and also from the controlling socket flow. */ +gboolean lxterminal_process_arguments(gint argc, gchar * * argv, CommandArguments * arguments) { - gint i; + /* Loop over the argument vector to produce the CommandArguments structure. */ + memset(arguments, 0, sizeof(CommandArguments)); + arguments->executable = argv[0]; + + gboolean login_shell = FALSE; + char * * argv_cursor = argv + 1; + argc -= 1; + while (argc > 0) + { + char * argument = *argv_cursor; + + /* --command=<command> */ + if (strncmp(argument, "--command=", 10) == 0) + { + g_free(arguments->command); + arguments->command = g_strdup(&argument[10]); + } + + /* -e <rest of arguments>, --command <rest of arguments> + * The <rest of arguments> behavior is demanded by distros who insist on this xterm feature. */ + else if ((strcmp(argument, "--command") == 0) || (strcmp(argument, "-e") == 0)) + { + while (argc > 1) + { + argc -= 1; + argv_cursor += 1; + if (arguments->command == NULL) + arguments->command = g_strdup(*argv_cursor); + else + { + gchar * new_command = g_strconcat(arguments->command, " ", *argv_cursor, NULL); + g_free(arguments->command); + arguments->command = new_command; + } + } + } + + /* --geometry=<columns>x<rows> */ + else if (strncmp(argument, "--geometry=", 11) == 0) + { + int result = sscanf(&argument[11], "%dx%d", &arguments->geometry_columns, &arguments->geometry_rows); + if (result != 2) + return FALSE; + } + + /* -l, --loginshell */ + else if ((strcmp(argument, "--loginshell") == 0) || (strcmp(argument, "-l") == 0)) + login_shell = TRUE; + + /* --title=<title> */ + else if (strncmp(argument, "--title=", 8) == 0) + arguments->title = &argument[8]; + + /* -t <title>, -T <title>, --title <title> + * The -T form is demanded by distros who insist on this xterm feature. */ + else if (((strcmp(argument, "--title") == 0) || (strcmp(argument, "-t") == 0) || (strcmp(argument, "-T") == 0)) + && (argc > 1)) + { + argc -= 1; + argv_cursor += 1; + arguments->title = *argv_cursor; + } + + /* --working-directory=<working directory> */ + else if (strncmp(argument, "--working-directory=", 20) == 0) + arguments->working_directory = &argument[20]; + + /* Undefined argument. */ + else + return FALSE; + + argc -= 1; + argv_cursor += 1; + } + + /* Handle --loginshell. */ + if (login_shell) + { + if (arguments->command == NULL) + arguments->command = g_strdup("sh -l"); + else + { + gchar * escaped_command = g_shell_quote(arguments->command); + gchar * new_command = g_strdup_printf("sh -l -c %s", escaped_command); + g_free(escaped_command); + g_free(arguments->command); + arguments->command = new_command; + } + } + return TRUE; +} + +/* Initialize a new LXTerminal. + * This is a toplevel window that may contain tabs, each of which will contain a VTE controlled by a process. */ +LXTerminal * lxterminal_initialize(LXTermWindow * lxtermwin, CommandArguments * arguments, Setting * setting) +{ + /* Allocate and initialize the LXTerminal structure. */ + LXTerminal * terminal = g_new0(LXTerminal, 1); + terminal->parent = lxtermwin; + terminal->terms = g_ptr_array_new(); + terminal->fixed_size = TRUE; + g_ptr_array_add(lxtermwin->windows, terminal); + terminal->index = terminal->parent->windows->len - 1; + terminal->setting = setting; + + /* Create toplevel window. */ + terminal->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + /* Try to get an RGBA colormap and assign it to the new window. */ + GdkColormap * colormap = gdk_screen_get_rgba_colormap(gtk_widget_get_screen(GTK_WIDGET(terminal->window))); + if (colormap != NULL) + gtk_widget_set_colormap(terminal->window, colormap); + + /* Set window title. */ + gtk_window_set_title(GTK_WINDOW(terminal->window), ((arguments->title != NULL) ? arguments->title : _("LXTerminal"))); + + /* Set window icon. */ + gtk_window_set_icon_from_file(GTK_WINDOW(terminal->window), PACKAGE_DATA_DIR "/pixmaps/lxterminal.png", NULL); + g_object_weak_ref(G_OBJECT(terminal->window), (GWeakNotify) terminal_window_exit, terminal); + + /* Create a vertical box as the child of the toplevel window. */ + terminal->box = gtk_vbox_new(FALSE, 1); + gtk_container_add(GTK_CONTAINER(terminal->window), terminal->box); + + /* Create the menu bar as the child of the vertical box. */ + terminal_menubar_initialize(terminal); + gtk_box_pack_start(GTK_BOX(terminal->box), terminal->menu, FALSE, TRUE, 0); + + /* Create a notebook as the child of the vertical box. */ + terminal->notebook = gtk_notebook_new(); + gtk_notebook_set_scrollable(GTK_NOTEBOOK(terminal->notebook), TRUE); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(terminal->notebook), FALSE); + gtk_notebook_set_show_border(GTK_NOTEBOOK(terminal->notebook), FALSE); + gtk_box_pack_start(GTK_BOX(terminal->box), terminal->notebook, TRUE, TRUE, 0); + + /* Initialize tab position. */ + terminal->tab_position = terminal_tab_get_position_id(terminal->setting->tab_position); + + /* Connect signals. */ + g_signal_connect_swapped(G_OBJECT(terminal->window), "composited-changed", G_CALLBACK(terminal_settings_apply), terminal); + g_signal_connect(G_OBJECT(terminal->notebook), "switch-page", G_CALLBACK(terminal_switch_page_event), terminal); + + /* Create the first terminal. */ + gchar * local_working_directory = NULL; + if (arguments->working_directory == NULL) + local_working_directory = g_get_current_dir(); + Term * term = terminal_new( + terminal, + _("LXTerminal"), + ((arguments->working_directory != NULL) ? arguments->working_directory : local_working_directory), + NULL, + arguments->command); + g_free(local_working_directory); + + /* Set the terminal geometry. */ + if ((arguments->geometry_columns != 0) && (arguments->geometry_rows != 0)) + vte_terminal_set_size(VTE_TERMINAL(term->vte), arguments->geometry_columns, arguments->geometry_rows); + + /* Add the first terminal to the notebook and the data structures. */ + gtk_notebook_append_page(GTK_NOTEBOOK(terminal->notebook), term->box, term->tab); + term->index = gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook)) - 1; + g_ptr_array_add(terminal->terms, term); + + /* Initialize accelerators. */ + terminal_accelerator_initialize(terminal); + terminal_initialize_switch_tab_accelerator(term); + + /* Update terminal settings. */ + terminal_settings_apply(terminal); + + /* Show the widget, so it is realized and has a window. */ + gtk_widget_show_all(terminal->window); + + /* Initialize the geometry hints. */ + gdk_window_get_geometry_hints(GTK_WIDGET(term->vte)->window, &terminal->geometry, &terminal->geometry_mask); + + /* Connect signals. */ + g_signal_connect(G_OBJECT(terminal->window), "size-request", G_CALLBACK(terminal_window_size_request_event), terminal); + return terminal; +} - /* know if it is composited */ - terminal->rgba = gtk_widget_is_composited( GTK_WIDGET(terminal->mainw) ); +/* Apply new settings to a terminal. */ +static void terminal_settings_apply(LXTerminal * terminal) +{ + /* Reinitialize "composited". */ + terminal->rgba = gtk_widget_is_composited(terminal->window); - /* update all of terminals */ - for (i=0;i<terminal->terms->len;i++) - terminal_term_setting_update(g_ptr_array_index(terminal->terms, i), terminal); + /* Apply settings to all windows. */ + int i; + for (i = 0; i < terminal->terms->len; i += 1) + terminal_settings_apply_to_term(terminal, g_ptr_array_index(terminal->terms, i)); - /* update tab position */ - lxterminal_tab_set_position(terminal->notebook, terminal->tabpos); + /* Update tab position. */ + terminal->tab_position = terminal_tab_get_position_id(terminal->setting->tab_position); + terminal_tab_set_position(terminal->notebook, terminal->tab_position); - /* update menu accel */ - lxterminal_menuaccel_update(terminal); + /* Update menu accelerators. */ + terminal_menu_accelerator_update(terminal); - /* update menubar */ - if( terminal->setting->hidemenubar ) - gtk_widget_hide( terminal->menubar->menu ); - else - gtk_widget_show( terminal->menubar->menu ); + /* Hide or show menubar. */ + if (terminal->setting->hide_menu_bar) + gtk_widget_hide(terminal->menu); + else gtk_widget_show(terminal->menu); } -LXTerminal *lxterminal_init(LXTermWindow *lxtermwin, gint argc, gchar **argv, Setting *setting) +/* Apply terminal settings to all tabs in all terminals. */ +void terminal_settings_apply_to_all(LXTerminal * terminal) { - LXTerminal *terminal; - Term *term = NULL; - gchar *cmd = NULL; - gchar *workdir = NULL; - gchar *title = NULL; - gint cols = 0, rows = 0; - - /* argument */ - if (argc>1) { - int i; - - for (i=1;i<argc;i++) { - if (strncmp(argv[i],"--command=", 10)==0) { - cmd = argv[i]+10; - continue; - } else if ((strcmp(argv[i],"--command")==0 || strcmp(argv[i],"-e") == 0) && (i+1<argc) ) { - cmd = argv[++i]; - continue; - } else if (strncmp(argv[i],"--title=", 8)==0) { - title = argv[i]+8; - continue; - } else if ((strcmp(argv[i],"--title")==0 || strcmp(argv[i],"-t")==0 || strcmp(argv[i],"-T")==0)&&(i+1<argc)) { - title = argv[++i]; - continue; - } else if (strncmp(argv[i],"--working-directory=", 20)==0) { - workdir = g_strdup(argv[i]+20); - continue; - } else if (strncmp(argv[i],"--geometry=", 11)==0) { - sscanf(argv[i]+11, "%dx%d", &cols, &rows); - continue; - } else if ((strcmp(argv[i],"--loginshell")==0 || strcmp(argv[i],"-l")==0)&&cmd==NULL) { - cmd = "sh -l"; - } - } - } - - terminal = g_new0(LXTerminal, 1); - terminal->parent = lxtermwin; - terminal->terms = g_ptr_array_new(); - terminal->fixedsize = TRUE; - - g_ptr_array_add(lxtermwin->windows, terminal); - terminal->index = terminal->parent->windows->len - 1; - - /* Setting */ - if (setting) - terminal->setting = setting; - - /* create window */ - terminal->mainw = gtk_window_new(GTK_WINDOW_TOPLEVEL); - - /* colormap assign */ - GdkColormap *colormap = gdk_screen_get_rgba_colormap( gtk_widget_get_screen( GTK_WIDGET(terminal->mainw) ) ); - if (colormap) gtk_widget_set_colormap( GTK_WIDGET(terminal->mainw), colormap ); - - /* when composited changed, reload the terminal settings */ - g_signal_connect_swapped(GTK_WIDGET(terminal->mainw), "composited-changed", G_CALLBACK(terminal_setting_update), terminal); - - if (!title) - gtk_window_set_title(GTK_WINDOW(terminal->mainw), _("LXTerminal")); - else - gtk_window_set_title(GTK_WINDOW(terminal->mainw), title); - - gtk_window_set_icon_from_file(GTK_WINDOW(terminal->mainw), PACKAGE_DATA_DIR "/pixmaps/lxterminal.png", NULL); - g_object_weak_ref((GObject *) terminal->mainw, terminal_windowexit, terminal); - - /* create box for putting menubar and notebook */ - terminal->box = gtk_vbox_new(FALSE, 1); - gtk_container_add(GTK_CONTAINER(terminal->mainw), terminal->box); - - /* create menubar */ - terminal->menubar = menubar_init(terminal); - gtk_box_pack_start(GTK_BOX(terminal->box), terminal->menubar->menu, FALSE, TRUE, 0); - - /* create notebook */ - terminal->notebook = gtk_notebook_new(); - gtk_notebook_set_scrollable(GTK_NOTEBOOK(terminal->notebook), TRUE); - gtk_notebook_set_show_tabs(GTK_NOTEBOOK(terminal->notebook), FALSE); - gtk_notebook_set_show_border(GTK_NOTEBOOK(terminal->notebook), FALSE); - - /* Tab Position */ - terminal->tabpos = lxterminal_tab_get_position_id(terminal->setting->tabpos); - - g_signal_connect(terminal->notebook, "switch-page", G_CALLBACK(terminal_switch_tab), terminal); - gtk_box_pack_start(GTK_BOX(terminal->box), terminal->notebook, TRUE, TRUE, 0); - - if ( ! workdir) workdir = g_get_current_dir(); - term = terminal_new(terminal, _("LXTerminal"), workdir, NULL, cmd); - g_free(workdir); - - /* set default cols and rows */ - if (cols&&rows) - vte_terminal_set_size(VTE_TERMINAL(term->vte), cols, rows); - - gtk_notebook_append_page(GTK_NOTEBOOK(terminal->notebook), term->box, term->label->main); - term->index = gtk_notebook_get_n_pages(GTK_NOTEBOOK(terminal->notebook)) - 1; - g_ptr_array_add(terminal->terms, term); - - /* initializing accelerator */ - lxterminal_accelerator_init(terminal); - term_set_swicth_accel(term); - - gtk_widget_show_all(terminal->mainw); - - /* update terminal settings */ - terminal_setting_update(terminal); - - /* original hints of VTE */ - gdk_window_get_geometry_hints(GTK_WIDGET(term->vte)->window, - &terminal->geometry, - &terminal->geom_mask); - - /* resizing terminal with window size */ - g_signal_connect(terminal->mainw, "size-request", G_CALLBACK(terminal_window_resize), terminal); - - return terminal; + /* Apply settings to all open windows. */ + g_ptr_array_foreach(terminal->parent->windows, (GFunc) terminal_settings_apply, terminal->setting); + terminal->setting->geometry_change = FALSE; } -int main(gint argc, gchar** argv) +/* Main entry point. */ +int main(gint argc, gchar * * argv) { - LXTermWindow *lxtermwin; - Setting *setting; - gchar *dir, *path; - gint i; - - gtk_init(&argc, &argv); + /* Initialize GTK. */ + gtk_init(&argc, &argv); #ifdef ENABLE_NLS - bindtextdomain ( GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR ); - bind_textdomain_codeset ( GETTEXT_PACKAGE, "UTF-8" ); - textdomain ( GETTEXT_PACKAGE ); + bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + textdomain(GETTEXT_PACKAGE); #endif - if (argc>1) { - for (i=1;i<argc;i++) { - if (strncmp(argv[i],"--command=", 10)==0) { - continue; - } else if ((strcmp(argv[i],"--command")==0||strcmp(argv[i],"-e")==0)&&(i+1<argc)) { - i++; - continue; - } else if (strncmp(argv[i],"--title=", 8)==0) { - continue; - } else if ((strcmp(argv[i],"--title")==0||strcmp(argv[i],"-t")==0||strcmp(argv[i],"-T")==0)&&(i+1<argc)) { - i++; - continue; - } else if (strncmp(argv[i],"--working-directory=", 20)==0) { - continue; - } else if (strncmp(argv[i],"--geometry=", 11)==0) { - continue; - } else if ((strcmp(argv[i],"--loginshell")==0 || strcmp(argv[i],"-l")==0)) { - continue; - } - - printf("%s\n", helpmsg); - return 0; - } - } - - /* initializing Window Array */ - lxtermwin = g_new0(LXTermWindow, 1); - - /* initializing socket */ - if (!lxterminal_socket_init(lxtermwin, argc, argv)) - return 0; - - /* load config file */ - dir = g_build_filename(g_get_user_config_dir(), "lxterminal" , NULL); - g_mkdir_with_parents(dir, 0700); - path = g_build_filename(dir, "lxterminal.conf", NULL); - g_free(dir); - - if (!g_file_test(path, G_FILE_TEST_EXISTS)) { - setting = load_setting_from_file(PACKAGE_DATA_DIR "/lxterminal/lxterminal.conf"); - /* save to user's directory */ - setting_save(setting); - } else { - setting = load_setting_from_file(path); - } - - g_free(path); - - /* initializing something */ - lxtermwin->windows = g_ptr_array_new(); - lxtermwin->setting = setting; - - /* initializing LXTerminal */ - lxterminal_init(lxtermwin, argc, argv, setting); - - gtk_main(); + /* Parse the command arguments. If there is an error, display usage help and exit. */ + CommandArguments arguments; + if ( ! lxterminal_process_arguments(argc, argv, &arguments)) + { + printf("%s\n", usage_display); + return 0; + } + + /* Initialize impure storage. */ + LXTermWindow * lxtermwin = g_new0(LXTermWindow, 1); + + /* Initialize socket. If we were able to get another LXTerminal to manage the window, exit. */ + if ( ! lxterminal_socket_initialize(lxtermwin, &arguments)) + return 0; + + /* Load user preferences. */ + gchar * dir = g_build_filename(g_get_user_config_dir(), "lxterminal" , NULL); + g_mkdir_with_parents(dir, S_IRUSR | S_IWUSR | S_IXUSR); + gchar * path = g_build_filename(dir, "lxterminal.conf", NULL); + g_free(dir); + + if ( ! g_file_test(path, G_FILE_TEST_EXISTS)) + { + /* Copy the system-wide settings to the user's configuration. */ + lxtermwin->setting = load_setting_from_file(PACKAGE_DATA_DIR "/lxterminal/lxterminal.conf"); + setting_save(lxtermwin->setting); + } + else + lxtermwin->setting = load_setting_from_file(path); + + g_free(path); + + /* Finish initializing the impure area and start the first LXTerminal. */ + lxtermwin->windows = g_ptr_array_new(); + lxterminal_initialize(lxtermwin, &arguments, lxtermwin->setting); + + /* Run the main loop. */ + gtk_main(); return 0; } diff --git a/src/lxterminal.h b/src/lxterminal.h index 175d5a3..2e5e873 100644 --- a/src/lxterminal.h +++ b/src/lxterminal.h @@ -1,3 +1,23 @@ +/** + * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details. + * + * 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. + */ + #ifndef LXTERMINAL_H #define LXTERMINAL_H @@ -9,7 +29,7 @@ #define QUIT_ACCEL "<CTRL><SHIFT>Q" #define COPY_ACCEL "<CTRL><SHIFT>C" #define PASTE_ACCEL "<CTRL><SHIFT>V" -#define RENAME_TAB_ACCEL "<CTRL><SHIFT>R" +#define NAME_TAB_ACCEL "<CTRL><SHIFT>I" #define PREVIOUS_TAB_ACCEL "<CTRL>Page_Up" #define NEXT_TAB_ACCEL "<CTRL>Page_Down" #define MOVE_TAB_LEFT_ACCEL "<CTRL><SHIFT>Page_Up" @@ -19,58 +39,59 @@ #define DINGUS1 "(((news|telnet|nttp|file|http|ftp|https)://)|(www|ftp)[-A-Za-z0-9]*\\.)[-A-Za-z0-9\\.]+(:[0-9]*)?" #define DINGUS2 "(((news|telnet|nttp|file|http|ftp|https)://)|(www|ftp)[-A-Za-z0-9]*\\.)[-A-Za-z0-9\\.]+(:[0-9]*)?/[-A-Za-z0-9_\\$\\.\\+\\!\\*\\(\\),;:@&=\\?/~\\#\\%]*[^]'\\.}>\\) ,\\\"]" +/* Top level application context. */ typedef struct _lxtermwindow { - Setting *setting; - GPtrArray *windows; + Setting * setting; /* Pointer to current user preferences */ + GPtrArray * windows; /* Array of pointers to LXTerminal structures */ } LXTermWindow; -typedef struct _menu { - GtkWidget *menu; - GtkItemFactory *item_factory; - GtkAccelGroup *accel_group; -} Menu; - +/* Representative of a toplevel window. */ typedef struct _lxterminal { - LXTermWindow *parent; - gint index; - GtkWidget *mainw; - GtkWidget *box; - Menu *menubar; - GtkWidget *notebook; - GPtrArray *terms; - gint resize_idle_id; - Setting *setting; - GdkGeometry geometry; - GdkWindowHints geom_mask; - gboolean fixedsize; - gboolean rgba; - GdkColor background; - GdkColor foreground; - gint tabpos; - gboolean hidemenubar; - gboolean hidescrollbar; - gint cursormode; - gboolean cursorblinks; + LXTermWindow * parent; /* Back pointer to top level context */ + gint index; /* Index of this element in parent->windows */ + GtkWidget * window; /* Toplevel window */ + GtkWidget * box; /* Vertical box, child of toplevel window */ + GtkWidget * menu; /* Menu bar, child of vertical box */ + GtkAccelGroup * accel_group; /* Accelerator group for accelerators on this window */ + GtkWidget * notebook; /* Notebook, child of vertical box */ + GPtrArray * terms; /* Array of pointers to Term structures */ + Setting * setting; /* A copy of parent->setting */ + GdkGeometry geometry; /* Geometry hints (see XGetWMNormalHints) */ + GdkWindowHints geometry_mask; /* Mask of valid data in geometry hints */ + gboolean fixed_size; /* True if the terminal is fixed size */ + gboolean rgba; /* True if colormap is RGBA */ + GdkColor background; /* User preference background color converted to GdkColor */ + GdkColor foreground; /* User preference foreground color converted to GdkColor */ + gint tab_position; /* Tab position as an integer value */ } LXTerminal; -typedef struct _tab { - GtkWidget *main; - GtkWidget *label; - GtkWidget *close_btn; -} LXTab; - +/* Representative of a tab within a toplevel window. */ typedef struct _term { - gint index; - LXTerminal *parent; - LXTab *label; - GtkWidget *vte; - GtkWidget *scrollbar; - GtkWidget *box; - pid_t pid; - GClosure* closure; + LXTerminal * parent; /* Back pointer to LXTerminal */ + gint index; /* Index of this element in parent->terms */ + GtkWidget * tab; /* Toplevel of the tab */ + GtkWidget * label; /* Label of the tab, child of the toplevel */ + gboolean user_specified_label; /* User did "Name Tab", so we will never overwrite this with the window title */ + GtkWidget * close_button; /* Close button for the tab, child of the toplevel */ + GtkWidget * box; /* Horizontal box, child of notebook */ + GtkWidget * vte; /* VteTerminal, child of horizontal box */ + GtkWidget * scrollbar; /* Scroll bar, child of horizontal box */ + pid_t pid; /* Process ID of the process that has this as its terminal */ + GClosure * closure; /* Accelerator structure */ } Term; -LXTerminal *lxterminal_init(LXTermWindow *lxtermwin, gint argc, gchar **argv, Setting *setting); -void terminal_setting_update(LXTerminal *terminal); +/* Output of lxterminal_process_arguments. */ +typedef struct _command_arguments { + char * executable; /* Value of argv[0]; points into argument vector */ + gchar * command; /* Value of -e, --command; memory allocated by glib */ + int geometry_columns; /* Value of --geometry */ + int geometry_rows; + char * title; /* Value of -t, -T, --title; points into argument vector */ + char * working_directory; /* Value of --working-directory; points into argument vector */ +} CommandArguments; + +extern gboolean lxterminal_process_arguments(gint argc, gchar * * argv, CommandArguments * arguments); +extern LXTerminal * lxterminal_initialize(LXTermWindow * lxtermwin, CommandArguments * arguments, Setting * setting); +extern void terminal_settings_apply_to_all(LXTerminal * terminal); #endif diff --git a/src/preferences.c b/src/preferences.c index 502cd13..913e686 100644 --- a/src/preferences.c +++ b/src/preferences.c @@ -1,5 +1,6 @@ -/* +/** * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details. * * 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 @@ -25,378 +26,241 @@ #include "setting.h" #include "preferences.h" -#if (GTK_MINOR_VERSION < 12) -gchar *gdk_color_to_string(const GdkColor *color) +static void preferences_dialog_response_event(GtkWidget * dialog, gint response, LXTerminal * terminal); +static void preferences_dialog_font_set_event(GtkFontButton * widget, LXTerminal * terminal); +static void preferences_dialog_background_color_set_event(GtkColorButton * widget, LXTerminal * terminal); +static void preferences_dialog_foreground_color_set_event(GtkColorButton * widget, LXTerminal * terminal); +static void preferences_dialog_allow_bold_toggled_event(GtkToggleButton * widget, LXTerminal * terminal); +static void preferences_dialog_cursor_blink_toggled_event(GtkToggleButton * widget, LXTerminal * terminal); +static void preferences_dialog_cursor_underline_toggled_event(GtkToggleButton * widget, LXTerminal * terminal); +static void preferences_dialog_audible_bell_toggled_event(GtkToggleButton * widget, LXTerminal * terminal); +static void preferences_dialog_tab_position_changed_event(GtkComboBox * widget, LXTerminal * terminal); +static void preferences_dialog_scrollback_value_changed_event(GtkSpinButton * widget, LXTerminal * terminal); +static void preferences_dialog_hide_scroll_bar_toggled_event(GtkToggleButton * widget, LXTerminal * terminal); +static void preferences_dialog_hide_menu_bar_toggled_event(GtkToggleButton * widget, LXTerminal * terminal); +static void preferences_dialog_hide_close_button_toggled_event(GtkToggleButton * widget, LXTerminal * terminal); +static gboolean preferences_dialog_selection_focus_out_event(GtkWidget * widget, GdkEventFocus * event, LXTerminal * terminal); +static void preferences_dialog_disable_f10_toggled_event(GtkToggleButton * widget, LXTerminal * terminal); + +/* Handler for "response" signal on preferences dialog. */ +static void preferences_dialog_response_event(GtkWidget * dialog, gint response, LXTerminal * terminal) { - return g_strdup_printf("#%04x%04x%04x", color->red, color->green, color->blue); + if (response == GTK_RESPONSE_OK) + setting_save(terminal->setting); + + /* Dismiss dialog. */ + gtk_widget_destroy(dialog); } -#endif - -void lxterminal_preferences_style_constructor(Prefer *prefer, TabWidget *tab); -void lxterminal_preferences_style_destructor(Prefer *prefer, TabWidget *tab); -void lxterminal_preferences_style_save(Prefer *prefer, TabWidget *tab); -void lxterminal_preferences_display_constructor(Prefer *prefer, TabWidget *tab); -void lxterminal_preferences_display_destructor(Prefer *prefer, TabWidget *tab); -void lxterminal_preferences_display_save(Prefer *prefer, TabWidget *tab); -void lxterminal_preferences_misc_constructor(Prefer *prefer, TabWidget *tab); -void lxterminal_preferences_misc_destructor(Prefer *prefer, TabWidget *tab); -void lxterminal_preferences_misc_save(Prefer *prefer, TabWidget *tab); - -static TabGroup tabs[] = { - { - N_("Style"), - "preferences-style.png", - lxterminal_preferences_style_constructor, - lxterminal_preferences_style_destructor, - lxterminal_preferences_style_save - }, - { - N_("Display"), - "preferences-display.png", - lxterminal_preferences_display_constructor, - lxterminal_preferences_display_destructor, - lxterminal_preferences_display_save - }, - { - N_("Misc"), - "preferences-misc.png", - lxterminal_preferences_misc_constructor, - lxterminal_preferences_misc_destructor, - lxterminal_preferences_misc_save - } -}; - -void lxterminal_preferences_style_constructor(Prefer *prefer, TabWidget *tab) + +/* Handler for "font-set" signal on Terminal Font font button. */ +static void preferences_dialog_font_set_event(GtkFontButton * widget, LXTerminal * terminal) { - PreferStyle *ps; - - /* create general data structure */ - ps = g_new0(PreferStyle, 1); - tab->childs = ps; - - ps->box = gtk_table_new(4, 4, FALSE); - gtk_table_set_row_spacings(GTK_TABLE(ps->box), 3); - gtk_table_set_col_spacings(GTK_TABLE(ps->box), 5); - - /* terminal font */ - ps->font_label = gtk_label_new(_("Terminal Font:")); - gtk_misc_set_alignment(GTK_MISC(ps->font_label), 1, 0.5); - ps->font_button = gtk_font_button_new_with_font(prefer->terminal->setting->fontname); - gtk_table_attach_defaults(GTK_TABLE(ps->box), ps->font_label, 0,2, 0,1); - gtk_table_attach_defaults(GTK_TABLE(ps->box), ps->font_button, 2,4, 0,1); - - /* Background color */ - ps->bgcolor_label = gtk_label_new(_("Background:")); - gtk_misc_set_alignment(GTK_MISC(ps->bgcolor_label), 1, 0.5); - ps->bgcolor_entry = gtk_color_button_new_with_color(&prefer->terminal->background); - gtk_color_button_set_use_alpha(GTK_COLOR_BUTTON(ps->bgcolor_entry), TRUE); - gtk_color_button_set_alpha(GTK_COLOR_BUTTON(ps->bgcolor_entry), prefer->terminal->setting->bgalpha); - gtk_table_attach_defaults(GTK_TABLE(ps->box), ps->bgcolor_label, 0,2, 1,2); - gtk_table_attach_defaults(GTK_TABLE(ps->box), ps->bgcolor_entry, 2,4, 1,2); - - /* Foreground color */ - ps->fgcolor_label = gtk_label_new(_("Foreground:")); - gtk_misc_set_alignment(GTK_MISC(ps->fgcolor_label), 1, 0.5); - ps->fgcolor_entry = gtk_color_button_new_with_color(&prefer->terminal->foreground); - gtk_table_attach_defaults(GTK_TABLE(ps->box), ps->fgcolor_label, 0,2, 2,3); - gtk_table_attach_defaults(GTK_TABLE(ps->box), ps->fgcolor_entry, 2,4, 2,3); - - /* Cursor Blinks */ - ps->cursorblinks_label = gtk_label_new(_("Cursor Blinks:")); - gtk_misc_set_alignment(GTK_MISC(ps->cursorblinks_label), 1, 0.5); - ps->cursorblinks_checkbox = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ps->cursorblinks_checkbox), prefer->terminal->setting->cursorblinks); - gtk_table_attach_defaults(GTK_TABLE(ps->box), ps->cursorblinks_label, 0,2,3,4); - gtk_table_attach_defaults(GTK_TABLE(ps->box), ps->cursorblinks_checkbox, 2,4,3,4); - - /* adding to page */ - gtk_container_add(GTK_CONTAINER(tab->page), ps->box); + g_free(terminal->setting->font_name); + terminal->setting->font_name = g_strdup(gtk_font_button_get_font_name(widget)); + terminal->setting->geometry_change = TRUE; /* Force the terminals to resize */ + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_display_constructor(Prefer *prefer, TabWidget *tab) +/* Handler for "color-set" signal on Background Color color button. */ +static void preferences_dialog_background_color_set_event(GtkColorButton * widget, LXTerminal * terminal) { - PreferDisplay *pd; - - /* create general data structure */ - pd = g_new0(PreferDisplay, 1); - tab->childs = pd; - - pd->box = gtk_table_new(4,4, FALSE); - gtk_table_set_row_spacings(GTK_TABLE(pd->box), 3); - gtk_table_set_col_spacings(GTK_TABLE(pd->box), 5); - - /* tab-panel position */ - pd->tabpos_label = gtk_label_new(_("Tab panel position:")); - gtk_misc_set_alignment(GTK_MISC(pd->tabpos_label), 1, 0.5); - pd->tabpos_combobox = gtk_combo_box_new_text(); - gtk_combo_box_append_text(GTK_COMBO_BOX (pd->tabpos_combobox), _("Top")); - gtk_combo_box_append_text(GTK_COMBO_BOX (pd->tabpos_combobox), _("Bottom")); - gtk_combo_box_append_text(GTK_COMBO_BOX (pd->tabpos_combobox), _("Left")); - gtk_combo_box_append_text(GTK_COMBO_BOX (pd->tabpos_combobox), _("Right")); - gtk_combo_box_set_active (GTK_COMBO_BOX (pd->tabpos_combobox), prefer->terminal->tabpos); - gtk_table_attach_defaults(GTK_TABLE(pd->box), pd->tabpos_label, 0,2, 0,1); - gtk_table_attach_defaults(GTK_TABLE(pd->box), pd->tabpos_combobox, 2,4, 0,1); - - /* Scrollback buffer */ - pd->scrollback_label = gtk_label_new(_("Scrollback lines:")); - gtk_misc_set_alignment(GTK_MISC(pd->scrollback_label), 1, 0.5); - pd->scrollback_entry = gtk_spin_button_new_with_range(100, 100000, 10); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(pd->scrollback_entry), (gdouble)prefer->terminal->setting->scrollback); - gtk_table_attach_defaults(GTK_TABLE(pd->box), pd->scrollback_label, 0,2, 1,2); - gtk_table_attach_defaults(GTK_TABLE(pd->box), pd->scrollback_entry, 2,4, 1,2); - - /* Hide scroll bar */ - pd->hidescrollbar_label = gtk_label_new(_("Hide scroll bar:")); - gtk_misc_set_alignment(GTK_MISC(pd->hidescrollbar_label), 1, 0.5); - pd->hidescrollbar_checkbox = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pd->hidescrollbar_checkbox), prefer->terminal->setting->hidescrollbar); - gtk_table_attach_defaults(GTK_TABLE(pd->box), pd->hidescrollbar_label, 0,2,2,3); - gtk_table_attach_defaults(GTK_TABLE(pd->box), pd->hidescrollbar_checkbox, 2,4,2,3); - - /* Hide menu bar */ - pd->hidemenubar_label = gtk_label_new(_("Hide menu bar:")); - gtk_misc_set_alignment(GTK_MISC(pd->hidemenubar_label), 1, 0.5); - pd->hidemenubar_checkbox = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pd->hidemenubar_checkbox), prefer->terminal->setting->hidemenubar); - gtk_table_attach_defaults(GTK_TABLE(pd->box), pd->hidemenubar_label, 0,2,3,4); - gtk_table_attach_defaults(GTK_TABLE(pd->box), pd->hidemenubar_checkbox, 2,4,3,4); - - /* adding to page */ - gtk_container_add(GTK_CONTAINER(tab->page), pd->box); + gtk_color_button_get_color(widget, &terminal->setting->background_color); + terminal->setting->background_alpha = gtk_color_button_get_alpha(widget); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_misc_constructor(Prefer *prefer, TabWidget *tab) +/* Handler for "color-set" signal on Foreground Color color button. */ +static void preferences_dialog_foreground_color_set_event(GtkColorButton * widget, LXTerminal * terminal) { - PreferMisc *pm; - - /* create general data structure */ - pm = g_new0(PreferMisc, 1); - tab->childs = pm; - - pm->box = gtk_table_new(2,4, FALSE); - gtk_table_set_row_spacings(GTK_TABLE(pm->box), 3); - gtk_table_set_col_spacings(GTK_TABLE(pm->box), 5); - - /* Select-by-word */ - pm->selchars_label = gtk_label_new(_("Select-by-word characters:")); - gtk_misc_set_alignment(GTK_MISC(pm->selchars_label), 1, 0.5); - pm->selchars_entry = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(pm->selchars_entry), prefer->terminal->setting->selchars); - gtk_table_attach_defaults(GTK_TABLE(pm->box), pm->selchars_label, 0,2, 0,1); - gtk_table_attach_defaults(GTK_TABLE(pm->box), pm->selchars_entry, 2,4, 0,1); - - /* Disable F10 for menu */ - pm->disablef10_label = gtk_label_new(_("Disable F10 shortcut for menu:")); - gtk_misc_set_alignment(GTK_MISC(pm->disablef10_label), 1, 0.5); - pm->disablef10_checkbox = gtk_check_button_new(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pm->disablef10_checkbox), prefer->terminal->setting->disablef10); - gtk_table_attach_defaults(GTK_TABLE(pm->box), pm->disablef10_label, 0,2, 1,2); - gtk_table_attach_defaults(GTK_TABLE(pm->box), pm->disablef10_checkbox, 2,4, 1,2); - - /* adding to page */ - gtk_container_add(GTK_CONTAINER(tab->page), pm->box); + gtk_color_button_get_color(widget, &terminal->setting->foreground_color); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_style_destructor(Prefer *prefer, TabWidget *tab) +/* Handler for "toggled" signal on Allow Bold toggle button. + * Use the complement so the default is FALSE. */ +static void preferences_dialog_allow_bold_toggled_event(GtkToggleButton * widget, LXTerminal * terminal) { - PreferStyle *ps = tab->childs; - - g_free(ps); + terminal->setting->disallow_bold = ! gtk_toggle_button_get_active(widget); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_display_destructor(Prefer *prefer, TabWidget *tab) +/* Handler for "toggled" signal on Cursor Blink toggle button. */ +static void preferences_dialog_cursor_blink_toggled_event(GtkToggleButton * widget, LXTerminal * terminal) { - PreferDisplay *pd = tab->childs; - - g_free(pd); + terminal->setting->cursor_blink = gtk_toggle_button_get_active(widget); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_misc_destructor(Prefer *prefer, TabWidget *tab) +/* Handler for "toggled" signal on Cursor Underline radio button. */ +static void preferences_dialog_cursor_underline_toggled_event(GtkToggleButton * widget, LXTerminal * terminal) { - PreferMisc *pm = tab->childs; - - g_free(pm); + terminal->setting->cursor_underline = gtk_toggle_button_get_active(widget); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_style_save(Prefer *prefer, TabWidget *tab) +/* Handler for "toggled" signal on Audible Bell radio button. */ +static void preferences_dialog_audible_bell_toggled_event(GtkToggleButton * widget, LXTerminal * terminal) { - PreferStyle *ps = tab->childs; - - g_free( prefer->terminal->setting->fontname ); - g_free( prefer->terminal->setting->selchars ); - prefer->terminal->setting->fontname = g_strdup( gtk_font_button_get_font_name((GtkFontButton *)ps->font_button) ); - prefer->terminal->setting->bgalpha = (guint16)gtk_color_button_get_alpha(GTK_COLOR_BUTTON(ps->bgcolor_entry)); - prefer->terminal->setting->cursorblinks = (gboolean)gtk_toggle_button_get_active((GtkToggleButton *)ps->cursorblinks_checkbox); - - /* background and foreground */ - gtk_color_button_get_color( GTK_COLOR_BUTTON(ps->bgcolor_entry), &prefer->terminal->background); - prefer->terminal->setting->bgcolor = g_strdup( gdk_color_to_string(&prefer->terminal->background) ); - gtk_color_button_get_color(GTK_COLOR_BUTTON(ps->fgcolor_entry), &prefer->terminal->foreground); - prefer->terminal->setting->fgcolor = g_strdup( gdk_color_to_string(&prefer->terminal->foreground) ); + terminal->setting->audible_bell = gtk_toggle_button_get_active(widget); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_display_save(Prefer *prefer, TabWidget *tab) +/* Handler for "changed" signal on Tab Position combo box. */ +static void preferences_dialog_tab_position_changed_event(GtkComboBox * widget, LXTerminal * terminal) { - PreferDisplay *pd = tab->childs; - - prefer->terminal->setting->scrollback = (glong)gtk_spin_button_get_value_as_int((GtkSpinButton *)pd->scrollback_entry); - prefer->terminal->setting->hidemenubar = (gboolean)gtk_toggle_button_get_active((GtkToggleButton *)pd->hidemenubar_checkbox); - prefer->terminal->setting->hidescrollbar = (gboolean)gtk_toggle_button_get_active((GtkToggleButton *)pd->hidescrollbar_checkbox); - - /* Tab position */ - g_free( prefer->terminal->setting->tabpos ); - prefer->terminal->tabpos = gtk_combo_box_get_active((GtkComboBox *)pd->tabpos_combobox); - switch(prefer->terminal->tabpos) { - case 0: - prefer->terminal->setting->tabpos = g_strdup("top"); - break; - case 1: - prefer->terminal->setting->tabpos = g_strdup("bottom"); - break; - case 2: - prefer->terminal->setting->tabpos = g_strdup("left"); - break; - case 3: - prefer->terminal->setting->tabpos = g_strdup("right"); - break; - } + /* Convert the index into a string, which is what we put in the configuration file. */ + char * p = NULL; + switch (gtk_combo_box_get_active(widget)) + { + case 0: p = "top"; break; + case 1: p = "bottom"; break; + case 2: p = "left"; break; + case 3: p = "right"; break; + } + if (p != NULL) + { + g_free(terminal->setting->tab_position); + terminal->setting->tab_position = g_strdup(p); + } + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_misc_save(Prefer *prefer, TabWidget *tab) +/* Handler for "value-changed" signal on Scrollback spin button. */ +static void preferences_dialog_scrollback_value_changed_event(GtkSpinButton * widget, LXTerminal * terminal) { - PreferMisc *pm = tab->childs; - - prefer->terminal->setting->selchars = g_strdup( gtk_entry_get_text((GtkEntry *)pm->selchars_entry) ); - prefer->terminal->setting->disablef10 = (gboolean)gtk_toggle_button_get_active((GtkToggleButton *)pm->disablef10_checkbox); + terminal->setting->scrollback = gtk_spin_button_get_value_as_int(widget); + terminal_settings_apply_to_all(terminal); } -TabWidget *lxterminal_preferences_page_new(TabGroup *tabgroup) +/* Handler for "toggled" signal on Hide Scroll Bar toggle button. */ +static void preferences_dialog_hide_scroll_bar_toggled_event(GtkToggleButton * widget, LXTerminal * terminal) { - TabWidget *tab; - - tab = g_new0(TabWidget, 1); - - /* create container without window */ - tab->page = gtk_event_box_new(); - GTK_WIDGET_SET_FLAGS(tab->page, GTK_NO_WINDOW); - - /* Sets the border width of the container. */ - gtk_container_set_border_width(GTK_CONTAINER(tab->page), 10); - - /* create container for label */ - tab->label_box = gtk_hbox_new(FALSE, 4); - - /* create icon */ - /* tab->icon = gtk_image_new_from_file(tabgroup->icon); */ - - /* create label */ - tab->label = gtk_label_new(_(tabgroup->name)); - gtk_misc_set_alignment(GTK_MISC(tab->label), 0.0, 0.5); - gtk_misc_set_padding(GTK_MISC(tab->label), 2, 2); - - /* add all of widgets to label box */ - /* gtk_box_pack_start(GTK_BOX(tab->label_box), tab->icon, FALSE, FALSE, 0); */ - gtk_box_pack_start(GTK_BOX(tab->label_box), tab->label, FALSE, FALSE, 0); - - gtk_widget_show_all(tab->label_box); - - return tab; + terminal->setting->hide_scroll_bar = gtk_toggle_button_get_active(widget); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_page_init(Prefer *prefer) +/* Handler for "toggled" signal on Hide Menu Bar toggle button. */ +static void preferences_dialog_hide_menu_bar_toggled_event(GtkToggleButton * widget, LXTerminal * terminal) { - TabWidget *tab; - int i; - - /* initializing all of tabs */ - for (i=0;i<G_N_ELEMENTS(tabs);i++) { - tab = lxterminal_preferences_page_new(&tabs[i]); - tabs[i].constructor(prefer, tab); - - /* add to Array */ - g_ptr_array_add(prefer->tab, tab); - - /* add to gtknotebook */ - gtk_notebook_append_page(GTK_NOTEBOOK(prefer->notebook), tab->page, tab->label_box); - } + terminal->setting->hide_menu_bar = gtk_toggle_button_get_active(widget); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_free(gpointer prefer_p, GObject * where_the_object_was) +/* Handler for "toggled" signal on Hide Close Button toggle button. */ +static void preferences_dialog_hide_close_button_toggled_event(GtkToggleButton * widget, LXTerminal * terminal) { - Prefer * prefer = (Prefer *) prefer_p; - TabWidget *tab; - int i; - - for (i=0;i<prefer->tab->len;i++) { - tab = g_ptr_array_index(prefer->tab, i); - tabs[i].destructor(prefer, tab); + terminal->setting->hide_close_button = gtk_toggle_button_get_active(widget); + terminal_settings_apply_to_all(terminal); +} - g_free(tab); - } +/* Handler for "focus-out-event" on Selection Characters entry. */ +static gboolean preferences_dialog_selection_focus_out_event(GtkWidget * widget, GdkEventFocus * event, LXTerminal * terminal) +{ + const gchar * new_text = gtk_entry_get_text(GTK_ENTRY(widget)); + g_free(terminal->setting->word_selection_characters); + terminal->setting->word_selection_characters = g_strdup(new_text); + terminal_settings_apply_to_all(terminal); + return FALSE; +} - g_ptr_array_free(prefer->tab, TRUE); - gtk_widget_destroy(prefer->dialog); - g_free(prefer); +/* Handler for "toggled" signal on Disable F10 toggle button. */ +static void preferences_dialog_disable_f10_toggled_event(GtkToggleButton * widget, LXTerminal * terminal) +{ + terminal->setting->disable_f10 = gtk_toggle_button_get_active(widget); + terminal_settings_apply_to_all(terminal); } -void lxterminal_preferences_on_response(GtkDialog* dlg, gint response, Prefer *prefer) +/* Convert the user preference on tab position, expressed as a string, to the internal representation. + * These have to match the order in the .glade file. */ +gint terminal_tab_get_position_id(gchar * position) { - gint i; - TabWidget *tab; - - if(G_LIKELY(response == GTK_RESPONSE_OK)) { - for (i=0;i<prefer->tab->len;i++) { - tab = g_ptr_array_index(prefer->tab, i); - tabs[i].save(prefer, tab); - } - - /* saving perferences */ - setting_save(prefer->terminal->setting); - - /* update NOW! */ - /* update all terminals in all windows */ - g_ptr_array_foreach(prefer->terminal->parent->windows, - (GFunc) terminal_setting_update, - prefer->terminal->setting); - } - - gtk_widget_destroy((GtkWidget *)dlg); + if (strcmp(position, "bottom") == 0) + return 1; + else if (strcmp(position, "left") == 0) + return 2; + else if (strcmp(position, "right") == 0) + return 3; + else + return 0; } -void lxterminal_preferences_dialog(GtkAction *action, gpointer data) +/* Initialize and display the preferences dialog. */ +void terminal_preferences_dialog(GtkAction * action, LXTerminal * terminal) { - LXTerminal *terminal = (LXTerminal *)data; - Prefer *prefer; - GtkWidget *pdialog; - GtkWidget *tab; - - prefer = g_new0(Prefer, 1); - prefer->terminal = terminal; - prefer->tab = g_ptr_array_new(); - - /* create window */ - prefer->dialog = gtk_dialog_new_with_buttons(_("Preferences"), - NULL, - GTK_DIALOG_NO_SEPARATOR, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_OK, - NULL ); - gtk_dialog_set_alternative_button_order((GtkDialog *)prefer->dialog, GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); - gtk_dialog_set_default_response((GtkDialog *)prefer->dialog, GTK_RESPONSE_OK); - gtk_window_set_position(GTK_WINDOW(prefer->dialog), GTK_WIN_POS_CENTER); - - /* g_signal */ - g_signal_connect(prefer->dialog, "response", G_CALLBACK(lxterminal_preferences_on_response), prefer); - g_object_weak_ref((GObject *)prefer->dialog, lxterminal_preferences_free, prefer); - - /* create notebook */ - prefer->notebook = gtk_notebook_new(); - gtk_notebook_set_scrollable(GTK_NOTEBOOK(prefer->notebook), TRUE); - gtk_notebook_set_show_tabs(GTK_NOTEBOOK(prefer->notebook), TRUE); - gtk_box_pack_start(GTK_BOX(((GtkDialog *)prefer->dialog)->vbox), prefer->notebook, FALSE, FALSE, 0); - - /* initializing pages */ - lxterminal_preferences_page_init(prefer); - - gtk_widget_show_all(prefer->dialog); + Setting * setting = terminal->setting; + + GtkBuilder * builder = gtk_builder_new(); + if ( ! gtk_builder_add_from_file(builder, PACKAGE_DATA_DIR "/lxterminal/lxterminal-preferences.ui", NULL)) + { + g_object_unref(builder); + return; + } + + GtkWidget * dialog = GTK_WIDGET(gtk_builder_get_object(builder, "lxterminal_preferences")); + gtk_window_set_title(GTK_WINDOW(dialog), _("LXTerminal")); + gtk_window_set_icon_from_file(GTK_WINDOW(dialog), PACKAGE_DATA_DIR "/pixmaps/lxterminal.png", NULL); + g_signal_connect(G_OBJECT(dialog), "response", G_CALLBACK(preferences_dialog_response_event), terminal); + + GtkWidget * w = GTK_WIDGET(gtk_builder_get_object(builder, "terminal_font")); + gtk_font_button_set_font_name(GTK_FONT_BUTTON(w), setting->font_name); + g_signal_connect(G_OBJECT(w), "font-set", G_CALLBACK(preferences_dialog_font_set_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "background_color")); + gtk_color_button_set_color(GTK_COLOR_BUTTON(w), &setting->background_color); + gtk_color_button_set_alpha(GTK_COLOR_BUTTON(w), setting->background_alpha); + g_signal_connect(G_OBJECT(w), "color-set", G_CALLBACK(preferences_dialog_background_color_set_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "foreground_color")); + gtk_color_button_set_color(GTK_COLOR_BUTTON(w), &setting->foreground_color); + g_signal_connect(G_OBJECT(w), "color-set", G_CALLBACK(preferences_dialog_foreground_color_set_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "allow_bold")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), ! setting->disallow_bold); + g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(preferences_dialog_allow_bold_toggled_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "cursor_blink")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), setting->cursor_blink); + g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(preferences_dialog_cursor_blink_toggled_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "cursor_style_block")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), ! setting->cursor_underline); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "cursor_style_underline")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), setting->cursor_underline); + g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(preferences_dialog_cursor_underline_toggled_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "audible_bell")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), setting->audible_bell); + g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(preferences_dialog_audible_bell_toggled_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "tab_position")); + gtk_combo_box_set_active(GTK_COMBO_BOX(w), terminal_tab_get_position_id(setting->tab_position)); + g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(preferences_dialog_tab_position_changed_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "scrollback_lines")); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), setting->scrollback); + g_signal_connect(G_OBJECT(w), "value-changed", G_CALLBACK(preferences_dialog_scrollback_value_changed_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "hide_scroll_bar")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), setting->hide_scroll_bar); + g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(preferences_dialog_hide_scroll_bar_toggled_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "hide_menu_bar")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), setting->hide_menu_bar); + g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(preferences_dialog_hide_menu_bar_toggled_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "hide_close_button")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), setting->hide_close_button); + g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(preferences_dialog_hide_close_button_toggled_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "select_by_word")); + gtk_entry_set_text(GTK_ENTRY(w), setting->word_selection_characters); + g_signal_connect(G_OBJECT(w), "focus-out-event", G_CALLBACK(preferences_dialog_selection_focus_out_event), terminal); + + w = GTK_WIDGET(gtk_builder_get_object(builder, "disable_f10")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), setting->disable_f10); + g_signal_connect(G_OBJECT(w), "toggled", G_CALLBACK(preferences_dialog_disable_f10_toggled_event), terminal); + + gtk_widget_show_all(dialog); + g_object_unref(builder); } diff --git a/src/preferences.h b/src/preferences.h index 6f86315..f1665e7 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -1,61 +1,27 @@ +/** + * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details. + * + * 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. + */ + #ifndef LXTERMINAL_PREFERENCES_H #define LXTERMINAL_PREFERENCES_H -typedef struct { - GtkWidget *label_box; - GtkWidget *icon; - GtkWidget *label; - GtkWidget *page; - void *childs; -} TabWidget; - -typedef struct { - LXTerminal *terminal; - GtkWidget *dialog; - GtkWidget *notebook; - GPtrArray *tab; -} Prefer; - -typedef struct { - gchar *name; - gchar *icon; - void (*constructor)(Prefer *prefer, TabWidget *tab); - void (*destructor)(Prefer *prefer, TabWidget *tab); - void (*save)(Prefer *prefer, TabWidget *tab); -} TabGroup; - -typedef struct { - GtkWidget *box; - GtkWidget *font_label; - GtkWidget *font_button; - GtkWidget *bgcolor_label; - GtkWidget *bgcolor_entry; - GtkWidget *fgcolor_label; - GtkWidget *fgcolor_entry; - GtkWidget *cursorblinks_label; - GtkWidget *cursorblinks_checkbox; -} PreferStyle; - -typedef struct { - GtkWidget *box; - GtkWidget *tabpos_label; - GtkWidget *tabpos_combobox; - GtkWidget *scrollback_label; - GtkWidget *scrollback_entry; - GtkWidget *hidescrollbar_label; - GtkWidget *hidescrollbar_checkbox; - GtkWidget *hidemenubar_label; - GtkWidget *hidemenubar_checkbox; -} PreferDisplay; - -typedef struct { - GtkWidget *box; - GtkWidget *selchars_label; - GtkWidget *selchars_entry; - GtkWidget *disablef10_label; - GtkWidget *disablef10_checkbox; -} PreferMisc; - -void lxterminal_preferences_dialog(GtkAction *action, gpointer data); +extern void terminal_preferences_dialog(GtkAction * action, LXTerminal * terminal); +extern gint terminal_tab_get_position_id(gchar * position); #endif diff --git a/src/setting.c b/src/setting.c index f4a3187..7c225b9 100644 --- a/src/setting.c +++ b/src/setting.c @@ -1,5 +1,6 @@ -/* +/** * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details. * * 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 @@ -20,121 +21,106 @@ #include <glib.h> #include <gtk/gtk.h> #include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> #include "setting.h" -void setting_save_to_file(const char *path, const char *data) +/* Save settings to configuration file. */ +void setting_save(Setting * setting) { - FILE *fp; - - if (!g_file_test(path, G_FILE_TEST_EXISTS)) - g_creat(path, 0700); - - /* open config file */ - fp = fopen(path, "w"); - if (fp != NULL) - { - fputs(data, fp); - fclose(fp); - } + /* Push settings to GKeyFile. */ + g_key_file_set_string(setting->keyfile, "general", "fontname", setting->font_name); + gchar * p = gdk_color_to_string(&setting->background_color); + if (p != NULL) + g_key_file_set_string(setting->keyfile, "general", "bgcolor", p); + g_free(p); + g_key_file_set_integer(setting->keyfile, "general", "bgalpha", setting->background_alpha); + p = gdk_color_to_string(&setting->foreground_color); + if (p != NULL) + g_key_file_set_string(setting->keyfile, "general", "fgcolor", p); + g_free(p); + g_key_file_set_boolean(setting->keyfile, "general", "disallowbold", setting->disallow_bold); + g_key_file_set_boolean(setting->keyfile, "general", "cursorblinks", setting->cursor_blink); + g_key_file_set_boolean(setting->keyfile, "general", "cursorunderline", setting->cursor_underline); + g_key_file_set_boolean(setting->keyfile, "general", "audiblebell", setting->audible_bell); + g_key_file_set_string(setting->keyfile, "general", "tabpos", setting->tab_position); + g_key_file_set_integer(setting->keyfile, "general", "scrollback", setting->scrollback); + g_key_file_set_boolean(setting->keyfile, "general", "hidescrollbar", setting->hide_scroll_bar); + g_key_file_set_boolean(setting->keyfile, "general", "hidemenubar", setting->hide_menu_bar); + g_key_file_set_boolean(setting->keyfile, "general", "hideclosebutton", setting->hide_close_button); + g_key_file_set_string(setting->keyfile, "general", "selchars", setting->word_selection_characters); + g_key_file_set_boolean(setting->keyfile, "general", "disablef10", setting->disable_f10); + + /* Convert GKeyFile to text and build path to configuration file. */ + gchar * file_data = g_key_file_to_data(setting->keyfile, NULL, NULL); + gchar * path = g_build_filename(g_get_user_config_dir(), "lxterminal/lxterminal.conf", NULL); + + if ((file_data != NULL) && (path != NULL)) + { + /* Create the file if necessary. */ + int fd = open(path, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + if (fd < 0) + g_warning("Configuration file create failed: %s\n", g_strerror(errno)); + else + { + write(fd, file_data, strlen(file_data)); + close(fd); + } + } + + /* Deallocate memory. */ + g_free(file_data); + g_free(path); } -void setting_save(Setting *setting) +/* Load settings from configuration file. */ +Setting * load_setting_from_file(const char * filename) { - gchar *path; - gchar *file_data; - - /* build config path */ - path = g_build_filename(g_get_user_config_dir(), "lxterminal/lxterminal.conf", NULL); - - /* push settings to GKeyFile */ - g_key_file_set_string(setting->keyfile, "general", "fontname", setting->fontname); - g_key_file_set_string(setting->keyfile, "general", "selchars", setting->selchars); - g_key_file_set_string(setting->keyfile, "general", "bgcolor", setting->bgcolor); - g_key_file_set_integer(setting->keyfile, "general", "bgalpha", setting->bgalpha); - g_key_file_set_string(setting->keyfile, "general", "fgcolor", setting->fgcolor); - g_key_file_set_string(setting->keyfile, "general", "tabpos", setting->tabpos); - g_key_file_set_integer(setting->keyfile, "general", "scrollback", (gint)setting->scrollback); - g_key_file_set_boolean(setting->keyfile, "general", "disablef10", setting->disablef10); - g_key_file_set_boolean(setting->keyfile, "general", "hidemenubar", setting->hidemenubar); - g_key_file_set_boolean(setting->keyfile, "general", "hidescrollbar", setting->hidescrollbar); - g_key_file_set_boolean(setting->keyfile, "general", "cursorblinks", setting->cursorblinks); - - /* generate config data */ - file_data = g_key_file_to_data(setting->keyfile, NULL, NULL); - - /* save to config file */ - setting_save_to_file(path, file_data); - - /* release */ - g_free(file_data); -} - -Setting *load_setting_from_file(const char *filename) -{ - GKeyFileFlags flags; - GError *error = NULL; - Setting *setting; - - setting = (Setting *)malloc(sizeof(Setting)); - - /* initiate key_file */ - setting->keyfile = g_key_file_new(); - flags = G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS; - - /* Load config */ - if (g_file_test(filename, G_FILE_TEST_EXISTS)) { - if (!g_key_file_load_from_file(setting->keyfile, filename, flags, &error)) - goto setting_default; - - /* general setting */ - setting->fontname = g_key_file_get_string(setting->keyfile, "general", "fontname", NULL); - setting->selchars = g_key_file_get_string(setting->keyfile, "general", "selchars", NULL); - setting->bgcolor = g_key_file_get_string(setting->keyfile, "general", "bgcolor", NULL); - setting->bgalpha = g_key_file_get_integer(setting->keyfile, "general", "bgalpha", NULL); - setting->fgcolor = g_key_file_get_string(setting->keyfile, "general", "fgcolor", NULL); - setting->tabpos = g_key_file_get_string(setting->keyfile, "general", "tabpos", NULL); - setting->scrollback = (glong)g_key_file_get_integer(setting->keyfile, "general", "scrollback", NULL); - setting->disablef10 = g_key_file_get_boolean(setting->keyfile, "general", "disablef10", NULL); - setting->hidemenubar = g_key_file_get_boolean(setting->keyfile, "general", "hidemenubar", NULL); - setting->hidescrollbar = g_key_file_get_boolean(setting->keyfile, "general", "hidescrollbar", NULL); - setting->cursorblinks = g_key_file_get_boolean(setting->keyfile, "general", "cursorblinks", NULL); - } - -setting_default: - - if (!setting->fontname) - setting->fontname = g_strdup("monospace 10"); - - if (!setting->selchars) - setting->selchars = g_strdup("-A-Za-z0-9,./?%&#:_"); - - if (!setting->bgcolor) - setting->bgcolor = g_strdup("#000000"); - - if (!setting->bgalpha) - setting->bgalpha = (guint16) 0xFFFF; - - if (!setting->fgcolor) - setting->fgcolor = g_strdup("#aaaaaa"); - - if (!setting->tabpos) - setting->tabpos = g_strdup("top"); - - if (!setting->scrollback) - setting->scrollback = (glong)1000; - - if (!setting->disablef10) - setting->disablef10 = FALSE; - - if (!setting->hidemenubar) - setting->hidemenubar = FALSE; - - if (!setting->hidescrollbar) - setting->hidescrollbar = FALSE; - - if (!setting->cursorblinks) - setting->cursorblinks = FALSE; - - return setting; + /* Allocate structure. */ + Setting * setting = g_new0(Setting, 1); + + /* Initialize nonzero integer values to defaults. */ + setting->background_alpha = 65535; + setting->foreground_color.red = setting->foreground_color.green = setting->foreground_color.blue = 0xaaaa; + + /* Load configuration. */ + setting->keyfile = g_key_file_new(); + GError * error = NULL; + if ((g_file_test(filename, G_FILE_TEST_EXISTS)) + && (g_key_file_load_from_file(setting->keyfile, filename, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error))) + { + setting->font_name = g_key_file_get_string(setting->keyfile, "general", "fontname", NULL); + char * p = g_key_file_get_string(setting->keyfile, "general", "bgcolor", NULL); + if (p != NULL) + gdk_color_parse(p, &setting->background_color); + setting->background_alpha = g_key_file_get_integer(setting->keyfile, "general", "bgalpha", NULL); + p = g_key_file_get_string(setting->keyfile, "general", "fgcolor", NULL); + if (p != NULL) + gdk_color_parse(p, &setting->foreground_color); + setting->disallow_bold = g_key_file_get_boolean(setting->keyfile, "general", "disallowbold", NULL); + setting->cursor_blink = g_key_file_get_boolean(setting->keyfile, "general", "cursorblinks", NULL); + setting->cursor_underline = g_key_file_get_boolean(setting->keyfile, "general", "cursorunderline", NULL); + setting->audible_bell = g_key_file_get_boolean(setting->keyfile, "general", "audiblebell", NULL); + setting->tab_position = g_key_file_get_string(setting->keyfile, "general", "tabpos", NULL); + setting->scrollback = g_key_file_get_integer(setting->keyfile, "general", "scrollback", NULL); + setting->hide_scroll_bar = g_key_file_get_boolean(setting->keyfile, "general", "hidescrollbar", NULL); + setting->hide_menu_bar = g_key_file_get_boolean(setting->keyfile, "general", "hidemenubar", NULL); + setting->hide_close_button = g_key_file_get_boolean(setting->keyfile, "general", "hideclosebutton", NULL); + setting->word_selection_characters = g_key_file_get_string(setting->keyfile, "general", "selchars", NULL); + setting->disable_f10 = g_key_file_get_boolean(setting->keyfile, "general", "disablef10", NULL); + } + + /* Default configuration strings. */ + if (setting->font_name == NULL) + setting->font_name = g_strdup("monospace 10"); + if (setting->tab_position == NULL) + setting->tab_position = g_strdup("top"); + if (setting->word_selection_characters == NULL) + setting->word_selection_characters = g_strdup("-A-Za-z0-9,./?%&#:_"); + return setting; } diff --git a/src/setting.h b/src/setting.h index 7ae28b1..f10e904 100644 --- a/src/setting.h +++ b/src/setting.h @@ -1,21 +1,53 @@ +/** + * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details. + * + * 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. + */ + #ifndef LXTERMINAL_SETTING_H #define LXTERMINAL_SETTING_H +#include <gtk/gtk.h> + +/* User preferences. */ typedef struct _setting { - char *fontname; - char *selchars; - char *bgcolor; - char *fgcolor; - char *tabpos; - guint16 bgalpha; - glong scrollback; - gboolean disablef10; - GKeyFile *keyfile; - gboolean hidemenubar; - gboolean hidescrollbar; - gboolean cursorblinks; + + GKeyFile * keyfile; /* Pointer to GKeyFile containing settings */ + char * font_name; /* Font name */ + GdkColor background_color; /* Background color */ + guint16 background_alpha; /* Alpha value to go with background color */ + GdkColor foreground_color; /* Foreground color */ + gboolean disallow_bold; /* Disallow bolding by VTE */ + gboolean cursor_blink; /* True if cursor blinks */ + gboolean cursor_underline; /* True if underline cursor; false if block cursor */ + gboolean audible_bell; /* True if audible bell */ + char * tab_position; /* Position of tabs on main window (top, bottom, left, right) */ + gint scrollback; /* Scrollback buffer size in lines */ + gboolean hide_scroll_bar; /* True if scroll bar is NOT visible */ + gboolean hide_menu_bar; /* True if menu bar is NOT visible */ + gboolean hide_close_button; /* True if close buttons are NOT visible */ + char * word_selection_characters; /* Characters that act as word breaks during selection by word */ + gboolean disable_f10; /* True if F10 will be passed to program; false if it brings up File menu */ + + gboolean geometry_change; /* True if there is a geometry change, until it has been acted on */ + } Setting; -Setting *load_setting_from_file(const char *filename); +extern void setting_save(Setting * setting); +extern Setting * load_setting_from_file(const char * filename); #endif diff --git a/src/tab.c b/src/tab.c index 4745bcd..11f44c8 100644 --- a/src/tab.c +++ b/src/tab.c @@ -1,4 +1,4 @@ -/* +/** * Copyright 2008 Fred Chien <cfsghost@gmail.com> * * This program is free software; you can redistribute it and/or modify @@ -24,83 +24,55 @@ #include "lxterminal.h" #include "tab.h" -void lxterminal_tab_set_position(GtkWidget *notebook, gint tabpos) +/* Set the "clicked" signal handler on a tab. */ +void lxterminal_tab_label_close_button_clicked(GCallback func, Term * term) { - switch(tabpos) { - case 0: - gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP); - break; - case 1: - gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_BOTTOM); - break; - case 2: - gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_LEFT); - break; - default: - gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_RIGHT); - } + g_signal_connect(G_OBJECT(term->label->close_btn), "clicked", func, term); } -gint lxterminal_tab_get_position_id(gchar *position) +/* Set the label on a tab. */ +void lxterminal_tab_label_set_text(LXTab * tab, const gchar * str) { - if (strcmp(position, "top")==0) - return 0; - else if (strcmp(position, "bottom")==0) - return 1; - else if (strcmp(position, "left")==0) - return 2; - else - return 3; + gtk_label_set_text(GTK_LABEL(tab->label), str); } -void lxterminal_tab_label_close_button_clicked(GCallback func, Term *term) +/* Set the tooltip on a tab. */ +void lxterminal_tab_label_set_tooltip_text(LXTab * tab, const gchar * str) { - g_signal_connect(term->label->close_btn, "clicked", func, term); + gtk_widget_set_tooltip_text(tab->label, str); } -void lxterminal_tab_label_set_text(LXTab *tab, const gchar *str) +/* Create a new tab. */ +LXTab * lxterminal_tab_label_new(const gchar * str) { - gtk_label_set_text((GtkLabel *)tab->label, str); -} - -void lxterminal_tab_label_set_tooltip_text(LXTab *tab, const gchar *str) -{ - gtk_widget_set_tooltip_text(tab->label, str); -} - -LXTab *lxterminal_tab_label_new(const gchar *str) -{ - LXTab *tab; - GtkRcStyle *rcstyle; - - tab = malloc(sizeof(LXTab)); - - tab->main = gtk_hbox_new(FALSE, 4); - - /* create button */ - tab->close_btn = gtk_button_new(); - gtk_button_set_relief(GTK_BUTTON(tab->close_btn), GTK_RELIEF_NONE); - gtk_button_set_focus_on_click(GTK_BUTTON(tab->close_btn), FALSE); - gtk_container_add(GTK_CONTAINER(tab->close_btn), gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU)); - - /* make button as small as possible */ - rcstyle = gtk_rc_style_new(); - rcstyle->xthickness = rcstyle->ythickness = 0; - gtk_widget_modify_style(tab->close_btn, rcstyle); - gtk_rc_style_unref(rcstyle), - - /* create label for tab */ - tab->label = gtk_label_new(str); - gtk_widget_set_size_request(GTK_WIDGET(tab->label), 100, -1); - gtk_label_set_ellipsize(GTK_LABEL(tab->label), PANGO_ELLIPSIZE_END); - gtk_misc_set_alignment(GTK_MISC(tab->label), 0.0, 0.5); - gtk_misc_set_padding(GTK_MISC(tab->label), 0, 0); - - gtk_box_pack_start(GTK_BOX(tab->main), tab->label, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(tab->main), gtk_label_new(""), TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(tab->main), tab->close_btn, FALSE, FALSE, 0); - - gtk_widget_show_all(tab->main); - - return tab; + /* Allocate LXTab structure. */ + LXTab * tab = g_new0(LXTab, 1); + + /* Create a horizontal box as the toplevel. */ + tab->main = gtk_hbox_new(FALSE, 4); + + /* Create the Close button. */ + tab->close_btn = gtk_button_new(); + gtk_button_set_relief(GTK_BUTTON(tab->close_btn), GTK_RELIEF_NONE); + gtk_button_set_focus_on_click(GTK_BUTTON(tab->close_btn), FALSE); + gtk_container_add(GTK_CONTAINER(tab->close_btn), gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU)); + + /* Make the button as small as possible. */ + GtkRcStyle * rcstyle = gtk_rc_style_new(); + rcstyle->xthickness = rcstyle->ythickness = 0; + gtk_widget_modify_style(tab->close_btn, rcstyle); + gtk_rc_style_unref(rcstyle), + + /* Create the label. */ + tab->label = gtk_label_new(str); + gtk_widget_set_size_request(GTK_WIDGET(tab->label), 100, -1); + gtk_label_set_ellipsize(GTK_LABEL(tab->label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment(GTK_MISC(tab->label), 0.0, 0.5); + gtk_misc_set_padding(GTK_MISC(tab->label), 0, 0); + + /* Pack everything, show the widget and return. */ + gtk_box_pack_start(GTK_BOX(tab->main), tab->label, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(tab->main), tab->close_btn, FALSE, FALSE, 0); + gtk_widget_show_all(tab->main); + return tab; } diff --git a/src/tab.h b/src/tab.h index 129ab2b..d36ac90 100644 --- a/src/tab.h +++ b/src/tab.h @@ -1,12 +1,33 @@ +/** + * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * + * 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. + */ + #ifndef LXTERMINAL_TAB_H #define LXTERMINAL_TAB_H #include "lxterminal.h" -void lxterminal_tab_label_close_button_clicked(GCallback func, Term *term); -void lxterminal_tab_label_set_text(LXTab *tab, const gchar *str); -void lxterminal_tab_label_set_tooltip_text(LXTab *tab, const gchar *str); -LXTab *lxterminal_tab_label_new(const gchar *str); +extern void lxterminal_tab_set_position(GtkWidget * notebook, gint tabpos); +extern gint lxterminal_tab_get_position_id(gchar * position); +extern void lxterminal_tab_label_close_button_clicked(GCallback func, Term * term); +extern void lxterminal_tab_label_set_text(LXTab * tab, const gchar * str); +extern void lxterminal_tab_label_set_tooltip_text(LXTab * tab, const gchar * str); +extern LXTab * lxterminal_tab_label_new(const gchar * str); #endif diff --git a/src/unixsocket.c b/src/unixsocket.c index b6ca767..88f6e18 100644 --- a/src/unixsocket.c +++ b/src/unixsocket.c @@ -1,5 +1,6 @@ -/* +/** * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details. * * 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 @@ -17,7 +18,6 @@ * MA 02110-1301, USA. */ -#include <sys/time.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> @@ -25,172 +25,215 @@ #include <errno.h> #include <glib.h> #include <gtk/gtk.h> -#include <glib/gi18n.h> #include "lxterminal.h" #include "unixsocket.h" -static gboolean -lxterminal_socket_read_channel(GIOChannel *gio, GIOCondition condition, gpointer lxtermwin) -{ - GIOStatus ret; - GError *err = NULL; - gchar *msg; - gsize len; - gsize term; - - /* read messages */ - ret = g_io_channel_read_line(gio, &msg, &len, &term, &err); - if (ret == G_IO_STATUS_ERROR) - g_error("Error reading: %s\n", err->message); - - if (len > 0) { - gchar **argv; - gint argc; - - msg[term] = '\0'; - - /* generate args */ - g_shell_parse_argv(msg, &argc, &argv, NULL); +static gboolean lxterminal_socket_read_channel(GIOChannel * gio, GIOCondition condition, LXTermWindow * lxtermwin); +static gboolean lxterminal_socket_accept_client(GIOChannel * source, GIOCondition condition, LXTermWindow * lxtermwin); - /* initializing LXTerminal and create a new window */ - lxterminal_init(lxtermwin, argc, argv, ((LXTermWindow *) lxtermwin)->setting); - - /* release */ - g_strfreev(argv); - } - g_free(msg); - - if (condition & G_IO_HUP) - return FALSE; - - return TRUE; +/* Handler for successful read on communication socket. */ +static gboolean lxterminal_socket_read_channel(GIOChannel * gio, GIOCondition condition, LXTermWindow * lxtermwin) +{ + /* Read message. */ + gchar * msg = NULL; + gsize len = 0; + gsize term = 0; + GError * err = NULL; + GIOStatus ret = g_io_channel_read_line(gio, &msg, &len, &term, &err); + if (ret == G_IO_STATUS_ERROR) + g_warning("Error reading socket: %s\n", err->message); + + /* Process message. */ + if (len > 0) + { + /* Overwrite the line termination with a NUL. */ + msg[term] = '\0'; + + /* Parse arguments. + * Initialize a new LXTerminal and create a new window. */ + gint argc; + gchar * * argv; + g_shell_parse_argv(msg, &argc, &argv, NULL); + CommandArguments arguments; + lxterminal_process_arguments(argc, argv, &arguments); + lxterminal_initialize(lxtermwin, &arguments, lxtermwin->setting); + g_strfreev(argv); + } + g_free(msg); + + /* If there was a disconnect, discontinue read. Otherwise, continue. */ + if (condition & G_IO_HUP) + return FALSE; + return TRUE; } -static gboolean -lxterminal_socket_accept_client(GIOChannel *source, GIOCondition condition, gpointer lxtermwin) +/* Handler for successful listen on communication socket. */ +static gboolean lxterminal_socket_accept_client(GIOChannel * source, GIOCondition condition, LXTermWindow * lxtermwin) { - if (condition & G_IO_IN) { - GIOChannel *gio; - int fd; - int flags; - - /* new connection */ - fd = accept(g_io_channel_unix_get_fd(source), NULL, NULL); - if (fd < 0) - g_error("Accept failed: %s\n", g_strerror(errno)); - - flags = fcntl(fd, F_GETFL, 0); - fcntl(fd, F_SETFL, flags | O_NONBLOCK); - - gio = g_io_channel_unix_new(fd); - if (!gio) - g_error("Cannot create new GIOChannel!\n"); - - g_io_channel_set_encoding(gio, NULL, NULL); - - g_io_add_watch(gio, G_IO_IN | G_IO_HUP, lxterminal_socket_read_channel, lxtermwin); - - g_io_channel_unref(gio); - } - - /* our listener socket hung up - we are dead */ - if (condition & G_IO_HUP) - g_error("Server listening socket died!\n"); - - return TRUE; + if (condition & G_IO_IN) + { + /* Accept the new connection. */ + int fd = accept(g_io_channel_unix_get_fd(source), NULL, NULL); + if (fd < 0) + g_warning("Accept failed: %s\n", g_strerror(errno)); + + /* Add O_NONBLOCK to the flags. */ + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + + /* Create a glib I/O channel. */ + GIOChannel * gio = g_io_channel_unix_new(fd); + if (gio == NULL) + g_warning("Cannot create new GIOChannel\n"); + else + { + /* Set up the glib I/O channel and add it to the event loop. */ + g_io_channel_set_encoding(gio, NULL, NULL); + g_io_add_watch(gio, G_IO_IN | G_IO_HUP, (GIOFunc) lxterminal_socket_read_channel, lxtermwin); + g_io_channel_unref(gio); + } + } + + /* Our listening socket hung up - we are dead. */ + if (condition & G_IO_HUP) + g_error("Server listening socket closed unexpectedly\n"); + + return TRUE; } -gboolean -lxterminal_socket_init(LXTermWindow *lxtermwin, int argc, char **argv) +gboolean lxterminal_socket_initialize(LXTermWindow * lxtermwin, CommandArguments * arguments) { - struct sockaddr_un skaddr; - GIOChannel *gio; - int skfd; - gchar *socket_path; - - socket_path = g_strdup_printf("/tmp/.lxterminal-socket%s-%s", gdk_get_display(), g_get_user_name()); - - /* create socket */ - skfd = socket(PF_UNIX, SOCK_STREAM, 0); - if (skfd < 0) { - if (g_file_test(socket_path, G_FILE_TEST_EXISTS)) { - unlink(socket_path); - } - - skfd = socket(PF_UNIX, SOCK_STREAM, 0); - if (skfd < 0) - g_error("Cannot create socket!"); - } - - /* Initiate socket */ - bzero(&skaddr, sizeof(skaddr)); - - /* setting UNIX socket */ - skaddr.sun_family = AF_UNIX; - snprintf(skaddr.sun_path, sizeof(skaddr.sun_path), "%s", socket_path); - - /* try to connect to current LXTerminal */ - if (connect(skfd, (struct sockaddr *)&skaddr, sizeof(skaddr)) < 0) { - unlink(socket_path); - - /* bind to socket */ - if (bind(skfd, (struct sockaddr *)&skaddr, sizeof(skaddr)) < 0) - g_error("Bind on socket failed: %s\n", g_strerror(errno)); - - /* listen on socket */ - if (listen(skfd, 5) < 0) - g_error("Listen on socket failed: %s\n", g_strerror(errno)); - - /* create I/O channel */ - gio = g_io_channel_unix_new(skfd); - if (!gio) - g_error("Cannot create new GIOChannel!\n"); - - /* setting encoding */ - g_io_channel_set_encoding(gio, NULL, NULL); - g_io_channel_set_buffered(gio, FALSE); - g_io_channel_set_close_on_unref(gio, TRUE); - - /* I/O channel into the main event loop */ - if (!g_io_add_watch(gio, G_IO_IN | G_IO_HUP, lxterminal_socket_accept_client, lxtermwin)) - g_error("Cannot add watch on GIOChannel\n"); - - /* channel will automatically shutdown when the watch returns FALSE */ - g_io_channel_set_close_on_unref(gio, TRUE); - g_io_channel_unref(gio); - - g_free(socket_path); - return TRUE; - } else { - int i; - gboolean setworkdir = FALSE; - - gio = g_io_channel_unix_new(skfd); - g_io_channel_set_encoding(gio, NULL, NULL); - - for (i=0;i<argc;i++) { - if (strncmp(argv[i],"--working-directory=", 20)==0) { - setworkdir = TRUE; - } - - g_io_channel_write_chars(gio, g_shell_quote(*(argv+i)), -1, NULL, NULL); - if (i+1!=argc) { - g_io_channel_write_chars(gio, " ", -1, NULL, NULL); - } else { - if (!setworkdir) { - gchar *workdir = g_get_current_dir(); - g_io_channel_write_chars(gio, " --working-directory=", -1, NULL, NULL); - g_io_channel_write_chars(gio, workdir, -1, NULL, NULL); - g_free(workdir); - } - } - } - - g_io_channel_write_chars(gio, "\n", -1, NULL, NULL); - g_io_channel_flush(gio, NULL); - g_io_channel_unref(gio); - g_free(socket_path); - return FALSE; - } + /* Normally, LXTerminal uses one process to control all of its windows. + * The first process to start will create a Unix domain socket in /tmp. + * It will then bind and listen on this socket. + * The subsequent processes will connect to the controller that owns the Unix domain socket. + * They will pass their command line over the socket and exit. + * + * If for any reason both the connect and bind fail, we will fall back to having that + * process be standalone; it will not be either the controller or a user of the controller. + * This behavior was introduced in response to a problem report (2973537). + * + * This function returns TRUE if this process should keep running and FALSE if it should exit. */ + + /* Formulate the path for the Unix domain socket. */ + gchar * socket_path = g_strdup_printf("/tmp/.lxterminal-socket%s-%s", gdk_get_display(), g_get_user_name()); + + /* Create socket. */ + int fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + { + g_warning("Socket create failed: %s\n", g_strerror(errno)); + g_free(socket_path); + return TRUE; + } + + /* Initialize socket address for Unix domain socket. */ + struct sockaddr_un sock_addr; + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sun_family = AF_UNIX; + snprintf(sock_addr.sun_path, sizeof(sock_addr.sun_path), "%s", socket_path); + + /* Try to connect to an existing LXTerminal process. */ + if (connect(fd, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) < 0) + { + /* Connect failed. We are the controller, unless something fails. */ + unlink(socket_path); + g_free(socket_path); + + /* Bind to socket. */ + if (bind(fd, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) < 0) + { + g_warning("Bind on socket failed: %s\n", g_strerror(errno)); + close(fd); + return TRUE; + } + + /* Listen on socket. */ + if (listen(fd, 5) < 0) + { + g_warning("Listen on socket failed: %s\n", g_strerror(errno)); + close(fd); + return TRUE; + } + + /* Create a glib I/O channel. */ + GIOChannel * gio = g_io_channel_unix_new(fd); + if (gio == NULL) + { + g_warning("Cannot create GIOChannel\n"); + close(fd); + return TRUE; + } + + /* Set up GIOChannel. */ + g_io_channel_set_encoding(gio, NULL, NULL); + g_io_channel_set_buffered(gio, FALSE); + g_io_channel_set_close_on_unref(gio, TRUE); + + /* Add I/O channel to the main event loop. */ + if ( ! g_io_add_watch(gio, G_IO_IN | G_IO_HUP, (GIOFunc) lxterminal_socket_accept_client, lxtermwin)) + { + g_warning("Cannot add watch on GIOChannel\n"); + close(fd); + g_io_channel_unref(gio); + return TRUE; + } + + /* Channel will automatically shut down when the watch returns FALSE. */ + g_io_channel_set_close_on_unref(gio, TRUE); + g_io_channel_unref(gio); + return TRUE; + } + else + { + g_free(socket_path); + + /* Create a glib I/O channel. */ + GIOChannel * gio = g_io_channel_unix_new(fd); + g_io_channel_set_encoding(gio, NULL, NULL); + + /* Reissue arguments to the socket. Start with the name of the executable. */ + g_io_channel_write_chars(gio, arguments->executable, -1, NULL, NULL); + + /* --command or -e. */ + if (arguments->command != NULL) + { + gchar * command = g_shell_quote(arguments->command); + gchar * command_argument = g_strdup_printf(" --command=%s", command); + g_io_channel_write_chars(gio, command_argument, -1, NULL, NULL); + g_free(command); + g_free(command_argument); + } + + /* --geometry. */ + if ((arguments->geometry_columns != 0) && (arguments->geometry_rows != 0)) + { + gchar * geometry = g_strdup_printf(" --geometry=%dx%d", arguments->geometry_columns, arguments->geometry_rows); + g_io_channel_write_chars(gio, geometry, -1, NULL, NULL); + g_free(geometry); + } + + /* -t, -T, or --title. */ + if (arguments->title != NULL) + { + gchar * title = g_shell_quote(arguments->title); + gchar * title_argument = g_strdup_printf(" --title=%s", title); + g_io_channel_write_chars(gio, title_argument, -1, NULL, NULL); + g_free(title); + g_free(title_argument); + } + + /* Always issue a --working-directory, either from the user's specification or the current directory. */ + gchar * working_directory = ((arguments->working_directory != NULL) ? g_shell_quote(arguments->working_directory) : g_get_current_dir()); + gchar * working_directory_argument = g_strdup_printf(" --working-directory=%s", working_directory); + g_io_channel_write_chars(gio, working_directory_argument, -1, NULL, NULL); + g_free(working_directory); + g_free(working_directory_argument); + + /* Finish up the transaction on the Unix domain socket. */ + g_io_channel_write_chars(gio, "\n", -1, NULL, NULL); + g_io_channel_flush(gio, NULL); + g_io_channel_unref(gio); + return FALSE; + } } diff --git a/src/unixsocket.h b/src/unixsocket.h index ac32e3f..db8f73c 100644 --- a/src/unixsocket.h +++ b/src/unixsocket.h @@ -1,7 +1,27 @@ +/** + * Copyright 2008 Fred Chien <cfsghost@gmail.com> + * Copyright (c) 2010 LxDE Developers, see the file AUTHORS for details. + * + * 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. + */ + #ifndef LXTERMINAL_UNIXSOCKET_H #define LXTERMINAL_UNIXSOCKET_H -gboolean lxterminal_socket_init(LXTermWindow *lxtermwin, int argc, char **argv); +extern gboolean lxterminal_socket_initialize(LXTermWindow * lxtermwin, CommandArguments * arguments); #endif -- 1.7.0.1