mirror of
https://github.com/GNS3/gns3-gui.git
synced 2026-05-17 00:46:01 +03:00
Merge remote-tracking branch 'origin/appliance-v8-support' into appliance-v8-support
This commit is contained in:
18
CHANGELOG
18
CHANGELOG
@@ -1,5 +1,23 @@
|
||||
# Change Log
|
||||
|
||||
## 2.2.42 09/08/2023
|
||||
|
||||
* Use the system's certificate store for SSL connections
|
||||
* Give a node some time to start before opening the console (for console auto start). Fixes #3474
|
||||
* Use Mate Terminal by default if installed on Debian, Ubuntu and Linux Mint.
|
||||
* Support for gnome-terminal tabs to be opened in the same window.
|
||||
* Remove import urllib3 and let sentry_sdk import and patch it. Fixes https://github.com/GNS3/gns3-gui/issues/3498
|
||||
* Add import sys in sudo.py
|
||||
* Rounded Rectangle support
|
||||
|
||||
## 2.2.41 12/07/2023
|
||||
|
||||
* Use alternative method to set the correct permissions for uBridge on macOS
|
||||
* Remove sending stats to GA
|
||||
* Catch urllib3 exceptions when sending crash report. Ref https://github.com/GNS3/gns3-gui/issues/3483
|
||||
* Backport UEFI boot mode support for Qemu VMs
|
||||
* Add debug for dropEvent. Ref https://github.com/GNS3/gns3-server/issues/2242
|
||||
|
||||
## 2.2.40.1 10/06/2023
|
||||
|
||||
* No changes
|
||||
|
||||
@@ -15,12 +15,6 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import struct
|
||||
import distro
|
||||
|
||||
try:
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
@@ -29,7 +23,12 @@ except ImportError:
|
||||
# Sentry SDK is not installed with deb package in order to simplify packaging
|
||||
SENTRY_SDK_AVAILABLE = False
|
||||
|
||||
from .utils.get_resource import get_resource
|
||||
import sys
|
||||
import os
|
||||
import platform
|
||||
import struct
|
||||
import distro
|
||||
|
||||
from .version import __version__, __version_info__
|
||||
|
||||
import logging
|
||||
@@ -51,7 +50,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://486bdeb4a1a94f129b676eb677f598e5@o19455.ingest.sentry.io/38506"
|
||||
DSN = "https://bae0411a1718612ee8c25cdb12ec7f02@o19455.ingest.sentry.io/38506"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
@@ -64,22 +63,16 @@ class CrashReport:
|
||||
self._sentry_initialized = False
|
||||
|
||||
if SENTRY_SDK_AVAILABLE:
|
||||
cacert = None
|
||||
if hasattr(sys, "frozen"):
|
||||
cacert_resource = get_resource("cacert.pem")
|
||||
if cacert_resource is not None and os.path.isfile(cacert_resource):
|
||||
cacert = cacert_resource
|
||||
else:
|
||||
log.error("The SSL certificate bundle file '{}' could not be found".format(cacert_resource))
|
||||
|
||||
# Don't send log records as events.
|
||||
sentry_logging = LoggingIntegration(level=logging.INFO, event_level=None)
|
||||
|
||||
sentry_sdk.init(dsn=CrashReport.DSN,
|
||||
release=__version__,
|
||||
ca_certs=cacert,
|
||||
default_integrations=False,
|
||||
integrations=[sentry_logging])
|
||||
try:
|
||||
sentry_sdk.init(dsn=CrashReport.DSN,
|
||||
release=__version__,
|
||||
default_integrations=False,
|
||||
integrations=[sentry_logging])
|
||||
except Exception as e:
|
||||
log.error("Crash report could not be sent: {}".format(e))
|
||||
return
|
||||
|
||||
tags = {
|
||||
"os:name": platform.system(),
|
||||
|
||||
@@ -22,6 +22,7 @@ Style editor to edit Shape items.
|
||||
from ..qt import QtCore, QtWidgets, QtGui
|
||||
from ..ui.style_editor_dialog_ui import Ui_StyleEditorDialog
|
||||
from ..items.shape_item import ShapeItem
|
||||
from ..items.rectangle_item import RectangleItem
|
||||
|
||||
|
||||
class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
@@ -70,6 +71,13 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
self._border_color.green(),
|
||||
self._border_color.blue(),
|
||||
self._border_color.alpha()))
|
||||
if isinstance(first_item, RectangleItem):
|
||||
# use the horizontal corner radius first and then the vertical one if it's not set
|
||||
# maybe we allow configuring them separately in the future
|
||||
corner_radius = first_item.horizontalCornerRadius()
|
||||
if not corner_radius:
|
||||
corner_radius = first_item.verticalCornerRadius()
|
||||
self.uiCornerRadiusSpinBox.setValue(corner_radius)
|
||||
self.uiRotationSpinBox.setValue(int(first_item.rotation()))
|
||||
self.uiBorderWidthSpinBox.setValue(pen.width())
|
||||
index = self.uiBorderStyleComboBox.findData(pen.style())
|
||||
@@ -116,10 +124,16 @@ class StyleEditorDialog(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
|
||||
for item in self._items:
|
||||
item.setPen(pen)
|
||||
# on multiselection it's possible to select many type of items
|
||||
# on multi-selection it's possible to select many type of items
|
||||
# but brush can be applied only on ShapeItem,
|
||||
if brush and isinstance(item, ShapeItem):
|
||||
item.setBrush(brush)
|
||||
if isinstance(item, RectangleItem):
|
||||
corner_radius = self.uiCornerRadiusSpinBox.value()
|
||||
# use the corner radius for both horizontal (rx) and vertical (ry)
|
||||
# maybe we support setting them separately in the future
|
||||
item.setHorizontalCornerRadius(corner_radius)
|
||||
item.setVerticalCornerRadius(corner_radius)
|
||||
item.setRotation(self.uiRotationSpinBox.value())
|
||||
|
||||
def done(self, result):
|
||||
|
||||
@@ -54,7 +54,9 @@ class StyleEditorDialogLink(QtWidgets.QDialog, Ui_StyleEditorDialog):
|
||||
self.uiColorLabel.hide()
|
||||
self.uiColorPushButton.hide()
|
||||
self._color = None
|
||||
|
||||
|
||||
self.uiCornerRadiusLabel.hide()
|
||||
self.uiCornerRadiusSpinBox.hide()
|
||||
self.uiRotationLabel.hide()
|
||||
self.uiRotationSpinBox.hide()
|
||||
|
||||
|
||||
@@ -32,8 +32,22 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
|
||||
"""
|
||||
|
||||
def __init__(self, width=200, height=100, **kws):
|
||||
self._rx = 0
|
||||
self._ry = 0
|
||||
super().__init__(width=width, height=height, **kws)
|
||||
|
||||
def setHorizontalCornerRadius(self, radius: int):
|
||||
self._rx = radius
|
||||
|
||||
def horizontalCornerRadius(self):
|
||||
return self._rx
|
||||
|
||||
def setVerticalCornerRadius(self, radius: int):
|
||||
self._ry = radius
|
||||
|
||||
def verticalCornerRadius(self):
|
||||
return self._ry
|
||||
|
||||
def paint(self, painter, option, widget=None):
|
||||
"""
|
||||
Paints the contents of an item in local coordinates.
|
||||
@@ -43,7 +57,9 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
|
||||
:param widget: QWidget instance
|
||||
"""
|
||||
|
||||
super().paint(painter, option, widget)
|
||||
painter.setPen(self.pen())
|
||||
painter.setBrush(self.brush())
|
||||
painter.drawRoundedRect(self.rect(), self._rx, self._ry)
|
||||
self.drawLayerInfo(painter)
|
||||
|
||||
def toSvg(self):
|
||||
@@ -57,7 +73,27 @@ class RectangleItem(QtWidgets.QGraphicsRectItem, ShapeItem):
|
||||
rect = ET.SubElement(svg, "rect")
|
||||
rect.set("width", str(int(self.rect().width())))
|
||||
rect.set("height", str(int(self.rect().height())))
|
||||
if self._rx:
|
||||
rect.set("rx", str(self._rx))
|
||||
if self._ry:
|
||||
rect.set("ry", str(self._ry))
|
||||
|
||||
rect = self._styleSvg(rect)
|
||||
|
||||
return ET.tostring(svg, encoding="utf-8").decode("utf-8")
|
||||
|
||||
def fromSvg(self, svg):
|
||||
svg_elem = ET.fromstring(svg)
|
||||
if len(svg_elem):
|
||||
# handle horizontal corner radius and vertical corner radius (specific to rectangles)
|
||||
rx = svg_elem[0].get("rx")
|
||||
ry = svg_elem[0].get("ry")
|
||||
if rx:
|
||||
self._rx = int(rx)
|
||||
elif ry:
|
||||
self._rx = int(ry) # defaults to ry if it is specified
|
||||
if ry:
|
||||
self._ry = int(ry)
|
||||
elif rx:
|
||||
self._ry = int(rx) # defaults to rx if it is specified
|
||||
super().fromSvg(svg)
|
||||
|
||||
@@ -173,7 +173,7 @@ class ShapeItem(DrawingItem):
|
||||
|
||||
def fromSvg(self, svg):
|
||||
"""
|
||||
Import element informations from an SVG
|
||||
Import element information from SVG
|
||||
"""
|
||||
svg = ET.fromstring(svg)
|
||||
width = float(svg.get("width", self.rect().width()))
|
||||
|
||||
@@ -193,7 +193,11 @@ class LocalServer(QtCore.QObject):
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No)
|
||||
if proceed == QtWidgets.QMessageBox.Yes:
|
||||
sudo(["chown", "root:admin", path], ["chmod", "4750", path])
|
||||
from gns3.utils.macos_ubridge_setuid import macos_ubridge_setuid
|
||||
if sys.platform.startswith("darwin") and hasattr(sys, "frozen"):
|
||||
macos_ubridge_setuid()
|
||||
else:
|
||||
sudo(["chown", "root:admin", path], ["chmod", "4750", path])
|
||||
except OSError as e:
|
||||
QtWidgets.QMessageBox.critical(self.parent(), "uBridge", "Can't set root permissions to uBridge {}: {}".format(path, str(e)))
|
||||
return False
|
||||
|
||||
21
gns3/main.py
21
gns3/main.py
@@ -30,16 +30,6 @@ try:
|
||||
except Exception as e:
|
||||
print("Fail update installation: {}".format(str(e)))
|
||||
|
||||
|
||||
# WARNING
|
||||
# Due to buggy user machines we choose to put this as the first loading modules
|
||||
# otherwise the egg cache is initialized in his standard location and
|
||||
# if is not writetable the application crash. It's the user fault
|
||||
# because one day the user as used sudo to run an egg and break his
|
||||
# filesystem permissions, but it's a common mistake.
|
||||
from gns3.utils.get_resource import get_resource
|
||||
|
||||
|
||||
import datetime
|
||||
import traceback
|
||||
import time
|
||||
@@ -60,12 +50,12 @@ from gns3.local_config import LocalConfig
|
||||
from gns3.application import Application
|
||||
from gns3.utils import parse_version
|
||||
from gns3.dialogs.profile_select import ProfileSelectDialog
|
||||
from gns3.version import __version__
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from gns3.version import __version__
|
||||
|
||||
|
||||
def locale_check():
|
||||
"""
|
||||
@@ -135,6 +125,13 @@ def main():
|
||||
if options.project:
|
||||
options.project = os.path.abspath(options.project)
|
||||
|
||||
try:
|
||||
import truststore
|
||||
truststore.inject_into_ssl()
|
||||
log.info("Using system certificate store for SSL connections")
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if hasattr(sys, "frozen"):
|
||||
# We add to the path where the OS search executable our binary location starting by GNS3
|
||||
# packaged binary
|
||||
|
||||
@@ -49,7 +49,6 @@ from .topology import Topology
|
||||
from .http_client import HTTPClient
|
||||
from .progress import Progress
|
||||
from .update_manager import UpdateManager
|
||||
from .utils.analytics import AnalyticsClient
|
||||
from .dialogs.appliance_wizard import ApplianceWizard
|
||||
from .dialogs.new_template_wizard import NewTemplateWizard
|
||||
from .dialogs.notif_dialog import NotifDialog, NotifDialogHandler
|
||||
@@ -133,7 +132,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self._local_config_timer = QtCore.QTimer(self)
|
||||
self._local_config_timer.timeout.connect(local_config.checkConfigChanged)
|
||||
self._local_config_timer.start(1000) # milliseconds
|
||||
self._analytics_client = AnalyticsClient()
|
||||
self._template_manager = TemplateManager().instance()
|
||||
self._appliance_manager = ApplianceManager().instance()
|
||||
|
||||
@@ -378,13 +376,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
self.uiConsoleDockWidget.setFloating(False)
|
||||
self.uiNodesDockWidget.setFloating(False)
|
||||
|
||||
def analyticsClient(self):
|
||||
"""
|
||||
Return the analytics client
|
||||
"""
|
||||
|
||||
return self._analytics_client
|
||||
|
||||
def _newProjectActionSlot(self):
|
||||
"""
|
||||
Slot called to create a new project.
|
||||
@@ -1144,8 +1135,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
progress.setCancelButtonText("Force quit")
|
||||
|
||||
log.debug("Close the Main Window")
|
||||
self._analytics_client.sendScreenView("Main Window", session_start=False)
|
||||
|
||||
self._finish_application_closing(close_windows=False)
|
||||
event.accept()
|
||||
self.uiConsoleTextEdit.closeIO()
|
||||
@@ -1234,7 +1223,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
Controller.instance().connected_signal.connect(self._controllerConnectedSlot)
|
||||
Controller.instance().project_list_updated_signal.connect(self.updateRecentProjectActions)
|
||||
|
||||
self._analytics_client.sendScreenView("Main Window")
|
||||
self.uiGraphicsView.setEnabled(False)
|
||||
|
||||
# show the setup wizard
|
||||
|
||||
@@ -57,6 +57,7 @@ class iouDeviceConfigurationPage(QtWidgets.QWidget, Ui_iouDeviceConfigPageWidget
|
||||
self.uiPrivateConfigToolButton.hide()
|
||||
|
||||
# location of the base config templates
|
||||
# FIXME: this does not work
|
||||
self._base_iou_l2_config_template = get_resource(os.path.join("configs", "iou_l2_base_startup-config.txt"))
|
||||
self._base_iou_l3_config_template = get_resource(os.path.join("configs", "iou_l3_base_startup-config.txt"))
|
||||
self._default_configs_dir = LocalServer.instance().localServerSettings()["configs_path"]
|
||||
|
||||
@@ -689,7 +689,8 @@ class Node(BaseNode):
|
||||
return
|
||||
super().setStatus(status)
|
||||
if status == self.started and "console_auto_start" in self.settings() and self.settings()["console_auto_start"]:
|
||||
self.openConsole()
|
||||
# give the node some time to start before opening the console
|
||||
QtCore.QTimer.singleShot(1000, self.openConsole)
|
||||
|
||||
def openConsole(self, command=None, aux=False):
|
||||
"""
|
||||
|
||||
@@ -301,7 +301,6 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
self.uiImagesPathLineEdit.setText(local_server["images_path"])
|
||||
self.uiConfigsPathLineEdit.setText(local_server["configs_path"])
|
||||
self.uiAppliancesPathLineEdit.setText(local_server["appliances_path"])
|
||||
self.uiStatsCheckBox.setChecked(settings["send_stats"])
|
||||
self.uiOverlayNotificationsCheckBox.setChecked(settings["overlay_notifications"])
|
||||
self.uiCrashReportCheckBox.setChecked(local_server["report_errors"])
|
||||
self.uiCheckForUpdateCheckBox.setChecked(settings["check_for_update"])
|
||||
@@ -411,7 +410,6 @@ class GeneralPreferencesPage(QtWidgets.QWidget, Ui_GeneralPreferencesPageWidget)
|
||||
"vnc_console_command": self.uiVNCConsoleCommandLineEdit.text(),
|
||||
"spice_console_command": self.uiSPICEConsoleCommandLineEdit.text(),
|
||||
"delay_console_all": self.uiDelayConsoleAllSpinBox.value(),
|
||||
"send_stats": self.uiStatsCheckBox.isChecked(),
|
||||
"multi_profiles": self.uiMultiProfilesCheckBox.isChecked(),
|
||||
"direct_file_upload": self.uiDirectFileUpload.isChecked()
|
||||
}
|
||||
|
||||
@@ -195,54 +195,6 @@ if hasattr(sys, '_called_from_test'):
|
||||
QtCore.pyqtSignal = FakeQtSignal
|
||||
|
||||
|
||||
class StatsQtWidgetsQWizard(QtWidgets.QWizard):
|
||||
"""
|
||||
Send stats from all the QWizard
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
from ..utils.analytics import AnalyticsClient
|
||||
name = self.__class__.__name__
|
||||
name = re.sub(r"([A-Z])", r" \1", name).strip()
|
||||
AnalyticsClient.instance().sendScreenView(name)
|
||||
|
||||
QtWidgets.QWizard = StatsQtWidgetsQWizard
|
||||
|
||||
|
||||
class StatsQtWidgetsQMainWindow(QtWidgets.QMainWindow):
|
||||
"""
|
||||
Send stats from all the QMainWindow
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
from ..utils.analytics import AnalyticsClient
|
||||
name = self.__class__.__name__
|
||||
name = re.sub(r"([A-Z])", r" \1", name).strip()
|
||||
AnalyticsClient.instance().sendScreenView(name)
|
||||
|
||||
QtWidgets.QMainWindow = StatsQtWidgetsQMainWindow
|
||||
|
||||
|
||||
class StatsQtWidgetsQDialog(QtWidgets.QDialog):
|
||||
"""
|
||||
Send stats from all the QWizard
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
from ..utils.analytics import AnalyticsClient
|
||||
name = self.__class__.__name__
|
||||
name = re.sub(r"([A-Z])", r" \1", name).strip()
|
||||
AnalyticsClient.instance().sendScreenView(name)
|
||||
|
||||
QtWidgets.QDialog = StatsQtWidgetsQDialog
|
||||
|
||||
|
||||
def qpartial(func, *args, **kwargs):
|
||||
"""
|
||||
A functools partial that you can use on qobject. If the targeted qobject is
|
||||
|
||||
@@ -74,7 +74,7 @@ class Appliance(collections.abc.Mapping):
|
||||
else:
|
||||
appliance_file = "appliance.json"
|
||||
|
||||
with open(get_resource(os.path.join("schemas", appliance_file))) as f:
|
||||
with open(get_resource("schemas/appliance.json")) as f:
|
||||
schema = json.load(f)
|
||||
v = jsonschema.Draft4Validator(schema)
|
||||
try:
|
||||
|
||||
@@ -24,6 +24,7 @@ from ..qt import QtCore, QtWidgets, QtNetwork
|
||||
from ..controller import Controller
|
||||
from .config import Config, ConfigException
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -152,12 +152,12 @@ elif sys.platform.startswith("darwin"):
|
||||
else:
|
||||
PRECONFIGURED_TELNET_CONSOLE_COMMANDS = {'Xterm': 'xterm -T "%d" -e "telnet %h %p"',
|
||||
'Putty': 'putty -telnet %h %p -title "%d" -sl 2500 -fg SALMON1 -bg BLACK',
|
||||
'Gnome Terminal': 'gnome-terminal -t "%d" -e "telnet %h %p"',
|
||||
'Gnome Terminal': 'gnome-terminal --tab -t "%d" -- telnet %h %p',
|
||||
'Xfce4 Terminal': 'xfce4-terminal --tab -T "%d" -e "telnet %h %p"',
|
||||
'ROXTerm': 'roxterm -n "%d" --tab -e "telnet %h %p"',
|
||||
'KDE Konsole': 'konsole --new-tab -p tabtitle="%d" -e "telnet %h %p"',
|
||||
'SecureCRT': 'SecureCRT /T /N "%d" /TELNET %h %p',
|
||||
'Mate Terminal': 'mate-terminal --tab -e "telnet %h %p" -t "%d"',
|
||||
'Mate Terminal': 'mate-terminal --tab -e "telnet %h %p" -t "%d"',
|
||||
'terminator': 'terminator -e "telnet %h %p" -T "%d"',
|
||||
'urxvt': 'urxvt -title %d -e telnet %h %p',
|
||||
'kitty': 'kitty -T %d telnet %h %p'}
|
||||
@@ -168,7 +168,10 @@ else:
|
||||
if sys.platform.startswith("linux"):
|
||||
distro_name = distro.name()
|
||||
if distro_name == "Debian" or distro_name == "Ubuntu" or distro_name == "LinuxMint":
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Gnome Terminal"]
|
||||
if shutil.which("mate-terminal"):
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Mate Terminal"]
|
||||
else:
|
||||
DEFAULT_TELNET_CONSOLE_COMMAND = PRECONFIGURED_TELNET_CONSOLE_COMMANDS["Gnome Terminal"]
|
||||
|
||||
# Pre-configured VNC console commands on various OSes
|
||||
if sys.platform.startswith("win"):
|
||||
@@ -284,7 +287,6 @@ GENERAL_SETTINGS = {
|
||||
"check_for_update": True,
|
||||
"overlay_notifications": True,
|
||||
"experimental_features": False,
|
||||
"send_stats": True,
|
||||
"stats_visitor_id": str(uuid.uuid4()), # An anonymous id for stats
|
||||
"last_check_for_update": 0,
|
||||
"telnet_console_command": DEFAULT_TELNET_CONSOLE_COMMAND,
|
||||
|
||||
@@ -25,6 +25,8 @@ import os
|
||||
import sys
|
||||
import shlex
|
||||
import subprocess
|
||||
import psutil
|
||||
|
||||
from .main_window import MainWindow
|
||||
from .controller import Controller
|
||||
|
||||
@@ -34,6 +36,39 @@ log = logging.getLogger(__name__)
|
||||
console_mutex = QtCore.QMutex()
|
||||
|
||||
|
||||
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 {}
|
||||
|
||||
|
||||
class ConsoleThread(QtCore.QThread):
|
||||
|
||||
consoleError = QtCore.pyqtSignal(str)
|
||||
@@ -60,7 +95,14 @@ class ConsoleThread(QtCore.QThread):
|
||||
except ValueError:
|
||||
self.consoleError.emit("Syntax error in command: '{}'".format(command))
|
||||
return
|
||||
subprocess.call(args, env=os.environ)
|
||||
|
||||
env = os.environ.copy()
|
||||
# special case to force gnome-terminal to correctly use tabs on Linux
|
||||
if sys.platform.startswith("linux") and "gnome-terminal" in args[0] and "--tab" in command:
|
||||
# inject gnome-terminal environment variables
|
||||
if "GNOME_TERMINAL_SERVICE" not in env or "GNOME_TERMINAL_SCREEN" not in env:
|
||||
env.update(gnome_terminal_env())
|
||||
subprocess.call(args, env=env)
|
||||
|
||||
def run(self):
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>941</width>
|
||||
<height>910</height>
|
||||
<width>512</width>
|
||||
<height>652</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -223,7 +223,16 @@
|
||||
<string>Binary images</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -373,7 +382,16 @@
|
||||
<string>Console applications</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -474,7 +492,16 @@
|
||||
<string>VNC</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -943,7 +970,16 @@
|
||||
<string>Miscellaneous</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
@@ -966,16 +1002,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiStatsCheckBox">
|
||||
<property name="text">
|
||||
<string>Send anonymous usage statistics</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="uiOverlayNotificationsCheckBox">
|
||||
<property name="text">
|
||||
@@ -1098,7 +1124,6 @@
|
||||
<tabstop>uiDefaultNoteColorPushButton</tabstop>
|
||||
<tabstop>uiCheckForUpdateCheckBox</tabstop>
|
||||
<tabstop>uiCrashReportCheckBox</tabstop>
|
||||
<tabstop>uiStatsCheckBox</tabstop>
|
||||
<tabstop>uiOverlayNotificationsCheckBox</tabstop>
|
||||
<tabstop>uiExperimentalFeaturesCheckBox</tabstop>
|
||||
<tabstop>uiHdpiCheckBox</tabstop>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/general_preferences_page.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.2
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
@@ -13,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_GeneralPreferencesPageWidget(object):
|
||||
def setupUi(self, GeneralPreferencesPageWidget):
|
||||
GeneralPreferencesPageWidget.setObjectName("GeneralPreferencesPageWidget")
|
||||
GeneralPreferencesPageWidget.resize(941, 910)
|
||||
GeneralPreferencesPageWidget.resize(512, 652)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(GeneralPreferencesPageWidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.uiMiscTabWidget = QtWidgets.QTabWidget(GeneralPreferencesPageWidget)
|
||||
@@ -446,10 +447,6 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.uiCrashReportCheckBox.setChecked(True)
|
||||
self.uiCrashReportCheckBox.setObjectName("uiCrashReportCheckBox")
|
||||
self.verticalLayout_2.addWidget(self.uiCrashReportCheckBox)
|
||||
self.uiStatsCheckBox = QtWidgets.QCheckBox(self.uiMiscTab)
|
||||
self.uiStatsCheckBox.setChecked(True)
|
||||
self.uiStatsCheckBox.setObjectName("uiStatsCheckBox")
|
||||
self.verticalLayout_2.addWidget(self.uiStatsCheckBox)
|
||||
self.uiOverlayNotificationsCheckBox = QtWidgets.QCheckBox(self.uiMiscTab)
|
||||
self.uiOverlayNotificationsCheckBox.setObjectName("uiOverlayNotificationsCheckBox")
|
||||
self.verticalLayout_2.addWidget(self.uiOverlayNotificationsCheckBox)
|
||||
@@ -520,8 +517,7 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiDefaultNoteFontPushButton, self.uiDefaultNoteColorPushButton)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiDefaultNoteColorPushButton, self.uiCheckForUpdateCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiCheckForUpdateCheckBox, self.uiCrashReportCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiCrashReportCheckBox, self.uiStatsCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiStatsCheckBox, self.uiOverlayNotificationsCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiCrashReportCheckBox, self.uiOverlayNotificationsCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiOverlayNotificationsCheckBox, self.uiExperimentalFeaturesCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiExperimentalFeaturesCheckBox, self.uiHdpiCheckBox)
|
||||
GeneralPreferencesPageWidget.setTabOrder(self.uiHdpiCheckBox, self.uiMultiProfilesCheckBox)
|
||||
@@ -601,7 +597,6 @@ class Ui_GeneralPreferencesPageWidget(object):
|
||||
self.uiMiscTabWidget.setTabText(self.uiMiscTabWidget.indexOf(self.uiSceneTab), _translate("GeneralPreferencesPageWidget", "Topology view"))
|
||||
self.uiCheckForUpdateCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Automatically check for update"))
|
||||
self.uiCrashReportCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Send anonymous crash reports"))
|
||||
self.uiStatsCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Send anonymous usage statistics"))
|
||||
self.uiOverlayNotificationsCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Display error, warning and info in an overlay popup"))
|
||||
self.uiExperimentalFeaturesCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Enable experimental features (dangerous, restart required)"))
|
||||
self.uiHdpiCheckBox.setText(_translate("GeneralPreferencesPageWidget", "Enable HDPI mode (this may crash on Linux, restart required)"))
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
<ui version="4.0">
|
||||
<class>StyleEditorDialog</class>
|
||||
<widget class="QDialog" name="StyleEditorDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>270</width>
|
||||
<height>294</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Style editor</string>
|
||||
</property>
|
||||
@@ -76,14 +84,14 @@
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="uiBorderStyleComboBox"/>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="uiRotationLabel">
|
||||
<property name="text">
|
||||
<string>Rotation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QSpinBox" name="uiRotationSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@@ -106,6 +114,23 @@ editing (notes only) with ALT and '+' (or P) / ALT and '-' (or M)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="uiCornerRadiusLabel">
|
||||
<property name="text">
|
||||
<string>Corner radius:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="uiCornerRadiusSpinBox">
|
||||
<property name="suffix">
|
||||
<string>°</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file '/home/grossmj/PycharmProjects/gns3-gui/gns3/ui/style_editor_dialog.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.2
|
||||
# Created by: PyQt5 UI code generator 5.15.9
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@@ -14,6 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
class Ui_StyleEditorDialog(object):
|
||||
def setupUi(self, StyleEditorDialog):
|
||||
StyleEditorDialog.setObjectName("StyleEditorDialog")
|
||||
StyleEditorDialog.resize(270, 294)
|
||||
StyleEditorDialog.setModal(True)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(StyleEditorDialog)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
@@ -52,7 +53,7 @@ class Ui_StyleEditorDialog(object):
|
||||
self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.uiBorderStyleComboBox)
|
||||
self.uiRotationLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
|
||||
self.uiRotationLabel.setObjectName("uiRotationLabel")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.uiRotationLabel)
|
||||
self.uiRotationSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -62,7 +63,14 @@ class Ui_StyleEditorDialog(object):
|
||||
self.uiRotationSpinBox.setMinimum(-360)
|
||||
self.uiRotationSpinBox.setMaximum(360)
|
||||
self.uiRotationSpinBox.setObjectName("uiRotationSpinBox")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
|
||||
self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.uiRotationSpinBox)
|
||||
self.uiCornerRadiusLabel = QtWidgets.QLabel(self.uiStyleSettingsGroupBox)
|
||||
self.uiCornerRadiusLabel.setObjectName("uiCornerRadiusLabel")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.uiCornerRadiusLabel)
|
||||
self.uiCornerRadiusSpinBox = QtWidgets.QSpinBox(self.uiStyleSettingsGroupBox)
|
||||
self.uiCornerRadiusSpinBox.setMaximum(100)
|
||||
self.uiCornerRadiusSpinBox.setObjectName("uiCornerRadiusSpinBox")
|
||||
self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.uiCornerRadiusSpinBox)
|
||||
self.verticalLayout.addWidget(self.uiStyleSettingsGroupBox)
|
||||
self.uiButtonBox = QtWidgets.QDialogButtonBox(StyleEditorDialog)
|
||||
self.uiButtonBox.setOrientation(QtCore.Qt.Horizontal)
|
||||
@@ -73,8 +81,8 @@ class Ui_StyleEditorDialog(object):
|
||||
self.verticalLayout.addItem(spacerItem)
|
||||
|
||||
self.retranslateUi(StyleEditorDialog)
|
||||
self.uiButtonBox.accepted.connect(StyleEditorDialog.accept)
|
||||
self.uiButtonBox.rejected.connect(StyleEditorDialog.reject)
|
||||
self.uiButtonBox.accepted.connect(StyleEditorDialog.accept) # type: ignore
|
||||
self.uiButtonBox.rejected.connect(StyleEditorDialog.reject) # type: ignore
|
||||
QtCore.QMetaObject.connectSlotsByName(StyleEditorDialog)
|
||||
|
||||
def retranslateUi(self, StyleEditorDialog):
|
||||
@@ -90,4 +98,6 @@ class Ui_StyleEditorDialog(object):
|
||||
self.uiRotationSpinBox.setToolTip(_translate("StyleEditorDialog", "Rotation can be ajusted on the scene for a selected item while\n"
|
||||
"editing (notes only) with ALT and \'+\' (or P) / ALT and \'-\' (or M)"))
|
||||
self.uiRotationSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
|
||||
self.uiCornerRadiusLabel.setText(_translate("StyleEditorDialog", "Corner radius:"))
|
||||
self.uiCornerRadiusSpinBox.setSuffix(_translate("StyleEditorDialog", "°"))
|
||||
from . import resources_rc
|
||||
|
||||
@@ -24,7 +24,6 @@ import re
|
||||
|
||||
|
||||
from gns3.utils import parse_version
|
||||
|
||||
from gns3 import version
|
||||
from gns3.qt import QtNetwork, QtCore, QtWidgets, QtGui, qslot
|
||||
from gns3.local_config import LocalConfig
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
# -*- 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/>.
|
||||
|
||||
|
||||
import platform
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from urllib.parse import quote
|
||||
from ..version import __version__
|
||||
from ..qt import QtCore, QtNetwork, QtWidgets
|
||||
from ..local_config import LocalConfig
|
||||
from ..settings import GENERAL_SETTINGS
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AnalyticsClient(QtCore.QObject):
|
||||
"""
|
||||
Google analytics client to send events.
|
||||
"""
|
||||
|
||||
_property_id = "UA-55817127-3"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._visitor_id = None
|
||||
self._manager = QtNetwork.QNetworkAccessManager(self)
|
||||
|
||||
def finished(network_reply):
|
||||
try:
|
||||
error = network_reply.error()
|
||||
except TypeError:
|
||||
# For unknow reason sometimes error is transform to a signal
|
||||
# we receive few crash report about that, but we are not able
|
||||
# to reproduce. We suspect the problem happen when the
|
||||
# application is closing.
|
||||
#
|
||||
# https://github.com/GNS3/gns3-gui/issues/2011
|
||||
return
|
||||
if error != QtNetwork.QNetworkReply.NoError:
|
||||
log.debug("Error when pushing to Google Analytics %s", network_reply.errorString())
|
||||
|
||||
self._manager.finished.connect(finished)
|
||||
|
||||
#
|
||||
# We need to build a user agent for Universal Analytics in order to
|
||||
# let analytics guess the OS
|
||||
# this could break by analytics at anytime :(
|
||||
if sys.platform.startswith("darwin"):
|
||||
self._user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X {release}) AppleWebKit/537.36 (KHTML, like Gecko) GNS3/{version}".format(release=platform.mac_ver()[0].replace(".", "_"), version=__version__)
|
||||
elif sys.platform.startswith("win"):
|
||||
self._user_agent = "Mozilla/5.0 (Windows NT {release}) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 GNS3/{version}".format(release=platform.release(), version=__version__)
|
||||
else:
|
||||
self._user_agent = "Mozilla/5.0 (X11; Linux {arch}) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 GNS3/{version}".format(arch=platform.machine(), version=__version__)
|
||||
self._rate_limit = {}
|
||||
|
||||
def sendScreenView(self, screen, session_start=None):
|
||||
"""
|
||||
:params session_start: True session start, None during session, False session stop
|
||||
"""
|
||||
|
||||
if session_start is not False and screen in self._rate_limit:
|
||||
if self._rate_limit[screen] + 60 * 1 > datetime.utcnow().timestamp():
|
||||
log.debug("Ignore call %s to Google Analytics because of rate limiting", screen)
|
||||
return
|
||||
|
||||
self._rate_limit[screen] = datetime.utcnow().timestamp()
|
||||
|
||||
settings = LocalConfig.instance().loadSectionSettings("MainWindow", GENERAL_SETTINGS)
|
||||
if settings["send_stats"] is False:
|
||||
log.debug("Stats is turn off ignore call %s", screen)
|
||||
return
|
||||
|
||||
body = "v=1" # Version
|
||||
body += "&tid={}".format(self._property_id) # Tracking ID / Property ID
|
||||
body += "&cid={}".format(settings["stats_visitor_id"]) # Anonymous Client ID
|
||||
body += "&aip=1" # Anonymize IP
|
||||
body += "&t=screenview" # Screenview hit type
|
||||
body += "&an=GNS3" # App name
|
||||
body += "&av={}".format(quote(__version__)) # App version.
|
||||
body += "&ua={}".format(quote(self._user_agent)) # User agent
|
||||
body += "&cd={}".format(quote(screen)) # Category
|
||||
body += "&ds=gns3-gui" # Data source
|
||||
if session_start is True:
|
||||
body += "&sc=start" # Session start
|
||||
elif session_start is False:
|
||||
body += "&sc=end" # Session end
|
||||
|
||||
screen = QtWidgets.QApplication.desktop().screenGeometry()
|
||||
body += "&sr={}x{}".format(screen.width(), screen.height()) # Screen resolution
|
||||
|
||||
locale = QtCore.QLocale.system().name().lower()
|
||||
if locale:
|
||||
body += "&ul={}".format(locale) # User language
|
||||
|
||||
# TODO: HTTPS when possible because it's broken for the moment with Qt on OSX:
|
||||
# https://bugreports.qt.io/browse/QTBUG-45487
|
||||
if sys.platform.startswith("darwin"):
|
||||
url = QtCore.QUrl('http://www.google-analytics.com/collect')
|
||||
else:
|
||||
url = QtCore.QUrl('https://www.google-analytics.com/collect')
|
||||
request_qt = QtNetwork.QNetworkRequest(url)
|
||||
request_qt.setRawHeader(b"Content-Type", b"application/x-www-form-urlencoded")
|
||||
request_qt.setRawHeader(b"User-Agent", self._user_agent.encode())
|
||||
self._manager.post(request_qt, body.encode())
|
||||
|
||||
log.debug("Send stats to Google Analytics: %s", body)
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""
|
||||
Singleton to return only on instance of AnalyticsClient.
|
||||
:returns: instance of AnalyticsClient
|
||||
"""
|
||||
|
||||
if not hasattr(AnalyticsClient, '_instance') or AnalyticsClient._instance is None:
|
||||
AnalyticsClient._instance = AnalyticsClient()
|
||||
return AnalyticsClient._instance
|
||||
38
gns3/utils/authorize_ubridge.py
Normal file
38
gns3/utils/authorize_ubridge.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2023 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/>.
|
||||
|
||||
# This script is intended to be built as a small executable for macOS to set the correct permissions on uBridge
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
|
||||
def authorize_ubridge():
|
||||
|
||||
path = shutil.which("ubridge", path=os.path.dirname(sys.executable))
|
||||
if path is None:
|
||||
raise SystemExit("Could not find ubridge executable at {}".format(path))
|
||||
try:
|
||||
shutil.chown(path, "root", "admin")
|
||||
os.chmod(path, 0o4750)
|
||||
except OSError as e:
|
||||
raise SystemExit("Could not authorize {}: {}".format(path, str(e)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
authorize_ubridge()
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2014 GNS3 Technologies Inc.
|
||||
# Copyright (C) 2023 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
|
||||
@@ -15,50 +15,35 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
import pkg_resources
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import importlib_resources
|
||||
except ImportError:
|
||||
from importlib import resources as importlib_resources
|
||||
|
||||
|
||||
from contextlib import ExitStack
|
||||
resource_manager = ExitStack()
|
||||
atexit.register(resource_manager.close)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
egg_cache_dir = tempfile.mkdtemp()
|
||||
pkg_resources.set_extraction_path(egg_cache_dir)
|
||||
except ValueError:
|
||||
# If the path is already set the module throw an error
|
||||
pass
|
||||
|
||||
|
||||
@atexit.register
|
||||
def clean_egg_cache():
|
||||
try:
|
||||
import shutil
|
||||
shutil.rmtree(egg_cache_dir, ignore_errors=True)
|
||||
except Exception:
|
||||
# We don't care if we can not cleanup
|
||||
pass
|
||||
|
||||
|
||||
def get_resource(resource_name):
|
||||
"""
|
||||
Return a resource in current directory or in frozen package
|
||||
"""
|
||||
|
||||
resource_path = None
|
||||
if hasattr(sys, "frozen"):
|
||||
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name))
|
||||
if sys.platform.startswith("darwin") and not os.path.exists(resource_path):
|
||||
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), "lib", resource_name))
|
||||
elif not hasattr(sys, "frozen"):
|
||||
if pkg_resources.resource_exists("gns3", resource_name):
|
||||
try:
|
||||
resource_path = pkg_resources.resource_filename("gns3", resource_name)
|
||||
except pkg_resources.ExtractionError as e:
|
||||
log.fatal(e)
|
||||
sys.stderr.write(e)
|
||||
sys.exit(1)
|
||||
resource_path = os.path.normpath(resource_path)
|
||||
else:
|
||||
resource_path = os.path.dirname(os.path.realpath(__file__))
|
||||
resource_path = os.path.join(resource_path, "..", "..", "resources", resource_name)
|
||||
else:
|
||||
ref = importlib_resources.files("gns3") / resource_name
|
||||
path = resource_manager.enter_context(importlib_resources.as_file(ref))
|
||||
if os.path.exists(path):
|
||||
resource_path = os.path.normpath(path)
|
||||
return resource_path
|
||||
|
||||
64
gns3/utils/macos_ubridge_setuid.py
Normal file
64
gns3/utils/macos_ubridge_setuid.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2023 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/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def macos_ubridge_setuid():
|
||||
|
||||
# AuthorizationExecuteWithPrivileges() has been deprecated since macOS 10.7 but it still works
|
||||
# and much simpler than using SMJobBless() which requires a separate helper tool
|
||||
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
from ctypes import byref
|
||||
|
||||
authorize_ubridge = shutil.which("authorize_ubridge", path=os.path.dirname(sys.executable))
|
||||
if authorize_ubridge is None:
|
||||
raise OSError("Could not find the authorize_ubridge executable")
|
||||
|
||||
# https://developer.apple.com/documentation/security
|
||||
sec = ctypes.cdll.LoadLibrary(ctypes.util.find_library("Security"))
|
||||
|
||||
try:
|
||||
sec.AuthorizationCreate
|
||||
except AttributeError:
|
||||
raise OSError("macOS security library does not support AuthorizationCreate")
|
||||
|
||||
kAuthorizationFlagDefaults = 0
|
||||
auth = ctypes.c_void_p()
|
||||
r_auth = byref(auth)
|
||||
err = sec.AuthorizationCreate(None, None, kAuthorizationFlagDefaults, r_auth)
|
||||
if err:
|
||||
raise OSError("Could not create authorization: {}".format(err))
|
||||
|
||||
exe = [authorize_ubridge]
|
||||
log.info("Executing '{}' with privileges".format(exe))
|
||||
args = (ctypes.c_char_p * len(exe))()
|
||||
for i, arg in enumerate(exe[1:]):
|
||||
args[i] = arg.encode('utf8')
|
||||
io = ctypes.c_void_p()
|
||||
err = sec.AuthorizationExecuteWithPrivileges(auth, exe[0].encode('utf8'), 0, args, byref(io))
|
||||
if err:
|
||||
raise OSError("Could not authorize uBridge: {}".format(err))
|
||||
else:
|
||||
log.info("Successfully authorized uBridge")
|
||||
@@ -15,9 +15,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from gns3.qt import QtWidgets
|
||||
from gns3.utils.progress_dialog import ProgressDialog
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
# or negative for a release candidate or beta (after the base version
|
||||
# number has been incremented)
|
||||
|
||||
__version__ = "2.2.41.dev2"
|
||||
__version_info__ = (2, 2, 41, 99)
|
||||
__version__ = "2.2.43.dev1"
|
||||
|
||||
__version_info__ = (2, 2, 43, 99)
|
||||
if "dev" in __version__:
|
||||
try:
|
||||
import os
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
-rrequirements.txt
|
||||
|
||||
PyQt5==5.15.7
|
||||
PyQt5==5.15.9
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
jsonschema>=4.17.3,<4.18; python_version >= '3.7'
|
||||
jsonschema>=4.17.3,<4.18; python_version >= '3.7' # v4.17.3 is the last version to support Python 3.7
|
||||
jsonschema==3.2.0; python_version < '3.7' # v3.2.0 is the last version to support Python 3.6
|
||||
sentry-sdk==1.17.0,<1.18
|
||||
psutil==5.9.4
|
||||
sentry-sdk==1.29.2,<1.30
|
||||
psutil==5.9.5
|
||||
distro>=1.8.0
|
||||
truststore>=0.7.0; python_version >= '3.10'
|
||||
importlib-resources>=1.3; python_version <= '3.9'
|
||||
setuptools>=60.8.1; python_version >= '3.7'
|
||||
setuptools==59.6.0; python_version < '3.7' # v59.6.0 is the last version to support Python 3.6
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
-rrequirements.txt
|
||||
|
||||
PyQt5==5.15.7 # pyup: ignore
|
||||
pywin32==305 # pyup: ignore
|
||||
PyQt5==5.15.9 # pyup: ignore
|
||||
pywin32==306 # pyup: ignore
|
||||
|
||||
Reference in New Issue
Block a user