Improvements

This commit is contained in:
Ircama 2024-08-13 18:01:12 +02:00
parent b9048b5bcd
commit 58dbd5cfd8
6 changed files with 136 additions and 72 deletions

View file

@ -23,6 +23,7 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
#pip install git+https://github.com/pyinstaller/pyinstaller@develop #pip install git+https://github.com/pyinstaller/pyinstaller@develop
pip install pyinstaller pip install pyinstaller
pip install Pillow
pip install -r requirements.txt pip install -r requirements.txt
- name: Run PyInstaller to create epson_print_conf.exe - name: Run PyInstaller to create epson_print_conf.exe
@ -30,9 +31,9 @@ jobs:
python -m PyInstaller epson_print_conf.spec -- --default python -m PyInstaller epson_print_conf.spec -- --default
- name: Zip the epson_print_conf.exe asset to epson_print_conf.zip - name: Zip the epson_print_conf.exe asset to epson_print_conf.zip
run: > run: |
powershell -Command Compress-Archive dist/epson_print_conf.exe Compress-Archive dist/epson_print_conf.exe dist/epson_print_conf.zip
dist/epson_print_conf.zip shell: pwsh
- name: Generate Changelog - name: Generate Changelog
run: > run: >

View file

@ -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`. 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: 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
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:
```bash ```bash
pip install pyinstaller # if not yet installed pip install pyinstaller # if not yet installed
curl -o devices.xml https://codeberg.org/attachments/147f41a3-a6ea-45f6-8c2a-25bac4495a1d 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 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. 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.
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.
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. 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 ## Utilities and notes
### parse_devices.py ### 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 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
*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*. *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*.

View file

@ -719,29 +719,13 @@ class EpsonPrinter:
else: else:
destination[key] = value destination[key] = value
return destination return destination
# process "alias" definintion
if conf_dict:
self.expand_printer_conf(conf_dict)
if conf_dict and replace_conf: if conf_dict and replace_conf:
self.PRINTER_CONFIG = conf_dict self.PRINTER_CONFIG = conf_dict
for printer_name, printer_data in self.PRINTER_CONFIG.copy().items(): else:
if "alias" in printer_data: self.expand_printer_conf(self.PRINTER_CONFIG)
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
if conf_dict and not replace_conf: if conf_dict and not replace_conf:
self.PRINTER_CONFIG = merge(self.PRINTER_CONFIG, conf_dict) self.PRINTER_CONFIG = merge(self.PRINTER_CONFIG, conf_dict)
for key, values in self.PRINTER_CONFIG.items(): for key, values in self.PRINTER_CONFIG.items():
@ -752,22 +736,6 @@ class EpsonPrinter:
] ]
if not values['alias']: if not values['alias']:
del 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.model = model
self.hostname = hostname self.hostname = hostname
self.port = port self.port = port
@ -793,6 +761,48 @@ class EpsonPrinter:
"""Return list of available information methods about a printer.""" """Return list of available information methods about a printer."""
return(filter(lambda x: x.startswith("get_"), dir(self))) 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): def stats(self):
"""Return all available information about a printer.""" """Return all available information about a printer."""
stat_set = {} stat_set = {}

View file

@ -1,17 +1,70 @@
# -*- mode: python ; coding: utf-8 -*- # -*- mode: python ; coding: utf-8 -*-
import argparse 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 = argparse.ArgumentParser()
parser.add_argument("--default", action="store_true") parser.add_argument("--default", action="store_true")
options = parser.parse_args() options = parser.parse_args()
PROGRAM = [ 'gui.py' ] 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: if options.default:
PROGRAM = [ 'ui.py' ]
DATAS = [] 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( a = Analysis(
PROGRAM, PROGRAM,
@ -28,27 +81,39 @@ a = Analysis(
) )
pyz = PYZ(a.pure) 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( exe = EXE(
pyz, pyz,
a.scripts, a.scripts,
a.binaries, a.binaries,
a.datas, 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). 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, bootloader_ignore_signals=False,
strip=False, strip=False,
upx=True, upx=True,
upx_exclude=[], upx_exclude=[],
runtime_tmpdir=None, runtime_tmpdir=None,
console=False, 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).
# 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).
disable_windowed_traceback=False, # Disable traceback dump of unhandled exception in windowed (noconsole) mode (Windows and macOS only) 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, argv_emulation=False,
target_arch=None, target_arch=None,
codesign_identity=None, codesign_identity=None,
entitlements_file=None, entitlements_file=None,
) )
os.remove(SPLASH_IMAGE)
os.remove(PROGRAM[0])

11
gui.py
View file

@ -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()

11
ui.py
View file

@ -942,7 +942,7 @@ class EpsonPrinterUI(tk.Tk):
self.clipboard_append(item_text) self.clipboard_append(item_text)
if __name__ == "__main__": def main():
import argparse import argparse
import pickle import pickle
@ -972,9 +972,12 @@ if __name__ == "__main__":
if args.pickle: if args.pickle:
conf_dict = pickle.load(args.pickle[0]) conf_dict = pickle.load(args.pickle[0])
app = EpsonPrinterUI(conf_dict=conf_dict, replace_conf=args.override) return EpsonPrinterUI(conf_dict=conf_dict, replace_conf=args.override)
try:
app.mainloop()
if __name__ == "__main__":
try:
main().mainloop()
except: except:
print("\nInterrupted.") print("\nInterrupted.")
sys.exit(0) sys.exit(0)