1// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2/* 3* Copyright (c) 2011-2017 elementary LLC. (https://elementary.io) 4* 5* This program is free software; you can redistribute it and/or 6* modify it under the terms of the GNU Lesser General Public 7* License version 3, as published by the Free Software Foundation. 8* 9* This program is distributed in the hope that it will be useful, 10* but WITHOUT ANY WARRANTY; without even the implied warranty of 11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12* General Public License for more details. 13* 14* You should have received a copy of the GNU Lesser General Public 15* License along with this program; if not, write to the 16* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17* Boston, MA 02110-1301 USA 18*/ 19 20public class Terminal.Application : Gtk.Application { 21 public static GLib.Settings saved_state; 22 public static GLib.Settings settings; 23 public static GLib.Settings settings_sys; 24 25 private GLib.List <MainWindow> windows; 26 27 public static string? working_directory = null; 28 [CCode (array_length = false, array_null_terminated = true)] 29 private static string[]? command_e = null; 30 private static string? command_x = null; 31 32 // option_help will be true if help flag was given. 33 private static bool option_help = false; 34 private static bool option_version = false; 35 36 // option_new_window will be true if the new-window flag was given. 37 private static bool option_new_window = false; 38 39 // option_new_tab will be true if the new-tab flag was given. 40 private static bool option_new_tab = false; 41 42 public int minimum_width; 43 public int minimum_height; 44 45 static construct { 46 saved_state = new GLib.Settings ("io.elementary.terminal.saved-state"); 47 settings = new GLib.Settings ("io.elementary.terminal.settings"); 48 settings_sys = new GLib.Settings ("org.gnome.desktop.interface"); 49 } 50 51 construct { 52 flags |= ApplicationFlags.HANDLES_COMMAND_LINE; 53 application_id = "io.elementary.terminal"; /* Ensures only one instance runs */ 54 55 Intl.setlocale (LocaleCategory.ALL, ""); 56 Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR); 57 Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); 58 Intl.textdomain (Config.GETTEXT_PACKAGE); 59 } 60 61 public Application () { 62 Granite.Services.Logger.initialize ("PantheonTerminal"); 63 Granite.Services.Logger.DisplayLevel = Granite.Services.LogLevel.DEBUG; 64 65 windows = new GLib.List <MainWindow> (); 66 } 67 68 public void new_window () { 69 var window = get_last_window (); 70 71 if (window == null) { 72 new MainWindow (this); 73 } else { 74 new MainWindow (this, false); 75 } 76 } 77 78 public override int command_line (ApplicationCommandLine command_line) { 79 // keep the application running until we are done with this commandline 80 hold (); 81 int res = _command_line (command_line); 82 release (); 83 return res; 84 } 85 86 public override void window_added (Gtk.Window window) { 87 windows.append (window as MainWindow); 88 base.window_added (window); 89 } 90 91 public override void window_removed (Gtk.Window window) { 92 windows.remove (window as MainWindow); 93 base.window_removed (window); 94 } 95 96 public override bool dbus_register (DBusConnection connection, string object_path) throws Error { 97 base.dbus_register (connection, object_path); 98 99 var dbus = new DBus (); 100 connection.register_object (object_path, dbus); 101 102 dbus.finished_process.connect ((id, process, exit_status) => { 103 foreach (var window in windows) { 104 foreach (var terminal in window.terminals) { 105 if (terminal.terminal_id == id) { 106 107 if (!terminal.is_init_complete ()) { 108 terminal.set_init_complete (); 109 } else { 110 var process_string = _("Process completed"); 111 var process_icon = new ThemedIcon ("process-completed-symbolic"); 112 if (exit_status != 0) { 113 process_string = _("Process exited with errors"); 114 process_icon = new ThemedIcon ("process-error-symbolic"); 115 } 116 117 if (terminal != window.current_terminal) { 118 terminal.tab.icon = process_icon; 119 } 120 121 if ((window.get_window ().get_state () & Gdk.WindowState.FOCUSED) == 0) { 122 var notification = new Notification (process_string); 123 notification.set_body (process); 124 notification.set_icon (process_icon); 125 send_notification (null, notification); 126 } 127 } 128 129 } 130 } 131 } 132 }); 133 134 return true; 135 } 136 137 private int _command_line (ApplicationCommandLine command_line) { 138 var context = new OptionContext (null); 139 context.add_main_entries (ENTRIES, "pantheon-terminal"); 140 context.add_group (Gtk.get_option_group (true)); 141 142 // Disable automatic help to prevent default `exit(0)` behaviour. 143 context.set_help_enabled (false); 144 145 string[] args = command_line.get_arguments (); 146 string commandline = ""; 147 string[] arg_opt = {}; 148 string[] arg_cmd = {}; 149 bool build_cmdline = false; 150 151 /* Everything after "--" or "-x" or "--commandline=" is to be treated as a single command to be executed 152 * (maybe with its own options) so it is not passed to the parser. It will be passed as is to a new tab/shell. 153 */ 154 foreach (unowned string s in args) { 155 if (build_cmdline) { 156 arg_cmd += s; 157 } else { 158 if (s == "--" || s == "-x" || s.has_prefix ("--commandline=")) { 159 if (s.has_prefix ("--commandline=") && s.length > 14) { 160 arg_cmd += s.substring (14); 161 } 162 163 build_cmdline = true; 164 } else { 165 arg_opt += s; 166 } 167 } 168 } 169 170 commandline = string.joinv (" ", arg_cmd); 171 172 try { 173 unowned string[] tmp = arg_opt; 174 context.parse (ref tmp); 175 } catch (Error e) { 176 stdout.printf ("pantheon-terminal: ERROR: " + e.message + "\n"); 177 return 0; 178 } 179 180 if (option_help) { 181 command_line.print (context.get_help (true, null)); 182 } else if (option_version) { 183 command_line.print ("%s %s", Config.PROJECT_NAME, Config.VERSION + "\n\n"); 184 } else { 185 if (command_e != null) { 186 run_commands (command_e, working_directory); 187 } else if (commandline.length > 0) { 188 run_command_line (commandline, working_directory); 189 } else if (command_x != null) { 190 const string WARNING = "Usage: --commandline=[COMMANDLINE] without spaces around '='\r\n\r\n"; 191 start_terminal_with_working_directory (working_directory); 192 get_last_window ().current_terminal.feed (WARNING.data); 193 } else { 194 start_terminal_with_working_directory (working_directory); 195 } 196 } 197 198 // Do not save the value until the next instance of 199 // Pantheon Terminal is started 200 command_e = null; 201 command_x = null; 202 option_help = false; 203 option_new_window = false; 204 option_new_tab = false; 205 working_directory = null; 206 207 return 0; 208 } 209 210 private void run_commands (string[] commands, string? working_directory = null) { 211 MainWindow? window; 212 window = get_last_window (); 213 214 if (window == null || option_new_window) { 215 window = new MainWindow (this, false); 216 } 217 218 foreach (string command in commands) { 219 window.add_tab_with_command (command, working_directory, option_new_tab); 220 } 221 } 222 223 private void run_command_line (string command_line, string? working_directory = null) { 224 MainWindow? window; 225 window = get_last_window (); 226 227 if (window == null || option_new_window) { 228 window = new MainWindow (this, false); 229 } 230 231 window.add_tab_with_command (command_line, working_directory, option_new_tab); 232 } 233 234 private void start_terminal_with_working_directory (string? working_directory) { 235 MainWindow? window; 236 window = get_last_window (); 237 238 if (window != null && !option_new_window) { 239 window.add_tab_with_working_directory (working_directory, null, option_new_tab); 240 window.present (); 241 } else 242 /* Uncertain whether tabs should be restored when app is launched with working directory from commandline. 243 * Currently they are set to restore (subject to the restore-tabs setting). 244 * If it is desired that tabs should never be restored in these circimstances set 3rd parameter to false 245 * below. */ 246 new MainWindow.with_working_directory (this, working_directory, window == null, option_new_tab); 247 } 248 249 private MainWindow? get_last_window () { 250 uint length = windows.length (); 251 252 return length > 0 ? windows.nth_data (length - 1) : null; 253 } 254 255 private const OptionEntry[] ENTRIES = { 256 { "version", 'v', 0, OptionArg.NONE, ref option_version, N_("Show version"), null }, 257 /* -e flag is used for running single string commands. May be more than one -e flag in cmdline */ 258 { "execute", 'e', 0, OptionArg.STRING_ARRAY, ref command_e, N_("Run a program in terminal"), "COMMAND" }, 259 260 /* -x flag is removed before OptionContext parser applied but is included here so that it appears in response 261 * to the --help flag */ 262 { "commandline", 'x', 0, OptionArg.STRING, ref command_x, 263 N_("Run remainder of line as a command in terminal. Can also use '--' as flag"), "COMMAND_LINE" }, 264 265 /* -n flag forces a new window, instead of a new tab */ 266 { "new-window", 'n', 0, OptionArg.NONE, ref option_new_window, N_("Open a new terminal window"), null }, 267 268 /* -t flag forces a new tab */ 269 { "new-tab", 't', 0, OptionArg.NONE, ref option_new_tab, N_("Open a new terminal tab"), null }, 270 271 { "help", 'h', 0, OptionArg.NONE, ref option_help, N_("Show help"), null }, 272 { "working-directory", 'w', 0, OptionArg.FILENAME, ref working_directory, 273 N_("Set shell working directory"), "DIR" }, 274 275 { null } 276 }; 277 278 public static int main (string[] args) { 279 var app = new Terminal.Application (); 280 return app.run (args); 281 } 282} 283