From 3fd5815677d3a7c65d489fbe651854950f80cf7c Mon Sep 17 00:00:00 2001 From: Dpeta <69427753+Dpeta@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:08:58 +0100 Subject: [PATCH 1/5] Remove libseccomp (silly) --- README.md | 4 - libseccomp.py | 245 -------------------------------------------------- pesterchum.py | 39 -------- 3 files changed, 288 deletions(-) delete mode 100644 libseccomp.py diff --git a/README.md b/README.md index 32200a9..02098f9 100644 --- a/README.md +++ b/README.md @@ -92,9 +92,6 @@ Pesterchum is a Python script. This means that as long as you have Python instal - Useful for Linux systems that don't meet the Qt6 requirements, as Qt5 Multimedia has a GStreamer dependency. - (Optional) [certifi] can provide alternative root certificates for TLS certificate validation. - Useful for MacOS, as Python doesn't use the system-provided certificates because of MacOS' outdated SSL library. Also miscellaneous systems without usable root certificates. - - (Optional) [libseccomp] and its Python bindings on Linux let Pesterchum apply seccomp-bpf restrictions on itself. - - Packages on Arch: ``libseccomp python-libseccomp`` - - Packages on Debian: ``libseccomp2 python-seccomp`` ### WALKTHROUGH @@ -123,7 +120,6 @@ Pesterchum is a Python script. This means that as long as you have Python instal [pygame-ce]: https://pypi.org/project/pygame-ce/ [certifi]: https://pypi.org/project/certifi/ [GStreamer]: https://gstreamer.freedesktop.org/ -[libseccomp]: https://github.com/seccomp/libseccomp/ ## FREEZE / BUILD Here's a quick guide on how to freeze Pesterchum, (that is, packaging it with python as an executable). :3 diff --git a/libseccomp.py b/libseccomp.py deleted file mode 100644 index 80a00cb..0000000 --- a/libseccomp.py +++ /dev/null @@ -1,245 +0,0 @@ -"""Functions for Applying a seccomp filter on Linux. - -This prevents the process from using certain system calls, which has some security benefits. -Since Python and Qt use many calls and are pretty high-level, things are prone to breaking though. -Certain features like opening links almost always break. - -Uses libseccomp's Python bindings, which sadly aren't available on PyPi (yet). -Check your distro's package manager for python-libseccomp (Arch) or python3-seccomp (Debian). - -For info on system calls referencing software that uses seccomp like firejail/flatpak is useful. -Bindings documentation: https://github.com/seccomp/libseccomp/blob/main/src/python/seccomp.pyx -""" -import os -import logging -import threading - -try: - import seccomp -except ImportError: - seccomp = None - -pesterchum_log = logging.getLogger("pchumLogger") - - -def load_seccomp_blacklist(): - """Applies a selective seccomp filter only disallows certain risky calls. - - Should be less likely to cause issues than a full-on whitelist.""" - if seccomp is None: - pesterchum_log.warning( - "Failed to import seccomp, verify you have" - " python-libseccomp (Arch) or python3-seccomp (Debian) installed." - " If this is a pyinstaller/cx_freeze build, it may also be a linking issue." - ) - return - # Allows all calls by default. - sec_filter = seccomp.SyscallFilter(defaction=seccomp.ALLOW) - - # Deny all socket domains other than AF_UNIX and and AF_INET. - sec_filter.add_rule(seccomp.ERRNO(1), "socket", seccomp.Arg(0, seccomp.LT, 1)) - sec_filter.add_rule(seccomp.ERRNO(1), "socket", seccomp.Arg(0, seccomp.GT, 2)) - - # Fully deny these calls. - for call in CALL_BLACKLIST: - try: - sec_filter.add_rule(seccomp.ERRNO(1), call) - except RuntimeError: - pesterchum_log.warning("Failed to load deny '%s' call seccomp rule.", call) - - sec_filter.load() - - -def load_seccomp_whitelist(): - """Applies a restrictive seccomp filter that disallows most calls by default.""" - if seccomp is None: - pesterchum_log.error( - "Failed to import seccomp, verify you have" - " python-libseccomp (Arch) or python3-seccomp (Debian) installed." - " If this is a pyinstaller/cx_freeze build, it may also be a linking issue." - ) - return - # Violation gives "Operation not permitted". - sec_filter = seccomp.SyscallFilter(defaction=seccomp.ERRNO(1)) - # Full access calls - for call in CALL_WHITELIST: - try: - sec_filter.add_rule(seccomp.ALLOW, call) - except RuntimeError: - pesterchum_log.warning("Failed to load allow '%s' call seccomp rule.", call) - - # Allow only UNIX and INET sockets, see the linux manual and source on socket for reference. - # Arg(0, seccomp.EQ, 1) means argument 0 must be equal to 1, 1 being the value of AF_UNIX. - # Allow AF_UNIX - sec_filter.add_rule(seccomp.ALLOW, "socket", seccomp.Arg(0, seccomp.EQ, 1)) - # Allow AF_INET - sec_filter.add_rule(seccomp.ALLOW, "socket", seccomp.Arg(0, seccomp.EQ, 2)) - - # Python/Qt might close itself via kill call in case of error. - sec_filter.add_rule(seccomp.ALLOW, "kill", seccomp.Arg(0, seccomp.EQ, os.getpid())) - sec_filter.add_rule( - seccomp.ALLOW, "tgkill", seccomp.Arg(1, seccomp.EQ, threading.get_native_id()) - ) - - # Allow openat as long as it's not in R+W mode. - # We can't really lock down open/openat further without breaking everything, - # even though it's one of the most important calls to lock down. - # Could probably allow breaking out of the sandbox in the case of full-on RCE/ACE. - sec_filter.add_rule(seccomp.ALLOW, "openat", seccomp.Arg(2, seccomp.NE, 2)) - - sec_filter.load() - - -# Required for Pesterchum to function normally. -# We don't call most of these directly, there's a lot of abstraction with Python and Qt. -CALL_WHITELIST = [ - "access", # Files - "brk", # Required - "clone3", # Required - "close", # Sockets (Audio + Network) - "connect", # Sockets (Audio + Network) - "exit", # Exiting - "exit_group", # Exiting - "fallocate", # Qt - "fcntl", # Required (+ Audio) - "fsync", # Fsync log files - "ftruncate", # Required - "futex", # Required - "getcwd", # Get working directory - "getdents", # Files? Required. - "getgid", # Audio - "getpeername", # Connect - "getpid", # Audio - "getrandom", # Malloc - "getsockname", # Required for sockets - "getsockopt", # Required for sockets - "getuid", # Audio - "ioctl", # Socket/Network - "lseek", # Files - "memfd_create", # Required (For Qt?) - "mkdir", # Gotta make folderz sometimez - "mmap", # Audio - "mprotect", # QThread::start - "munmap", # Required (segfault) - "newfstatat", # Required (Audio + Path?) - "pipe2", # Audio - "poll", # Required for literally everything - "pselect6", # Sockets/Network - "read", # It's read :3 - "readlink", # Files - "recv", # Network - "recvfrom", # Network + DNS - "recvmsg", # Sockets (incl. Audio + Network) - "rseq", # Required - "rt_sigprocmask", # Required (segfault) - "select", # Useful for sockets - "sendmmsg", # Network - "sendmsg", # Sockets - "sendto", # Eternal eepy!! Sockets + required for waking up mainloop. - "setsockopt", # Audio - "statx", # File info - "uname", # Required - "write", # Required -] - -# Blacklists of calls we should be able to safely deny. -# Setuid might be useful to drop privileges. -SETUID = [ - "setgid", - "setgroups", - "setregid", - "setresgid", - "setresuid", - "setreuid", - "setuid", -] -SYSTEM = [ - "acct", - "bpf", - "capset", - "chown", - "chroot", - "fanotify_init", - "fsconfig", - "fsmount", - "fsopen", - "fspick", - "kexec_file_load", - "kexec_load", - "lookup_dcookie", - "mount", - "move_mount", - "nfsservctl", - "open_by_handle_at", - "open_tree", - "perf_event_open", - "personality", - "pidfd_getfd", - "pivot_root", - "pivot_root", - "process_vm_readv", - "process_vm_writev", - "ptrace", # <-- Important - "quotactl", - "reboot", - "rtas", - "s390_runtime_instr", - "setdomainname", - "setfsuid", - "sethostname", - "swapoff", - "swapon", - "sys_debug_setcontext", - "umount", - "umount2", - "vhangup", -] -CALL_BLACKLIST = SYSTEM # + SETUID - -""" -# Optional -EXTRA_CALLS = [ - "mlock", - "munlock", - "socketcall", - "socketpair", - "readlink", - "getsockname", - "getpeername", - "writev", - "open", - "time", - "listen", -] -# Required on launch -LAUNCH_CALLS = [ - "prctl", - "faccessat", - "faccessat2", - "pwrite64", -] -# Required before full initialize -PRE_INITIALIZE_CALLS = [ - "bind", - "eventfd2", - "getegid", - "geteuid", - "getresgid", - "getresuid", - "gettid", - "recvmmsg", - "restart_syscall", - "rt_sigaction", - "rt_sigreturn", - "sched_getaffinity", - "sched_getattr", - "sched_setattr", - "shutdown", - "umask", -] -# Required for opening links, but opening links still doesn't work anyway. -LINK_CALLS = [ - "wait4", # for links? - "clone", # for links? -] -""" diff --git a/pesterchum.py b/pesterchum.py index f7f1055..c1c6a05 100755 --- a/pesterchum.py +++ b/pesterchum.py @@ -19,9 +19,6 @@ import ostools import nickservmsgs import pytwmn -if ostools.isLinux(): - import libseccomp - # import console from user_profile import ( userConfig, @@ -114,21 +111,6 @@ parser.add_argument( parser.add_argument( "--nohonk", action="store_true", help="Disables the honk soundeffect 🤡📣" ) -if ostools.isLinux(): - parser.add_argument( - "--no-seccomp", - action="store_true", - help=("Disable seccomp completely. (do this if it causes issues)"), - ) - parser.add_argument( - "--strict-seccomp", - action="store_true", - help=( - "Apply a stricter seccomp-bpf filter that only allows required system calls." - " This breaks certain features like opening links." - " (Requires Linux and libseccomp's Python bindings.)" - ), - ) # Set logging config section, log level is in oppts. # Logger @@ -1692,22 +1674,6 @@ class PesterWindow(MovingWindow): if ostools.isLinux(): # Set no_new_privs bit. self.set_no_new_privs() - # Activate seccomp. - self.seccomp(options) - - def seccomp(self, options): - """Load seccomp-bpf filter depending on arguments passed.""" - if "no-seccomp" in options: - if options["no-seccomp"]: - return - try: - libseccomp.load_seccomp_blacklist() # Load blacklist filter by default - if "strict-seccomp" in options: - if options["strict-seccomp"]: - libseccomp.load_seccomp_whitelist() # Load whitelist filter if enabled - except RuntimeError: - # We probably tried to interact with a call not available on this kernel. - PchumLog.exception("") @QtCore.pyqtSlot(str, str) def updateMsg(self, ver, url): @@ -4592,11 +4558,6 @@ class MainProgram(QtCore.QObject): # Disable honks if args.nohonk: options["honk"] = False - if ostools.isLinux(): - if args.strict_seccomp: - options["strict-seccomp"] = True - if args.no_seccomp: - options["no-seccomp"] = True except Exception as e: print(e) return options From b04679872482d6bc55e94297bf81af13e7014648 Mon Sep 17 00:00:00 2001 From: Dpeta <69427753+Dpeta@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:13:59 +0100 Subject: [PATCH 2/5] Remove setup.py/cx_freeze setup, currently superseded by pyinstaller. --- README.md | 4 -- setup.py.disabled_for_now | 147 -------------------------------------- 2 files changed, 151 deletions(-) delete mode 100644 setup.py.disabled_for_now diff --git a/README.md b/README.md index 02098f9..98ae575 100644 --- a/README.md +++ b/README.md @@ -126,10 +126,6 @@ Here's a quick guide on how to freeze Pesterchum, (that is, packaging it with py Ideally, you'll want to create and activate a [virtual environment](https://docs.python.org/3/library/venv.html) before anything else, this is not 100% required though. -### [CX_FREEZE](https://cx-freeze.readthedocs.io/en/latest/index.html) -1. ``python3 -m pip install cx_freeze`` -2. ``python3 setup.py build`` - ### [PYINSTALLER](https://pyinstaller.readthedocs.io/en/stable/) 1. ``python3 -m pip install pyinstaller`` 2. ``python3 pyinst.py`` diff --git a/setup.py.disabled_for_now b/setup.py.disabled_for_now deleted file mode 100644 index 5037d6b..0000000 --- a/setup.py.disabled_for_now +++ /dev/null @@ -1,147 +0,0 @@ -# Windows-only cx_freeze setup file, macOS may work but I've not tested it. -import sys - -from cx_Freeze import setup, Executable - -from version import buildVersion - -if sys.version_info < (3, 0, 0): - sys.exit("Python versions lower than 3 are not supported.") - - -def is_64bit() -> bool: - return sys.maxsize > 2**32 - - -path = "" -base = None -if sys.platform == "win32": - base = "Win32GUI" - - path = sys.path - if is_64bit() == True: - path.append( - r"C:\Program Files (x86)\Windows Kits\10\Redist\10.0.22000.0\ucrt\DLLs\x64" - ) - elif is_64bit() == False: - path.append( - r"C:\Program Files (x86)\Windows Kits\10\Redist\10.0.22000.0\ucrt\DLLs\x86" - ) - - print("Path = " + str(path)) - -includefiles = [ - "quirks", - "smilies", - "themes", - "docs", - "README.md", - "LICENSE", - "CHANGELOG.md", - "PCskins.png", - "Pesterchum.png", -] -build_exe_options = { - # "includes": ['PyQt6.QtCore', - # 'PyQt6.QtGui', - # 'PyQt6.QtWidgets'], - "excludes": [ - "collections.sys", - "collections._sre", - "collections._json", - "collections._locale", - "collections._struct", - "collections.array", - "collections._weakref", - "PyQt6.QtMultimedia", - "PyQt6.QtDBus", - "PyQt6.QtDeclarative", - "PyQt6.QtHelp", - "PyQt6.QtNetwork", - "PyQt6.QtSql", - "PyQt6.QtSvg", - "PyQt6.QtTest", - "PyQt6.QtWebKit", - "PyQt6.QtXml", - "PyQt6.QtXmlPatterns", - "PyQt6.phonon", - "PyQt6.QtAssistant", - "PyQt6.QtDesigner", - "PyQt6.QAxContainer", - "pygame.tests", - "pydoc_data", - ], - "include_files": includefiles, - "include_msvcr": True, # cx_freeze copies 64-bit binaries always? - "path": path # Improved in 6.6, path to be safe - # VCRUNTIME140.dll <3 -} - -if ( - (sys.platform == "win32") - & (sys.version_info.major == 3) - & (sys.version_info.minor == 8) -): - build_exe_options["excludes"].append("tkinter") - - -bdist_mac_options = {"iconfile": "trayicon32.icns", "bundle_name": "Pesterchum"} - -description = "Pesterchum" -icon = "pesterchum.ico" - -# See https://stackoverflow.com/questions/15734703/use-cx-freeze-to-create-an-msi-that-adds-a-shortcut-to-the-desktop -shortcut_table = [ - ( - "DesktopShortcut", # Shortcut - "DesktopFolder", # Directory_ - "Pesterchum", # Name - "TARGETDIR", # Component_ - "[TARGETDIR]pesterchum.exe", # Target - None, # Arguments - description, # Description - None, # Hotkey - None, # Icon (Is inherited from pesterchum.exe) - None, # IconIndex - None, # ShowCmd - "TARGETDIR", # WkDir - ), - ( - "StartMenuShortcut", # Shortcut - "StartMenuFolder", # Directory_ - "Pesterchum", # Name - "TARGETDIR", # Component_ - "[TARGETDIR]pesterchum.exe", # Target - None, # Arguments - description, # Description - None, # Hotkey - None, # Icon - None, # IconIndex - None, # ShowCmd - "TARGETDIR", # WkDir - ), -] - -msi_data = {"Shortcut": shortcut_table} -bdist_msi_options = { - "data": msi_data, - "summary_data": {"comments": "FL1P", "keywords": "Pesterchum"}, - "upgrade_code": "{86740d75-f1f2-48e8-8266-f36395a2d77f}", - "add_to_path": False, # !!! - "all_users": False, - "install_icon": "pesterchum.ico", -} - -setup( - name="Pesterchum", - version=buildVersion, - url="https://github.com/Dpeta/pesterchum-alt-servers", - description=description, # "P3ST3RCHUM", - options={ - "build_exe": build_exe_options, - "bdist_msi": bdist_msi_options, - "bdist_mac": bdist_mac_options, - }, - packages="", - executables=[Executable("pesterchum.py", base=base, icon=icon)], -) From 3585547558bac0d6b4591da993ebe6068c19dcbb Mon Sep 17 00:00:00 2001 From: Dpeta <69427753+Dpeta@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:16:00 +0100 Subject: [PATCH 3/5] Remove remnants of lua quirks, they've not worked since python 3 and I'm not aware of anyone who uses them. --- luaquirks.py | 95 --------------------------------------------------- parsetools.py | 3 -- 2 files changed, 98 deletions(-) delete mode 100644 luaquirks.py diff --git a/luaquirks.py b/luaquirks.py deleted file mode 100644 index 636afeb..0000000 --- a/luaquirks.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Okay so, this definitely doesn't work in its current state- -Probably broke when transitioning to Python 3? Might've been broken for longer- -Hard for me to work on this since I know absolutely nothing about lua, plus I'm not sure what 'lua' library this was originally supposed to work with. - + I asked and there doesn't seem to be a single person who actually used this 💀 - -import logging -import os -import sys -import re - -from PyQt6 import QtCore, QtGui, QtWidgets - -import ostools -from quirks import ScriptQuirks - -PchumLog = logging.getLogger('pchumLogger') - -try: - try: - import lua - except ImportError: - import lupa - from lupa import LuaRuntime - - lua = LuaRuntime(unpack_returned_tuples=True) - PchumLog.info("Lua \"successfully\" imported.") -except ImportError as e: - PchumLog.warning("No lua library. " + str(e)) - lua = None - -class LuaQuirks(ScriptQuirks): - def loadModule(self, name, filename): - if lua is None: - return None - - lua.globals().package.loaded[name] = None - - CurrentDir = os.getcwd() - os.chdir('quirks') - - lua.globals().package.path = filename.replace(name+".lua", "?.lua") - - try: - return lua.require(name) - except Exception as e: - PchumLog.warning(e) - return None - finally: - os.chdir(CurrentDir) - - def getExtension(self): - return '.lua' - - def modHas(self, module, attr): - return attr in module - - def register(self, module): - class Wrapper(object): - def __init__(self, module, name): - self.module = module - self.name = name - - def __call__(self, text): - CurrentDir = os.getcwd() - os.chdir('quirks') - try: - return self.module.commands[self.name](lua.globals().tostring(text)) - except Exception as e: - PchumLog.warning(e) - return None - finally: - os.chdir(CurrentDir) - - for name in module.commands: - CommandWrapper = Wrapper(module,name) - try: - if not isinstance(CommandWrapper("test"), str): - raise Exception - except: - #print("Quirk malformed: %s" % (name)) - PchumLog.error("Quirk malformed: %s" % (name)) - - # Since this is executed before QApplication is constructed, - # This prevented pesterchum from starting entirely when a quirk was malformed :/ - # (QWidget: Must construct a QApplication before a QWidget) - - if QtWidgets.QApplication.instance() != None: - msgbox = QtWidgets.QMessageBox() - msgbox.setWindowTitle("Error!") - msgbox.setText("Quirk malformed: %s" % (name)) - msgbox.exec_() - else: - self.quirks[name] = CommandWrapper -""" diff --git a/parsetools.py b/parsetools.py index 0eefe4f..5c8003f 100644 --- a/parsetools.py +++ b/parsetools.py @@ -18,8 +18,6 @@ from generic import mysteryTime from quirks import ScriptQuirks from pyquirks import PythonQuirks -# from luaquirks import LuaQuirks - PchumLog = logging.getLogger("pchumLogger") # I'll clean up the things that are no longer needed once the transition is @@ -50,7 +48,6 @@ _functionre = None def loadQuirks(): global quirkloader, _functionre quirkloader.add(PythonQuirks()) - # quirkloader.add(LuaQuirks()) quirkloader.loadAll() quirkloader.funcre() _functionre = re.compile(r"%s" % quirkloader.funcre()) From 9c3ca20ad3b2467447cc7dbf730361cf1e5a0fdd Mon Sep 17 00:00:00 2001 From: Dpeta <69427753+Dpeta@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:23:53 +0100 Subject: [PATCH 4/5] Remove console, not a lot of people used this. --- TODO.md | 9 - console.py.disabled | 554 -------------------------------------------- pesterchum.py | 147 ------------ start.sh | 1 - 4 files changed, 711 deletions(-) delete mode 100644 console.py.disabled delete mode 100644 start.sh diff --git a/TODO.md b/TODO.md index 0ecff04..f71306d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,17 +1,8 @@ # TODO LIST : ) -## ADD - - ~~Memoserv support.~~ - - ~~Use memopermabanmsg function?~~ <--- Permabans just give a KICK server response, there's no way to check for this. - ## FIX - - Mask & target not being passed to ``_max_msg_len``. - - Is this even possible to keep updated reliably? - - ~~No error message when Pesterchum fails to join a channel. (For example, when the channel name length is over CHANNELLEN)~~ - Choose memo window doesn't get updated on theme change. - Right click menu's color doesn't get updated on theme change in memos. - - help() causes console to crash...? - - Console is hopelessly broken, it'd be easier to make a list of what commands *don't* cause it to crash. Does anyone use this? ## CHANGE - When everything has been tested and more people have compatible versions, switch Pesterchum over to using metadata and message tags by default instead of wrapping its protocol in PRIVMSG. The current method should stay available for compatibility. diff --git a/console.py.disabled b/console.py.disabled deleted file mode 100644 index 68a37fd..0000000 --- a/console.py.disabled +++ /dev/null @@ -1,554 +0,0 @@ -""" -# vim: set autoindent ts=4 sts=4 sw=4 textwidth=79 expandtab: -# -*- coding=UTF-8; tab-width: 4 -*- -# import os -# from os import remove -import sys -import traceback -import time -import datetime -import logging - -try: - from PyQt6 import QtCore, QtGui, QtWidgets - from PyQt6.QtGui import QAction -except ImportError: - print("PyQt5 fallback (console.py)") - from PyQt5 import QtCore, QtGui, QtWidgets - from PyQt5.QtWidgets import QAction - -import dataobjs - -# import generic -# import memos -# import parsetools -import ostools - -# from version import _pcVersion -from pnc.dep.attrdict import AttrDict - -PchumLog = logging.getLogger("pchumLogger") - - -class ConsoleWindow(QtWidgets.QDialog): - # ~class ConsoleWindow(styler.PesterBaseWindow): - # A simple console class, cobbled together from the corpse of another. - - stylesheet_path = "main/defaultwindow/style" - # This is a holder for our text inputs. - text = AttrDict() - # I should probably put up constants for 'direction' if this is going to - # get this complicated. TODO! - incoming_prefix = "<<<" - miscinfo_prefix = "==>" - outgoing_prefix = ">>>" - neutral_prefix = "!!!" - waiting_prefix = "..." - - selected_widget = None - show_info_on_select = True - - _CUSTOM_ENV = {} - - def __init__(self, parent): - super(ConsoleWindow, self).__init__(parent) - self.prnt = parent - try: - self.mainwindow = parent.mainwindow - except: - self.mainwindow = parent - theme = self.mainwindow.theme - # This won't initialize the sub-objects, because they don't exist yet. - self.initTheme(theme) - - self.text = AttrDict() - self.text.area = ConsoleText(theme, self) - self.text.input = ConsoleInput(theme, self) - self.text.input.setFocus() - - self.text.input.returnPressed.connect(self.sentMessage) - - self.text.history = dataobjs.PesterHistory() - - # For backing these up - self.stdout = self.stderr = None - - layout_0 = QtWidgets.QVBoxLayout() - layout_0.addWidget(self.text.area) - layout_0.addWidget(self.text.input) - self.setLayout(layout_0) - - def parent(self): - return self.prnt - - def clearNewMessage(self): - pass - - @QtCore.pyqtSlot() - def sentMessage(self): - text = self.text.input.text() - # TODO: Make this deal with unicode text, it'll crash and burn as-is. - text = str(text) - text = text.rstrip() - - self.text.history.add(text) - self.text.input.setText("") - - self.execInConsole(text) - # Scroll down to the bottom so we can see the results. - sb = self.text.area.verticalScrollBar() - sb.setValue(sb.maximum()) - - def addTraceback(self, tb=None): - # We should do the formatting here, but eventually pass it to text.area - # to addMessage whatever output we produced. - # If we're called by addMessage - and we should be - then sys.stdout is - # still being redirected into the console. - # TODO: Just make an object for setting contexts (and thus optionally - # redirecting prints). Use 'with', of course. - # TODO: Make this exclude *our* processing from the traceback stack. - try: - self.addMessage(traceback.format_exc(), direction=0) - except Exception as err: - PchumLog.error("Failed to display error message (???): %s" % err) - - def addMessage(self, msg, direction): - # Redirect to where these things belong. - self.text.area.addMessage(msg, direction=direction) - - def closeEvent(self, event): - # TODO: Set up ESC to close the console...or refer to hiding it as - # closing it. Not sure which is preferable. - parent = self.parent() - parent.console.is_open = False - parent.console.window = None - return super(ConsoleWindow, self).closeEvent(event) - - def hideEvent(self, event): - parent = self.parent() - parent.console.is_open = False - - def initTheme(self, theme): - # Set up our style/window specifics - self.changeTheme(theme) - self.resize(400, 600) - - def changeTheme(self, theme): - self.setStyleSheet(theme[self.stylesheet_path]) - self.setWindowTitle("==> Console") - if "area" in self.text and "input" in self.text: - self.text.area.changeTheme(theme) - self.text.input.changeTheme(theme) - - @QtCore.pyqtSlot() - def designateCurrentWidget(self): - # Display and save the current widget! - # TODO: Consider (reversible) highlighting or selection or something - # fancy. It'd help people write styles, wouldn't it? - # ...just remember to use mouseRelease() if you work with hovering. - - # Direction: Misc. Info - direction = 2 - - pos = QtGui.QCursor.pos() - wgt = QtWidgets.QApplication.widgetAt(pos) - if wgt is None: - # Don't set None, for now. May change this later. - self.addMessage( - "You need to have your cursor over something " - + "in Pesterchum to use that.", - direction=direction, - ) - return - - self.selected_widget = wgt - nchild = len(wgt.children()) - output = [] - output.append("CONSOLE.selected_widget = {0!r}".format(wgt)) - output.append("{0: <4}Parent: {1!r}".format("", wgt.parent())) - output.append( - "{0: <4}{1:4d} child{2}".format( - "", nchild, ("ren" if abs(nchild) != 1 else "") - ) - ) - if self.show_info_on_select: - qtss = None - uses_ss = None - try: - qtss = wgt.styleSheet() - except: - pass - else: - if str(qtss) == str(""): - uses_ss, ss_msg = False, "No" - elif qtss is not None: - uses_ss, ss_msg = True, "Yes" - else: - uses_ss, ss_msg = None, "Invalid" - - ss_par, ss_par_msg = None, "" - if uses_ss is False: - # TODO: Split this into a sub-function or integrate it into - # Styler or *something*. - # The stylesheet was probably defined on a parent higher up. - # Rungs above the start - i = 0 - # qtss is still "" from earlier - while not qtss: - try: - ss_par = wgt.parent() - qtss = ss_par.styleSheet() - except: - # Can't ascend...and we're still in loop, so we don't - # have what we came for. - # Either that, or it's incompatible, which means the - # ones above are anyway. - ss_par = False - break - else: - # Indicate that we got this from a parent - i += 1 - - if not qtss: - # There are no stylesheets here. - if ss_par is False: - # We had parent issues. - # TODO: Specifically indicate invalid parent. - uses_ss, ss_msg = None, "Invalid" - else: - uses_ss, ss_msg = False, "No" - else: - # We got a stylesheet out of this! - uses_ss, ss_msg = True, "Yes" - # ~ss_par_msg = "{0: <4}...on parent ↑{1:d}: {2!r}".format('', - ss_par_msg = "{0: <4}...on parent #{1:d}: {2!r}".format( - "", i, ss_par - ) - - msg = [] - msg.append("{0: <4}QtSS?: {1}".format("", ss_msg)) - # A stylesheet analyzer would be wonderful here. Perhaps something - # that tells us how many parent classes define stylesheets? - if uses_ss: - if ss_par_msg: - # We got this stylesheet from a parent object somewhere. - msg.append(ss_par_msg) - msg.append("{0: <4}".format("Stylesheet:")) - for ln in qtss.split("\n"): - msg.append("{0: <8}".format(ln)) - - # Actually add this stuff to the result we're constructing - output.extend(msg) - - output = "\n".join(output) - self.addMessage(output, direction=direction) - - # Actual console stuff. - def execInConsole(self, scriptstr, env=None): - # Since that's what imports *us*, this should be okay - # Tab completion could be set up in ConsoleInput, and would be nice - import pesterchum as pchum - - if env is None: - env = pchum._retrieveGlobals() - - # Modify the environment the script will execute in. - # Fetch from the class/instance first. - _CUSTOM_ENV = self._CUSTOM_ENV.copy() - # Modify with some hard-coded environmental additions. - _CUSTOM_ENV.update( - { - "CONSOLE": self, - "MAINWIN": self.mainwindow, - "PCONFIG": self.mainwindow.config, - "exit": lambda: self.mainwindow.exitaction.trigger(), - } - ) - # Aliases. - _CUSTOM_ENV.update({"quit": _CUSTOM_ENV["exit"]}) - # Add whatever additions were set in the main pesterchum file. - _CUSTOM_ENV.update(pchum._CONSOLE_ENV) - - _CUSTOM_ENV_USED = [] - cenv = pchum.__dict__ - # Display the input we provided - # We do this here, *before* we do our variable injection, so that it - # doesn't have to be part of the try statement, where it could - # potentially complicate matters/give false positives. - self.addMessage(scriptstr, 1) - for k in _CUSTOM_ENV: - if k not in cenv: - # Inject the variable for ease of use. - cenv[k] = _CUSTOM_ENV[k] - # Record that we injected it. - _CUSTOM_ENV_USED.append(k) - else: - # Don't overwrite anything! - warn = "Console environment item {0!r} already exists in CENV." - warn.format(k) - PchumLog.warning(warn) - # Because all we did was change a linked AttrDict, we should be fine - # here. - try: - # Replace the old writer (for now) - sysout, sys.stdout = sys.stdout, self - try: - code = compile(scriptstr + "\n", "", "single") - # Will using cenv instead of env cause problems?... - result = eval(code, cenv) - except: - # Something went wrong. - self.addTraceback(sys.exc_info()[2]) - else: - # No errors. - if result is not None: - PchumLog.info(repr(result)) - finally: - # Restore system output. - sys.stdout = sysout - finally: - # Try to clean us out of globals - this might be disabled - # later. - for k in _CUSTOM_ENV_USED: - # Remove the key we added. - cenv.pop(k, None) - - def write(self, data): - # Replaces sys.stdout briefly - # We only ever use this for receiving, so it's safe to assume the - # direction is always -1. - if not isinstance(data, list): - data = data.split("\n") - for line in data: - if len(line): - self.addMessage(line, -1) - - -class ConsoleText(QtWidgets.QTextEdit): - stylesheet_template = """ - QScrollBar:vertical {{ {style[convo/scrollbar/style]} }} - QScrollBar::handle:vertical {{ {style[convo/scrollbar/handle]} }} - QScrollBar::add-line:vertical {{ {style[convo/scrollbar/downarrow]} }} - QScrollBar::sub-line:vertical {{ {style[convo/scrollbar/uparrow]} }} - QScrollBar:up-arrow:vertical {{ {style[convo/scrollbar/uarrowstyle]} }} - QScrollBar:down-arrow:vertical {{ {style[convo/scrollbar/darrowstyle]} }} - """ - stylesheet_path = "convo/textarea/style" - # NOTE: Qt applies stylesheets like switching CSS files. They are NOT - # applied piecemeal. - # TODO: Consider parsing the themes out into stylesheets with pieces that - # we can hand to each widget. - - def __init__(self, theme, parent=None): - super(ConsoleText, self).__init__(parent) - if hasattr(self.window(), "mainwindow"): - self.mainwindow = self.window().mainwindow - else: - self.mainwindow = self.window() - - self.hasTabs = False - self.initTheme(theme) - self.setReadOnly(True) - self.setMouseTracking(True) - self.textSelected = False - - self.copyAvailable[bool].connect(self.textReady) - self.urls = {} - - # Stripped out animation init - it's all cruft to us. - - @QtCore.pyqtSlot(bool) - def textReady(self, ready): - self.textSelected = ready - - def initTheme(self, theme): - # The basic style... - stylesheet = "QTextEdit {{ {style[convo/textarea/style]} }}" - if "convo/scrollbar" in theme: - # TODO: Make all of this into a Styler mixin, so we can just feed - # it a theme whenever we want to change. - # We'd have to define the keys we're affecting, but that shouldn't - # be too hard - it's what dicts are for. - - # Add the rest. - stylesheet += "\n" + self.stylesheet_template - stylesheet = stylesheet.format(style=theme) - self.setStyleSheet(stylesheet) - - def addMessage(self, msg, direction): - # Display a message we've received. - # Direction > 0 == out (sent by us); < 0 == in (sent by script). - if len(msg) == 0: - return - # ~color = chum.colorcmd() - # ~initials = chum.initials() - parent = self.window() - mwindow = parent.mainwindow - - # systemColor = QtGui.QColor(mwindow.theme["convo/systemMsgColor"]) - - if mwindow.config.showTimeStamps(): - if mwindow.config.time12Format(): - timestamp = time.strftime("[%I:%M") - else: - timestamp = time.strftime("[%H:%M") - if mwindow.config.showSeconds(): - timestamp += time.strftime(":%S] ") - else: - timestamp += "] " - else: - timestamp = "" - - # Figure out what prefix to use. - if direction > 1: - # Misc. Info - prefix = parent.miscinfo_prefix - elif direction > 0: - # Outgoing. - prefix = parent.outgoing_prefix - elif direction < 0: - # Incoming. - prefix = parent.incoming_prefix - elif direction == 0: - # We could just 'else' here, but there might be some oddness later. - prefix = parent.neutral_prefix - - # Later, this will have to escape things so we don't parse them, - # likely...hm. - # ~result = "{} {} {!r}" - # The input we get is already repr'd...we pass it via print, and thus - # do that there. - result = "{}{} {}\n" - result = result.format(timestamp, prefix, msg) - self.appendPlainText(result) - - # Direction doesn't matter here - it's the console. - self.lastmsg = datetime.datetime.now() - # This needs to finish being rewritten.... - - def appendPlainText(self, text): - """Add plain text to the end of the document, a la insertPlainText.""" - # Save the old cursor - oldcur = self.textCursor() - # Move the cursor to the end of the document for insertion - self.moveCursor(QtGui.QTextCursor.MoveOperation.End) - # Insert the text - self.insertPlainText(text) - # Return the cursor to wherever it was prior - self.setTextCursor(oldcur) - - def changeTheme(self, theme): - self.initTheme(theme) - sb = self.verticalScrollBar() - sb.setValue(sb.maximum()) - - def focusInEvent(self, event): - self.window().clearNewMessage() - super(ConsoleText, self).focusInEvent(event) - - def keyPressEvent(self, event): - # NOTE: This doesn't give focus to the input bar, which it probably - # should. - # karxi: Test for tab changing? - if self.window().text.input: - if event.key() not in ( - QtCore.Qt.Key.Key_PageUp, - QtCore.Qt.Key.Key_PageDown, - QtCore.Qt.Key.Key_Up, - QtCore.Qt.Key.Key_Down, - ): - self.window().text.input.keyPressEvent(event) - - super(ConsoleText, self).keyPressEvent(event) - - def mousePressEvent(self, event): - if event.button() == QtCore.Qt.MouseButton.LeftButton: - try: - # PyQt6 - url = self.anchorAt(event.position().toPoint()) - except AttributeError: - # PyQt5 - url = self.anchorAt(event.pos()) - if url != "": - # Skip memo/handle recognition - # NOTE: Ctrl+Click copies the URL. Maybe it should select it? - if event.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier: - QtWidgets.QApplication.clipboard().setText(url) - else: - # This'll probably be removed. May change the lexer out. - QtGui.QDesktopServices.openUrl( - QtCore.QUrl(url, QtCore.QUrl.ParsingMode.TolerantMode) - ) - - super(ConsoleText, self).mousePressEvent(event) - - def mouseMoveEvent(self, event): - # Change our cursor when we roll over links (anchors). - super(ConsoleText, self).mouseMoveEvent(event) - try: - # PyQt6 - pos = event.position().toPoint() - except AttributeError: - # PyQt5 - pos = event.pos() - if self.anchorAt(pos): - if ( - self.viewport().cursor().shape - != QtCore.Qt.CursorShape.PointingHandCursor - ): - self.viewport().setCursor( - QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor) - ) - else: - self.viewport().setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.IBeamCursor)) - - def contextMenuEvent(self, event): - textMenu = self.createStandardContextMenu() - textMenu.exec(event.globalPos()) - - -class ConsoleInput(QtWidgets.QLineEdit): - """The actual text entry box on a ConsoleWindow.""" - - # I honestly feel like this could just be made a private class of - # ConsoleWindow, but...best not to overcomplicate things. - stylesheet_path = "convo/input/style" - - def __init__(self, theme, parent=None): - super(ConsoleInput, self).__init__(parent) - - self.changeTheme(theme) - - def changeTheme(self, theme): - self.setStyleSheet(theme[self.stylesheet_path]) - - def focusInEvent(self, event): - # We gained focus. Notify the parent window that this happened. - self.window().clearNewMessage() - self.window().text.area.textCursor().clearSelection() - - super(ConsoleInput, self).focusInEvent(event) - - def keyPressEvent(self, event): - evtkey = event.key() - parent = self.window() - - # If a key is pressed here, we're not idle.... - # NOTE: Do we really want everyone knowing we're around if we're - # messing around in the console? Hm. - parent.mainwindow.idler.time = 0 - - if evtkey == QtCore.Qt.Key.Key_Up: - text = str(self.text()) - next = parent.text.history.next(text) - if next is not None: - self.setText(next) - elif evtkey == QtCore.Qt.Key.Key_Down: - prev = parent.text.history.prev() - if prev is not None: - self.setText(prev) - elif evtkey in (QtCore.Qt.Key.Key_PageUp, QtCore.Qt.Key.Key_PageDown): - parent.text.area.keyPressEvent(event) - else: - super(ConsoleInput, self).keyPressEvent(event) -""" diff --git a/pesterchum.py b/pesterchum.py index c1c6a05..ce215e3 100755 --- a/pesterchum.py +++ b/pesterchum.py @@ -19,7 +19,6 @@ import ostools import nickservmsgs import pytwmn -# import console from user_profile import ( userConfig, userProfile, @@ -140,11 +139,6 @@ SERVICES = [ ] BOTNAMES.extend(CUSTOMBOTS) BOTNAMES.extend(SERVICES) -# Save the main app. From here, we should be able to get everything else in -# order, for console use. -_CONSOLE = False -_CONSOLE_ENV = {} -_CONSOLE_ENV["PAPP"] = None # Command line arguments _ARGUMENTS = parser.parse_args() @@ -1290,8 +1284,6 @@ class PesterWindow(MovingWindow): | QtCore.Qt.WindowType.FramelessWindowHint ), ) - # For debugging - _CONSOLE_ENV["PAPP"] = self # TODO: karxi: SO! At the end of this function it seems like that # object is just made into None or.../something/. Somehow, it just # DIES, and I haven't the slightest idea why. I've tried multiple ways @@ -1446,46 +1438,6 @@ class PesterWindow(MovingWindow): self.menu.setNativeMenuBar(False) self.menu.setObjectName("mainmenu") - """ - if self.theme.has_key("main/menus/client/console"): - self.console = AttrDict( - dict( - window=None, - action=QAction(self.theme["main/menus/client/console"], self), - is_open=False, - ) - ) - else: - self.console = AttrDict( - dict(window=None, action=QAction("Console", self), is_open=False) - ) - self.console.shortcuts = AttrDict( - dict( - conkey=QShortcut( - QtGui.QKeySequence("Ctrl+`"), - self, - context=QtCore.Qt.ShortcutContext.ApplicationShortcut, - ), - curwgt=QShortcut( - QtGui.QKeySequence("Ctrl+Alt+w"), - self, - context=QtCore.Qt.ShortcutContext.ApplicationShortcut, - ), - ) - ) - self.console.action.triggered.connect(self.toggleConsole) - # Make sure the shortcut works anywhere. - # karxi: There's something wrong with the inheritance scheme here. - # ~self.console.shortcuts.conkey.setContext(QtCore.Qt.ShortcutContext.ApplicationShortcut) - self.console.shortcuts.conkey.activated.connect(self.toggleConsole) - # ~# Use new-style connections - # ~self.console.shortcut.activated.connect(self.toggleConsole) - # Apparently those can crash sometimes...c'est la vie. Can't use 'em. - # ~self.connect(self.console.shortcuts.curwgt, - # ~ QtCore.SIGNAL('activate()'), self.console. - self.console.is_open = False - """ - filemenu = self.menu.addMenu(self.theme["main/menus/client/_name"]) self.filemenu = filemenu filemenu.addAction(opts) @@ -1498,8 +1450,6 @@ class PesterWindow(MovingWindow): filemenu.addAction(talk) filemenu.addAction(self.idleaction) filemenu.addAction(grps) - if _CONSOLE: - filemenu.addAction(self.console.action) filemenu.addAction(self.importaction) filemenu.addAction(self.reconnectAction) filemenu.addAction(exitaction) @@ -2018,76 +1968,6 @@ class PesterWindow(MovingWindow): self.tabmemo = MemoTabWindow(self) self.tabmemo.windowClosed.connect(self.memoTabsClosed) - """ - @QtCore.pyqtSlot() - def toggleConsole(self): - if not _CONSOLE: - # We don't have the module file for this at the moment. - return - win = self.console.window - if win is None: - # We have no console window; make one. - PchumLog.info("Making a console....") - self.console.window = win = console.ConsoleWindow(parent=self) - PchumLog.info("Console made.") - # 'ConsoleWindow' object has no attribute 'windowClosed' - win.finished.connect(self.consoleWindowClosed) - self.console.shortcuts.curwgt.activated.connect(win.designateCurrentWidget) - - if self.console.is_open: - for wgt in win.findChildren(QtWidgets.QWidget): - try: - focused = wgt.hasFocus() - except AttributeError: - # The widget doesn't have a hasFocus method. - # Just to reduce ambiguity - focused = False - # Try the next one. - continue - # No error, let's see if we're actually focused. - if focused: - PchumLog.debug( - "{0!r} is in focus (parent: {1!r}); hiding".format( - wgt, wgt.parent() - ) - ) - # This widget, a child of our console, has focus. - # So...the console has focus. - # Set focus to the text input just for good measure. - win.text.input.setFocus() - # ...then hide it all. - win.hide() - self.console.is_open = False - break - else: - # The console is open, but not in focus. Bring it to the fore. - # NOTE: This is a bit of a kludgy method - it may not work - # properly on Windows. Need to look into workarounds. - # - # karxi: I *think* that Windows doesn't mind if the - # process/window that 'owns' this one changes our focus, since - # the application ultimately already *has* our focus - but I'm - # not sure. - PchumLog.debug("Console isn't in focus; fixing") - win.raise_() - win.show() - win.activateWindow() - - win.text.input.setFocus() - # No need to explicitly set it as open; it already is. - else: - PchumLog.debug("Console not visible; showing") - # Console isn't visible - show it. - win.show() - self.console.is_open = True - - @QtCore.pyqtSlot() - def consoleWindowClosed(self): - self.console.is_open = False - self.console.window = None - PchumLog.info("Console closed.") - """ - def newMemo(self, channel, timestr, secret=False, invite=False): if channel == "#pesterchum": return @@ -2222,17 +2102,6 @@ class PesterWindow(MovingWindow): self.nickServAction.setText(self.theme["main/menus/help/nickserv"]) self.helpmenu.setTitle(self.theme["main/menus/help/_name"]) - # Console - ## if self.theme.has_key("main/menus/client/console"): - ## self.console.action.setText(self.theme["main/menus/client/console"]) - ## else: - ## self.console.action.setText("Console") - # has_key doesn't work out here for some reason, possibly because of inherits? - # try: - # self.console.action.setText(self.theme["main/menus/client/console"]) - # except: - # self.console.action.setText("Console") - try: self.reportBugAction.setText(self.theme["main/menus/help/reportbug"]) except: @@ -2333,11 +2202,6 @@ class PesterWindow(MovingWindow): self.mychumhandleLabel.adjustSize() # Required so "CHUMHANDLE:" regardless of style-sheet. self.moodsLabel.adjustSize() # Required so "MOOD:" regardless of style-sheet. - if _CONSOLE: - if self.console.window: - # A bit of an ugly hack.... - self.console.window.changeTheme(theme) - # sounds self._setup_sounds() self.setVolume(self.config.volume()) @@ -4609,17 +4473,6 @@ class MainProgram(QtCore.QObject): def run(self): sys.exit(self.app.exec()) - -def _retrieveGlobals(): - # NOTE: Yes, this is a terrible kludge so that the console can work - # properly. I'm open to alternatives. - return globals() - - -# def main(): -# pesterchum = MainProgram() -# pesterchum.run() - if __name__ == "__main__": # We're being run as a script - not being imported. try: diff --git a/start.sh b/start.sh deleted file mode 100644 index 78439b7..0000000 --- a/start.sh +++ /dev/null @@ -1 +0,0 @@ -python3 pesterchum.py From ebb9c082ae2cd518bafd693934d459abe457e96c Mon Sep 17 00:00:00 2001 From: Dpeta <69427753+Dpeta@users.noreply.github.com> Date: Tue, 14 Mar 2023 15:29:10 +0100 Subject: [PATCH 5/5] Remove external config import. At least for now. Pretty sure the function leaks a file handle and it has no documentation, no one seems to know what it does. --- pesterchum.py | 49 +------------------------------------------------ 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/pesterchum.py b/pesterchum.py index ce215e3..9fb6233 100755 --- a/pesterchum.py +++ b/pesterchum.py @@ -1426,8 +1426,6 @@ class PesterWindow(MovingWindow): memoaction = QAction(self.theme["main/menus/client/memos"], self) self.memoaction = memoaction memoaction.triggered.connect(self.showMemos) - self.importaction = QAction(self.theme["main/menus/client/import"], self) - self.importaction.triggered.connect(self.importExternalConfig) self.idleaction = QAction(self.theme["main/menus/client/idle"], self) self.idleaction.setCheckable(True) self.idleaction.toggled[bool].connect(self.toggleIdle) @@ -1450,7 +1448,6 @@ class PesterWindow(MovingWindow): filemenu.addAction(talk) filemenu.addAction(self.idleaction) filemenu.addAction(grps) - filemenu.addAction(self.importaction) filemenu.addAction(self.reconnectAction) filemenu.addAction(exitaction) @@ -2086,7 +2083,6 @@ class PesterWindow(MovingWindow): self.exitaction.setText(theme["main/menus/client/exit"]) self.userlistaction.setText(theme["main/menus/client/userlist"]) self.memoaction.setText(theme["main/menus/client/memos"]) - self.importaction.setText(theme["main/menus/client/import"]) self.idleaction.setText(theme["main/menus/client/idle"]) self.reconnectAction.setText(theme["main/menus/client/reconnect"]) self.filemenu.setTitle(theme["main/menus/client/_name"]) @@ -2829,50 +2825,6 @@ class PesterWindow(MovingWindow): def isBot(handle): return handle.upper() in BOTNAMES - @QtCore.pyqtSlot() - def importExternalConfig(self): - f = QtWidgets.QFileDialog.getOpenFileName(self)[0] - if f == "": - return - fp = open(f) - regexp_state = None - for l in fp: - # import chumlist - l = l.rstrip() - chum_mo = re.match("handle: ([A-Za-z0-9]+)", l) - if chum_mo is not None: - chum = PesterProfile(chum_mo.group(1)) - self.addChum(chum) - continue - if regexp_state is not None: - replace_mo = re.match("replace: (.+)", l) - if replace_mo is not None: - replace = replace_mo.group(1) - try: - re.compile(regexp_state) - except re.error: - continue - newquirk = pesterQuirk( - {"type": "regexp", "from": regexp_state, "to": replace} - ) - qs = self.userprofile.quirks - qs.addQuirk(newquirk) - self.userprofile.setQuirks(qs) - regexp_state = None - continue - search_mo = re.match("search: (.+)", l) - if search_mo is not None: - regexp_state = search_mo.group(1) - continue - other_mo = re.match("(prefix|suffix): (.+)", l) - if other_mo is not None: - newquirk = pesterQuirk( - {"type": other_mo.group(1), "value": other_mo.group(2)} - ) - qs = self.userprofile.quirks - qs.addQuirk(newquirk) - self.userprofile.setQuirks(qs) - @QtCore.pyqtSlot() def showMemos(self, channel=""): if not hasattr(self, "memochooser"): @@ -4473,6 +4425,7 @@ class MainProgram(QtCore.QObject): def run(self): sys.exit(self.app.exec()) + if __name__ == "__main__": # We're being run as a script - not being imported. try: