From 269a72967bd08ac376c5c9debe808dc6f06b22b9 Mon Sep 17 00:00:00 2001 From: Vasiliy Doylov Date: Tue, 24 Jun 2025 16:36:09 +0300 Subject: [PATCH] GST&UI: add QR code scan logic Signed-off-by: Vasiliy Doylov --- .../actions/qr-code-scanner-symbolic.svg | 2 + data/icons/icons.gresource.xml | 1 + src/gst.vala | 71 +++++++++++++++---- src/window.blp | 6 ++ src/window.vala | 18 ++--- 5 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 data/icons/actions/qr-code-scanner-symbolic.svg diff --git a/data/icons/actions/qr-code-scanner-symbolic.svg b/data/icons/actions/qr-code-scanner-symbolic.svg new file mode 100644 index 0000000..f68e1a1 --- /dev/null +++ b/data/icons/actions/qr-code-scanner-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/data/icons/icons.gresource.xml b/data/icons/icons.gresource.xml index 7d4a3f8..80fba08 100644 --- a/data/icons/icons.gresource.xml +++ b/data/icons/icons.gresource.xml @@ -8,5 +8,6 @@ actions/camera-photo-symbolic.svg actions/pick-camera-alt2-symbolic.svg actions/encoder-knob-symbolic.svg + actions/qr-code-scanner-symbolic.svg diff --git a/src/gst.vala b/src/gst.vala index c972076..86d607b 100644 --- a/src/gst.vala +++ b/src/gst.vala @@ -5,9 +5,39 @@ public class EyeNeko.Gstreamer : Object { PHOTO = 1, } + public enum CameraMode { + VIDEO, + PHOTO, + QR; + + public string to_string () { + return ((EnumClass) typeof (CameraMode).class_ref ()).get_value (this).value_nick; + } + + public static CameraMode parse (string data) { + return ((EnumClass) typeof (CameraMode).class_ref ()).get_value_by_nick (data) ? .value ?? 0; + } + + public CameraBinMode to_camerabin_mode () { + return this == VIDEO ? CameraBinMode.VIDEO : CameraBinMode.PHOTO; + } + } + + private static Gee.HashMap mode_caps = new Gee.HashMap (); + private static Gee.HashMap mode_downscale = new Gee.HashMap (); + + static construct { + mode_caps[CameraMode.PHOTO] = new Gst.Caps.any (); + mode_caps[CameraMode.VIDEO] = new Gst.Caps.any (); + mode_caps[CameraMode.QR] = Gst.Caps.from_string ("video/x-raw, format=GRAY8"); + mode_downscale[CameraMode.VIDEO] = int.parse (Env.get_variable_or ("DOWNSCALE_VIDEO", "640")); + mode_downscale[CameraMode.PHOTO] = int.parse (Env.get_variable_or ("DOWNSCALE_PHOTO", "0")); + mode_downscale[CameraMode.QR] = int.parse (Env.get_variable_or ("DOWNSCALE_QR", "0")); + } + // Public properties public bool ready { get; set; default = false; } - public CameraBinMode camerabin_mode { get; set; default = CameraBinMode.PHOTO; } + public CameraMode camera_mode { get; set; default = CameraMode.PHOTO; } public Gdk.Paintable viewfinder_paintable { get; set; } public ListStore available_caps = new ListStore (typeof (FriendlyCaps)); public Gtk.SingleSelection caps_selecton_model = new Gtk.SingleSelection (null); @@ -19,11 +49,11 @@ public class EyeNeko.Gstreamer : Object { private Gst.Bin camerasrc_wrapper = (Gst.Bin) Gst.ElementFactory.make ("wrappercamerabinsrc"); private Elements.ColorCorrectionMatrix color_correction_matrix = new Elements.ColorCorrectionMatrix (); private Elements.Downscale downscale = new Elements.Downscale (); + private Gst.Element camera_convert_caps = Gst.ElementFactory.make ("capsfilter"); + public Elements.QrScaner qr_scaner = new Elements.QrScaner (); // Other Fields public Camera current_camera = null; - public int downscale_video_to = int.parse (Env.get_variable_or ("DOWNSCALE_VIDEO", "640")); - public int downscale_photo_to = int.parse (Env.get_variable_or ("DOWNSCALE_PHOTO", "0")); private bool can_start_stream = true; private void set_up_caps (Gst.Device device) { @@ -66,7 +96,7 @@ public class EyeNeko.Gstreamer : Object { } public void start_capture () { - var file_dir = Environment.get_user_special_dir (camerabin_mode == CameraBinMode.VIDEO ? UserDirectory.VIDEOS : UserDirectory.PICTURES); + var file_dir = Environment.get_user_special_dir (camera_mode == CameraMode.VIDEO ? UserDirectory.VIDEOS : UserDirectory.PICTURES); var dir = File.new_build_filename (file_dir, "EyeNeko"); try { @@ -79,7 +109,7 @@ public class EyeNeko.Gstreamer : Object { var time = new DateTime.now (); var formated = time.format ("%Y-%m-%d-%H-%M-%S"); - var filename = "%s.%s".printf (formated, camerabin_mode == CameraBinMode.VIDEO ? "mp4" : "jpeg"); + var filename = "%s.%s".printf (formated, camera_mode == CameraMode.VIDEO ? "mp4" : "jpeg"); var location = Path.build_filename (dir.get_path (), filename); message ("Capturing %s", location); camerabin.set_property ("location", location); @@ -163,6 +193,10 @@ public class EyeNeko.Gstreamer : Object { camerasrc_wrapper.set_property ("video-source-filter", pipe_elements ("Camera Processing", downscale, + Gst.ElementFactory.make ("videoconvert"), + camera_convert_caps, + qr_scaner, + Gst.ElementFactory.make ("videoconvert"), Gst.ElementFactory.make ("glupload"), color_correction_matrix, Gst.ElementFactory.make ("gldownload") @@ -181,15 +215,26 @@ public class EyeNeko.Gstreamer : Object { ))); camerabin.bind_property ("idle", this, "ready", BindingFlags.SYNC_CREATE); - this.bind_property ("camerabin-mode", camerabin, "mode"); - add_enc_profile (); - - notify["camerabin-mode"].connect (() => { - downscale.max_size = camerabin_mode == CameraBinMode.VIDEO ? downscale_video_to : downscale_photo_to; - if (current_camera != null) { - start_stream_from (current_camera); - } + this.bind_property ("camera-mode", camerabin, "mode", BindingFlags.SYNC_CREATE, (b, s, ref d) => { + d.set_enum (((CameraMode) s).to_camerabin_mode ()); + return true; }); + this.bind_property ("camera-mode", camera_convert_caps, "caps", BindingFlags.SYNC_CREATE, (b, s, ref d) => { + d = mode_caps[((CameraMode) s)]; + return true; + }); + this.bind_property ("camera-mode", downscale, "max-size", BindingFlags.SYNC_CREATE, (b, s, ref d) => { + d = mode_downscale[((CameraMode) s)]; + return true; + }); + this.bind_property ("camera-mode", qr_scaner, "enabled", BindingFlags.SYNC_CREATE, (b, s, ref d) => { + d = (CameraMode) s == CameraMode.QR; + return true; + }); + + notify["camera-mode"].connect (() => start_stream_from (current_camera)); + + add_enc_profile (); Gdk.GLContext context; viewfinder_paintable.get ("gl-context", out context); diff --git a/src/window.blp b/src/window.blp index 071f018..d29ab4c 100644 --- a/src/window.blp +++ b/src/window.blp @@ -74,6 +74,12 @@ Adw.ToolbarView toolbar { label: "Video"; name: "video"; } + + Adw.Toggle { + icon-name: "qr-code-scanner-symbolic"; + label: "QR"; + name: "qr"; + } } Box { diff --git a/src/window.vala b/src/window.vala index 6864840..5a07e31 100644 --- a/src/window.vala +++ b/src/window.vala @@ -109,24 +109,16 @@ public class EyeNeko.Window : Adw.ApplicationWindow { Gstreamer.instance.bind_property ("viewfinder-paintable", viewfinder, "paintable", BindingFlags.SYNC_CREATE); capture_btn.clicked.connect (() => { - if (Gstreamer.instance.camerabin_mode == Gstreamer.CameraBinMode.VIDEO && !Gstreamer.instance.ready) { + if (Gstreamer.instance.camera_mode == Gstreamer.CameraMode.VIDEO && !Gstreamer.instance.ready) { Gstreamer.instance.stop_capture (); } else { Gstreamer.instance.start_capture (); } }); - camera_mode.notify["active"].connect (() => { - switch (camera_mode.active_name) { - case "photo": - Gstreamer.instance.camerabin_mode = Gstreamer.CameraBinMode.PHOTO; - break; - case "video": - Gstreamer.instance.camerabin_mode = Gstreamer.CameraBinMode.VIDEO; - break; - default: - break; - } + camera_mode.bind_property ("active-name", Gstreamer.instance, "camera-mode", BindingFlags.SYNC_CREATE, (b, s, ref d) => { + d = Gstreamer.CameraMode.parse (s.get_string ()); + return true; }); camera_mode.active_name = Env.get_variable_or ("CAM_M", "photo"); @@ -136,5 +128,7 @@ public class EyeNeko.Window : Adw.ApplicationWindow { Gstreamer.instance.start_stream_from (null); return false; }); + + Gstreamer.instance.qr_scaner.new_qr.connect ((qr) => message ("QR scaned %s", qr)); } }