WIP stuff

Signed-off-by: Vasiliy Doylov <nekocwd@mainlining.org>
This commit is contained in:
Vasiliy Doylov 2025-03-17 21:50:10 +03:00
parent 2e12fa26c4
commit a5e5128740
Signed by: NekoCWD
GPG key ID: B7BE22D44474A582
17 changed files with 19858 additions and 13 deletions

View file

@ -19,5 +19,7 @@
</schema> </schema>
<schema id="io.gitlab.nekocwd.pipetap" path="/io/gitlab/nekocwd/pipetap/"> <schema id="io.gitlab.nekocwd.pipetap" path="/io/gitlab/nekocwd/pipetap/">
<child name="main-controls-pos" schema="io.gitlab.nekocwd.pipetap.adjustments"></child> <child name="main-controls-pos" schema="io.gitlab.nekocwd.pipetap.adjustments"></child>
<child name="exp-pos" schema="io.gitlab.nekocwd.pipetap.adjustments"></child>
<child name="focus-pos" schema="io.gitlab.nekocwd.pipetap.adjustments"></child>
</schema> </schema>
</schemalist> </schemalist>

19473
src/Wp-0.5.gir Normal file

File diff suppressed because it is too large Load diff

6
src/Wp-0.5.metadata Normal file
View file

@ -0,0 +1,6 @@
SPA_TYPE_INVALID skip
Transition.get_source_object skip
SpaPod.get_property
.key unowned
SpaPod.get_string
.value unowned

View file

