Repository: pyinstaller/pyinstaller

Issue:
Can we minimise the console window to taskbar?
I want the console window running in the background, but it stays behind after opening the main window of my app.
I want that console window to be **minimized in the taskbar** after opening the main app. Is it possible?
Or is there any other method to get the access to that console window?


Discussion:
I don't know the gory details of Windows's APIs but I very much doubt that this is possible. Whatever it is that you want to see in that console, I suggest you redirect it somewhere else such as a log file or a hidden diagnostics window.
You can programmatically minimize the console in your code. Example:

```python
import sys
import signal
import ctypes

from PySide2 import QtCore, QtWidgets

if sys.platform == 'win32':
    import ctypes
    import ctypes.wintypes
    
    # Get console window
    console_window = ctypes.windll.kernel32.GetConsoleWindow()
    if console_window:
        # Check if console is owned by this process...
        process_id = ctypes.windll.kernel32.GetCurrentProcessId()
        console_process_id = ctypes.wintypes.DWORD()
        ctypes.windll.user32.GetWindowThreadProcessId(console_window, ctypes.byref(console_process_id))
        console_process_id = console_process_id.value
        if process_id == console_process_id:
            # ... and if it is, minimize it
            ctypes.windll.user32.ShowWindow(console_window, 2)  # SW_SHOWMINIMIZED

# The example GUI application
app = QtWidgets.QApplication(sys.argv)
signal.signal(signal.SIGINT, signal.SIG_DFL)

window = QtWidgets.QWidget()
window.setWindowTitle("Hello world!")
window.show()

app.exec_()
```

The above example explicitly verifies that the process owns console (i.e., was not launched from already-opened console, in which case you might not want to minimize it). The downside is that it works only for `onedir` builds; in `onefile` build, the console would be owned by the parent process, and so the check would have to be done differently.

