This commit is contained in:
Fabio Lenherr / DashieTM 2023-12-06 23:40:47 +01:00
parent 3821cf48c2
commit 741d678ffd
12 changed files with 224 additions and 63 deletions

View file

@ -5,8 +5,8 @@ edition = "2021"
description = "A wip universal Linux settings application."
[dependencies]
reset_daemon = "0.4.2"
re_set-lib = "0.6.4"
reset_daemon = "0.4.3"
re_set-lib = "0.6.5"
adw = { version = "0.5.3", package = "libadwaita", features = ["v1_4"] }
dbus = "0.9.7"
gtk = { version = "0.7.3", package = "gtk4", features = ["v4_12"] }

View file

@ -28,6 +28,7 @@ pub struct Listeners {
pub wifi_disabled: AtomicBool,
pub wifi_listener: AtomicBool,
pub bluetooth_listener: AtomicBool,
pub bluetooth_scan_requested: AtomicBool,
pub pulse_listener: AtomicBool,
}

View file

@ -41,7 +41,6 @@ fn setup_callbacks(
listeners: Arc<Listeners>,
bluetooth_box: Arc<BluetoothBox>,
) -> Arc<BluetoothBox> {
let bluetooth_box_button_ref = bluetooth_box.clone();
let bluetooth_box_ref = bluetooth_box.clone();
let listeners_ref = listeners.clone();
let imp = bluetooth_box.imp();
@ -58,7 +57,9 @@ fn setup_callbacks(
imp.reset_bluetooth_refresh_button
.connect_clicked(move |button| {
button.set_sensitive(false);
start_bluetooth_listener(listeners.clone(), bluetooth_box_button_ref.clone());
listeners
.bluetooth_scan_requested
.store(true, Ordering::SeqCst);
});
imp.reset_bluetooth_discoverable_switch
@ -176,7 +177,7 @@ pub fn populate_conntected_bluetooth_devices(bluetooth_box: Arc<BluetoothBox>) {
for device in devices {
let path = device.path.clone();
let connected = device.connected;
let bluetooth_entry = Arc::new(BluetoothEntry::new(&device));
let bluetooth_entry = BluetoothEntry::new(&device);
imp.available_devices
.borrow_mut()
.insert(path, (bluetooth_entry.clone(), device));
@ -236,7 +237,7 @@ pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<Bl
let imp = bluetooth_box.imp();
let path = ir.bluetooth_device.path.clone();
let connected = ir.bluetooth_device.connected;
let bluetooth_entry = Arc::new(BluetoothEntry::new(&ir.bluetooth_device));
let bluetooth_entry = BluetoothEntry::new(&ir.bluetooth_device);
imp.available_devices
.borrow_mut()
.insert(path, (bluetooth_entry.clone(), ir.bluetooth_device));
@ -275,7 +276,7 @@ pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<Bl
let res = conn.add_match(device_changed, move |ir: BluetoothDeviceChanged, _, _| {
let bluetooth_box = device_changed_box.clone();
println!("removed");
println!("changed");
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = bluetooth_box.imp();
@ -318,17 +319,21 @@ pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<Bl
}
listeners.bluetooth_listener.store(true, Ordering::SeqCst);
let time = SystemTime::now();
let mut time = SystemTime::now();
let mut listener_active = true;
loop {
let _ = conn.process(Duration::from_millis(1000));
if !listeners.bluetooth_listener.load(Ordering::SeqCst)
|| time.elapsed().unwrap() > Duration::from_millis(25000)
{
listeners.bluetooth_listener.store(false, Ordering::SeqCst);
if !listeners.bluetooth_listener.load(Ordering::SeqCst) {
println!("stopping bluetooth listener");
break;
}
if listener_active && time.elapsed().unwrap() > Duration::from_millis(25000) {
listener_active = false;
let instance_ref = loop_box.clone();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = loop_box.imp();
let imp = instance_ref.imp();
let mut entries = imp.available_devices.borrow_mut();
for entry in entries.iter() {
imp.reset_bluetooth_available_devices.remove(&*entry.1 .0);
@ -339,8 +344,15 @@ pub fn start_bluetooth_listener(listeners: Arc<Listeners>, bluetooth_box: Arc<Bl
});
let _: Result<(), Error> =
proxy.method_call("org.Xetibo.ReSetBluetooth", "StopBluetoothListener", ());
println!("stopping bluetooth listener");
break;
}
if !listener_active && listeners.bluetooth_scan_requested.load(Ordering::SeqCst) {
listeners
.bluetooth_scan_requested
.store(false, Ordering::SeqCst);
listener_active = true;
let _: Result<(), Error> =
proxy.method_call("org.Xetibo.ReSetBluetooth", "StartBluetoothListener", ());
time = SystemTime::now();
}
thread::sleep(Duration::from_millis(100));
}

View file

@ -4,13 +4,13 @@ use std::time::Duration;
use crate::components::bluetooth::bluetooth_entry_impl;
use adw::glib::Object;
use adw::{glib, ActionRow};
use adw::prelude::{ActionRowExt, PreferencesRowExt};
use adw::{glib, ActionRow};
use dbus::blocking::Connection;
use dbus::{Error, Path};
use glib::subclass::prelude::ObjectSubclassIsExt;
use gtk::prelude::{ButtonExt, ListBoxRowExt, WidgetExt};
use gtk::{gio, GestureClick, Image, Button, Align};
use gtk::{gio, Align, Button, GestureClick, Image, Label};
use re_set_lib::bluetooth::bluetooth_structures::BluetoothDevice;
glib::wrapper! {
@ -23,15 +23,25 @@ unsafe impl Send for BluetoothEntry {}
unsafe impl Sync for BluetoothEntry {}
impl BluetoothEntry {
pub fn new(device: &BluetoothDevice) -> Self {
let entry: BluetoothEntry = Object::builder().build();
pub fn new(device: &BluetoothDevice) -> Arc<Self> {
let entry: Arc<BluetoothEntry> = Arc::new(Object::builder().build());
let entry_imp = entry.imp();
let entry_ref = entry.clone();
entry.set_title(&device.alias);
entry.set_subtitle(&device.address);
entry.set_activatable(true);
entry_imp.remove_device_button.replace(Button::builder().icon_name("user-trash-symbolic").valign(Align::Center).build());
entry_imp.remove_device_button.replace(
Button::builder()
.icon_name("user-trash-symbolic")
.valign(Align::Center)
.build(),
);
entry_imp.connecting_label.replace(
Label::builder()
.label("")
.build(),
);
entry.add_suffix(entry_imp.remove_device_button.borrow().deref());
if device.icon.is_empty() {
entry.add_prefix(&Image::from_icon_name("dialog-question-symbolic"));
@ -44,9 +54,12 @@ impl BluetoothEntry {
entry_imp.remove_device_button.borrow().set_sensitive(false);
}
let path = Arc::new(device.path.clone());
entry_imp.remove_device_button.borrow().connect_clicked(move |_| {
remove_device_pairing((*path).clone());
});
entry_imp
.remove_device_button
.borrow()
.connect_clicked(move |_| {
remove_device_pairing((*path).clone());
});
let gesture = GestureClick::new();
let connected = device.connected;
// let paired = device.paired;
@ -54,11 +67,23 @@ impl BluetoothEntry {
// TODO implement paired
let path = device.path.clone();
gesture.connect_released(move |_, _, _, _| {
connect_to_device(path.clone());
if connected {
disconnect_from_device(path.clone());
let imp = entry_ref.imp();
imp.remove_device_button.borrow().set_sensitive(false);
entry_ref
.imp()
.connecting_label
.borrow()
.set_text("Disconnecting...");
disconnect_from_device(entry_ref.clone(), path.clone());
} else {
connect_to_device(path.clone());
entry_ref.set_sensitive(false);
entry_ref
.imp()
.connecting_label
.borrow()
.set_text("Connecting...");
connect_to_device(entry_ref.clone(), path.clone());
}
});
entry.add_controller(gesture);
@ -66,7 +91,7 @@ impl BluetoothEntry {
}
}
fn connect_to_device(path: Path<'static>) {
fn connect_to_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
@ -74,11 +99,26 @@ fn connect_to_device(path: Path<'static>) {
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(1000),
);
let _: Result<(bool,), Error> = proxy.method_call(
let res: Result<(bool,), Error> = proxy.method_call(
"org.Xetibo.ReSetBluetooth",
"ConnectToBluetoothDevice",
(path,),
);
glib::spawn_future(async move {
glib::idle_add_once(move || {
if res.is_err() {
entry.set_sensitive(true);
entry
.imp()
.connecting_label
.borrow()
.set_text("Error on connecting");
} else {
entry.set_sensitive(true);
entry.imp().connecting_label.borrow().set_text("");
}
});
});
});
}
@ -98,7 +138,7 @@ fn connect_to_device(path: Path<'static>) {
// });
// }
fn disconnect_from_device(path: Path<'static>) {
fn disconnect_from_device(entry: Arc<BluetoothEntry>, path: Path<'static>) {
gio::spawn_blocking(move || {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
@ -106,11 +146,25 @@ fn disconnect_from_device(path: Path<'static>) {
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(1000),
);
let _: Result<(bool,), Error> = proxy.method_call(
let res: Result<(bool,), Error> = proxy.method_call(
"org.Xetibo.ReSetBluetooth",
"DisconnectFromBluetoothDevice",
(path,),
);
glib::spawn_future(async move {
glib::idle_add_once(move || {
let imp = entry.imp();
if res.is_err() {
imp.remove_device_button.borrow().set_sensitive(true);
imp.connecting_label
.borrow()
.set_text("Error on disconnecting");
} else {
imp.remove_device_button.borrow().set_sensitive(true);
imp.connecting_label.borrow().set_text("");
}
});
});
});
}

View file

@ -3,13 +3,14 @@ use adw::subclass::action_row::ActionRowImpl;
use adw::subclass::preferences_row::PreferencesRowImpl;
use adw::ActionRow;
use gtk::subclass::prelude::*;
use gtk::{glib, Button, CompositeTemplate};
use gtk::{glib, Button, CompositeTemplate, Label};
use std::cell::RefCell;
#[derive(Default, CompositeTemplate)]
#[template(resource = "/org/Xetibo/ReSet/resetBluetoothEntry.ui")]
pub struct BluetoothEntry {
pub remove_device_button: RefCell<Button>,
pub connecting_label: RefCell<Label>,
pub device_name: RefCell<String>,
}

View file

@ -193,19 +193,16 @@ pub fn populate_sources(input_box: Arc<SourceBox>) {
.reset_source_mute
.connect_clicked(move |_| {
let imp = output_box_ref_mute.imp();
let stream = imp.reset_default_source.clone();
let mut stream = stream.borrow_mut();
let mut stream = imp.reset_default_source.borrow_mut();
stream.muted = !stream.muted;
let muted = stream.muted;
let index = stream.index;
if muted {
if stream.muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
toggle_source_mute(index, muted);
toggle_source_mute(stream.index, stream.muted);
});
});
});
@ -295,6 +292,21 @@ fn get_cards() -> Vec<Card> {
res.unwrap().0
}
fn get_default_source_name() -> String {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(1000),
);
let res: Result<(String,), Error> =
proxy.method_call("org.Xetibo.ReSetAudio", "GetDefaultSourceName", ());
if res.is_err() {
return String::from("");
}
res.unwrap().0
}
fn get_default_source() -> Source {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
@ -393,7 +405,6 @@ pub fn start_input_box_listener(conn: Connection, source_box: Arc<SourceBox>) ->
let res = conn.add_match(source_removed, move |ir: SourceRemoved, _, _| {
let source_box = source_removed_box.clone();
println!("removed {}", ir.index);
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = source_box.clone();
@ -433,12 +444,12 @@ pub fn start_input_box_listener(conn: Connection, source_box: Arc<SourceBox>) ->
let res = conn.add_match(source_changed, move |ir: SourceChanged, _, _| {
let source_box = source_changed_box.clone();
let default_source = get_default_source();
let default_source = get_default_source_name();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = source_box.clone();
let output_box_imp = output_box.imp();
let is_default = ir.source.name == default_source.name;
let is_default = ir.source.name == default_source;
let volume = ir.source.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
@ -451,6 +462,18 @@ pub fn start_input_box_listener(conn: Connection, source_box: Arc<SourceBox>) ->
if is_default {
output_box_imp.reset_volume_percentage.set_text(&percentage);
output_box_imp.reset_volume_slider.set_value(*volume as f64);
output_box_imp
.reset_default_source
.replace(ir.source.clone());
if ir.source.muted {
output_box_imp
.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
output_box_imp
.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
imp.reset_selected_source.set_active(true);
} else {
imp.reset_selected_source.set_active(false);
@ -459,6 +482,13 @@ pub fn start_input_box_listener(conn: Connection, source_box: Arc<SourceBox>) ->
.set_title(ir.source.alias.clone().as_str());
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
if ir.source.muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
});
});
true

View file

@ -59,7 +59,6 @@ impl SourceEntry {
}),
);
imp.reset_selected_source.set_group(Some(&*check_group));
// check_group.set_group(Some(&*imp.resetSelectedSink));
if is_default {
imp.reset_selected_source.set_active(true);
} else {
@ -75,16 +74,14 @@ impl SourceEntry {
let stream = imp.stream.clone();
let mut stream = stream.borrow_mut();
stream.muted = !stream.muted;
let muted = stream.muted;
let index = stream.index;
if muted {
if stream.muted {
imp.reset_source_mute
.set_icon_name("microphone-disabled-symbolic");
} else {
imp.reset_source_mute
.set_icon_name("audio-input-microphone-symbolic");
}
toggle_source_mute(index, muted);
toggle_source_mute(stream.index, stream.muted);
}));
}
obj

View file

@ -200,19 +200,16 @@ pub fn populate_sinks(output_box: Arc<SinkBox>) {
.reset_sink_mute
.connect_clicked(move |_| {
let imp = output_box_ref_mute.imp();
let stream = imp.reset_default_sink.clone();
let mut stream = stream.borrow_mut();
let mut stream = imp.reset_default_sink.borrow_mut();
stream.muted = !stream.muted;
let muted = stream.muted;
let index = stream.index;
if muted {
if stream.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
toggle_sink_mute(index, muted);
toggle_sink_mute(stream.index, stream.muted);
});
});
});
@ -286,6 +283,21 @@ fn get_sinks() -> Vec<Sink> {
res.unwrap().0
}
fn get_default_sink_name() -> String {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
"org.Xetibo.ReSetDaemon",
"/org/Xetibo/ReSetDaemon",
Duration::from_millis(1000),
);
let res: Result<(String,), Error> =
proxy.method_call("org.Xetibo.ReSetAudio", "GetDefaultSinkName", ());
if res.is_err() {
return String::from("");
}
res.unwrap().0
}
fn get_default_sink() -> Sink {
let conn = Connection::new_session().unwrap();
let proxy = conn.with_proxy(
@ -437,12 +449,12 @@ pub fn start_output_box_listener(conn: Connection, sink_box: Arc<SinkBox>) -> Co
let res = conn.add_match(sink_changed, move |ir: SinkChanged, _, _| {
let sink_box = sink_changed_box.clone();
let default_sink = get_default_sink();
let default_sink = get_default_sink_name();
glib::spawn_future(async move {
glib::idle_add_once(move || {
let output_box = sink_box.clone();
let output_box_imp = output_box.imp();
let is_default = ir.sink.name == default_sink.name;
let is_default = ir.sink.name == default_sink;
let volume = ir.sink.volume.first().unwrap_or(&0_u32);
let fraction = (*volume as f64 / 655.36).round();
let percentage = (fraction).to_string() + "%";
@ -456,6 +468,16 @@ pub fn start_output_box_listener(conn: Connection, sink_box: Arc<SinkBox>) -> Co
if is_default {
output_box_imp.reset_volume_percentage.set_text(&percentage);
output_box_imp.reset_volume_slider.set_value(*volume as f64);
output_box_imp.reset_default_sink.replace(ir.sink.clone());
if ir.sink.muted {
output_box_imp
.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
output_box_imp
.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
imp.reset_selected_sink.set_active(true);
} else {
imp.reset_selected_sink.set_active(false);
@ -464,12 +486,19 @@ pub fn start_output_box_listener(conn: Connection, sink_box: Arc<SinkBox>) -> Co
.set_title(ir.sink.alias.clone().as_str());
imp.reset_volume_percentage.set_text(&percentage);
imp.reset_volume_slider.set_value(*volume as f64);
if ir.sink.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
});
});
true
});
if res.is_err() {
println!("fail on sink remove");
println!("fail on sink change");
return conn;
}

View file

@ -71,16 +71,14 @@ impl SinkEntry {
let stream = imp.stream.clone();
let mut stream = stream.borrow_mut();
stream.muted = !stream.muted;
let muted = stream.muted;
let index = stream.index;
if muted {
if stream.muted {
imp.reset_sink_mute
.set_icon_name("audio-volume-muted-symbolic");
} else {
imp.reset_sink_mute
.set_icon_name("audio-volume-high-symbolic");
}
toggle_sink_mute(index, muted);
toggle_sink_mute(stream.index, stream.muted);
}));
}
obj

View file

@ -1,5 +1,4 @@
use gtk::prelude::FrameExt;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use crate::components::base::setting_box::SettingBox;
@ -87,9 +86,6 @@ pub const HANDLE_VOLUME_CLICK: fn(Arc<Listeners>, FlowBox) =
listeners.stop_bluetooth_listener();
let audio_output = Arc::new(SinkBox::new());
start_audio_listener(listeners.clone(), Some(audio_output.clone()), None);
while !listeners.pulse_listener.load(Ordering::SeqCst) {
std::hint::spin_loop()
}
populate_sinks(audio_output.clone());
let audio_frame = wrap_in_flow_box_child(SettingBox::new(&*audio_output));
reset_main.remove_all();

View file

@ -1,6 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Created with Cambalache 0.17.0 -->
<interface>
<requires lib="gtk" version="4.12"/>
<requires lib="libadwaita" version="1.0"/>
<template class="resetBluetoothEntry" parent="AdwActionRow">
<property name="margin-start">5</property>

View file

@ -582,6 +582,48 @@
(10,206,"GtkWidget","margin-top","5",None,None,None,None,None,None,None,None,None),
(10,207,"GtkButton","icon-name","view-refresh-symbolic",None,None,None,None,None,None,None,None,None),
(11,1,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None),
(11,32,"GtkListBoxRow","child",None,None,None,None,None,39,None,None,None,None),
(11,32,"GtkWidget","margin-start","5",None,None,None,None,None,None,None,None,None),
(11,40,"GtkImage","icon-name","input-mouse-symbolic",None,None,None,None,None,None,None,None,None),
(11,40,"GtkWidget","halign","start",None,None,None,None,None,None,None,None,None),
(11,40,"GtkWidget","margin-end","15",None,None,None,None,None,None,None,None,None),
(11,44,"GtkButton","has-frame","False",None,None,None,None,None,None,None,None,None),
(11,44,"GtkButton","icon-name","user-trash-symbolic",None,None,None,None,None,None,None,None,None),
(11,44,"GtkWidget","halign","end",None,None,None,None,None,None,None,None,None),
(11,47,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(11,47,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(11,47,"GtkWidget","hexpand-set","True",None,None,None,None,None,None,None,None,None),
(11,48,"GtkLabel","ellipsize","end",None,None,None,None,None,None,None,None,None),
(11,48,"GtkLabel","label","LoremIpsum Wireless Mouse",None,None,None,None,None,None,None,None,None),
(11,48,"GtkLabel","single-line-mode","True",None,None,None,None,None,None,None,None,None),
(11,48,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
(11,48,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(11,48,"GtkWidget","hexpand-set","True",None,None,None,None,None,None,None,None,None),
(11,48,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
(11,48,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(11,48,"GtkWidget","vexpand-set","True",None,None,None,None,None,None,None,None,None),
(11,48,"GtkWidget","width-request","200",None,None,None,None,None,None,None,None,None),
(11,49,"GtkLabel","ellipsize","end",None,None,None,None,None,None,None,None,None),
(11,49,"GtkLabel","label","LoremIpsum Wireless Mouse",None,None,None,None,None,None,None,None,None),
(11,49,"GtkLabel","single-line-mode","True",None,None,None,None,None,None,None,None,None),
(11,49,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
(11,49,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(11,49,"GtkWidget","hexpand-set","True",None,None,None,None,None,None,None,None,None),
(11,49,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
(11,49,"GtkWidget","sensitive","False",None,None,None,None,None,None,None,None,None),
(11,49,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(11,49,"GtkWidget","vexpand-set","True",None,None,None,None,None,None,None,None,None),
(11,49,"GtkWidget","width-request","200",None,None,None,None,None,None,None,None,None),
(11,50,"GtkLabel","ellipsize","end",None,None,None,None,None,None,None,None,None),
(11,50,"GtkLabel","single-line-mode","True",None,None,None,None,None,None,None,None,None),
(11,50,"GtkLabel","xalign","0.0",None,None,None,None,None,None,None,None,None),
(11,50,"GtkWidget","hexpand","True",None,None,None,None,None,None,None,None,None),
(11,50,"GtkWidget","hexpand-set","True",None,None,None,None,None,None,None,None,None),
(11,50,"GtkWidget","margin-end","10",None,None,None,None,None,None,None,None,None),
(11,50,"GtkWidget","sensitive","False",None,None,None,None,None,None,None,None,None),
(11,50,"GtkWidget","vexpand","True",None,None,None,None,None,None,None,None,None),
(11,50,"GtkWidget","vexpand-set","True",None,None,None,None,None,None,None,None,None),
(11,50,"GtkWidget","width-request","200",None,None,None,None,None,None,None,None,None),
(12,11,"GtkOrientable","orientation","vertical",None,None,None,None,None,None,None,None,None),
(12,11,"GtkWidget","valign","start",None,None,None,None,None,None,None,None,None),
(12,12,"GtkLabel","label","Input",None,None,None,None,None,None,None,None,None),