From 58dbd5cfd841db15caa1b22e51b62c5c13624aef Mon Sep 17 00:00:00 2001 From: Ircama Date: Tue, 13 Aug 2024 18:01:12 +0200 Subject: [PATCH] Improvements --- .github/workflows/build.yml | 7 ++-- README.md | 18 ++++---- epson_print_conf.py | 84 +++++++++++++++++++++---------------- epson_print_conf.spec | 77 +++++++++++++++++++++++++++++++--- gui.py | 11 ----- ui.py | 11 +++-- 6 files changed, 136 insertions(+), 72 deletions(-) delete mode 100644 gui.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 865cbb7..83c14fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,7 @@ jobs: python -m pip install --upgrade pip #pip install git+https://github.com/pyinstaller/pyinstaller@develop pip install pyinstaller + pip install Pillow pip install -r requirements.txt - name: Run PyInstaller to create epson_print_conf.exe @@ -30,9 +31,9 @@ jobs: python -m PyInstaller epson_print_conf.spec -- --default - name: Zip the epson_print_conf.exe asset to epson_print_conf.zip - run: > - powershell -Command Compress-Archive dist/epson_print_conf.exe - dist/epson_print_conf.zip + run: | + Compress-Archive dist/epson_print_conf.exe dist/epson_print_conf.zip + shell: pwsh - name: Generate Changelog run: > diff --git a/README.md b/README.md index 06e87d3..2ded39c 100644 --- a/README.md +++ b/README.md @@ -212,27 +212,21 @@ pyinstaller epson_print_conf.spec -- --default Then run the executable file created in the *dist/* folder, which has the same options of `ui.py`. -An alternative way to create the executable file from *ui.py* without using *epson_print_conf.spec* is the following: - -```bash -pyinstaller --onefile ui.py --name epson_print_conf --hidden-import babel.numbers --windowed -``` - -A file named *gui.py* is also included (similar to *ui.py*), which automatically loads a previously created configuration file that has to be named *printer_conf.pickle*, merging it with the program configuration. (See below the *parse_devices.py* utility.) To build the executable program with this file instead of the default *ui.py*, run the following command: +It is also possible to automatically load a previously created configuration file that has to be named *epson_print_conf.pickle*, merging it with the program configuration. (See below the *parse_devices.py* utility.) To build the executable program with this file, run the following command: ```bash pip install pyinstaller # if not yet installed curl -o devices.xml https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25bac4495a1d -python3 parse_devices.py -a 192.168.178.29 -s XP-205 -p printer_conf.pickle # use your default IP address and printer model as default settings for the GUI +python3 parse_devices.py -a 192.168.178.29 -s XP-205 -p epson_print_conf.pickle # use your default IP address and printer model as default settings for the GUI pyinstaller epson_print_conf.spec ``` -When the build operation is completed, you can run the executable program created in the *dist/* folder. It does not have options, embeds the *printer_conf.pickle* file and starts with the default IP address and printer model defined in the build phase. - -This repository includes a Windows *epson_print_conf.exe* executable file which is automatically generated by a [GitHub Action](.github/workflows/build.yml). It is packaged in a ZIP file named *epson_print_conf.zip* and uploaded into the [Releases](https://github.com/Ircama/epson_print_conf/releases/latest) folder. +When embedding *epson_print_conf.pickle*, the created program does not have options and starts with the default IP address and printer model defined in the build phase. As mentioned in the [documentation](https://pyinstaller.org/en/stable/), PyInstaller supports Windows, MacOS X, Linux and other UNIX Operating Systems. It creates an executable file which is only compatible with the operating system that is used to build the asset. +This repository includes a Windows *epson_print_conf.exe* executable file which is automatically generated by a [GitHub Action](.github/workflows/build.yml). It is packaged in a ZIP file named *epson_print_conf.zip* and uploaded into the [Releases](https://github.com/Ircama/epson_print_conf/releases/latest) folder. + ## Utilities and notes ### parse_devices.py @@ -291,6 +285,8 @@ Generate printer configuration from devices.xml The output is better formatted when also installing [black](https://pypi.org/project/black/). +The program does not provide *printer_head_id* and *Power off timer*. + ### find_printers.py *find_printers.py* can be executed via `python find_printers.py` and prints the list of the discovered printers to the standard output. It is internally used as a library by *ui.py*. diff --git a/epson_print_conf.py b/epson_print_conf.py index e9cdc82..0d2c1bd 100644 --- a/epson_print_conf.py +++ b/epson_print_conf.py @@ -719,29 +719,13 @@ class EpsonPrinter: else: destination[key] = value return destination - # process "alias" definintion + + if conf_dict: + self.expand_printer_conf(conf_dict) if conf_dict and replace_conf: self.PRINTER_CONFIG = conf_dict - for printer_name, printer_data in self.PRINTER_CONFIG.copy().items(): - if "alias" in printer_data: - aliases = printer_data["alias"] - del printer_data["alias"] - if not isinstance(aliases, list): - logging.error( - "Alias '%s' of printer '%s' in configuration " - "must be a list.", - aliases, printer_name - ) - continue - for alias_name in aliases: - if alias_name in self.PRINTER_CONFIG: - logging.error( - "Alias '%s' of printer '%s' is already defined " - "in configuration.", - alias_name, printer_name - ) - else: - self.PRINTER_CONFIG[alias_name] = printer_data + else: + self.expand_printer_conf(self.PRINTER_CONFIG) if conf_dict and not replace_conf: self.PRINTER_CONFIG = merge(self.PRINTER_CONFIG, conf_dict) for key, values in self.PRINTER_CONFIG.items(): @@ -752,22 +736,6 @@ class EpsonPrinter: ] if not values['alias']: del values['alias'] - # process "same-as" definintion - for printer_name, printer_data in self.PRINTER_CONFIG.copy().items(): - if "same-as" in printer_data: - sameas = printer_data["same-as"] - #del printer_data["same-as"] - if sameas in self.PRINTER_CONFIG: - self.PRINTER_CONFIG[printer_name] = { - **self.PRINTER_CONFIG[sameas], - **printer_data - } - else: - logging.error( - "Undefined 'same-as' printer '%s' " - "in '%s' configuration.", - sameas, printer_name - ) self.model = model self.hostname = hostname self.port = port @@ -793,6 +761,48 @@ class EpsonPrinter: """Return list of available information methods about a printer.""" return(filter(lambda x: x.startswith("get_"), dir(self))) + def expand_printer_conf(self, conf): + """ + Expand "alias" and "same-as" of a printer database for all printers + """ + # process "alias" definintion + for printer_name, printer_data in conf.copy().items(): + if "alias" in printer_data: + aliases = printer_data["alias"] + del printer_data["alias"] + if not isinstance(aliases, list): + logging.error( + "Alias '%s' of printer '%s' in configuration " + "must be a list.", + aliases, printer_name + ) + continue + for alias_name in aliases: + if alias_name in conf: + logging.error( + "Alias '%s' of printer '%s' is already defined " + "in configuration.", + alias_name, printer_name + ) + else: + conf[alias_name] = printer_data + # process "same-as" definintion + for printer_name, printer_data in conf.copy().items(): + if "same-as" in printer_data: + sameas = printer_data["same-as"] + #del printer_data["same-as"] + if sameas in conf: + conf[printer_name] = { + **conf[sameas], + **printer_data + } + else: + logging.error( + "Undefined 'same-as' printer '%s' " + "in '%s' configuration.", + sameas, printer_name + ) + def stats(self): """Return all available information about a printer.""" stat_set = {} diff --git a/epson_print_conf.spec b/epson_print_conf.spec index 404a67c..390a478 100644 --- a/epson_print_conf.spec +++ b/epson_print_conf.spec @@ -1,17 +1,70 @@ # -*- mode: python ; coding: utf-8 -*- import argparse +import os +import os.path +from PIL import Image, ImageDraw, ImageFont + + +def create_image(png_file, text): + img = Image.new('RGB', (800, 150), color='black') + fnt = ImageFont.truetype('arialbd.ttf', 30) + d = ImageDraw.Draw(img) + shadow_offset = 2 + bbox = d.textbbox((0, 0), text, font=fnt) + x, y = (800-bbox[2])/2, (150-bbox[3])/2 + d.text((x+shadow_offset, y+shadow_offset), text, font=fnt, fill='gray') + d.text((x, y), text, font=fnt, fill='#baf8f8') + img.save(png_file, 'PNG') + parser = argparse.ArgumentParser() parser.add_argument("--default", action="store_true") options = parser.parse_args() PROGRAM = [ 'gui.py' ] -DATAS = [('printer_conf.pickle', '.')] +BASENAME = 'epson_print_conf' + +DATAS = [(BASENAME + '.pickle', '.')] +SPLASH_IMAGE = BASENAME + '.png' + +create_image( + SPLASH_IMAGE, 'Epson Printer Configuration tool loading...' +) + +if not options.default and not os.path.isfile(DATAS[0][0]): + print("\nMissing file", DATAS[0][0], "without using the default option.") + quit() + +gui_wrapper = """import pyi_splash +import pickle +from ui import EpsonPrinterUI +from os import path + +path_to_pickle = path.abspath( + path.join(path.dirname(__file__), '""" + DATAS[0][0] + """') +) +with open(path_to_pickle, 'rb') as fp: + conf_dict = pickle.load(fp) +app = EpsonPrinterUI(conf_dict=conf_dict, replace_conf=False) +pyi_splash.close() +app.mainloop() +""" if options.default: - PROGRAM = [ 'ui.py' ] DATAS = [] + gui_wrapper = """import pyi_splash +import pickle +from ui import main +from os import path + +app = main() +pyi_splash.close() +app.mainloop() +""" + +with open(PROGRAM[0], 'w') as file: + file.write(gui_wrapper) a = Analysis( PROGRAM, @@ -28,27 +81,39 @@ a = Analysis( ) pyz = PYZ(a.pure) +splash = Splash( + SPLASH_IMAGE, + binaries=a.binaries, + datas=a.datas, + text_pos=None, + text_size=12, + minify_script=True, + always_on_top=True, +) exe = EXE( pyz, a.scripts, a.binaries, a.datas, + splash, + splash.binaries, [], - name='epson_print_conf', + name=BASENAME, debug=False, # Setting to True gives you progress messages from the executable (for console=False there will be annoying MessageBoxes on Windows). bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, - console=False, - # console=False, # 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). + console=options.default, # 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). disable_windowed_traceback=False, # Disable traceback dump of unhandled exception in windowed (noconsole) mode (Windows and macOS only) - # hide_console='hide-early', # Windows only. In console-enabled executable, hide or minimize the console window ('hide-early', 'minimize-early', 'hide-late', 'minimize-late') + hide_console='hide-early', # Windows only. In console-enabled executable, hide or minimize the console window ('hide-early', 'minimize-early', 'hide-late', 'minimize-late') argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) +os.remove(SPLASH_IMAGE) +os.remove(PROGRAM[0]) diff --git a/gui.py b/gui.py deleted file mode 100644 index 1e78e04..0000000 --- a/gui.py +++ /dev/null @@ -1,11 +0,0 @@ -import pickle -from ui import EpsonPrinterUI -from os import path - -PICKLE_CONF_FILE = "printer_conf.pickle" - -path_to_pickle = path.abspath(path.join(path.dirname(__file__), PICKLE_CONF_FILE)) -with open(path_to_pickle, 'rb') as fp: - conf_dict = pickle.load(fp) -app = EpsonPrinterUI(conf_dict=conf_dict, replace_conf=False) -app.mainloop() diff --git a/ui.py b/ui.py index d23cb47..fe06360 100644 --- a/ui.py +++ b/ui.py @@ -942,7 +942,7 @@ class EpsonPrinterUI(tk.Tk): self.clipboard_append(item_text) -if __name__ == "__main__": +def main(): import argparse import pickle @@ -972,9 +972,12 @@ if __name__ == "__main__": if args.pickle: conf_dict = pickle.load(args.pickle[0]) - app = EpsonPrinterUI(conf_dict=conf_dict, replace_conf=args.override) - try: - app.mainloop() + return EpsonPrinterUI(conf_dict=conf_dict, replace_conf=args.override) + + +if __name__ == "__main__": + try: + main().mainloop() except: print("\nInterrupted.") sys.exit(0)