diff --git a/ui/qml/Main.qml b/ui/qml/Main.qml index 3752b61..47a4829 100644 --- a/ui/qml/Main.qml +++ b/ui/qml/Main.qml @@ -858,7 +858,7 @@ ApplicationWindow { border.width: parent.parent.st === "wait" || parent.parent.st === "run" ? 2 : 0; border.color: parent.parent.st === "run" ? t.accent : "#343f50" Text { anchors.centerIn: parent; text: parent.parent.parent.st === "ok" ? "✓" : (parent.parent.parent.st === "fail" ? "!" : ""); color: parent.parent.parent.st === "ok" ? t.good : t.bad; font.pixelSize: 13; font.weight: Font.Bold } RotationAnimator on rotation { running: parent.parent.parent.st === "run"; from: 0; to: 360; duration: 1000; loops: Animation.Infinite } } - Text { text: ({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.modelData.action] + ((texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id) + Text { text: (({ on: "Turn on ", off: "Turn off ", again: "Re-apply ", update: "Update ", desktop: "Boot into ", gaming: "Boot into ", check: "Download and check the ", flash: "Hand to fwupd: the " })[parent.parent.modelData.action] || "") + ((texts[parent.parent.modelData.id] || {}).label || parent.parent.modelData.id) color: parent.parent.st === "wait" ? t.faint : t.textHi; font.family: t.body; font.pixelSize: 16; font.weight: Font.DemiBold; anchors.verticalCenter: parent.verticalCenter; width: 380; elide: Text.ElideRight } Text { text: ({ wait: "Waiting", run: "Working…", ok: "Done", fail: "Problem" })[parent.parent.st]; color: parent.parent.st === "ok" ? t.good : (parent.parent.st === "fail" ? t.bad : "#b8c3d1"); font.family: t.body; font.pixelSize: 13; anchors.verticalCenter: parent.verticalCenter } } diff --git a/ui/steamify-ui b/ui/steamify-ui index f0913f4..0bc994e 100755 --- a/ui/steamify-ui +++ b/ui/steamify-ui @@ -197,19 +197,19 @@ class Backend(QObject): p.start() def _make_askpass(self, password): - """A private folder with the password, a helper that prints it for - `sudo -A`, and a `sudo` that always uses the helper (makepkg and - yay call plain sudo).""" + """A private folder with a helper that `sudo -A` runs for the + password, and a `sudo` that always uses it (makepkg and yay call + plain sudo). The password is never written to a file: the helper + reads it from a named pipe, and the app writes it into the pipe from + memory each time sudo asks, until _drop_askpass.""" base = os.environ.get("XDG_RUNTIME_DIR") or tempfile.gettempdir() d = tempfile.mkdtemp(prefix="steamify-", dir=base) os.chmod(d, stat.S_IRWXU) - secret = os.path.join(d, "secret") - fd = os.open(secret, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, "w") as f: - f.write(password + "\n") + pipe = os.path.join(d, "pipe") + os.mkfifo(pipe, 0o600) askpass = os.path.join(d, "askpass") with open(askpass, "w") as f: - f.write("#!/bin/sh\nexec cat %s\n" % json.dumps(secret)) + f.write("#!/bin/sh\nexec cat %s\n" % json.dumps(pipe)) os.chmod(askpass, 0o700) bindir = os.path.join(d, "bin") os.mkdir(bindir) @@ -217,9 +217,35 @@ class Backend(QObject): with open(wrapper, "w") as f: f.write("#!/bin/sh\nexec /usr/bin/sudo -A \"$@\"\n") os.chmod(wrapper, 0o700) + stop = threading.Event() + data = (password + "\n").encode() + + def serve(): + while not stop.is_set(): + try: + fd = os.open(pipe, os.O_WRONLY) # waits for a reader + except OSError: + return + try: + if not stop.is_set(): + os.write(fd, data) + except OSError: + pass + finally: + os.close(fd) + threading.Thread(target=serve, daemon=True).start() + self._askpass_stop = (stop, pipe) return d, askpass, bindir def _drop_askpass(self): + if getattr(self, "_askpass_stop", None): + stop, pipe = self._askpass_stop + self._askpass_stop = None + stop.set() + try: # wakes the writer waiting for a reader, which then ends + os.close(os.open(pipe, os.O_RDONLY | os.O_NONBLOCK)) + except OSError: + pass if self._secret_dir: shutil.rmtree(self._secret_dir, ignore_errors=True) self._secret_dir = None