Merge pull request #191 from quexten/feature/gtk-blueprints

Feature/gtk blueprints
This commit is contained in:
Bernd Schoolmann 2024-05-03 22:20:50 +02:00 committed by GitHub
commit f415d98f22
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1085 additions and 694 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ __pycache__
flatpak-pip-generator
repo
__debug*
.templates

View File

@ -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: ./

View File

@ -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;
}
}
}

View File

@ -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 threading import Thread
import sys
import os
from . import components
class MyApp(Adw.Application):
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
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
self.load()
self.window.present()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def load(self):
builder = load_template("browserbiometrics.ui")
self.window = builder.get_object("window")
self.window.set_application(self)
# 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.set_default_size(700, 400)
self.set_title("Goldwarden Browser Biometrics Setup")
app = MyApp(application_id="com.quexten.Goldwarden.browserbiometrics")
app.run(sys.argv)
if __name__ == "__main__":
app = GoldwardenBrowserBiometricsSetupGuideApp(application_id="com.quexten.Goldwarden.browserbiometrics")
app.run(sys.argv)

69
gui/src/gui/login.blp Normal file
View File

@ -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;
}
}
}

66
gui/src/gui/login.py Normal file
View File

@ -0,0 +1,66 @@
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())
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()
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, "")
self.window.close()
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)

43
gui/src/gui/main_view.blp Normal file
View File

@ -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",
]
}
}
}

48
gui/src/gui/pinentry.blp Normal file
View File

@ -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;
}
}
}

View File

@ -5,65 +5,45 @@ 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
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
self.load()
self.window.present()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.set_child(self.stack)
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)
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.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)
self.window.set_application(self)
# 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)
# Approve button
approve_button = Gtk.Button(label="Approve")
approve_button.set_hexpand(True) # Make the button expand horizontally
def on_approve_button_clicked(button):
def on_approve_button_clicked(self, 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)
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)
if __name__ == "__main__":
app = GoldwardenPinentryApp(application_id="com.quexten.Goldwarden.pinentry")
message = sys.stdin.readline()
app.message = message
app.run(sys.argv)

View File

@ -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;
}
}
}

View File

@ -5,59 +5,42 @@ 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
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
self.load()
self.window.present()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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.stack = Gtk.Stack()
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
self.set_child(self.stack)
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)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.stack.add_child(box)
self.window.set_application(self)
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):
def on_approve_button_clicked(self, 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")
def on_cancel_button_clicked(self, button):
print("false", flush=True)
os._exit(0)
app = MyApp(application_id="com.quexten.Goldwarden.pinentry")
app.run(sys.argv)
if __name__ == "__main__":
app = GoldwardenPinentryApprovalApp(application_id="com.quexten.Goldwarden.pinentry")
message = sys.stdin.readline()
app.message = message
app.run(sys.argv)

View File

@ -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;
}
}
}

View File

@ -7,21 +7,117 @@ import time
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)
class GoldwardenQuickAccessApp(Adw.Application):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.logins = []
self.filtered_logins = []
self.query = ""
self.connect('activate', self.on_activate)
self.selected_index = 0
def autotype(text):
def on_activate(self, app):
self.load()
self.window.present()
thread = Thread(target=self.update_logins)
thread.start()
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)
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_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:
self.autotype(totp.totp(self.filtered_logins[self.selected_index]["totp"]))
if copy_combo:
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:
self.autotype(self.filtered_logins[self.selected_index]["username"])
if copy_combo:
self.set_clipboard(self.filtered_logins[self.selected_index]["username"])
if keyval == Gdk.KEY_p or keyval == Gdk.KEY_P:
if auto_type_combo:
self.autotype(self.filtered_logins[self.selected_index]["password"])
if copy_combo:
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']}")
def update(self):
self.update_list()
self.render_list()
return True
def autotype(self, text):
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(text):
def set_clipboard(self, text):
Gdk.Display.get_clipboard(Gdk.Display.get_default()).set_content(
Gdk.ContentProvider.new_for_value(text)
)
@ -32,58 +128,30 @@ def set_clipboard(text):
thread = Thread(target=kill)
thread.start()
class MyApp(Adw.Application):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.connect('activate', self.on_activate)
def update_logins(self):
logins = goldwarden.get_vault_logins()
if logins == None:
os._exit(0)
def update_list(self):
if self.query == "":
self.filtered_logins = []
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
thread = Thread(target=self.update_logins)
thread.start()
self.filtered_logins = list(filter(lambda i: self.query.lower() in i["name"].lower(), self.logins))
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)
self.box = Gtk.Box()
self.box.set_orientation(Gtk.Orientation.VERTICAL)
self.stack.add_named(self.box, "box")
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()
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.status_page.set_visible(False)
else:
self.results_list.set_visible(False)
self.status_page.set_visible(True)
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 :
for i in self.filtered_logins:
action_row = Adw.ActionRow()
action_row.set_title(i["name"])
action_row.set_subtitle(i["username"])
@ -95,84 +163,33 @@ class MainWindow(Gtk.ApplicationWindow):
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
self.text_view.connect("changed", lambda entry: on_type(entry))
self.box.append(self.text_view)
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()
def on_type(self, entry):
search_query = entry.get_text()
self.query = search_query
self.update()
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:
def update_logins(self):
logins = goldwarden.get_vault_logins()
if logins == None:
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()
self.logins = logins
self.update()
keycont.connect('key-pressed', handle_keypress, self)
self.add_controller(keycont)
if __name__ == "__main__":
settings = Gtk.Settings.get_default()
settings.set_property("gtk-error-bell", False)
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)
token = sys.stdin.readline()
goldwarden.create_authenticated_connection(token)
app = GoldwardenQuickAccessApp(application_id="com.quexten.Goldwarden.quickaccess")
app.run(sys.argv)

