From d232116e00b52432eb949cf600330742bdf8ea2a Mon Sep 17 00:00:00 2001
From: ttimo <ttimo@ttimo.net>
Date: Thu, 31 Aug 2023 14:17:50 -0500
Subject: [PATCH] Add support for custom windows terminals, pass
 --with-conemu=<path to conemu> or --with-cmder=<path to cmder>. Setting is
 persistent, set to empty string to reset.

---
 client/devkit_client/__init__.py        |  6 +++
 client/devkit_client/custom_terminal.py | 65 +++++++++++++++++++++++++
 client/devkit_client/gui2/gui2.py       | 11 +++++
 3 files changed, 82 insertions(+)
 create mode 100644 client/devkit_client/custom_terminal.py

diff --git a/client/devkit_client/__init__.py b/client/devkit_client/__init__.py
index 6a12cf0..e531d0f 100644
--- a/client/devkit_client/__init__.py
+++ b/client/devkit_client/__init__.py
@@ -61,6 +61,7 @@ import appdirs
 import paramiko
 import devkit_client.zeroconf as zeroconf
 import devkit_client.captured_popen as captured_popen
+import devkit_client.custom_terminal as custom_terminal
 
 try:
     import devkit_client.version
@@ -122,6 +123,7 @@ g_external_tools = None
 g_lock = threading.Lock()
 
 g_captured_popen_factory = captured_popen.CapturedPopenFactory()
+g_custom_terminal = custom_terminal.CustomTerminal()
 
 # This burned me twice now .. https://twitter.com/TTimo/status/1582509449838989313
 from os import getenv as os_getenv
@@ -1286,6 +1288,10 @@ def run_in_terminal(commands):
     cwd = os.getcwd()
     creationflags=0
     if platform.system() == 'Windows':
+        p = g_custom_terminal.Popen(commands)
+        if p is not None:
+            return p
+
         # After a bunch of iteration, writing a ps1 script and running it from the ssh.exe directory seems the most reliable
         # trying to pass all command line parameters with a direct powershell.exe invocation runs into a nightmare of path escaping business
         # this also enables us to provide better error handling and diagnostics
diff --git a/client/devkit_client/custom_terminal.py b/client/devkit_client/custom_terminal.py
new file mode 100644
index 0000000..9d4891e
--- /dev/null
+++ b/client/devkit_client/custom_terminal.py
@@ -0,0 +1,65 @@
+import logging
+import shutil
+import subprocess
+
+logger = logging.getLogger(__name__)
+
+# Support the use of conemu or cmder on Windows
+# glue between config settings and the low level run_in_terminal logic
+class CustomTerminal:
+    TERM_KEY = 'CustomTerminal.Term'
+    STYLE_KEY = 'CustomTerminal.Style' # 0: disabled, 1: conemu, 2: cmder
+
+    def __init__(self):
+        self.conf = None
+        self.settings = None
+
+    def setup(self, conf, settings):
+        self.conf = conf
+        self.settings = settings
+
+        matched = False
+        for (term_attr, style) in (('with_conemu', 1), ('with_cmder', 2)):
+            term = vars(self.conf).get(term_attr)
+            if term is None:
+                continue
+            matched = True
+            if term == "":
+                # Explicit disable
+                style = 0
+                break
+            if len(term) > 0:
+                # Can pass a full path, or we will lookup here
+                term = shutil.which(term)
+                if term is not None:
+                    break
+
+        if not matched:
+            # settings are unchanged, the command line options were not used
+            return
+
+        if style == 0 or term is None:
+            logger.info(f'Custom windows terminal is disabled')
+            self.settings[self.STYLE_KEY] = 0
+            return
+
+        logger.info(f'Using custom windows terminal: {term}')
+        self.settings[self.STYLE_KEY] = style
+        self.settings[self.TERM_KEY] = term
+
+    def Popen(self, commands):
+        if self.settings[self.STYLE_KEY] == 0:
+            return
+
+        if self.settings[self.STYLE_KEY] == 1:
+            commands = [self.settings[self.TERM_KEY], '-run'] + commands
+        elif self.settings[self.STYLE_KEY] == 2:
+            commands = [self.settings[self.TERM_KEY], '/x', '-run ' + ' '.join(commands)]
+        else:
+            raise Exception(f'Unexpected STYLE_KEY {self.settings[self.STYLE_KEY]} value in CustomTerminal')
+
+        logging.info(f'Run in terminal - custom terminal: {commands!r}')
+        p = subprocess.Popen(
+            commands,
+        )
+        return p
diff --git a/client/devkit_client/gui2/gui2.py b/client/devkit_client/gui2/gui2.py
index 5bb89f9..b92ccb6 100644
--- a/client/devkit_client/gui2/gui2.py
+++ b/client/devkit_client/gui2/gui2.py
@@ -3315,6 +3315,15 @@ def main():
         '--disable-popen-capture', required=False, action='store_true',
         help='Disable capturing of launched external processes output to the status window.'
     )
+    if platform.system() == 'Windows':
+        parser.add_argument(
+            '--with-conemu', required=False, action='store',
+            help='Configure run in terminal to use given exe with conemu syntax. Set to blank to clear.'
+        )
+        parser.add_argument(
+            '--with-cmder', required=False, action='store',
+            help='Configure run in terminal to use given exe with cmder syntax. Set to blank to clear.'
+        )
 
     conf = parser.parse_args()
 
@@ -3349,6 +3358,8 @@ def main():
     shutdown_signal.connect(settings.on_shutdown_signal)
     atexit.register(settings.shutdown) # saves settings on abnormal termination
 
+    devkit_client.g_custom_terminal.setup(conf, settings)
+
     devkit_commands = DevkitCommands(conf, shutdown_signal)
     devkit_commands.setup()
 
-- 
GitLab