feat(windows): Desktop-Symbol und Startmenue-Eintrag — plus --oeffnen
Ampel / ampel (push) Failing after 47s
Ampel / ampel (push) Failing after 47s
WAS: Das Setup legt jetzt eine Verknuepfung auf dem Desktop UND im
Startmenue an, die Deinstallation nimmt beide zurueck, und `--status`
zeigt, ob sie da sind. Abwaehlbar mit --keine-verknuepfungen.
WARUM (Commander-Meldung): "es gibt kein icon aufm desktop und keinen
startmenü eintrag". Zu Recht — ein Hintergrundprogramm ohne Symbol ist
praktisch unauffindbar: Es laeuft, aber es gibt keinen Weg hinein ausser
der Adresse im Kopf.
DIE VERKNUEPFUNG ZEIGT AUF --oeffnen, NICHT AUF --dienst. Ein Doppelklick
soll Rippy OEFFNEN; ob der Dienst laeuft, ist dem Nutzer egal und er kann
es auch nicht wissen. Zeigte das Symbol auf --dienst, startete jeder Klick
einen ZWEITEN Server, der am belegten Port scheitert — das Fenster blitzt
auf, im Browser passiert nichts. Ein Symbol, das beim zweiten Klick nichts
tut, ist schlimmer als keins.
`--oeffnen` sieht deshalb erst NACH, ob jemand antwortet, startet nur wenn
noetig, und wartet dann, bis der Dienst wirklich da ist — statt sofort
einen Browser auf eine tote Adresse zu schicken. Gemessen: erster Aufruf
2 s bis HTTP 200, zweiter Aufruf keine zusaetzlichen Prozesse.
DIE ORDNER KOMMEN AUS DER REGISTRY, nicht aus %USERPROFILE%. Wer OneDrive
benutzt oder seine Ordner verschoben hat, dessen Desktop liegt woanders —
die Verknuepfung landete dann in einem Ordner, den niemand sieht, und der
Nutzer suchte ein Symbol, das es nirgends gibt.
UEBER POWERSHELL statt pywin32: Eine .lnk ist ein binaeres Shell-Objekt.
pywin32 koennte es, waere aber 20 MB fuer vier Zeilen. Denselben Weg geht
der alte Worker-Installer schon ("nativ via PowerShell").
ZWEI FEHLER, DIE DIE TESTS GEFUNDEN HABEN:
- os.makedirs stand AUSSERHALB des try. Auf einem Laufwerk, das es nicht
gibt, wirft es — aus "Verknuepfung konnte nicht angelegt werden" waere
ein Abbruch der ganzen Installation geworden.
- Der Test dafuer nahm fest Z: als "gibt es nicht". Auf dem Commander-PC
ist Z: ein NETZLAUFWERK (X, Y, Z sind belegt). Ein fest verdrahteter
Laufwerksbuchstabe ist eine Annahme ueber eine fremde Maschine; jetzt
wird zur Laufzeit ein freier gesucht.
Und der Apostroph: In PowerShell wird er innerhalb einfacher
Anfuehrungszeichen durch VERDOPPELN maskiert. Ohne das braeche ein Pfad wie
C:\Users\O'Brien\... das Skript entzwei, und der Fehler saehe aus wie ein
Syntaxfehler in Rippy.
GEMESSEN, nach echter Installation nach %LOCALAPPDATA%\Rippy:
Desktop-Symbol -> Rippy.exe --oeffnen, mit Symbol und Beschreibung
Startmenue -> ebenso
Programme+Feat. -> Rippy 2.0.0, 100 MB, Deinstallieren funktioniert
Autostart -> "...\Rippy.exe" --dienst
Start ueber das Symbol: HTTP 200 nach 2 s
Worker: TobisNicerPC, Encoder cpu-x264/x265/av1, vce, vce-av1
GEMESSEN: ruff sauber, 521 Tests gruen + 15 uebersprungen (vorher 512).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f4a8d77598
commit
99c05865af
@@ -0,0 +1,129 @@
|
|||||||
|
"""Verknuepfungen: der Ordner, das Skript, das Zurueckraeumen.
|
||||||
|
|
||||||
|
Das PowerShell-Skript wird als reine Funktion gebaut und geprueft — so faellt
|
||||||
|
ein Zitier-Fehler auf, ohne dass eine .lnk entstehen muss. Die Tests, die
|
||||||
|
wirklich eine anlegen, laufen nur unter Windows und raeumen hinter sich auf.
|
||||||
|
|
||||||
|
Hinweis zu den Pfaden: Windows-Pfade stehen hier in ROHEN Zeichenketten oder
|
||||||
|
werden aus `B` (dem Backslash) zusammengesetzt. In normalen Zeichenketten
|
||||||
|
waeren `C:\\D` und `C:\\Users` ungueltige Escape-Sequenzen — heute eine
|
||||||
|
DeprecationWarning, in einer kuenftigen Python-Version ein harter Fehler.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from rippy.platform import verknuepfungen as vk
|
||||||
|
|
||||||
|
nur_windows = pytest.mark.skipif(os.name != "nt", reason="nur unter Windows")
|
||||||
|
|
||||||
|
B = chr(92) # Backslash — haelt jede Escape-Falle aus den Tests raus
|
||||||
|
|
||||||
|
|
||||||
|
# ── Das Skript ──────────────────────────────────────────────────────────
|
||||||
|
def test_skript_setzt_ziel_und_argumente():
|
||||||
|
lnk = "C:" + B + "D" + B + "Rippy.lnk"
|
||||||
|
exe = "C:" + B + "R" + B + "Rippy.exe"
|
||||||
|
s = vk.skript(lnk, exe, "--oeffnen")
|
||||||
|
assert "CreateShortcut('" + lnk + "')" in s
|
||||||
|
assert "$s.TargetPath = '" + exe + "'" in s
|
||||||
|
assert "$s.Arguments = '--oeffnen'" in s
|
||||||
|
assert s.rstrip().endswith("$s.Save()")
|
||||||
|
|
||||||
|
|
||||||
|
def test_apostroph_im_pfad_bricht_das_skript_nicht():
|
||||||
|
"""In PowerShell wird ein Apostroph innerhalb einfacher Anfuehrungszeichen
|
||||||
|
durch VERDOPPELN maskiert. Ohne das bricht ein Pfad mit Apostroph das
|
||||||
|
Skript mitten entzwei — und der Fehler sieht aus wie ein Syntaxfehler in
|
||||||
|
Rippy statt wie ein Pfad, der ein Sonderzeichen enthaelt."""
|
||||||
|
s = vk.skript("C:" + B + "Users" + B + "O'Brien" + B + "Rippy.lnk",
|
||||||
|
"C:" + B + "R" + B + "Rippy.exe")
|
||||||
|
assert "O''Brien" in s
|
||||||
|
# Gerade Zahl von Anfuehrungszeichen — sonst ist ein String offen.
|
||||||
|
assert s.count("'") % 2 == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_ohne_arbeitsordner_wird_der_ordner_des_ziels_genommen():
|
||||||
|
ordner = "C:" + B + "Programme" + B + "Rippy"
|
||||||
|
s = vk.skript("C:" + B + "D" + B + "R.lnk", ordner + B + "Rippy.exe")
|
||||||
|
assert "$s.WorkingDirectory = '" + ordner + "'" in s
|
||||||
|
|
||||||
|
|
||||||
|
def test_leere_angaben_erzeugen_keine_leeren_zeilen():
|
||||||
|
"""Ein `$s.IconLocation = ''` wuerde das Symbol LOESCHEN statt es
|
||||||
|
unveraendert zu lassen."""
|
||||||
|
s = vk.skript("C:" + B + "D" + B + "R.lnk", "C:" + B + "R" + B + "R.exe")
|
||||||
|
assert "IconLocation" not in s
|
||||||
|
assert "Description" not in s
|
||||||
|
|
||||||
|
|
||||||
|
# ── Die Orte ────────────────────────────────────────────────────────────
|
||||||
|
@nur_windows
|
||||||
|
def test_desktop_und_startmenue_kommen_aus_der_registry():
|
||||||
|
"""Ein fest verdrahtetes %USERPROFILE%/Desktop waere eine ANNAHME. Wer
|
||||||
|
OneDrive benutzt oder seine Ordner verschoben hat, dessen Desktop liegt
|
||||||
|
woanders — die Verknuepfung landete dann in einem Ordner, den niemand
|
||||||
|
sieht, und der Nutzer suchte ein Symbol, das es nirgends gibt."""
|
||||||
|
desktop = vk.shell_ordner("Desktop")
|
||||||
|
programme = vk.shell_ordner("Programs")
|
||||||
|
assert desktop and os.path.isdir(desktop)
|
||||||
|
assert programme and os.path.isdir(programme)
|
||||||
|
assert vk.desktop_pfad().endswith("Rippy.lnk")
|
||||||
|
assert "Start Menu" in vk.startmenue_pfad()
|
||||||
|
|
||||||
|
|
||||||
|
def test_unbekannter_ordner_ergibt_leer_statt_zu_raten():
|
||||||
|
assert vk.shell_ordner("GibtEsNicht") == ""
|
||||||
|
|
||||||
|
|
||||||
|
# ── Wirklich anlegen ────────────────────────────────────────────────────
|
||||||
|
@nur_windows
|
||||||
|
def test_verknuepfung_entsteht_wirklich(tmp_path):
|
||||||
|
"""Nachsehen statt glauben: PowerShell meldet auch dann Erfolg, wenn
|
||||||
|
Save() still scheitert. `anlegen()` prueft deshalb die Datei."""
|
||||||
|
exe = tmp_path / "Rippy.exe"
|
||||||
|
exe.write_bytes(b"x" * 512)
|
||||||
|
lnk = str(tmp_path / "Rippy.lnk")
|
||||||
|
|
||||||
|
assert vk.anlegen(lnk, str(exe), "--oeffnen", beschreibung="Test") is True
|
||||||
|
assert os.path.isfile(lnk)
|
||||||
|
assert os.path.getsize(lnk) > 0
|
||||||
|
|
||||||
|
assert vk.entfernen(lnk) is True
|
||||||
|
assert not os.path.exists(lnk)
|
||||||
|
|
||||||
|
|
||||||
|
def _freier_laufwerksbuchstabe() -> str:
|
||||||
|
"""Ein Buchstabe, den es auf DIESER Maschine wirklich nicht gibt.
|
||||||
|
|
||||||
|
Der erste Anlauf nahm fest `Z:` — auf dem Commander-PC ist das ein
|
||||||
|
NETZLAUFWERK (X, Y und Z sind dort belegt). Der Test legte damit
|
||||||
|
froehlich eine Verknuepfung auf einer Freigabe an und schlug fehl, weil
|
||||||
|
er einen Misserfolg erwartete. Ein fest verdrahteter Laufwerksbuchstabe
|
||||||
|
ist eine Annahme ueber eine fremde Maschine.
|
||||||
|
"""
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
maske = ctypes.WinDLL("kernel32").GetLogicalDrives()
|
||||||
|
for i in range(25, -1, -1): # von Z rueckwaerts
|
||||||
|
if not (maske & (1 << i)):
|
||||||
|
return chr(ord("A") + i)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@nur_windows
|
||||||
|
def test_anlegen_auf_unmoeglichem_pfad_wirft_nicht(tmp_path):
|
||||||
|
"""Eine fehlende Verknuepfung ist aergerlich, aber kein Grund, eine sonst
|
||||||
|
geglueckte Installation scheitern zu lassen."""
|
||||||
|
buchstabe = _freier_laufwerksbuchstabe()
|
||||||
|
if not buchstabe:
|
||||||
|
pytest.skip("Alle 26 Laufwerksbuchstaben sind belegt")
|
||||||
|
exe = tmp_path / "Rippy.exe"
|
||||||
|
exe.write_bytes(b"x")
|
||||||
|
assert vk.anlegen(buchstabe + ":" + B + "gibt-es-nicht" + B + "R.lnk",
|
||||||
|
str(exe)) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_entfernen_auf_nichts_ist_kein_fehler():
|
||||||
|
assert vk.entfernen("C:" + B + "gibt-es-nicht" + B + "R.lnk") is False
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
"""Verknüpfungen auf dem Desktop und im Startmenü.
|
||||||
|
|
||||||
|
## Warum über PowerShell und nicht über pywin32
|
||||||
|
|
||||||
|
Eine `.lnk` ist keine Textdatei, sondern ein binäres Shell-Objekt. Sie von
|
||||||
|
Hand zu schreiben ist Bastelei; `pywin32` könnte es, wäre aber eine
|
||||||
|
20-MB-Abhängigkeit für vier Zeilen. Windows bringt die Fähigkeit selbst mit:
|
||||||
|
`WScript.Shell` ist seit Jahrzehnten da und braucht keine Installation.
|
||||||
|
|
||||||
|
Dieses Projekt geht den Weg schon einmal — der alte Worker-Installer legt
|
||||||
|
seine Verknüpfungen ebenfalls „nativ via PowerShell (kein win32com mehr
|
||||||
|
nötig)" an.
|
||||||
|
|
||||||
|
## Warum die Ordner aus der Registry kommen
|
||||||
|
|
||||||
|
`%USERPROFILE%\\Desktop` ist eine ANNAHME. Wer OneDrive benutzt oder seine
|
||||||
|
Ordner verschoben hat, dessen Desktop liegt woanders — die Verknüpfung
|
||||||
|
landete dann in einem Ordner, den niemand sieht, und der Nutzer suchte ein
|
||||||
|
Symbol, das es nirgends gibt.
|
||||||
|
|
||||||
|
Die Wahrheit steht unter `Shell Folders` in der Registry. Auf diesem Rechner
|
||||||
|
am 28.08.2026 nachgesehen:
|
||||||
|
|
||||||
|
Desktop C:\\Users\\…\\Desktop
|
||||||
|
Programs C:\\Users\\…\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs
|
||||||
|
|
||||||
|
## Worauf die Verknüpfung zeigt
|
||||||
|
|
||||||
|
Nicht auf `--dienst`. Ein Doppelklick auf ein Desktop-Symbol soll Rippy
|
||||||
|
ÖFFNEN — und dabei ist es dem Nutzer egal, ob der Dienst schon läuft. Die
|
||||||
|
Verknüpfung ruft deshalb `Rippy.exe --oeffnen`: Läuft der Dienst, geht nur
|
||||||
|
der Browser auf; läuft er nicht, wird er zuerst gestartet. Ein Symbol, das
|
||||||
|
beim zweiten Klick einen zweiten Server startet, wäre eine Falle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def shell_ordner(name: str) -> str:
|
||||||
|
"""Der ECHTE Pfad eines Windows-Sonderordners („Desktop", „Programs").
|
||||||
|
|
||||||
|
Aus der Registry, nicht geraten. Leerer String, wenn er nicht zu
|
||||||
|
ermitteln ist — dann wird eben keine Verknüpfung angelegt, statt eine
|
||||||
|
an einer erfundenen Stelle.
|
||||||
|
"""
|
||||||
|
if os.name != "nt":
|
||||||
|
return ""
|
||||||
|
import winreg
|
||||||
|
|
||||||
|
try:
|
||||||
|
with winreg.OpenKey(
|
||||||
|
winreg.HKEY_CURRENT_USER,
|
||||||
|
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
|
||||||
|
) as k:
|
||||||
|
wert, _ = winreg.QueryValueEx(k, name)
|
||||||
|
return os.path.expandvars(str(wert))
|
||||||
|
except OSError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _ps_text(wert: str) -> str:
|
||||||
|
"""Text für ein einfach gequotetes PowerShell-Argument.
|
||||||
|
|
||||||
|
In PowerShell wird ein Apostroph innerhalb einfacher Anführungszeichen
|
||||||
|
durch VERDOPPELN maskiert. Ohne das bricht ein Pfad wie
|
||||||
|
`C:\\Users\\O'Brien\\…` das Skript mitten entzwei — und der Fehler sieht
|
||||||
|
aus wie ein Syntaxfehler in Rippy.
|
||||||
|
"""
|
||||||
|
return (wert or "").replace("'", "''")
|
||||||
|
|
||||||
|
|
||||||
|
def skript(lnk: str, ziel: str, argumente: str = "", icon: str = "",
|
||||||
|
beschreibung: str = "", arbeitsordner: str = "") -> str:
|
||||||
|
"""Das PowerShell-Skript, das die Verknüpfung anlegt (reine Funktion)."""
|
||||||
|
zeilen = [
|
||||||
|
"$s = (New-Object -ComObject WScript.Shell).CreateShortcut('%s')" % _ps_text(lnk),
|
||||||
|
"$s.TargetPath = '%s'" % _ps_text(ziel),
|
||||||
|
]
|
||||||
|
if argumente:
|
||||||
|
zeilen.append("$s.Arguments = '%s'" % _ps_text(argumente))
|
||||||
|
if icon:
|
||||||
|
zeilen.append("$s.IconLocation = '%s'" % _ps_text(icon))
|
||||||
|
if beschreibung:
|
||||||
|
zeilen.append("$s.Description = '%s'" % _ps_text(beschreibung))
|
||||||
|
zeilen.append("$s.WorkingDirectory = '%s'"
|
||||||
|
% _ps_text(arbeitsordner or os.path.dirname(ziel)))
|
||||||
|
zeilen.append("$s.Save()")
|
||||||
|
return "; ".join(zeilen)
|
||||||
|
|
||||||
|
|
||||||
|
def anlegen(lnk: str, ziel: str, argumente: str = "", icon: str = "",
|
||||||
|
beschreibung: str = "", arbeitsordner: str = "") -> bool:
|
||||||
|
"""Legt eine Verknüpfung an. False, wenn es nicht geklappt hat.
|
||||||
|
|
||||||
|
Wirft nicht: Eine fehlende Verknüpfung ist ärgerlich, aber kein Grund,
|
||||||
|
eine sonst geglückte Installation scheitern zu lassen. Der Aufrufer
|
||||||
|
sagt es dem Nutzer.
|
||||||
|
"""
|
||||||
|
if os.name != "nt":
|
||||||
|
return False
|
||||||
|
from rippy.platform.winlauf import OHNE_FENSTER
|
||||||
|
|
||||||
|
try:
|
||||||
|
# os.makedirs MUSS mit in den try: Auf einem Laufwerk, das es nicht
|
||||||
|
# gibt, wirft es — und dann waere aus "Verknuepfung konnte nicht
|
||||||
|
# angelegt werden" ein Abbruch der ganzen Installation geworden.
|
||||||
|
os.makedirs(os.path.dirname(lnk), exist_ok=True)
|
||||||
|
ergebnis = subprocess.run(
|
||||||
|
["powershell", "-NoProfile", "-NonInteractive", "-Command",
|
||||||
|
skript(lnk, ziel, argumente, icon, beschreibung, arbeitsordner)],
|
||||||
|
capture_output=True, text=True, timeout=60,
|
||||||
|
creationflags=OHNE_FENSTER,
|
||||||
|
)
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return False
|
||||||
|
# Nachsehen statt dem Rueckgabewert glauben: PowerShell meldet auch dann
|
||||||
|
# Erfolg, wenn Save() still scheitert.
|
||||||
|
return ergebnis.returncode == 0 and os.path.isfile(lnk)
|
||||||
|
|
||||||
|
|
||||||
|
def entfernen(lnk: str) -> bool:
|
||||||
|
try:
|
||||||
|
os.remove(lnk)
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# ── Die beiden Orte ─────────────────────────────────────────────────────
|
||||||
|
def desktop_pfad(name: str = "Rippy") -> str:
|
||||||
|
ordner = shell_ordner("Desktop")
|
||||||
|
return os.path.join(ordner, name + ".lnk") if ordner else ""
|
||||||
|
|
||||||
|
|
||||||
|
def startmenue_pfad(name: str = "Rippy") -> str:
|
||||||
|
ordner = shell_ordner("Programs")
|
||||||
|
return os.path.join(ordner, name + ".lnk") if ordner else ""
|
||||||
|
|
||||||
|
|
||||||
|
def alle_anlegen(programm: str, icon: str = "", desktop: bool = True,
|
||||||
|
startmenue: bool = True) -> dict:
|
||||||
|
"""Legt beide Verknüpfungen an. Gibt zurück, was WIRKLICH entstanden ist.
|
||||||
|
|
||||||
|
Der Rückgabewert ist die Liste der angelegten Dateien — nicht eine
|
||||||
|
Erfolgsmeldung. So kann der Installer sagen, was er getan hat, statt
|
||||||
|
zu behaupten, es sei alles gut gegangen.
|
||||||
|
"""
|
||||||
|
gemacht = {}
|
||||||
|
ziele = []
|
||||||
|
if desktop:
|
||||||
|
ziele.append(("desktop", desktop_pfad()))
|
||||||
|
if startmenue:
|
||||||
|
ziele.append(("startmenue", startmenue_pfad()))
|
||||||
|
|
||||||
|
for schluessel, lnk in ziele:
|
||||||
|
if not lnk:
|
||||||
|
continue
|
||||||
|
if anlegen(lnk, programm, argumente="--oeffnen", icon=icon or programm,
|
||||||
|
beschreibung="Rippy öffnen — automatisches Ripping"):
|
||||||
|
gemacht[schluessel] = lnk
|
||||||
|
return gemacht
|
||||||
|
|
||||||
|
|
||||||
|
def alle_entfernen() -> list:
|
||||||
|
"""Nimmt beide Verknüpfungen zurück. Gibt zurück, was entfernt wurde."""
|
||||||
|
entfernt = []
|
||||||
|
for lnk in (desktop_pfad(), startmenue_pfad()):
|
||||||
|
if lnk and os.path.isfile(lnk) and entfernen(lnk):
|
||||||
|
entfernt.append(lnk)
|
||||||
|
return entfernt
|
||||||
@@ -41,6 +41,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
|
||||||
|
from rippy.platform import verknuepfungen
|
||||||
from rippy.platform import win_registry as reg
|
from rippy.platform import win_registry as reg
|
||||||
|
|
||||||
VERSION = os.environ.get("RIPPY_VERSION", "2.0.0")
|
VERSION = os.environ.get("RIPPY_VERSION", "2.0.0")
|
||||||
@@ -90,7 +91,7 @@ def melden(text: str) -> None:
|
|||||||
|
|
||||||
# ═══════════════════════════════════════════════════ Installation ═══════
|
# ═══════════════════════════════════════════════════ Installation ═══════
|
||||||
def installieren(ziel: str = None, autostart: bool = True,
|
def installieren(ziel: str = None, autostart: bool = True,
|
||||||
starten_danach: bool = True) -> str:
|
starten_danach: bool = True, verknuepfen: bool = True) -> str:
|
||||||
"""Kopiert Rippy an seinen Platz und meldet es bei Windows an.
|
"""Kopiert Rippy an seinen Platz und meldet es bei Windows an.
|
||||||
|
|
||||||
Gibt den Installationsordner zurück. Wirft bei echten Fehlern — eine
|
Gibt den Installationsordner zurück. Wirft bei echten Fehlern — eine
|
||||||
@@ -129,6 +130,20 @@ def installieren(ziel: str = None, autostart: bool = True,
|
|||||||
if autostart:
|
if autostart:
|
||||||
reg.autostart_setzen('"%s" --dienst' % programm)
|
reg.autostart_setzen('"%s" --dienst' % programm)
|
||||||
|
|
||||||
|
if verknuepfen:
|
||||||
|
# Ohne Symbol auf dem Desktop und ohne Startmenue-Eintrag ist ein
|
||||||
|
# Hintergrundprogramm praktisch unauffindbar: Es laeuft, aber der
|
||||||
|
# Nutzer hat keinen Weg hinein ausser der Adresse im Kopf.
|
||||||
|
angelegt = verknuepfungen.alle_anlegen(
|
||||||
|
programm, icon=os.path.join(ziel, "rippy.ico"))
|
||||||
|
for ort in ("desktop", "startmenue"):
|
||||||
|
if ort in angelegt:
|
||||||
|
melden(" Verknuepfung angelegt: %s" % angelegt[ort])
|
||||||
|
else:
|
||||||
|
# Sagen, was NICHT geklappt hat — sonst sucht der Nutzer ein
|
||||||
|
# Symbol, das es nie gab.
|
||||||
|
melden(" Verknuepfung (%s) konnte nicht angelegt werden." % ort)
|
||||||
|
|
||||||
if starten_danach:
|
if starten_danach:
|
||||||
starte_hintergrund(programm)
|
starte_hintergrund(programm)
|
||||||
|
|
||||||
@@ -153,6 +168,10 @@ def deinstallieren(still: bool = False) -> None:
|
|||||||
ordner = (reg.gelesene_werte().get("InstallLocation")
|
ordner = (reg.gelesene_werte().get("InstallLocation")
|
||||||
or reg.standard_installationsordner())
|
or reg.standard_installationsordner())
|
||||||
|
|
||||||
|
for lnk in verknuepfungen.alle_entfernen():
|
||||||
|
if not still:
|
||||||
|
melden(" Verknuepfung entfernt: %s" % lnk)
|
||||||
|
|
||||||
reg.autostart_entfernen()
|
reg.autostart_entfernen()
|
||||||
reg.austragen()
|
reg.austragen()
|
||||||
laeuft_von_hier = os.path.abspath(_eigene_datei()).lower().startswith(
|
laeuft_von_hier = os.path.abspath(_eigene_datei()).lower().startswith(
|
||||||
@@ -336,6 +355,42 @@ def dienst_starten(port: int = STANDARD_PORT) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def oeffnen(port: int = STANDARD_PORT) -> int:
|
||||||
|
"""Rippy oeffnen — Dienst starten, falls noetig, dann den Browser.
|
||||||
|
|
||||||
|
## Warum die Verknuepfungen hierher zeigen und nicht auf --dienst
|
||||||
|
|
||||||
|
Ein Doppelklick auf ein Desktop-Symbol soll Rippy OEFFNEN. Ob der Dienst
|
||||||
|
schon laeuft, ist dem Nutzer egal — und er kann es auch nicht wissen.
|
||||||
|
|
||||||
|
Zeigte die Verknuepfung auf `--dienst`, startete jeder Klick einen
|
||||||
|
ZWEITEN Server. Der zweite scheiterte am belegten Port, das Fenster
|
||||||
|
blitzte auf und verschwand, und im Browser passierte nichts. Ein Symbol,
|
||||||
|
das beim zweiten Klick nichts tut, ist schlimmer als keins.
|
||||||
|
|
||||||
|
Deshalb: erst NACHSEHEN, ob jemand antwortet. Nur wenn nicht, starten —
|
||||||
|
und dann warten, bis er wirklich da ist, statt sofort einen Browser auf
|
||||||
|
eine tote Adresse zu schicken.
|
||||||
|
"""
|
||||||
|
dienst = Dienst(port)
|
||||||
|
|
||||||
|
if not dienst.erreichbar(timeout=1.5):
|
||||||
|
melden("Rippy wird gestartet …")
|
||||||
|
programm = _eigene_datei()
|
||||||
|
starte_hintergrund(programm)
|
||||||
|
# Warten, bis er ANTWORTET — nicht bis der Prozess existiert.
|
||||||
|
for _ in range(60):
|
||||||
|
if dienst.erreichbar(timeout=1.0):
|
||||||
|
break
|
||||||
|
time.sleep(0.5)
|
||||||
|
else:
|
||||||
|
melden("Rippy antwortet nicht. Laeuft schon etwas auf Port %d?" % port)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
webbrowser.open(dienst.adresse)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def tray_bauen(dienst):
|
def tray_bauen(dienst):
|
||||||
"""Das Tray-Symbol. None, wenn pystray/Pillow fehlen."""
|
"""Das Tray-Symbol. None, wenn pystray/Pillow fehlen."""
|
||||||
try:
|
try:
|
||||||
@@ -380,6 +435,7 @@ def tray_bauen(dienst):
|
|||||||
HILFE = """Rippy für Windows
|
HILFE = """Rippy für Windows
|
||||||
|
|
||||||
RippySetup.exe installieren (Doppelklick)
|
RippySetup.exe installieren (Doppelklick)
|
||||||
|
Rippy.exe --oeffnen oeffnen (startet den Dienst, falls noetig)
|
||||||
Rippy.exe --dienst im Hintergrund laufen lassen
|
Rippy.exe --dienst im Hintergrund laufen lassen
|
||||||
Rippy.exe --deinstallieren entfernen
|
Rippy.exe --deinstallieren entfernen
|
||||||
Rippy.exe --status nachsehen, was installiert ist
|
Rippy.exe --status nachsehen, was installiert ist
|
||||||
@@ -388,6 +444,7 @@ Optionen:
|
|||||||
--port N Port (Vorgabe: 7788)
|
--port N Port (Vorgabe: 7788)
|
||||||
--ziel PFAD Installationsordner
|
--ziel PFAD Installationsordner
|
||||||
--kein-autostart
|
--kein-autostart
|
||||||
|
--keine-verknuepfungen ohne Desktop-Symbol und Startmenue-Eintrag
|
||||||
--still ohne Rückmeldungen
|
--still ohne Rückmeldungen
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -401,6 +458,10 @@ def status() -> int:
|
|||||||
return 0
|
return 0
|
||||||
melden(" In Programme+Features %s" % ("ja" if reg.ist_eingetragen() else "nein"))
|
melden(" In Programme+Features %s" % ("ja" if reg.ist_eingetragen() else "nein"))
|
||||||
melden(" Autostart %s" % (reg.autostart_wert() or "nein"))
|
melden(" Autostart %s" % (reg.autostart_wert() or "nein"))
|
||||||
|
for name, pfad in (("Desktop-Symbol", verknuepfungen.desktop_pfad()),
|
||||||
|
("Startmenue", verknuepfungen.startmenue_pfad())):
|
||||||
|
melden(" %-21s %s" % (name, pfad if pfad and os.path.isfile(pfad)
|
||||||
|
else "nein"))
|
||||||
for name, wert in sorted(reg.gelesene_werte().items()):
|
for name, wert in sorted(reg.gelesene_werte().items()):
|
||||||
melden(" %-18s %s" % (name, wert))
|
melden(" %-18s %s" % (name, wert))
|
||||||
return 0
|
return 0
|
||||||
@@ -420,6 +481,8 @@ def main(argv=None) -> int:
|
|||||||
if "--deinstallieren" in argv:
|
if "--deinstallieren" in argv:
|
||||||
deinstallieren(still="--still" in argv)
|
deinstallieren(still="--still" in argv)
|
||||||
return 0
|
return 0
|
||||||
|
if "--oeffnen" in argv:
|
||||||
|
return oeffnen(int(wert("--port", STANDARD_PORT)))
|
||||||
if "--dienst" in argv:
|
if "--dienst" in argv:
|
||||||
return dienst_starten(int(wert("--port", STANDARD_PORT)))
|
return dienst_starten(int(wert("--port", STANDARD_PORT)))
|
||||||
|
|
||||||
@@ -428,6 +491,7 @@ def main(argv=None) -> int:
|
|||||||
ziel=wert("--ziel"),
|
ziel=wert("--ziel"),
|
||||||
autostart="--kein-autostart" not in argv,
|
autostart="--kein-autostart" not in argv,
|
||||||
starten_danach="--nicht-starten" not in argv,
|
starten_danach="--nicht-starten" not in argv,
|
||||||
|
verknuepfen="--keine-verknuepfungen" not in argv,
|
||||||
)
|
)
|
||||||
melden("Rippy wurde nach %s installiert." % ziel)
|
melden("Rippy wurde nach %s installiert." % ziel)
|
||||||
melden("Die Oberfläche öffnet sich gleich im Browser.")
|
melden("Die Oberfläche öffnet sich gleich im Browser.")
|
||||||
|
|||||||
Reference in New Issue
Block a user