summaryrefslogtreecommitdiffstats
path: root/sw/programmer/linux
diff options
context:
space:
mode:
Diffstat (limited to 'sw/programmer/linux')
-rw-r--r--sw/programmer/linux/config.h26
-rw-r--r--sw/programmer/linux/configure.ac23
-rw-r--r--sw/programmer/linux/makefile.am2
-rw-r--r--sw/programmer/linux/reading_links.txt4
-rw-r--r--sw/programmer/linux/src/flash.c59
-rw-r--r--sw/programmer/linux/src/flash.h22
-rw-r--r--sw/programmer/linux/src/main.c13
-rw-r--r--sw/programmer/linux/src/makefile.am7
-rw-r--r--sw/programmer/linux/src/serial.c43
-rw-r--r--sw/programmer/linux/src/serial.h13
-rw-r--r--sw/programmer/linux/src/ui.c142
-rw-r--r--sw/programmer/linux/src/ui.h24
-rwxr-xr-xsw/programmer/linux/src/z80progbin0 -> 48160 bytes
-rw-r--r--sw/programmer/linux/src/z80prog.ui223
14 files changed, 601 insertions, 0 deletions
diff --git a/sw/programmer/linux/config.h b/sw/programmer/linux/config.h
new file mode 100644
index 0000000..d319f43
--- /dev/null
+++ b/sw/programmer/linux/config.h
@@ -0,0 +1,26 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Name of package */
+#define PACKAGE "z80prog"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "naopross@tharcway.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "z80prog"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "z80prog 0.1"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "z80prog"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "0.1"
+
+/* Version number of package */
+#define VERSION "0.1"
diff --git a/sw/programmer/linux/configure.ac b/sw/programmer/linux/configure.ac
new file mode 100644
index 0000000..a5c4b3b
--- /dev/null
+++ b/sw/programmer/linux/configure.ac
@@ -0,0 +1,23 @@
+AC_CONFIG_SRCDIR([src])
+AC_CONFIG_HEADERS([config.h])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_AUX_DIR([build-aux])
+
+AC_INIT([z80prog], [0.1], [naopross@tharcway.org])
+AM_INIT_AUTOMAKE([-Wall -Werror foreign])
+
+AC_PROG_CC
+
+####
+# Check for required packages / libraries
+#
+LIBGTK_REQUIRED=2.91
+
+PKG_CHECK_MODULES(gtk3, [gtk+-3.0 >= $LIBGTK_REQUIRED])
+
+AC_CONFIG_FILES([
+ makefile
+ src/makefile
+])
+
+AC_OUTPUT
diff --git a/sw/programmer/linux/makefile.am b/sw/programmer/linux/makefile.am
new file mode 100644
index 0000000..4d27cea
--- /dev/null
+++ b/sw/programmer/linux/makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = src
+CLEANFILES = *~
diff --git a/sw/programmer/linux/reading_links.txt b/sw/programmer/linux/reading_links.txt
new file mode 100644
index 0000000..83c62f4
--- /dev/null
+++ b/sw/programmer/linux/reading_links.txt
@@ -0,0 +1,4 @@
+https://developer.gnome.org/gtk3/stable/ch01s04.html#id-1.2.3.12.5
+https://developer.gnome.org/gio/unstable/GApplication.html
+https://git.gnome.org/browse/gnome-hello/tree/src/app.c
+
diff --git a/sw/programmer/linux/src/flash.c b/sw/programmer/linux/src/flash.c
new file mode 100644
index 0000000..48189da
--- /dev/null
+++ b/sw/programmer/linux/src/flash.c
@@ -0,0 +1,59 @@
+#include "flash.h"
+
+static int flash_serial_fd = -1;
+
+int flash_open(const char *path, unsigned long baudrate)
+{
+ flash_serial_fd = serial_open(path, baudrate);
+
+ if (flash_serial_fd < 0)
+ return -1;
+
+ return 0;
+}
+
+int flash_write(const char *romfile, void (*log)(const char *))
+{
+ int romfd;
+ ssize_t written;
+
+ struct stat romst;
+ struct flash_blk head;
+
+ uint8_t *buffer = malloc(FLASH_BLOCK_SIZE);
+
+ romfd = open(romfile, O_RDONLY);
+
+ if (fstat(romfd, &romst) != 0)
+ goto exit_romfd;
+
+ while ((head.size = read(romfd, buffer, FLASH_BLOCK_SIZE))) {
+ head.addr = (uint16_t) lseek(romfd, 0, SEEK_CUR);
+
+ written = write(flash_serial_fd, &head, sizeof(struct flash_blk));
+ if (written < 0) {
+ // error
+ }
+
+ written = write(flash_serial_fd, buffer, head.size);
+ if (written < 0) {
+ // error
+ }
+
+ char logbuf[64];
+ sprintf(logbuf, "[@] Written %d bytes at address %d\n", head.size, head.addr);
+ log(logbuf);
+ }
+
+exit_romfd:
+ close(romfd);
+
+ free(buffer);
+ return 0;
+}
+
+void flash_close(void)
+{
+ if (flash_serial_fd >= 0)
+ close(flash_serial_fd);
+} \ No newline at end of file
diff --git a/sw/programmer/linux/src/flash.h b/sw/programmer/linux/src/flash.h
new file mode 100644
index 0000000..373ebf3
--- /dev/null
+++ b/sw/programmer/linux/src/flash.h
@@ -0,0 +1,22 @@
+#ifndef __FLASH_H__
+#define __FLASH_H__
+
+#include "serial.h"
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define FLASH_BLOCK_SIZE 512
+
+struct flash_blk
+{
+ uint16_t addr;
+ uint16_t size;
+};
+
+int flash_open(const char *devpath, unsigned long baudrate);
+int flash_write(const char *romfile, void (*log)(const char *));
+void flash_close(void);
+
+#endif
diff --git a/sw/programmer/linux/src/main.c b/sw/programmer/linux/src/main.c
new file mode 100644
index 0000000..769992a
--- /dev/null
+++ b/sw/programmer/linux/src/main.c
@@ -0,0 +1,13 @@
+#include "config.h"
+#include "ui.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+int main(int argc, char *argv[])
+{
+ ui_init(&argc, &argv);
+ return 0;
+}
+
diff --git a/sw/programmer/linux/src/makefile.am b/sw/programmer/linux/src/makefile.am
new file mode 100644
index 0000000..33ae438
--- /dev/null
+++ b/sw/programmer/linux/src/makefile.am
@@ -0,0 +1,7 @@
+bin_PROGRAMS = z80prog
+z80prog_SOURCES = main.c ui.c serial.c flash.c
+
+z80prog_CFLAGS = -Wall -g $(gtk3_CFLAGS) -pthread
+z80prog_LDADD = $(gtk3_LIBS)
+
+CLEANFILES = *~
diff --git a/sw/programmer/linux/src/serial.c b/sw/programmer/linux/src/serial.c
new file mode 100644
index 0000000..e2544be
--- /dev/null
+++ b/sw/programmer/linux/src/serial.c
@@ -0,0 +1,43 @@
+#include "serial.h"
+
+int serial_open(const char *devpath, unsigned long baudrate)
+{
+ int fd;
+ struct termios tty;
+ // struct termios tty_old;
+
+ // open device
+ if ((fd = open(devpath, O_RDWR | O_NOCTTY)) < 0) {
+ return -1;
+ }
+
+ // set parameters
+ if (tcgetattr(fd, &tty) != 0) {
+ return -1;
+ }
+
+ // TODO: update UI or add support for custom baudrate
+ // cfsetospeed(&tty,
+ // cfsetispeed(&tty,
+
+ tty.c_cflag &= ~PARENB; // no parity
+ tty.c_cflag &= ~CSTOPB; // no stop bit
+ tty.c_cflag |= CS8; // 8 bit data
+ tty.c_cflag &= ~CRTSCTS; // no flow control
+
+ tty.c_lflag = 0; // no signaling chars, no echo, no canonical processing
+ tty.c_oflag = 0; // no remapping, no delays
+ tty.c_cc[VMIN] = 0; // no block read
+ tty.c_cc[VTIME] = .5; // .5 seconds read timeout
+
+ tty.c_cflag |= CREAD | CLOCAL; // turn on read and ignore ctrl lines
+ tty.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
+ tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
+ tty.c_oflag &= ~OPOST; // make raw
+
+ tcflush(fd , TCIFLUSH);
+ // if (tcsetaddr(
+
+ return fd;
+}
+
diff --git a/sw/programmer/linux/src/serial.h b/sw/programmer/linux/src/serial.h
new file mode 100644
index 0000000..fe21524
--- /dev/null
+++ b/sw/programmer/linux/src/serial.h
@@ -0,0 +1,13 @@
+#ifndef __Z80PROG_SERIAL_H__
+#define __Z80PROG_SERIAL_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+
+int serial_open(const char *devpath, unsigned long baudrate);
+
+#endif
diff --git a/sw/programmer/linux/src/ui.c b/sw/programmer/linux/src/ui.c
new file mode 100644
index 0000000..87dc0c7
--- /dev/null
+++ b/sw/programmer/linux/src/ui.c
@@ -0,0 +1,142 @@
+#include "ui.h"
+
+static bool ui_connected, ui_fileset;
+static GtkBuilder *ui_builder;
+
+void ui_init(int *argc, char **argv[])
+{
+ GtkWindow *window;
+ GtkFileChooserButton *filechoosebtn;
+ GtkButton *connectbtn, *flashbtn;
+
+ ui_connected = ui_fileset = false;
+
+ gtk_init(argc, argv);
+
+ ui_builder = gtk_builder_new();
+ gtk_builder_add_from_file(ui_builder, "z80prog.ui", NULL);
+
+ /* connect objects to callbacks */
+ window = GTK_WINDOW(gtk_builder_get_object(ui_builder, "window"));
+ g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
+
+ connectbtn = GTK_BUTTON(gtk_builder_get_object(ui_builder, "connectbtn"));
+ g_signal_connect(connectbtn, "clicked", G_CALLBACK(ui_connect_clicked), NULL);
+
+ flashbtn = GTK_BUTTON(gtk_builder_get_object(ui_builder, "flashbtn"));
+ g_signal_connect(flashbtn, "clicked", G_CALLBACK(ui_flash_clicked), NULL);
+
+ filechoosebtn = GTK_FILE_CHOOSER_BUTTON(gtk_builder_get_object(ui_builder, "filechoosebtn"));
+ g_signal_connect(filechoosebtn, "file-set", G_CALLBACK(ui_file_set), NULL);
+
+ /* start gtk window */
+ gtk_main();
+}
+
+void ui_log(const char *msg)
+{
+ GtkTextIter end;
+ GtkTextView *ui_log;
+
+ static GtkTextBuffer *ui_logbuf = NULL;
+
+ ui_log = GTK_TEXT_VIEW(gtk_builder_get_object(ui_builder, "logview"));
+
+ if (ui_logbuf == NULL) {
+ ui_logbuf = gtk_text_buffer_new(NULL);
+ gtk_text_view_set_buffer(ui_log, ui_logbuf);
+ }
+
+ gtk_text_buffer_get_end_iter(ui_logbuf, &end);
+ gtk_text_buffer_insert(ui_logbuf, &end, msg, -1);
+ gtk_text_view_scroll_to_iter(ui_log, &end, .0, TRUE, .0, .1);
+}
+
+void ui_check_enable_flashbtn(void)
+{
+ GtkWidget *ui_flashbtn = GTK_WIDGET(gtk_builder_get_object(ui_builder, "flashbtn"));
+
+ if (ui_connected && ui_fileset)
+ gtk_widget_set_sensitive(ui_flashbtn, TRUE);
+ else
+ gtk_widget_set_sensitive(ui_flashbtn, FALSE);
+}
+
+void ui_file_set(GtkFileChooserButton *btn, gpointer user_data)
+{
+ GtkEntry *filepath = GTK_ENTRY(gtk_builder_get_object(ui_builder, "filepath"));
+
+ gtk_entry_set_text(filepath, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(btn)));
+ ui_log(UI_LOG_MSG "file set\n");
+ ui_fileset = true;
+
+ ui_check_enable_flashbtn();
+}
+
+void ui_connect_clicked(void)
+{
+ GtkEntry *ui_devpath = GTK_ENTRY(gtk_builder_get_object(ui_builder, "devpath"));
+ GtkSpinButton *ui_baudrate = GTK_SPIN_BUTTON(gtk_builder_get_object(ui_builder, "devbaudrate"));
+ GtkButton *ui_connectbtn = GTK_BUTTON(gtk_builder_get_object(ui_builder, "connectbtn"));
+
+ unsigned long baudrate = gtk_spin_button_get_value_as_int(ui_baudrate);
+ const char *devpath = gtk_entry_get_text(ui_devpath);
+
+ if (flash_open(devpath, baudrate) != 0) {
+ ui_log(UI_LOG_ERR "failed to open serial device\n");
+ } else {
+ ui_connected = true;
+ ui_log(UI_LOG_MSG "connected to serial device ");
+ ui_log(devpath);
+ ui_log("\n");
+
+ g_signal_handlers_disconnect_by_func(ui_connectbtn, G_CALLBACK(ui_connect_clicked), NULL);
+ g_signal_connect(ui_connectbtn, "clicked", G_CALLBACK(ui_disconnect_clicked), NULL);
+
+ gtk_button_set_label(ui_connectbtn, "gtk-disconnect");
+ ui_check_enable_flashbtn();
+ }
+}
+
+void ui_disconnect_clicked(void)
+{
+ GtkButton *ui_connectbtn = GTK_BUTTON(gtk_builder_get_object(ui_builder, "connectbtn"));
+
+ flash_close();
+
+ ui_connected = false;
+ ui_log(UI_LOG_MSG "disconnected\n");
+
+ g_signal_handlers_disconnect_by_func(ui_connectbtn, G_CALLBACK(ui_disconnect_clicked), NULL);
+ g_signal_connect(ui_connectbtn, "clicked", G_CALLBACK(ui_connect_clicked), NULL);
+
+ gtk_button_set_label(ui_connectbtn, "gtk-connect");
+ ui_check_enable_flashbtn();
+}
+
+int ui_flash_write_start(void *ptr)
+{
+ const char *filepath = (const char *) ptr;
+
+ flash_write(filepath, ui_log);
+ ui_check_enable_flashbtn();
+
+ return 0;
+}
+
+void ui_flash_clicked(void)
+{
+ GtkWidget *ui_flashbtn = GTK_WIDGET(gtk_builder_get_object(ui_builder, "flashbtn"));
+ GtkEntry *ui_filepath = GTK_ENTRY(gtk_builder_get_object(ui_builder, "filepath"));
+
+ const char *filepath = gtk_entry_get_text(ui_filepath);
+
+ gdk_threads_add_idle_full(
+ G_PRIORITY_HIGH_IDLE,
+ ui_flash_write_start,
+ (void *) filepath,
+ NULL
+ );
+
+ gtk_widget_set_sensitive(ui_flashbtn, FALSE);
+}
diff --git a/sw/programmer/linux/src/ui.h b/sw/programmer/linux/src/ui.h
new file mode 100644
index 0000000..510537a
--- /dev/null
+++ b/sw/programmer/linux/src/ui.h
@@ -0,0 +1,24 @@
+#ifndef __Z80PROG_UI_H__
+#define __Z80PROG_UI_H_
+
+#include "flash.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#define UI_LOG_ERR "[!] "
+#define UI_LOG_WARN "[#] "
+#define UI_LOG_MSG "[@] "
+
+void ui_init(int *argc, char **argv[]);
+void ui_log(const char *msg);
+void ui_check_enable_flashbtn(void);
+
+void ui_file_set(GtkFileChooserButton *btn, gpointer user_data);
+void ui_connect_clicked(void);
+void ui_disconnect_clicked(void);
+void ui_flash_clicked(void);
+
+#endif
diff --git a/sw/programmer/linux/src/z80prog b/sw/programmer/linux/src/z80prog
new file mode 100755
index 0000000..2f0d230
--- /dev/null
+++ b/sw/programmer/linux/src/z80prog
Binary files differ
diff --git a/sw/programmer/linux/src/z80prog.ui b/sw/programmer/linux/src/z80prog.ui
new file mode 100644
index 0000000..6c652ec
--- /dev/null
+++ b/sw/programmer/linux/src/z80prog.ui
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkAdjustment" id="baudrateadjust">
+ <property name="upper">1000000</property>
+ <property name="value">9600</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkFileFilter" id="gbbinfilter">
+ <patterns>
+ <pattern>*.bin</pattern>
+ <pattern>*.gb</pattern>
+ <pattern>*.gba</pattern>
+ <pattern>*.gbc</pattern>
+ <pattern>*.hex</pattern>
+ </patterns>
+ </object>
+ <object class="GtkWindow" id="window">
+ <property name="width_request">450</property>
+ <property name="height_request">250</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Z80 ROM Programmer</property>
+ <property name="default_width">450</property>
+ <property name="default_height">200</property>
+ <child>
+ <object class="GtkBox" id="mainbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_left">10</property>
+ <property name="margin_right">10</property>
+ <property name="margin_top">10</property>
+ <property name="margin_bottom">10</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkBox" id="devbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkEntry" id="devpath">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="text" translatable="yes">/dev/tty</property>
+ <property name="placeholder_text" translatable="yes">Enter device path</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="devbaudrate">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">0</property>
+ <property name="max_width_chars">7</property>
+ <property name="overwrite_mode">True</property>
+ <property name="placeholder_text" translatable="yes">1200</property>
+ <property name="input_purpose">number</property>
+ <property name="adjustment">baudrateadjust</property>
+ <property name="climb_rate">10</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">if-valid</property>
+ <property name="value">1200</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="connectbtn">
+ <property name="label">gtk-connect</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="filebox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkEntry" id="filepath">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="placeholder_text" translatable="yes">Enter binary path</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserButton" id="filechoosebtn">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="filter">gbbinfilter</property>
+ <property name="title" translatable="yes"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="flashbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkProgressBar" id="flashbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show_text">True</property>
+ <property name="ellipsize">start</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="flashbtn">
+ <property name="label" translatable="yes">Flash</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkViewport" id="logviewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkExpander" id="logexpander">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="expanded">True</property>
+ <property name="label_fill">True</property>
+ <property name="resize_toplevel">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="logscroller">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="logview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="editable">False</property>
+ <property name="monospace">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="loglabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Logs</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>