Agrego la factory basada en sockets unix y un bloqueo de archivos.
parent
0c216e68f6
commit
bf285963f3
|
@ -775,6 +775,8 @@ static gchar **terminal_app_profile_list_from_dir (TerminalApp *app) {
|
|||
|
||||
g_ptr_array_add (builder_str, NULL);
|
||||
profile_list = (gchar **) g_ptr_array_free (builder_str, FALSE);
|
||||
|
||||
g_dir_close (dir);
|
||||
}
|
||||
|
||||
//g_ptr_array_unref (builder_str);
|
||||
|
|
452
src/terminal.c
452
src/terminal.c
|
@ -26,6 +26,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <glib/gstdio.h>
|
||||
|
@ -34,6 +35,11 @@
|
|||
#include <gdk/gdk.h>
|
||||
#include <gdk/gdkx.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <semaphore.h>
|
||||
|
||||
#include "terminal-accels.h"
|
||||
#include "terminal-app.h"
|
||||
#include "terminal-debug.h"
|
||||
|
@ -41,9 +47,12 @@
|
|||
#include "terminal-options.h"
|
||||
#include "terminal-util.h"
|
||||
|
||||
#define TERMINAL_FACTORY_SERVICE_NAME_PREFIX "org.mate.Terminal.Display"
|
||||
#define TERMINAL_FACTORY_SERVICE_PATH "/org/mate/Terminal/Factory"
|
||||
#define TERMINAL_FACTORY_INTERFACE_NAME "org.mate.Terminal.Factory"
|
||||
#define TERMINAL_FACTORY_UNIX_FOLDER "gatuno-terminal"
|
||||
#define TERMINAL_FACTORY_SERVICE_NAME_PREFIX "factory"
|
||||
|
||||
int factory_fd_lock = -1;
|
||||
int factory_unix_fd = -1;
|
||||
guint factory_unix_watch = 0;
|
||||
|
||||
static char *
|
||||
ay_to_string (GVariant *variant,
|
||||
|
@ -130,181 +139,101 @@ typedef struct
|
|||
int argc;
|
||||
} OwnData;
|
||||
|
||||
static void
|
||||
method_call_cb (GDBusConnection *connection,
|
||||
const char *sender,
|
||||
const char *object_path,
|
||||
const char *interface_name,
|
||||
const char *method_name,
|
||||
GVariant *parameters,
|
||||
GDBusMethodInvocation *invocation,
|
||||
gpointer user_data)
|
||||
{
|
||||
if (g_strcmp0 (method_name, "HandleArguments") == 0)
|
||||
{
|
||||
TerminalOptions *options = NULL;
|
||||
GVariant *v_wd, *v_display, *v_sid, *v_envv, *v_argv;
|
||||
char *working_directory = NULL, *display_name = NULL, *startup_id = NULL;
|
||||
int initial_workspace = -1;
|
||||
char **envv = NULL, **argv = NULL;
|
||||
int argc;
|
||||
GError *error = NULL;
|
||||
gboolean unix_socket_dgram_recv (GIOChannel *source, GIOCondition condition, gpointer data) {
|
||||
char *message = NULL, byte;
|
||||
int message_len;
|
||||
int s;
|
||||
|
||||
g_variant_get (parameters, "(@ay@ay@ay@ayi@ay)",
|
||||
&v_wd, &v_display, &v_sid, &v_envv, &initial_workspace, &v_argv);
|
||||
TerminalOptions *options = NULL;
|
||||
GVariant *v_wd, *v_display, *v_sid, *v_envv, *v_argv;
|
||||
char *working_directory = NULL, *display_name = NULL, *startup_id = NULL;
|
||||
int initial_workspace = -1;
|
||||
char **envv = NULL, **argv = NULL;
|
||||
int argc;
|
||||
GError *error = NULL;
|
||||
GVariant *parameters;
|
||||
|
||||
working_directory = ay_to_string (v_wd, &error);
|
||||
if (error)
|
||||
goto out;
|
||||
display_name = ay_to_string (v_display, &error);
|
||||
if (error)
|
||||
goto out;
|
||||
startup_id = ay_to_string (v_sid, &error);
|
||||
if (error)
|
||||
goto out;
|
||||
envv = ay_to_strv (v_envv, NULL);
|
||||
argv = ay_to_strv (v_argv, &argc);
|
||||
s = g_io_channel_unix_get_fd (source);
|
||||
|
||||
_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
|
||||
"Factory invoked with working-dir='%s' display='%s' startup-id='%s'"
|
||||
"workspace='%d'\n",
|
||||
working_directory ? working_directory : "(null)",
|
||||
display_name ? display_name : "(null)",
|
||||
startup_id ? startup_id : "(null)",
|
||||
initial_workspace);
|
||||
message_len = recvfrom (s, &byte, 1, MSG_TRUNC | MSG_PEEK, NULL, NULL);
|
||||
|
||||
options = terminal_options_parse (working_directory,
|
||||
display_name,
|
||||
startup_id,
|
||||
envv,
|
||||
TRUE,
|
||||
TRUE,
|
||||
&argc, &argv,
|
||||
&error,
|
||||
NULL);
|
||||
message = g_malloc (message_len);
|
||||
|
||||
if (options != NULL)
|
||||
{
|
||||
options->initial_workspace = initial_workspace;
|
||||
if (message == NULL) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
terminal_app_handle_options (terminal_app_get (), options, FALSE /* no resume */, &error);
|
||||
terminal_options_free (options);
|
||||
}
|
||||
message_len = recvfrom (s, message, message_len, 0, NULL, NULL);
|
||||
if (message_len < 0) {
|
||||
g_free (message);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
parameters = g_variant_new_from_data (G_VARIANT_TYPE ("(ayayayayiay)"), message, message_len, FALSE, NULL, NULL);
|
||||
|
||||
g_variant_get (parameters, "(@ay@ay@ay@ayi@ay)",
|
||||
&v_wd, &v_display, &v_sid, &v_envv, &initial_workspace, &v_argv);
|
||||
|
||||
working_directory = ay_to_string (v_wd, &error);
|
||||
if (error)
|
||||
goto out;
|
||||
display_name = ay_to_string (v_display, &error);
|
||||
if (error)
|
||||
goto out;
|
||||
startup_id = ay_to_string (v_sid, &error);
|
||||
if (error)
|
||||
goto out;
|
||||
envv = ay_to_strv (v_envv, NULL);
|
||||
argv = ay_to_strv (v_argv, &argc);
|
||||
|
||||
_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
|
||||
"Factory invoked with working-dir='%s' display='%s' startup-id='%s'"
|
||||
"workspace='%d'\n",
|
||||
working_directory ? working_directory : "(null)",
|
||||
display_name ? display_name : "(null)",
|
||||
startup_id ? startup_id : "(null)",
|
||||
initial_workspace);
|
||||
|
||||
options = terminal_options_parse (working_directory,
|
||||
display_name,
|
||||
startup_id,
|
||||
envv,
|
||||
TRUE,
|
||||
TRUE,
|
||||
&argc, &argv,
|
||||
&error,
|
||||
NULL);
|
||||
|
||||
if (options != NULL) {
|
||||
options->initial_workspace = initial_workspace;
|
||||
|
||||
terminal_app_handle_options (terminal_app_get (), options, FALSE /* no resume */, &error);
|
||||
terminal_options_free (options);
|
||||
}
|
||||
out:
|
||||
g_variant_unref (v_wd);
|
||||
g_free (working_directory);
|
||||
g_variant_unref (v_display);
|
||||
g_free (display_name);
|
||||
g_variant_unref (v_sid);
|
||||
g_free (startup_id);
|
||||
g_variant_unref (v_envv);
|
||||
g_strfreev (envv);
|
||||
g_variant_unref (v_argv);
|
||||
g_strfreev (argv);
|
||||
g_variant_unref (v_wd);
|
||||
g_free (working_directory);
|
||||
g_variant_unref (v_display);
|
||||
g_free (display_name);
|
||||
g_variant_unref (v_sid);
|
||||
g_free (startup_id);
|
||||
g_variant_unref (v_envv);
|
||||
g_strfreev (envv);
|
||||
g_variant_unref (v_argv);
|
||||
g_strfreev (argv);
|
||||
|
||||
if (error == NULL)
|
||||
{
|
||||
g_dbus_method_invocation_return_value (invocation, g_variant_new ("()"));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_dbus_method_invocation_return_gerror (invocation, error);
|
||||
g_error_free (error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
bus_acquired_cb (GDBusConnection *connection,
|
||||
const char *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
static const char dbus_introspection_xml[] =
|
||||
"<node name='/org/mate/Terminal'>"
|
||||
"<interface name='org.mate.Terminal.Factory'>"
|
||||
"<method name='HandleArguments'>"
|
||||
"<arg type='ay' name='working_directory' direction='in' />"
|
||||
"<arg type='ay' name='display_name' direction='in' />"
|
||||
"<arg type='ay' name='startup_id' direction='in' />"
|
||||
"<arg type='ay' name='environment' direction='in' />"
|
||||
"<arg type='i' name='workspace' direction='in' />"
|
||||
"<arg type='ay' name='arguments' direction='in' />"
|
||||
"</method>"
|
||||
"</interface>"
|
||||
"</node>";
|
||||
|
||||
static const GDBusInterfaceVTable interface_vtable =
|
||||
{
|
||||
method_call_cb,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
OwnData *data = (OwnData *) user_data;
|
||||
GDBusNodeInfo *introspection_data;
|
||||
guint registration_id;
|
||||
GError *error = NULL;
|
||||
|
||||
_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
|
||||
"Bus %s acquired\n", name);
|
||||
|
||||
introspection_data = g_dbus_node_info_new_for_xml (dbus_introspection_xml, NULL);
|
||||
g_assert (introspection_data != NULL);
|
||||
|
||||
registration_id = g_dbus_connection_register_object (connection,
|
||||
TERMINAL_FACTORY_SERVICE_PATH,
|
||||
introspection_data->interfaces[0],
|
||||
&interface_vtable,
|
||||
NULL, NULL,
|
||||
&error);
|
||||
g_dbus_node_info_unref (introspection_data);
|
||||
|
||||
if (registration_id == 0)
|
||||
{
|
||||
g_printerr ("Failed to register object: %s\n", error->message);
|
||||
if (error != NULL) {
|
||||
g_error_free (error);
|
||||
data->exit_code = EXIT_FAILURE;
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
g_free (message);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
name_acquired_cb (GDBusConnection *connection,
|
||||
const char *name,
|
||||
gpointer user_data)
|
||||
|
||||
static void send_ask_open (const char *name, OwnData *data)
|
||||
{
|
||||
OwnData *data = (OwnData *) user_data;
|
||||
GError *error = NULL;
|
||||
|
||||
_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
|
||||
"Acquired the name %s on the session bus\n", name);
|
||||
|
||||
if (data->options == NULL)
|
||||
{
|
||||
/* Name re-acquired!? */
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
|
||||
if (!terminal_app_handle_options (terminal_app_get (), data->options, TRUE /* do resume */, &error))
|
||||
{
|
||||
g_printerr ("Failed to handle options: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
data->exit_code = EXIT_FAILURE;
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
terminal_options_free (data->options);
|
||||
data->options = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
name_lost_cb (GDBusConnection *connection,
|
||||
const char *name,
|
||||
gpointer user_data)
|
||||
{
|
||||
OwnData *data = (OwnData *) user_data;
|
||||
GError *error = NULL;
|
||||
char **envv;
|
||||
int i;
|
||||
|
@ -313,23 +242,22 @@ name_lost_cb (GDBusConnection *connection,
|
|||
GString *string;
|
||||
char *s;
|
||||
gsize len;
|
||||
struct sockaddr_un unix_dest;
|
||||
int fd_u, ret;
|
||||
|
||||
_terminal_debug_print (TERMINAL_DEBUG_FACTORY,
|
||||
"Lost the name %s on the session bus\n", name);
|
||||
|
||||
/* Couldn't get the connection? No way to continue! */
|
||||
if (connection == NULL)
|
||||
{
|
||||
data->exit_code = EXIT_FAILURE;
|
||||
gtk_main_quit ();
|
||||
return;
|
||||
}
|
||||
"Sending message to %s on the unix socket\n", name);
|
||||
|
||||
if (data->options == NULL)
|
||||
{
|
||||
/* Already handled */
|
||||
data->exit_code = EXIT_SUCCESS;
|
||||
gtk_main_quit ();
|
||||
return;
|
||||
}
|
||||
fd_u = socket (AF_UNIX, SOCK_DGRAM, 0);
|
||||
|
||||
if (fd_u < 0) {
|
||||
data->exit_code = EXIT_FAILURE;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -374,7 +302,21 @@ name_lost_cb (GDBusConnection *connection,
|
|||
g_variant_builder_add (&builder, "@ay",
|
||||
g_variant_new_from_data (G_VARIANT_TYPE ("ay"), s, len, TRUE, g_free, s));
|
||||
|
||||
value = g_dbus_connection_call_sync (connection,
|
||||
GVariant *todo;
|
||||
todo = g_variant_builder_end (&builder);
|
||||
|
||||
unix_dest.sun_family = AF_UNIX;
|
||||
g_snprintf (unix_dest.sun_path, sizeof (unix_dest.sun_path), "%s%s%s%s%s", g_get_user_runtime_dir (), G_DIR_SEPARATOR_S, TERMINAL_FACTORY_UNIX_FOLDER, G_DIR_SEPARATOR_S, name);
|
||||
|
||||
g_free (subcadena);
|
||||
ret = sendto (fd_u, g_variant_get_data (todo), g_variant_get_size (todo), 0, (struct sockaddr *) &unix_dest, sizeof (unix_dest));
|
||||
if (ret < 0) {
|
||||
g_printerr ("Failed to forward arguments: \n");
|
||||
data->exit_code = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
close (fd_u);
|
||||
/*value = g_dbus_connection_call_sync (connection,
|
||||
data->factory_name,
|
||||
TERMINAL_FACTORY_SERVICE_PATH,
|
||||
TERMINAL_FACTORY_INTERFACE_NAME,
|
||||
|
@ -384,29 +326,17 @@ name_lost_cb (GDBusConnection *connection,
|
|||
G_DBUS_CALL_FLAGS_NONE,
|
||||
-1,
|
||||
NULL,
|
||||
&error);
|
||||
if (value == NULL)
|
||||
{
|
||||
g_printerr ("Failed to forward arguments: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
data->exit_code = EXIT_FAILURE;
|
||||
gtk_main_quit ();
|
||||
}
|
||||
else
|
||||
{
|
||||
g_variant_unref (value);
|
||||
data->exit_code = EXIT_SUCCESS;
|
||||
}
|
||||
&error);*/
|
||||
g_variant_unref (todo);
|
||||
data->exit_code = EXIT_SUCCESS;
|
||||
|
||||
terminal_options_free (data->options);
|
||||
data->options = NULL;
|
||||
|
||||
gtk_main_quit ();
|
||||
}
|
||||
|
||||
/* Settings storage works as follows:
|
||||
* /apps/mate-terminal/global/
|
||||
* /apps/mate-terminal/profiles/Foo/
|
||||
* ~/.config/gatuno-terminal/config
|
||||
* ~/.config/gatuno-terminal/profiles/Foo/
|
||||
*
|
||||
* It's somewhat tricky to manage the profiles/ dir since we need to track
|
||||
* the list of profiles, but GSettings doesn't have a concept of notifying that
|
||||
|
@ -482,8 +412,11 @@ get_factory_name_for_display (const char *display_name)
|
|||
{
|
||||
GString *name;
|
||||
const char *p;
|
||||
int len;
|
||||
|
||||
name = g_string_sized_new (strlen (TERMINAL_FACTORY_SERVICE_NAME_PREFIX) + strlen (display_name) + 1 /* NUL */);
|
||||
len = strlen (TERMINAL_FACTORY_SERVICE_NAME_PREFIX) + strlen (display_name) + 10 /* NUL */;
|
||||
|
||||
name = g_string_sized_new (len);
|
||||
g_string_append (name, TERMINAL_FACTORY_SERVICE_NAME_PREFIX);
|
||||
|
||||
for (p = display_name; *p; ++p)
|
||||
|
@ -500,6 +433,83 @@ get_factory_name_for_display (const char *display_name)
|
|||
return g_string_free (name, FALSE);
|
||||
}
|
||||
|
||||
void create_factory_unix_socket_folder (void) {
|
||||
char path[8192];
|
||||
|
||||
g_snprintf (path, sizeof (path), "%s%s%s", g_get_user_runtime_dir (), G_DIR_SEPARATOR_S, TERMINAL_FACTORY_UNIX_FOLDER);
|
||||
|
||||
/* Crear el directorio de los sockets unix */
|
||||
g_mkdir_with_parents (path, 0700);
|
||||
}
|
||||
|
||||
int get_lock_factory (char *factory_name) {
|
||||
char path[8192];
|
||||
struct flock fl;
|
||||
int fd;
|
||||
|
||||
g_snprintf (path, sizeof (path), "%s%s%s%s%s.lock", g_get_user_runtime_dir (), G_DIR_SEPARATOR_S, TERMINAL_FACTORY_UNIX_FOLDER, G_DIR_SEPARATOR_S, factory_name);
|
||||
|
||||
fd = open (path, O_RDWR | O_CREAT, 0600);
|
||||
|
||||
fl.l_start = 0;
|
||||
fl.l_len = 0;
|
||||
fl.l_type = F_WRLCK;
|
||||
fl.l_whence = SEEK_SET;
|
||||
if (fcntl(fd, F_SETLK, &fl) < 0) {
|
||||
//fputs("Another instance of this program is running.\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
factory_fd_lock = fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void release_lock_factory (void) {
|
||||
if (factory_fd_lock != -1) {
|
||||
close (factory_fd_lock);
|
||||
factory_fd_lock = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int do_bind_factory_unix_socket (char *factory_name) {
|
||||
int s, ret;
|
||||
struct sockaddr_un name;
|
||||
|
||||
s = socket (AF_UNIX, SOCK_DGRAM, 0);
|
||||
|
||||
if (s < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
name.sun_family = AF_UNIX;
|
||||
g_snprintf (name.sun_path, sizeof (name.sun_path), "%s%s%s%s%s", g_get_user_runtime_dir (), G_DIR_SEPARATOR_S, TERMINAL_FACTORY_UNIX_FOLDER, G_DIR_SEPARATOR_S, factory_name);
|
||||
|
||||
unlink (name.sun_path);
|
||||
|
||||
ret = bind (s, (struct sockaddr *) &name, sizeof (name));
|
||||
if (ret < 0) {
|
||||
close (s);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void release_factory_unix_socket (char *factory_name) {
|
||||
struct sockaddr_un name;
|
||||
if (factory_unix_fd != -1) {
|
||||
g_source_remove (factory_unix_watch);
|
||||
factory_unix_watch = 0;
|
||||
|
||||
close (factory_unix_fd);
|
||||
factory_unix_fd = -1;
|
||||
|
||||
g_snprintf (name.sun_path, sizeof (name.sun_path), "%s%s%s%s%s", g_get_user_runtime_dir (), G_DIR_SEPARATOR_S, TERMINAL_FACTORY_UNIX_FOLDER, G_DIR_SEPARATOR_S, factory_name);
|
||||
unlink (name.sun_path);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
get_initial_workspace (void)
|
||||
{
|
||||
|
@ -611,29 +621,65 @@ main (int argc, char **argv)
|
|||
{
|
||||
OwnData *data;
|
||||
guint owner_id;
|
||||
|
||||
GError * error = NULL;
|
||||
data = g_new (OwnData, 1);
|
||||
data->factory_name = get_factory_name_for_display (display_name);
|
||||
data->options = options;
|
||||
data->exit_code = -1;
|
||||
data->argv = argv_copy;
|
||||
data->argc = argc_copy;
|
||||
GIOChannel *io_channel;
|
||||
|
||||
gtk_init(&argc, &argv);
|
||||
options->initial_workspace = get_initial_workspace ();
|
||||
|
||||
owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
||||
data->factory_name,
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
bus_acquired_cb,
|
||||
name_acquired_cb,
|
||||
name_lost_cb,
|
||||
data, NULL);
|
||||
/* El proceso del factory funciona así:
|
||||
* Si me pidieron usar el factory, intentar conseguir el candado.
|
||||
* Si obtenemos el candado, poner el socket a la escucha.
|
||||
* Si obtenemos el socket a la escucha, somos el factory. Lanzar terminal
|
||||
* Si no consigo el socket a la escucha, error. Lanzar terminal como si no hubiera factory.
|
||||
* Si no obtengo el candado, enviar mensaje a la otra terminal.
|
||||
*/
|
||||
create_factory_unix_socket_folder ();
|
||||
|
||||
gtk_main ();
|
||||
if (get_lock_factory (data->factory_name) == 0) {
|
||||
factory_unix_fd = do_bind_factory_unix_socket (data->factory_name);
|
||||
|
||||
if (factory_unix_fd >= 0) {
|
||||
/* Soy la factory */
|
||||
_terminal_debug_print (TERMINAL_DEBUG_FACTORY, "Acquired the name %s on the unix socket\n", data->factory_name);
|
||||
/* Instalar un vigilador del socket unix */
|
||||
io_channel = g_io_channel_unix_new (factory_unix_fd);
|
||||
|
||||
factory_unix_watch = g_io_add_watch (io_channel, G_IO_IN, unix_socket_dgram_recv, NULL);
|
||||
} else {
|
||||
/* ¿Tengo lock pero no pude conseguir el socket unix? Raro, soltar el lock, puesto que no puedo recibir las peticiones del factory */
|
||||
release_lock_factory ();
|
||||
|
||||
/* Correr la terminal de a solas */
|
||||
}
|
||||
|
||||
/* Correr la terminal */
|
||||
if (!terminal_app_handle_options (terminal_app_get (), data->options, TRUE /* do resume */, &error))
|
||||
{
|
||||
g_printerr ("Failed to handle options: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
data->exit_code = EXIT_FAILURE;
|
||||
} else {
|
||||
terminal_options_free (data->options);
|
||||
data->options = NULL;
|
||||
|
||||
gtk_main ();
|
||||
}
|
||||
|
||||
release_factory_unix_socket (data->factory_name);
|
||||
release_lock_factory ();
|
||||
} else {
|
||||
/* Ya hay otra factory corriendo, enviar el mensaje */
|
||||
send_ask_open (data->factory_name, data);
|
||||
}
|
||||
|
||||
ret = data->exit_code;
|
||||
g_bus_unown_name (owner_id);
|
||||
|
||||
g_free (data->factory_name);
|
||||
g_free (data);
|
||||
|
|
Loading…
Reference in New Issue