Introduces Gio to tell the user that their password is being verified
instead of just freezing the GUI.
---
hiprompt_gtk/proto.py | 66 ++++++++++++++++++++++++++++++++----------
hiprompt_gtk/window.py | 49 ++++++++++++++++++++-----------
2 files changed, 83 insertions(+), 32 deletions(-)
diff --git a/hiprompt_gtk/proto.py b/hiprompt_gtk/proto.py
index 5f4749f..fecaf6a 100644
--- a/hiprompt_gtk/proto.py
+++ b/hiprompt_gtk/proto.py
@@ -1,8 +1,39 @@
import sys
from enum import Enum
+from gi.repository import GLib, Gio
from .key import Key
+def gio_lines(stream):
+ line = bytearray()
+
+ while not stream.is_closed():
+ byte = stream.read_bytes(1).get_data()
+ if not byte:
+ return
+ line += byte
+ if line[-1] == ord("\n"):
+ yield line.decode()
+ line.clear()
+
+def gio_lines_async(stream, callback):
+ line = bytearray()
+
+ def _callback(source, result):
+ nonlocal line
+ byte = source.read_bytes_finish(result).get_data() if result else b""
+ line += byte
+ if line and line[-1] == ord("\n"):
+ more = callback(line.decode())
+ if not more:
+ return
+ line.clear()
+
+ stream.read_bytes_async(1, GLib.PRIORITY_DEFAULT, None, _callback)
+
+ _callback(stream, None)
+
+
class PromptMode(Enum):
disclose = "disclose"
delete = "delete"
@@ -14,10 +45,11 @@ class Session:
self.unlock = False
self.status = 127
self.mode = None
+ self.stream = Gio.UnixInputStream.new(sys.stdin.fileno(), False)
self.parse()
def parse(self):
- for line in sys.stdin:
+ for line in gio_lines(self.stream):
parts = line.strip().partition(" ")
cmd = parts[0]
args = parts[2]
@@ -44,22 +76,26 @@ class Session:
self.mode = PromptMode(args)
return False
- def password(self, pw):
+ def password(self, pw, callback):
"""
- Sends a password to the daemon in response to an unlock command. Blocks
- until the daemon answers regarding the password's validity, returning
- True if it was correct and False otherwise.
+ Sends a password to the daemon in response to an unlock command and
+ returns immediately. When the daemon answers regarding the password's
+ validity, calls the callback function with one argument: True if it was
+ correct and False otherwise.
"""
sys.stdout.write(f"password {pw}\n")
sys.stdout.flush()
- # TODO: Don't block
- line = sys.stdin.readline()
- if line == "password incorrect\n":
+
+ def _callback(line):
+ if line == "password incorrect\n":
+ callback(False)
+ elif line == "password correct\n":
+ self.unlock = False
+ self.parse()
+ callback(True)
+ else:
+ sys.stderr.write(f"Unexpected command '{line}'\n")
+ exit(127)
return False
- elif line == "password correct\n":
- self.unlock = False
- self.parse()
- return True
- else:
- sys.stderr.write(f"Unexpected command '{line}'\n")
- exit(127)
+
+ gio_lines_async(self.stream, _callback)
diff --git a/hiprompt_gtk/window.py b/hiprompt_gtk/window.py
index 1f80ae4..4afed53 100644
--- a/hiprompt_gtk/window.py
+++ b/hiprompt_gtk/window.py
@@ -23,6 +23,8 @@ class Window:
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.window.add(self.box)
+ self.deny = None
+ self.grant = None
self.entry = None
self.buttons = []
@@ -63,17 +65,30 @@ class Window:
def on_unlock_grant(self, *args):
password = self.entry.get_text()
+ self.deny.set_sensitive(False)
+ self.grant.set_sensitive(False)
self.entry.set_sensitive(False)
+ self.entry.set_text("Verifying...")
+ self.entry.set_visibility(True)
+ self.window.remove_accel_group(self.accel)
confirmed = len(self.session.keys) > 0
- if self.session.password(password):
- if not self.session.mode or confirmed:
- self.session.status = 0
- Gtk.main_quit()
+
+ def _callback(status):
+ if status:
+ if not self.session.mode or confirmed:
+ self.session.status = 0
+ Gtk.main_quit()
+ else:
+ self.make_ui()
else:
- self.make_ui()
- else:
- self.entry.set_text("")
- self.entry.set_sensitive(True)
+ self.entry.set_text("")
+ self.entry.set_visibility(False)
+ self.entry.set_sensitive(True)
+ self.grant.set_sensitive(True)
+ self.window.add_accel_group(self.accel)
+ self.entry.grab_focus()
+
+ self.session.password(password, _callback)
def on_disclose_grant(self, *args):
self.session.status = 0
@@ -88,16 +103,16 @@ class Window:
self.header.set_title("Unlock keyring")
if len(self.session.keys) == 0:
- deny = Gtk.Button("Cancel")
+ self.deny = Gtk.Button("Cancel")
else:
- deny = Gtk.Button("Deny access")
- deny.connect("clicked", self.on_deny)
- self.header.pack_start(deny)
-
- grant = Gtk.Button("Grant access")
- grant.get_style_context().add_class("destructive-action")
- grant.connect("clicked", self.on_unlock_grant)
- self.header.pack_end(grant)
+ self.deny = Gtk.Button("Deny access")
+ self.deny.connect("clicked", self.on_deny)
+ self.header.pack_start(self.deny)
+
+ self.grant = Gtk.Button("Grant access")
+ self.grant.get_style_context().add_class("destructive-action")
+ self.grant.connect("clicked", self.on_unlock_grant)
+ self.header.pack_end(self.grant)
scroller = Gtk.ScrolledWindow()
scroller.set_propagate_natural_height(True)
--
2.36.2