Add upload functionality

This commit is contained in:
lemon-sh 2021-09-22 17:00:10 +02:00
parent fb9be91262
commit d4b5fbaf97
5 changed files with 273 additions and 139 deletions

167
Cargo.lock generated
View File

@ -10,9 +10,8 @@ checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
[[package]]
name = "atk"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a83b21d2aa75e464db56225e1bda2dd5993311ba1095acaa8fa03d1ae67026ba"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk3-rs#dac484857793cebac5becc15fe605dd99f3453a4"
dependencies = [
"atk-sys",
"bitflags",
@ -22,9 +21,8 @@ dependencies = [
[[package]]
name = "atk-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "badcf670157c84bb8b1cf6b5f70b650fed78da2033c9eed84c4e49b11cbe83ea"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk3-rs#dac484857793cebac5becc15fe605dd99f3453a4"
dependencies = [
"glib-sys",
"gobject-sys",
@ -58,9 +56,8 @@ checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]]
name = "cairo-rs"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f859ade407c19810ae920b4fafab92189ed312adad490d08fb16b5f49f1e2207"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"bitflags",
"cairo-sys-rs",
@ -71,9 +68,8 @@ dependencies = [
[[package]]
name = "cairo-sys-rs"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c9c3928781e8a017ece15eace05230f04b647457d170d2d9641c94a444ff80"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"glib-sys",
"libc",
@ -88,9 +84,9 @@ checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
[[package]]
name = "cfg-expr"
version = "0.8.1"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e"
checksum = "edae0b9625d1fce32f7d64b71784d9b1bf8469ec1a9c417e44aaf16a9cbd7571"
dependencies = [
"smallvec",
]
@ -108,10 +104,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]]
name = "either"
version = "1.6.1"
name = "directories"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "field-offset"
@ -187,9 +197,8 @@ dependencies = [
[[package]]
name = "gdk"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a453eae5ec10345b3a96ca1b547328bfc94edd40aa95b08f14bb4c35863db140"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk3-rs#dac484857793cebac5becc15fe605dd99f3453a4"
dependencies = [
"bitflags",
"cairo-rs",
@ -203,9 +212,8 @@ dependencies = [
[[package]]
name = "gdk-pixbuf"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534192cb8f01daeb8fab2c8d4baa8f9aae5b7a39130525779f5c2608e235b10f"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"gdk-pixbuf-sys",
"gio",
@ -215,9 +223,8 @@ dependencies = [
[[package]]
name = "gdk-pixbuf-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f097c0704201fbc8f69c1762dc58c6947c8bb188b8ed0bc7e65259f1894fe590"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"gio-sys",
"glib-sys",
@ -228,9 +235,8 @@ dependencies = [
[[package]]
name = "gdk-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e091b3d3d6696949ac3b3fb3c62090e5bfd7bd6850bef5c3c5ea701de1b1f1e"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk3-rs#dac484857793cebac5becc15fe605dd99f3453a4"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@ -256,9 +262,8 @@ dependencies = [
[[package]]
name = "gio"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a4c12fcba7a6402ae843a0085ec16d3658a87901763b6a7f0a7c5d60e555a5"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"bitflags",
"futures-channel",
@ -273,9 +278,8 @@ dependencies = [
[[package]]
name = "gio-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0a41df66e57fcc287c4bcf74fc26b884f31901ea9792ec75607289b456f48fa"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"glib-sys",
"gobject-sys",
@ -286,9 +290,8 @@ dependencies = [
[[package]]
name = "glib"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4a930b7208e6e0ab839eea5f65ac2b82109f729621430d47fe905e2e09d33f4"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"bitflags",
"futures-channel",
@ -305,9 +308,8 @@ dependencies = [
[[package]]
name = "glib-macros"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"anyhow",
"heck",
@ -320,9 +322,8 @@ dependencies = [
[[package]]
name = "glib-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c1d60554a212445e2a858e42a0e48cece1bd57b311a19a9468f70376cf554ae"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"libc",
"system-deps",
@ -330,9 +331,8 @@ dependencies = [
[[package]]
name = "gobject-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa92cae29759dae34ab5921d73fff5ad54b3d794ab842c117e36cafc7994c3f5"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"glib-sys",
"libc",
@ -341,9 +341,8 @@ dependencies = [
[[package]]
name = "gtk"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6603bb79ded6ac6f3bac203794383afa8b1d6a8656d34a93a88f0b22826cd46c"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk3-rs#dac484857793cebac5becc15fe605dd99f3453a4"
dependencies = [
"atk",
"bitflags",
@ -364,9 +363,8 @@ dependencies = [
[[package]]
name = "gtk-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c14c8d3da0545785a7c5a120345b3abb534010fb8ae0f2ef3f47c027fba303e"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk3-rs#dac484857793cebac5becc15fe605dd99f3453a4"
dependencies = [
"atk-sys",
"cairo-sys-rs",
@ -382,9 +380,8 @@ dependencies = [
[[package]]
name = "gtk3-macros"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21de1da96dc117443fb03c2e270b2d34b7de98d0a79a19bbb689476173745b79"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk3-rs#dac484857793cebac5becc15fe605dd99f3453a4"
dependencies = [
"anyhow",
"heck",
@ -415,15 +412,6 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "itertools"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [
"either",
]
[[package]]
name = "js-sys"
version = "0.3.55"
@ -512,9 +500,8 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "pango"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fc88307d9797976ea62722ff2ec5de3fae279c6e20100ed3f49ca1a4bf3f96"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"bitflags",
"glib",
@ -525,9 +512,8 @@ dependencies = [
[[package]]
name = "pango-sys"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2367099ca5e761546ba1d501955079f097caa186bb53ce0f718dca99ac1942fe"
version = "0.15.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#eb0d2f89f1dc4f0bdb4ae011a46626fea946ea2d"
dependencies = [
"glib-sys",
"gobject-sys",
@ -675,6 +661,16 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@ -773,24 +769,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strum"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
[[package]]
name = "strum_macros"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.76"
@ -804,18 +782,13 @@ dependencies = [
[[package]]
name = "system-deps"
version = "3.2.0"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6"
checksum = "6c1889ab44c2a423ba9ba4d64cd04989b25c0280ca7ade813f05368418722a04"
dependencies = [
"anyhow",
"cfg-expr",
"heck",
"itertools",
"pkg-config",
"strum",
"strum_macros",
"thiserror",
"toml",
"version-compare",
]
@ -1082,7 +1055,7 @@ name = "zdiu"
version = "0.1.0"
dependencies = [
"anyhow",
"glib",
"directories",
"gtk",
"json",
"multipart",

View File

@ -7,9 +7,9 @@ edition = "2018"
lto = true
[dependencies]
gtk = "0"
glib = "0"
gtk = { git = "https://github.com/gtk-rs/gtk3-rs" }
anyhow = "1"
directories = "4.0"
[dependencies.json]
version = "0"

View File

@ -50,7 +50,7 @@ impl<T: Read, F: FnMut(u64)> Read for CountingRead<'_, T, F> {
}
}
pub fn upload_discord<F: FnMut(f64)>(filename: &str, data: &[u8], webhook: &str, mut percent_callback: F) -> anyhow::Result<(String, String)> {
pub fn upload_discord<F: FnMut(f64)>(filename: &str, data: &[u8], webhook: &str, mut percent_callback: F) -> anyhow::Result<String> {
let mut cursor = io::Cursor::new(data);
let mut mp = Multipart::new();
let counting_reader = CountingRead::new(&mut cursor, move |bytes| {
@ -63,7 +63,5 @@ pub fn upload_discord<F: FnMut(f64)>(filename: &str, data: &[u8], webhook: &str,
.send(mpdata)?;
let response_string = response.into_string()?;
let mut rjson = json::parse(&response_string)?;
let url = rjson["attachments"][0]["url"].take_string().ok_or_else(|| DiscordJsonError::new(response_string.clone()))?;
let id = rjson["id"].take_string().ok_or_else(|| DiscordJsonError::new(response_string.clone()))?;
Ok((url, id))
Ok(rjson["attachments"][0]["url"].take_string().ok_or_else(|| DiscordJsonError::new(response_string.clone()))?)
}

View File

@ -1,10 +1,16 @@
use gtk::*;
use gtk::prelude::*;
use gtk::glib::clone;
use std::sync::{Mutex, Arc};
use glib::Sender;
use std::fs::File;
use std::fs;
use std::path::Path;
use std::io::{SeekFrom, Seek, Read};
use anyhow::anyhow;
use crate::discord::upload_discord;
mod discord;
const MAXBUF: u64 = 8_380_416;
fn main() {
let app = Application::builder().application_id("moe.lemonsh.zdiu").build();
@ -19,11 +25,35 @@ fn errormsg<P: IsA<Window>>(text: &str, parent: Option<&P>) {
}
enum UiThreadTask {
UpdateProgress(f64), Finalize(String)
UpdateProgress(f64), Finalize(anyhow::Result<String>)
}
fn upload_file_task(tx: Sender<UiThreadTask>) {
fn upload_file_task(tx: &Sender<UiThreadTask>, mut file: File, size: u64, name: &str, webhook: &str) -> anyhow::Result<String> {
let mut buffer = vec![0_u8; size as usize];
file.read_exact(&mut buffer[..])?;
upload_discord(name, &buffer, webhook, move |fr| {
tx.send(UiThreadTask::UpdateProgress(fr)).unwrap();
})
}
fn lock_gui(file_upload_button: &FileChooserButton, clip_upload_button: &Button, webhook_control: &Button, progress: &ProgressBar) {
file_upload_button.set_sensitive(false);
clip_upload_button.set_sensitive(false);
webhook_control.set_sensitive(false);
progress.set_fraction(0.0);
progress.set_text(Some("Loading..."));
}
fn open_limited_and_get_name(path: &Path) -> anyhow::Result<(File, u64, String)> {
let filename = path.file_name().unwrap().to_str().unwrap();
let mut file_handle = File::open(path)?;
let filesize = file_handle.seek(SeekFrom::End(0))?;
file_handle.seek(SeekFrom::Start(0))?;
if filesize > MAXBUF {
Err(anyhow!("File is too big."))
} else {
Ok((file_handle, filesize, filename.to_string()))
}
}
fn build_ui(app: &Application) {
@ -33,27 +63,119 @@ fn build_ui(app: &Application) {
let file_upload_button: FileChooserButton = builder.object("file_upload_button").unwrap();
let clip_upload_button: Button = builder.object("clip_upload_button").unwrap();
let settings_button: Button = builder.object("settings").unwrap();
let progress: ProgressBar = builder.object("upload_progress").unwrap();
let copy_button: Button = builder.object("copy_button").unwrap();
let output_field: Entry = builder.object("output_box").unwrap();
let webhook_entry: Entry = builder.object("webhook_entry").unwrap();
let webhook_control: Button = builder.object("webhook_control").unwrap();
file_upload_button.connect_file_set(clone!(@strong tx, @weak file_upload_button, @weak clip_upload_button, @weak settings_button, @weak progress => move |b| {
file_upload_button.set_sensitive(false);
clip_upload_button.set_sensitive(false);
settings_button.set_sensitive(false);
let new_sender = tx.clone();
std::thread::spawn(move || { upload_file_task(new_sender); });
let projectdir = directories::ProjectDirs::from("moe", "lemonsh", "zdiu").unwrap();
let configdir = projectdir.config_dir();
if !configdir.is_dir() {
fs::create_dir_all(configdir).unwrap();
}
let webhookpath = configdir.join("webhook.txt");
if webhookpath.is_file() {
webhook_entry.set_text(fs::read_to_string(&webhookpath).unwrap().as_str());
}
file_upload_button.connect_file_set(clone!(@strong tx, @weak window, @weak webhook_entry, @weak clip_upload_button, @weak webhook_control, @weak progress => move |b| {
let webhook = webhook_entry.text().to_string();
if webhook.is_empty() {
errormsg("Set a webhook first.", Some(&window));
b.unselect_all();
return;
}
let file = b.filename().unwrap();
match open_limited_and_get_name(&file) {
Ok(o) => {
lock_gui(b, &clip_upload_button, &webhook_control, &progress);
let new_sender = tx.clone();
std::thread::spawn(move || {
let result = upload_file_task(&new_sender, o.0, o.1, &o.2, &webhook);
new_sender.send(UiThreadTask::Finalize(result)).unwrap();
});
}
Err(e) => {
errormsg(e.to_string().as_str(), Some(&window));
b.unselect_all();
}
};
}));
clip_upload_button.connect_clicked(clone!(@strong tx, @weak window, @weak webhook_entry, @weak file_upload_button, @weak webhook_control, @weak progress => move |b| {
let webhook = webhook_entry.text().to_string();
if webhook.is_empty() {
errormsg("Set a webhook first.", Some(&window));
return;
}
match gtk::Clipboard::get(&gdk::SELECTION_CLIPBOARD).wait_for_image() {
Some(p) => {
let pngbuffer = p.save_to_bufferv("png", &[]).unwrap();
lock_gui(&file_upload_button, b, &webhook_control, &progress);
let new_sender = tx.clone();
std::thread::spawn(move || {
let result = upload_discord("zdiupload.png", &pngbuffer, webhook.as_str(), |fr| {
new_sender.send(UiThreadTask::UpdateProgress(fr)).unwrap();
});
new_sender.send(UiThreadTask::Finalize(result)).unwrap();
});
}
None => {
errormsg("There are no images in the clipboard.", Some(&window));
}
}
}));
copy_button.connect_clicked(clone!(@weak output_field => move |_| {
gtk::Clipboard::get(&gdk::SELECTION_CLIPBOARD).set_text(output_field.text().as_str());
let text = output_field.text();
if !text.is_empty() {
gtk::Clipboard::get(&gdk::SELECTION_CLIPBOARD).set_text(text.as_str());
}
}));
webhook_control.connect_clicked(clone!(@weak window, @weak webhook_entry, @weak file_upload_button, @weak clip_upload_button => move |b| {
if webhook_entry.get_sensitive() {
file_upload_button.set_sensitive(true);
clip_upload_button.set_sensitive(true);
webhook_entry.set_sensitive(false);
webhook_entry.set_visibility(false);
b.set_label("Edit");
if let Err(e) = fs::write(&webhookpath, webhook_entry.text().as_str()) {
errormsg(format!("Couldn't save webhook: {}", e).as_str(), Some(&window));
} else {
println!("Saved webhook to '{:?}'", &webhookpath);
}
} else {
file_upload_button.set_sensitive(false);
clip_upload_button.set_sensitive(false);
webhook_entry.set_sensitive(true);
webhook_entry.set_visibility(true);
b.set_label("Save");
}
}));
output_field.set_text("AE");
rx.attach(None, move |data| {
rx.attach(None, clone!(@strong window => move |data| {
match data {
UiThreadTask::UpdateProgress(p) => {
progress.set_fraction(p);
progress.set_text(Some(format!("{:.1}%", p*100.0).as_str()));
}
UiThreadTask::Finalize(u) => {
file_upload_button.set_sensitive(true);
clip_upload_button.set_sensitive(true);
webhook_control.set_sensitive(true);
progress.set_fraction(0.0);
progress.set_text(Some("0%"));
match u {
Ok(o) => {
output_field.set_text(o.as_str());
}
Err(e) => {
errormsg(e.to_string().as_str(), Some(&window));
}
}
}
}
glib::Continue(true)
});
}));
window.set_application(Some(app));
window.present();
}

View File

@ -5,7 +5,7 @@
<object class="GtkApplicationWindow" id="window">
<property name="can-focus">False</property>
<child>
<!-- n-columns=2 n-rows=4 -->
<!-- n-columns=2 n-rows=6 -->
<object class="GtkGrid" id="main_container">
<property name="visible">True</property>
<property name="can-focus">False</property>
@ -27,7 +27,7 @@
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
@ -36,12 +36,11 @@
<property name="can-focus">False</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="text" translatable="yes">0%</property>
<property name="show-text">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
<property name="top-attach">2</property>
<property name="width">2</property>
</packing>
</child>
@ -55,7 +54,64 @@
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
<property name="top-attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="webhook_entry">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can-focus">True</property>
<property name="visibility">False</property>
<property name="invisible-char">*</property>
<property name="placeholder-text" translatable="yes">Webhook URL</property>
<property name="input-purpose">password</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="webhook_control">
<property name="label" translatable="yes">Edit</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">5</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">4</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">center</property>
<property name="margin-bottom">5</property>
<property name="label" translatable="yes">zdiu</property>
<attributes>
<attribute name="style" value="italic"/>
<attribute name="weight" value="thin"/>
<attribute name="size" value="32768"/>
</attributes>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
@ -112,22 +168,7 @@
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
<property name="width">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="settings">
<property name="label">gtk-preferences</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<property name="always-show-image">True</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">3</property>
<property name="top-attach">1</property>
<property name="width">2</property>
</packing>
</child>