diff --git a/.gitignore b/.gitignore
index 4fffb2f..cebbfcc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/target
/Cargo.lock
+.dir-locals.el
diff --git a/simplelogin-gui/.gitignore b/simplelogin-gui/.gitignore
new file mode 100644
index 0000000..acba102
--- /dev/null
+++ b/simplelogin-gui/.gitignore
@@ -0,0 +1 @@
+/res/gschemas.compiled
\ No newline at end of file
diff --git a/simplelogin-gui/Cargo.toml b/simplelogin-gui/Cargo.toml
index d6ad723..396c343 100644
--- a/simplelogin-gui/Cargo.toml
+++ b/simplelogin-gui/Cargo.toml
@@ -6,3 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+gtk4 = { version = "0.7.3", features = ["blueprint"] }
+simplelogin = { version = "0.1.0", path = "../simplelogin" }
+tokio = { version = "1.33.0", features = ["full"] }
diff --git a/simplelogin-gui/build.rs b/simplelogin-gui/build.rs
new file mode 100644
index 0000000..5f3d2d6
--- /dev/null
+++ b/simplelogin-gui/build.rs
@@ -0,0 +1,29 @@
+use std::{env, io, fs, path::Path, process::Command};
+
+const SCHEMA_NAME: &'static str = "im.zander.SimpleLogin.gschema.xml";
+
+fn main() -> io::Result<()> {
+ println!("cargo:rerun-if-changed=data/{}", SCHEMA_NAME);
+ #[cfg(debug_assertions)] {
+ let out_dir = env::var("OUT_DIR").unwrap();
+ let source_path = Path::new("data/").join(SCHEMA_NAME);
+ let compile_dir = Path::new(&out_dir).join("schemas");
+ let compile_path = compile_dir.join(SCHEMA_NAME);
+ eprintln!("Creating \"{}\"...", compile_dir.display());
+ fs::create_dir_all(&compile_dir)?;
+ eprintln!(
+ "Copping \"{}\" to \"{}\"...",
+ source_path.display(),
+ compile_path.display()
+ );
+ fs::copy(source_path, &compile_path)?;
+ eprintln!("Compiling schemas...");
+ let status = Command::new("glib-compile-schemas")
+ .args(["--strict", compile_dir.to_str().unwrap()])
+ .status()?;
+ if !status.success() {
+ panic!("failed to compile schemas");
+ }
+ }
+ Ok(())
+}
diff --git a/simplelogin-gui/data/im.zander.SimpleLogin.gschema.xml b/simplelogin-gui/data/im.zander.SimpleLogin.gschema.xml
new file mode 100644
index 0000000..bf42b08
--- /dev/null
+++ b/simplelogin-gui/data/im.zander.SimpleLogin.gschema.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ ""
+ Api key
+ The api key for the current user
+
+
+ (-1,-1)
+
+
+
diff --git a/simplelogin-gui/data/login_window.blp b/simplelogin-gui/data/login_window.blp
new file mode 100644
index 0000000..fe14482
--- /dev/null
+++ b/simplelogin-gui/data/login_window.blp
@@ -0,0 +1,240 @@
+using Gtk 4.0;
+
+Popover device_entry_popover {
+ child: Label {
+ accessible-role: tooltip;
+ label: "This is the human readable name that will show up in the API key section of the SimpleLogin website.";
+ max-width-chars: 30;
+ wrap: true;
+ }
+
+ ;
+}
+
+template $LoginWindow : ApplicationWindow {
+ title: "SimpleLogin";
+
+ Box {
+ orientation: vertical;
+
+ Overlay forms_overlay {
+ child: forms_wrapper;
+ valign: end;
+ vexpand: true;
+
+ Box forms_wrapper {
+ valign: end;
+ vexpand: true;
+
+ Grid {
+ halign: end;
+ hexpand: true;
+ margin-bottom: 10;
+ margin-end: 10;
+ margin-start: 10;
+ margin-top: 10;
+ valign: center;
+
+ Label {
+ hexpand: true;
+ label: "Login:";
+ margin-bottom: 5;
+ vexpand: true;
+
+ styles [
+ "login-title-label",
+ ]
+
+ layout {
+ column: "0";
+ column-span: "2";
+ row: "0";
+ }
+ }
+
+ Label error_label {
+ label: "ERROR";
+ margin-top: 5;
+
+ styles [
+ "error-message-label",
+ ]
+
+ layout {
+ column: "0";
+ column-span: "2";
+ row: "4";
+ }
+ }
+
+ Entry device_entry {
+ margin-start: 6;
+ placeholder-text: "My Awesome Computer";
+ secondary-icon-name: "dialog-question-symbolic";
+ truncate-multiline: true;
+ width-chars: 18;
+
+ layout {
+ column: "1";
+ row: "3";
+ }
+ }
+
+ Label {
+ label: "Device:";
+ xalign: 0.0;
+
+ layout {
+ column: "0";
+ row: "3";
+ }
+ }
+
+ Label {
+ label: "Email:";
+ xalign: 0.0;
+
+ layout {
+ column: "0";
+ row: "1";
+ }
+ }
+
+ Label {
+ label: "Password:";
+ xalign: 0.0;
+
+ layout {
+ column: "0";
+ row: "2";
+ }
+ }
+
+ Entry email_entry {
+ hexpand: true;
+ input-purpose: email;
+ margin-start: 6;
+ receives-default: true;
+ truncate-multiline: true;
+
+ styles [
+ ]
+
+ layout {
+ column: "1";
+ row: "1";
+ }
+ }
+
+ PasswordEntry password_entry {
+ hexpand: true;
+ margin-start: 6;
+ show-peek-icon: true;
+
+ layout {
+ column: "1";
+ row: "2";
+ }
+ }
+
+ Button login_button {
+ halign: center;
+ label: "Login";
+ margin-top: 5;
+ sensitive: false;
+ valign: center;
+
+ layout {
+ column: "0";
+ column-span: "2";
+ row: "5";
+ }
+ }
+
+ styles [
+ "login-form-warpper",
+ ]
+ }
+
+ Label {
+ halign: center;
+ justify: center;
+ label: "- or -";
+ }
+
+ Box {
+ halign: start;
+ hexpand: true;
+ margin-bottom: 10;
+ margin-end: 10;
+ margin-start: 10;
+ margin-top: 10;
+ orientation: vertical;
+ valign: center;
+
+ Label {
+ hexpand: true;
+ label: "Set API Key:";
+ margin-bottom: 5;
+
+ styles [
+ "login-title-label",
+ ]
+ }
+
+ PasswordEntry api_key_entry {
+ hexpand: true;
+ margin-end: 3;
+ margin-start: 8;
+ show-peek-icon: true;
+ width-chars: 27;
+ }
+
+ Button api_key_button {
+ halign: center;
+ label: "Accept";
+ margin-top: 5;
+ sensitive: false;
+ }
+ }
+ }
+ }
+
+ Label {
+ label: "Don\'t have an account? You can register here.";
+ margin-bottom: 10;
+ use-markup: true;
+ valign: start;
+ vexpand: true;
+ yalign: 0.0;
+ }
+ }
+}
+
+Box loading_overlay {
+ orientation: vertical;
+
+ Spinner {
+ spinning: true;
+ valign: end;
+ vexpand: true;
+
+ styles [
+ "loading-label",
+ ]
+ }
+
+ Label {
+ label: "Loading...";
+ valign: start;
+ vexpand: true;
+
+ styles [
+ "loading-label",
+ ]
+ }
+
+ styles [
+ "loading-label",
+ ]
+}
diff --git a/simplelogin-gui/data/totp_window.blp b/simplelogin-gui/data/totp_window.blp
new file mode 100644
index 0000000..a2ca672
--- /dev/null
+++ b/simplelogin-gui/data/totp_window.blp
@@ -0,0 +1,121 @@
+using Gtk 4.0;
+
+template $TotpWindow : Window {
+ modal: true;
+ title: "TOTP Entry";
+
+ Overlay overlay {
+ child: form_wrapper;
+ halign: center;
+ hexpand: true;
+ valign: center;
+ vexpand: true;
+
+ Grid form_wrapper {
+ column-spacing: 5;
+ halign: center;
+ hexpand: true;
+ margin-bottom: 10;
+ margin-end: 10;
+ margin-start: 10;
+ margin-top: 10;
+ row-spacing: 5;
+ valign: center;
+ vexpand: true;
+
+ Box {
+ halign: center;
+ hexpand: true;
+
+ Image {
+ halign: center;
+ icon-name: "dialog-password-symbolic";
+ }
+
+ Label {
+ label: "TOTP";
+
+ styles [
+ "login-title-label",
+ ]
+ }
+
+ layout {
+ column: "0";
+ column-span: "2";
+ row: "0";
+ }
+ }
+
+ Button accept_button {
+ label: "Accept";
+
+ layout {
+ column: "1";
+ row: "3";
+ }
+ }
+
+ Button cancel_button {
+ label: "Cancel";
+
+ layout {
+ column: "0";
+ row: "3";
+ }
+ }
+
+ Entry code_entry {
+ placeholder-text: "TOTP Code";
+
+ layout {
+ column: "0";
+ column-span: "2";
+ row: "1";
+ }
+ }
+
+ Label error_label {
+ label: "ERROR";
+
+ styles [
+ "error-message-label",
+ ]
+
+ layout {
+ column: "0";
+ column-span: "2";
+ row: "2";
+ }
+ }
+ }
+ }
+}
+
+Box loading_overlay {
+ orientation: vertical;
+
+ Spinner {
+ spinning: true;
+ valign: end;
+ vexpand: true;
+
+ styles [
+ "loading-label",
+ ]
+ }
+
+ Label {
+ label: "Loading...";
+ valign: start;
+ vexpand: true;
+
+ styles [
+ "loading-label",
+ ]
+ }
+
+ styles [
+ "loading-label",
+ ]
+}
diff --git a/simplelogin-gui/res.old/global.css b/simplelogin-gui/res.old/global.css
new file mode 100644
index 0000000..f5c5b89
--- /dev/null
+++ b/simplelogin-gui/res.old/global.css
@@ -0,0 +1,15 @@
+.login-title-label {
+ font-size: x-large;
+}
+
+.error-message-label {
+ color: red;
+}
+
+.loading-label {
+ font-size: xx-large;
+}
+
+.bold {
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/simplelogin-gui/res.old/main-panel.ui b/simplelogin-gui/res.old/main-panel.ui
new file mode 100644
index 0000000..873787e
--- /dev/null
+++ b/simplelogin-gui/res.old/main-panel.ui
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
+
+ vertical
+
+
+ True
+ end
+ True
+
+
+
+
+
+ Loading...
+ start
+ True
+
+
+
+
+
+
+ True
+ No more results
+ True
+
+
+
+ True
+ vertical
+
+
+ center
+ end
+
+
+ center
+ view-refresh-symbolic
+ end
+ True
+
+
+
+
+
+ center
+ window-close-symbolic
+ end
+
+
+
+
+
+
+
+ ERROR
+ start
+ True
+
+
+
+
+
+
diff --git a/simplelogin-gui/res.old/settings-panel.ui b/simplelogin-gui/res.old/settings-panel.ui
new file mode 100644
index 0000000..3996bee
--- /dev/null
+++ b/simplelogin-gui/res.old/settings-panel.ui
@@ -0,0 +1,263 @@
+
+
+
+
+
+
+
+ content_wrapper
+ True
+
+
+ True
+ True
+
+
+ True
+ True
+ start
+
+
+ False
+
+
+ center
+ user-info-symbolic
+ large
+ 5
+ center
+ True
+
+
+
+
+ 0
+ 1
+ 0
+ 2
+
+
+
+
+
+
+
+ window-close-symbolic
+
+
+
+
+ False
+ False
+ True
+ no-emoji | no-spellcheck
+ Testy McTesterface
+
+
+
+
+ 1
+ 1
+ 0
+ 1
+
+
+
+
+
+
+
+ start
+ Edit
+ center
+
+
+
+
+ Cancel
+
+
+
+ 2
+ 1
+ 0
+ 1
+
+
+
+
+
+ center
+ True
+ Stats go here
+
+ 0
+ 3
+ 2
+ 1
+
+
+
+
+
+ True
+ mctesterface@test.tld
+ 0.0
+
+ 1
+ 1
+
+
+
+
+
+ start
+ Logout
+ center
+
+ 2
+ 1
+
+
+
+
+
+ Account Status:
+ 5
+ 1.0
+
+ 0
+ 1
+ 3
+ 1
+
+
+
+
+
+ Api Key:
+ 5
+ 1.0
+
+
+ 0
+ 4
+
+
+
+
+
+ False
+ False
+ True
+ True
+ center
+
+ 1
+ 4
+
+
+
+
+
+ True
+ Premium
+ 0.0
+
+ 1
+ 1
+ 3
+ 1
+
+
+
+
+
+ start
+ Clear
+ center
+
+ 2
+ 4
+
+
+
+
+
+
+
+
+
+
+
+ True
+ vertical
+
+
+ True
+ end
+ True
+
+
+
+
+
+ Loading...
+ start
+ True
+
+
+
+
+
+
+ True
+ vertical
+
+
+ center
+ end
+
+
+ center
+ view-refresh-symbolic
+ end
+ True
+
+
+
+
+
+ center
+ window-close-symbolic
+ end
+
+
+
+
+
+
+
+ ERROR
+ start
+ True
+
+
+
+
+
diff --git a/simplelogin-gui/res.old/simplelogin.cmb b/simplelogin-gui/res.old/simplelogin.cmb
new file mode 100644
index 0000000..c4deb09
--- /dev/null
+++ b/simplelogin-gui/res.old/simplelogin.cmb
@@ -0,0 +1,531 @@
+
+
+
+
+ (1,1,"main-window.ui","main-panel.ui",None,None,None,None,None,None,""),
+ (3,18,None,"settings-panel.ui",None,None,None,None,None,None,None),
+ (4,13,None,"login-window.ui",None,None,None,None,None,None,None),
+ (5,1,None,"totp-dialog.ui",None,None,None,None,None,None,None)
+
+
+ (1,"global.css",None,1)
+
+
+ (1,1),
+ (1,3),
+ (1,4),
+ (1,5)
+
+
+ (1,1,"GtkBox","SLMainPanel",None,None,None,None,None,None),
+ (1,2,"GtkOverlay","overlay",1,None,None,None,None,None),
+ (1,3,"GtkBox","content_wrapper",2,None,None,None,None,None),
+ (1,4,"GtkBox",None,3,None,None,None,None,None),
+ (1,5,"GtkSearchEntry","search_box",4,None,None,None,None,None),
+ (1,8,"GtkDropDown","view_dropdown",4,None,None,None,1,None),
+ (1,9,"GtkStringList",None,8,None,None,None,None,None),
+ (1,10,"GtkButton","search_button",4,None,None,None,2,None),
+ (1,11,"GtkBox",None,3,None,None,None,1,None),
+ (1,12,"GtkButton","new_button",11,None,None,None,None,None),
+ (1,13,"GtkButton","edit_button",11,None,None,None,1,None),
+ (1,14,"GtkButton","delete_button",11,None,None,None,2,None),
+ (1,22,"GtkBox","loading_overlay",None,None,None,None,None,None),
+ (1,23,"GtkSpinner",None,22,None,None,None,None,None),
+ (1,24,"GtkLabel",None,22,None,None,None,1,None),
+ (1,28,"GtkButton","prev_page_button",11,None,None,None,3,None),
+ (1,29,"GtkButton","next_page_button",11,None,None,None,5,None),
+ (1,30,"GtkEntry","page_label",11,None,None,None,4,None),
+ (1,31,"GtkOverlay","scroll_overlay",3,None,None,None,2,None),
+ (1,32,"GtkScrolledWindow",None,31,None,None,None,None,None),
+ (1,33,"GtkListView","alias_list_view",32,None,None,None,None,None),
+ (1,34,"GtkSignalListItemFactory","alias_item_factory",33,None,None,None,None,None),
+ (1,35,"GtkMultiSelection","alias_selection_model",33,None,None,None,None,None),
+ (1,36,"GtkLabel","no_content_label",None,None,None,None,None,None),
+ (1,37,"GtkBox","error_overlay",None,None,None,None,None,None),
+ (1,38,"GtkBox",None,37,None,None,None,None,None),
+ (1,39,"GtkButton","error_refresh_button",38,None,None,None,None,None),
+ (1,40,"GtkButton","error_close_button",38,None,None,None,1,None),
+ (1,41,"GtkLabel","error_label",37,None,None,None,1,None),
+ (3,18,"GtkBox","SLSettingsPanel",None,None,None,None,None,None),
+ (3,19,"GtkOverlay","overlay",18,None,None,None,None,None),
+ (3,30,"GtkBox","loading_overlay",None,None,None,None,None,None),
+ (3,31,"GtkSpinner",None,30,None,None,None,None,None),
+ (3,32,"GtkLabel",None,30,None,None,None,1,None),
+ (3,33,"GtkBox","error_overlay",None,None,None,None,None,None),
+ (3,35,"GtkLabel","error_label",33,None,None,None,1,None),
+ (3,45,"GtkBox",None,33,None,None,None,None,None),
+ (3,46,"GtkButton","error_refresh_button",45,None,None,None,None,None),
+ (3,47,"GtkButton","error_close_button",45,None,None,None,1,None),
+ (3,48,"GtkBox","content_wrapper",19,None,None,None,-1,None),
+ (3,49,"GtkGrid","content",48,None,None,None,None,None),
+ (3,51,"GtkLabel","stats_label",49,None,None,None,1,None),
+ (3,52,"GtkLabel","email_label",49,None,None,None,2,None),
+ (3,53,"GtkButton","logout_button",49,None,None,None,3,None),
+ (3,55,"GtkLabel",None,49,None,None,None,5,None),
+ (3,57,"GtkLabel",None,49,None,None,None,7,None),
+ (3,58,"GtkPasswordEntry","api_key_field",49,None,None,None,8,None),
+ (3,59,"GtkLabel","status_label",49,None,None,None,9,None),
+ (3,60,"GtkButton","api_key_clear_button",49,None,None,None,10,None),
+ (3,63,"GtkButton","profile_image_button",49,None,None,None,None,""),
+ (3,64,"GtkImage","profile_image",63,None,None,None,None,None),
+ (3,65,"GtkBox",None,49,None,None,None,None,None),
+ (3,66,"GtkButton","remove_picture_button",65,None,None,None,None,None),
+ (3,67,"GtkEntry","name_label",65,None,None,None,1,None),
+ (3,68,"GtkBox",None,49,None,None,None,None,None),
+ (3,69,"GtkButton","edit_button",68,None,None,None,None,None),
+ (3,70,"GtkButton","cancel_edit_button",68,None,None,None,1,None),
+ (4,11,"GtkPopover","device_entry_popover",None,None,None,None,None,None),
+ (4,12,"GtkLabel",None,11,None,None,None,None,None),
+ (4,13,"GtkApplicationWindow","LoginWindow",None,None,None,None,None,None),
+ (4,14,"GtkBox",None,13,None,None,None,-1,None),
+ (4,30,"GtkLabel",None,14,None,None,None,1,None),
+ (4,31,"GtkOverlay","forms_overlay",14,None,None,None,None,None),
+ (4,32,"GtkBox","forms_wrapper",31,None,None,None,None,None),
+ (4,33,"GtkGrid",None,32,None,None,None,None,None),
+ (4,34,"GtkLabel",None,33,None,None,None,None,None),
+ (4,35,"GtkLabel","error_label",33,None,None,None,1,None),
+ (4,36,"GtkEntry","device_entry",33,None,None,None,2,None),
+ (4,37,"GtkLabel",None,33,None,None,None,3,None),
+ (4,38,"GtkLabel",None,33,None,None,None,4,None),
+ (4,39,"GtkLabel",None,33,None,None,None,5,None),
+ (4,40,"GtkEntry","email_entry",33,None,None,None,6,None),
+ (4,41,"GtkPasswordEntry","password_entry",33,None,None,None,7,None),
+ (4,42,"GtkButton","login_button",33,None,None,None,8,None),
+ (4,43,"GtkLabel",None,32,None,None,None,1,None),
+ (4,44,"GtkBox",None,32,None,None,None,2,None),
+ (4,45,"GtkLabel",None,44,None,None,None,None,None),
+ (4,46,"GtkPasswordEntry","api_key_entry",44,None,None,None,1,None),
+ (4,47,"GtkButton","api_key_button",44,None,None,None,2,None),
+ (4,48,"GtkBox","loading_overlay",None,None,None,None,None,None),
+ (4,49,"GtkSpinner",None,48,None,None,None,None,None),
+ (4,50,"GtkLabel",None,48,None,None,None,1,None),
+ (5,1,"GtkWindow","SLTotpDialog",None,None,None,None,None,None),
+ (5,2,"GtkOverlay","overlay",1,None,None,None,-1,None),
+ (5,11,"GtkBox","loading_overlay",None,None,None,None,None,None),
+ (5,12,"GtkSpinner",None,11,None,None,None,None,None),
+ (5,13,"GtkLabel",None,11,None,None,None,1,None),
+ (5,14,"GtkGrid","form_wrapper",2,None,None,None,None,None),
+ (5,15,"GtkBox",None,14,None,None,None,None,None),
+ (5,16,"GtkImage",None,15,None,None,None,None,None),
+ (5,17,"GtkLabel",None,15,None,None,None,1,None),
+ (5,19,"GtkButton","accept_button",14,None,None,None,2,None),
+ (5,20,"GtkButton","cancel_button",14,None,None,None,3,None),
+ (5,21,"GtkEntry","code_entry",14,None,None,None,4,None),
+ (5,23,"GtkLabel","error_label",14,None,None,None,4,None)
+
+
+ (1,3,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (1,3,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,5,"GtkSearchEntry","placeholder-text","Filter by email, description, etc.",None,None,None,None,None,None,None,None,None),
+ (1,5,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,8,"GtkDropDown","model",None,None,None,None,None,9,None,None,None,None),
+ (1,8,"GtkDropDown","selected","0",None,None,None,None,None,None,None,None,None),
+ (1,8,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,10,"GtkButton","label","Search",None,None,None,None,None,None,None,None,None),
+ (1,12,"GtkButton","label","New",None,None,None,None,None,None,None,None,None),
+ (1,13,"GtkButton","label","Edit",None,None,None,None,None,None,None,None,None),
+ (1,14,"GtkButton","label","Delete",None,None,None,None,None,None,None,None,None),
+ (1,22,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (1,23,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
+ (1,23,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (1,23,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,24,"GtkLabel","label","Loading...",None,None,None,None,None,None,None,None,None),
+ (1,24,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
+ (1,24,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,28,"GtkButton","icon-name","go-previous-symbolic",None,None,None,None,None,None,None,None,None),
+ (1,28,"GtkWidget","halign","end",None,None,None,None,None,None,None,None,None),
+ (1,28,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,29,"GtkButton","icon-name","go-next-symbolic",None,None,None,None,None,None,None,None,None),
+ (1,30,"GtkEditable","max-width-chars","0",None,None,None,None,None,None,None,None,None),
+ (1,30,"GtkEditable","text","1",None,None,None,None,None,None,None,None,None),
+ (1,30,"GtkEditable","width-chars","5",None,None,None,None,None,None,None,None,None),
+ (1,30,"GtkEditable","xalign","0.5",None,None,None,None,None,None,None,None,None),
+ (1,30,"GtkEntry","has-frame","False",None,None,None,None,None,None,None,None,None),
+ (1,30,"GtkEntry","input-hints","no-emoji | no-spellcheck",None,None,None,None,None,None,None,None,None),
+ (1,30,"GtkEntry","input-purpose","digits",None,None,None,None,None,None,None,None,None),
+ (1,30,"GtkEntry","truncate-multiline","True",None,None,None,None,None,None,None,None,None),
+ (1,32,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,33,"GtkListView","factory",None,None,None,None,None,34,None,None,None,None),
+ (1,33,"GtkListView","model",None,None,None,None,None,35,None,None,None,None),
+ (1,36,"GtkLabel","label","No more results",None,None,None,None,None,None,None,None,None),
+ (1,36,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,36,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,37,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (1,37,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,38,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (1,38,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (1,39,"GtkButton","icon-name","view-refresh-symbolic",None,None,None,None,None,None,None,None,None),
+ (1,39,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (1,39,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (1,39,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (1,40,"GtkButton","icon-name","window-close-symbolic",None,None,None,None,None,None,None,None,None),
+ (1,40,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (1,40,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (1,41,"GtkLabel","label","ERROR",None,None,None,None,None,None,None,None,None),
+ (1,41,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
+ (1,41,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,19,"GtkOverlay","child","48",None,None,None,None,None,None,None,None,None),
+ (3,19,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,30,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (3,30,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,31,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
+ (3,31,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (3,31,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,32,"GtkLabel","label","Loading...",None,None,None,None,None,None,None,None,None),
+ (3,32,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
+ (3,32,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,33,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (3,33,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,35,"GtkLabel","label","ERROR",None,None,None,None,None,None,None,None,None),
+ (3,35,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
+ (3,35,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,45,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (3,45,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (3,46,"GtkButton","icon-name","view-refresh-symbolic",None,None,None,None,None,None,None,None,None),
+ (3,46,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (3,46,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (3,46,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,47,"GtkButton","icon-name","window-close-symbolic",None,None,None,None,None,None,None,None,None),
+ (3,47,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (3,47,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (3,48,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,48,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,49,"GtkGrid","row-homogeneous","True",None,None,None,None,None,None,None,None,None),
+ (3,49,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,49,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
+ (3,51,"GtkLabel","label","Stats go here",None,None,None,None,None,None,None,None,None),
+ (3,51,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (3,51,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,52,"GtkLabel","label","mctesterface@test.tld",None,None,None,None,None,None,None,None,None),
+ (3,52,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
+ (3,52,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,53,"GtkButton","label","Logout",None,None,None,None,None,None,None,None,None),
+ (3,53,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
+ (3,53,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (3,55,"GtkLabel","label","Account Status:",None,None,None,None,None,None,None,None,None),
+ (3,55,"GtkLabel","xalign","1.0",None,None,None,None,None,None,None,None,None),
+ (3,55,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None),
+ (3,57,"GtkLabel","label","Api Key:",None,None,None,None,None,None,None,None,None),
+ (3,57,"GtkLabel","xalign","1.0",None,None,None,None,None,None,None,None,None),
+ (3,57,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None),
+ (3,58,"GtkEditable","editable","False",None,None,None,None,None,None,None,None,None),
+ (3,58,"GtkEditable","enable-undo","False",None,None,None,None,None,None,None,None,None),
+ (3,58,"GtkPasswordEntry","show-peek-icon","True",None,None,None,None,None,None,None,None,None),
+ (3,58,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,58,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (3,59,"GtkLabel","label","Premium",None,None,None,None,None,None,None,None,None),
+ (3,59,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
+ (3,59,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,60,"GtkButton","label","Clear",None,None,None,None,None,None,None,None,None),
+ (3,60,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
+ (3,60,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (3,63,"GtkButton","has-frame","False",None,None,None,None,None,None,None,None,None),
+ (3,64,"GtkImage","icon-name","user-info-symbolic",None,None,None,None,None,None,None,None,None),
+ (3,64,"GtkImage","icon-size","large",None,None,None,None,None,None,None,None,None),
+ (3,64,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (3,64,"GtkWidget","margin-end","5",None,None,None,None,None,None,None,None,None),
+ (3,64,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (3,64,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,66,"GtkButton","icon-name","window-close-symbolic",None,None,None,None,None,None,None,None,None),
+ (3,67,"GtkEditable","editable","False",None,None,None,None,None,None,None,None,None),
+ (3,67,"GtkEditable","text","Testy McTesterface",None,None,None,None,None,None,None,None,None),
+ (3,67,"GtkEntry","has-frame","False",None,None,None,None,None,None,None,None,None),
+ (3,67,"GtkEntry","input-hints","no-emoji | no-spellcheck",None,None,None,None,None,None,None,None,None),
+ (3,67,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (3,69,"GtkButton","label","Edit",None,None,None,None,None,None,None,None,None),
+ (3,69,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
+ (3,69,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (3,70,"GtkButton","label","Cancel",None,None,None,None,None,None,None,None,None),
+ (4,11,"GtkPopover","child",None,None,None,None,None,12,None,None,None,None),
+ (4,12,"GtkAccessible","accessible-role","tooltip",None,None,None,None,None,None,None,None,None),
+ (4,12,"GtkLabel","label","This is the human readable name that will show up in the API key section of the SimpleLogin website.",None,None,None,None,None,None,None,None,None),
+ (4,12,"GtkLabel","max-width-chars","30",None,None,None,None,None,None,None,None,None),
+ (4,12,"GtkLabel","wrap","True",None,None,None,None,None,None,None,None,None),
+ (4,13,"GtkWindow","title","SimpleLogin",None,None,None,None,None,None,None,None,None),
+ (4,14,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (4,30,"GtkLabel","label","Don't have an account? You can register <a href=\"https://app.simplelogin.io/auth/register\">here</a>.",None,None,None,None,None,None,None,None,None),
+ (4,30,"GtkLabel","use-markup","True",None,None,None,None,None,None,None,None,None),
+ (4,30,"GtkLabel","yalign","0.0",None,None,None,None,None,None,None,None,None),
+ (4,30,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None),
+ (4,30,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
+ (4,30,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,31,"GtkOverlay","child","32",None,None,None,None,None,None,None,None,None),
+ (4,31,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (4,31,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,32,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (4,32,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,33,"GtkWidget","halign","end",None,None,None,None,None,None,None,None,None),
+ (4,33,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,33,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None),
+ (4,33,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
+ (4,33,"GtkWidget","margin-start","10",None,None,None,None,None,None,None,None,None),
+ (4,33,"GtkWidget","margin-top","10",None,None,None,None,None,None,None,None,None),
+ (4,33,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (4,34,"GtkLabel","label","Login:",None,None,None,None,None,None,None,None,None),
+ (4,34,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,34,"GtkWidget","margin-bottom","5",None,None,None,None,None,None,None,None,None),
+ (4,34,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,35,"GtkLabel","label","ERROR",None,None,None,None,None,None,None,None,None),
+ (4,35,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None),
+ (4,36,"GtkEditable","width-chars","18",None,None,None,None,None,None,None,None,None),
+ (4,36,"GtkEntry","placeholder-text","My Awesome Computer",None,None,None,None,None,None,None,None,None),
+ (4,36,"GtkEntry","secondary-icon-name","dialog-question-symbolic",None,None,None,None,None,None,None,None,None),
+ (4,36,"GtkEntry","truncate-multiline","True",None,None,None,None,None,None,None,None,None),
+ (4,36,"GtkWidget","margin-start","6",None,None,None,None,None,None,None,None,None),
+ (4,37,"GtkLabel","label","Device:",None,None,None,None,None,None,None,None,None),
+ (4,37,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
+ (4,38,"GtkLabel","label","Email:",None,None,None,None,None,None,None,None,None),
+ (4,38,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
+ (4,39,"GtkLabel","label","Password:",None,None,None,None,None,None,None,None,None),
+ (4,39,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
+ (4,40,"GtkEntry","input-purpose","email",None,None,None,None,None,None,None,None,None),
+ (4,40,"GtkEntry","truncate-multiline","True",None,None,None,None,None,None,None,None,None),
+ (4,40,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,40,"GtkWidget","margin-start","6",None,None,None,None,None,None,None,None,None),
+ (4,40,"GtkWidget","receives-default","True",None,None,None,None,None,None,None,None,None),
+ (4,41,"GtkPasswordEntry","show-peek-icon","True",None,None,None,None,None,None,None,None,None),
+ (4,41,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,41,"GtkWidget","margin-start","6",None,None,None,None,None,None,None,None,None),
+ (4,42,"GtkButton","label","Login",None,None,None,None,None,None,None,None,None),
+ (4,42,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (4,42,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None),
+ (4,42,"GtkWidget","sensitive","False",None,None,None,None,None,None,None,None,None),
+ (4,42,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (4,43,"GtkLabel","justify","center",None,None,None,None,None,None,None,None,None),
+ (4,43,"GtkLabel","label","- or -",None,None,None,None,None,None,None,None,None),
+ (4,43,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (4,44,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (4,44,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
+ (4,44,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,44,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None),
+ (4,44,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
+ (4,44,"GtkWidget","margin-start","10",None,None,None,None,None,None,None,None,None),
+ (4,44,"GtkWidget","margin-top","10",None,None,None,None,None,None,None,None,None),
+ (4,44,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (4,45,"GtkLabel","label","Set API Key:",None,None,None,None,None,None,None,None,None),
+ (4,45,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,45,"GtkWidget","margin-bottom","5",None,None,None,None,None,None,None,None,None),
+ (4,46,"GtkEditable","width-chars","27",None,None,None,None,None,None,None,None,None),
+ (4,46,"GtkPasswordEntry","show-peek-icon","True",None,None,None,None,None,None,None,None,None),
+ (4,46,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,46,"GtkWidget","margin-end","3",None,None,None,None,None,None,None,None,None),
+ (4,46,"GtkWidget","margin-start","8",None,None,None,None,None,None,None,None,None),
+ (4,47,"GtkButton","label","Accept",None,None,None,None,None,None,None,None,None),
+ (4,47,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (4,47,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None),
+ (4,47,"GtkWidget","sensitive","False",None,None,None,None,None,None,None,None,None),
+ (4,48,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (4,49,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
+ (4,49,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (4,49,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (4,50,"GtkLabel","label","Loading...",None,None,None,None,None,None,None,None,None),
+ (4,50,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
+ (4,50,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (5,1,"GtkWindow","modal","True",None,None,None,None,None,None,None,None,None),
+ (5,1,"GtkWindow","title","TOTP Entry",None,None,None,None,None,None,None,None,None),
+ (5,2,"GtkOverlay","child","14",None,None,None,None,None,None,None,None,None),
+ (5,2,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (5,2,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (5,2,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (5,2,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (5,11,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
+ (5,12,"GtkSpinner","spinning","True",None,None,None,None,None,None,None,None,None),
+ (5,12,"GtkWidget","valign","end",None,None,None,None,None,None,None,None,None),
+ (5,12,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (5,13,"GtkLabel","label","Loading...",None,None,None,None,None,None,None,None,None),
+ (5,13,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
+ (5,13,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkGrid","column-spacing","5",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkGrid","row-spacing","5",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkWidget","margin-bottom","10",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkWidget","margin-start","10",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkWidget","margin-top","10",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkWidget","valign","center",None,None,None,None,None,None,None,None,None),
+ (5,14,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
+ (5,15,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (5,15,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
+ (5,16,"GtkImage","icon-name","dialog-password-symbolic",None,None,None,None,None,None,None,None,None),
+ (5,16,"GtkWidget","halign","center",None,None,None,None,None,None,None,None,None),
+ (5,17,"GtkLabel","label","TOTP",None,None,None,None,None,None,None,None,None),
+ (5,19,"GtkButton","label","Accept",None,None,None,None,None,None,None,None,None),
+ (5,20,"GtkButton","label","Cancel",None,None,None,None,None,None,None,None,None),
+ (5,21,"GtkEntry","placeholder-text","TOTP Code",None,None,None,None,None,None,None,None,None),
+ (5,23,"GtkLabel","label","ERROR",None,None,None,None,None,None,None,None,None)
+
+
+ (3,49,51,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (3,49,51,"GtkGridLayoutChild","column-span","3",None,None,None,None),
+ (3,49,51,"GtkGridLayoutChild","row","2",None,None,None,None),
+ (3,49,51,"GtkGridLayoutChild","row-span","1",None,None,None,None),
+ (3,49,52,"GtkGridLayoutChild","column","1",None,None,None,None),
+ (3,49,52,"GtkGridLayoutChild","row","1",None,None,None,None),
+ (3,49,53,"GtkGridLayoutChild","column","2",None,None,None,None),
+ (3,49,53,"GtkGridLayoutChild","row","1",None,None,None,None),
+ (3,49,55,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (3,49,55,"GtkGridLayoutChild","column-span","1",None,None,None,None),
+ (3,49,55,"GtkGridLayoutChild","row","3",None,None,None,None),
+ (3,49,55,"GtkGridLayoutChild","row-span","1",None,None,None,None),
+ (3,49,57,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (3,49,57,"GtkGridLayoutChild","row","4",None,None,None,None),
+ (3,49,58,"GtkGridLayoutChild","column","1",None,None,None,None),
+ (3,49,58,"GtkGridLayoutChild","row","4",None,None,None,None),
+ (3,49,59,"GtkGridLayoutChild","column","1",None,None,None,None),
+ (3,49,59,"GtkGridLayoutChild","column-span","1",None,None,None,None),
+ (3,49,59,"GtkGridLayoutChild","row","3",None,None,None,None),
+ (3,49,59,"GtkGridLayoutChild","row-span","1",None,None,None,None),
+ (3,49,60,"GtkGridLayoutChild","column","2",None,None,None,None),
+ (3,49,60,"GtkGridLayoutChild","row","4",None,None,None,None),
+ (3,49,63,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (3,49,63,"GtkGridLayoutChild","column-span","1",None,None,None,None),
+ (3,49,63,"GtkGridLayoutChild","row-span","2",None,None,None,None),
+ (3,49,65,"GtkGridLayoutChild","column","1",None,None,None,None),
+ (3,49,65,"GtkGridLayoutChild","column-span","1",None,None,None,None),
+ (3,49,65,"GtkGridLayoutChild","row","0",None,None,None,None),
+ (3,49,65,"GtkGridLayoutChild","row-span","1",None,None,None,None),
+ (3,49,68,"GtkGridLayoutChild","column","2",None,None,None,None),
+ (3,49,68,"GtkGridLayoutChild","column-span","1",None,None,None,None),
+ (3,49,68,"GtkGridLayoutChild","row","0",None,None,None,None),
+ (3,49,68,"GtkGridLayoutChild","row-span","1",None,None,None,None),
+ (4,33,34,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (4,33,34,"GtkGridLayoutChild","column-span","2",None,None,None,None),
+ (4,33,34,"GtkGridLayoutChild","row","0",None,None,None,None),
+ (4,33,35,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (4,33,35,"GtkGridLayoutChild","column-span","2",None,None,None,None),
+ (4,33,35,"GtkGridLayoutChild","row","4",None,None,None,None),
+ (4,33,36,"GtkGridLayoutChild","column","1",None,None,None,None),
+ (4,33,36,"GtkGridLayoutChild","row","3",None,None,None,None),
+ (4,33,37,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (4,33,37,"GtkGridLayoutChild","row","3",None,None,None,None),
+ (4,33,38,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (4,33,38,"GtkGridLayoutChild","row","1",None,None,None,None),
+ (4,33,39,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (4,33,39,"GtkGridLayoutChild","row","2",None,None,None,None),
+ (4,33,40,"GtkGridLayoutChild","column","1",None,None,None,None),
+ (4,33,40,"GtkGridLayoutChild","row","1",None,None,None,None),
+ (4,33,41,"GtkGridLayoutChild","column","1",None,None,None,None),
+ (4,33,41,"GtkGridLayoutChild","row","2",None,None,None,None),
+ (4,33,42,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (4,33,42,"GtkGridLayoutChild","column-span","2",None,None,None,None),
+ (4,33,42,"GtkGridLayoutChild","row","5",None,None,None,None),
+ (5,14,15,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (5,14,15,"GtkGridLayoutChild","column-span","2",None,None,None,None),
+ (5,14,15,"GtkGridLayoutChild","row","0",None,None,None,None),
+ (5,14,19,"GtkGridLayoutChild","column","1",None,None,None,None),
+ (5,14,19,"GtkGridLayoutChild","row","3",None,None,None,None),
+ (5,14,20,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (5,14,20,"GtkGridLayoutChild","row","3",None,None,None,None),
+ (5,14,21,"GtkGridLayoutChild","column","0",None,None,None,None),
+ (5,14,21,"GtkGridLayoutChild","column-span","2",None,None,None,None),
+ (5,14,21,"GtkGridLayoutChild","row","1",None,None,None,None),
+ (5,14,23,"GtkGridLayoutChild","column-span","2",None,None,None,None),
+ (5,14,23,"GtkGridLayoutChild","row","2",None,None,None,None)
+
+
+ (4,34,"GtkWidget",1,1,None,None,None,None,None,None),
+ (4,34,"GtkWidget",2,2,None,1,None,None,None,None),
+ (4,35,"GtkWidget",1,1,None,None,None,None,None,None),
+ (4,35,"GtkWidget",2,2,None,1,None,None,None,None),
+ (4,40,"GtkWidget",1,1,None,None,None,None,None,None),
+ (4,33,"GtkWidget",1,1,None,None,None,None,None,None),
+ (4,33,"GtkWidget",2,2,None,1,None,None,None,None),
+ (4,45,"GtkWidget",1,1,None,None,None,None,None,None),
+ (4,45,"GtkWidget",2,2,None,1,None,None,None,None),
+ (1,9,"GtkStringList",2,5,"Disabled only",1,None,None,None,None),
+ (1,9,"GtkStringList",2,4,"Enabled only",1,None,None,None,None),
+ (1,9,"GtkStringList",2,3,"Pinned only",1,None,None,None,None),
+ (1,9,"GtkStringList",2,2,"All aliases",1,None,None,None,None),
+ (1,9,"GtkStringList",1,1,None,None,None,None,None,None),
+ (1,22,"GtkWidget",1,1,None,None,None,None,None,None),
+ (1,22,"GtkWidget",2,2,None,1,None,None,None,None),
+ (1,24,"GtkWidget",1,1,None,None,None,None,None,None),
+ (1,24,"GtkWidget",2,2,None,1,None,None,None,None),
+ (1,23,"GtkWidget",1,1,None,None,None,None,None,None),
+ (1,23,"GtkWidget",2,2,None,1,None,None,None,None),
+ (4,49,"GtkWidget",1,1,None,None,None,None,None,None),
+ (4,49,"GtkWidget",2,2,None,1,None,None,None,None),
+ (4,50,"GtkWidget",1,1,None,None,None,None,None,None),
+ (4,50,"GtkWidget",2,2,None,1,None,None,None,None),
+ (4,48,"GtkWidget",1,1,None,None,None,None,None,None),
+ (4,48,"GtkWidget",2,2,None,1,None,None,None,None),
+ (5,17,"GtkWidget",1,1,None,None,None,None,None,None),
+ (5,17,"GtkWidget",2,2,None,1,None,None,None,None),
+ (5,23,"GtkWidget",1,1,None,None,None,None,None,None),
+ (5,23,"GtkWidget",2,2,None,1,None,None,None,None),
+ (5,13,"GtkWidget",2,2,None,1,None,None,None,None),
+ (5,13,"GtkWidget",1,1,None,None,None,None,None,None),
+ (5,12,"GtkWidget",2,2,None,1,None,None,None,None),
+ (5,12,"GtkWidget",1,1,None,None,None,None,None,None),
+ (5,11,"GtkWidget",2,2,None,1,None,None,None,None),
+ (5,11,"GtkWidget",1,1,None,None,None,None,None,None),
+ (1,36,"GtkWidget",1,1,None,None,None,None,None,None),
+ (1,36,"GtkWidget",2,2,None,1,None,None,None,None),
+ (3,31,"GtkWidget",1,1,None,None,None,None,None,None),
+ (3,31,"GtkWidget",2,2,None,1,None,None,None,None),
+ (3,32,"GtkWidget",1,1,None,None,None,None,None,None),
+ (3,32,"GtkWidget",2,2,None,1,None,None,None,None),
+ (3,30,"GtkWidget",1,1,None,None,None,None,None,None),
+ (3,30,"GtkWidget",2,2,None,1,None,None,None,None),
+ (3,57,"GtkLabel",1,1,None,None,None,None,None,None),
+ (1,39,"GtkWidget",1,1,None,None,None,None,None,None),
+ (1,39,"GtkWidget",2,2,None,1,None,None,None,None),
+ (1,40,"GtkWidget",1,1,None,None,None,None,None,None),
+ (1,40,"GtkWidget",2,2,None,1,None,None,None,None),
+ (1,41,"GtkWidget",1,1,None,None,None,None,None,None),
+ (1,41,"GtkWidget",2,2,None,1,None,None,None,None),
+ (1,41,"GtkWidget",2,3,None,1,None,None,None,None),
+ (3,67,"GtkWidget",1,1,None,None,None,None,None,None),
+ (3,67,"GtkWidget",2,2,None,1,None,None,None,None),
+ (3,47,"GtkWidget",2,2,None,1,None,None,None,None),
+ (3,47,"GtkWidget",1,1,None,None,None,None,None,None),
+ (3,46,"GtkWidget",2,2,None,1,None,None,None,None),
+ (3,46,"GtkWidget",1,1,None,None,None,None,None,None),
+ (3,35,"GtkWidget",2,3,None,1,None,None,None,None),
+ (3,35,"GtkWidget",2,2,None,1,None,None,None,None),
+ (3,35,"GtkWidget",1,1,None,None,None,None,None,None)
+
+
+ (4,34,"GtkWidget",2,2,"name","login-title-label"),
+ (4,35,"GtkWidget",2,2,"name","error-message-label"),
+ (4,33,"GtkWidget",2,2,"name","login-form-warpper"),
+ (4,45,"GtkWidget",2,2,"name","login-title-label"),
+ (1,9,"GtkStringList",2,5,"translatable",None),
+ (1,9,"GtkStringList",2,5,"context",None),
+ (1,9,"GtkStringList",2,5,"comments",None),
+ (1,9,"GtkStringList",2,4,"translatable",None),
+ (1,9,"GtkStringList",2,4,"context",None),
+ (1,9,"GtkStringList",2,4,"comments",None),
+ (1,9,"GtkStringList",2,3,"translatable",None),
+ (1,9,"GtkStringList",2,3,"context",None),
+ (1,9,"GtkStringList",2,3,"comments",None),
+ (1,9,"GtkStringList",2,2,"translatable",None),
+ (1,9,"GtkStringList",2,2,"context",None),
+ (1,9,"GtkStringList",2,2,"comments",None),
+ (1,24,"GtkWidget",2,2,"name","loading-label"),
+ (1,22,"GtkWidget",2,2,"name","loading-label"),
+ (1,23,"GtkWidget",2,2,"name","loading-label"),
+ (4,49,"GtkWidget",2,2,"name","loading-label"),
+ (4,50,"GtkWidget",2,2,"name","loading-label"),
+ (4,48,"GtkWidget",2,2,"name","loading-label"),
+ (5,17,"GtkWidget",2,2,"name","login-title-label"),
+ (5,23,"GtkWidget",2,2,"name","error-message-label"),
+ (5,13,"GtkWidget",2,2,"name","loading-label"),
+ (5,12,"GtkWidget",2,2,"name","loading-label"),
+ (5,11,"GtkWidget",2,2,"name","loading-label"),
+ (1,36,"GtkWidget",2,2,"name","loading-label"),
+ (3,31,"GtkWidget",2,2,"name","loading-label"),
+ (3,32,"GtkWidget",2,2,"name","loading-label"),
+ (3,30,"GtkWidget",2,2,"name","loading-label"),
+ (1,39,"GtkWidget",2,2,"name","loading-label"),
+ (1,40,"GtkWidget",2,2,"name","loading-label"),
+ (1,41,"GtkWidget",2,2,"name","error-message-label"),
+ (1,41,"GtkWidget",2,3,"name","loading-label"),
+ (3,67,"GtkWidget",2,2,"name","login-title-label"),
+ (3,47,"GtkWidget",2,2,"name","loading-label"),
+ (3,46,"GtkWidget",2,2,"name","loading-label"),
+ (3,35,"GtkWidget",2,3,"name","loading-label"),
+ (3,35,"GtkWidget",2,2,"name","error-message-label")
+
+
diff --git a/simplelogin-gui/res.old/subprojects/blueprint-compiler.wrap b/simplelogin-gui/res.old/subprojects/blueprint-compiler.wrap
new file mode 100644
index 0000000..521d368
--- /dev/null
+++ b/simplelogin-gui/res.old/subprojects/blueprint-compiler.wrap
@@ -0,0 +1,8 @@
+[wrap-git]
+directory = blueprint-compiler
+url = https://gitlab.gnome.org/jwestman/blueprint-compiler.git
+revision = v0.10.0
+depth = 1
+
+[provide]
+program_names = blueprint-compiler
\ No newline at end of file
diff --git a/simplelogin-gui/res.old/totp-dialog.ui b/simplelogin-gui/res.old/totp-dialog.ui
new file mode 100644
index 0000000..e6dd234
--- /dev/null
+++ b/simplelogin-gui/res.old/totp-dialog.ui
@@ -0,0 +1,124 @@
+
+
+
+
+
+ True
+ TOTP Entry
+
+
+ form_wrapper
+ center
+ True
+ center
+ True
+
+
+ 5
+ center
+ True
+ 10
+ 10
+ 10
+ 10
+ 5
+ center
+ True
+
+
+ center
+ True
+
+
+ center
+ dialog-password-symbolic
+
+
+
+
+ TOTP
+
+
+
+
+ 0
+ 2
+ 0
+
+
+
+
+
+ Accept
+
+ 1
+ 3
+
+
+
+
+
+ Cancel
+
+ 0
+ 3
+
+
+
+
+
+ TOTP Code
+
+ 0
+ 2
+ 1
+
+
+
+
+
+ ERROR
+
+
+ 0
+ 2
+ 2
+
+
+
+
+
+
+
+
+
+ vertical
+
+
+ True
+ end
+ True
+
+
+
+
+
+ Loading...
+ start
+ True
+
+
+
+
+
+
diff --git a/simplelogin-gui/src/application.rs b/simplelogin-gui/src/application.rs
new file mode 100644
index 0000000..4fe0b7d
--- /dev/null
+++ b/simplelogin-gui/src/application.rs
@@ -0,0 +1,50 @@
+use crate::login_window::LoginWindow;
+
+use gtk4 as gtk;
+use gtk::{glib, gio, prelude::*, subclass::prelude::*};
+
+mod imp {
+ use super::*;
+
+ #[derive(Debug)]
+ pub struct Application {
+ pub settings: gio::Settings,
+ }
+
+ impl Default for Application {
+ fn default() -> Self {
+ Self {
+ settings: gio::Settings::new(crate::APP_ID)
+ }
+ }
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for Application {
+ const NAME: &'static str = "Application";
+ type Type = super::Application;
+ type ParentType = gtk::Application;
+ }
+
+ impl ApplicationImpl for Application {
+ fn activate(&self) {
+ let login_window = LoginWindow::new(&self.obj());
+ login_window.present();
+ }
+ }
+
+ impl ObjectImpl for Application {}
+ impl GtkApplicationImpl for Application {}
+}
+
+glib::wrapper! {
+ pub struct Application(ObjectSubclass)
+ @extends gtk::Application, gio::Application,
+ @implements gio::ActionMap, gio::ActionGroup;
+}
+
+impl Application {
+ pub fn new(app_id: &str) -> Self {
+ glib::Object::builder().property("application-id", app_id).build()
+ }
+}
diff --git a/simplelogin-gui/src/login_window.rs b/simplelogin-gui/src/login_window.rs
new file mode 100644
index 0000000..05f76a8
--- /dev/null
+++ b/simplelogin-gui/src/login_window.rs
@@ -0,0 +1,65 @@
+use gtk4 as gtk;
+use gtk::{glib, gio, subclass::prelude::*};
+
+mod imp {
+ use super::*;
+
+ #[derive(Debug, Default, gtk::CompositeTemplate)]
+ #[template(file = "data/login_window.blp")]
+ pub struct LoginWindow {
+ #[template_child]
+ pub forms_overlay: TemplateChild,
+ #[template_child]
+ pub forms_wrapper: TemplateChild,
+ #[template_child]
+ pub error_label: TemplateChild,
+ #[template_child]
+ pub device_entry: TemplateChild,
+ #[template_child]
+ pub email_entry: TemplateChild,
+ #[template_child]
+ pub password_entry: TemplateChild,
+ #[template_child]
+ pub login_button: TemplateChild,
+ #[template_child]
+ pub api_key_entry: TemplateChild,
+ #[template_child]
+ pub api_key_button: TemplateChild,
+ #[template_child]
+ pub device_entry_popover: TemplateChild,
+ #[template_child]
+ pub loading_overlay: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for LoginWindow {
+ const NAME: &'static str = "LoginWindow";
+ type Type = super::LoginWindow;
+ type ParentType = gtk::ApplicationWindow;
+
+ fn class_init(class: &mut Self::Class) {
+ class.bind_template();
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for LoginWindow {}
+ impl WidgetImpl for LoginWindow {}
+ impl WindowImpl for LoginWindow {}
+ impl ApplicationWindowImpl for LoginWindow {}
+}
+
+glib::wrapper! {
+ pub struct LoginWindow(ObjectSubclass)
+ @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow,
+ @implements gio::ActionMap, gio::ActionGroup;
+}
+
+impl LoginWindow {
+ pub fn new(app: &crate::application::Application) -> Self {
+ glib::Object::builder().property("application", app).build()
+ }
+}
diff --git a/simplelogin-gui/src/main.rs b/simplelogin-gui/src/main.rs
index e7a11a9..90adb39 100644
--- a/simplelogin-gui/src/main.rs
+++ b/simplelogin-gui/src/main.rs
@@ -1,3 +1,20 @@
-fn main() {
- println!("Hello, world!");
+mod login_window;
+mod totp_window;
+mod application;
+
+use gtk4 as gtk;
+use gtk::prelude::*;
+use gtk::glib::{self, g_message};
+
+const APP_ID: &str = "im.zander.SimpleLogin";
+
+fn main() -> glib::ExitCode {
+ if cfg!(debug_assertions) {
+ // allow us to use a test schema for debug runs
+ let schema_dir = concat!(env!("OUT_DIR"), "/schemas/");
+ g_message!("SimpleLogin", "Using schema dir: \"{}\"", schema_dir);
+ std::env::set_var("GSETTINGS_SCHEMA_DIR", schema_dir);
+ }
+ let app = application::Application::new(APP_ID);
+ app.run()
}
diff --git a/simplelogin-gui/src/totp_window.rs b/simplelogin-gui/src/totp_window.rs
new file mode 100644
index 0000000..17a6866
--- /dev/null
+++ b/simplelogin-gui/src/totp_window.rs
@@ -0,0 +1,43 @@
+use gtk4 as gtk;
+use gtk::{glib, gio, prelude::*, subclass::prelude::*};
+
+mod imp {
+ use super::*;
+
+ #[derive(Debug, Default, gtk::CompositeTemplate)]
+ #[template(file = "data/totp_window.blp")]
+ pub struct TotpWindow {
+
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for TotpWindow {
+ const NAME: &'static str = "TotpWindow";
+ type Type = super::TotpWindow;
+ type ParentType = gtk::Window;
+
+ fn class_init(class: &mut Self::Class) {
+ class.bind_template();
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for TotpWindow {}
+ impl WidgetImpl for TotpWindow {}
+ impl WindowImpl for TotpWindow {}
+}
+
+glib::wrapper! {
+ pub struct TotpWindow(ObjectSubclass)
+ @extends gtk::Widget, gtk::Window,
+ @implements gio::ActionMap, gio::ActionGroup;
+}
+
+impl TotpWindow {
+ pub fn new>(app: &P) -> Self {
+ glib::Object::builder().property("application", app).build()
+ }
+}
diff --git a/simplelogin/src/lib.rs b/simplelogin/src/lib.rs
index e8f5ab1..c476e76 100644
--- a/simplelogin/src/lib.rs
+++ b/simplelogin/src/lib.rs
@@ -1,7 +1,9 @@
#![allow(dead_code)]
+use core::fmt;
+
use serde::{Serialize, Deserialize};
-struct Context {
+pub struct Context {
client: reqwest::Client,
api_key: Option,
url: String,
@@ -32,12 +34,60 @@ struct LoginResponse {
api_key: Option,
}
-pub enum Error {
- FIDO(String),
- Json(String),
- Auth(String),
- Http(String),
- Internal(String)
+#[derive(Serialize)]
+struct MfaRequest<'a> {
+ mfa_token: &'a str,
+ mfa_key: &'a str,
+ device: &'a str
+}
+
+#[derive(Deserialize)]
+struct MfaResponse {
+ name: String,
+ api_key: String,
+ email: String,
+}
+
+pub enum ErrorKind {
+ State,
+ Json,
+ Http,
+ Auth,
+}
+
+pub struct Error {
+ kind: ErrorKind,
+ msg: String,
+}
+
+impl Error {
+ pub(crate) fn new(
+ kind: ErrorKind, msg: T
+ ) -> Self where T: Into {
+ Self {kind, msg: msg.into()}
+ }
+
+ pub fn is_state(&self) -> bool {
+ matches!(self.kind, ErrorKind::State)
+ }
+
+ pub fn is_http(&self) -> bool {
+ matches!(self.kind, ErrorKind::Http)
+ }
+
+ pub fn is_auth(&self) -> bool {
+ matches!(self.kind, ErrorKind::Auth)
+ }
+
+ pub fn is_json(&self) -> bool {
+ matches!(self.kind, ErrorKind::Json)
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.msg)
+ }
}
pub const DEFAULT_URL: &str = "https://app.simplelogin.io/";
@@ -63,6 +113,14 @@ impl Context {
}
}
+ pub fn api_key(&self) -> Option<&String> {
+ self.api_key.as_ref()
+ }
+
+ pub fn set_api_key(&mut self, api_key: Option<&str>) {
+ self.api_key = api_key.map(|s| s.to_owned());
+ }
+
fn parse_error_json(error_body: &String) -> String {
match serde_json::from_str(error_body) {
Ok::(obj) => obj.error,
@@ -77,22 +135,43 @@ impl Context {
Ok(resp) => {
let status = resp.status();
let resp_text = resp.text().await
- .map_err(|e| Error::Http(e.to_string()))?;
+ .map_err(|e| Error::new(ErrorKind::Http, e.to_string()))?;
return if status.is_success() {
Ok(resp_text)
} else if status.is_client_error() {
- Err(Error::Auth(Self::parse_error_json(&resp_text)))
+ Err(Error::new(ErrorKind::Auth, Self::parse_error_json(&resp_text)))
} else {
- Err(Error::Http("Unknown error! Response code: ".to_owned()
- + status.as_str()))
+ Err(Error::new(ErrorKind::Http, status.to_string()))
}
},
Err(err) => {
- Err(Error::Http(err.to_string()))
+ Err(Error::new(ErrorKind::Http, err.to_string()))
},
}
}
+ async fn post_request(
+ &mut self, endpoint: &str, body: &str
+ ) -> Result {
+ Self::perform_request(self.client.post(self.url.to_owned() + endpoint)
+ .header("Content-Type", "application/json")
+ .body(body.to_owned())).await
+ }
+
+ pub fn obj_to_json(
+ obj: &T
+ ) -> Result where T: serde::Serialize {
+ serde_json::to_string(obj)
+ .map_err(|e| Error::new(ErrorKind::Json, e.to_string()))
+ }
+
+ pub fn json_to_obj<'a, T>(
+ json: &'a str
+ ) -> Result where T: serde::Deserialize<'a> {
+ serde_json::from_str(json)
+ .map_err(|e| Error::new(ErrorKind::Json, e.to_string()))
+ }
+
pub async fn login(
&mut self,
email: &str,
@@ -100,21 +179,16 @@ impl Context {
device: &str
) -> Result {
let req_body = LoginRequest {email, password, device};
- let response = Self::perform_request(self.client.post(self.url.to_owned())
- .header("Content-Type", "application/json")
- .body(serde_json::to_string(&req_body).unwrap()));
- let body = response.await?;
- let resp_obj: LoginResponse = serde_json::from_str(body.as_str())
- .map_err(|_| Error::Json("Could not parse response json"
- .to_owned()))?;
+ let body = self.post_request("api/auth/login",
+ Self::obj_to_json(&req_body)?.as_str()).await?;
+ let resp_obj: LoginResponse = Self::json_to_obj(body.as_str())?;
if resp_obj.mfa_enabled {
if resp_obj.mfa_key.is_some() {
self.device = Some(device.to_owned());
self.mfa_key = resp_obj.mfa_key;
Ok(true)
} else {
- Err(Error::Json("Error response did not contain mfa key"
- .to_owned()))
+ Err(Error::new(ErrorKind::Json, "Error response did not contain mfa key"))
}
} else {
if resp_obj.api_key.is_some() {
@@ -123,13 +197,26 @@ impl Context {
self.device = None;
Ok(true)
} else {
- Err(Error::Json("Response did not contain api key".to_owned()))
+ Err(Error::new(ErrorKind::Json, "Response did not contain api key"))
}
}
}
pub async fn mfa(&mut self, mfa_token: &str) -> Result<(), Error> {
- // TODO implement
- Ok(())
+ if self.mfa_key.is_some() && self.device.is_some() {
+ let req_json = Self::obj_to_json(&MfaRequest {
+ mfa_token,
+ mfa_key: self.mfa_key.as_ref().unwrap().as_str(),
+ device: self.device.as_ref().unwrap().as_str(),
+ })?;
+ let body = self.post_request("api/auth/mfa", req_json.as_str()).await?;
+ let resp_obj: MfaResponse = Self::json_to_obj(body.as_str())?;
+ self.api_key = Some(resp_obj.api_key);
+ self.device = None;
+ self.mfa_key = None;
+ Ok(())
+ } else {
+ Err(Error::new(ErrorKind::State, "No login operation in progress"))
+ }
}
}