UI: Add QR scan results

Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
This commit is contained in:
Vasiliy Doylov 2025-06-24 19:25:18 +03:00
parent 92202b9084
commit 93b607955f
Signed by: NekoCWD
GPG key ID: B7BE22D44474A582
8 changed files with 157 additions and 17 deletions

View file

@ -78,9 +78,13 @@ public class EyeNeko.Elements.QrScaner : Gst.Base.Transform {
image.set_data ((void[]) img, () => {}); // Don't free data image.set_data ((void[]) img, () => {}); // Don't free data
scaner.scan_image (image); scaner.scan_image (image);
for (unowned var sym = image.first_symbol (); sym != null; sym = sym.next ()) { for (unowned var sym = image.first_symbol (); sym != null; sym = sym.next ()) {
var data = sym.get_data (); unowned var data = sym.get_data ();
if (qr_codes.add (data)) if (qr_codes.add (data)) {
new_qr (data); GLib.Idle.add (() => { // HACK it fixes GTK crash
new_qr (data);
return false;
}, Priority.DEFAULT);
}
} }
return Gst.FlowReturn.OK; return Gst.FlowReturn.OK;
} }

View file

@ -4,6 +4,7 @@
<file preprocess="xml-stripblanks">window.ui</file> <file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file> <file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file preprocess="xml-stripblanks">ui/caps-row.ui</file> <file preprocess="xml-stripblanks">ui/caps-row.ui</file>
<file preprocess="xml-stripblanks">ui/qr-row.ui</file>
<file>style.css</file> <file>style.css</file>
<file>shaders/ccm.glsl</file> <file>shaders/ccm.glsl</file>
</gresource> </gresource>

View file

@ -13,6 +13,7 @@ eyeneko_sources = [
'elements/qr_scaner.vala', 'elements/qr_scaner.vala',
'ui/preferences.vala', 'ui/preferences.vala',
'ui/caps-row.vala', 'ui/caps-row.vala',
'ui/qr-row.vala',
'logic/friendly-caps.vala', 'logic/friendly-caps.vala',
] ]
@ -35,6 +36,7 @@ blueprints = custom_target(
input: files( input: files(
'gtk/help-overlay.blp', 'gtk/help-overlay.blp',
'ui/caps-row.blp', 'ui/caps-row.blp',
'ui/qr-row.blp',
'window.blp', 'window.blp',
), ),
output: '.', output: '.',

View file

@ -65,3 +65,15 @@
border-radius: 3px; border-radius: 3px;
padding: 2px; padding: 2px;
} }
.qr-list {
background: none;
padding: 12px;
}
.qr-list > row {
margin: 6px;
padding: 12px;
border-radius: 10px;
background-color: alpha(var(--view-fg-color), 0.1);
}

40
src/ui/qr-row.blp Normal file
View file

@ -0,0 +1,40 @@
using Gtk 4.0;
template $EyeNekoUiQRRow: Box {
styles [
"qr-row",
]
spacing: 12;
halign: fill;
hexpand: true;
Label data {
styles [
"qr-data",
]
use-markup: false;
wrap: true;
wrap-mode: word_char;
}
Separator {
styles [
"spacer",
]
halign: fill;
hexpand: true;
}
Button copy {
valign: center;
styles [
"flat",
]
icon-name: "edit-copy-symbolic";
}
}

17
src/ui/qr-row.vala Normal file
View file

@ -0,0 +1,17 @@
[GtkTemplate(ui = "/io/gitlab/nekocwd/eyeneko/ui/qr-row.ui")]
public class EyeNeko.Ui.QRRow : Gtk.Box {
[GtkChild]
private unowned Gtk.Label data;
[GtkChild]
private unowned Gtk.Button copy;
public void set_qr(string qr) {
data.label = qr;
}
construct {
copy.clicked.connect(() => {
data.get_clipboard().set_text(data.label);
});
}
}

View file

@ -135,6 +135,39 @@ Adw.ToolbarView toolbar {
// content: TODO :) // content: TODO :)
} }
Adw.Dialog qr_dialog {
follows-content-size: true;
title: _("Scaned QRs");
Adw.ToolbarView {
styles [
"flat",
]
[top]
Adw.HeaderBar {}
content: ScrolledWindow {
propagate-natural-height: true;
propagate-natural-width: true;
ListView {
styles [
"qr-list",
]
show-separators: true;
factory: SignalListItemFactory qr_factory {};
model: NoSelection {
model: StringList qr_list {};
};
}
};
}
}
menu primary_menu { menu primary_menu {
section { section {
item { item {

View file

@ -34,7 +34,12 @@ public class EyeNeko.Window : Adw.ApplicationWindow {
private unowned Gtk.Button capture_btn; private unowned Gtk.Button capture_btn;
[GtkChild] [GtkChild]
private unowned Adw.ToggleGroup camera_mode; private unowned Adw.ToggleGroup camera_mode;
[GtkChild]
private unowned Gtk.SignalListItemFactory qr_factory;
[GtkChild]
private unowned Gtk.StringList qr_list;
[GtkChild]
private unowned Adw.Dialog qr_dialog;
public string camera_path { get; set; default = "Unknown"; } public string camera_path { get; set; default = "Unknown"; }
public string current_caps { get; set; default = "Unknown"; } public string current_caps { get; set; default = "Unknown"; }
@ -103,32 +108,58 @@ public class EyeNeko.Window : Adw.ApplicationWindow {
video_caps_btn.set_popover (popover); video_caps_btn.set_popover (popover);
} }
private void setup_qr_scaner () {
qr_factory.setup.connect ((mdl) => {
var item = (Gtk.ListItem) mdl;
item.child = new QRRow ();
});
qr_factory.bind.connect ((mdl) => {
var item = (Gtk.ListItem) mdl;
var row = (QRRow) item.child;
var data = (Gtk.StringObject) item.item;
row.set_qr (data.string);
});
Gstreamer.instance.qr_scaner.new_qr.connect ((qr) => {
qr_list.append (qr);
qr_dialog.present (this);
});
}
public Window (Gtk.Application app) { public Window (Gtk.Application app) {
Object (application: app); Object (application: app);
overlay.add_overlay (toolbar); overlay.add_overlay (toolbar);
Gstreamer.instance.bind_property ("viewfinder-paintable", viewfinder, "paintable", BindingFlags.SYNC_CREATE); Gstreamer.instance.bind_property ("viewfinder-paintable", viewfinder, "paintable", BindingFlags.SYNC_CREATE);
capture_btn.clicked.connect (() => { capture_btn.clicked.connect (() => {
if (Gstreamer.instance.camera_mode == Gstreamer.CameraMode.VIDEO && !Gstreamer.instance.ready) { switch (Gstreamer.instance.camera_mode) {
Gstreamer.instance.stop_capture (); case Gstreamer.CameraMode.QR:
} else { qr_dialog.present (this);
Gstreamer.instance.start_capture (); break;
case Gstreamer.CameraMode.VIDEO:
if (Gstreamer.instance.ready)
Gstreamer.instance.start_capture ();
else
Gstreamer.instance.stop_capture ();
break;
default:
Gstreamer.instance.start_capture ();
break;
} }
}); });
setup_video_source_changer ();
setup_caps_changer ();
setup_qr_scaner ();
close_request.connect (() => {
Gstreamer.instance.start_stream_from (null);
return false;
});
camera_mode.bind_property ("active-name", Gstreamer.instance, "camera-mode", BindingFlags.SYNC_CREATE, (b, s, ref d) => { camera_mode.bind_property ("active-name", Gstreamer.instance, "camera-mode", BindingFlags.SYNC_CREATE, (b, s, ref d) => {
d = Gstreamer.CameraMode.parse (s.get_string ()); d = Gstreamer.CameraMode.parse (s.get_string ());
return true; return true;
}); });
camera_mode.active_name = Env.get_variable_or ("CAM_M", "photo"); camera_mode.active_name = Env.get_variable_or ("CAM_M", "photo");
setup_video_source_changer ();
setup_caps_changer ();
close_request.connect (() => {
Gstreamer.instance.start_stream_from (null);
return false;
});
Gstreamer.instance.qr_scaner.new_qr.connect ((qr) => message ("QR scaned %s", qr));
} }
} }