How can I create a GUI application that also provides a CLI without having a popup shell using PyInstaller?
For example, if I create the following application with pyinstaller argparse_gui.py --noconsole, stdout isn't displayed in the shell:
C:\projects\argparse_gui\dist\argparse_gui>argparse_gui.exe -V
C:\projects\argparse_gui\dist\argparse_gui>
I can redirect stdout/stderr to a file with argparse_gui.exe -V > log.txt 2>&1, but that's not exactly user-friendly. I can see stdout if built without --noconsole, but then there's a nagging separate shell window.
# argparse_gui.py
import sys
import argparse
from PyQt5 import QtCore, QtWidgets, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.init_widgets()
self.init_layout()
def init_widgets(self):
self.label = QtWidgets.QLabel('Hello, world!')
def init_layout(self):
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.label)
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("-V", "--version", help="display application information", action='store_true')
args = parser.parse_args()
if args.version:
print('Version 123', flush=True)
else:
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
EDIT: I found a roundabout way to do it, but the console window appears until the GUI loads up. I placed the following code at the top of my main script and ran Pyinstaller without --windowed. This hides the console window if the script isn't run from an existing console.
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
process_array = (ctypes.c_uint8 * 1)()
num_processes = kernel32.GetConsoleProcessList(process_array, 1)
if num_processes < 3: ctypes.WinDLL('user32').ShowWindow(kernel32.GetConsoleWindow(), 0)
Otherwise, I'm going to conclude that there isn't a way using PyInstaller. The EXE is bundled with pythonw if --noconsole/--windowed. PythonW doesn't have a console attached, even if launched from the console.
Command line arguments are still passed, however. You can still use sys.argv or argparser to access them.
When running in --noconsole, sys.stdout is a NullWriter object and sys.__stdout__ is None. Using open() on 1 raises an exception, CON and CONOUT$ fail to do anything. Redirecting the console to >&1 throws an error.
Note: Under some conditions stdin, stdout and stderr as well as the original values stdin, stdout and stderr can be None. It > is usually the case for Windows GUI apps that aren’t connected to a console and Python apps started with pythonw. https://docs.python.org/3/library/sys.html#sys.__stderr__
Pyinstaller explicitly uses PythonW.exe if the EXE is built with --noconsole. I wasn't able to find a way around this, such as loading the console bootloader if called from the cli but pythonw otherwise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With