diff --git a/icloud_gui.py b/icloud_gui.py
index 2067218..41a53f1 100644
--- a/icloud_gui.py
+++ b/icloud_gui.py
@@ -1188,32 +1188,59 @@ class AuthPage(Gtk.Box):
def _on_auth(self, btn):
btn.set_sensitive(False)
- btn.set_label("Signing in…")
- args = ["auth"]
+ btn.set_label("Waiting for Apple…")
+ self._auth_btn = btn
+ cmd = [ICLOUDCTL, "auth"]
if self.sms_check.get_active():
- args.append("--force-sms")
+ cmd.append("--force-sms")
if self.debug_check.get_active():
- args.append("--debug")
- self.status_label.set_markup('Connecting to Apple — a code will be sent to your device…')
+ cmd.append("--debug")
+ self.status_label.set_markup('Connecting to Apple — a code will appear on your device…')
- def _worker():
- rc, out, err = icloudctl(*args)
- GLib.idle_add(self._auth_result, rc, out + err, btn)
- threading.Thread(target=_worker, daemon=True).start()
-
- def _auth_result(self, rc, output, btn):
- btn.set_sensitive(True)
- btn.set_label("Sign in with Apple ID")
- if "two-factor" in output.lower() or "verification" in output.lower() or "2fa" in output.lower() or "enter" in output.lower():
- self.status_label.set_markup(
- 'Code sent — enter it in the box below.'
+ try:
+ self._auth_proc = subprocess.Popen(
+ cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT, text=True, bufsize=1
)
- self.tfa_entry.grab_focus()
- elif rc == 0:
+ except Exception as e:
+ btn.set_sensitive(True)
+ btn.set_label("Sign in with Apple ID")
+ self.status_label.set_markup(f'Failed to start: {e}')
+ return
+
+ proc = self._auth_proc
+ output_lines = []
+
+ def _reader():
+ for line in proc.stdout:
+ output_lines.append(line)
+ low = line.lower()
+ if "code" in low or "enter" in low or "sent" in low or "2fa" in low:
+ GLib.idle_add(self._prompt_for_code)
+ proc.wait()
+ out = "".join(output_lines)
+ GLib.idle_add(self._auth_proc_done, proc.returncode, out)
+
+ threading.Thread(target=_reader, daemon=True).start()
+
+ def _prompt_for_code(self):
+ self.status_label.set_markup('Code sent — enter it below and click Verify.')
+ self.tfa_entry.grab_focus()
+
+ def _auth_proc_done(self, rc, output):
+ if rc == 0 and "authenticated" in output.lower():
+ self._unlock_auth_btn()
self.status_label.set_markup('Authentication successful!')
- else:
- msg = GLib.markup_escape_text(output[:300])
- self.status_label.set_markup(f'{msg}')
+ elif rc != 0:
+ self._unlock_auth_btn()
+ msg = GLib.markup_escape_text(output[-300:].strip())
+ self.status_label.set_markup(f'{msg or f"Exited {rc}"}')
+ # rc==0 but still waiting for code → leave button locked until _on_tfa completes
+
+ def _unlock_auth_btn(self):
+ if hasattr(self, "_auth_btn"):
+ self._auth_btn.set_sensitive(True)
+ self._auth_btn.set_label("Sign in with Apple ID")
def _on_tfa(self, _):
code = self.tfa_entry.get_text().strip()
@@ -1222,12 +1249,24 @@ class AuthPage(Gtk.Box):
return
self.tfa_status.set_markup('Verifying…')
+ proc = getattr(self, "_auth_proc", None)
+ if proc and proc.poll() is None:
+ # Feed the code into the waiting auth process
+ try:
+ proc.stdin.write(code + "\n")
+ proc.stdin.flush()
+ except Exception as e:
+ self.tfa_status.set_markup(f'Error: {e}')
+ return
+
+ # auth process already finished — run a fresh auth with code piped in
def _worker():
rc, out, err = icloudctl("auth", input_data=code + "\n")
GLib.idle_add(self._tfa_result, rc, out + err)
threading.Thread(target=_worker, daemon=True).start()
def _tfa_result(self, rc, output):
+ self._unlock_auth_btn()
if rc == 0 or "authenticated" in output.lower() or "success" in output.lower():
self.tfa_status.set_markup('Verified! You can now start the service.')
self.tfa_entry.set_text("")