moleditpy-installer 3.0.2__tar.gz → 3.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/PKG-INFO +9 -9
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/README.md +8 -8
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/main.py +229 -45
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/tui.py +6 -6
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/PKG-INFO +9 -9
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/pyproject.toml +1 -1
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/LICENSE +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/__init__.py +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/__main__.py +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/file_icon.icns +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/file_icon.ico +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/file_icon.png +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/icon.icns +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/icon.ico +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/icon.png +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/SOURCES.txt +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/dependency_links.txt +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/entry_points.txt +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/requires.txt +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/top_level.txt +0 -0
- {moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moleditpy-installer
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.3
|
|
4
4
|
Summary: Installer and application bundler for MoleditPy
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -732,7 +732,7 @@ This package is a helper utility that automatically installs the correct version
|
|
|
732
732
|
moleditpy-installer --no-tui # install with the defaults
|
|
733
733
|
moleditpy-installer --desktop # also create a Desktop shortcut
|
|
734
734
|
moleditpy-installer --no-file-assoc # skip the .pmeprj association
|
|
735
|
-
sudo moleditpy-installer --system # system-wide (
|
|
735
|
+
sudo moleditpy-installer --system # system-wide (admin terminal on Windows)
|
|
736
736
|
```
|
|
737
737
|
|
|
738
738
|
Any explicit option skips the TUI.
|
|
@@ -753,13 +753,13 @@ This package is a helper utility that automatically installs the correct version
|
|
|
753
753
|
|
|
754
754
|
> **Tip:** Run the installer from the Python environment where `moleditpy` is installed (e.g. after `conda activate <env>`). The installer verifies the launch command and warns you if the pairing cannot start MoleditPy.
|
|
755
755
|
|
|
756
|
-
3. **
|
|
757
|
-
To remove the shortcuts, unregister file associations, and clean up extracted icon files, run (add `--system` for a system-wide install):
|
|
756
|
+
3. **Uninstall**
|
|
757
|
+
To remove the shortcuts, unregister file associations, and clean up extracted icon files, run (add `--system` for a system-wide install; `--remove` remains as a deprecated alias):
|
|
758
758
|
|
|
759
759
|
```bash
|
|
760
|
-
moleditpy-installer --
|
|
760
|
+
moleditpy-installer --uninstall
|
|
761
761
|
# or
|
|
762
|
-
python -m moleditpy_installer --
|
|
762
|
+
python -m moleditpy_installer --uninstall
|
|
763
763
|
```
|
|
764
764
|
|
|
765
765
|
4. **Check Executable Location**
|
|
@@ -789,7 +789,7 @@ This package is a helper utility that automatically installs the correct version
|
|
|
789
789
|
## What the installer sets up
|
|
790
790
|
|
|
791
791
|
### Windows
|
|
792
|
-
- Start Menu shortcut (icon included); Desktop shortcut when enabled (`--desktop` or the TUI checkbox).
|
|
792
|
+
- Start Menu shortcut (icon included); Desktop shortcut when enabled (`--desktop` or the TUI checkbox). With `--system` (admin) the shortcuts go to the all-users locations and the association to HKLM.
|
|
793
793
|
- `.pmeprj` files open MoleditPy on double-click (per-user registry, no admin rights needed).
|
|
794
794
|
- Icons are stored under `%LOCALAPPDATA%\MoleditPy\installer` so they survive temp-folder cleanup.
|
|
795
795
|
|
|
@@ -805,13 +805,13 @@ This package is a helper utility that automatically installs the correct version
|
|
|
805
805
|
- `.pmeprj` files open MoleditPy on double-click: a per-user `application/x-moleditpy-project` MIME type is registered (freedesktop shared-mime-info, no root needed) and set as the default handler.
|
|
806
806
|
- `.pmeprj` documents get their own file icon in file managers, matching Windows and macOS.
|
|
807
807
|
|
|
808
|
-
`moleditpy-installer --
|
|
808
|
+
`moleditpy-installer --uninstall` undoes all of the above for the current user (add `--system` for system-wide installs).
|
|
809
809
|
|
|
810
810
|
## Version 3.0 highlights
|
|
811
811
|
|
|
812
812
|
- **New:** interactive Textual TUI — pick components and scope, watch the log, press Install/Remove.
|
|
813
813
|
- **New:** component selection (`--desktop`, `--app-menu`, `--file-assoc`) with safer defaults (no Desktop shortcut unless requested).
|
|
814
|
-
- **New:** system-wide scope on Linux (`/usr/share
|
|
814
|
+
- **New:** system-wide scope on Linux (`/usr/share`), macOS (`/Applications`), and Windows (HKLM + all-users Start Menu; admin terminal) — all requiring sudo/admin.
|
|
815
815
|
- Per-user Linux paths now honor `XDG_DATA_HOME`.
|
|
816
816
|
- End-to-end smoke tests on all three OSes in CI, including the sudo/system path.
|
|
817
817
|
|
|
@@ -29,7 +29,7 @@ This package is a helper utility that automatically installs the correct version
|
|
|
29
29
|
moleditpy-installer --no-tui # install with the defaults
|
|
30
30
|
moleditpy-installer --desktop # also create a Desktop shortcut
|
|
31
31
|
moleditpy-installer --no-file-assoc # skip the .pmeprj association
|
|
32
|
-
sudo moleditpy-installer --system # system-wide (
|
|
32
|
+
sudo moleditpy-installer --system # system-wide (admin terminal on Windows)
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
Any explicit option skips the TUI.
|
|
@@ -50,13 +50,13 @@ This package is a helper utility that automatically installs the correct version
|
|
|
50
50
|
|
|
51
51
|
> **Tip:** Run the installer from the Python environment where `moleditpy` is installed (e.g. after `conda activate <env>`). The installer verifies the launch command and warns you if the pairing cannot start MoleditPy.
|
|
52
52
|
|
|
53
|
-
3. **
|
|
54
|
-
To remove the shortcuts, unregister file associations, and clean up extracted icon files, run (add `--system` for a system-wide install):
|
|
53
|
+
3. **Uninstall**
|
|
54
|
+
To remove the shortcuts, unregister file associations, and clean up extracted icon files, run (add `--system` for a system-wide install; `--remove` remains as a deprecated alias):
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
moleditpy-installer --
|
|
57
|
+
moleditpy-installer --uninstall
|
|
58
58
|
# or
|
|
59
|
-
python -m moleditpy_installer --
|
|
59
|
+
python -m moleditpy_installer --uninstall
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
4. **Check Executable Location**
|
|
@@ -86,7 +86,7 @@ This package is a helper utility that automatically installs the correct version
|
|
|
86
86
|
## What the installer sets up
|
|
87
87
|
|
|
88
88
|
### Windows
|
|
89
|
-
- Start Menu shortcut (icon included); Desktop shortcut when enabled (`--desktop` or the TUI checkbox).
|
|
89
|
+
- Start Menu shortcut (icon included); Desktop shortcut when enabled (`--desktop` or the TUI checkbox). With `--system` (admin) the shortcuts go to the all-users locations and the association to HKLM.
|
|
90
90
|
- `.pmeprj` files open MoleditPy on double-click (per-user registry, no admin rights needed).
|
|
91
91
|
- Icons are stored under `%LOCALAPPDATA%\MoleditPy\installer` so they survive temp-folder cleanup.
|
|
92
92
|
|
|
@@ -102,13 +102,13 @@ This package is a helper utility that automatically installs the correct version
|
|
|
102
102
|
- `.pmeprj` files open MoleditPy on double-click: a per-user `application/x-moleditpy-project` MIME type is registered (freedesktop shared-mime-info, no root needed) and set as the default handler.
|
|
103
103
|
- `.pmeprj` documents get their own file icon in file managers, matching Windows and macOS.
|
|
104
104
|
|
|
105
|
-
`moleditpy-installer --
|
|
105
|
+
`moleditpy-installer --uninstall` undoes all of the above for the current user (add `--system` for system-wide installs).
|
|
106
106
|
|
|
107
107
|
## Version 3.0 highlights
|
|
108
108
|
|
|
109
109
|
- **New:** interactive Textual TUI — pick components and scope, watch the log, press Install/Remove.
|
|
110
110
|
- **New:** component selection (`--desktop`, `--app-menu`, `--file-assoc`) with safer defaults (no Desktop shortcut unless requested).
|
|
111
|
-
- **New:** system-wide scope on Linux (`/usr/share
|
|
111
|
+
- **New:** system-wide scope on Linux (`/usr/share`), macOS (`/Applications`), and Windows (HKLM + all-users Start Menu; admin terminal) — all requiring sudo/admin.
|
|
112
112
|
- Per-user Linux paths now honor `XDG_DATA_HOME`.
|
|
113
113
|
- End-to-end smoke tests on all three OSes in CI, including the sudo/system path.
|
|
114
114
|
|
|
@@ -66,8 +66,43 @@ class InstallOptions:
|
|
|
66
66
|
system: bool = False # system-wide (sudo/admin) instead of per-user
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
@contextlib.contextmanager
|
|
70
|
+
def _com_initialized():
|
|
71
|
+
"""
|
|
72
|
+
Initialize COM for the current thread on Windows.
|
|
73
|
+
|
|
74
|
+
pyshortcuts creates .lnk files through COM, which must be initialized
|
|
75
|
+
per-thread. The TUI runs install() in a worker thread, where shortcut
|
|
76
|
+
creation otherwise fails with CO_E_NOTINITIALIZED
|
|
77
|
+
("CoInitialize has not been called").
|
|
78
|
+
"""
|
|
79
|
+
initialized = False
|
|
80
|
+
if platform.system() == "Windows":
|
|
81
|
+
try:
|
|
82
|
+
import pythoncom
|
|
83
|
+
|
|
84
|
+
pythoncom.CoInitialize()
|
|
85
|
+
initialized = True
|
|
86
|
+
except ImportError:
|
|
87
|
+
pass
|
|
88
|
+
try:
|
|
89
|
+
yield
|
|
90
|
+
finally:
|
|
91
|
+
if initialized:
|
|
92
|
+
import pythoncom
|
|
93
|
+
|
|
94
|
+
pythoncom.CoUninitialize()
|
|
95
|
+
|
|
96
|
+
|
|
69
97
|
def is_root() -> bool:
|
|
70
|
-
"""True when running with root
|
|
98
|
+
"""True when running with root (POSIX) or admin (Windows) privileges."""
|
|
99
|
+
if platform.system() == "Windows":
|
|
100
|
+
try:
|
|
101
|
+
import ctypes
|
|
102
|
+
|
|
103
|
+
return bool(ctypes.windll.shell32.IsUserAnAdmin())
|
|
104
|
+
except (OSError, AttributeError):
|
|
105
|
+
return False
|
|
71
106
|
geteuid = getattr(os, "geteuid", None)
|
|
72
107
|
return geteuid is not None and geteuid() == 0
|
|
73
108
|
|
|
@@ -80,7 +115,7 @@ def linux_data_home(system: bool = False) -> Path:
|
|
|
80
115
|
return Path(xdg) if xdg else Path.home() / ".local" / "share"
|
|
81
116
|
|
|
82
117
|
|
|
83
|
-
def get_persistent_data_dir() -> Path:
|
|
118
|
+
def get_persistent_data_dir(system: bool = False) -> Path:
|
|
84
119
|
"""
|
|
85
120
|
Per-user directory for extracted installer assets (icons).
|
|
86
121
|
|
|
@@ -89,7 +124,13 @@ def get_persistent_data_dir() -> Path:
|
|
|
89
124
|
(which is periodically cleaned, silently breaking the icons).
|
|
90
125
|
"""
|
|
91
126
|
if platform.system() == "Windows":
|
|
92
|
-
|
|
127
|
+
if system:
|
|
128
|
+
# all users must be able to read icons referenced from HKLM
|
|
129
|
+
base = os.environ.get("PROGRAMDATA") or "C:/ProgramData"
|
|
130
|
+
else:
|
|
131
|
+
base = os.environ.get("LOCALAPPDATA") or str(
|
|
132
|
+
Path.home() / "AppData" / "Local"
|
|
133
|
+
)
|
|
93
134
|
data_dir = Path(base) / "MoleditPy" / "installer"
|
|
94
135
|
else:
|
|
95
136
|
data_dir = Path.home() / ".moleditpy" / "installer"
|
|
@@ -97,12 +138,12 @@ def get_persistent_data_dir() -> Path:
|
|
|
97
138
|
return data_dir
|
|
98
139
|
|
|
99
140
|
|
|
100
|
-
def _extract_data_file(file_name: str) -> Optional[str]:
|
|
141
|
+
def _extract_data_file(file_name: str, system: bool = False) -> Optional[str]:
|
|
101
142
|
"""Extract a packaged data file to the persistent data dir and return its path."""
|
|
102
143
|
try:
|
|
103
144
|
ref = importlib.resources.files("moleditpy_installer") / "data" / file_name
|
|
104
145
|
content = ref.read_bytes()
|
|
105
|
-
out_path = get_persistent_data_dir() / file_name
|
|
146
|
+
out_path = get_persistent_data_dir(system) / file_name
|
|
106
147
|
out_path.write_bytes(content)
|
|
107
148
|
return str(out_path)
|
|
108
149
|
except (OSError, ModuleNotFoundError, ValueError) as e:
|
|
@@ -362,13 +403,36 @@ def find_executable(name: str) -> Optional[str]:
|
|
|
362
403
|
return None
|
|
363
404
|
|
|
364
405
|
|
|
365
|
-
def
|
|
406
|
+
def _notify_windows_assoc_changed() -> None:
|
|
407
|
+
"""
|
|
408
|
+
Tell Explorer the file associations changed.
|
|
409
|
+
|
|
410
|
+
Without SHChangeNotify(SHCNE_ASSOCCHANGED) Explorer keeps showing the
|
|
411
|
+
old association/icon from its cache until the next logon.
|
|
412
|
+
"""
|
|
413
|
+
try:
|
|
414
|
+
import ctypes
|
|
415
|
+
|
|
416
|
+
SHCNE_ASSOCCHANGED = 0x08000000
|
|
417
|
+
SHCNF_IDLIST = 0x0
|
|
418
|
+
ctypes.windll.shell32.SHChangeNotify(
|
|
419
|
+
SHCNE_ASSOCCHANGED, SHCNF_IDLIST, None, None
|
|
420
|
+
)
|
|
421
|
+
except (OSError, AttributeError):
|
|
422
|
+
pass # not on Windows / shell32 unavailable
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def register_file_associations_windows(
|
|
426
|
+
exe_path: str, icon_path: Optional[str], system: bool = False
|
|
427
|
+
) -> bool:
|
|
366
428
|
"""
|
|
367
429
|
Register file associations for .pmeprj files on Windows.
|
|
368
430
|
|
|
369
431
|
Args:
|
|
370
432
|
exe_path (str): Path to the executable to associate.
|
|
371
433
|
icon_path (Optional[str]): Path to the icon file for the association.
|
|
434
|
+
system (bool): Register machine-wide under HKLM (requires admin)
|
|
435
|
+
instead of per-user under HKCU.
|
|
372
436
|
|
|
373
437
|
Returns:
|
|
374
438
|
bool: True if successful, False otherwise.
|
|
@@ -376,6 +440,8 @@ def register_file_associations_windows(exe_path: str, icon_path: Optional[str])
|
|
|
376
440
|
if platform.system() != "Windows":
|
|
377
441
|
return False
|
|
378
442
|
|
|
443
|
+
root = winreg.HKEY_LOCAL_MACHINE if system else winreg.HKEY_CURRENT_USER
|
|
444
|
+
|
|
379
445
|
try:
|
|
380
446
|
extensions = [".pmeprj"]
|
|
381
447
|
prog_id = "MoleditPy.File"
|
|
@@ -384,21 +450,19 @@ def register_file_associations_windows(exe_path: str, icon_path: Optional[str])
|
|
|
384
450
|
print("Registering file associations...")
|
|
385
451
|
|
|
386
452
|
# Create ProgID
|
|
387
|
-
with winreg.CreateKey(
|
|
388
|
-
winreg.HKEY_CURRENT_USER, f"Software\\Classes\\{prog_id}"
|
|
389
|
-
) as key:
|
|
453
|
+
with winreg.CreateKey(root, f"Software\\Classes\\{prog_id}") as key:
|
|
390
454
|
winreg.SetValue(key, "", winreg.REG_SZ, f"{app_name} File")
|
|
391
455
|
|
|
392
456
|
# Set default icon
|
|
393
457
|
if icon_path and os.path.exists(icon_path):
|
|
394
458
|
with winreg.CreateKey(
|
|
395
|
-
|
|
459
|
+
root, f"Software\\Classes\\{prog_id}\\DefaultIcon"
|
|
396
460
|
) as key:
|
|
397
461
|
winreg.SetValue(key, "", winreg.REG_SZ, icon_path)
|
|
398
462
|
|
|
399
463
|
# Friendly context-menu label for the open verb
|
|
400
464
|
with winreg.CreateKey(
|
|
401
|
-
|
|
465
|
+
root, f"Software\\Classes\\{prog_id}\\shell\\open"
|
|
402
466
|
) as key:
|
|
403
467
|
winreg.SetValue(key, "", winreg.REG_SZ, f"Open with {app_name}")
|
|
404
468
|
|
|
@@ -406,19 +470,18 @@ def register_file_associations_windows(exe_path: str, icon_path: Optional[str])
|
|
|
406
470
|
# Quote paths to handle spaces
|
|
407
471
|
command = f'"{exe_path}" "%1"'
|
|
408
472
|
with winreg.CreateKey(
|
|
409
|
-
|
|
473
|
+
root,
|
|
410
474
|
f"Software\\Classes\\{prog_id}\\shell\\open\\command",
|
|
411
475
|
) as key:
|
|
412
476
|
winreg.SetValue(key, "", winreg.REG_SZ, command)
|
|
413
477
|
|
|
414
478
|
# Associate extensions
|
|
415
479
|
for ext in extensions:
|
|
416
|
-
with winreg.CreateKey(
|
|
417
|
-
winreg.HKEY_CURRENT_USER, f"Software\\Classes\\{ext}"
|
|
418
|
-
) as key:
|
|
480
|
+
with winreg.CreateKey(root, f"Software\\Classes\\{ext}") as key:
|
|
419
481
|
winreg.SetValue(key, "", winreg.REG_SZ, prog_id)
|
|
420
482
|
print(f" Associated {ext} with {app_name}")
|
|
421
483
|
|
|
484
|
+
_notify_windows_assoc_changed()
|
|
422
485
|
print("File associations registered successfully.")
|
|
423
486
|
return True
|
|
424
487
|
|
|
@@ -623,6 +686,27 @@ def register_file_associations_linux(system: bool = False) -> bool:
|
|
|
623
686
|
return False
|
|
624
687
|
|
|
625
688
|
|
|
689
|
+
def _clean_linux_mimeapps() -> None:
|
|
690
|
+
"""Drop MoleditPy's MIME entries from the per-user mimeapps.list files
|
|
691
|
+
(xdg-mime has no 'unset'; a dangling default otherwise remains)."""
|
|
692
|
+
config_home = Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config")
|
|
693
|
+
candidates = [
|
|
694
|
+
config_home / "mimeapps.list",
|
|
695
|
+
linux_data_home() / "applications" / "mimeapps.list", # legacy location
|
|
696
|
+
]
|
|
697
|
+
for path in candidates:
|
|
698
|
+
if not path.is_file():
|
|
699
|
+
continue
|
|
700
|
+
try:
|
|
701
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
702
|
+
kept = [line for line in lines if LINUX_MIME_TYPE not in line]
|
|
703
|
+
if len(kept) != len(lines):
|
|
704
|
+
path.write_text("\n".join(kept) + "\n", encoding="utf-8")
|
|
705
|
+
print(f" Cleaned {path}")
|
|
706
|
+
except OSError as e:
|
|
707
|
+
print(f" Failed to clean {path}: {e}")
|
|
708
|
+
|
|
709
|
+
|
|
626
710
|
def unregister_file_associations_linux(system: bool = False) -> None:
|
|
627
711
|
"""Remove the .pmeprj MIME type, its icon, and refresh the databases."""
|
|
628
712
|
if platform.system() != "Linux":
|
|
@@ -647,6 +731,9 @@ def unregister_file_associations_linux(system: bool = False) -> None:
|
|
|
647
731
|
except OSError as e:
|
|
648
732
|
print(f" Failed to remove {path}: {e}")
|
|
649
733
|
|
|
734
|
+
if not system:
|
|
735
|
+
_clean_linux_mimeapps()
|
|
736
|
+
|
|
650
737
|
_run_quiet(["update-mime-database", str(mime_dir)])
|
|
651
738
|
_run_quiet(["update-desktop-database", str(data_home / "applications")])
|
|
652
739
|
_run_quiet(["gtk-update-icon-cache", str(data_home / "icons" / "hicolor")])
|
|
@@ -710,17 +797,20 @@ def delete_registry_tree(key, sub_key):
|
|
|
710
797
|
return False
|
|
711
798
|
|
|
712
799
|
|
|
713
|
-
def unregister_file_associations_windows() -> None:
|
|
800
|
+
def unregister_file_associations_windows(system: bool = False) -> None:
|
|
714
801
|
"""
|
|
715
802
|
Unregister file associations for .pmeprj and .pmeraw files on Windows.
|
|
803
|
+
With ``system=True`` removes the machine-wide (HKLM) registration.
|
|
716
804
|
"""
|
|
717
805
|
if platform.system() != "Windows":
|
|
718
806
|
return
|
|
719
807
|
|
|
808
|
+
root = winreg.HKEY_LOCAL_MACHINE if system else winreg.HKEY_CURRENT_USER
|
|
809
|
+
|
|
720
810
|
print("Unregistering file associations...")
|
|
721
811
|
keys_to_remove = [
|
|
722
|
-
(
|
|
723
|
-
(
|
|
812
|
+
(root, "Software\\Classes\\.pmeprj"),
|
|
813
|
+
(root, "Software\\Classes\\.pmeraw"),
|
|
724
814
|
]
|
|
725
815
|
|
|
726
816
|
# ProgID might have subkeys (shell, DefaultIcon), so we need recursive delete
|
|
@@ -738,9 +828,20 @@ def unregister_file_associations_windows() -> None:
|
|
|
738
828
|
print(f" Failed to remove {key_path}: {e}")
|
|
739
829
|
|
|
740
830
|
# Remove ProgID recursively
|
|
741
|
-
if delete_registry_tree(
|
|
831
|
+
if delete_registry_tree(root, prog_id_key):
|
|
742
832
|
print(f" Removed registry tree: {prog_id_key}")
|
|
743
833
|
|
|
834
|
+
# Explorer pins its own per-user choice under FileExts; without
|
|
835
|
+
# removing it (and notifying), the association looks still present.
|
|
836
|
+
for ext in (".pmeprj",):
|
|
837
|
+
file_exts_key = (
|
|
838
|
+
"Software\\Microsoft\\Windows\\CurrentVersion\\"
|
|
839
|
+
f"Explorer\\FileExts\\{ext}"
|
|
840
|
+
)
|
|
841
|
+
if delete_registry_tree(winreg.HKEY_CURRENT_USER, file_exts_key):
|
|
842
|
+
print(f" Removed Explorer cache: {file_exts_key}")
|
|
843
|
+
|
|
844
|
+
_notify_windows_assoc_changed()
|
|
744
845
|
print("File associations unregistered.")
|
|
745
846
|
except OSError as e:
|
|
746
847
|
print(f"Error during file association removal: {e}")
|
|
@@ -757,11 +858,17 @@ def remove_shortcut(system_scope: bool = False) -> None:
|
|
|
757
858
|
shortcut_paths = []
|
|
758
859
|
shortcut_name = "MoleditPy"
|
|
759
860
|
|
|
760
|
-
if system_scope and
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
861
|
+
if system_scope and not is_root():
|
|
862
|
+
if system == "Windows":
|
|
863
|
+
print(
|
|
864
|
+
"Warning: removing a system-wide install requires an "
|
|
865
|
+
"administrator terminal; system entries will likely remain."
|
|
866
|
+
)
|
|
867
|
+
else:
|
|
868
|
+
print(
|
|
869
|
+
"Warning: removing a system-wide install requires root; "
|
|
870
|
+
"system files will likely remain. Re-run with sudo."
|
|
871
|
+
)
|
|
765
872
|
|
|
766
873
|
if system == "Windows":
|
|
767
874
|
# Usually in APPDATA/Microsoft/Windows/Start Menu/Programs
|
|
@@ -780,6 +887,20 @@ def remove_shortcut(system_scope: bool = False) -> None:
|
|
|
780
887
|
|
|
781
888
|
unregister_file_associations_windows()
|
|
782
889
|
|
|
890
|
+
if system_scope:
|
|
891
|
+
program_data = os.environ.get("PROGRAMDATA") or "C:/ProgramData"
|
|
892
|
+
public = os.environ.get("PUBLIC") or "C:/Users/Public"
|
|
893
|
+
shortcut_paths.append(
|
|
894
|
+
Path(program_data)
|
|
895
|
+
/ "Microsoft"
|
|
896
|
+
/ "Windows"
|
|
897
|
+
/ "Start Menu"
|
|
898
|
+
/ "Programs"
|
|
899
|
+
/ f"{shortcut_name}.lnk"
|
|
900
|
+
)
|
|
901
|
+
shortcut_paths.append(Path(public) / "Desktop" / f"{shortcut_name}.lnk")
|
|
902
|
+
unregister_file_associations_windows(system=True)
|
|
903
|
+
|
|
783
904
|
elif system == "Linux":
|
|
784
905
|
# Usually in ~/.local/share/applications/ plus a Desktop copy
|
|
785
906
|
home = Path.home()
|
|
@@ -832,11 +953,13 @@ def remove_shortcut(system_scope: bool = False) -> None:
|
|
|
832
953
|
# Clean up the icons extracted at install time
|
|
833
954
|
try:
|
|
834
955
|
shutil.rmtree(get_persistent_data_dir(), ignore_errors=True)
|
|
956
|
+
if system_scope:
|
|
957
|
+
shutil.rmtree(get_persistent_data_dir(system=True), ignore_errors=True)
|
|
835
958
|
except OSError:
|
|
836
959
|
pass
|
|
837
960
|
|
|
838
961
|
|
|
839
|
-
def get_file_icon_path() -> Optional[str]:
|
|
962
|
+
def get_file_icon_path(system: bool = False) -> Optional[str]:
|
|
840
963
|
"""
|
|
841
964
|
Gets the absolute path to the file icon (file_icon.ico) for Windows associations.
|
|
842
965
|
"""
|
|
@@ -845,7 +968,7 @@ def get_file_icon_path() -> Optional[str]:
|
|
|
845
968
|
|
|
846
969
|
# Extract to a persistent location: as_file() paths may be temporary
|
|
847
970
|
# (zip installs) and would be dead by the time the registry reads them.
|
|
848
|
-
return _extract_data_file("file_icon.ico")
|
|
971
|
+
return _extract_data_file("file_icon.ico", system=system)
|
|
849
972
|
|
|
850
973
|
|
|
851
974
|
def python_for_executable(exe_path: str) -> str:
|
|
@@ -969,6 +1092,56 @@ def refresh_launch_services(app_path: Path, unregister: bool = False) -> None:
|
|
|
969
1092
|
return
|
|
970
1093
|
|
|
971
1094
|
|
|
1095
|
+
def _move_windows_shortcuts_to_all_users(desktop: bool, app_menu: bool) -> None:
|
|
1096
|
+
"""
|
|
1097
|
+
Relocate freshly created per-user shortcuts to the all-users locations
|
|
1098
|
+
(ProgramData Start Menu / Public Desktop). pyshortcuts only writes
|
|
1099
|
+
per-user paths, so a system-wide install moves them afterwards.
|
|
1100
|
+
"""
|
|
1101
|
+
name = "MoleditPy"
|
|
1102
|
+
moves = []
|
|
1103
|
+
appdata = os.environ.get("APPDATA")
|
|
1104
|
+
program_data = os.environ.get("PROGRAMDATA") or "C:/ProgramData"
|
|
1105
|
+
public = os.environ.get("PUBLIC") or "C:/Users/Public"
|
|
1106
|
+
|
|
1107
|
+
if app_menu and appdata:
|
|
1108
|
+
moves.append(
|
|
1109
|
+
(
|
|
1110
|
+
Path(appdata)
|
|
1111
|
+
/ "Microsoft"
|
|
1112
|
+
/ "Windows"
|
|
1113
|
+
/ "Start Menu"
|
|
1114
|
+
/ "Programs"
|
|
1115
|
+
/ f"{name}.lnk",
|
|
1116
|
+
Path(program_data)
|
|
1117
|
+
/ "Microsoft"
|
|
1118
|
+
/ "Windows"
|
|
1119
|
+
/ "Start Menu"
|
|
1120
|
+
/ "Programs"
|
|
1121
|
+
/ f"{name}.lnk",
|
|
1122
|
+
)
|
|
1123
|
+
)
|
|
1124
|
+
if desktop:
|
|
1125
|
+
moves.append(
|
|
1126
|
+
(
|
|
1127
|
+
Path.home() / "Desktop" / f"{name}.lnk",
|
|
1128
|
+
Path(public) / "Desktop" / f"{name}.lnk",
|
|
1129
|
+
)
|
|
1130
|
+
)
|
|
1131
|
+
|
|
1132
|
+
for src, dest in moves:
|
|
1133
|
+
if not src.is_file():
|
|
1134
|
+
continue
|
|
1135
|
+
try:
|
|
1136
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
1137
|
+
if dest.exists():
|
|
1138
|
+
dest.unlink()
|
|
1139
|
+
shutil.move(str(src), str(dest))
|
|
1140
|
+
print(f"Moved shortcut to all-users location: {dest}")
|
|
1141
|
+
except OSError as e:
|
|
1142
|
+
print(f"Warning: could not move {src} to {dest}: {e}")
|
|
1143
|
+
|
|
1144
|
+
|
|
972
1145
|
def install(options: Optional[InstallOptions] = None) -> int:
|
|
973
1146
|
"""
|
|
974
1147
|
Creates shortcuts and file associations for the installed moleditpy
|
|
@@ -990,15 +1163,14 @@ def install(options: Optional[InstallOptions] = None) -> int:
|
|
|
990
1163
|
print("Nothing to install: all components are disabled.")
|
|
991
1164
|
return 1
|
|
992
1165
|
|
|
993
|
-
if options.system:
|
|
1166
|
+
if options.system and not is_root():
|
|
994
1167
|
if system == "Windows":
|
|
995
|
-
print("Error: system-wide installation
|
|
996
|
-
print("
|
|
997
|
-
|
|
998
|
-
if not is_root():
|
|
1168
|
+
print("Error: system-wide installation requires an elevated terminal.")
|
|
1169
|
+
print("Re-run from an administrator terminal, or drop --system.")
|
|
1170
|
+
else:
|
|
999
1171
|
print("Error: system-wide installation requires root privileges.")
|
|
1000
1172
|
print("Re-run with sudo, or drop --system for a per-user install.")
|
|
1001
|
-
|
|
1173
|
+
return 1
|
|
1002
1174
|
|
|
1003
1175
|
command_name = "moleditpy"
|
|
1004
1176
|
|
|
@@ -1081,14 +1253,19 @@ def install(options: Optional[InstallOptions] = None) -> int:
|
|
|
1081
1253
|
"skipped for a system-wide install."
|
|
1082
1254
|
)
|
|
1083
1255
|
else:
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1256
|
+
with _com_initialized():
|
|
1257
|
+
make_shortcut(
|
|
1258
|
+
script=full_command,
|
|
1259
|
+
name=shortcut_name,
|
|
1260
|
+
icon=icon_path,
|
|
1261
|
+
desktop=options.desktop,
|
|
1262
|
+
startmenu=options.app_menu,
|
|
1263
|
+
noexe=True,
|
|
1264
|
+
)
|
|
1265
|
+
if system == "Windows" and options.system:
|
|
1266
|
+
_move_windows_shortcuts_to_all_users(
|
|
1267
|
+
options.desktop, options.app_menu
|
|
1268
|
+
)
|
|
1092
1269
|
created_in = [
|
|
1093
1270
|
loc
|
|
1094
1271
|
for enabled, loc in (
|
|
@@ -1283,11 +1460,13 @@ end open
|
|
|
1283
1460
|
# pickle-based format and is intentionally NOT associated with
|
|
1284
1461
|
# double-click for safety).
|
|
1285
1462
|
if system == "Windows" and original_exe_path and options.file_assoc:
|
|
1286
|
-
file_icon_path = get_file_icon_path()
|
|
1463
|
+
file_icon_path = get_file_icon_path(system=options.system)
|
|
1287
1464
|
if not file_icon_path:
|
|
1288
1465
|
file_icon_path = icon_path
|
|
1289
1466
|
|
|
1290
|
-
register_file_associations_windows(
|
|
1467
|
+
register_file_associations_windows(
|
|
1468
|
+
str(original_exe_path), file_icon_path, system=options.system
|
|
1469
|
+
)
|
|
1291
1470
|
|
|
1292
1471
|
print("\nYou can remove the shortcut and file associations by running:")
|
|
1293
1472
|
print(" moleditpy-installer --remove")
|
|
@@ -1343,9 +1522,14 @@ def main() -> int:
|
|
|
1343
1522
|
),
|
|
1344
1523
|
)
|
|
1345
1524
|
parser.add_argument(
|
|
1525
|
+
"--uninstall",
|
|
1346
1526
|
"--remove",
|
|
1527
|
+
dest="uninstall",
|
|
1347
1528
|
action="store_true",
|
|
1348
|
-
help=
|
|
1529
|
+
help=(
|
|
1530
|
+
"Uninstall: remove the shortcuts and unregister file "
|
|
1531
|
+
"associations (--remove is a deprecated alias)."
|
|
1532
|
+
),
|
|
1349
1533
|
)
|
|
1350
1534
|
parser.add_argument(
|
|
1351
1535
|
"--check",
|
|
@@ -1373,7 +1557,7 @@ def main() -> int:
|
|
|
1373
1557
|
parser.add_argument(
|
|
1374
1558
|
"--system",
|
|
1375
1559
|
action="store_true",
|
|
1376
|
-
help="Install system-wide instead of per-user (
|
|
1560
|
+
help="Install system-wide instead of per-user (requires sudo/admin).",
|
|
1377
1561
|
)
|
|
1378
1562
|
parser.add_argument(
|
|
1379
1563
|
"--no-tui",
|
|
@@ -1389,7 +1573,7 @@ def main() -> int:
|
|
|
1389
1573
|
|
|
1390
1574
|
args = parser.parse_args()
|
|
1391
1575
|
|
|
1392
|
-
if args.
|
|
1576
|
+
if args.uninstall:
|
|
1393
1577
|
remove_shortcut(system_scope=args.system)
|
|
1394
1578
|
return 0
|
|
1395
1579
|
|
|
@@ -101,7 +101,7 @@ class InstallerApp(App):
|
|
|
101
101
|
|
|
102
102
|
BINDINGS = [
|
|
103
103
|
("i", "install", "Install"),
|
|
104
|
-
("
|
|
104
|
+
("u", "remove", "Uninstall"),
|
|
105
105
|
("q", "quit", "Quit"),
|
|
106
106
|
]
|
|
107
107
|
|
|
@@ -121,13 +121,12 @@ class InstallerApp(App):
|
|
|
121
121
|
with RadioSet(id="scope"):
|
|
122
122
|
yield RadioButton("User (recommended)", value=True, id="scope_user")
|
|
123
123
|
yield RadioButton(
|
|
124
|
-
"System-wide (requires sudo/
|
|
124
|
+
"System-wide (requires sudo / admin terminal)",
|
|
125
125
|
id="scope_system",
|
|
126
|
-
disabled=platform.system() == "Windows",
|
|
127
126
|
)
|
|
128
127
|
with Horizontal(id="buttons"):
|
|
129
128
|
yield Button("Install", variant="success", id="install")
|
|
130
|
-
yield Button("
|
|
129
|
+
yield Button("Uninstall", variant="error", id="remove")
|
|
131
130
|
yield Button("Quit", variant="default", id="quit")
|
|
132
131
|
yield Static("Status: detecting moleditpy executable…", id="status")
|
|
133
132
|
yield RichLog(id="log", wrap=True, markup=False)
|
|
@@ -136,7 +135,7 @@ class InstallerApp(App):
|
|
|
136
135
|
def on_mount(self) -> None:
|
|
137
136
|
self.log_line("Welcome! Pick components, then press Install.")
|
|
138
137
|
if platform.system() == "Windows":
|
|
139
|
-
self.log_line("Note: system-wide scope
|
|
138
|
+
self.log_line("Note: system-wide scope needs an administrator terminal.")
|
|
140
139
|
self.run_worker(self._detect_executable, thread=True)
|
|
141
140
|
|
|
142
141
|
# ------------------------------------------------------------------ #
|
|
@@ -233,7 +232,8 @@ class InstallerApp(App):
|
|
|
233
232
|
self._set_status("Removing…")
|
|
234
233
|
self.run_worker(
|
|
235
234
|
lambda: self._run_installer_action(
|
|
236
|
-
lambda: installer.remove_shortcut(system_scope=system_scope),
|
|
235
|
+
lambda: installer.remove_shortcut(system_scope=system_scope),
|
|
236
|
+
"Uninstall",
|
|
237
237
|
),
|
|
238
238
|
thread=True,
|
|
239
239
|
)
|
{moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: moleditpy-installer
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.3
|
|
4
4
|
Summary: Installer and application bundler for MoleditPy
|
|
5
5
|
Author-email: HiroYokoyama <titech.yoko.hiro@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -732,7 +732,7 @@ This package is a helper utility that automatically installs the correct version
|
|
|
732
732
|
moleditpy-installer --no-tui # install with the defaults
|
|
733
733
|
moleditpy-installer --desktop # also create a Desktop shortcut
|
|
734
734
|
moleditpy-installer --no-file-assoc # skip the .pmeprj association
|
|
735
|
-
sudo moleditpy-installer --system # system-wide (
|
|
735
|
+
sudo moleditpy-installer --system # system-wide (admin terminal on Windows)
|
|
736
736
|
```
|
|
737
737
|
|
|
738
738
|
Any explicit option skips the TUI.
|
|
@@ -753,13 +753,13 @@ This package is a helper utility that automatically installs the correct version
|
|
|
753
753
|
|
|
754
754
|
> **Tip:** Run the installer from the Python environment where `moleditpy` is installed (e.g. after `conda activate <env>`). The installer verifies the launch command and warns you if the pairing cannot start MoleditPy.
|
|
755
755
|
|
|
756
|
-
3. **
|
|
757
|
-
To remove the shortcuts, unregister file associations, and clean up extracted icon files, run (add `--system` for a system-wide install):
|
|
756
|
+
3. **Uninstall**
|
|
757
|
+
To remove the shortcuts, unregister file associations, and clean up extracted icon files, run (add `--system` for a system-wide install; `--remove` remains as a deprecated alias):
|
|
758
758
|
|
|
759
759
|
```bash
|
|
760
|
-
moleditpy-installer --
|
|
760
|
+
moleditpy-installer --uninstall
|
|
761
761
|
# or
|
|
762
|
-
python -m moleditpy_installer --
|
|
762
|
+
python -m moleditpy_installer --uninstall
|
|
763
763
|
```
|
|
764
764
|
|
|
765
765
|
4. **Check Executable Location**
|
|
@@ -789,7 +789,7 @@ This package is a helper utility that automatically installs the correct version
|
|
|
789
789
|
## What the installer sets up
|
|
790
790
|
|
|
791
791
|
### Windows
|
|
792
|
-
- Start Menu shortcut (icon included); Desktop shortcut when enabled (`--desktop` or the TUI checkbox).
|
|
792
|
+
- Start Menu shortcut (icon included); Desktop shortcut when enabled (`--desktop` or the TUI checkbox). With `--system` (admin) the shortcuts go to the all-users locations and the association to HKLM.
|
|
793
793
|
- `.pmeprj` files open MoleditPy on double-click (per-user registry, no admin rights needed).
|
|
794
794
|
- Icons are stored under `%LOCALAPPDATA%\MoleditPy\installer` so they survive temp-folder cleanup.
|
|
795
795
|
|
|
@@ -805,13 +805,13 @@ This package is a helper utility that automatically installs the correct version
|
|
|
805
805
|
- `.pmeprj` files open MoleditPy on double-click: a per-user `application/x-moleditpy-project` MIME type is registered (freedesktop shared-mime-info, no root needed) and set as the default handler.
|
|
806
806
|
- `.pmeprj` documents get their own file icon in file managers, matching Windows and macOS.
|
|
807
807
|
|
|
808
|
-
`moleditpy-installer --
|
|
808
|
+
`moleditpy-installer --uninstall` undoes all of the above for the current user (add `--system` for system-wide installs).
|
|
809
809
|
|
|
810
810
|
## Version 3.0 highlights
|
|
811
811
|
|
|
812
812
|
- **New:** interactive Textual TUI — pick components and scope, watch the log, press Install/Remove.
|
|
813
813
|
- **New:** component selection (`--desktop`, `--app-menu`, `--file-assoc`) with safer defaults (no Desktop shortcut unless requested).
|
|
814
|
-
- **New:** system-wide scope on Linux (`/usr/share
|
|
814
|
+
- **New:** system-wide scope on Linux (`/usr/share`), macOS (`/Applications`), and Windows (HKLM + all-users Start Menu; admin terminal) — all requiring sudo/admin.
|
|
815
815
|
- Per-user Linux paths now honor `XDG_DATA_HOME`.
|
|
816
816
|
- End-to-end smoke tests on all three OSes in CI, including the sudo/system path.
|
|
817
817
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/file_icon.icns
RENAMED
|
File without changes
|
{moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/file_icon.ico
RENAMED
|
File without changes
|
{moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer/data/file_icon.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/requires.txt
RENAMED
|
File without changes
|
{moleditpy_installer-3.0.2 → moleditpy_installer-3.0.3}/moleditpy_installer.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|