Fix flatpak support; split up code; add autostart
This commit is contained in:
parent
5a41145d4b
commit
0717e34796
|
@ -1,3 +1,4 @@
|
||||||
.vscode
|
.vscode
|
||||||
goldwarden
|
goldwarden
|
||||||
__pycache__
|
__pycache__
|
||||||
|
.flatpak-builder
|
|
@ -3,6 +3,8 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/quexten/goldwarden/autotype"
|
"github.com/quexten/goldwarden/autotype"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -13,7 +15,8 @@ var autofillCmd = &cobra.Command{
|
||||||
Long: `Autotype credentials`,
|
Long: `Autotype credentials`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
username, _ := cmd.Flags().GetString("username")
|
username, _ := cmd.Flags().GetString("username")
|
||||||
password, _ := cmd.Flags().GetString("password")
|
// get pasword from env
|
||||||
|
password := os.Getenv("PASSWORD")
|
||||||
autotype.TypeString(username + "\t" + password)
|
autotype.TypeString(username + "\t" + password)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -21,5 +24,4 @@ var autofillCmd = &cobra.Command{
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(autofillCmd)
|
rootCmd.AddCommand(autofillCmd)
|
||||||
autofillCmd.PersistentFlags().String("username", "", "")
|
autofillCmd.PersistentFlags().String("username", "", "")
|
||||||
autofillCmd.PersistentFlags().String("password", "", "")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ finish-args:
|
||||||
- --socket=wayland
|
- --socket=wayland
|
||||||
- --socket=fallback-x11
|
- --socket=fallback-x11
|
||||||
- --filesystem=home
|
- --filesystem=home
|
||||||
|
# todo remove this
|
||||||
|
- --socket=session-bus
|
||||||
modules:
|
modules:
|
||||||
- name: goldwarden
|
- name: goldwarden
|
||||||
buildsystem: simple
|
buildsystem: simple
|
||||||
|
@ -16,13 +18,11 @@ modules:
|
||||||
build-args:
|
build-args:
|
||||||
- "--share=network"
|
- "--share=network"
|
||||||
build-commands:
|
build-commands:
|
||||||
- pip3 install --prefix=/app timeago secretstorage pycryptodome chacha20
|
- pip3 install --prefix=/app tendo
|
||||||
- install -D main.py /app/bin/main.py
|
- install -D main.py /app/bin/main.py
|
||||||
- install -D com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop
|
- install -D com.quexten.Goldwarden.desktop /app/share/applications/com.quexten.Goldwarden.desktop
|
||||||
- install -D goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
|
- install -D goldwarden.svg /app/share/icons/hicolor/scalable/apps/com.quexten.Goldwarden.svg
|
||||||
- cp -R ui/ /app/bin/gui/
|
- cp -R ./ /app/bin/
|
||||||
- cp -R backend/ /app/bin/backend/
|
|
||||||
- cp -R event_monitors/ /app/bin/event_monitors/
|
|
||||||
sources:
|
sources:
|
||||||
- type: dir
|
- type: dir
|
||||||
path: ui
|
path: ui
|
|
@ -0,0 +1,108 @@
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '4.0')
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
import gc
|
||||||
|
import time
|
||||||
|
from gi.repository import Gtk, Adw, GLib
|
||||||
|
import goldwarden
|
||||||
|
import clipboard
|
||||||
|
from threading import Thread
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
class MyApp(Adw.Application):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.connect('activate', self.on_activate)
|
||||||
|
|
||||||
|
def on_activate(self, app):
|
||||||
|
self.autofill_window = MainWindow(application=app)
|
||||||
|
self.autofill_window.logins = []
|
||||||
|
self.autofill_window.present()
|
||||||
|
logins = goldwarden.get_vault_logins()
|
||||||
|
if logins == None:
|
||||||
|
return
|
||||||
|
app.autofill_window.logins = 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")
|
||||||
|
# on type func
|
||||||
|
def on_type(entry):
|
||||||
|
if len(entry.get_text()) > 1:
|
||||||
|
self.history_list.show()
|
||||||
|
else:
|
||||||
|
self.history_list.hide()
|
||||||
|
|
||||||
|
while self.history_list.get_first_child() != None:
|
||||||
|
self.history_list.remove(self.history_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"]
|
||||||
|
self.history_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.history_list = Gtk.ListBox()
|
||||||
|
# margin'
|
||||||
|
self.history_list.set_margin_start(10)
|
||||||
|
self.history_list.set_margin_end(10)
|
||||||
|
self.history_list.set_margin_top(10)
|
||||||
|
self.history_list.set_margin_bottom(10)
|
||||||
|
self.history_list.hide()
|
||||||
|
|
||||||
|
keycont = Gtk.EventControllerKey()
|
||||||
|
def handle_keypress(cotroller, keyval, keycode, state, user_data):
|
||||||
|
if keycode == 36:
|
||||||
|
print("enter")
|
||||||
|
self.hide()
|
||||||
|
def do_autotype(username, password):
|
||||||
|
time.sleep(0.5)
|
||||||
|
goldwarden.autotype(username, password)
|
||||||
|
GLib.idle_add(lambda: self.show())
|
||||||
|
autotypeThread = Thread(target=do_autotype, args=(self.history_list.get_selected_row().username, self.history_list.get_selected_row().password,))
|
||||||
|
autotypeThread.start()
|
||||||
|
print(self.history_list.get_selected_row().get_title())
|
||||||
|
if keyval == 112:
|
||||||
|
print("copy password")
|
||||||
|
clipboard.write(self.history_list.get_selected_row().password)
|
||||||
|
elif keyval == 117:
|
||||||
|
print("copy username")
|
||||||
|
clipboard.write(self.history_list.get_selected_row().username)
|
||||||
|
|
||||||
|
keycont.connect('key-pressed', handle_keypress, self)
|
||||||
|
self.add_controller(keycont)
|
||||||
|
|
||||||
|
self.history_list.get_style_context().add_class("boxed-list")
|
||||||
|
self.box.append(self.history_list)
|
||||||
|
self.set_default_size(700, 700)
|
||||||
|
self.set_title("Goldwarden")
|
||||||
|
|
||||||
|
app = MyApp(application_id="com.quexten.Goldwarden.autofill")
|
||||||
|
app.run(sys.argv)
|
|
@ -0,0 +1,58 @@
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '4.0')
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
import gc
|
||||||
|
from gi.repository import Gtk, Adw, GLib, Gio
|
||||||
|
from random import randint
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def receive_autostart(self, *args):
|
||||||
|
print("autostart enabled..!?")
|
||||||
|
print(args)
|
||||||
|
|
||||||
|
def request_autostart():
|
||||||
|
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||||
|
proxy = Gio.DBusProxy.new_sync(
|
||||||
|
bus,
|
||||||
|
Gio.DBusProxyFlags.NONE,
|
||||||
|
None,
|
||||||
|
'org.freedesktop.portal.Desktop',
|
||||||
|
'/org/freedesktop/portal/desktop',
|
||||||
|
'org.freedesktop.portal.Background',
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
token = 0 + randint(10000000, 20000000)
|
||||||
|
options = {
|
||||||
|
'handle_token': GLib.Variant('s', f'com/quexten/Goldwarden/{token}'),
|
||||||
|
'reason': GLib.Variant('s', ('Autostart Goldwarden in the background.')),
|
||||||
|
'autostart': GLib.Variant('b', True),
|
||||||
|
'commandline': GLib.Variant('as', ['main.py', '--hidden']),
|
||||||
|
'dbus-activatable': GLib.Variant('b', False),
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
request = proxy.RequestBackground('(sa{sv})', "", options)
|
||||||
|
if request is None:
|
||||||
|
raise Exception(
|
||||||
|
"Registering with background portal failed."
|
||||||
|
)
|
||||||
|
|
||||||
|
bus.signal_subscribe(
|
||||||
|
'org.freedesktop.portal.Desktop',
|
||||||
|
'org.freedesktop.portal.Request',
|
||||||
|
'Response',
|
||||||
|
request,
|
||||||
|
None,
|
||||||
|
Gio.DBusSignalFlags.NO_MATCH_RULE,
|
||||||
|
receive_autostart,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
request_autostart()
|
||||||
|
|
||||||
|
loop = GLib.MainLoop()
|
||||||
|
loop.run()
|
|
@ -5,6 +5,6 @@ Keywords=backup;recovery;
|
||||||
Exec=main.py
|
Exec=main.py
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=com.quexten.goldwarden.svg
|
Icon=com.quexten.Goldwarden.svg
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
Categories=GNOME;GTK;Password Manager;
|
Categories=GNOME;GTK;Password Manager;
|
|
@ -1,5 +1,6 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
BINARY_PATH = "/home/quexten/go/src/github.com/quexten/goldwarden/goldwarden"
|
BINARY_PATH = "/home/quexten/go/src/github.com/quexten/goldwarden/goldwarden"
|
||||||
|
|
||||||
|
@ -99,7 +100,10 @@ def get_vault_logins():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def autotype(username, password):
|
def autotype(username, password):
|
||||||
restic_cmd = f"{BINARY_PATH} autotype --username {username} --password {password}"
|
# environment
|
||||||
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True)
|
env = os.environ.copy()
|
||||||
|
env["PASSWORD"] = password
|
||||||
|
restic_cmd = f"{BINARY_PATH} autotype --username {username}"
|
||||||
|
result = subprocess.run(restic_cmd.split(), capture_output=True, text=True, env=env)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise Exception("Failed to initialize repository, err", result.stderr)
|
raise Exception("Failed to initialize repository, err", result.stderr)
|
|
@ -0,0 +1,115 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="100mm"
|
||||||
|
height="100mm"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
|
||||||
|
sodipodi:docname="goldwarden.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="1.6792579"
|
||||||
|
inkscape:cx="142.62253"
|
||||||
|
inkscape:cy="292.09332"
|
||||||
|
inkscape:window-width="3840"
|
||||||
|
inkscape:window-height="2091"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1">
|
||||||
|
<linearGradient
|
||||||
|
id="linearGradient17"
|
||||||
|
inkscape:collect="always">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffeb28;stop-opacity:1;"
|
||||||
|
offset="0"
|
||||||
|
id="stop17" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffb608;stop-opacity:1;"
|
||||||
|
offset="0.76853603"
|
||||||
|
id="stop18" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#ffa21f;stop-opacity:1;"
|
||||||
|
offset="1"
|
||||||
|
id="stop19" />
|
||||||
|
</linearGradient>
|
||||||
|
<radialGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient17"
|
||||||
|
id="radialGradient18"
|
||||||
|
cx="66.050179"
|
||||||
|
cy="50.758305"
|
||||||
|
fx="66.050179"
|
||||||
|
fy="50.758305"
|
||||||
|
r="45"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(1.1205298,9.6606045e-4,-7.5884389e-4,0.88018377,-7.9225014,6.0178603)" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:url(#radialGradient18);fill-opacity:1;stroke-width:0.262037;fill-rule:nonzero"
|
||||||
|
id="rect1"
|
||||||
|
width="90"
|
||||||
|
height="90"
|
||||||
|
x="5.0169253"
|
||||||
|
y="4.8409019"
|
||||||
|
ry="12.342399" />
|
||||||
|
<g
|
||||||
|
id="g17"
|
||||||
|
transform="matrix(1.0914831,0,0,1.0914831,-10.347732,-4.5673979)">
|
||||||
|
<circle
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.279194"
|
||||||
|
id="path1"
|
||||||
|
cx="70"
|
||||||
|
cy="50"
|
||||||
|
r="10" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
|
||||||
|
id="rect2"
|
||||||
|
width="5"
|
||||||
|
height="50"
|
||||||
|
x="67.5"
|
||||||
|
y="24.926144"
|
||||||
|
ry="2.0560377" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
|
||||||
|
id="rect3"
|
||||||
|
width="5"
|
||||||
|
height="50"
|
||||||
|
x="-10.85097"
|
||||||
|
y="60.616943"
|
||||||
|
ry="2.0560377"
|
||||||
|
transform="rotate(-60.033259)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke-width:0.200309"
|
||||||
|
id="rect4"
|
||||||
|
width="5"
|
||||||
|
height="50"
|
||||||
|
x="-80.843193"
|
||||||
|
y="10.529473"
|
||||||
|
ry="2.0560377"
|
||||||
|
transform="rotate(-120.06752)" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
318
ui/main.py
318
ui/main.py
|
@ -1,307 +1,29 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
import sys
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '4.0')
|
|
||||||
gi.require_version('Adw', '1')
|
|
||||||
import gc
|
|
||||||
|
|
||||||
from gi.repository import Gtk, Adw, Gdk, Graphene, Gsk, Gio, GLib, GObject
|
|
||||||
import monitors.dbus_autofill_monitor
|
|
||||||
import goldwarden
|
|
||||||
import clipboard
|
|
||||||
import time
|
import time
|
||||||
from threading import Thread
|
import subprocess
|
||||||
|
from tendo import singleton
|
||||||
|
import monitors.dbus_autofill_monitor
|
||||||
|
import sys
|
||||||
|
|
||||||
class MainWindow(Gtk.ApplicationWindow):
|
try:
|
||||||
def __init__(self, *args, **kwargs):
|
subprocess.Popen(["python3", "/app/bin/background.py"], start_new_session=True)
|
||||||
super().__init__(*args, **kwargs)
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
self.stack = Gtk.Stack()
|
if "--hidden" not in sys.argv:
|
||||||
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT)
|
try:
|
||||||
self.set_child(self.stack)
|
subprocess.Popen(["python3", "/app/bin/settings.py"], start_new_session=True)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
self.box = Gtk.Box()
|
try:
|
||||||
self.box.set_orientation(Gtk.Orientation.VERTICAL)
|
me = singleton.SingleInstance()
|
||||||
self.stack.add_named(self.box, "box")
|
except:
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
self.text_view = Adw.EntryRow()
|
|
||||||
self.text_view.set_title("Search")
|
|
||||||
# on type func
|
|
||||||
def on_type(entry):
|
|
||||||
if len(entry.get_text()) > 1:
|
|
||||||
self.history_list.show()
|
|
||||||
else:
|
|
||||||
self.history_list.hide()
|
|
||||||
|
|
||||||
while self.history_list.get_first_child() != None:
|
|
||||||
self.history_list.remove(self.history_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"]
|
|
||||||
self.history_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.history_list = Gtk.ListBox()
|
|
||||||
# margin'
|
|
||||||
self.history_list.set_margin_start(10)
|
|
||||||
self.history_list.set_margin_end(10)
|
|
||||||
self.history_list.set_margin_top(10)
|
|
||||||
self.history_list.set_margin_bottom(10)
|
|
||||||
self.history_list.hide()
|
|
||||||
|
|
||||||
keycont = Gtk.EventControllerKey()
|
|
||||||
def handle_keypress(controller, keyval, keycode, state, user_data):
|
|
||||||
if keycode == 36:
|
|
||||||
print("enter")
|
|
||||||
self.hide()
|
|
||||||
def do_autotype(username, password):
|
|
||||||
time.sleep(0.5)
|
|
||||||
goldwarden.autotype(username, password)
|
|
||||||
GLib.idle_add(lambda: self.show())
|
|
||||||
autotypeThread = Thread(target=do_autotype, args=(self.history_list.get_selected_row().username, self.history_list.get_selected_row().password,))
|
|
||||||
autotypeThread.start()
|
|
||||||
print(self.history_list.get_selected_row().get_title())
|
|
||||||
if keyval == 112:
|
|
||||||
print("copy password")
|
|
||||||
clipboard.write(self.history_list.get_selected_row().password)
|
|
||||||
elif keyval == 117:
|
|
||||||
print("copy username")
|
|
||||||
clipboard.write(self.history_list.get_selected_row().username)
|
|
||||||
|
|
||||||
keycont.connect('key-pressed', handle_keypress, self)
|
|
||||||
self.add_controller(keycont)
|
|
||||||
|
|
||||||
self.history_list.get_style_context().add_class("boxed-list")
|
|
||||||
self.box.append(self.history_list)
|
|
||||||
self.set_default_size(700, 700)
|
|
||||||
self.set_title("Goldwarden")
|
|
||||||
|
|
||||||
def on_close(window):
|
|
||||||
while self.history_list.get_first_child() != None:
|
|
||||||
self.history_list.remove(self.history_list.get_first_child())
|
|
||||||
window.hide()
|
|
||||||
gc.collect()
|
|
||||||
return True
|
|
||||||
self.connect("close-request", on_close)
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
for i in range(0, 5):
|
|
||||||
action_row = Adw.ActionRow()
|
|
||||||
action_row.set_title("aaa")
|
|
||||||
action_row.set_subtitle("Test")
|
|
||||||
self.history_list.append(action_row)
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsWinvdow(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.preferences_page = Adw.PreferencesPage()
|
|
||||||
self.preferences_page.set_title("General")
|
|
||||||
self.stack.add_named(self.preferences_page, "preferences_page")
|
|
||||||
|
|
||||||
self.preferences_group = Adw.PreferencesGroup()
|
|
||||||
self.preferences_group.set_title("Services")
|
|
||||||
self.preferences_page.add(self.preferences_group)
|
|
||||||
|
|
||||||
self.ssh_row = Adw.ActionRow()
|
|
||||||
self.ssh_row.set_title("SSH Daemon")
|
|
||||||
self.ssh_row.set_subtitle("Listening at ~/.goldwarden-ssh-agent.sock")
|
|
||||||
self.preferences_group.add(self.ssh_row)
|
|
||||||
|
|
||||||
self.login_with_device = Adw.ActionRow()
|
|
||||||
self.login_with_device.set_title("Login with device")
|
|
||||||
self.login_with_device.set_subtitle("Waiting for requests...")
|
|
||||||
self.preferences_group.add(self.login_with_device)
|
|
||||||
|
|
||||||
self.autofill_row = Adw.ActionRow()
|
|
||||||
self.autofill_row.set_title("Autofill Shortcut")
|
|
||||||
self.autofill_row.set_subtitle("Unavailable, please set up a shortcut in your desktop environment (README)")
|
|
||||||
self.preferences_group.add(self.autofill_row)
|
|
||||||
|
|
||||||
self.status_row = Adw.ActionRow()
|
|
||||||
self.status_row.set_title("DBUS Service")
|
|
||||||
self.status_row.set_subtitle("Listening")
|
|
||||||
self.preferences_group.add(self.status_row)
|
|
||||||
|
|
||||||
self.status_row = Adw.ActionRow()
|
|
||||||
self.status_row.set_title("Vault Status")
|
|
||||||
self.status_row.set_subtitle("Locked")
|
|
||||||
self.preferences_group.add(self.status_row)
|
|
||||||
|
|
||||||
self.login_row = Adw.ActionRow()
|
|
||||||
self.login_row.set_title("Vault Login Entries")
|
|
||||||
self.login_row.set_subtitle("0")
|
|
||||||
self.preferences_group.add(self.login_row)
|
|
||||||
|
|
||||||
self.notes_row = Adw.ActionRow()
|
|
||||||
self.notes_row.set_title("Vault Notes")
|
|
||||||
self.notes_row.set_subtitle("0")
|
|
||||||
self.preferences_group.add(self.notes_row)
|
|
||||||
|
|
||||||
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.preferences_group.add(self.login_button)
|
|
||||||
|
|
||||||
self.set_pin_button = Gtk.Button()
|
|
||||||
self.set_pin_button.set_label("Set Pin")
|
|
||||||
def set_pin():
|
|
||||||
set_pin_thread = Thread(target=goldwarden.enable_pin)
|
|
||||||
set_pin_thread.start()
|
|
||||||
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.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.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.preferences_group.add(self.logout_button)
|
|
||||||
|
|
||||||
def update_labels():
|
|
||||||
pin_set = goldwarden.is_pin_enabled()
|
|
||||||
status = goldwarden.get_vault_status()
|
|
||||||
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.status_row.set_subtitle(str("Unlocked" if not locked else "Locked"))
|
|
||||||
self.login_row.set_subtitle(str(status["loginEntries"]))
|
|
||||||
self.notes_row.set_subtitle(str(status["noteEntries"]))
|
|
||||||
self.unlock_button.set_sensitive(True)
|
|
||||||
self.unlock_button.set_label("Unlock" if locked else "Lock")
|
|
||||||
GLib.timeout_add(1000, update_labels)
|
|
||||||
|
|
||||||
GLib.timeout_add(1000, update_labels)
|
|
||||||
self.set_default_size(400, 700)
|
|
||||||
self.set_title("Goldwarden")
|
|
||||||
|
|
||||||
|
|
||||||
#add title buttons
|
|
||||||
self.title_bar = Gtk.HeaderBar()
|
|
||||||
self.set_titlebar(self.title_bar)
|
|
||||||
|
|
||||||
class MyApp(Adw.Application):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
self.connect('activate', self.on_activate)
|
|
||||||
|
|
||||||
def on_activate(self, app):
|
|
||||||
if hasattr(self, "win") is False:
|
|
||||||
app.hold()
|
|
||||||
self.win = MainWindow(application=app)
|
|
||||||
self.win.set_hide_on_close(True)
|
|
||||||
self.win.hide()
|
|
||||||
self.settings_win = SettingsWinvdow(application=app)
|
|
||||||
self.settings_win.set_hide_on_close(True)
|
|
||||||
self.settings_win.present()
|
|
||||||
|
|
||||||
app = MyApp(application_id="com.quexten.Goldwarden")
|
|
||||||
def on_autofill():
|
def on_autofill():
|
||||||
logins = goldwarden.get_vault_logins()
|
subprocess.Popen(["python3", "/app/bin/autofill.py"], start_new_session=True)
|
||||||
if logins == None:
|
|
||||||
return
|
|
||||||
app.win.logins = logins
|
|
||||||
app.win.show()
|
|
||||||
app.win.present()
|
|
||||||
monitors.dbus_autofill_monitor.on_autofill = on_autofill
|
monitors.dbus_autofill_monitor.on_autofill = on_autofill
|
||||||
|
|
||||||
def show_login():
|
while True:
|
||||||
dialog = Gtk.Dialog(title="Goldwarden")
|
time.sleep(60)
|
||||||
preference_group = Adw.PreferencesGroup()
|
|
||||||
preference_group.set_title("Config")
|
|
||||||
dialog.get_content_area().append(preference_group)
|
|
||||||
|
|
||||||
api_url_entry = Adw.EntryRow()
|
|
||||||
api_url_entry.set_title("API Url")
|
|
||||||
# set value
|
|
||||||
api_url_entry.set_text("https://api.bitwarden.com/")
|
|
||||||
preference_group.add(api_url_entry)
|
|
||||||
|
|
||||||
identity_url_entry = Adw.EntryRow()
|
|
||||||
identity_url_entry.set_title("Identity Url")
|
|
||||||
identity_url_entry.set_text("https://identity.bitwarden.com/")
|
|
||||||
preference_group.add(identity_url_entry)
|
|
||||||
|
|
||||||
notification_url_entry = Adw.EntryRow()
|
|
||||||
notification_url_entry.set_title("Notification URL")
|
|
||||||
notification_url_entry.set_text("https://notifications.bitwarden.com/")
|
|
||||||
preference_group.add(notification_url_entry)
|
|
||||||
|
|
||||||
auth_preference_group = Adw.PreferencesGroup()
|
|
||||||
auth_preference_group.set_title("Authentication")
|
|
||||||
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)
|
|
||||||
|
|
||||||
dialog.add_button("Login", Gtk.ResponseType.OK)
|
|
||||||
def on_save(res):
|
|
||||||
if res != Gtk.ResponseType.OK:
|
|
||||||
return
|
|
||||||
goldwarden.set_api_url(api_url_entry.get_text())
|
|
||||||
goldwarden.set_identity_url(identity_url_entry.get_text())
|
|
||||||
goldwarden.set_notification_url(notification_url_entry.get_text())
|
|
||||||
def login():
|
|
||||||
res = goldwarden.login_with_password(email_entry.get_text(), "password")
|
|
||||||
def handle_res():
|
|
||||||
print("handle res", res)
|
|
||||||
if res == "ok":
|
|
||||||
dialog.close()
|
|
||||||
print("ok")
|
|
||||||
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()
|
|
||||||
|
|
||||||
#ok response
|
|
||||||
dialog.connect("response", lambda dialog, response: on_save(response))
|
|
||||||
dialog.set_default_size(400, 200)
|
|
||||||
dialog.set_modal(True)
|
|
||||||
dialog.present()
|
|
||||||
|
|
||||||
|
|
||||||
app.run(sys.argv)
|
|
||||||
|
|
|
@ -0,0 +1,216 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
import sys
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '4.0')
|
||||||
|
gi.require_version('Adw', '1')
|
||||||
|
import gc
|
||||||
|
|
||||||
|
from gi.repository import Gtk, Adw, GLib
|
||||||
|
import goldwarden
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
class SettingsWinvdow(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.preferences_page = Adw.PreferencesPage()
|
||||||
|
self.preferences_page.set_title("General")
|
||||||
|
self.stack.add_named(self.preferences_page, "preferences_page")
|
||||||
|
|
||||||
|
self.preferences_group = Adw.PreferencesGroup()
|
||||||
|
self.preferences_group.set_title("Services")
|
||||||
|
self.preferences_page.add(self.preferences_group)
|
||||||
|
|
||||||
|
self.ssh_row = Adw.ActionRow()
|
||||||
|
self.ssh_row.set_title("SSH Daemon")
|
||||||
|
self.ssh_row.set_subtitle("Listening at ~/.goldwarden-ssh-agent.sock")
|
||||||
|
self.preferences_group.add(self.ssh_row)
|
||||||
|
|
||||||
|
self.login_with_device = Adw.ActionRow()
|
||||||
|
self.login_with_device.set_title("Login with device")
|
||||||
|
self.login_with_device.set_subtitle("Waiting for requests...")
|
||||||
|
self.preferences_group.add(self.login_with_device)
|
||||||
|
|
||||||
|
self.status_row = Adw.ActionRow()
|
||||||
|
self.status_row.set_title("DBUS Service")
|
||||||
|
self.status_row.set_subtitle("Listening")
|
||||||
|
self.preferences_group.add(self.status_row)
|
||||||
|
|
||||||
|
self.shortcut_preferences_group = Adw.PreferencesGroup()
|
||||||
|
self.shortcut_preferences_group.set_title("Shortcuts")
|
||||||
|
self.preferences_page.add(self.shortcut_preferences_group)
|
||||||
|
|
||||||
|
self.autofill_row = Adw.ActionRow()
|
||||||
|
self.autofill_row.set_title("Autofill Shortcut")
|
||||||
|
self.autofill_row.set_subtitle("Unavailable, please set up a shortcut in your desktop environment (README)")
|
||||||
|
self.shortcut_preferences_group.add(self.autofill_row)
|
||||||
|
|
||||||
|
self.copy_username_shortcut_row = Adw.ActionRow()
|
||||||
|
self.copy_username_shortcut_row.set_title("Copy Username Shortcut")
|
||||||
|
self.copy_username_shortcut_row.set_subtitle("U")
|
||||||
|
self.shortcut_preferences_group.add(self.copy_username_shortcut_row)
|
||||||
|
|
||||||
|
self.copy_password_shortcut_row = Adw.ActionRow()
|
||||||
|
self.copy_password_shortcut_row.set_title("Copy Password Shortcut")
|
||||||
|
self.copy_password_shortcut_row.set_subtitle("P")
|
||||||
|
self.shortcut_preferences_group.add(self.copy_password_shortcut_row)
|
||||||
|
|
||||||
|
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 = Adw.ActionRow()
|
||||||
|
self.status_row.set_title("Vault Status")
|
||||||
|
self.status_row.set_subtitle("Locked")
|
||||||
|
self.vault_status_preferences_group.add(self.status_row)
|
||||||
|
|
||||||
|
self.login_row = Adw.ActionRow()
|
||||||
|
self.login_row.set_title("Vault Login Entries")
|
||||||
|
self.login_row.set_subtitle("0")
|
||||||
|
self.vault_status_preferences_group.add(self.login_row)
|
||||||
|
|
||||||
|
self.notes_row = Adw.ActionRow()
|
||||||
|
self.notes_row.set_title("Vault Notes")
|
||||||
|
self.notes_row.set_subtitle("0")
|
||||||
|
self.vault_status_preferences_group.add(self.notes_row)
|
||||||
|
|
||||||
|
self.action_preferences_group = Adw.PreferencesGroup()
|
||||||
|
self.action_preferences_group.set_title("Actions")
|
||||||
|
self.preferences_page.add(self.action_preferences_group)
|
||||||
|
|
||||||
|
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")
|
||||||
|
def set_pin():
|
||||||
|
set_pin_thread = Thread(target=goldwarden.enable_pin)
|
||||||
|
set_pin_thread.start()
|
||||||
|
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)
|
||||||
|
|
||||||
|
def update_labels():
|
||||||
|
pin_set = goldwarden.is_pin_enabled()
|
||||||
|
status = goldwarden.get_vault_status()
|
||||||
|
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.status_row.set_subtitle(str("Unlocked" if not locked else "Locked"))
|
||||||
|
self.login_row.set_subtitle(str(status["loginEntries"]))
|
||||||
|
self.notes_row.set_subtitle(str(status["noteEntries"]))
|
||||||
|
self.unlock_button.set_sensitive(True)
|
||||||
|
self.unlock_button.set_label("Unlock" if locked else "Lock")
|
||||||
|
GLib.timeout_add(1000, update_labels)
|
||||||
|
|
||||||
|
GLib.timeout_add(1000, update_labels)
|
||||||
|
self.set_default_size(400, 700)
|
||||||
|
self.set_title("Goldwarden")
|
||||||
|
|
||||||
|
|
||||||
|
#add title buttons
|
||||||
|
self.title_bar = Gtk.HeaderBar()
|
||||||
|
self.set_titlebar(self.title_bar)
|
||||||
|
|
||||||
|
class MyApp(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()
|
||||||
|
|
||||||
|
app = MyApp(application_id="com.quexten.Goldwarden")
|
||||||
|
|
||||||
|
def show_login():
|
||||||
|
dialog = Gtk.Dialog(title="Goldwarden")
|
||||||
|
preference_group = Adw.PreferencesGroup()
|
||||||
|
preference_group.set_title("Config")
|
||||||
|
dialog.get_content_area().append(preference_group)
|
||||||
|
|
||||||
|
api_url_entry = Adw.EntryRow()
|
||||||
|
api_url_entry.set_title("API Url")
|
||||||
|
# set value
|
||||||
|
api_url_entry.set_text("https://api.bitwarden.com/")
|
||||||
|
preference_group.add(api_url_entry)
|
||||||
|
|
||||||
|
identity_url_entry = Adw.EntryRow()
|
||||||
|
identity_url_entry.set_title("Identity Url")
|
||||||
|
identity_url_entry.set_text("https://identity.bitwarden.com/")
|
||||||
|
preference_group.add(identity_url_entry)
|
||||||
|
|
||||||
|
notification_url_entry = Adw.EntryRow()
|
||||||
|
notification_url_entry.set_title("Notification URL")
|
||||||
|
notification_url_entry.set_text("https://notifications.bitwarden.com/")
|
||||||
|
preference_group.add(notification_url_entry)
|
||||||
|
|
||||||
|
auth_preference_group = Adw.PreferencesGroup()
|
||||||
|
auth_preference_group.set_title("Authentication")
|
||||||
|
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)
|
||||||
|
|
||||||
|
dialog.add_button("Login", Gtk.ResponseType.OK)
|
||||||
|
def on_save(res):
|
||||||
|
if res != Gtk.ResponseType.OK:
|
||||||
|
return
|
||||||
|
goldwarden.set_api_url(api_url_entry.get_text())
|
||||||
|
goldwarden.set_identity_url(identity_url_entry.get_text())
|
||||||
|
goldwarden.set_notification_url(notification_url_entry.get_text())
|
||||||
|
def login():
|
||||||
|
res = goldwarden.login_with_password(email_entry.get_text(), "password")
|
||||||
|
def handle_res():
|
||||||
|
print("handle res", res)
|
||||||
|
if res == "ok":
|
||||||
|
dialog.close()
|
||||||
|
print("ok")
|
||||||
|
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()
|
||||||
|
|
||||||
|
#ok response
|
||||||
|
dialog.connect("response", lambda dialog, response: on_save(response))
|
||||||
|
dialog.set_default_size(400, 200)
|
||||||
|
dialog.set_modal(True)
|
||||||
|
dialog.present()
|
||||||
|
|
||||||
|
app.run(sys.argv)
|
Loading…
Reference in New Issue