mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 08:34:40 +01:00
ui, pop-ups: added app description, better icon discovery
Added the description of an app to the pop-ups, to help users know what an application is or does. The discovery of app icons has been improved for those edge cases where the system is not properly configured and we were not able to get the icon of the app.
This commit is contained in:
parent
c7d93d83a5
commit
0362a0b780
3 changed files with 177 additions and 67 deletions
|
@ -6,6 +6,7 @@ import glob
|
|||
import os
|
||||
import re
|
||||
import shutil
|
||||
import locale
|
||||
|
||||
DESKTOP_PATHS = tuple([
|
||||
os.path.join(d, 'applications')
|
||||
|
@ -20,6 +21,7 @@ class LinuxDesktopParser(threading.Thread):
|
|||
self.running = False
|
||||
self.apps = {}
|
||||
self.apps_by_name = {}
|
||||
self.get_locale()
|
||||
# some things are just weird
|
||||
# (not really, i don't want to keep track of parent pids
|
||||
# just because of icons though, this hack is way easier)
|
||||
|
@ -37,6 +39,14 @@ class LinuxDesktopParser(threading.Thread):
|
|||
|
||||
self.start()
|
||||
|
||||
def get_locale(self):
|
||||
try:
|
||||
self.locale = locale.getlocale()[0]
|
||||
self.locale_country = self.locale.split("_")[0]
|
||||
except Exception as e:
|
||||
self.locale = ""
|
||||
self.locale_country = ""
|
||||
|
||||
def _parse_exec(self, cmd):
|
||||
# remove stuff like %U
|
||||
cmd = re.sub( r'%[a-zA-Z]+', '', cmd)
|
||||
|
@ -53,23 +63,39 @@ class LinuxDesktopParser(threading.Thread):
|
|||
if os.path.exists(filename):
|
||||
cmd = filename
|
||||
break
|
||||
|
||||
|
||||
return cmd
|
||||
|
||||
def _discover_app_icon(self, app_name):
|
||||
def get_app_description(self, parser):
|
||||
try:
|
||||
desc = parser.get('Desktop Entry', 'Comment[%s]' % self.locale_country, raw=True, fallback=None)
|
||||
if desc == None:
|
||||
desc = parser.get('Desktop Entry', 'Comment[%s]' % self.locale, raw=True, fallback=None)
|
||||
|
||||
if desc == None:
|
||||
desc = parser.get('Desktop Entry', 'Comment', raw=True, fallback=None)
|
||||
|
||||
return desc
|
||||
except:
|
||||
return None
|
||||
|
||||
def discover_app_icon(self, app_name):
|
||||
# more hacks
|
||||
# normally qt will find icons if the system if configured properly.
|
||||
# if it's not, qt won't be able to find the icon by using QIcon().fromTheme(""),
|
||||
# so we fallback to try to determine if the icon exist in some well known system paths.
|
||||
icon_dirs = ("/usr/share/icons/gnome/48x48/apps/", "/usr/share/pixmaps/", "/usr/share/icons/hicolor/48x48/apps/")
|
||||
icon_exts = (".png", ".xpm", ".svg")
|
||||
|
||||
for idir in icon_dirs:
|
||||
for iext in icon_exts:
|
||||
iconPath = idir + app_name + iext
|
||||
if os.path.exists(iconPath):
|
||||
print("found on last chance: ", iconPath)
|
||||
return iconPath
|
||||
if iext in app_name:
|
||||
iconPath = idir + app_name
|
||||
if os.path.exists(iconPath):
|
||||
return iconPath
|
||||
else:
|
||||
iconPath = idir + app_name + iext
|
||||
if os.path.exists(iconPath):
|
||||
return iconPath
|
||||
|
||||
def _parse_desktop_file(self, desktop_path):
|
||||
parser = configparser.ConfigParser(strict=False) # Allow duplicate config entries
|
||||
|
@ -84,21 +110,23 @@ class LinuxDesktopParser(threading.Thread):
|
|||
cmd = self._parse_exec(cmd)
|
||||
icon = parser.get('Desktop Entry', 'Icon', raw=True, fallback=None)
|
||||
name = parser.get('Desktop Entry', 'Name', raw=True, fallback=None)
|
||||
desc = self.get_app_description(parser)
|
||||
|
||||
if icon == None:
|
||||
# Some .desktop files doesn't have the Icon entry
|
||||
# FIXME: even if we return an icon, if the DE is not properly configured,
|
||||
# it won't be loaded/displayed.
|
||||
icon = self._discover_app_icon(basename)
|
||||
icon = self.discover_app_icon(basename)
|
||||
|
||||
with self.lock:
|
||||
# The Exec entry may have an absolute path to a binary or just the binary with parameters.
|
||||
# /path/binary or binary, so save both
|
||||
self.apps[cmd] = (name, icon, desktop_path)
|
||||
self.apps[basename] = (name, icon, desktop_path)
|
||||
self.apps[cmd] = (name, icon, desc, desktop_path)
|
||||
self.apps[basename] = (name, icon, desc, desktop_path)
|
||||
# if the command is a symlink, add the real binary too
|
||||
if os.path.islink(cmd):
|
||||
link_to = os.path.realpath(cmd)
|
||||
self.apps[link_to] = (name, icon, desktop_path)
|
||||
self.apps[link_to] = (name, icon, desc, desktop_path)
|
||||
except:
|
||||
print("Exception parsing .desktop file ", desktop_path)
|
||||
|
||||
|
@ -112,9 +140,9 @@ class LinuxDesktopParser(threading.Thread):
|
|||
|
||||
app_name = self.apps.get(path)
|
||||
if app_name == None:
|
||||
return self.apps.get(def_name, (def_name, default_icon, None))
|
||||
return self.apps.get(def_name, (def_name, default_icon, "", None))
|
||||
|
||||
return self.apps.get(path, (def_name, default_icon, None))
|
||||
return self.apps.get(path, (def_name, default_icon, "", None))
|
||||
|
||||
def get_info_by_binname(self, name, default_icon):
|
||||
def_name = os.path.basename(name)
|
||||
|
|
|
@ -2,11 +2,13 @@ import threading
|
|||
import sys
|
||||
import time
|
||||
import os
|
||||
import os.path
|
||||
import pwd
|
||||
import json
|
||||
import ipaddress
|
||||
|
||||
from PyQt5 import QtCore, QtGui, uic, QtWidgets
|
||||
from PyQt5.QtCore import QCoreApplication as QC
|
||||
|
||||
from slugify import slugify
|
||||
|
||||
|
@ -46,9 +48,9 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
# don't translate
|
||||
|
||||
# label displayed in the pop-up combo
|
||||
DURATION_session = QtCore.QCoreApplication.translate("popups", "until reboot")
|
||||
DURATION_session = QC.translate("popups", "until reboot")
|
||||
# label displayed in the pop-up combo
|
||||
DURATION_forever = QtCore.QCoreApplication.translate("popups", "forever")
|
||||
DURATION_forever = QC.translate("popups", "forever")
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
|
||||
|
@ -81,14 +83,15 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.denyButton.clicked.connect(self._on_deny_clicked)
|
||||
# also accept button
|
||||
self.applyButton.clicked.connect(self._on_apply_clicked)
|
||||
self._apply_text = QtCore.QCoreApplication.translate("popups", "Allow")
|
||||
self._deny_text = QtCore.QCoreApplication.translate("popups", "Deny")
|
||||
self._apply_text = QC.translate("popups", "Allow")
|
||||
self._deny_text = QC.translate("popups", "Deny")
|
||||
self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY)
|
||||
|
||||
self.whatIPCombo.setVisible(False)
|
||||
self.checkDstIP.setVisible(False)
|
||||
self.checkDstPort.setVisible(False)
|
||||
self.checkUserID.setVisible(False)
|
||||
self.appDescriptionLabel.setVisible(False)
|
||||
|
||||
self._ischeckAdvanceded = False
|
||||
self.checkAdvanced.toggled.connect(self._checkbox_toggled)
|
||||
|
@ -195,44 +198,61 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.denyButton.setText("%s (%d)" % (self._deny_text, self._tick))
|
||||
self.applyButton.setText(self._apply_text)
|
||||
|
||||
def _render_connection(self, con):
|
||||
app_name, app_icon, _ = self._apps_parser.get_info_by_path(con.process_path, "terminal")
|
||||
app_args = " ".join(con.process_args)
|
||||
def _set_app_description(self, description):
|
||||
if description != None:
|
||||
self.appDescriptionLabel.setVisible(True)
|
||||
self.appDescriptionLabel.setToolTip(description)
|
||||
self._set_elide_text(self.appDescriptionLabel, "%s" % description)
|
||||
else:
|
||||
self.appDescriptionLabel.setVisible(False)
|
||||
self.appDescriptionLabel.setText("")
|
||||
|
||||
def _set_app_path(self, app_name, app_args, con):
|
||||
# show the binary path if it's not part of the cmdline args:
|
||||
# cmdline: telnet 1.1.1.1 (path: /usr/bin/telnet.netkit)
|
||||
# cmdline: /usr/bin/telnet.netkit 1.1.1.1 (the binary path is part of the cmdline args, no need to display it)
|
||||
if con.process_path != "" and len(con.process_args) >= 1 and con.process_path not in con.process_args:
|
||||
self.appPathLabel.setToolTip("Process path: %s" % con.process_path)
|
||||
self._set_elide_text(self.appPathLabel, "(%s)" % con.process_path)
|
||||
if app_name.lower() == app_args:
|
||||
self._set_elide_text(self.appPathLabel, "%s" % con.process_path)
|
||||
else:
|
||||
self._set_elide_text(self.appPathLabel, "(%s)" % con.process_path)
|
||||
self.appPathLabel.setVisible(True)
|
||||
else:
|
||||
self.appPathLabel.setVisible(False)
|
||||
self.appPathLabel.setText("")
|
||||
|
||||
self.argsLabel.setVisible(True)
|
||||
self._set_elide_text(self.argsLabel, app_args)
|
||||
self.argsLabel.setToolTip(app_args)
|
||||
|
||||
def _set_app_args(self, app_name, app_args):
|
||||
# if the app name and the args are the same, there's no need to display
|
||||
# the args label (amule for example)
|
||||
if app_name.lower() == app_args:
|
||||
if app_name.lower() != app_args:
|
||||
self.argsLabel.setVisible(True)
|
||||
self._set_elide_text(self.argsLabel, app_args)
|
||||
self.argsLabel.setToolTip(app_args)
|
||||
else:
|
||||
self.argsLabel.setVisible(False)
|
||||
self.argsLabel.setText("")
|
||||
|
||||
def _render_connection(self, con):
|
||||
app_name, app_icon, description, _ = self._apps_parser.get_info_by_path(con.process_path, "terminal")
|
||||
app_args = " ".join(con.process_args)
|
||||
self._set_app_description(description)
|
||||
self._set_app_path(app_name, app_args, con)
|
||||
self._set_app_args(app_name, app_args)
|
||||
|
||||
if app_name == "":
|
||||
self.appPathLabel.setVisible(False)
|
||||
self.argsLabel.setVisible(False)
|
||||
app_name = QtCore.QCoreApplication.translate("popups", "Unknown process: %s" % con.process_path)
|
||||
self.appNameLabel.setText(QtCore.QCoreApplication.translate("popups", "Outgoing connection"))
|
||||
app_name = QC.translate("popups", "Unknown process: %s" % con.process_path)
|
||||
self.appNameLabel.setText(QC.translate("popups", "Outgoing connection"))
|
||||
else:
|
||||
self.appNameLabel.setText(app_name)
|
||||
self.appNameLabel.setToolTip(app_name)
|
||||
|
||||
self.cwdLabel.setToolTip("%s %s" % (QtCore.QCoreApplication.translate("popups", "Process launched from:"), con.process_cwd))
|
||||
self.cwdLabel.setToolTip("%s %s" % (QC.translate("popups", "Process launched from:"), con.process_cwd))
|
||||
self._set_elide_text(self.cwdLabel, con.process_cwd, max_size=32)
|
||||
|
||||
icon = QtGui.QIcon().fromTheme(app_icon)
|
||||
pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
|
||||
pixmap = self._get_app_icon(app_icon)
|
||||
self.iconLabel.setPixmap(pixmap)
|
||||
|
||||
message = self._get_popup_message(app_name, con)
|
||||
|
@ -258,29 +278,29 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.whatCombo.clear()
|
||||
self.whatIPCombo.clear()
|
||||
if int(con.process_id) > 0:
|
||||
self.whatCombo.addItem(QtCore.QCoreApplication.translate("popups", "from this executable"), self.FIELD_PROC_PATH)
|
||||
self.whatCombo.addItem(QC.translate("popups", "from this executable"), self.FIELD_PROC_PATH)
|
||||
|
||||
self.whatCombo.addItem(QtCore.QCoreApplication.translate("popups", "from this command line"), self.FIELD_PROC_ARGS)
|
||||
self.whatCombo.addItem(QC.translate("popups", "from this command line"), self.FIELD_PROC_ARGS)
|
||||
|
||||
# the order of the entries must match those in the preferences dialog
|
||||
# prefs -> UI -> Default target
|
||||
self.whatCombo.addItem(QtCore.QCoreApplication.translate("popups", "to port {0}").format(con.dst_port), self.FIELD_DST_PORT)
|
||||
self.whatCombo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP)
|
||||
self.whatCombo.addItem(QC.translate("popups", "to port {0}").format(con.dst_port), self.FIELD_DST_PORT)
|
||||
self.whatCombo.addItem(QC.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP)
|
||||
if int(con.user_id) >= 0:
|
||||
self.whatCombo.addItem(QtCore.QCoreApplication.translate("popups", "from user {0}").format(uid), self.FIELD_USER_ID)
|
||||
self.whatCombo.addItem(QC.translate("popups", "from user {0}").format(uid), self.FIELD_USER_ID)
|
||||
|
||||
self._add_dst_networks_to_combo(self.whatCombo, con.dst_ip)
|
||||
|
||||
if con.dst_host != "" and con.dst_host != con.dst_ip:
|
||||
self._add_dsthost_to_combo(con.dst_host)
|
||||
|
||||
self.whatIPCombo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP)
|
||||
self.whatIPCombo.addItem(QC.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP)
|
||||
|
||||
parts = con.dst_ip.split('.')
|
||||
nparts = len(parts)
|
||||
for i in range(1, nparts):
|
||||
self.whatCombo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}.*").format('.'.join(parts[:i])), self.FIELD_REGEX_IP)
|
||||
self.whatIPCombo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}.*").format( '.'.join(parts[:i])), self.FIELD_REGEX_IP)
|
||||
self.whatCombo.addItem(QC.translate("popups", "to {0}.*").format('.'.join(parts[:i])), self.FIELD_REGEX_IP)
|
||||
self.whatIPCombo.addItem(QC.translate("popups", "to {0}.*").format( '.'.join(parts[:i])), self.FIELD_REGEX_IP)
|
||||
|
||||
self._add_dst_networks_to_combo(self.whatIPCombo, con.dst_ip)
|
||||
|
||||
|
@ -311,12 +331,12 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
|
||||
def _add_dst_networks_to_combo(self, combo, dst_ip):
|
||||
if type(ipaddress.ip_address(dst_ip)) == ipaddress.IPv4Address:
|
||||
combo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/24", strict=False)), self.FIELD_DST_NETWORK)
|
||||
combo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/16", strict=False)), self.FIELD_DST_NETWORK)
|
||||
combo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/8", strict=False)), self.FIELD_DST_NETWORK)
|
||||
combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/24", strict=False)), self.FIELD_DST_NETWORK)
|
||||
combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/16", strict=False)), self.FIELD_DST_NETWORK)
|
||||
combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/8", strict=False)), self.FIELD_DST_NETWORK)
|
||||
else:
|
||||
combo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/64", strict=False)), self.FIELD_DST_NETWORK)
|
||||
combo.addItem(QtCore.QCoreApplication.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/128", strict=False)), self.FIELD_DST_NETWORK)
|
||||
combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/64", strict=False)), self.FIELD_DST_NETWORK)
|
||||
combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/128", strict=False)), self.FIELD_DST_NETWORK)
|
||||
|
||||
def _add_dsthost_to_combo(self, dst_host):
|
||||
self.whatCombo.addItem("%s" % dst_host, self.FIELD_DST_HOST)
|
||||
|
@ -325,12 +345,32 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
parts = dst_host.split('.')[1:]
|
||||
nparts = len(parts)
|
||||
for i in range(0, nparts - 1):
|
||||
self.whatCombo.addItem(QtCore.QCoreApplication.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST)
|
||||
self.whatIPCombo.addItem(QtCore.QCoreApplication.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST)
|
||||
self.whatCombo.addItem(QC.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST)
|
||||
self.whatIPCombo.addItem(QC.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST)
|
||||
|
||||
if nparts == 1:
|
||||
self.whatCombo.addItem(QtCore.QCoreApplication.translate("popups", "to *{0}").format(dst_host), self.FIELD_REGEX_HOST)
|
||||
self.whatIPCombo.addItem(QtCore.QCoreApplication.translate("popups", "to *{0}").format(dst_host), self.FIELD_REGEX_HOST)
|
||||
self.whatCombo.addItem(QC.translate("popups", "to *{0}").format(dst_host), self.FIELD_REGEX_HOST)
|
||||
self.whatIPCombo.addItem(QC.translate("popups", "to *{0}").format(dst_host), self.FIELD_REGEX_HOST)
|
||||
|
||||
def _get_app_icon(self, app_icon):
|
||||
"""we try to get the icon of an app from the system.
|
||||
If it's not found, then we'll try to search for it in common directories
|
||||
of the system.
|
||||
"""
|
||||
icon = QtGui.QIcon().fromTheme(app_icon)
|
||||
pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
|
||||
if QtGui.QIcon().hasThemeIcon(app_icon) == False or pixmap.height() == 0:
|
||||
# sometimes the icon is an absolute path, sometimes it's not
|
||||
if os.path.isabs(app_icon):
|
||||
icon = QtGui.QIcon(app_icon)
|
||||
pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
|
||||
else:
|
||||
icon_path = self._apps_parser.discover_app_icon(app_icon)
|
||||
if icon_path != None:
|
||||
icon = QtGui.QIcon(icon_path)
|
||||
pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
|
||||
|
||||
return pixmap
|
||||
|
||||
def _get_popup_message(self, app_name, con):
|
||||
"""
|
||||
|
@ -340,17 +380,17 @@ class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
"""
|
||||
message = "<b>%s</b>" % app_name
|
||||
if not self._local:
|
||||
message = QtCore.QCoreApplication.translate("popups", "<b>Remote</b> process %s running on <b>%s</b>") % ( \
|
||||
message = QC.translate("popups", "<b>Remote</b> process %s running on <b>%s</b>") % ( \
|
||||
message,
|
||||
self._peer.split(':')[1])
|
||||
|
||||
msg_action = QtCore.QCoreApplication.translate("popups", "is connecting to <b>%s</b> on %s port %d") % ( \
|
||||
msg_action = QC.translate("popups", "is connecting to <b>%s</b> on %s port %d") % ( \
|
||||
con.dst_host or con.dst_ip,
|
||||
con.protocol.upper(),
|
||||
con.dst_port )
|
||||
|
||||
if con.dst_port == 53 and con.dst_ip != con.dst_host and con.dst_host != "":
|
||||
msg_action = QtCore.QCoreApplication.translate("popups", "is attempting to resolve <b>%s</b> via %s, %s port %d") % ( \
|
||||
msg_action = QC.translate("popups", "is attempting to resolve <b>%s</b> via %s, %s port %d") % ( \
|
||||
con.dst_host,
|
||||
con.dst_ip,
|
||||
con.protocol.upper(),
|
||||
|
|
|
@ -494,7 +494,7 @@
|
|||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>18</pointsize>
|
||||
<pointsize>16</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
<kerning>true</kerning>
|
||||
|
@ -511,14 +511,8 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="argsLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="appDescriptionLabel">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
|
@ -528,24 +522,22 @@
|
|||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
<weight>50</weight>
|
||||
<italic>true</italic>
|
||||
<bold>false</bold>
|
||||
<underline>true</underline>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>/opt/google/chrome/bin/chrome --something abc --more-long def --for-word-wrapping</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::NoTextInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="appPathLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
|
@ -573,6 +565,56 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="argsLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>500</width>
|
||||
<height>60</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
<kerning>true</kerning>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>(/path/to/bin/chromium)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::NoTextInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
Loading…
Add table
Reference in a new issue