2014-02-27 21:45:56 -07:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
#
|
|
|
|
|
# Copyright (C) 2014 GNS3 Technologies Inc.
|
|
|
|
|
#
|
|
|
|
|
# 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
Functions to start external console terminals.
|
|
|
|
|
"""
|
2014-11-23 19:04:11 -07:00
|
|
|
|
2018-01-29 18:59:28 +07:00
|
|
|
from .qt import QtCore
|
2014-02-27 21:45:56 -07:00
|
|
|
|
2015-09-22 16:06:35 -06:00
|
|
|
import os
|
2014-02-27 21:45:56 -07:00
|
|
|
import sys
|
|
|
|
|
import shlex
|
|
|
|
|
import subprocess
|
2023-07-30 22:15:38 +10:00
|
|
|
import psutil
|
2024-11-10 18:23:01 +10:00
|
|
|
import shutil
|
2023-07-30 22:15:38 +10:00
|
|
|
|
2014-02-27 21:45:56 -07:00
|
|
|
from .main_window import MainWindow
|
2016-08-15 15:48:48 +02:00
|
|
|
from .controller import Controller
|
2014-02-27 21:45:56 -07:00
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
2015-12-02 11:21:16 +01:00
|
|
|
console_mutex = QtCore.QMutex()
|
2014-02-27 21:45:56 -07:00
|
|
|
|
2017-04-26 16:19:59 +02:00
|
|
|
|
2023-07-30 22:15:38 +10:00
|
|
|
def gnome_terminal_env():
|
|
|
|
|
|
|
|
|
|
uid = os.getuid()
|
|
|
|
|
|
|
|
|
|
# get list of processes of current user
|
|
|
|
|
procs = [p.info for p in psutil.process_iter(
|
|
|
|
|
attrs=['name', 'pid', 'ppid', 'create_time', 'uids']
|
|
|
|
|
) if p.info['uids'].real == uid]
|
|
|
|
|
|
|
|
|
|
# get pid of gnome-terminal-server process
|
|
|
|
|
gnome_terminal_server_pid = [p['pid'] for p in procs if p['name'] == "gnome-terminal-server"]
|
|
|
|
|
if not gnome_terminal_server_pid:
|
|
|
|
|
return {}
|
|
|
|
|
gnome_terminal_server_pid = gnome_terminal_server_pid[0]
|
|
|
|
|
|
|
|
|
|
# get subprocesses of gnome-terminal-server
|
|
|
|
|
gnome_terminal_server_children = [p for p in procs if p['ppid'] == gnome_terminal_server_pid]
|
|
|
|
|
gnome_terminal_server_children.sort(key=lambda p: p['create_time'], reverse=True)
|
|
|
|
|
|
|
|
|
|
# return the gnome-terminal environment variables of the first subprocess named telnet
|
|
|
|
|
for proc in gnome_terminal_server_children:
|
|
|
|
|
if proc['name'] == "telnet":
|
|
|
|
|
try:
|
|
|
|
|
env = psutil.Process(proc['pid']).environ()
|
|
|
|
|
if 'GNOME_TERMINAL_SERVICE' in env and \
|
|
|
|
|
'GNOME_TERMINAL_SCREEN' in env:
|
|
|
|
|
return {'GNOME_TERMINAL_SERVICE': env['GNOME_TERMINAL_SERVICE'],
|
|
|
|
|
'GNOME_TERMINAL_SCREEN': env['GNOME_TERMINAL_SCREEN']}
|
|
|
|
|
except psutil.Error:
|
|
|
|
|
pass
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
2014-11-23 19:04:11 -07:00
|
|
|
class ConsoleThread(QtCore.QThread):
|
2014-10-31 12:39:16 +01:00
|
|
|
|
2025-12-14 18:19:47 +08:00
|
|
|
consoleError = QtCore.Signal(str)
|
2014-10-31 12:39:16 +01:00
|
|
|
|
2016-07-20 12:00:27 +02:00
|
|
|
def __init__(self, parent, command, node, port):
|
2015-04-17 12:14:21 +02:00
|
|
|
super().__init__(parent)
|
2015-05-01 13:49:43 +02:00
|
|
|
|
2014-10-31 12:39:16 +01:00
|
|
|
self._command = command
|
2016-07-20 12:00:27 +02:00
|
|
|
self._name = node.name()
|
|
|
|
|
self._host = node.consoleHost()
|
2016-09-02 16:26:52 +02:00
|
|
|
assert self._host
|
2014-10-31 12:39:16 +01:00
|
|
|
self._port = port
|
2016-07-20 12:06:59 +02:00
|
|
|
self._node = node
|
2014-10-31 12:39:16 +01:00
|
|
|
|
2015-05-01 13:49:43 +02:00
|
|
|
def exec_command(self, command):
|
2014-10-31 12:39:16 +01:00
|
|
|
|
2015-01-05 15:32:01 -07:00
|
|
|
if sys.platform.startswith("win"):
|
|
|
|
|
# use the string on Windows
|
2023-11-03 14:43:25 +10:00
|
|
|
subprocess.Popen(command, env=os.environ)
|
2015-01-05 15:32:01 -07:00
|
|
|
else:
|
|
|
|
|
# use arguments on other platforms
|
2015-07-17 17:14:57 +02:00
|
|
|
try:
|
|
|
|
|
args = shlex.split(command)
|
|
|
|
|
except ValueError:
|
2018-01-29 18:59:28 +07:00
|
|
|
self.consoleError.emit("Syntax error in command: '{}'".format(command))
|
2015-07-17 17:14:57 +02:00
|
|
|
return
|
2023-07-30 22:15:38 +10:00
|
|
|
|
|
|
|
|
env = os.environ.copy()
|
2023-07-31 01:44:39 +10:00
|
|
|
# special case to force gnome-terminal to correctly use tabs on Linux
|
2023-07-31 12:32:49 +10:00
|
|
|
if sys.platform.startswith("linux") and "gnome-terminal" in args[0] and "--tab" in command:
|
2023-07-31 01:44:39 +10:00
|
|
|
# inject gnome-terminal environment variables
|
2023-07-31 01:34:59 +10:00
|
|
|
if "GNOME_TERMINAL_SERVICE" not in env or "GNOME_TERMINAL_SCREEN" not in env:
|
2023-07-31 01:44:39 +10:00
|
|
|
env.update(gnome_terminal_env())
|
2024-11-10 18:23:01 +10:00
|
|
|
proc = subprocess.Popen(args, env=env)
|
|
|
|
|
if sys.platform.startswith("linux"):
|
|
|
|
|
wmctrl_path = shutil.which("wmctrl")
|
|
|
|
|
if wmctrl_path:
|
|
|
|
|
proc.wait() # wait for the terminal to open
|
|
|
|
|
try:
|
|
|
|
|
# use wmctrl to raise the window based on the node name
|
2025-02-16 00:10:10 +10:00
|
|
|
subprocess.run([wmctrl_path, "-Fa", self._name], env=os.environ)
|
2024-11-10 18:23:01 +10:00
|
|
|
except OSError as e:
|
|
|
|
|
self.consoleError.emit("Count not focus on terminal window: '{}'".format(e))
|
2014-10-31 12:39:16 +01:00
|
|
|
|
2015-01-05 15:32:01 -07:00
|
|
|
def run(self):
|
|
|
|
|
|
2016-05-19 14:05:06 +02:00
|
|
|
host = self._host
|
2016-01-25 19:21:36 +01:00
|
|
|
port = self._port
|
2015-05-01 13:49:43 +02:00
|
|
|
|
|
|
|
|
# replace the place-holders by the actual values
|
|
|
|
|
command = self._command.replace("%h", host)
|
|
|
|
|
command = command.replace("%p", str(port))
|
2018-01-10 22:16:35 +07:00
|
|
|
command = command.replace("%d", self._name.replace('"', '\\"'))
|
2023-10-31 14:43:50 +10:00
|
|
|
command = command.replace("%P", self._node.project().name().replace('"', '\\"'))
|
2016-07-20 12:06:59 +02:00
|
|
|
command = command.replace("%i", self._node.project().id())
|
2017-04-27 15:46:39 +02:00
|
|
|
command = command.replace("%n", str(self._node.id()))
|
2016-08-15 15:48:48 +02:00
|
|
|
command = command.replace("%c", Controller.instance().httpClient().fullUrl())
|
2015-05-01 13:49:43 +02:00
|
|
|
|
2023-10-31 14:43:50 +10:00
|
|
|
command = command.replace("{host}", host)
|
2023-10-31 12:33:52 +10:00
|
|
|
command = command.replace("{port}", str(port))
|
|
|
|
|
command = command.replace("{name}", self._name.replace('"', '\\"'))
|
2023-10-31 14:43:50 +10:00
|
|
|
command = command.replace("{project}", self._node.project().name().replace('"', '\\"'))
|
2023-10-31 12:33:52 +10:00
|
|
|
command = command.replace("{project_id}", self._node.project().id())
|
|
|
|
|
command = command.replace("{node_id}", str(self._node.id()))
|
|
|
|
|
command = command.replace("{url}", Controller.instance().httpClient().fullUrl())
|
|
|
|
|
|
2015-12-02 11:21:16 +01:00
|
|
|
# If the console use an apple script we lock to avoid multiple console
|
2016-05-18 11:27:38 +02:00
|
|
|
# to interact at the same time
|
2015-12-02 11:21:16 +01:00
|
|
|
if sys.platform.startswith("darwin") and "osascript" in command:
|
|
|
|
|
console_mutex.lock()
|
|
|
|
|
|
2015-01-05 15:32:01 -07:00
|
|
|
try:
|
2015-05-01 13:49:43 +02:00
|
|
|
self.exec_command(command)
|
2014-12-11 12:15:24 -07:00
|
|
|
except (OSError, subprocess.SubprocessError) as e:
|
2018-01-29 18:59:28 +07:00
|
|
|
self.consoleError.emit("Could not start Telnet console with command '{}': {}".format(command, e))
|
2015-01-05 15:32:01 -07:00
|
|
|
finally:
|
2017-04-27 15:46:39 +02:00
|
|
|
log.debug('Telnet console {}:{} closed'.format(host, port))
|
2015-12-02 11:21:16 +01:00
|
|
|
if sys.platform.startswith("darwin") and "osascript" in command:
|
|
|
|
|
console_mutex.unlock()
|
2015-05-01 13:49:43 +02:00
|
|
|
|
|
|
|
|
|
2016-07-20 12:00:27 +02:00
|
|
|
def nodeTelnetConsole(node, port, command=None):
|
2015-05-01 13:49:43 +02:00
|
|
|
"""
|
2018-11-27 18:30:16 +07:00
|
|
|
Start a Telnet console program for a node.
|
2015-05-01 13:49:43 +02:00
|
|
|
|
2016-07-20 12:00:27 +02:00
|
|
|
:param node: The node
|
2016-02-05 19:00:53 +01:00
|
|
|
:param command: Console command
|
2015-05-01 13:49:43 +02:00
|
|
|
"""
|
|
|
|
|
|
2016-08-22 11:05:05 +02:00
|
|
|
if not node.isStarted():
|
|
|
|
|
return
|
|
|
|
|
|
2016-07-20 12:00:27 +02:00
|
|
|
if command is None:
|
|
|
|
|
general_settings = MainWindow.instance().settings()
|
|
|
|
|
command = general_settings["telnet_console_command"]
|
|
|
|
|
if not command:
|
|
|
|
|
return
|
|
|
|
|
|
2018-09-03 16:48:23 +07:00
|
|
|
if len(command.strip(' ')) == 0:
|
|
|
|
|
log.warning('Telnet console program is not configured')
|
|
|
|
|
return
|
|
|
|
|
|
2017-04-27 15:46:39 +02:00
|
|
|
log.debug('Starting telnet console in thread "{}"'.format(command))
|
2016-07-20 12:00:27 +02:00
|
|
|
console_thread = ConsoleThread(MainWindow.instance(), command, node, port)
|
2015-07-17 17:14:57 +02:00
|
|
|
console_thread.consoleError.connect(_consoleErrorSlot)
|
2015-05-01 13:49:43 +02:00
|
|
|
console_thread.start()
|
2014-10-31 12:39:16 +01:00
|
|
|
|
2015-05-01 13:49:43 +02:00
|
|
|
|
2015-07-17 17:14:57 +02:00
|
|
|
def _consoleErrorSlot(message):
|
2018-01-29 18:59:28 +07:00
|
|
|
log.error(message)
|