feat: add sysauth service for Polkit authentication

- Add SysauthService with D-Bus agent registration
- Add AuthDialog with centered overlay password prompt
- Add password utils (PAM verification, keyrings.alt caching)
- Integrate sysauth into main app initialization
- Update WiFi dialog to use sysauth for password prompts
- Add sysauth.css with dialog styling
- Remove standalone WiFi password dialog
This commit is contained in:
sankalp committed 2026-07-26 15:28:09 +05:45
1 parent 7917eea54b
commit eba5827189
11 files changed
+1100 -297

No files matched your search

+277
View File
@@ -0,0 +1,277 @@
"""Fabric-based authentication dialog for sysauth.
Provides a centered overlay password prompt matching the WiFi dialog pattern.
Uses Fabric widgets and integrates with the SysauthService for
Polkit authentication requests.
"""
import threading
from fabric.utils import Gdk, GLib
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.entry import Entry
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.window import Window
class AuthDialog(Window):
"""Centered authentication dialog matching the WiFi password dialog pattern."""
def __init__(self, action_id: str, message: str, icon_name: str):
self._result_accepted = False
self._main_loop = None
super().__init__(
title="modus-dialog",
layer="overlay",
anchor="center",
keyboard_mode="on-demand",
visible=False,
name="sysauth-dialog",
)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
self.set_keep_above(True)
self.set_modal(True)
self.set_skip_taskbar_hint(True)
self.set_skip_pager_hint(True)
self.set_resizable(False)
self._action_id = action_id
self._message = message
self._icon_name = icon_name
self._build_ui()
self._connect_signals()
def _build_ui(self):
"""Construct the dialog UI matching the WiFi dialog layout."""
self._image = Image(
icon_name=self._icon_name,
size=48,
name="sysauth-icon",
)
self._label_title = Label(
label=self._action_id,
name="sysauth-title",
h_align="start",
wrap=True,
max_width_chars=40,
)
self._title_container = Box(
orientation="h",
spacing=8,
children=[self._image, self._label_title],
name="sysauth-title-container",
h_align="start",
)
self._label_description = Label(
label=self._message,
name="sysauth-description",
h_align="start",
wrap=True,
max_width_chars=40,
lines=3,
)
self._error_label = Label(
label="Incorrect password. Please try again.",
name="sysauth-error",
h_align="center",
visible=False,
)
self._password_label = Label(
label="Password:",
name="sysauth-password-label",
h_align="start",
)
self._entry_password = Entry(
placeholder_text="Enter password",
name="sysauth-password-entry",
visibility=False,
h_expand=True,
h_align="fill",
)
self._password_container = Box(
orientation="v",
h_expand=True,
spacing=6,
children=[self._password_label, self._entry_password],
name="sysauth-password-container",
)
self._button_cancel = Button(
label="Cancel",
name="sysauth-cancel-button",
on_clicked=self._on_cancel_clicked,
)
self._button_ok = Button(
label="Authenticate",
name="sysauth-ok-button",
on_clicked=self._on_ok_clicked,
)
self._button_box = Box(
orientation="h",
spacing=12,
h_expand=True,
children=[self._button_cancel, self._button_ok],
name="sysauth-button-box",
h_align="end",
)
self._content_box = Box(
orientation="v",
h_expand=True,
spacing=12,
children=[
self._title_container,
self._label_description,
self._error_label,
self._password_container,
self._button_box,
],
name="sysauth-content",
h_align="fill",
v_align="center",
)
self._dialog_background = Box(
children=[self._content_box],
name="sysauth-dialog-background",
h_align="center",
v_align="center",
)
self.children = self._dialog_background
def _connect_signals(self):
"""Connect button and entry signals."""
self._entry_password.connect("activate", lambda *_: self._on_ok_clicked())
self.connect("key-press-event", self._on_key_press)
self.connect("notify::visible", self._on_visibility_changed)
def run(self) -> bool:
"""Show the dialog and run a nested main loop until dismissed.
Returns:
True if the user accepted (entered password), False if cancelled.
"""
self._main_loop = GLib.MainLoop()
self.show_all()
self._error_label.set_visible(False)
self._entry_password.grab_focus()
self._main_loop.run()
return self._result_accepted
def _on_key_press(self, _widget, event):
"""Handle keyboard input."""
keyval = event.keyval
if keyval == Gdk.KEY_Return or keyval == Gdk.KEY_KP_Enter:
self._on_ok_clicked()
return True
elif keyval == Gdk.KEY_Escape:
self._on_cancel_clicked()
return True
return False
def _on_visibility_changed(self, _widget, *_args):
"""Focus password entry when dialog becomes visible."""
if self.get_visible():
GLib.timeout_add(100, lambda: self._entry_password.grab_focus())
def _on_ok_clicked(self, *_args):
"""Handle OK button press or Enter key."""
password = self._entry_password.get_text().strip()
if not password:
self._entry_password.grab_focus()
return
self._result_accepted = True
self._dismiss()
def _on_cancel_clicked(self, *_args):
"""Handle Cancel button press or Escape key."""
self._result_accepted = False
self._dismiss()
def _dismiss(self):
"""Dismiss the dialog."""
self.hide()
GLib.timeout_add(100, self._quit_main_loop)
def _quit_main_loop(self):
"""Quit the nested main loop."""
if self._main_loop is not None and self._main_loop.is_running():
self._main_loop.quit()
return False
def show_error(self, message: str = "Incorrect password. Please try again."):
"""Show an error message in the dialog."""
self._error_label.set_text(message)
self._error_label.set_visible(True)
self._entry_password.set_text("")
GLib.timeout_add(10, lambda: self._entry_password.grab_focus())
def reset(self):
"""Reset the dialog for reuse."""
self._result_accepted = False
self._error_label.set_visible(False)
self._entry_password.set_text("")
@property
def password(self) -> str:
"""Get the entered password."""
return self._entry_password.get_text()
def run_auth_dialog(
action_id: str,
message: str,
icon_name: str,
on_result=None,
error_message: "str | None" = None,
):
"""Run an authentication dialog in a background thread.
Args:
action_id: The action identifier to display.
message: Description of the action.
icon_name: Icon name for the action.
on_result: Callback(password: str | None) when dialog closes.
error_message: Optional error message to show on first display.
"""
def _run():
dialog = AuthDialog(action_id, message, icon_name)
if error_message:
GLib.idle_add(lambda: dialog.show_error(error_message))
accepted = dialog.run()
def _callback():
if accepted and dialog.password:
on_result(dialog.password)
else:
on_result(None)
return False
GLib.idle_add(_callback)
thread = threading.Thread(target=_run, daemon=True)
thread.start()