diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 17:06:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 17:06:32 +0000 |
commit | 2dad5357405ad33cfa792f04b3ab62a5d188841e (patch) | |
tree | b8f8893942060fe3cfb04ac374cda96fdfc8f453 /plugins/exec | |
parent | Initial commit. (diff) | |
download | remmina-2dad5357405ad33cfa792f04b3ab62a5d188841e.tar.xz remmina-2dad5357405ad33cfa792f04b3ab62a5d188841e.zip |
Adding upstream version 1.4.34+dfsg.upstream/1.4.34+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/exec')
-rw-r--r-- | plugins/exec/CMakeLists.txt | 55 | ||||
-rw-r--r-- | plugins/exec/exec_plugin.c | 340 | ||||
-rw-r--r-- | plugins/exec/exec_plugin_config.h | 64 | ||||
-rw-r--r-- | plugins/exec/scalable/emblems/org.remmina.Remmina-tool-symbolic.svg | 85 |
4 files changed, 544 insertions, 0 deletions
diff --git a/plugins/exec/CMakeLists.txt b/plugins/exec/CMakeLists.txt new file mode 100644 index 0000000..4906f3b --- /dev/null +++ b/plugins/exec/CMakeLists.txt @@ -0,0 +1,55 @@ +# remmina-plugin-tool_hello_world - The GTK+ Remote Desktop Client +# +# Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo +# Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the +# OpenSSL library under certain conditions as described in each +# individual source file, and distribute linked combinations +# including the two. +# You must obey the GNU General Public License in all respects +# for all of the code used other than OpenSSL. If you modify +# file(s) with this exception, you may extend this exception to your +# version of the file(s), but you are not obligated to do so. If you +# do not wish to do so, delete this exception statement from your +# version. If you delete this exception statement from all source +# files in the program, then also delete it here. + + +set(REMMINA_PLUGIN_EXEC_SRCS + exec_plugin_config.h + exec_plugin.c +) + +add_library(remmina-plugin-exec MODULE ${REMMINA_PLUGIN_EXEC_SRCS}) +set_target_properties(remmina-plugin-exec PROPERTIES PREFIX "") +set_target_properties(remmina-plugin-exec PROPERTIES NO_SONAME 1) + +include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${GTK_INCLUDE_DIRS}) +target_link_libraries(remmina-plugin-exec ${REMMINA_COMMON_LIBRARIES}) + +install(TARGETS remmina-plugin-exec DESTINATION ${REMMINA_PLUGINDIR}) + +install(FILES + scalable/emblems/org.remmina.Remmina-tool-symbolic.svg + DESTINATION ${APPICONSCALE_EMBLEMS_DIR}) + +if(WITH_ICON_CACHE) + gtk_update_icon_cache("${REMMINA_DATADIR}/icons/hicolor") +endif() diff --git a/plugins/exec/exec_plugin.c b/plugins/exec/exec_plugin.c new file mode 100644 index 0000000..f22b535 --- /dev/null +++ b/plugins/exec/exec_plugin.c @@ -0,0 +1,340 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2017-2023 Antenore Gatta, Giovanni Panozzo + * + * Initially based on the plugin "Remmina Plugin EXEC", created and written by + * Fabio Castelli (Muflone) <muflone@vbsimple.net>. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#include "exec_plugin_config.h" + +#include "common/remmina_plugin.h" + +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <glib.h> +#include <stdlib.h> +#include <sys/wait.h> +#include <unistd.h> + +#define GET_PLUGIN_DATA(gp) (RemminaPluginExecData*)g_object_get_data(G_OBJECT(gp), "plugin-data") + +typedef struct _RemminaPluginExecData { + GtkWidget *log_view; + GtkTextBuffer *log_buffer; + GtkTextBuffer *err; + GtkWidget *sw; + GPid pid; +} RemminaPluginExecData; + +static RemminaPluginService *remmina_plugin_service = NULL; +#define REMMINA_PLUGIN_DEBUG(fmt, ...) remmina_plugin_service->_remmina_debug(__func__, fmt, ##__VA_ARGS__) + + + static void +cb_child_watch( GPid pid, gint status) +{ + /* Close pid */ + g_spawn_close_pid( pid ); +} + +static void cb_child_setup(gpointer data){ + int pid = getpid(); + setpgid(pid, 0); +} + + static gboolean +cb_out_watch (GIOChannel *channel, GIOCondition cond, RemminaProtocolWidget *gp) +{ + gchar *string; + gsize size; + + RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp); + + if( cond == G_IO_HUP ) + { + g_io_channel_unref( channel ); + return FALSE; + } + + g_io_channel_read_line( channel, &string, &size, NULL, NULL ); + gtk_text_buffer_insert_at_cursor( gpdata->log_buffer, string, -1 ); + g_free( string ); + + return TRUE; +} + + static gboolean +cb_err_watch (GIOChannel *channel, GIOCondition cond, RemminaProtocolWidget *gp) +{ + gchar *string; + gsize size; + + RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp); + + if( cond == G_IO_HUP ) + { + g_io_channel_unref( channel ); + return FALSE; + } + + g_io_channel_read_line( channel, &string, &size, NULL, NULL ); + gtk_text_buffer_insert_at_cursor( gpdata->err, string, -1 ); + g_free( string ); + + return TRUE; +} + +static void remmina_plugin_exec_init(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + RemminaPluginExecData *gpdata; + + REMMINA_PLUGIN_DEBUG("[%s] Plugin init", PLUGIN_NAME); + + gpdata = g_new0(RemminaPluginExecData, 1); + g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free); + + gpdata->pid = 0; + gpdata->log_view = gtk_text_view_new(); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gpdata->log_view), GTK_WRAP_CHAR); + gtk_text_view_set_editable(GTK_TEXT_VIEW(gpdata->log_view), FALSE); + gtk_text_view_set_left_margin (GTK_TEXT_VIEW (gpdata->log_view), 20); + gtk_text_view_set_right_margin (GTK_TEXT_VIEW (gpdata->log_view), 20); + gpdata->log_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (gpdata->log_view)); + gpdata->sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_set_size_request (gpdata->sw, 640, 480); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (gpdata->sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_container_add(GTK_CONTAINER(gp), gpdata->sw); + gtk_container_add(GTK_CONTAINER(gpdata->sw), gpdata->log_view); + gtk_text_buffer_set_text (gpdata->log_buffer, "Remmina Exec Plugin Logger", -1); + + gtk_widget_show_all(gpdata->sw); +} + +static gboolean remmina_plugin_exec_run(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + RemminaFile* remminafile; + const gchar *cmd; + gchar *stdout_buffer; + gchar *stderr_buffer; + char **argv; + GError *error = NULL; + GPid child_pid; + gint child_stdout, child_stderr; + GtkDialog *dialog; + GIOChannel *out_ch, *err_ch; + + REMMINA_PLUGIN_DEBUG("[%s] Plugin run", PLUGIN_NAME); + RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp); + remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + + cmd = remmina_plugin_service->file_get_string(remminafile, "execcommand"); + if (!cmd) { + gtk_text_buffer_set_text (gpdata->log_buffer, + _("You did not set any command to be executed"), -1); + remmina_plugin_service->protocol_plugin_signal_connection_opened(gp); + return TRUE; + } + + g_shell_parse_argv(cmd, NULL, &argv, &error); + if (error) { + gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1); + remmina_plugin_service->protocol_plugin_signal_connection_opened(gp); + g_error_free(error); + return TRUE; + } + + if (remmina_plugin_service->file_get_int(remminafile, "runasync", FALSE)) { + REMMINA_PLUGIN_DEBUG("[%s] Run Async", PLUGIN_NAME); + g_spawn_async_with_pipes( NULL, + argv, + NULL, + G_SPAWN_DO_NOT_REAP_CHILD | + G_SPAWN_SEARCH_PATH_FROM_ENVP | + G_SPAWN_SEARCH_PATH, + cb_child_setup, + NULL, + &child_pid, + NULL, + &child_stdout, + &child_stderr, + &error); + if (error != NULL) { + gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1); + g_error_free(error); + remmina_plugin_service->protocol_plugin_signal_connection_opened(gp); + return TRUE; + } + g_child_watch_add(child_pid, (GChildWatchFunc)cb_child_watch, gp ); + + gpdata->pid = child_pid; + /* Create channels that will be used to read data from pipes. */ + out_ch = g_io_channel_unix_new(child_stdout); + err_ch = g_io_channel_unix_new(child_stderr); + /* Add watches to channels */ + g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP, (GIOFunc)cb_out_watch, gp ); + g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP, (GIOFunc)cb_err_watch, gp ); + + }else { + dialog = GTK_DIALOG(gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, + _("Warning: Running a command synchronously may cause Remmina not to respond.\nDo you really want to continue?"))); + gint result = gtk_dialog_run (GTK_DIALOG (dialog)); + + switch (result) + { + case GTK_RESPONSE_YES: + break; + default: + gtk_widget_destroy(GTK_WIDGET(dialog)); + return FALSE; + break; + } + gtk_widget_destroy(GTK_WIDGET(dialog)); + REMMINA_PLUGIN_DEBUG("[%s] Run Sync", PLUGIN_NAME); + g_spawn_sync (NULL, // CWD or NULL + argv, + NULL, // ENVP or NULL + G_SPAWN_SEARCH_PATH | + G_SPAWN_SEARCH_PATH_FROM_ENVP, + NULL, + NULL, + &stdout_buffer, // STDOUT + &stderr_buffer, // STDERR + NULL, // Exit status + &error); + if (!error) { + REMMINA_PLUGIN_DEBUG("[%s] Command executed", PLUGIN_NAME); + gtk_text_buffer_set_text (gpdata->log_buffer, stdout_buffer, -1); + }else { + g_warning("Command %s exited with error: %s\n", cmd, error->message); + gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1); + g_error_free(error); + } + } + + g_strfreev(argv); + + remmina_plugin_service->protocol_plugin_signal_connection_opened(gp); + return TRUE; +} + +static gboolean remmina_plugin_exec_close(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + REMMINA_PLUGIN_DEBUG("[%s] Plugin close", PLUGIN_NAME); + RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp); + //if async process was started, make sure it's dead if option is selected + RemminaFile* remminafile = remmina_plugin_service->protocol_plugin_get_file(gp); + if (remmina_plugin_service->file_get_int(remminafile, "kill_proc", FALSE)) { + if (gpdata->pid !=0 ){ + int pgid = getpgid(gpdata->pid); + if (pgid != 0){ + kill(-gpdata->pid, SIGHUP); + } + else{ + kill(gpdata->pid, SIGHUP); + } + + } + } + remmina_plugin_service->protocol_plugin_signal_connection_closed(gp); + return FALSE; +} + +/* Array of RemminaProtocolSetting for basic settings. + * Each item is composed by: + * a) RemminaProtocolSettingType for setting type + * b) Setting name + * c) Setting description + * d) Compact disposition + * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO + * f) Setting tooltip + * g) Validation data pointer, will be passed to the validation callback method. + * h) Validation callback method (Can be NULL. Every entry will be valid then.) + * use following prototype: + * gboolean mysetting_validator_method(gpointer key, gpointer value, + * gpointer validator_data); + * gpointer key is a gchar* containing the setting's name, + * gpointer value contains the value which should be validated, + * gpointer validator_data contains your passed data. + */ +static const RemminaProtocolSetting remmina_plugin_exec_basic_settings[] = +{ + { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "execcommand", N_("Command"), FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "runasync", N_("Asynchronous execution"), FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "kill_proc", N_("Kill process on disconnect"), FALSE, NULL, NULL, NULL, NULL }, + { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL, NULL, NULL } +}; + +/* Protocol plugin definition and features */ +static RemminaProtocolPlugin remmina_plugin = { + REMMINA_PLUGIN_TYPE_PROTOCOL, // Type + PLUGIN_NAME, // Name + PLUGIN_DESCRIPTION, // Description + GETTEXT_PACKAGE, // Translation domain + PLUGIN_VERSION, // Version number + PLUGIN_APPICON, // Icon for normal connection + PLUGIN_APPICON, // Icon for SSH connection + remmina_plugin_exec_basic_settings, // Array for basic settings + NULL, // Array for advanced settings + REMMINA_PROTOCOL_SSH_SETTING_NONE, // SSH settings type + NULL, // Array for available features + remmina_plugin_exec_init, // Plugin initialization + remmina_plugin_exec_run, // Plugin open connection + remmina_plugin_exec_close, // Plugin close connection + NULL, // Query for available features + NULL, // Call a feature + NULL, // Send a keystroke + NULL, // No screenshot support available + NULL, // RCW map event + NULL // RCW unmap event +}; + +G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service) +{ + TRACE_CALL(__func__); + remmina_plugin_service = service; + + bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + + if (!service->register_plugin((RemminaPlugin*)&remmina_plugin)) { + return FALSE; + } + + return TRUE; +} diff --git a/plugins/exec/exec_plugin_config.h b/plugins/exec/exec_plugin_config.h new file mode 100644 index 0000000..d62a4fa --- /dev/null +++ b/plugins/exec/exec_plugin_config.h @@ -0,0 +1,64 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo + * + * Initially based on the plugin "Remmina Plugin EXEC", created and written by + * Fabio Castelli (Muflone) <muflone@vbsimple.net>. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + */ + +#pragma once + +#define PLUGIN_NAME "EXEC" +#define PLUGIN_DESCRIPTION N_("Execute a command") +#define PLUGIN_VERSION "1.0" +#define PLUGIN_APPICON "org.remmina.Remmina-tool-symbolic" + +#define REMMINA_PLUGIN_INFO(fmt, ...) \ + remmina_plugin_service->_remmina_info(__func__, fmt, ##__VA_ARGS__) + +#define REMMINA_PLUGIN_MESSAGE(fmt, ...) \ + remmina_plugin_service->_remmina_message(__func, fmt, ##__VA_ARGS__) + +#define REMMINA_PLUGIN_DEBUG(fmt, ...) \ + remmina_plugin_service->_remmina_debug(__func__, fmt, ##__VA_ARGS__) + +#define REMMINA_PLUGIN_WARNING(fmt, ...) \ + remmina_plugin_service->_remmina_warning(__func__, fmt, ##__VA_ARGS__) + +/* This will intentionally crash Remmina */ +#define REMMINA_PLUGIN_ERROR(fmt, ...) \ + remmina_plugin_service->_remmina_error(__func__, fmt, ##__VA_ARGS__) + +#define REMMINA_PLUGIN_CRITICAL(fmt, ...) \ + remmina_plugin_service->_remmina_critical(__func__, fmt, ##__VA_ARGS__) +#define REMMINA_PLUGIN_AUDIT(fmt, ...) \ + remmina_plugin_service->_remmina_audit(__func__, fmt, ##__VA_ARGS__) diff --git a/plugins/exec/scalable/emblems/org.remmina.Remmina-tool-symbolic.svg b/plugins/exec/scalable/emblems/org.remmina.Remmina-tool-symbolic.svg new file mode 100644 index 0000000..f383629 --- /dev/null +++ b/plugins/exec/scalable/emblems/org.remmina.Remmina-tool-symbolic.svg @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="24" + height="24" + viewBox="0 0 24 24" + version="1.1" + id="svg8" + inkscape:version="1.2 (dc2aedaf03, 2022-05-15)" + sodipodi:docname="org.remmina.Remmina-tool-symbolic.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="11.871974" + inkscape:cx="34.28242" + inkscape:cy="27.670209" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:document-rotation="0" + showgrid="false" + borderlayer="true" + inkscape:showpageshadow="true" + units="px" + inkscape:pagecheckerboard="false" + showguides="true" + inkscape:window-width="1920" + inkscape:window-height="995" + inkscape:window-x="1080" + inkscape:window-y="427" + inkscape:window-maximized="1" + objecttolerance="10" + guidetolerance="10" + inkscape:snap-tangential="true" + inkscape:snap-perpendicular="true" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:deskcolor="#d1d1d1"> + <inkscape:grid + type="xygrid" + id="grid10" + dotted="false" + originx="-171.36869" + originy="-105.18186" /> + </sodipodi:namedview> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <cc:license + rdf:resource="" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-171.36866,-165.16877)"> + <path + d="m 187.77406,165.16877 v 0.20572 l -7.85028,10.64488 c 0.22721,0.0656 0.45083,0.14294 0.66991,0.23189 -0.77754,-0.31322 -1.62348,-0.4954 -2.51368,-0.4954 -1.77942,0 -3.48494,0.70612 -4.74227,1.95807 -0.35468,0.35318 -0.66823,0.74465 -0.93515,1.16728 l 3.12261,3.10514 -2.0827,2.82424 c 0.4982,0.95633 1.28315,1.73475 2.24581,2.22754 l 2.83728,-2.06819 3.12548,3.10799 c 0.42429,-0.26637 0.817,-0.57912 1.17098,-0.93253 1.25898,-1.25029 1.96741,-2.94651 1.96908,-4.71574 0,-0.8619 -0.17842,-1.6798 -0.47762,-2.43611 0.0814,0.19645 0.15331,0.39646 0.21549,0.59958 l 10.70216,-8.009 z m -12.05944,0.3332 -4.21776,4.19702 5.47604,5.44278 4.21801,-4.19416 z m 13.91206,13.83421 -4.21801,4.19703 5.47628,5.44563 4.21801,-4.19726 z m -17.14253,6.67356 -1.11549,1.63082 1.49123,1.48312 1.52064,-1.10949 c -0.75315,-0.49516 -1.39819,-1.13659 -1.89638,-1.88553 z" + id="path32" + inkscape:connector-curvature="0" + style="isolation:isolate;fill:#171717;stroke-width:0.2385" /> + </g> +</svg> |