Another thing to note is that the console will be minimized only after the program reaches your code (so it won't *start* minimized). If you wanted to further reduce the amount of time the console is open, you would have to perform minimization in the bootloader, probably at the very beginning of its execution. But even with that, it would not be instantaneous.

@rokm 

> The downside is that it works only for onedir builds; in onefile build, the console would be owned by the parent process, and so the check would have to be done differently.

Unfortunately, I am making an one-file executable. How can I do the check there?

> Another thing to note is that the console will be minimized only after the program reaches your code (so it won't start minimized). 

Yes, that is the what I exactly need. Just like other programs, eg: blender.
@rokm btw can this feature be added to pyinstaller?
It would be great if we have option like `minimise_console`.
> @rokm btw can this feature be added to pyinstaller? It would be great if we have option like `minimise_console`.

I suppose we could try adding a bootloader flag that would, depending on the value, call `ShowWindow` with either SW_SHOWMINIMIZED or SW_HIDE as soon as we can read options from the PKG. This would allow console to be hidden/minimized when console is owned by either onedir or onefile program.

I'll try to come up with a prototype and see how it behaves.
> > @rokm btw can this feature be added to pyinstaller? It would be great if we have option like `minimise_console`.
> 
> I suppose we could try adding a bootloader flag that would, depending on the value, call `ShowWindow` with either SW_SHOWMINIMIZED or SW_HIDE as soon as we can read options from the PKG. This would allow console to be hidden/minimized when console is owned by either onedir or onefile program.
> 
> I'll try to come up with a prototype and see how it behaves.

Although if you want to minimize console when your UI comes up, you should be doing this in your code; as minimizing in bootloader will happen before any python code is ran...

Resolving patch:
diff --git a/PyInstaller/building/api.py b/PyInstaller/building/api.py
--- a/PyInstaller/building/api.py
+++ b/PyInstaller/building/api.py
@@ -347,6 +347,14 @@ def __init__(self, *args, **kwargs):
             console
                 On Windows or Mac OS governs whether to use the console executable or the windowed executable. Always
                 True on Linux/Unix (always console executable - it does not matter there).
+            hide_console
+                Windows only. In console-enabled executable, hide or minimize the console window if the program owns the
+                console window (i.e., was not launched from existing console window). Depending on the setting, the
+                console is hidden/mininized either early in the bootloader execution ('hide-early', 'minimize-early') or
+                late in the bootloader execution ('hide-late', 'minimize-late'). The early option takes place as soon as
+                the PKG archive is found. In onefile builds, the late option takes place after application has unpacked
+                itself and before it launches the child process. In onedir builds, the late option takes place before
+                starting the embedded python interpreter.
             disable_windowed_traceback
                 Disable traceback dump of unhandled exception in windowed (noconsole) mode (Windows and macOS only),
                 and instead display a message that this feature is disabled.
@@ -393,6 +401,7 @@ def __init__(self, *args, **kwargs):
         self.exclude_binaries = kwargs.get('exclude_binaries', False)
         self.bootloader_ignore_signals = kwargs.get('bootloader_ignore_signals', False)
         self.console = kwargs.get('console', True)
+        self.hide_console = kwargs.get('hide_console', None)
         self.disable_windowed_traceback = kwargs.get('disable_windowed_traceback', False)
         self.debug = kwargs.get('debug', False)
         self.name = kwargs.get('name', None)
@@ -452,6 +461,9 @@ def __init__(self, *args, **kwargs):
         if self.icon and not (is_win or is_darwin):
             logger.warning('Ignoring icon; supported only on Windows and macOS!')
             self.icon = None
+        if self.hide_console and not is_win:
+            logger.warning('Ignoring hide_console; supported only on Windows!')
+            self.hide_console = None
 
         if self.contents_directory in ("", ".", "..") \
                 or "/" in self.contents_directory or "\\" in self.contents_directory:
@@ -524,6 +536,16 @@ def __init__(self, *args, **kwargs):
 
         self.toc.append(("pyi-contents-directory " + self.contents_directory, "", "OPTION"))
 
+        if self.hide_console:
+            # Validate the value
+            _HIDE_CONSOLE_VALUES = {'hide-early', 'minimize-early', 'hide-late', 'minimize-late'}
+            self.hide_console = self.hide_console.lower()
+            if self.hide_console not in _HIDE_CONSOLE_VALUES:
+                raise ValueError(
+                    f"Invalid hide_console value: {self.hide_console}! Allowed values: {_HIDE_CONSOLE_VALUES}"
+                )
+            self.toc.append((f"pyi-hide-console {self.hide_console}", "", "OPTION"))
+
         # If the icon path is relative, make it relative to the .spec file.
         if self.icon and self.icon != "NONE":
             if isinstance(self.icon, list):
diff --git a/PyInstaller/building/makespec.py b/PyInstaller/building/makespec.py
--- a/PyInstaller/building/makespec.py
+++ b/PyInstaller/building/makespec.py
@@ -492,6 +492,14 @@ def __add_options(parser):
         "building a Mac OS .app bundle. On Windows this option is automatically set if the first script is a '.pyw' "
         "file. This option is ignored on *NIX systems.",
     )
+    g.add_argument(
+        "--hide-console",
+        type=str,
+        choices={'hide-early', 'hide-late', 'minimize-early', 'minimize-late'},
+        default=None,
+        help="Windows only: in console-enabled executable, have bootloader automatically hide or minimize the console "
+        "window if the program owns the console window (i.e., was not launched from an existing console window).",
+    )
     g.add_argument(
         "-i",
         "--icon",
@@ -673,6 +681,7 @@ def main(
     codesign_identity=None,
     entitlements_file=None,
     argv_emulation=False,
+    hide_console=None,
     **_kwargs
 ):
     # Default values for onefile and console when not explicitly specified on command-line (indicated by None)
@@ -723,6 +732,8 @@ def main(
         icon_file = 'None'
     if contents_directory:
         exe_options += "\n    contents_directory='%s'," % (contents_directory or "_internal")
+    if hide_console:
+        exe_options += "\n    hide_console='%s'," % hide_console
 
     if bundle_identifier:
         # We need to encapsulate it into apostrofes.


Test patch: