Skip to content

Commit

Permalink
[gui] Support installation desktop shortcut, FTA and PATH
Browse files Browse the repository at this point in the history
  • Loading branch information
kuba2k2 committed Dec 15, 2023
1 parent 1e7b7ef commit e4dc88d
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 3 deletions.
128 changes: 126 additions & 2 deletions ltchiptool/util/ltim.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) Kuba Szczodrzyński 2023-12-13.

import platform
import shutil
import sys
from functools import lru_cache
from io import BytesIO
Expand All @@ -25,6 +26,10 @@
)
PYTHON_GET_PIP = "https://bootstrap.pypa.io/get-pip.py"

PYTHON_WIN = "python.exe"
PYTHONW_WIN = "pythonw.exe"
ICON_FILE = "ltchiptool.ico"


# utilities to manage ltchiptool installation in different modes,
# fetch version information, find bundled resources, etc.
Expand Down Expand Up @@ -128,6 +133,13 @@ def install(
if return_code != 0:
raise RuntimeError(f"pip install returned {return_code}")

if shortcut:
self._install_shortcut_windows(out_path, public=shortcut == "public")
if fta:
self._install_fta_windows(out_path, *fta)
if add_path:
self._install_path_windows(out_path)

self.callback.finish()

def _install_python_windows(self, out_path: Path) -> Tuple[Path, Path]:
Expand Down Expand Up @@ -187,8 +199,8 @@ def _install_python_windows(self, out_path: Path) -> Tuple[Path, Path]:
self.callback.on_total(None)

self.callback.on_message("Checking installed executable")
python_path = out_path / "python.exe"
pythonw_path = out_path / "pythonw.exe"
python_path = out_path / PYTHON_WIN
pythonw_path = out_path / PYTHONW_WIN
p = Popen(
args=[python_path, "--version"],
stdout=PIPE,
Expand All @@ -206,8 +218,120 @@ def _install_python_windows(self, out_path: Path) -> Tuple[Path, Path]:
pth = pth.replace("#import site", "import site")
pth_path.write_text(pth)

self.callback.on_message("Installing icon resource")
icon_path = out_path / ICON_FILE
icon_res = self.get_gui_resource(ICON_FILE)
shutil.copy(icon_res, icon_path)

return python_path, pythonw_path

def _install_shortcut_windows(self, out_path: Path, public: bool) -> None:
import pylnk3
from win32comext.shell.shell import SHGetFolderPath
from win32comext.shell.shellcon import (
CSIDL_COMMON_DESKTOPDIRECTORY,
CSIDL_DESKTOP,
)

if public:
desktop_dir = SHGetFolderPath(0, CSIDL_COMMON_DESKTOPDIRECTORY, 0, 0)
else:
desktop_dir = SHGetFolderPath(0, CSIDL_DESKTOP, 0, 0)

gui_path = Path(desktop_dir) / "ltchiptool GUI.lnk"
cli_path = Path(desktop_dir) / "ltchiptool CLI.lnk"

self.callback.on_message("Creating desktop shortcuts")
pylnk3.for_file(
target_file=str(out_path / PYTHONW_WIN),
lnk_name=str(gui_path),
arguments="-m ltchiptool gui",
description="Launch ltchiptool GUI",
icon_file=str(out_path / ICON_FILE),
)
pylnk3.for_file(
target_file=expandvars("%COMSPEC%"),
lnk_name=str(cli_path),
arguments="/K ltchiptool",
description="Launch ltchiptool CLI",
icon_file=str(out_path / ICON_FILE),
work_dir=str(out_path / "Scripts"),
)

def _install_fta_windows(self, out_path: Path, *fta: str) -> None:
from winreg import HKEY_LOCAL_MACHINE, REG_SZ, CreateKeyEx, OpenKey, SetValue

from win32comext.shell.shell import SHChangeNotify
from win32comext.shell.shellcon import SHCNE_ASSOCCHANGED, SHCNF_IDLIST

for ext in fta:
ext = ext.lower().strip(".")
self.callback.on_message(f"Associating {ext.upper()} file type")
with OpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Classes") as classes:
with CreateKeyEx(classes, f".{ext}") as ext_key:
SetValue(ext_key, "", REG_SZ, f"ltchiptool.{ext.upper()}")
with CreateKeyEx(classes, f"ltchiptool.{ext.upper()}") as cls_key:
SetValue(cls_key, "", REG_SZ, f"ltchiptool {ext.upper()} file")
with CreateKeyEx(cls_key, "DefaultIcon") as icon_key:
SetValue(icon_key, "", REG_SZ, str(out_path / ICON_FILE))
with CreateKeyEx(cls_key, "shell") as shell_key:
SetValue(shell_key, "", REG_SZ, f"open")
with CreateKeyEx(shell_key, "open") as open_key:
with CreateKeyEx(open_key, "command") as command_key:
command = [
str(out_path / PYTHONW_WIN),
"-m",
"ltchiptool",
"gui",
'"%1"',
]
SetValue(command_key, "", REG_SZ, " ".join(command))
self.callback.on_message("Notifying other programs")
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None)

def _install_path_windows(self, out_path: Path) -> None:
from winreg import (
HKEY_LOCAL_MACHINE,
KEY_ALL_ACCESS,
REG_SZ,
OpenKey,
QueryValueEx,
SetValueEx,
)

from win32con import HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE
from win32gui import SendMessageTimeout

script_path = out_path / "Scripts" / "ltchiptool.exe"
bin_path = out_path / "bin" / "ltchiptool.exe"
bin_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(script_path, bin_path)

self.callback.on_message("Updating PATH variable")
with OpenKey(
HKEY_LOCAL_MACHINE,
"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",
0,
KEY_ALL_ACCESS,
) as key:
new_dir = str(bin_path.parent)
path, _ = QueryValueEx(key, "PATH")
path = path.split(";")
while new_dir in path:
path.remove(new_dir)
path.insert(0, new_dir)
SetValueEx(key, "PATH", None, REG_SZ, ";".join(path))

self.callback.on_message("Notifying other programs")
SendMessageTimeout(
HWND_BROADCAST,
WM_SETTINGCHANGE,
0,
u"Environment",
SMTO_ABORTIFHUNG,
5000,
)


if __name__ == "__main__":
LoggingHandler.get().level = DEBUG
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ hexdump = "^3.3"
bitstruct = "^8.1.1"
zeroconf = "^0"
requests = "^2.31.0"
pyuac = {version = "^0.0.3", optional = true, markers = "sys_platform == 'win32'"}
pylnk3 = {version = "^0.4.2", optional = true, markers = "sys_platform == 'win32'"}

[tool.poetry.dependencies.pycryptodome]
version = "^3.9.9"
Expand All @@ -41,7 +43,7 @@ version = "^1.6.1"
markers = "platform_machine in 'armv6l,armv7l,armv8l,armv8b,aarch64'"

[tool.poetry.extras]
gui = ["wxPython", "pywin32"]
gui = ["wxPython", "pywin32", "pyuac", "pylnk3"]

[tool.poetry.dev-dependencies]
black = "^22.6.0"
Expand Down

0 comments on commit e4dc88d

Please sign in to comment.