@ -21,6 +21,10 @@
public class PipeTap.Application : Adw.Application { public class PipeTap.Application : Adw.Application {
public Settings settings; public Settings settings;
public bool adjust_overlay_visible { get; set; } public bool adjust_overlay_visible { get; set; }
public Ui.SliderOverlay exposure;
public Ui.SliderOverlay focus;
public Ui.MainBar main_bar;
public string device { get; set; }
public Application () { public Application () {
Object ( Object (
@ -50,9 +54,18 @@ public class PipeTap.Application : Adw.Application {
styling, styling,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
var win = this.active_window ?? new PipeTap.Ui.MainBar (this); if (exposure == null)
var win2 = new PipeTap.Ui.MainBar (this); exposure = new Ui.SliderOverlay (this, Logic.Ctrl.CtrlType.Exposure, Logic.Ctrl.CtrlType.ExposureEnable, "exp", "camera-iso-symbolic");
win.present (); if (focus == null)
focus = new Ui.SliderOverlay (this, Logic.Ctrl.CtrlType.Focus, Logic.Ctrl.CtrlType.AutoFocusEnable, "focus", "camera-focus-symbolic");
if (main_bar == null)
main_bar = new Ui.MainBar (this);
bind_property ("device", main_bar, "visible", GLib.BindingFlags.SYNC_CREATE, (b, v, ref tgt) => {
tgt.set_boolean (device != null);
message ("%s", device);
return true;
});
} }
private void on_about_action () { private void on_about_action () {

View file

View file

@ -4,8 +4,6 @@ using Adw 1;
template $PipeTapUiMainBar: $PipeTapUiFloatingWindow { template $PipeTapUiMainBar: $PipeTapUiFloatingWindow {
title: _("PipeTap"); title: _("PipeTap");
window-name: "main-controls"; window-name: "main-controls";
default-width: 60;
default-height: 60;
content: Gtk.Box { content: Gtk.Box {
orientation: bind template.orientation; orientation: bind template.orientation;
@ -13,7 +11,7 @@ template $PipeTapUiMainBar: $PipeTapUiFloatingWindow {
homogeneous: true; homogeneous: true;
spacing: 10; spacing: 10;
Gtk.Button { Gtk.ToggleButton exposure {
icon-name: "camera-iso-symbolic"; icon-name: "camera-iso-symbolic";
tooltip-text: _("Exposure"); tooltip-text: _("Exposure");
@ -22,7 +20,7 @@ template $PipeTapUiMainBar: $PipeTapUiFloatingWindow {
] ]
} }
Gtk.Button { Gtk.ToggleButton focus {
icon-name: "camera-focus-symbolic"; icon-name: "camera-focus-symbolic";
tooltip-text: _("Focus"); tooltip-text: _("Focus");

View file

@ -1,6 +1,21 @@
[GtkTemplate (ui = "/io/gitlab/nekocwd/pipetap/gui/main_bar.ui")] [GtkTemplate (ui = "/io/gitlab/nekocwd/pipetap/gui/main_bar.ui")]
public class PipeTap.Ui.MainBar : PipeTap.Ui.FloatingWindow { public class PipeTap.Ui.MainBar : PipeTap.Ui.FloatingWindow {
public MainBar (Gtk.Application app) { [GtkChild]
base (app); private unowned Gtk.ToggleButton exposure;
[GtkChild]
private new unowned Gtk.ToggleButton focus;
public MainBar (PipeTap.Application app) {
base (app, "main-controls");
exposure.bind_property ("active", app.exposure, "visible", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
focus.bind_property ("active", app.focus, "visible", BindingFlags.SYNC_CREATE | BindingFlags.BIDIRECTIONAL);
app.exposure.bind_property ("is_available", exposure, "sensitive", GLib.BindingFlags.SYNC_CREATE);
app.focus.bind_property ("is_available", focus, "sensitive", GLib.BindingFlags.SYNC_CREATE);
notify["visible"].connect (() => {
if (visible == true)
return;
app.exposure.visible = false;
app.focus.visible = false;
});
} }
} }

View file

@ -0,0 +1,46 @@
using Gtk 4.0;
using Adw 1;
template $PipeTapUiSliderOverlay: $PipeTapUiFloatingWindow {
title: _("PipeTap Slider");
content: Gtk.Box content_box {
orientation: bind template.orientation;
halign: fill;
spacing: 10;
Separator spacer {
styles [
"spacer",
]
}
Scale scale {
orientation: bind template.orientation;
vexpand: true;
hexpand: true;
halign: fill;
valign: fill;
adjustment: Adjustment {
lower: 0;
upper: 1000;
step-increment: 1;
value: 500;
};
styles [
"pt-slider",
]
}
Gtk.ToggleButton lock_btn {
icon-name: "camera-iso-symbolic";
tooltip-text: _("Lock");
styles [
"flat",
]
}
};
}

View file

@ -0,0 +1,55 @@
[GtkTemplate(ui = "/io/gitlab/nekocwd/pipetap/gui/slider_overlay.ui")]
public class PipeTap.Ui.SliderOverlay : PipeTap.Ui.FloatingWindow {
[GtkChild]
private unowned Gtk.Scale scale;
[GtkChild]
private unowned Gtk.ToggleButton lock_btn;
public Logic.Ctrl.CtrlType slider_type { get; set; }
public Logic.Ctrl.CtrlType? toggle_type { get; set; }
public bool is_available { get; protected set; }
public SliderOverlay(PipeTap.Application app, Logic.Ctrl.CtrlType slider_type, Logic.Ctrl.CtrlType toggle_type, string name, string icon_name) {
base(app, name);
this.slider_type = slider_type;
this.toggle_type = toggle_type;
scale.value_changed.connect(value_changed);
lock_btn.toggled.connect(lock_changed);
app.notify["device"].connect(set_ctrls);
lock_btn.icon_name = icon_name;
set_ctrls();
}
void set_ctrls() {
var slider_ctrl = Logic.find_ctrl(slider_type);
var lock_ctrl = Logic.find_ctrl(toggle_type);
scale.sensitive = slider_ctrl != null;
lock_btn.sensitive = lock_ctrl != null;
is_available = slider_ctrl != null || lock_ctrl != null;
if (slider_ctrl != null)
scale.set_value(slider_ctrl.value);
if (lock_ctrl != null)
lock_btn.active = lock_ctrl.value == 1;
}
void value_changed() {
message("Value %f", scale.get_value());
var ctrl = Logic.find_ctrl(slider_type);
if (ctrl == null) {
message("Slider ctrl not found");
return;
}
ctrl.value = (int) scale.get_value();
}
void lock_changed() {
message("Lock %s", lock_btn.active ? "on" : "off");
var ctrl = Logic.find_ctrl(toggle_type);
if (ctrl == null) {
message("Toggle ctrl not found");
return;
}
ctrl.value = (int) lock_btn.active;
}
}

View file

@ -3,8 +3,8 @@ using Adw 1;
template $PipeTapUiFloatingWindow: Gtk.Window { template $PipeTapUiFloatingWindow: Gtk.Window {
title: _("PipeTap"); title: _("PipeTap");
default-width: 800; default-width: 60;
default-height: 100; default-height: 60;
styles [ styles [
"pt-transparent", "pt-transparent",
@ -37,7 +37,7 @@ template $PipeTapUiFloatingWindow: Gtk.Window {
"flat", "flat",
] ]
} }
Button move { Button move {
visible: bind template.move-visible; visible: bind template.move-visible;
icon-name: "move-tool-symbolic"; icon-name: "move-tool-symbolic";

View file

@ -49,8 +49,9 @@ public class PipeTap.Ui.FloatingWindow : Gtk.Window, Gtk.Orientable {
public Gtk.Widget content { get { return content_bin.get_child (); } set { content_bin.set_child (value); } } public Gtk.Widget content { get { return content_bin.get_child (); } set { content_bin.set_child (value); } }
public FloatingWindow (Gtk.Application app) { public FloatingWindow (Gtk.Application app, string name) {
Object (application: app); Object (application: app);
_window_name = name;
GtkLayerShell.init_for_window (this); GtkLayerShell.init_for_window (this);
var move_controller = new Gtk.GestureDrag (); var move_controller = new Gtk.GestureDrag ();

31
src/logic/ctrl.vala Normal file
View file

@ -0,0 +1,31 @@
namespace PipeTap.Logic {
public PipeTap.Application app;
public interface Ctrl : Object {
public enum CtrlType {
Exposure,
ExposureEnable,
Contrast,
Focus,
AutoFocusEnable,
}
public abstract CtrlType ctrl_type { get; }
public abstract int value { get; set; }
}
public HashTable<string, List<Logic.Ctrl>> ctrls;
public void init() {
ctrls = new HashTable<string, List<Logic.Ctrl>> (str_hash, str_equal);
}
public Ctrl ? find_ctrl(Ctrl.CtrlType type) {
if (app.device == null)
return null;
foreach (var ctrl in ctrls.get(app.device)) {
message("%u", ctrl.ctrl_type);
if (ctrl.ctrl_type == type)
return ctrl;
}
return null;
}
}

170
src/logic/wireplumber.vala Normal file
View file

@ -0,0 +1,170 @@
namespace PipeTap.Logic.WirePlumber {
public Wp.Core core;
public Wp.ObjectManager node_manager;
public class Ctrl : Logic.Ctrl, Object {
public Wp.Node node;
public uint32 id;
public float min;
public float max;
public int _value = 0;
public string wp_type;
public int value { get { return _value; } set { _value = value; send_value(value); } }
public CtrlType ctrl_type { get; }
public Ctrl(CtrlType pt_type, Wp.Node node, uint id, string type, float min, float max) {
this.node = node;
this.min = min;
this.max = max;
this.wp_type = type;
this.id = id;
_ctrl_type = pt_type;
}
private void send_value(int value) {
var pb = new Wp.SpaPodBuilder.object("Spa:Pod:Object:Param:Props", "Props");
pb.add_property_id(id);
switch (wp_type) {
case "float":
var adjusted = value / 1000.0 * (max - min) + min;
message("Sending float %f", (float) adjusted);
pb.add_float((float) adjusted);
break;
case "bool":
var adjusted = value == 1;
message("Sending bool %s", adjusted ? "true" : "false");
pb.add_boolean(adjusted);
break;
default:
message("Unknown type %s", wp_type);
break;
}
var pod = pb.end();
node.set_param("Props", 0, pod);
}
}
void init_manager() {
message("Core connected");
node_manager = new Wp.ObjectManager();
// Use V4L2 ONLY FOR TESTING
var interest_lcam = new Wp.ObjectInterest.type(typeof (Wp.Node));
var interest_v4l2 = new Wp.ObjectInterest.type(typeof (Wp.Node));
interest_lcam.add_constraint(Wp.ConstraintType.PW_PROPERTY, "device.api", Wp.ConstraintVerb.EQUALS, "libcamera");
interest_v4l2.add_constraint(Wp.ConstraintType.PW_PROPERTY, "device.api", Wp.ConstraintVerb.EQUALS, "v4l2");
node_manager.add_interest_full(interest_lcam);
node_manager.add_interest_full(interest_v4l2);
node_manager.request_object_features(typeof (Wp.Node), Wp.OBJECT_FEATURES_ALL);
node_manager.object_added.connect((obj) => {
Wp.Node node = obj as Wp.Node;
var name = node.properties["node.nick"];
message("Found camera %s", name);
node.params_changed.connect((str) => message("%s", str));
node.state_changed.connect(() => {
message("Camera %s state changed -> %s", name, node.state == Wp.NodeState.RUNNING ? "Running" : "Not running :D");
if (node.state == Wp.NodeState.RUNNING) {
app.device = name;
return;
} else if (app.device == name) {
app.device = null;
}
});
message("== Enumerating properties ==");
var found_ctrls = new List<Logic.Ctrl> ();
var params = node.enum_params_sync("PropInfo", null);
for (Value param_info_val; params.next(out param_info_val); ) {
Wp.SpaPod param_info_pod = (Wp.SpaPod) param_info_val;
var iterator = param_info_pod.new_iterator();
string description = "IDK";
uint32 id = 0;
string type = "IDK";
for (Value param_kv_val; iterator.next(out param_kv_val); ) {
Wp.SpaPod param_kv_pod = (Wp.SpaPod) param_kv_val;
string key;
Wp.SpaPod value;
param_kv_pod.get_property(out key, out value);
switch (key) {
case "id":
value.get_id(out id);
break;
case "description":
value.get_string(out description);
break;
case "type":
if (!value.is_choice())
break;
Wp.SpaPod choice = value.get_choice_child();
if (choice.is_double())
type = "double";
else if (choice.is_float())
type = "float";
else if (choice.is_int())
type = "int";
else if (choice.is_boolean())
type = "bool";
else if (choice.is_rectangle())
type = "rect (unsupported)";
break;
default:
warning("Report ME: Unexpected key in property enumeration \"%s\"", key);
break;
}
}
switch (description) {
case "Brightness":
if (type != "float") {
warning("Wrong type");
break;
}
message("Found EXPOSURE");
var ctrl = new Ctrl(Logic.Ctrl.CtrlType.Exposure, node, id, type, 0, 2);
found_ctrls.append(ctrl);
break;
case "LensPosition":
if (type != "float") {
warning("Wrong type");
break;
}
message("Found FOCUS");
var ctrl = new Ctrl(Logic.Ctrl.CtrlType.Focus, node, id, type, 0, 100);
found_ctrls.append(ctrl);
break;
case "AeEnable":
if (type != "bool") {
warning("Wrong type");
break;
}
message("Found AE enable");
var ctrl = new Ctrl(Logic.Ctrl.CtrlType.ExposureEnable, node, id, type, 0, 1);
found_ctrls.append(ctrl);
break;
default:
message("Unknown ctrl %s of %s", description, type);
break;
}
// message("%s: %s -> 0x%lx", description, type, id);
}
message("== End of property enumeration ==");
message("list is %s", found_ctrls == null ? "null" : "not null");
ctrls[name] = (owned) found_ctrls;
message("%s %s", name, ctrls[name] == null ? "Is nulllll" : "OK");
foreach (var ctrl in ctrls.get(name)) {
message("CTRL %u", ctrl.ctrl_type);
}
});
core.install_object_manager(node_manager);
}
public void init() {
Wp.init(Wp.InitFlags.PIPEWIRE | Wp.InitFlags.SPA_TYPES);
core = new Wp.Core(null, null, null);
core.activate.begin(Wp.CoreFeatures.CONNECTED, null, init_manager);
}
}

View file

@ -24,5 +24,8 @@ int main (string[] args) {
Intl.textdomain (Config.GETTEXT_PACKAGE); Intl.textdomain (Config.GETTEXT_PACKAGE);
var app = new PipeTap.Application (); var app = new PipeTap.Application ();
PipeTap.Logic.app = app;
PipeTap.Logic.init ();
PipeTap.Logic.WirePlumber.init ();
return app.run (args); return app.run (args);
} }

View file

@ -3,13 +3,28 @@ pipetap_sources = [
'application.vala', 'application.vala',
'gui/window.vala', 'gui/window.vala',
'gui/main_bar.vala', 'gui/main_bar.vala',
'gui/slider_overlay.vala',
'logic/ctrl.vala',
'logic/wireplumber.vala',
] ]
wp_api = gnome.generate_vapi(
'wireplumber-0.5',
sources: ['Wp-0.5.gir'],
metadata_dirs: srcdir,
packages: ['gio-2.0', 'gobject-2.0', 'glib-2.0'],
)
# That's for WirePlumber
add_project_arguments(['--vapidir', meson.current_build_dir()], language: 'vala')
pipetap_deps = [ pipetap_deps = [
config_dep, config_dep,
dependency('gtk4'), dependency('gtk4'),
dependency('libadwaita-1', version: '>= 1.4'), dependency('libadwaita-1', version: '>= 1.4'),
dependency('gtk4-layer-shell-0'), dependency('gtk4-layer-shell-0'),
dependency('wireplumber-0.5'),
wp_api,
] ]
blueprints = custom_target( blueprints = custom_target(
@ -17,6 +32,7 @@ blueprints = custom_target(
input: files( input: files(
'gtk/help-overlay.blp', 'gtk/help-overlay.blp',
'gui/main_bar.blp', 'gui/main_bar.blp',
'gui/slider_overlay.blp',
'gui/window.blp', 'gui/window.blp',
), ),
output: '.', output: '.',

View file

@ -3,6 +3,7 @@
<gresource prefix="/io/gitlab/nekocwd/pipetap"> <gresource prefix="/io/gitlab/nekocwd/pipetap">
<file preprocess="xml-stripblanks">gui/window.ui</file> <file preprocess="xml-stripblanks">gui/window.ui</file>
<file preprocess="xml-stripblanks">gui/main_bar.ui</file> <file preprocess="xml-stripblanks">gui/main_bar.ui</file>
<file preprocess="xml-stripblanks">gui/slider_overlay.ui</file>
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file> <file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file>style.css</file> <file>style.css</file>
</gresource> </gresource>

View file

@ -1,3 +1,18 @@
.pt-transparent { .pt-transparent {
border-radius: 10px; border-radius: 10px;
}
.pt-slider>trough>slider {
padding: 3px;
}
.pt-slider>trough {
padding: 3px;
}
.pt-slider>trough>highlight {
box-shadow: none;
outline: none;
background-color: transparent;
background-image: none;
} }