From 34dd188a98952d4b7b3191f32166a70e0dadaac1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 30 Apr 2024 22:53:29 +0200 Subject: [PATCH 01/12] Add GTK blueprint for settings --- .gitignore | 3 +- gui/src/gui/main_view.blp | 43 +++ gui/src/gui/settings.blp | 84 +++++ gui/src/gui/settings.py | 593 +++++++++++++++++---------------- gui/src/gui/template_loader.py | 10 + 5 files changed, 449 insertions(+), 284 deletions(-) create mode 100644 gui/src/gui/main_view.blp create mode 100644 gui/src/gui/settings.blp create mode 100644 gui/src/gui/template_loader.py diff --git a/.gitignore b/.gitignore index bab7d6d..add7776 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ __pycache__ .flatpak-builder flatpak-pip-generator repo -__debug* \ No newline at end of file +__debug* +.templates diff --git a/gui/src/gui/main_view.blp b/gui/src/gui/main_view.blp new file mode 100644 index 0000000..e6ea21b --- /dev/null +++ b/gui/src/gui/main_view.blp @@ -0,0 +1,43 @@ +using Gtk 4.0; +using Adw 1; + +Adw.ToolbarView view { + content: Box{ + orientation: vertical; + Adw.Banner paused_banner { + title: 'Backups paused due to Power Saver Mode'; + revealed: false; + } + + ScrolledWindow { + vexpand: true; + hexpand: true; + margin-top: 20; + child: Box content { + orientation: vertical; + spacing: 6; + margin-start: 80; + margin-end: 80; + }; + } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'Backup View'; + }; + + valign: start; + + Button add_button { + label: 'Add Backup'; + + styles [ + "suggested-action", + ] + } + } +} \ No newline at end of file diff --git a/gui/src/gui/settings.blp b/gui/src/gui/settings.blp new file mode 100644 index 0000000..38acfa9 --- /dev/null +++ b/gui/src/gui/settings.blp @@ -0,0 +1,84 @@ +using Gtk 4.0; +using Adw 1; + +Box content { + orientation: vertical; + Adw.Banner paused_banner { + title: 'No pin set, please set it now'; + button-label: 'Set Pin'; + revealed: false; + } + Adw.PreferencesPage preferences_page { + title: "General"; + Adw.PreferencesGroup { + title: "Actions"; + Button quick_access_button { + label: "Quick Access"; + margin-top: 20; + styles [ + "suggested-action" + ] + } + Button login_button { + label: "Login"; + margin-top: 20; + styles [ + "suggested-action" + ] + } + Button pin_button { + label: "Set pin"; + margin-top: 20; + styles [ + "suggested-action" + ] + } + Button unlock_button { + label: "Unlock"; + sensitive: false; + margin-top: 20; + styles [ + "suggested-action" + ] + } + Button logout_button { + label: "Logout"; + margin-top: 20; + styles [ + "destructive-action" + ] + } + LinkButton { + uri: "https://github.com/quexten/goldwarden/wiki/Flatpak-Configuration"; + label: "Help & Wiki"; + + } + } + Adw.PreferencesGroup { + title: "Vault Status"; + Adw.ActionRow { + title: "Vault Status"; + subtitle: "Locked"; + } + Adw.ActionRow { + title: "Last Sync"; + subtitle: "Never"; + icon-name: "emblem-synchronizing-symbolic"; + } + Adw.ActionRow { + title: "Websocked Connected"; + subtitle: "False"; + } + Adw.ActionRow { + title: "Vault Login Entries"; + subtitle: "0"; + icon-name: "dialog-password-symbolic"; + } + Adw.ActionRow { + title: "Vault Notes"; + subtitle: "0"; + icon-name: "emblem-documents-symbolic"; + } + } + } +} diff --git a/gui/src/gui/settings.py b/gui/src/gui/settings.py index 71bab67..2bbf039 100644 --- a/gui/src/gui/settings.py +++ b/gui/src/gui/settings.py @@ -1,312 +1,339 @@ #!/usr/bin/env python3 import sys import gi + gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -import gc from gi.repository import Gtk, Adw, GLib, Gdk, Gio from ..services import goldwarden from threading import Thread +from .template_loader import load_template import subprocess from . import components import os root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir)) -token = sys.stdin.readline() -goldwarden.create_authenticated_connection(None) - -def quickaccess_button_clicked(): - p = subprocess.Popen(["python3", "-m", "src.gui.quickaccess"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) - p.stdin.write(f"{token}\n".encode()) - p.stdin.flush() - -def shortcuts_button_clicked(): - p = subprocess.Popen(["python3", "-m", "src.gui.shortcuts"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) - p.stdin.write(f"{token}\n".encode()) - p.stdin.flush() - -def ssh_button_clicked(): - p = subprocess.Popen(["python3", "-m", "src.gui.ssh"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) - p.stdin.write(f"{token}\n".encode()) - p.stdin.flush() - -def browserbiometrics_button_clicked(): - p = subprocess.Popen(["python3", "-m", "src.gui.browserbiometrics"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) - p.stdin.write(f"{token}\n".encode()) - p.stdin.flush() - -def add_action_row(parent, title, subtitle, icon=None): - row = Adw.ActionRow() - row.set_title(title) - row.set_subtitle(subtitle) - if icon != None: - row.set_icon_name(icon) - parent.add(row) - return row - -class SettingsWinvdow(Gtk.ApplicationWindow): +# token = sys.stdin.readline() +# goldwarden.create_authenticated_connection(None) +# +# def quickaccess_button_clicked(): +# p = subprocess.Popen(["python3", "-m", "src.gui.quickaccess"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) +# if p.stdin != None: +# p.stdin.write(f"{token}\n".encode()) +# p.stdin.flush() +# +# def shortcuts_button_clicked(): +# p = subprocess.Popen(["python3", "-m", "src.gui.shortcuts"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) +# p.stdin.write(f"{token}\n".encode()) +# p.stdin.flush() +# +# def ssh_button_clicked(): +# p = subprocess.Popen(["python3", "-m", "src.gui.ssh"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) +# p.stdin.write(f"{token}\n".encode()) +# p.stdin.flush() +# +# def browserbiometrics_button_clicked(): +# p = subprocess.Popen(["python3", "-m", "src.gui.browserbiometrics"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) +# p.stdin.write(f"{token}\n".encode()) +# p.stdin.flush() +# +# def add_action_row(parent, title, subtitle, icon=None): +# row = Adw.ActionRow() +# row.set_title(title) +# row.set_subtitle(subtitle) +# if icon != None: +# row.set_icon_name(icon) +# parent.add(row) +# return row +# + +class SettingsWindow(Gtk.ApplicationWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - - # vertical box + # # vertical box self.box = Gtk.Box() self.box.set_orientation(Gtk.Orientation.VERTICAL) self.set_child(self.box) - - def set_pin(): - set_pin_thread = Thread(target=goldwarden.enable_pin) - set_pin_thread.start() - - self.banner = Adw.Banner() - self.banner.set_title("No pin set, please set it now") - self.banner.set_button_label("Set Pin") - self.banner.connect("button-clicked", lambda banner: set_pin()) - self.box.append(self.banner) - - self.stack = Gtk.Stack() - self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) - self.box.append(self.stack) - - self.preferences_page = Adw.PreferencesPage() - self.preferences_page.set_title("General") - self.stack.add_named(self.preferences_page, "preferences_page") - - self.action_preferences_group = Adw.PreferencesGroup() - self.action_preferences_group.set_title("Actions") - self.preferences_page.add(self.action_preferences_group) - - self.autotype_button = Gtk.Button() - self.autotype_button.set_label("Quick Access") - self.autotype_button.set_margin_top(10) - - self.autotype_button.connect("clicked", lambda button: quickaccess_button_clicked()) - self.autotype_button.get_style_context().add_class("suggested-action") - self.action_preferences_group.add(self.autotype_button) - - self.login_button = Gtk.Button() - self.login_button.set_label("Login") - self.login_button.connect("clicked", lambda button: show_login()) - self.login_button.set_sensitive(False) - self.login_button.set_margin_top(10) - self.login_button.get_style_context().add_class("suggested-action") - self.action_preferences_group.add(self.login_button) - - self.set_pin_button = Gtk.Button() - self.set_pin_button.set_label("Set Pin") - self.set_pin_button.connect("clicked", lambda button: set_pin()) - self.set_pin_button.set_margin_top(10) - self.set_pin_button.set_sensitive(False) - self.set_pin_button.get_style_context().add_class("suggested-action") - self.action_preferences_group.add(self.set_pin_button) - - self.unlock_button = Gtk.Button() - self.unlock_button.set_label("Unlock") - self.unlock_button.set_margin_top(10) - def unlock_button_clicked(): - action = goldwarden.unlock if self.unlock_button.get_label() == "Unlock" else goldwarden.lock - unlock_thread = Thread(target=action) - unlock_thread.start() - self.unlock_button.connect("clicked", lambda button: unlock_button_clicked()) - # set disabled - self.unlock_button.set_sensitive(False) - self.action_preferences_group.add(self.unlock_button) - - self.logout_button = Gtk.Button() - self.logout_button.set_label("Logout") - self.logout_button.set_margin_top(10) - self.logout_button.connect("clicked", lambda button: goldwarden.purge()) - self.logout_button.get_style_context().add_class("destructive-action") - self.action_preferences_group.add(self.logout_button) - - self.wiki_button = Gtk.LinkButton(uri="https://github.com/quexten/goldwarden/wiki/Flatpak-Configuration") - self.wiki_button.set_label("Help & Wiki") - self.wiki_button.set_margin_top(10) - self.action_preferences_group.add(self.wiki_button) - - self.vault_status_preferences_group = Adw.PreferencesGroup() - self.vault_status_preferences_group.set_title("Vault Status") - self.preferences_page.add(self.vault_status_preferences_group) - - self.status_row = add_action_row(self.vault_status_preferences_group, "Vault Status", "Locked") - - self.vault_status_icon = components.StatusIcon() - self.vault_status_icon.set_icon("dialog-error", "error") - self.status_row.add_prefix(self.vault_status_icon) - - self.last_sync_row = add_action_row(self.vault_status_preferences_group, "Last Sync", "Never", "emblem-synchronizing-symbolic") - self.websocket_connected_row = add_action_row(self.vault_status_preferences_group, "Websocket Connected", "False") - - self.websocket_connected_status_icon = components.StatusIcon() - self.websocket_connected_status_icon.set_icon("dialog-error", "error") - self.websocket_connected_row.add_prefix(self.websocket_connected_status_icon) - - self.login_row = add_action_row(self.vault_status_preferences_group, "Vault Login Entries", "0", "dialog-password-symbolic") - self.notes_row = add_action_row(self.vault_status_preferences_group, "Vault Notes", "0", "emblem-documents-symbolic") - - self.header = Gtk.HeaderBar() - self.set_titlebar(self.header) - - action = Gio.SimpleAction.new("shortcuts", None) - action.connect("activate", lambda action, parameter: shortcuts_button_clicked()) - self.add_action(action) - menu = Gio.Menu.new() - menu.append("Keyboard Shortcuts", "win.shortcuts") - self.popover = Gtk.PopoverMenu() - self.popover.set_menu_model(menu) - - action = Gio.SimpleAction.new("ssh", None) - action.connect("activate", lambda action, parameter: ssh_button_clicked()) - self.add_action(action) - menu.append("SSH Agent", "win.ssh") - - action = Gio.SimpleAction.new("browserbiometrics", None) - action.connect("activate", lambda action, parameter: browserbiometrics_button_clicked()) - self.add_action(action) - menu.append("Browser Biometrics", "win.browserbiometrics") - - self.hamburger = Gtk.MenuButton() - self.hamburger.set_popover(self.popover) - self.hamburger.set_icon_name("open-menu-symbolic") - self.header.pack_start(self.hamburger) - - - def update_labels(): - pin_set = goldwarden.is_pin_enabled() - status = goldwarden.get_vault_status() - print("status", status) - runtimeCfg = goldwarden.get_runtime_config() - - if status != None: - if pin_set: - self.unlock_button.set_sensitive(True) - self.banner.set_revealed(False) - else: - self.unlock_button.set_sensitive(False) - self.banner.set_revealed(True) - logged_in = status["loggedIn"] - if logged_in and not status["locked"]: - self.autotype_button.set_visible(True) - self.login_row.set_sensitive(True) - self.notes_row.set_sensitive(True) - self.websocket_connected_row.set_sensitive(True) - else: - self.autotype_button.set_visible(False) - self.websocket_connected_row.set_sensitive(False) - self.login_row.set_sensitive(False) - self.notes_row.set_sensitive(False) - - locked = status["locked"] - self.login_button.set_sensitive(pin_set and not locked) - self.set_pin_button.set_sensitive(not pin_set or not locked) - self.autotype_button.set_sensitive(not locked) - self.status_row.set_subtitle(str("Logged in" if (logged_in and not locked) else "Logged out") if not locked else "Locked") - if locked or not logged_in: - self.vault_status_icon.set_icon("dialog-warning", "warning") - else: - self.vault_status_icon.set_icon("emblem-default", "ok") - if not logged_in: - self.logout_button.set_sensitive(False) - else: - self.logout_button.set_sensitive(True) - self.login_row.set_subtitle(str(status["loginEntries"])) - self.notes_row.set_subtitle(str(status["noteEntries"])) - self.websocket_connected_row.set_subtitle("Connected" if status["websocketConnected"] else "Disconnected") - if status["websocketConnected"]: - self.websocket_connected_status_icon.set_icon("emblem-default", "ok") - else: - self.websocket_connected_status_icon.set_icon("dialog-error", "error") - self.last_sync_row.set_subtitle(str(status["lastSynced"])) - if status["lastSynced"].startswith("1970") or status["lastSynced"].startswith("1969"): - self.last_sync_row.set_subtitle("Never") - self.unlock_button.set_label("Unlock" if locked else "Lock") - else: - is_daemon_running = goldwarden.is_daemon_running() - if not is_daemon_running: - self.status_row.set_subtitle("Daemon not running") - self.vault_status_icon.set_icon("dialog-error", "error") - - GLib.timeout_add(5000, update_labels) - - GLib.timeout_add(1000, update_labels) + # + # def set_pin(): + # set_pin_thread = Thread(target=goldwarden.enable_pin) + # set_pin_thread.start() + # + # self.banner = Adw.Banner() + # self.banner.set_title("No pin set, please set it now") + # self.banner.set_button_label("Set Pin") + # self.banner.connect("button-clicked", lambda banner: set_pin()) + # self.box.append(self.banner) + # + # self.stack = Gtk.Stack() + # self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) + # self.box.append(self.stack) + # + # self.preferences_page = Adw.PreferencesPage() + # self.preferences_page.set_title("General") + # self.stack.add_named(self.preferences_page, "preferences_page") + # + # self.action_preferences_group = Adw.PreferencesGroup() + # self.action_preferences_group.set_title("Actions") + # self.preferences_page.add(self.action_preferences_group) + # + # self.autotype_button = Gtk.Button() + # self.autotype_button.set_label("Quick Access") + # self.autotype_button.set_margin_top(10) + # + # self.autotype_button.connect("clicked", lambda button: quickaccess_button_clicked()) + # self.autotype_button.get_style_context().add_class("suggested-action") + # self.action_preferences_group.add(self.autotype_button) + # + # self.login_button = Gtk.Button() + # self.login_button.set_label("Login") + # self.login_button.connect("clicked", lambda button: show_login()) + # self.login_button.set_sensitive(False) + # self.login_button.set_margin_top(10) + # self.login_button.get_style_context().add_class("suggested-action") + # self.action_preferences_group.add(self.login_button) + # + # self.set_pin_button = Gtk.Button() + # self.set_pin_button.set_label("Set Pin") + # self.set_pin_button.connect("clicked", lambda button: set_pin()) + # self.set_pin_button.set_margin_top(10) + # self.set_pin_button.set_sensitive(False) + # self.set_pin_button.get_style_context().add_class("suggested-action") + # self.action_preferences_group.add(self.set_pin_button) + # + # self.unlock_button = Gtk.Button() + # self.unlock_button.set_label("Unlock") + # self.unlock_button.set_margin_top(10) + # def unlock_button_clicked(): + # action = goldwarden.unlock if self.unlock_button.get_label() == "Unlock" else goldwarden.lock + # unlock_thread = Thread(target=action) + # unlock_thread.start() + # self.unlock_button.connect("clicked", lambda button: unlock_button_clicked()) + # # set disabled + # self.unlock_button.set_sensitive(False) + # self.action_preferences_group.add(self.unlock_button) + # + # self.logout_button = Gtk.Button() + # self.logout_button.set_label("Logout") + # self.logout_button.set_margin_top(10) + # self.logout_button.connect("clicked", lambda button: goldwarden.purge()) + # self.logout_button.get_style_context().add_class("destructive-action") + # self.action_preferences_group.add(self.logout_button) + # + # self.wiki_button = Gtk.LinkButton(uri="https://github.com/quexten/goldwarden/wiki/Flatpak-Configuration") + # self.wiki_button.set_label("Help & Wiki") + # self.wiki_button.set_margin_top(10) + # self.action_preferences_group.add(self.wiki_button) + # + # self.vault_status_preferences_group = Adw.PreferencesGroup() + # self.vault_status_preferences_group.set_title("Vault Status") + # self.preferences_page.add(self.vault_status_preferences_group) + # + # self.status_row = add_action_row(self.vault_status_preferences_group, "Vault Status", "Locked") + # + # self.vault_status_icon = components.StatusIcon() + # self.vault_status_icon.set_icon("dialog-error", "error") + # self.status_row.add_prefix(self.vault_status_icon) + # + # self.last_sync_row = add_action_row(self.vault_status_preferences_group, "Last Sync", "Never", "emblem-synchronizing-symbolic") + # self.websocket_connected_row = add_action_row(self.vault_status_preferences_group, "Websocket Connected", "False") + # + # self.websocket_connected_status_icon = components.StatusIcon() + # self.websocket_connected_status_icon.set_icon("dialog-error", "error") + # self.websocket_connected_row.add_prefix(self.websocket_connected_status_icon) + # + # self.login_row = add_action_row(self.vault_status_preferences_group, "Vault Login Entries", "0", "dialog-password-symbolic") + # self.notes_row = add_action_row(self.vault_status_preferences_group, "Vault Notes", "0", "emblem-documents-symbolic") + # + # self.header = Gtk.HeaderBar() + # self.set_titlebar(self.header) + # + # action = Gio.SimpleAction.new("shortcuts", None) + # action.connect("activate", lambda action, parameter: shortcuts_button_clicked()) + # self.add_action(action) + # menu = Gio.Menu.new() + # menu.append("Keyboard Shortcuts", "win.shortcuts") + # self.popover = Gtk.PopoverMenu() + # self.popover.set_menu_model(menu) + # + # action = Gio.SimpleAction.new("ssh", None) + # action.connect("activate", lambda action, parameter: ssh_button_clicked()) + # self.add_action(action) + # menu.append("SSH Agent", "win.ssh") + # + # action = Gio.SimpleAction.new("browserbiometrics", None) + # action.connect("activate", lambda action, parameter: browserbiometrics_button_clicked()) + # self.add_action(action) + # menu.append("Browser Biometrics", "win.browserbiometrics") + # + # self.hamburger = Gtk.MenuButton() + # self.hamburger.set_popover(self.popover) + # self.hamburger.set_icon_name("open-menu-symbolic") + # self.header.pack_start(self.hamburger) + # + # + # def update_labels(): + # pin_set = goldwarden.is_pin_enabled() + # status = goldwarden.get_vault_status() + # print("status", status) + # runtimeCfg = goldwarden.get_runtime_config() + # + # if status != None: + # if pin_set: + # self.unlock_button.set_sensitive(True) + # self.banner.set_revealed(False) + # else: + # self.unlock_button.set_sensitive(False) + # self.banner.set_revealed(True) + # logged_in = status["loggedIn"] + # if logged_in and not status["locked"]: + # self.autotype_button.set_visible(True) + # self.login_row.set_sensitive(True) + # self.notes_row.set_sensitive(True) + # self.websocket_connected_row.set_sensitive(True) + # else: + # self.autotype_button.set_visible(False) + # self.websocket_connected_row.set_sensitive(False) + # self.login_row.set_sensitive(False) + # self.notes_row.set_sensitive(False) + # + # locked = status["locked"] + # self.login_button.set_sensitive(pin_set and not locked) + # self.set_pin_button.set_sensitive(not pin_set or not locked) + # self.autotype_button.set_sensitive(not locked) + # self.status_row.set_subtitle(str("Logged in" if (logged_in and not locked) else "Logged out") if not locked else "Locked") + # if locked or not logged_in: + # self.vault_status_icon.set_icon("dialog-warning", "warning") + # else: + # self.vault_status_icon.set_icon("emblem-default", "ok") + # if not logged_in: + # self.logout_button.set_sensitive(False) + # else: + # self.logout_button.set_sensitive(True) + # self.login_row.set_subtitle(str(status["loginEntries"])) + # self.notes_row.set_subtitle(str(status["noteEntries"])) + # self.websocket_connected_row.set_subtitle("Connected" if status["websocketConnected"] else "Disconnected") + # if status["websocketConnected"]: + # self.websocket_connected_status_icon.set_icon("emblem-default", "ok") + # else: + # self.websocket_connected_status_icon.set_icon("dialog-error", "error") + # self.last_sync_row.set_subtitle(str(status["lastSynced"])) + # if status["lastSynced"].startswith("1970") or status["lastSynced"].startswith("1969"): + # self.last_sync_row.set_subtitle("Never") + # self.unlock_button.set_label("Unlock" if locked else "Lock") + # else: + # is_daemon_running = goldwarden.is_daemon_running() + # if not is_daemon_running: + # self.status_row.set_subtitle("Daemon not running") + # self.vault_status_icon.set_icon("dialog-error", "error") + # + # GLib.timeout_add(5000, update_labels) + # + # GLib.timeout_add(1000, update_labels) self.set_default_size(400, 700) self.set_title("Goldwarden") -class MyApp(Adw.Application): + def load(self): + builder = load_template("settings.ui") + self.edit_view = builder.get_object("window") + self.content = builder.get_object("content") + print(self.content) + # self.back_button = builder.get_object("back_button") + # self.back_button.connect("clicked", lambda _: self.navigate_callback("main", None)) + # self.id_row = builder.get_object("id_row") + # self.name_row = builder.get_object("name_row") + # self.rclone_path_row = builder.get_object("rclone_path_row") + # self.password_row = builder.get_object("password_row") + # self.rclone_button = builder.get_object("rclone_row") + # self.rclone_button.connect("activated", lambda _: self.edit_rclone()) + # self.save_button = builder.get_object("save_button") + # self.save_button.connect("clicked", lambda _: self.save()) + # self.remove_button = builder.get_object("remove_button") + # self.remove_button.connect("clicked", lambda _: self.remove()) + print(self.box) + print(self.content) + self.box.append(self.content) + return self.edit_view + + +class GoldwardenSettingsApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) def on_activate(self, app): - self.settings_win = SettingsWinvdow(application=app) + print("on activate") + self.settings_win = SettingsWindow(application=app) self.settings_win.present() - -def show_login(): - dialog = Gtk.Dialog(title="Goldwarden") - - auth_preference_group = Adw.PreferencesGroup() - auth_preference_group.set_title("Authentication") - auth_preference_group.set_margin_top(10) - auth_preference_group.set_margin_bottom(10) - auth_preference_group.set_margin_start(10) - auth_preference_group.set_margin_end(10) - dialog.get_content_area().append(auth_preference_group) - - email_entry = Adw.EntryRow() - email_entry.set_title("Email") - email_entry.set_text("") - auth_preference_group.add(email_entry) - - client_id_entry = Adw.EntryRow() - client_id_entry.set_title("Client ID (optional)") - client_id_entry.set_text("") - auth_preference_group.add(client_id_entry) - - client_secret_entry = Adw.EntryRow() - client_secret_entry.set_title("Client Secret (optional)") - client_secret_entry.set_text("") - auth_preference_group.add(client_secret_entry) - - dialog.add_button("Login", Gtk.ResponseType.OK) - def on_save(res): - if res != Gtk.ResponseType.OK: - return - goldwarden.set_url(url_entry.get_text()) - goldwarden.set_client_id(client_id_entry.get_text()) - goldwarden.set_client_secret(client_secret_entry.get_text()) - def login(): - res = goldwarden.login_with_password(email_entry.get_text(), "password") - def handle_res(): - if res == "ok": - dialog.close() - # elif res == "badpass": - # bad_pass_diag = Gtk.MessageDialog(transient_for=dialog, modal=True, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text="Bad password") - # bad_pass_diag.connect("response", lambda dialog, response: bad_pass_diag.close()) - # bad_pass_diag.present() - GLib.idle_add(handle_res) - - login_thread = Thread(target=login) - login_thread.start() - - preference_group = Adw.PreferencesGroup() - preference_group.set_title("Config") - preference_group.set_margin_top(10) - preference_group.set_margin_bottom(10) - preference_group.set_margin_start(10) - preference_group.set_margin_end(10) - - dialog.get_content_area().append(preference_group) - - url_entry = Adw.EntryRow() - url_entry.set_title("Base Url") - url_entry.set_text("https://vault.bitwarden.com/") - preference_group.add(url_entry) - - #ok response - dialog.connect("response", lambda dialog, response: on_save(response)) - dialog.set_default_size(400, 200) - dialog.set_modal(True) - dialog.present() + self.settings_win.load() + +# def show_login(): +# dialog = Gtk.Dialog(title="Goldwarden") +# +# auth_preference_group = Adw.PreferencesGroup() +# auth_preference_group.set_title("Authentication") +# auth_preference_group.set_margin_top(10) +# auth_preference_group.set_margin_bottom(10) +# auth_preference_group.set_margin_start(10) +# auth_preference_group.set_margin_end(10) +# dialog.get_content_area().append(auth_preference_group) +# +# email_entry = Adw.EntryRow() +# email_entry.set_title("Email") +# email_entry.set_text("") +# auth_preference_group.add(email_entry) +# +# client_id_entry = Adw.EntryRow() +# client_id_entry.set_title("Client ID (optional)") +# client_id_entry.set_text("") +# auth_preference_group.add(client_id_entry) +# +# client_secret_entry = Adw.EntryRow() +# client_secret_entry.set_title("Client Secret (optional)") +# client_secret_entry.set_text("") +# auth_preference_group.add(client_secret_entry) +# +# dialog.add_button("Login", Gtk.ResponseType.OK) +# def on_save(res): +# if res != Gtk.ResponseType.OK: +# return +# goldwarden.set_url(url_entry.get_text()) +# goldwarden.set_client_id(client_id_entry.get_text()) +# goldwarden.set_client_secret(client_secret_entry.get_text()) +# def login(): +# res = goldwarden.login_with_password(email_entry.get_text(), "password") +# def handle_res(): +# if res == "ok": +# dialog.close() +# # elif res == "badpass": +# # bad_pass_diag = Gtk.MessageDialog(transient_for=dialog, modal=True, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text="Bad password") +# # bad_pass_diag.connect("response", lambda dialog, response: bad_pass_diag.close()) +# # bad_pass_diag.present() +# GLib.idle_add(handle_res) +# +# login_thread = Thread(target=login) +# login_thread.start() +# +# preference_group = Adw.PreferencesGroup() +# preference_group.set_title("Config") +# preference_group.set_margin_top(10) +# preference_group.set_margin_bottom(10) +# preference_group.set_margin_start(10) +# preference_group.set_margin_end(10) +# +# dialog.get_content_area().append(preference_group) +# +# url_entry = Adw.EntryRow() +# url_entry.set_title("Base Url") +# url_entry.set_text("https://vault.bitwarden.com/") +# preference_group.add(url_entry) +# +# #ok response +# dialog.connect("response", lambda dialog, response: on_save(response)) +# dialog.set_default_size(400, 200) +# dialog.set_modal(True) +# dialog.present() isflatpak = os.path.exists("/.flatpak-info") pathprefix = "/app/bin/" if isflatpak else "./" @@ -318,5 +345,5 @@ def handle_res(): Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) -app = MyApp(application_id="com.quexten.Goldwarden.settings") -app.run(sys.argv) \ No newline at end of file +app = GoldwardenSettingsApp(application_id="com.quexten.Goldwarden.settings") +app.run(sys.argv) diff --git a/gui/src/gui/template_loader.py b/gui/src/gui/template_loader.py new file mode 100644 index 0000000..675beb0 --- /dev/null +++ b/gui/src/gui/template_loader.py @@ -0,0 +1,10 @@ +import os +from gi.repository import Gtk + +isflatpak = os.path.exists("/.flatpak-info") +pathprefix = "/app/bin/" if isflatpak else "./src/gui/" + +def load_template(path): + builder = Gtk.Builder() + builder.add_from_file(pathprefix + ".templates/" + path) + return builder From 6da52ba6dee73366f4ce32e6dfc93b6b5168b275 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 19:47:04 +0200 Subject: [PATCH 02/12] Initial port to templates --- gui/src/gui/browserbiometrics.blp | 44 ++++ gui/src/gui/browserbiometrics.py | 59 ++--- gui/src/gui/login.blp | 69 ++++++ gui/src/gui/login.py | 49 ++++ gui/src/gui/pinentry.blp | 48 ++++ gui/src/gui/pinentry.py | 78 +++--- gui/src/gui/pinentry_approval.blp | 43 ++++ gui/src/gui/pinentry_approval.py | 79 +++--- gui/src/gui/quickaccess.blp | 54 ++++ gui/src/gui/quickaccess.py | 239 +++++++----------- gui/src/gui/settings.blp | 213 ++++++++++------ gui/src/gui/settings.py | 398 ++++++------------------------ gui/src/gui/shortcuts.blp | 95 +++++++ gui/src/gui/shortcuts.py | 86 ++----- gui/src/gui/ssh.blp | 57 +++++ gui/src/gui/ssh.py | 73 ++---- 16 files changed, 876 insertions(+), 808 deletions(-) create mode 100644 gui/src/gui/browserbiometrics.blp create mode 100644 gui/src/gui/login.blp create mode 100644 gui/src/gui/login.py create mode 100644 gui/src/gui/pinentry.blp create mode 100644 gui/src/gui/pinentry_approval.blp create mode 100644 gui/src/gui/quickaccess.blp create mode 100644 gui/src/gui/shortcuts.blp create mode 100644 gui/src/gui/ssh.blp diff --git a/gui/src/gui/browserbiometrics.blp b/gui/src/gui/browserbiometrics.blp new file mode 100644 index 0000000..af50892 --- /dev/null +++ b/gui/src/gui/browserbiometrics.blp @@ -0,0 +1,44 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Window window { + default-width: 500; + default-height: 500; + + Adw.ToolbarView view { + content: Box{ + orientation: vertical; + ScrolledWindow { + vexpand: true; + hexpand: true; + child: Box content { + orientation: vertical; + Adw.PreferencesPage preferences_page { + title: "General"; + + Adw.PreferencesGroup register_browser_biometrics_group { + title: "Register Browser Biometrics"; + description: "Run the following command in your terminal to set up the browser biometrics integration"; + + Adw.ActionRow setup_command_row { + subtitle: "flatpak run --filesystem=home --command=goldwarden com.quexten.Goldwarden setup browserbiometrics"; + subtitle-selectable: true; + } + } + } + }; + } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'Goldwarden Browser Biometrics Setup'; + }; + + valign: start; + } + } +} \ No newline at end of file diff --git a/gui/src/gui/browserbiometrics.py b/gui/src/gui/browserbiometrics.py index 3199786..289f1b1 100644 --- a/gui/src/gui/browserbiometrics.py +++ b/gui/src/gui/browserbiometrics.py @@ -1,53 +1,32 @@ +#!/usr/bin/env python3 +import sys import gi + gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -import gc -import time -from gi.repository import Gtk, Adw, GLib, Notify, Gdk + +from gi.repository import Gtk, Adw, GLib, Gdk, Gio +from ..services import goldwarden from threading import Thread -import sys -import os +from .template_loader import load_template +import subprocess from . import components +import os -class MyApp(Adw.Application): +class GoldwardenBrowserBiometricsSetupGuideApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) def on_activate(self, app): - self.pinentry_window = MainWindow(application=app) - self.pinentry_window.present() - self.app = app - -class MainWindow(Gtk.ApplicationWindow): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # vertical box - self.box = Gtk.Box() - self.box.set_orientation(Gtk.Orientation.VERTICAL) - self.set_child(self.box) - - self.stack = Gtk.Stack() - self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) - self.box.append(self.stack) - - self.preferences_page = Adw.PreferencesPage() - self.preferences_page.set_title("General") - self.stack.add_named(self.preferences_page, "preferences_page") - - self.register_browser_biometrics_group = Adw.PreferencesGroup() - self.register_browser_biometrics_group.set_title("Register Browser Biometrics") - self.register_browser_biometrics_group.set_description("Run the following command in your terminal to set up the browser biometrics integration") - self.preferences_page.add(self.register_browser_biometrics_group) - - self.setup_command_row = Adw.ActionRow() - self.setup_command_row.set_subtitle("flatpak run --filesystem=home --command=goldwarden com.quexten.Goldwarden setup browserbiometrics") - self.setup_command_row.set_subtitle_selectable(True) - self.register_browser_biometrics_group.add(self.setup_command_row) + self.load() + self.window.present() - self.set_default_size(700, 400) - self.set_title("Goldwarden Browser Biometrics Setup") + def load(self): + builder = load_template("browserbiometrics.ui") + self.window = builder.get_object("window") + self.window.set_application(self) -app = MyApp(application_id="com.quexten.Goldwarden.browserbiometrics") -app.run(sys.argv) \ No newline at end of file +if __name__ == "__main__": + app = GoldwardenBrowserBiometricsSetupGuideApp(application_id="com.quexten.Goldwarden.browserbiometrics") + app.run(sys.argv) \ No newline at end of file diff --git a/gui/src/gui/login.blp b/gui/src/gui/login.blp new file mode 100644 index 0000000..0cc752e --- /dev/null +++ b/gui/src/gui/login.blp @@ -0,0 +1,69 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Window window { + default-width: 400; + default-height: 550; + + Adw.ToolbarView view { + content: Box { + orientation: vertical; + spacing: 6; + + Adw.PreferencesPage preferences_page { + Adw.PreferencesGroup { + title: "Authentication"; + margin-start: 12; + margin-end: 12; + margin-bottom: 12; + Adw.EntryRow email_row { + title: "Email"; + text: ""; + } + Adw.EntryRow client_id_row { + title: "Client ID (optional)"; + text: ""; + } + Adw.EntryRow client_secret_row { + title: "Client Secret (optional)"; + text: ""; + } + } + Adw.PreferencesGroup { + title: "Server"; + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + Adw.EntryRow server_row { + title: "Server URL"; + text: "https://vault.bitwarden.com"; + } + } + Adw.PreferencesGroup { + margin-start: 12; + margin-end: 12; + margin-top: 12; + margin-bottom: 12; + Button login_button { + label: "Login"; + styles [ + "suggested-action" + ] + } + } + } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'Goldwarden Login'; + }; + + valign: start; + } + } +} diff --git a/gui/src/gui/login.py b/gui/src/gui/login.py new file mode 100644 index 0000000..1f82ce0 --- /dev/null +++ b/gui/src/gui/login.py @@ -0,0 +1,49 @@ +import gi +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +import gc +import time +from gi.repository import Gtk, Adw, GLib, Notify, Gdk +from threading import Thread +from .template_loader import load_template +import sys +import os +from ..services import goldwarden + +goldwarden.create_authenticated_connection(None) + +class GoldwardenLoginApp(Adw.Application): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.connect('activate', self.on_activate) + + def on_activate(self, app): + self.load() + self.window.present() + + def load(self): + builder = load_template("login.ui") + self.window = builder.get_object("window") + self.window.set_application(self) + self.email_row = builder.get_object("email_row") + self.client_id_row = builder.get_object("client_id_row") + self.client_secret_row = builder.get_object("client_secret_row") + self.server_row = builder.get_object("server_row") + self.login_button = builder.get_object("login_button") + self.login_button.connect("clicked", lambda x: self.on_login()) + + def on_login(self): + email = self.email_row.get_text() + client_id = self.client_id_row.get_text() + client_secret = self.client_secret_row.get_text() + server = self.server_row.get_text() + goldwarden.set_url(server) + if client_id != "": + goldwarden.set_client_id(client_id) + if client_secret != "": + goldwarden.set_client_secret(client_secret) + goldwarden.login_with_password(email, "") + +if __name__ == "__main__": + app = GoldwardenLoginApp(application_id="com.quexten.Goldwarden.login") + app.run(sys.argv) \ No newline at end of file diff --git a/gui/src/gui/pinentry.blp b/gui/src/gui/pinentry.blp new file mode 100644 index 0000000..236d45c --- /dev/null +++ b/gui/src/gui/pinentry.blp @@ -0,0 +1,48 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Window window { + default-width: 400; + default-height: 700; + + Adw.ToolbarView view { + content: Box { + orientation: vertical; + spacing: 6; + + Label message { + label: 'Placeholder label'; + } + + Entry password_entry { + placeholder-text: "Enter your password"; + visibility: false; + } + + Box button_box { + spacing: 6; + + Button cancel_button { + label: "Cancel"; + hexpand: true; + } + + Button approve_button { + label: "Approve"; + hexpand: true; + } + } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'Goldwarden Pinentry'; + }; + + valign: start; + } + } +} diff --git a/gui/src/gui/pinentry.py b/gui/src/gui/pinentry.py index a1abb0a..8cb8b8c 100644 --- a/gui/src/gui/pinentry.py +++ b/gui/src/gui/pinentry.py @@ -5,65 +5,45 @@ import time from gi.repository import Gtk, Adw, GLib, Notify, Gdk from threading import Thread +from .template_loader import load_template import sys import os -message = sys.stdin.readline() - -class MyApp(Adw.Application): +class GoldwardenPinentryApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) def on_activate(self, app): - self.pinentry_window = MainWindow(application=app) - self.pinentry_window.present() - self.app = app - -class MainWindow(Gtk.ApplicationWindow): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.stack = Gtk.Stack() - self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) - self.set_child(self.stack) - - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - self.stack.add_child(box) - - label = Gtk.Label(label=message) - box.append(label) - - # Create the password entry - self.password_entry = Gtk.Entry() + self.load() + self.window.present() + + def load(self): + builder = load_template("pinentry.ui") + self.window = builder.get_object("window") + self.message_label = builder.get_object("message") + self.message_label.set_label(self.message) + + self.cancel_button = builder.get_object("cancel_button") + self.cancel_button.connect("clicked", self.on_cancel_button_clicked) + self.approve_button = builder.get_object("approve_button") + self.approve_button.connect("clicked", self.on_approve_button_clicked) + + self.password_entry = builder.get_object("password_entry") self.password_entry.set_placeholder_text("Enter your password") - self.password_entry.set_visibility(False) # Hide the password - box.append(self.password_entry) - - # Create a button box for cancel and approve buttons - button_box = Gtk.Box(spacing=6) - box.append(button_box) - # Cancel button - cancel_button = Gtk.Button(label="Cancel") - cancel_button.set_hexpand(True) # Make the button expand horizontally - def on_cancel_button_clicked(button): - print("", flush=True) - os._exit(0) - cancel_button.connect("clicked", on_cancel_button_clicked) - button_box.append(cancel_button) + self.window.set_application(self) - # Approve button - approve_button = Gtk.Button(label="Approve") - approve_button.set_hexpand(True) # Make the button expand horizontally - def on_approve_button_clicked(button): - print(self.password_entry.get_text(), flush=True) - os._exit(0) - approve_button.connect("clicked", on_approve_button_clicked) - button_box.append(approve_button) + def on_approve_button_clicked(self, button): + print(self.password_entry.get_text(), flush=True) + os._exit(0) - self.set_default_size(700, 200) - self.set_title("Goldwarden Pinentry") + def on_cancel_button_clicked(self, button): + print("", flush=True) + os._exit(0) -app = MyApp(application_id="com.quexten.Goldwarden.pinentry") -app.run(sys.argv) \ No newline at end of file +if __name__ == "__main__": + app = GoldwardenPinentryApp(application_id="com.quexten.Goldwarden.pinentry") + message = sys.stdin.readline() + app.message = message + app.run(sys.argv) \ No newline at end of file diff --git a/gui/src/gui/pinentry_approval.blp b/gui/src/gui/pinentry_approval.blp new file mode 100644 index 0000000..96285c4 --- /dev/null +++ b/gui/src/gui/pinentry_approval.blp @@ -0,0 +1,43 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Window window { + default-width: 400; + default-height: 200; + + Adw.ToolbarView view { + content: Box { + orientation: vertical; + spacing: 6; + + Label message { + label: 'Dynamic Message'; + } + + Box button_box { + spacing: 6; + + Button cancel_button { + label: "Cancel"; + hexpand: true; + } + + Button approve_button { + label: "Approve"; + hexpand: true; + } + } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'Goldwarden Approval'; + }; + + valign: start; + } + } +} \ No newline at end of file diff --git a/gui/src/gui/pinentry_approval.py b/gui/src/gui/pinentry_approval.py index 17bd2e2..0ecff53 100644 --- a/gui/src/gui/pinentry_approval.py +++ b/gui/src/gui/pinentry_approval.py @@ -5,59 +5,42 @@ import time from gi.repository import Gtk, Adw, GLib, Notify, Gdk from threading import Thread +from .template_loader import load_template import sys import os -message = sys.stdin.readline() - -class MyApp(Adw.Application): +class GoldwardenPinentryApprovalApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) def on_activate(self, app): - self.pinentry_window = MainWindow(application=app) - self.pinentry_window.present() - self.app = app - -class MainWindow(Gtk.ApplicationWindow): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.stack = Gtk.Stack() - self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) - self.set_child(self.stack) - - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - self.stack.add_child(box) - - label = Gtk.Label(label=message) - box.append(label) - - # Create a button box for cancel and approve buttons - button_box = Gtk.Box(spacing=6) - box.append(button_box) - - # Cancel button - cancel_button = Gtk.Button(label="Cancel") - cancel_button.set_hexpand(True) # Make the button expand horizontally - def on_cancel_button_clicked(button): - print("false", flush=True) - os._exit(0) - cancel_button.connect("clicked", on_cancel_button_clicked) - button_box.append(cancel_button) - - # Approve button - approve_button = Gtk.Button(label="Approve") - approve_button.set_hexpand(True) # Make the button expand horizontally - def on_approve_button_clicked(button): - print("true", flush=True) - os._exit(0) - approve_button.connect("clicked", on_approve_button_clicked) - button_box.append(approve_button) - - self.set_default_size(700, 200) - self.set_title("Goldwarden Approval") - -app = MyApp(application_id="com.quexten.Goldwarden.pinentry") -app.run(sys.argv) \ No newline at end of file + self.load() + self.window.present() + + def load(self): + builder = load_template("pinentry_approval.ui") + self.window = builder.get_object("window") + self.message_label = builder.get_object("message") + self.message_label.set_label(self.message) + + self.cancel_button = builder.get_object("cancel_button") + self.cancel_button.connect("clicked", self.on_cancel_button_clicked) + self.approve_button = builder.get_object("approve_button") + self.approve_button.connect("clicked", self.on_approve_button_clicked) + + self.window.set_application(self) + + def on_approve_button_clicked(self, button): + print("true", flush=True) + os._exit(0) + + def on_cancel_button_clicked(self, button): + print("false", flush=True) + os._exit(0) + +if __name__ == "__main__": + app = GoldwardenPinentryApprovalApp(application_id="com.quexten.Goldwarden.pinentry") + message = sys.stdin.readline() + app.message = message + app.run(sys.argv) \ No newline at end of file diff --git a/gui/src/gui/quickaccess.blp b/gui/src/gui/quickaccess.blp new file mode 100644 index 0000000..688df1b --- /dev/null +++ b/gui/src/gui/quickaccess.blp @@ -0,0 +1,54 @@ +using Gtk 4.0; +using Adw 1; + + +Adw.Window window { + default-width: 400; + default-height: 700; + + Adw.ToolbarView view { + content: Box{ + orientation: vertical; + Box { + orientation: vertical; + + Adw.PreferencesPage preferences_page { + Adw.PreferencesGroup { + Adw.EntryRow search_row { + title: "Search"; + } + } + } + + Adw.StatusPage status_page { + visible: true; + margin-top: 100; + title: "Type to search"; + icon-name: "system-search-symbolic"; + } + + ListBox results_list { + margin-start: 10; + margin-end: 10; + margin-top: 10; + margin-bottom: 10; + visible: false; + styles [ + "boxed-list" + ] + } + } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'QuickAccess'; + }; + + valign: start; + } + } +} \ No newline at end of file diff --git a/gui/src/gui/quickaccess.py b/gui/src/gui/quickaccess.py index f6bc976..6f70b81 100644 --- a/gui/src/gui/quickaccess.py +++ b/gui/src/gui/quickaccess.py @@ -7,172 +7,111 @@ from gi.repository import Gtk, Adw, GLib, Notify, Gdk from ..services import goldwarden from threading import Thread +from .template_loader import load_template import sys import os from ..services import totp Notify.init("Goldwarden") -# read line from stdin -token = sys.stdin.readline() -goldwarden.create_authenticated_connection(token) - -def autotype(text): - goldwarden.autotype(text) - time.sleep(0.1) - os._exit(0) - -def set_clipboard(text): - Gdk.Display.get_clipboard(Gdk.Display.get_default()).set_content( - Gdk.ContentProvider.new_for_value(text) - ) - - def kill(): - time.sleep(0.5) - os._exit(0) - thread = Thread(target=kill) - thread.start() - -class MyApp(Adw.Application): +class GoldwardenQuickAccessApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) + self.logins = [] + self.filtered_logins = [] + self.query = "" self.connect('activate', self.on_activate) - def update_logins(self): - logins = goldwarden.get_vault_logins() - if logins == None: - os._exit(0) - return - self.app.autofill_window.logins = logins - def on_activate(self, app): - self.autofill_window = MainWindow(application=app) - self.autofill_window.logins = [] - self.autofill_window.present() - self.app = app + self.load() + self.window.present() thread = Thread(target=self.update_logins) thread.start() -class MainWindow(Gtk.ApplicationWindow): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.stack = Gtk.Stack() - self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) - self.set_child(self.stack) + def load(self): + builder = load_template("quickaccess.ui") + self.window = builder.get_object("window") + self.results_list = builder.get_object("results_list") + self.status_page = builder.get_object("status_page") + self.text_view = builder.get_object("search_row") + self.text_view.connect("changed", self.on_type) + self.window.set_application(self) + + def update(self): + self.update_list() + self.render_list() + + def autotype(self, text): + goldwarden.autotype(text) + time.sleep(0.1) + os._exit(0) - self.box = Gtk.Box() - self.box.set_orientation(Gtk.Orientation.VERTICAL) - self.stack.add_named(self.box, "box") + def set_clipboard(self, text): + Gdk.Display.get_clipboard(Gdk.Display.get_default()).set_content( + Gdk.ContentProvider.new_for_value(text) + ) - self.text_view = Adw.EntryRow() - self.text_view.set_title("Search") - - def on_type(entry): - if len(entry.get_text()) > 1: - self.results_list.show() - else: - self.results_list.hide() + def kill(): + time.sleep(0.5) + os._exit(0) + thread = Thread(target=kill) + thread.start() + def update_list(self): + if self.query == "": + self.filtered_logins = [] + return + + self.filtered_logins = list(filter(lambda i: self.query.lower() in i["name"].lower(), self.logins)) + + self.starts_with_logins = list(filter(lambda i: i["name"].lower().startswith(self.query.lower()), self.filtered_logins)) + self.other_logins = list(filter(lambda i: i not in self.starts_with_logins, self.filtered_logins)) + self.filtered_logins = self.starts_with_logins + self.other_logins + if len(self.filtered_logins) > 7: + self.filtered_logins = self.filtered_logins[0:7] + + def render_list(self): + if len(self.filtered_logins) > 1: + self.results_list.set_visible(True) while self.results_list.get_first_child() != None: self.results_list.remove(self.results_list.get_first_child()) - - self.filtered_logins = list(filter(lambda i: entry.get_text().lower() in i["name"].lower(), self.logins)) - if len( self.filtered_logins) > 10: - self.filtered_logins = self.filtered_logins[0:10] - self.starts_with_logins = list(filter(lambda i: i["name"].lower().startswith(entry.get_text().lower()), self.logins)) - self.other_logins = list(filter(lambda i: i not in self.starts_with_logins , self.filtered_logins)) - self.filtered_logins = None - - for i in self.starts_with_logins + self.other_logins : - action_row = Adw.ActionRow() - action_row.set_title(i["name"]) - action_row.set_subtitle(i["username"]) - action_row.set_icon_name("dialog-password") - action_row.set_activatable(True) - action_row.password = i["password"] - action_row.username = i["username"] - action_row.uuid = i["uuid"] - action_row.uri = i["uri"] - action_row.totp = i["totp"] - self.results_list.append(action_row) - self.starts_with_logins = None - self.other_logins = None - self.text_view.connect("changed", lambda entry: on_type(entry)) - self.box.append(self.text_view) + self.status_page.set_visible(False) + else: + self.results_list.set_visible(False) + self.status_page.set_visible(True) + + for i in self.filtered_logins: + action_row = Adw.ActionRow() + action_row.set_title(i["name"]) + action_row.set_subtitle(i["username"]) + action_row.set_icon_name("dialog-password") + action_row.set_activatable(True) + action_row.password = i["password"] + action_row.username = i["username"] + action_row.uuid = i["uuid"] + action_row.uri = i["uri"] + action_row.totp = i["totp"] + self.results_list.append(action_row) + self.starts_with_logins = None + self.other_logins = None + + def on_type(self, entry): + search_query = entry.get_text() + self.query = search_query + self.update() - self.results_list = Gtk.ListBox() - # margin' - self.results_list.set_margin_start(10) - self.results_list.set_margin_end(10) - self.results_list.set_margin_top(10) - self.results_list.set_margin_bottom(10) - self.results_list.hide() - - keycont = Gtk.EventControllerKey() - def handle_keypress(controller, keyval, keycode, state, user_data): - ctrl_pressed = state & Gdk.ModifierType.CONTROL_MASK > 0 - alt_pressed = state & Gdk.ModifierType.ALT_MASK > 0 - - if keycode == 9: - os._exit(0) - - if keyval == 65364: - # focus results - if self.results_list.get_first_child() != None: - self.results_list.get_first_child().grab_focus() - self.results_list.select_row(self.results_list.get_first_child()) - - if keyval == 113: - return False - - if keycode == 36: - self.hide() - autotypeThread = Thread(target=autotype, args=(f"{self.results_list.get_selected_row().username}\t{self.results_list.get_selected_row().password}",)) - autotypeThread.start() - if keyval == 112: - print("pass", ctrl_pressed, alt_pressed) - if ctrl_pressed and not alt_pressed: - set_clipboard(self.results_list.get_selected_row().password) - if ctrl_pressed and alt_pressed: - self.hide() - autotypeThread = Thread(target=autotype, args=(self.results_list.get_selected_row().password,)) - autotypeThread.start() - elif keyval == 117: - if ctrl_pressed and not alt_pressed: - set_clipboard(self.results_list.get_selected_row().username) - if ctrl_pressed and alt_pressed: - self.hide() - autotypeThread = Thread(target=autotype, args=(self.results_list.get_selected_row().username,)) - autotypeThread.start() - elif keyval == 118: - if ctrl_pressed and alt_pressed: - environment = goldwarden.get_environment() - if environment == None: - return - item_uri = environment["vault"] + "#/vault?itemId=" + self.results_list.get_selected_row().uuid - Gtk.show_uri(None, item_uri, Gdk.CURRENT_TIME) - elif keyval == 108: - if ctrl_pressed and alt_pressed: - Gtk.show_uri(None, self.results_list.get_selected_row().uri, Gdk.CURRENT_TIME) - elif keyval == 116: - totp_code = totp.totp(self.results_list.get_selected_row().totp) - if ctrl_pressed and not alt_pressed: - set_clipboard(totp_code) - if ctrl_pressed and alt_pressed: - self.hide() - autotypeThread = Thread(target=autotype, args=(totp_code,)) - autotypeThread.start() - elif keyval == 102: - # focus search - self.text_view.grab_focus() - - keycont.connect('key-pressed', handle_keypress, self) - self.add_controller(keycont) - - self.results_list.get_style_context().add_class("boxed-list") - self.box.append(self.results_list) - self.set_default_size(700, 700) - self.set_title("Goldwarden Quick Access") - -app = MyApp(application_id="com.quexten.Goldwarden.autofill-menu") -app.run(sys.argv) + def update_logins(self): + logins = goldwarden.get_vault_logins() + print(logins) + if logins == None: + os._exit(0) + return + self.logins = logins + self.update() + +if __name__ == "__main__": + # todo add proper method to debug this + # token = sys.stdin.readline() + token = "Test" + goldwarden.create_authenticated_connection(token) + app = GoldwardenQuickAccessApp(application_id="com.quexten.Goldwarden.quickaccess") + app.run(sys.argv) \ No newline at end of file diff --git a/gui/src/gui/settings.blp b/gui/src/gui/settings.blp index 38acfa9..77d341c 100644 --- a/gui/src/gui/settings.blp +++ b/gui/src/gui/settings.blp @@ -1,84 +1,145 @@ using Gtk 4.0; using Adw 1; -Box content { - orientation: vertical; - Adw.Banner paused_banner { - title: 'No pin set, please set it now'; - button-label: 'Set Pin'; - revealed: false; - } - Adw.PreferencesPage preferences_page { - title: "General"; - Adw.PreferencesGroup { - title: "Actions"; - Button quick_access_button { - label: "Quick Access"; - margin-top: 20; - styles [ - "suggested-action" - ] - } - Button login_button { - label: "Login"; - margin-top: 20; - styles [ - "suggested-action" - ] - } - Button pin_button { - label: "Set pin"; - margin-top: 20; - styles [ - "suggested-action" - ] - } - Button unlock_button { - label: "Unlock"; - sensitive: false; - margin-top: 20; - styles [ - "suggested-action" - ] - } - Button logout_button { - label: "Logout"; - margin-top: 20; - styles [ - "destructive-action" - ] - } - LinkButton { - uri: "https://github.com/quexten/goldwarden/wiki/Flatpak-Configuration"; - label: "Help & Wiki"; +Adw.Window window { + default-width: 400; + default-height: 700; + + Adw.ToolbarView view { + content: Box{ + orientation: vertical; + + Stack stack { + Box set_pin_status { + orientation: vertical; + visible: false; + Adw.StatusPage { + margin-top: 100; + title: "Pin required"; + icon-name: "dialog-password-symbolic"; + } + Button set_pin_button { + label: "Set pin"; + margin-start: 20; + margin-end: 20; + styles [ + "suggested-action", + "pill" + ] + } + } + + Box unlock_status { + orientation: vertical; + Adw.StatusPage { + margin-top: 100; + title: "Vault locked"; + icon-name: "security-high-symbolic"; + } + Button unlock_button { + label: "Unlock"; + margin-start: 20; + margin-end: 20; + styles [ + "suggested-action", + "pill" + ] + } + } + + Box login_status { + orientation: vertical; + Adw.StatusPage { + margin-top: 100; + title: "Logged out"; + icon-name: "system-users-symbolic"; + } + Button { + label: "Log in"; + margin-start: 20; + margin-end: 20; + styles [ + "suggested-action", + "pill" + ] + } + } + + ScrolledWindow settings_view { + vexpand: true; + hexpand: true; + child: Box content { + orientation: vertical; + Adw.PreferencesPage preferences_page { + title: "General"; + Adw.PreferencesGroup { + title: "Actions"; + Button quickaccess_button { + label: "Quick Access"; + styles [ + "suggested-action" + ] + } + Button update_pin_button { + label: "Update pin"; + margin-top: 20; + styles [ + "suggested-action" + ] + } + Button lock_button { + label: "Lock"; + margin-top: 20; + styles [ + "suggested-action" + ] + } + Button logout_button { + label: "Logout"; + margin-top: 20; + styles [ + "destructive-action" + ] + } + } + Adw.PreferencesGroup { + title: "Vault Status"; + Adw.ActionRow last_sync_row { + title: "Last Sync"; + subtitle: "Never"; + icon-name: "emblem-synchronizing-symbolic"; + } + Adw.ActionRow websocket_connected_row { + title: "Websocked Connected"; + subtitle: "False"; + } + Adw.ActionRow logins_row { + title: "Vault Login Entries"; + subtitle: "0"; + icon-name: "dialog-password-symbolic"; + } + Adw.ActionRow notes_row { + title: "Vault Notes"; + subtitle: "0"; + icon-name: "emblem-documents-symbolic"; + } + } + } + }; + } } - } - Adw.PreferencesGroup { - title: "Vault Status"; - Adw.ActionRow { - title: "Vault Status"; - subtitle: "Locked"; - } - Adw.ActionRow { - title: "Last Sync"; - subtitle: "Never"; - icon-name: "emblem-synchronizing-symbolic"; - } - Adw.ActionRow { - title: "Websocked Connected"; - subtitle: "False"; - } - Adw.ActionRow { - title: "Vault Login Entries"; - subtitle: "0"; - icon-name: "dialog-password-symbolic"; - } - Adw.ActionRow { - title: "Vault Notes"; - subtitle: "0"; - icon-name: "emblem-documents-symbolic"; - } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'Goldwarden'; + }; + + valign: start; } } } diff --git a/gui/src/gui/settings.py b/gui/src/gui/settings.py index 2bbf039..6197850 100644 --- a/gui/src/gui/settings.py +++ b/gui/src/gui/settings.py @@ -13,249 +13,13 @@ from . import components import os -root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir)) -# token = sys.stdin.readline() -# goldwarden.create_authenticated_connection(None) -# -# def quickaccess_button_clicked(): -# p = subprocess.Popen(["python3", "-m", "src.gui.quickaccess"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) -# if p.stdin != None: -# p.stdin.write(f"{token}\n".encode()) -# p.stdin.flush() -# -# def shortcuts_button_clicked(): -# p = subprocess.Popen(["python3", "-m", "src.gui.shortcuts"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) -# p.stdin.write(f"{token}\n".encode()) -# p.stdin.flush() -# -# def ssh_button_clicked(): -# p = subprocess.Popen(["python3", "-m", "src.gui.ssh"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) -# p.stdin.write(f"{token}\n".encode()) -# p.stdin.flush() -# -# def browserbiometrics_button_clicked(): -# p = subprocess.Popen(["python3", "-m", "src.gui.browserbiometrics"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=root_path, start_new_session=True) -# p.stdin.write(f"{token}\n".encode()) -# p.stdin.flush() -# -# def add_action_row(parent, title, subtitle, icon=None): -# row = Adw.ActionRow() -# row.set_title(title) -# row.set_subtitle(subtitle) -# if icon != None: -# row.set_icon_name(icon) -# parent.add(row) -# return row -# - -class SettingsWindow(Gtk.ApplicationWindow): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - # # vertical box - self.box = Gtk.Box() - self.box.set_orientation(Gtk.Orientation.VERTICAL) - self.set_child(self.box) - # - # def set_pin(): - # set_pin_thread = Thread(target=goldwarden.enable_pin) - # set_pin_thread.start() - # - # self.banner = Adw.Banner() - # self.banner.set_title("No pin set, please set it now") - # self.banner.set_button_label("Set Pin") - # self.banner.connect("button-clicked", lambda banner: set_pin()) - # self.box.append(self.banner) - # - # self.stack = Gtk.Stack() - # self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) - # self.box.append(self.stack) - # - # self.preferences_page = Adw.PreferencesPage() - # self.preferences_page.set_title("General") - # self.stack.add_named(self.preferences_page, "preferences_page") - # - # self.action_preferences_group = Adw.PreferencesGroup() - # self.action_preferences_group.set_title("Actions") - # self.preferences_page.add(self.action_preferences_group) - # - # self.autotype_button = Gtk.Button() - # self.autotype_button.set_label("Quick Access") - # self.autotype_button.set_margin_top(10) - # - # self.autotype_button.connect("clicked", lambda button: quickaccess_button_clicked()) - # self.autotype_button.get_style_context().add_class("suggested-action") - # self.action_preferences_group.add(self.autotype_button) - # - # self.login_button = Gtk.Button() - # self.login_button.set_label("Login") - # self.login_button.connect("clicked", lambda button: show_login()) - # self.login_button.set_sensitive(False) - # self.login_button.set_margin_top(10) - # self.login_button.get_style_context().add_class("suggested-action") - # self.action_preferences_group.add(self.login_button) - # - # self.set_pin_button = Gtk.Button() - # self.set_pin_button.set_label("Set Pin") - # self.set_pin_button.connect("clicked", lambda button: set_pin()) - # self.set_pin_button.set_margin_top(10) - # self.set_pin_button.set_sensitive(False) - # self.set_pin_button.get_style_context().add_class("suggested-action") - # self.action_preferences_group.add(self.set_pin_button) - # - # self.unlock_button = Gtk.Button() - # self.unlock_button.set_label("Unlock") - # self.unlock_button.set_margin_top(10) - # def unlock_button_clicked(): - # action = goldwarden.unlock if self.unlock_button.get_label() == "Unlock" else goldwarden.lock - # unlock_thread = Thread(target=action) - # unlock_thread.start() - # self.unlock_button.connect("clicked", lambda button: unlock_button_clicked()) - # # set disabled - # self.unlock_button.set_sensitive(False) - # self.action_preferences_group.add(self.unlock_button) - # - # self.logout_button = Gtk.Button() - # self.logout_button.set_label("Logout") - # self.logout_button.set_margin_top(10) - # self.logout_button.connect("clicked", lambda button: goldwarden.purge()) - # self.logout_button.get_style_context().add_class("destructive-action") - # self.action_preferences_group.add(self.logout_button) - # - # self.wiki_button = Gtk.LinkButton(uri="https://github.com/quexten/goldwarden/wiki/Flatpak-Configuration") - # self.wiki_button.set_label("Help & Wiki") - # self.wiki_button.set_margin_top(10) - # self.action_preferences_group.add(self.wiki_button) - # - # self.vault_status_preferences_group = Adw.PreferencesGroup() - # self.vault_status_preferences_group.set_title("Vault Status") - # self.preferences_page.add(self.vault_status_preferences_group) - # - # self.status_row = add_action_row(self.vault_status_preferences_group, "Vault Status", "Locked") - # - # self.vault_status_icon = components.StatusIcon() - # self.vault_status_icon.set_icon("dialog-error", "error") - # self.status_row.add_prefix(self.vault_status_icon) - # - # self.last_sync_row = add_action_row(self.vault_status_preferences_group, "Last Sync", "Never", "emblem-synchronizing-symbolic") - # self.websocket_connected_row = add_action_row(self.vault_status_preferences_group, "Websocket Connected", "False") - # - # self.websocket_connected_status_icon = components.StatusIcon() - # self.websocket_connected_status_icon.set_icon("dialog-error", "error") - # self.websocket_connected_row.add_prefix(self.websocket_connected_status_icon) - # - # self.login_row = add_action_row(self.vault_status_preferences_group, "Vault Login Entries", "0", "dialog-password-symbolic") - # self.notes_row = add_action_row(self.vault_status_preferences_group, "Vault Notes", "0", "emblem-documents-symbolic") - # - # self.header = Gtk.HeaderBar() - # self.set_titlebar(self.header) - # - # action = Gio.SimpleAction.new("shortcuts", None) - # action.connect("activate", lambda action, parameter: shortcuts_button_clicked()) - # self.add_action(action) - # menu = Gio.Menu.new() - # menu.append("Keyboard Shortcuts", "win.shortcuts") - # self.popover = Gtk.PopoverMenu() - # self.popover.set_menu_model(menu) - # - # action = Gio.SimpleAction.new("ssh", None) - # action.connect("activate", lambda action, parameter: ssh_button_clicked()) - # self.add_action(action) - # menu.append("SSH Agent", "win.ssh") - # - # action = Gio.SimpleAction.new("browserbiometrics", None) - # action.connect("activate", lambda action, parameter: browserbiometrics_button_clicked()) - # self.add_action(action) - # menu.append("Browser Biometrics", "win.browserbiometrics") - # - # self.hamburger = Gtk.MenuButton() - # self.hamburger.set_popover(self.popover) - # self.hamburger.set_icon_name("open-menu-symbolic") - # self.header.pack_start(self.hamburger) - # - # - # def update_labels(): - # pin_set = goldwarden.is_pin_enabled() - # status = goldwarden.get_vault_status() - # print("status", status) - # runtimeCfg = goldwarden.get_runtime_config() - # - # if status != None: - # if pin_set: - # self.unlock_button.set_sensitive(True) - # self.banner.set_revealed(False) - # else: - # self.unlock_button.set_sensitive(False) - # self.banner.set_revealed(True) - # logged_in = status["loggedIn"] - # if logged_in and not status["locked"]: - # self.autotype_button.set_visible(True) - # self.login_row.set_sensitive(True) - # self.notes_row.set_sensitive(True) - # self.websocket_connected_row.set_sensitive(True) - # else: - # self.autotype_button.set_visible(False) - # self.websocket_connected_row.set_sensitive(False) - # self.login_row.set_sensitive(False) - # self.notes_row.set_sensitive(False) - # - # locked = status["locked"] - # self.login_button.set_sensitive(pin_set and not locked) - # self.set_pin_button.set_sensitive(not pin_set or not locked) - # self.autotype_button.set_sensitive(not locked) - # self.status_row.set_subtitle(str("Logged in" if (logged_in and not locked) else "Logged out") if not locked else "Locked") - # if locked or not logged_in: - # self.vault_status_icon.set_icon("dialog-warning", "warning") - # else: - # self.vault_status_icon.set_icon("emblem-default", "ok") - # if not logged_in: - # self.logout_button.set_sensitive(False) - # else: - # self.logout_button.set_sensitive(True) - # self.login_row.set_subtitle(str(status["loginEntries"])) - # self.notes_row.set_subtitle(str(status["noteEntries"])) - # self.websocket_connected_row.set_subtitle("Connected" if status["websocketConnected"] else "Disconnected") - # if status["websocketConnected"]: - # self.websocket_connected_status_icon.set_icon("emblem-default", "ok") - # else: - # self.websocket_connected_status_icon.set_icon("dialog-error", "error") - # self.last_sync_row.set_subtitle(str(status["lastSynced"])) - # if status["lastSynced"].startswith("1970") or status["lastSynced"].startswith("1969"): - # self.last_sync_row.set_subtitle("Never") - # self.unlock_button.set_label("Unlock" if locked else "Lock") - # else: - # is_daemon_running = goldwarden.is_daemon_running() - # if not is_daemon_running: - # self.status_row.set_subtitle("Daemon not running") - # self.vault_status_icon.set_icon("dialog-error", "error") - # - # GLib.timeout_add(5000, update_labels) - # - # GLib.timeout_add(1000, update_labels) - self.set_default_size(400, 700) - self.set_title("Goldwarden") - - def load(self): - builder = load_template("settings.ui") - self.edit_view = builder.get_object("window") - self.content = builder.get_object("content") - print(self.content) - # self.back_button = builder.get_object("back_button") - # self.back_button.connect("clicked", lambda _: self.navigate_callback("main", None)) - # self.id_row = builder.get_object("id_row") - # self.name_row = builder.get_object("name_row") - # self.rclone_path_row = builder.get_object("rclone_path_row") - # self.password_row = builder.get_object("password_row") - # self.rclone_button = builder.get_object("rclone_row") - # self.rclone_button.connect("activated", lambda _: self.edit_rclone()) - # self.save_button = builder.get_object("save_button") - # self.save_button.connect("clicked", lambda _: self.save()) - # self.remove_button = builder.get_object("remove_button") - # self.remove_button.connect("clicked", lambda _: self.remove()) - print(self.box) - print(self.content) - self.box.append(self.content) - return self.edit_view - +def run_window(name, token): + gui_path = os.path.dirname(os.path.realpath(__file__)) + cwd = os.path.abspath(os.path.join(gui_path, os.pardir, os.pardir)) + print(f"Running window {name} with path {cwd}") + p = subprocess.Popen(["python3", "-m", "src.gui." + name], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd, start_new_session=True) + p.stdin.write(f"{token}\n".encode()) + p.stdin.flush() class GoldwardenSettingsApp(Adw.Application): def __init__(self, **kwargs): @@ -263,87 +27,73 @@ def __init__(self, **kwargs): self.connect('activate', self.on_activate) def on_activate(self, app): - print("on activate") - self.settings_win = SettingsWindow(application=app) - self.settings_win.present() - self.settings_win.load() + self.load() + self.window.present() + GLib.timeout_add(100, self.update) + + def load(self): + builder = load_template("settings.ui") + self.window = builder.get_object("window") + self.window.set_application(self) + self.stack = builder.get_object("stack") + + self.set_pin_status_box = builder.get_object("set_pin_status") + self.set_pin_button = builder.get_object("set_pin_button") + self.set_pin_button.connect("clicked", lambda x: goldwarden.enable_pin()) + + self.unlock_status_box = builder.get_object("unlock_status") + self.unlock_button = builder.get_object("unlock_button") + self.unlock_button.connect("clicked", lambda x: goldwarden.unlock()) + self.login_status_box = builder.get_object("login_status") + + self.settings_view = builder.get_object("settings_view") + self.lock_button = builder.get_object("lock_button") + self.lock_button.connect("clicked", lambda x: goldwarden.lock()) + self.logout_button = builder.get_object("logout_button") + self.logout_button.connect("clicked", lambda x: goldwarden.purge()) + self.update_pin_button = builder.get_object("update_pin_button") + self.update_pin_button.connect("clicked", lambda x: goldwarden.enable_pin()) + self.quickaccess_button = builder.get_object("quickaccess_button") + self.quickaccess_button.connect("clicked", lambda x: run_window("quickaccess", "Test")) + self.last_sync_row = builder.get_object("last_sync_row") + self.websocket_connected_row = builder.get_object("websocket_connected_row") + self.logins_row = builder.get_object("logins_row") + self.notes_row = builder.get_object("notes_row") + + def update(self): + self.render() + return True + + def render(self): + pin_set = goldwarden.is_pin_enabled() + status = goldwarden.get_vault_status() + runtimeCfg = goldwarden.get_runtime_config() + if status == None: + is_daemon_running = goldwarden.is_daemon_running() + if not is_daemon_running: + self.status_row.set_subtitle("Daemon not running") + self.vault_status_icon.set_icon("dialog-error", "error") + return -# def show_login(): -# dialog = Gtk.Dialog(title="Goldwarden") -# -# auth_preference_group = Adw.PreferencesGroup() -# auth_preference_group.set_title("Authentication") -# auth_preference_group.set_margin_top(10) -# auth_preference_group.set_margin_bottom(10) -# auth_preference_group.set_margin_start(10) -# auth_preference_group.set_margin_end(10) -# dialog.get_content_area().append(auth_preference_group) -# -# email_entry = Adw.EntryRow() -# email_entry.set_title("Email") -# email_entry.set_text("") -# auth_preference_group.add(email_entry) -# -# client_id_entry = Adw.EntryRow() -# client_id_entry.set_title("Client ID (optional)") -# client_id_entry.set_text("") -# auth_preference_group.add(client_id_entry) -# -# client_secret_entry = Adw.EntryRow() -# client_secret_entry.set_title("Client Secret (optional)") -# client_secret_entry.set_text("") -# auth_preference_group.add(client_secret_entry) -# -# dialog.add_button("Login", Gtk.ResponseType.OK) -# def on_save(res): -# if res != Gtk.ResponseType.OK: -# return -# goldwarden.set_url(url_entry.get_text()) -# goldwarden.set_client_id(client_id_entry.get_text()) -# goldwarden.set_client_secret(client_secret_entry.get_text()) -# def login(): -# res = goldwarden.login_with_password(email_entry.get_text(), "password") -# def handle_res(): -# if res == "ok": -# dialog.close() -# # elif res == "badpass": -# # bad_pass_diag = Gtk.MessageDialog(transient_for=dialog, modal=True, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text="Bad password") -# # bad_pass_diag.connect("response", lambda dialog, response: bad_pass_diag.close()) -# # bad_pass_diag.present() -# GLib.idle_add(handle_res) -# -# login_thread = Thread(target=login) -# login_thread.start() -# -# preference_group = Adw.PreferencesGroup() -# preference_group.set_title("Config") -# preference_group.set_margin_top(10) -# preference_group.set_margin_bottom(10) -# preference_group.set_margin_start(10) -# preference_group.set_margin_end(10) -# -# dialog.get_content_area().append(preference_group) -# -# url_entry = Adw.EntryRow() -# url_entry.set_title("Base Url") -# url_entry.set_text("https://vault.bitwarden.com/") -# preference_group.add(url_entry) -# -# #ok response -# dialog.connect("response", lambda dialog, response: on_save(response)) -# dialog.set_default_size(400, 200) -# dialog.set_modal(True) -# dialog.present() + logged_in = status["loggedIn"] + unlocked = not status["locked"] + if not pin_set: + self.stack.set_visible_child(self.set_pin_status_box) + return + if not unlocked: + self.stack.set_visible_child(self.unlock_status_box) + return + if not logged_in: + self.stack.set_visible_child(self.login_status_box) + return + self.stack.set_visible_child(self.settings_view) -isflatpak = os.path.exists("/.flatpak-info") -pathprefix = "/app/bin/" if isflatpak else "./" -css_provider = Gtk.CssProvider() -css_provider.load_from_path(pathprefix+"style.css") -Gtk.StyleContext.add_provider_for_display( - Gdk.Display.get_default(), - css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION -) + self.last_sync_row.set_subtitle(status["lastSynced"]) + self.websocket_connected_row.set_subtitle("Yes" if status["websocketConnected"] else "No") + self.logins_row.set_subtitle(str(status["loginEntries"])) + self.notes_row.set_subtitle(str(status["noteEntries"])) -app = GoldwardenSettingsApp(application_id="com.quexten.Goldwarden.settings") -app.run(sys.argv) +if __name__ == "__main__": + goldwarden.create_authenticated_connection(None) + app = GoldwardenSettingsApp(application_id="com.quexten.Goldwarden.settings") + app.run(sys.argv) diff --git a/gui/src/gui/shortcuts.blp b/gui/src/gui/shortcuts.blp new file mode 100644 index 0000000..ff3994a --- /dev/null +++ b/gui/src/gui/shortcuts.blp @@ -0,0 +1,95 @@ +using Gtk 4.0; +using Adw 1; + +Adw.Window window { + default-width: 400; + default-height: 700; + + Adw.ToolbarView view { + content: Box{ + orientation: vertical; + Adw.Banner pin_banner { + title: 'No pin set, please set it now'; + button-label: 'Set Pin'; + revealed: false; + } + + ScrolledWindow { + vexpand: true; + hexpand: true; + child: Box content { + orientation: vertical; + Adw.PreferencesPage preferences_page { + title: "General"; + + Adw.PreferencesGroup global_preferences_group { + title: "Global Shortcuts"; + + Adw.ActionRow autofill_row { + title: "Autofill Shortcut"; + subtitle: "Not implemented - check the wiki for manual setup"; + } + } + + Adw.PreferencesGroup quick_access_preferences_group { + title: "Quick Access Shortcuts"; + Adw.ActionRow { + title: "Copy Username Shortcut"; + subtitle: "CTRL + U"; + } + Adw.ActionRow { + title: "Autotype Username Shortcut"; + subtitle: "CTRL + ALT + U"; + } + Adw.ActionRow { + title: "Copy Password Shortcut"; + subtitle: "CTRL + P"; + } + Adw.ActionRow { + title: "Autotype Password Shortcut"; + subtitle: "CTRL + ALT + P"; + } + Adw.ActionRow { + title: "Copy TOTP Shortcut"; + subtitle: "CTRL + T"; + } + Adw.ActionRow { + title: "Autotype TOTP Shortcut"; + subtitle: "CTRL + ALT + T"; + } + Adw.ActionRow { + title: "Launch URI Shortcut"; + subtitle: "CTRL + L"; + } + Adw.ActionRow { + title: "Launch Web Vault Shortcut"; + subtitle: "CTRL + V"; + } + Adw.ActionRow { + title: "Focus Search Shortcut"; + subtitle: "F"; + } + Adw.ActionRow { + title: "Quit Shortcut"; + subtitle: "Esc"; + } + } + } + }; + } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'Settings'; + }; + + valign: start; + } + } +} + + diff --git a/gui/src/gui/shortcuts.py b/gui/src/gui/shortcuts.py index 1cf4e6a..68b824f 100644 --- a/gui/src/gui/shortcuts.py +++ b/gui/src/gui/shortcuts.py @@ -1,80 +1,32 @@ +#!/usr/bin/env python3 +import sys import gi + gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -import gc -import time -from gi.repository import Gtk, Adw, GLib, Notify, Gdk + +from gi.repository import Gtk, Adw, GLib, Gdk, Gio +from ..services import goldwarden from threading import Thread -import sys -import os +from .template_loader import load_template +import subprocess from . import components +import os -def add_action_row(parent, title, subtitle, icon=None): - row = Adw.ActionRow() - row.set_title(title) - row.set_subtitle(subtitle) - if icon != None: - row.set_icon_name(icon) - parent.add(row) - return row - -class MyApp(Adw.Application): +class GoldwardenShortcutsSetupGuideApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) def on_activate(self, app): - self.pinentry_window = MainWindow(application=app) - self.pinentry_window.present() - self.app = app - -class MainWindow(Gtk.ApplicationWindow): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # vertical box - self.box = Gtk.Box() - self.box.set_orientation(Gtk.Orientation.VERTICAL) - self.set_child(self.box) - - self.stack = Gtk.Stack() - self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) - self.box.append(self.stack) - - self.preferences_page = Adw.PreferencesPage() - self.preferences_page.set_title("General") - self.stack.add_named(self.preferences_page, "preferences_page") - - self.global_preferences_group = Adw.PreferencesGroup() - self.global_preferences_group.set_title("Global Shortcuts") - self.preferences_page.add(self.global_preferences_group) - - self.autofill_row = Adw.ActionRow() - self.autofill_row.set_title("Autofill Shortcut") - self.autofill_row.set_subtitle("Not implemented - check the wiki for manual setup") - self.global_preferences_group.add(self.autofill_row) - - self.autofill_icon = components.StatusIcon() - self.autofill_icon.set_icon("dialog-warning", "warning") - self.autofill_row.add_prefix(self.autofill_icon) - - self.quickaccess_preferences_group = Adw.PreferencesGroup() - self.quickaccess_preferences_group.set_title("Quick Access Shortcuts") - self.preferences_page.add(self.quickaccess_preferences_group) - - add_action_row(self.quickaccess_preferences_group, "Copy Username Shortcut", "CTRL + U") - add_action_row(self.quickaccess_preferences_group, "Autotype Username Shortcut", "CTRL + ALT + U") - add_action_row(self.quickaccess_preferences_group, "Copy Password Shortcut", "CTRL + P") - add_action_row(self.quickaccess_preferences_group, "Autotype Password Shortcut", "CTRL + ALT + P") - add_action_row(self.quickaccess_preferences_group, "Copy TOTP Shortcut", "CTRL + T") - add_action_row(self.quickaccess_preferences_group, "Autotype TOTP Shortcut", "CTRL + ALT + T") - add_action_row(self.quickaccess_preferences_group, "Launch URI Shortcut", "CTRL+L") - add_action_row(self.quickaccess_preferences_group, "Launch Web Vault Shortcut", "CTRL+V") - add_action_row(self.quickaccess_preferences_group, "Focus Search Shortcut", "F") - add_action_row(self.quickaccess_preferences_group, "Quit Shortcut", "Esc") + self.load() + self.window.present() - self.set_default_size(700, 700) - self.set_title("Goldwarden Shortcuts") + def load(self): + builder = load_template("shortcuts.ui") + self.window = builder.get_object("window") + self.window.set_application(self) -app = MyApp(application_id="com.quexten.Goldwarden.shortcuts") -app.run(sys.argv) \ No newline at end of file +if __name__ == "__main__": + app = GoldwardenShortcutsSetupGuideApp(application_id="com.quexten.Goldwarden.shortcuts") + app.run(sys.argv) \ No newline at end of file diff --git a/gui/src/gui/ssh.blp b/gui/src/gui/ssh.blp new file mode 100644 index 0000000..9233813 --- /dev/null +++ b/gui/src/gui/ssh.blp @@ -0,0 +1,57 @@ +using Gtk 4.0; +using Adw 1; + + +Adw.Window window { + default-width: 400; + default-height: 700; + + Adw.ToolbarView view { + content: Box{ + orientation: vertical; + ScrolledWindow { + vexpand: true; + hexpand: true; + child: Box content { + orientation: vertical; + Adw.PreferencesPage { + title: "General"; + + Adw.PreferencesGroup add_ssh_key_group { + title: "Add an SSH Key"; + Adw.ActionRow add_ssh_key_row { + subtitle: "flatpak run --command=goldwarden com.quexten.Goldwarden ssh add --name MY_KEY_NAME"; + subtitle-selectable: true; + } + } + + Adw.PreferencesGroup ssh_socket_path_group { + title: "SSH Socket Path"; + description: "Add this to your environment variables"; + Adw.ActionRow ssh_socket_path_row { + subtitle: "export SSH_AUTH_SOCK=/home/$USER/.var/app/com.quexten.Goldwarden/data/ssh-auth-sock"; + subtitle-selectable: true; + } + } + + Adw.PreferencesGroup git_signing_group { + title: "Git Signing"; + description: "Check the wiki for more information"; + } + } + }; + } + }; + + [top] + Adw.HeaderBar { + halign: baseline; + + title-widget: Adw.WindowTitle { + title: 'SSH Configuration'; + }; + + valign: start; + } + } +} \ No newline at end of file diff --git a/gui/src/gui/ssh.py b/gui/src/gui/ssh.py index a76a3ac..3681997 100644 --- a/gui/src/gui/ssh.py +++ b/gui/src/gui/ssh.py @@ -1,67 +1,32 @@ +#!/usr/bin/env python3 +import sys import gi + gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') -import gc -import time -from gi.repository import Gtk, Adw, GLib, Notify, Gdk + +from gi.repository import Gtk, Adw, GLib, Gdk, Gio +from ..services import goldwarden from threading import Thread -import sys -import os +from .template_loader import load_template +import subprocess from . import components +import os -class MyApp(Adw.Application): +class GoldwardenSSHSetupGuideApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) def on_activate(self, app): - self.pinentry_window = MainWindow(application=app) - self.pinentry_window.present() - self.app = app - -class MainWindow(Gtk.ApplicationWindow): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # vertical box - self.box = Gtk.Box() - self.box.set_orientation(Gtk.Orientation.VERTICAL) - self.set_child(self.box) - - self.stack = Gtk.Stack() - self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT) - self.box.append(self.stack) - - self.preferences_page = Adw.PreferencesPage() - self.preferences_page.set_title("General") - self.stack.add_named(self.preferences_page, "preferences_page") - - self.add_ssh_key_group = Adw.PreferencesGroup() - self.add_ssh_key_group.set_title("Add an SSH Key") - self.preferences_page.add(self.add_ssh_key_group) - - self.add_ssh_key_row = Adw.ActionRow() - self.add_ssh_key_row.set_subtitle("flatpak run --command=goldwarden com.quexten.Goldwarden ssh add --name MY_KEY_NAME") - self.add_ssh_key_row.set_subtitle_selectable(True) - self.add_ssh_key_group.add(self.add_ssh_key_row) - - self.ssh_socket_path_group = Adw.PreferencesGroup() - self.ssh_socket_path_group.set_title("SSH Socket Path") - self.ssh_socket_path_group.set_description("Add this to your environment variables") - self.preferences_page.add(self.ssh_socket_path_group) - - self.ssh_socket_path_row = Adw.ActionRow() - self.ssh_socket_path_row.set_subtitle("export SSH_AUTH_SOCK=/home/$USER/.var/app/com.quexten.Goldwarden/data/ssh-auth-sock") - self.ssh_socket_path_row.set_subtitle_selectable(True) - self.ssh_socket_path_group.add(self.ssh_socket_path_row) - - self.git_signing_group = Adw.PreferencesGroup() - self.git_signing_group.set_title("Git Signing") - self.git_signing_group.set_description("Check the wiki for more information") - self.preferences_page.add(self.git_signing_group) + self.load() + self.window.present() - self.set_default_size(400, 700) - self.set_title("Goldwarden SSH Setup") + def load(self): + builder = load_template("ssh.ui") + self.window = builder.get_object("window") + self.window.set_application(self) -app = MyApp(application_id="com.quexten.Goldwarden.sshsetup") -app.run(sys.argv) \ No newline at end of file +if __name__ == "__main__": + app = GoldwardenSSHSetupGuideApp(application_id="com.quexten.Goldwarden.sshsetup") + app.run(sys.argv) \ No newline at end of file From f7e76056d77b8332b70c7693f75b7361af62591c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 20:02:25 +0200 Subject: [PATCH 03/12] Fix login screen --- gui/src/gui/settings.blp | 3 +-- gui/src/gui/settings.py | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gui/src/gui/settings.blp b/gui/src/gui/settings.blp index 77d341c..ac02d93 100644 --- a/gui/src/gui/settings.blp +++ b/gui/src/gui/settings.blp @@ -13,7 +13,6 @@ Adw.Window window { Stack stack { Box set_pin_status { orientation: vertical; - visible: false; Adw.StatusPage { margin-top: 100; title: "Pin required"; @@ -55,7 +54,7 @@ Adw.Window window { title: "Logged out"; icon-name: "system-users-symbolic"; } - Button { + Button login_button { label: "Log in"; margin-start: 20; margin-end: 20; diff --git a/gui/src/gui/settings.py b/gui/src/gui/settings.py index 6197850..dd7b3ac 100644 --- a/gui/src/gui/settings.py +++ b/gui/src/gui/settings.py @@ -28,6 +28,7 @@ def __init__(self, **kwargs): def on_activate(self, app): self.load() + self.update() self.window.present() GLib.timeout_add(100, self.update) @@ -44,7 +45,10 @@ def load(self): self.unlock_status_box = builder.get_object("unlock_status") self.unlock_button = builder.get_object("unlock_button") self.unlock_button.connect("clicked", lambda x: goldwarden.unlock()) + self.login_status_box = builder.get_object("login_status") + self.login_button = builder.get_object("login_button") + self.login_button.connect("clicked", lambda x: run_window("login", "Test")) self.settings_view = builder.get_object("settings_view") self.lock_button = builder.get_object("lock_button") From dde49f6a842a8954a03108563551c240ec6683dd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 20:38:26 +0200 Subject: [PATCH 04/12] Fix hamburger menu and add about --- gui/src/gui/settings.blp | 7 ++++++- gui/src/gui/settings.py | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/gui/src/gui/settings.blp b/gui/src/gui/settings.blp index ac02d93..ba2584a 100644 --- a/gui/src/gui/settings.blp +++ b/gui/src/gui/settings.blp @@ -2,7 +2,7 @@ using Gtk 4.0; using Adw 1; -Adw.Window window { +Adw.ApplicationWindow window { default-width: 400; default-height: 700; @@ -138,6 +138,11 @@ Adw.Window window { title: 'Goldwarden'; }; + [end] + MenuButton menu_button { + icon-name: "open-menu-symbolic"; + } + valign: start; } } diff --git a/gui/src/gui/settings.py b/gui/src/gui/settings.py index dd7b3ac..92d4ce0 100644 --- a/gui/src/gui/settings.py +++ b/gui/src/gui/settings.py @@ -26,6 +26,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) + def on_activate(self, app): self.load() self.update() @@ -63,6 +64,32 @@ def load(self): self.websocket_connected_row = builder.get_object("websocket_connected_row") self.logins_row = builder.get_object("logins_row") self.notes_row = builder.get_object("notes_row") + + self.menu_button = builder.get_object("menu_button") + menu = Gio.Menu.new() + self.popover = Gtk.PopoverMenu() + self.popover.set_menu_model(menu) + self.menu_button.set_popover(self.popover) + + action = Gio.SimpleAction.new("shortcuts", None) + action.connect("activate", lambda action, parameter: run_window("shortcuts", "Test")) + self.window.add_action(action) + menu.append("Keyboard Shortcuts", "win.shortcuts") + + action = Gio.SimpleAction.new("ssh", None) + action.connect("activate", lambda action, parameter: run_window("ssh", "Test")) + self.window.add_action(action) + menu.append("SSH Agent", "win.ssh") + + action = Gio.SimpleAction.new("browserbiometrics", None) + action.connect("activate", lambda action, parameter: run_window("browserbiometrics", "Test")) + self.window.add_action(action) + menu.append("Browser Biometrics", "win.browserbiometrics") + + action = Gio.SimpleAction.new("about", None) + action.connect("activate", lambda action, parameter: self.show_about()) + self.window.add_action(action) + menu.append("About", "win.about") def update(self): self.render() @@ -97,6 +124,21 @@ def render(self): self.logins_row.set_subtitle(str(status["loginEntries"])) self.notes_row.set_subtitle(str(status["noteEntries"])) + def show_about(self): + dialog = Adw.AboutWindow(transient_for=app.get_active_window()) + dialog.set_application_name("Goldwarden") + dialog.set_version("dev") + dialog.set_developer_name("Bernd Schoolmann (Quexten)") + dialog.set_license_type(Gtk.License(Gtk.License.MIT_X11)) + dialog.set_comments("A Bitwarden compatible password manager") + dialog.set_website("https://github.com/quexten/goldwarden") + dialog.set_issue_url("https://github.com/quexten/goldwarden/issues") + dialog.add_credit_section("Contributors", ["Bernd Schoolmann"]) + dialog.set_copyright("© 2024 Bernd Schoolmann") + dialog.set_developers(["Bernd Schoolmann"]) + dialog.set_application_icon("com.quexten.Goldwarden") + dialog.set_visible(True) + if __name__ == "__main__": goldwarden.create_authenticated_connection(None) app = GoldwardenSettingsApp(application_id="com.quexten.Goldwarden.settings") From a1ab7b006971e87c1786b76cc5ca4331eb248dd7 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 20:41:34 +0200 Subject: [PATCH 05/12] Use proper token --- gui/src/gui/settings.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/gui/src/gui/settings.py b/gui/src/gui/settings.py index 92d4ce0..4c54ae0 100644 --- a/gui/src/gui/settings.py +++ b/gui/src/gui/settings.py @@ -10,7 +10,6 @@ from threading import Thread from .template_loader import load_template import subprocess -from . import components import os def run_window(name, token): @@ -20,13 +19,11 @@ def run_window(name, token): p = subprocess.Popen(["python3", "-m", "src.gui." + name], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd, start_new_session=True) p.stdin.write(f"{token}\n".encode()) p.stdin.flush() - class GoldwardenSettingsApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) self.connect('activate', self.on_activate) - def on_activate(self, app): self.load() self.update() @@ -49,7 +46,7 @@ def load(self): self.login_status_box = builder.get_object("login_status") self.login_button = builder.get_object("login_button") - self.login_button.connect("clicked", lambda x: run_window("login", "Test")) + self.login_button.connect("clicked", lambda x: run_window("login", self.token)) self.settings_view = builder.get_object("settings_view") self.lock_button = builder.get_object("lock_button") @@ -59,7 +56,7 @@ def load(self): self.update_pin_button = builder.get_object("update_pin_button") self.update_pin_button.connect("clicked", lambda x: goldwarden.enable_pin()) self.quickaccess_button = builder.get_object("quickaccess_button") - self.quickaccess_button.connect("clicked", lambda x: run_window("quickaccess", "Test")) + self.quickaccess_button.connect("clicked", lambda x: run_window("quickaccess", self.token)) self.last_sync_row = builder.get_object("last_sync_row") self.websocket_connected_row = builder.get_object("websocket_connected_row") self.logins_row = builder.get_object("logins_row") @@ -72,17 +69,17 @@ def load(self): self.menu_button.set_popover(self.popover) action = Gio.SimpleAction.new("shortcuts", None) - action.connect("activate", lambda action, parameter: run_window("shortcuts", "Test")) + action.connect("activate", lambda action, parameter: run_window("shortcuts", self.token)) self.window.add_action(action) menu.append("Keyboard Shortcuts", "win.shortcuts") action = Gio.SimpleAction.new("ssh", None) - action.connect("activate", lambda action, parameter: run_window("ssh", "Test")) + action.connect("activate", lambda action, parameter: run_window("ssh", self.token)) self.window.add_action(action) menu.append("SSH Agent", "win.ssh") action = Gio.SimpleAction.new("browserbiometrics", None) - action.connect("activate", lambda action, parameter: run_window("browserbiometrics", "Test")) + action.connect("activate", lambda action, parameter: run_window("browserbiometrics", self.token)) self.window.add_action(action) menu.append("Browser Biometrics", "win.browserbiometrics") @@ -140,6 +137,10 @@ def show_about(self): dialog.set_visible(True) if __name__ == "__main__": + # read from stdin + token = sys.stdin.readline().strip() + goldwarden.create_authenticated_connection(None) app = GoldwardenSettingsApp(application_id="com.quexten.Goldwarden.settings") + app.token = token app.run(sys.argv) From 8a8a2c10a6a04630ac0f46ad247b05db18fab9eb Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 20:52:16 +0200 Subject: [PATCH 06/12] Fix quickaccess token passing --- gui/src/gui/quickaccess.py | 5 +---- gui/src/gui/settings.py | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gui/src/gui/quickaccess.py b/gui/src/gui/quickaccess.py index 6f70b81..642a8ea 100644 --- a/gui/src/gui/quickaccess.py +++ b/gui/src/gui/quickaccess.py @@ -101,7 +101,6 @@ def on_type(self, entry): def update_logins(self): logins = goldwarden.get_vault_logins() - print(logins) if logins == None: os._exit(0) return @@ -109,9 +108,7 @@ def update_logins(self): self.update() if __name__ == "__main__": - # todo add proper method to debug this - # token = sys.stdin.readline() - token = "Test" + token = sys.stdin.readline() goldwarden.create_authenticated_connection(token) app = GoldwardenQuickAccessApp(application_id="com.quexten.Goldwarden.quickaccess") app.run(sys.argv) \ No newline at end of file diff --git a/gui/src/gui/settings.py b/gui/src/gui/settings.py index 4c54ae0..d133422 100644 --- a/gui/src/gui/settings.py +++ b/gui/src/gui/settings.py @@ -19,6 +19,8 @@ def run_window(name, token): p = subprocess.Popen(["python3", "-m", "src.gui." + name], stdin=subprocess.PIPE, stdout=subprocess.PIPE, cwd=cwd, start_new_session=True) p.stdin.write(f"{token}\n".encode()) p.stdin.flush() + + class GoldwardenSettingsApp(Adw.Application): def __init__(self, **kwargs): super().__init__(**kwargs) From 70f6a47fd56c731a3d9e808f2187c71152314fd5 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 21:10:14 +0200 Subject: [PATCH 07/12] Disabel error bell --- gui/src/gui/login.py | 3 +++ gui/src/gui/quickaccess.py | 3 +++ gui/src/gui/settings.py | 4 +++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gui/src/gui/login.py b/gui/src/gui/login.py index 1f82ce0..9dfd00b 100644 --- a/gui/src/gui/login.py +++ b/gui/src/gui/login.py @@ -45,5 +45,8 @@ def on_login(self): goldwarden.login_with_password(email, "") if __name__ == "__main__": + settings = Gtk.Settings.get_default() + settings.set_property("gtk-error-bell", False) + app = GoldwardenLoginApp(application_id="com.quexten.Goldwarden.login") app.run(sys.argv) \ No newline at end of file diff --git a/gui/src/gui/quickaccess.py b/gui/src/gui/quickaccess.py index 642a8ea..fd39429 100644 --- a/gui/src/gui/quickaccess.py +++ b/gui/src/gui/quickaccess.py @@ -108,6 +108,9 @@ def update_logins(self): self.update() if __name__ == "__main__": + settings = Gtk.Settings.get_default() + settings.set_property("gtk-error-bell", False) + token = sys.stdin.readline() goldwarden.create_authenticated_connection(token) app = GoldwardenQuickAccessApp(application_id="com.quexten.Goldwarden.quickaccess") diff --git a/gui/src/gui/settings.py b/gui/src/gui/settings.py index d133422..69ada13 100644 --- a/gui/src/gui/settings.py +++ b/gui/src/gui/settings.py @@ -139,7 +139,9 @@ def show_about(self): dialog.set_visible(True) if __name__ == "__main__": - # read from stdin + settings = Gtk.Settings.get_default() + settings.set_property("gtk-error-bell", False) + token = sys.stdin.readline().strip() goldwarden.create_authenticated_connection(None) From e71622e2365ce8bbbeddfb06ab1dc9e59cb6e7ab Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 21:47:05 +0200 Subject: [PATCH 08/12] Fix totp parsing --- gui/src/services/totp.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gui/src/services/totp.py b/gui/src/services/totp.py index ff3fa5b..9893bd8 100644 --- a/gui/src/services/totp.py +++ b/gui/src/services/totp.py @@ -17,4 +17,8 @@ def hotp(key, counter, digits=6, digest='sha1'): def totp(key, time_step=30, digits=6, digest='sha1'): + if key.startswith('otpauth://'): + key = key.split('secret=')[1].split('&')[0] + key = key.replace(' ', '') + key = key.strip() return hotp(key, int(time.time() / time_step), digits, digest) \ No newline at end of file From 171d833a83d300fd8d1cbf1521042c08cb30a45d Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 21:47:22 +0200 Subject: [PATCH 09/12] Add keyboard controller --- gui/src/gui/quickaccess.py | 81 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/gui/src/gui/quickaccess.py b/gui/src/gui/quickaccess.py index fd39429..62e35af 100644 --- a/gui/src/gui/quickaccess.py +++ b/gui/src/gui/quickaccess.py @@ -20,6 +20,7 @@ def __init__(self, **kwargs): self.filtered_logins = [] self.query = "" self.connect('activate', self.on_activate) + self.selected_index = 0 def on_activate(self, app): self.load() @@ -36,14 +37,82 @@ def load(self): self.text_view.connect("changed", self.on_type) self.window.set_application(self) + evk = Gtk.EventControllerKey.new() + evk.set_propagation_phase(Gtk.PropagationPhase.CAPTURE) + evk.connect("key-pressed", self.key_press) + self.window.add_controller(evk) + + def key_press(self, event, keyval, keycode, state): + print(keyval, keycode) + + if keyval == Gdk.KEY_Escape: + os._exit(0) + + if keyval == Gdk.KEY_Tab: + return True + + if keyval == Gdk.KEY_Up: + self.selected_index = self.selected_index - 1 + if self.selected_index < 0: + self.selected_index = 0 + self.render_list() + return True + elif keyval == Gdk.KEY_Down: + self.selected_index = self.selected_index + 1 + if self.selected_index >= len(self.filtered_logins): + self.selected_index = len(self.filtered_logins) - 1 + self.render_list() + return True + + if self.selected_index >= len(self.filtered_logins) or self.selected_index < 0: + self.selected_index = 0 + + auto_type_combo = state & Gdk.ModifierType.CONTROL_MASK and state & Gdk.ModifierType.SHIFT_MASK + copy_combo = state & Gdk.ModifierType.CONTROL_MASK and not state & Gdk.ModifierType.SHIFT_MASK + + # totp code + if keyval == Gdk.KEY_t or keyval == Gdk.KEY_T: + if auto_type_combo: + print("TOTP type") + self.autotype(totp.totp(self.filtered_logins[self.selected_index]["totp"])) + if copy_combo: + print("TOTP copy") + self.set_clipboard(totp.totp(self.filtered_logins[self.selected_index]["totp"])) + + if keyval == Gdk.KEY_u or keyval == Gdk.KEY_U: + if auto_type_combo: + print("Username type") + self.autotype(self.filtered_logins[self.selected_index]["username"]) + if copy_combo: + print("Username copy") + self.set_clipboard(self.filtered_logins[self.selected_index]["username"]) + + if keyval == Gdk.KEY_p or keyval == Gdk.KEY_P: + if auto_type_combo: + print("Password type") + self.autotype(self.filtered_logins[self.selected_index]["password"]) + if copy_combo: + print("Password copy") + self.set_clipboard(self.filtered_logins[self.selected_index]["password"]) + + if keyval == Gdk.KEY_Return: + if auto_type_combo: + self.autotype(f"{self.filtered_logins[self.selected_index]['username']}\t{self.filtered_logins[self.selected_index]['password']}") + def update(self): self.update_list() self.render_list() + return True def autotype(self, text): - goldwarden.autotype(text) - time.sleep(0.1) - os._exit(0) + def perform_autotype(text): + self.window.hide() + time.sleep(0.1) + goldwarden.autotype(text) + time.sleep(0.1) + os._exit(0) + thread = Thread(target=perform_autotype, args=(text,)) + thread.start() def set_clipboard(self, text): Gdk.Display.get_clipboard(Gdk.Display.get_default()).set_content( @@ -91,6 +160,12 @@ def render_list(self): action_row.uri = i["uri"] action_row.totp = i["totp"] self.results_list.append(action_row) + + # select the nth item + if len(self.filtered_logins) > 0: + self.results_list.select_row(self.results_list.get_row_at_index(self.selected_index)) + self.results_list.set_focus_child(self.results_list.get_row_at_index(self.selected_index)) + self.starts_with_logins = None self.other_logins = None From 6e983b6b7a0f6b3662a6aa24c39d9c5297978728 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 21:54:04 +0200 Subject: [PATCH 10/12] Vault and uri shortcuts --- gui/src/gui/quickaccess.py | 19 +++++++++++-------- gui/src/gui/shortcuts.blp | 14 +++++--------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/gui/src/gui/quickaccess.py b/gui/src/gui/quickaccess.py index 62e35af..9ed95e7 100644 --- a/gui/src/gui/quickaccess.py +++ b/gui/src/gui/quickaccess.py @@ -43,8 +43,6 @@ def load(self): self.window.add_controller(evk) def key_press(self, event, keyval, keycode, state): - print(keyval, keycode) - if keyval == Gdk.KEY_Escape: os._exit(0) @@ -73,28 +71,33 @@ def key_press(self, event, keyval, keycode, state): # totp code if keyval == Gdk.KEY_t or keyval == Gdk.KEY_T: if auto_type_combo: - print("TOTP type") self.autotype(totp.totp(self.filtered_logins[self.selected_index]["totp"])) if copy_combo: - print("TOTP copy") self.set_clipboard(totp.totp(self.filtered_logins[self.selected_index]["totp"])) if keyval == Gdk.KEY_u or keyval == Gdk.KEY_U: if auto_type_combo: - print("Username type") self.autotype(self.filtered_logins[self.selected_index]["username"]) if copy_combo: - print("Username copy") self.set_clipboard(self.filtered_logins[self.selected_index]["username"]) if keyval == Gdk.KEY_p or keyval == Gdk.KEY_P: if auto_type_combo: - print("Password type") self.autotype(self.filtered_logins[self.selected_index]["password"]) if copy_combo: - print("Password copy") self.set_clipboard(self.filtered_logins[self.selected_index]["password"]) + if (keyval == Gdk.KEY_l or keyval == Gdk.KEY_L) and auto_type_combo: + Gtk.show_uri(None, self.results_list.get_selected_row().uri, Gdk.CURRENT_TIME) + + if (keyval == Gdk.KEY_v or keyval == Gdk.KEY_V) and auto_type_combo: + self.set_clipboard(self.filtered_logins[self.selected_index]["uri"]) + environment = goldwarden.get_environment() + if environment == None: + return + item_uri = environment["vault"] + "#/vault?itemId=" + self.results_list.get_selected_row().uuid + Gtk.show_uri(None, item_uri, Gdk.CURRENT_TIME) + if keyval == Gdk.KEY_Return: if auto_type_combo: self.autotype(f"{self.filtered_logins[self.selected_index]['username']}\t{self.filtered_logins[self.selected_index]['password']}") diff --git a/gui/src/gui/shortcuts.blp b/gui/src/gui/shortcuts.blp index ff3994a..3c3d53f 100644 --- a/gui/src/gui/shortcuts.blp +++ b/gui/src/gui/shortcuts.blp @@ -39,7 +39,7 @@ Adw.Window window { } Adw.ActionRow { title: "Autotype Username Shortcut"; - subtitle: "CTRL + ALT + U"; + subtitle: "CTRL + SHIFT + U"; } Adw.ActionRow { title: "Copy Password Shortcut"; @@ -47,7 +47,7 @@ Adw.Window window { } Adw.ActionRow { title: "Autotype Password Shortcut"; - subtitle: "CTRL + ALT + P"; + subtitle: "CTRL + SHIFT + P"; } Adw.ActionRow { title: "Copy TOTP Shortcut"; @@ -55,19 +55,15 @@ Adw.Window window { } Adw.ActionRow { title: "Autotype TOTP Shortcut"; - subtitle: "CTRL + ALT + T"; + subtitle: "CTRL + SHIFT + T"; } Adw.ActionRow { title: "Launch URI Shortcut"; - subtitle: "CTRL + L"; + subtitle: "CTRL + SHIFT + L"; } Adw.ActionRow { title: "Launch Web Vault Shortcut"; - subtitle: "CTRL + V"; - } - Adw.ActionRow { - title: "Focus Search Shortcut"; - subtitle: "F"; + subtitle: "CTRL + SHIFT + V"; } Adw.ActionRow { title: "Quit Shortcut"; From 8569fbc2da43b2591a036d52066301d3519dec39 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 21:55:25 +0200 Subject: [PATCH 11/12] Add login shortcut --- gui/src/gui/login.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gui/src/gui/login.py b/gui/src/gui/login.py index 9dfd00b..a062b1a 100644 --- a/gui/src/gui/login.py +++ b/gui/src/gui/login.py @@ -31,6 +31,19 @@ def load(self): self.server_row = builder.get_object("server_row") self.login_button = builder.get_object("login_button") self.login_button.connect("clicked", lambda x: self.on_login()) + + evk = Gtk.EventControllerKey.new() + evk.set_propagation_phase(Gtk.PropagationPhase.CAPTURE) + evk.connect("key-pressed", self.key_press) + self.window.add_controller(evk) + + def key_press(self, event, keyval, keycode, state): + if keyval == Gdk.KEY_Escape: + os._exit(0) + + if keyval == Gdk.KEY_Return and state & Gdk.ModifierType.CONTROL_MASK: + self.on_login() + return True def on_login(self): email = self.email_row.get_text() @@ -43,6 +56,7 @@ def on_login(self): if client_secret != "": goldwarden.set_client_secret(client_secret) goldwarden.login_with_password(email, "") + self.window.close() if __name__ == "__main__": settings = Gtk.Settings.get_default() From cb6d3d5e91cdd74fc293dd0e11f10307f6b5e27f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 3 May 2024 22:16:48 +0200 Subject: [PATCH 12/12] Fix flatpak --- com.quexten.Goldwarden.yml | 8 ++++++++ gui/src/gui/template_loader.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/com.quexten.Goldwarden.yml b/com.quexten.Goldwarden.yml index ac801f1..b177f29 100644 --- a/com.quexten.Goldwarden.yml +++ b/com.quexten.Goldwarden.yml @@ -25,6 +25,13 @@ finish-args: # biometric / user password auth - --system-talk-name=org.freedesktop.PolicyKit1 modules: + - name: "blueprint-compiler" + buildsystem: meson + cleanup: ['*'] + sources: + - type: git + url: https://gitlab.gnome.org/jwestman/blueprint-compiler + tag: v0.12.0 - ./gui/python3-requirements.json - name: goldwarden-python-ui buildsystem: simple @@ -35,6 +42,7 @@ modules: - install -D ./gui/com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop - install -D ./gui/goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg - install -Dm644 ./gui/com.quexten.Goldwarden.metainfo.xml -t /app/share/metainfo/ + - blueprint-compiler batch-compile /app/bin/src/gui/.templates/ /app/bin/src/gui/ /app/bin/src/gui/*.blp sources: - type: dir path: ./ diff --git a/gui/src/gui/template_loader.py b/gui/src/gui/template_loader.py index 675beb0..65c0a8c 100644 --- a/gui/src/gui/template_loader.py +++ b/gui/src/gui/template_loader.py @@ -2,7 +2,7 @@ from gi.repository import Gtk isflatpak = os.path.exists("/.flatpak-info") -pathprefix = "/app/bin/" if isflatpak else "./src/gui/" +pathprefix = "/app/bin/src/gui/" if isflatpak else "./src/gui/" def load_template(path): builder = Gtk.Builder()