149
gui/src/gui/settings.blp Normal file
View File

@ -0,0 +1,149 @@
using Gtk 4.0;
using Adw 1;
Adw.ApplicationWindow window {
default-width: 400;
default-height: 700;
Adw.ToolbarView view {
content: Box{
orientation: vertical;
Stack stack {
Box set_pin_status {
orientation: vertical;
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 login_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";
}
}
}
};
}
}
};
[top]
Adw.HeaderBar {
halign: baseline;
title-widget: Adw.WindowTitle {
title: 'Goldwarden';
};
[end]
MenuButton menu_button {
icon-name: "open-menu-symbolic";
}
valign: start;
}
}
}

View File

@ -1,322 +1,150 @@
#!/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)
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()
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):
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")
class MyApp(Adw.Application):
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)
self.settings_win.present()
self.load()
self.update()
self.window.present()
GLib.timeout_add(100, self.update)
def show_login():
dialog = Gtk.Dialog(title="Goldwarden")
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")
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)
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())
email_entry = Adw.EntryRow()
email_entry.set_title("Email")
email_entry.set_text("")
auth_preference_group.add(email_entry)
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())
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)
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", self.token))
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)
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", 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")
self.notes_row = builder.get_object("notes_row")
dialog.add_button("Login", Gtk.ResponseType.OK)
def on_save(res):
if res != Gtk.ResponseType.OK:
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", 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", 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", self.token))
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()
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
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()
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)
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)
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"]))
dialog.get_content_area().append(preference_group)
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)
url_entry = Adw.EntryRow()
url_entry.set_title("Base Url")
url_entry.set_text("https://vault.bitwarden.com/")
preference_group.add(url_entry)
if __name__ == "__main__":
settings = Gtk.Settings.get_default()
settings.set_property("gtk-error-bell", False)
#ok response
dialog.connect("response", lambda dialog, response: on_save(response))
dialog.set_default_size(400, 200)
dialog.set_modal(True)
dialog.present()
token = sys.stdin.readline().strip()
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
)
app = MyApp(application_id="com.quexten.Goldwarden.settings")
app.run(sys.argv)
goldwarden.create_authenticated_connection(None)
app = GoldwardenSettingsApp(application_id="com.quexten.Goldwarden.settings")
app.token = token
app.run(sys.argv)

91
gui/src/gui/shortcuts.blp Normal file
View File

@ -0,0 +1,91 @@
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 + SHIFT + U";
}
Adw.ActionRow {
title: "Copy Password Shortcut";
subtitle: "CTRL + P";
}
Adw.ActionRow {
title: "Autotype Password Shortcut";
subtitle: "CTRL + SHIFT + P";
}
Adw.ActionRow {
title: "Copy TOTP Shortcut";
subtitle: "CTRL + T";
}
Adw.ActionRow {
title: "Autotype TOTP Shortcut";
subtitle: "CTRL + SHIFT + T";
}
Adw.ActionRow {
title: "Launch URI Shortcut";
subtitle: "CTRL + SHIFT + L";
}
Adw.ActionRow {
title: "Launch Web Vault Shortcut";
subtitle: "CTRL + SHIFT + V";
}
Adw.ActionRow {
title: "Quit Shortcut";
subtitle: "Esc";
}
}
}
};
}
};
[top]
Adw.HeaderBar {
halign: baseline;
title-widget: Adw.WindowTitle {
title: 'Settings';
};
valign: start;
}
}
}

View File

@ -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
self.load()
self.window.present()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def load(self):
builder = load_template("shortcuts.ui")
self.window = builder.get_object("window")
self.window.set_application(self)
# 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.set_default_size(700, 700)
self.set_title("Goldwarden Shortcuts")
app = MyApp(application_id="com.quexten.Goldwarden.shortcuts")
app.run(sys.argv)
if __name__ == "__main__":
app = GoldwardenShortcutsSetupGuideApp(application_id="com.quexten.Goldwarden.shortcuts")
app.run(sys.argv)

57
gui/src/gui/ssh.blp Normal file
View File

@ -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;
}
}
}

View File

@ -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 threading import Thread
import sys
import os
from . import components
class MyApp(Adw.Application):
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
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
self.load()
self.window.present()
class MainWindow(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def load(self):
builder = load_template("ssh.ui")
self.window = builder.get_object("window")
self.window.set_application(self)
# 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.set_default_size(400, 700)
self.set_title("Goldwarden SSH Setup")
app = MyApp(application_id="com.quexten.Goldwarden.sshsetup")
app.run(sys.argv)
if __name__ == "__main__":
app = GoldwardenSSHSetupGuideApp(application_id="com.quexten.Goldwarden.sshsetup")
app.run(sys.argv)

View File

@ -0,0 +1,10 @@
import os
from gi.repository import Gtk
isflatpak = os.path.exists("/.flatpak-info")
pathprefix = "/app/bin/src/gui/" if isflatpak else "./src/gui/"
def load_template(path):
builder = Gtk.Builder()
builder.add_from_file(pathprefix + ".templates/" + path)
return builder

View File

@ -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)