From fa33725fddf743286da4a18731b78505d93ac831 Mon Sep 17 00:00:00 2001 From: Cedric Bail Date: Mon, 3 Mar 2014 06:11:49 -0300 Subject: [PATCH] control: beginning integration of ssh into terminology. --- configure.ac | 11 ++ src/bin/Makefile.am | 5 +- src/bin/controls.c | 21 ++- src/bin/main.c | 2 + src/bin/options.c | 1 + src/bin/options_ssh.c | 408 ++++++++++++++++++++++++++++++++++++++++++ src/bin/options_ssh.h | 14 ++ 7 files changed, 458 insertions(+), 4 deletions(-) create mode 100644 src/bin/options_ssh.c create mode 100644 src/bin/options_ssh.h diff --git a/configure.ac b/configure.ac index 55e5b5b8..35cd2bf1 100644 --- a/configure.ac +++ b/configure.ac @@ -53,6 +53,16 @@ PKG_CHECK_MODULES([ELDBUS], [have_eldbus="no"] ) +PKG_CHECK_MODULES([AVAHI], + [avahi-client ecore-avahi], + [ + AC_DEFINE(HAVE_AVAHI, 1, [Avahi support]) + have_avahi="yes" + ], + [ + have_avahi="no" + ]) + AC_CHECK_FUNCS(mkstemps) EFL_WITH_BIN([edje], [edje-cc], [edje_cc]) @@ -114,4 +124,5 @@ echo " prefix...................: $prefix" echo echo "Features:" echo " dbus................: $have_eldbus" +echo " avahi...............: $have_avahi" echo diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am index 46ba0e9b..f7a34aeb 100644 --- a/src/bin/Makefile.am +++ b/src/bin/Makefile.am @@ -5,9 +5,9 @@ bin_PROGRAMS = terminology tybg tyalpha typop tyq tycat tyls terminology_CPPFLAGS = -I. \ -DPACKAGE_BIN_DIR=\"$(bindir)\" -DPACKAGE_LIB_DIR=\"$(libdir)\" \ --DPACKAGE_DATA_DIR=\"$(pkgdatadir)\" @TERMINOLOGY_CFLAGS@ +-DPACKAGE_DATA_DIR=\"$(pkgdatadir)\" @TERMINOLOGY_CFLAGS@ @AVAHI_CFLAGS@ -terminology_LDADD = @TERMINOLOGY_LIBS@ @ELDBUS_LIBS@ +terminology_LDADD = @TERMINOLOGY_LIBS@ @ELDBUS_LIBS@ @AVAHI_LIBS@ terminology_SOURCES = \ private.h \ @@ -20,6 +20,7 @@ keyin.c keyin.h \ main.c main.h \ media.c media.h \ options.c options.h \ +options_ssh.h options_ssh.c \ options_font.c options_font.h \ options_theme.c options_theme.h \ options_themepv.c options_themepv.h \ diff --git a/src/bin/controls.c b/src/bin/controls.c index 3fb86588..25ab8972 100644 --- a/src/bin/controls.c +++ b/src/bin/controls.c @@ -8,7 +8,7 @@ #include "main.h" static Evas_Object *ct_frame = NULL, *ct_boxh = NULL, *ct_box = NULL; -static Evas_Object *ct_box2 = NULL, *ct_over = NULL; +static Evas_Object *ct_box2 = NULL, *ct_boxssh = NULL, *ct_over = NULL; static Eina_Bool ct_out = EINA_FALSE; static Ecore_Timer *ct_del_timer = NULL; static Evas_Object *ct_win = NULL, *ct_bg = NULL, *ct_term = NULL; @@ -240,9 +240,13 @@ controls_toggle(Evas_Object *win, Evas_Object *bg, Evas_Object *term, evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); elm_object_text_set(o, "Controls"); + ct_boxssh = o = elm_box_add(win); + elm_object_content_set(ct_frame, o); + evas_object_show(o); + ct_boxh = o = elm_box_add(win); elm_box_horizontal_set(o, EINA_TRUE); - elm_object_content_set(ct_frame, o); + elm_box_pack_end(ct_boxssh, o); evas_object_show(o); ct_box2 = o = elm_box_add(win); @@ -293,6 +297,19 @@ controls_toggle(Evas_Object *win, Evas_Object *bg, Evas_Object *term, o = _button_add(win, "About", "about", _cb_ct_about, NULL); elm_box_pack_end(ct_box, o); + o = _sep_add_h(win); + elm_box_pack_end(ct_boxssh, o); + + o = elm_genlist_add(win); + evas_object_size_hint_weight_set(o, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(o, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_size_hint_request_set(o, 64, 64); + elm_genlist_mode_set(o, ELM_LIST_COMPRESS); + elm_genlist_homogeneous_set(o, EINA_TRUE); + elm_box_pack_end(ct_boxssh, o); + evas_object_show(o); + options_genlist_add(o); + evas_object_event_callback_add(ct_frame, EVAS_CALLBACK_DEL, _cb_frame_del, NULL); diff --git a/src/bin/main.c b/src/bin/main.c index 9ccb4a0c..7eb8a251 100644 --- a/src/bin/main.c +++ b/src/bin/main.c @@ -2704,6 +2704,7 @@ elm_main(int argc, char **argv) elm_app_info_set(elm_main, "terminology", "themes/default.edj"); config_init(); + options_ssh_init(); main_config = config_load("config"); @@ -3058,6 +3059,7 @@ remote: termpty_shutdown(); config_del(main_config); + options_ssh_shutdown(); config_shutdown(); eina_log_domain_unregister(_log_domain); _log_domain = -1; diff --git a/src/bin/options.c b/src/bin/options.c index 6eea718c..1ccb0223 100644 --- a/src/bin/options.c +++ b/src/bin/options.c @@ -10,6 +10,7 @@ #include "options_behavior.h" #include "options_keys.h" #include "options_helpers.h" +#include "options_ssh.h" #include "config.h" #include "termio.h" diff --git a/src/bin/options_ssh.c b/src/bin/options_ssh.c new file mode 100644 index 00000000..c8211825 --- /dev/null +++ b/src/bin/options_ssh.c @@ -0,0 +1,408 @@ +#include "private.h" + +#include + +#ifdef HAVE_AVAHI +# include +# include +# include +# include +#endif + +#include "options_ssh.h" + +#define FREE_CLEAN(h, cb) do { if (h) cb(h); h = NULL; } while (0); + +typedef struct _SSH_Genlist SSH_Genlist; +struct _SSH_Genlist +{ + Evas_Object *gl; + Elm_Object_Item *announced_servers; + Elm_Object_Item *ww_servers; + Eina_List *items; +}; + +struct _SSH_Server +{ + const char *domain; + const char *name; + const char *ip; + unsigned int port; + + unsigned char announced : 1; +}; + +static Eina_List *announced_servers = NULL; +static Eina_List *ww_servers = NULL; +static Eina_List *genlists = NULL; + +static Elm_Genlist_Item_Class *itc_group = NULL; +static Elm_Genlist_Item_Class *itc = NULL; + +static void +_option_genlist_ssh_add(SSH_Genlist *genlist, unsigned char announced, const SSH_Server *server) +{ + Elm_Object_Item *parent; + Elm_Object_Item *it; + + if (announced) + { + if (!genlist->announced_servers) + { + genlist->announced_servers = elm_genlist_item_append(genlist->gl, itc_group, + (void *)(uintptr_t)1 /* item data */, + NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL); + elm_genlist_item_select_mode_set(genlist->announced_servers, ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY); + } + parent = genlist->announced_servers; + } + else + { + if (!genlist->ww_servers) + { + genlist->ww_servers = elm_genlist_item_append(genlist->gl, itc_group, NULL, + NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL); + elm_genlist_item_select_mode_set(genlist->ww_servers, ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY); + } + parent = genlist->ww_servers; + } + + it = elm_genlist_item_append(genlist->gl, itc, server /* item data */, + parent /* parent */, + ELM_GENLIST_ITEM_NONE, + NULL /* func */, // FIXME: there should the action come + NULL); + + genlist->items = eina_list_append(genlist->items, it); +} + +static void +_option_genlist_ssh_del(SSH_Genlist *genlist, const SSH_Server *server) +{ + Elm_Object_Item *it; + Eina_List *l, *ll; + + EINA_LIST_FOREACH_SAFE(genlist->items, l, ll, it) + if (elm_object_item_data_get(it) == server) + { + elm_object_item_del(it); + genlist->items = eina_list_remove_list(genlist->items, l); + } +} + +void +options_ssh_server_add(unsigned char announced, const char *domain, const char *name, const char *ip, unsigned int port) +{ + SSH_Server *srv; + SSH_Genlist *genlist; + Eina_List* l; + + srv = calloc(1, sizeof (SSH_Server)); + if (!srv) return ; + + srv->domain = eina_stringshare_add(domain); + srv->name = eina_stringshare_add(name); + srv->ip = eina_stringshare_add(ip); + srv->port = port; + srv->announced = announced; + + INF("Avahi discovered new host '%s' (%s:%i) from domain '%s'.", + name, ip, port, domain); + + if (announced) + announced_servers = eina_list_append(announced_servers, srv); + else + ww_servers = eina_list_append(ww_servers, srv); + + EINA_LIST_FOREACH(genlists, l, genlist) + _option_genlist_ssh_add(genlist, announced, srv); +} + +void +options_ssh_server_data_del(SSH_Server *server) +{ + SSH_Genlist *genlist; + Eina_List *l; + + INF("Avahi discovered host disapeared '%s' (%s:%i) from domain '%s'.", + server->name, server->ip, server->port, server->domain); + + EINA_LIST_FOREACH(genlists, l, genlist) + _option_genlist_ssh_del(genlist, server); + + eina_stringshare_del(server->name); + eina_stringshare_del(server->domain); + eina_stringshare_del(server->ip); + + if (server->announced) + announced_servers = eina_list_remove(announced_servers, server); + else + ww_servers = eina_list_remove(ww_servers, server); + + free(server); +} + +void +options_ssh_server_del(unsigned char announced, const char *domain, const char *name) +{ + SSH_Server *srv; + Eina_List *l; + + domain = eina_stringshare_add(domain); + name = eina_stringshare_add(name); + + l = announced ? announced_servers : ww_servers; + + EINA_LIST_FOREACH(l, l, srv) + if (srv->name == name && + srv->domain == domain) + { + options_ssh_server_data_del(srv); + break; + } + + eina_stringshare_del(domain); + eina_stringshare_del(name); +} + +static Eina_Bool +_options_genlist_death(void *data, Eo *obj EINA_UNUSED, + const Eo_Event_Description *desc EINA_UNUSED, + void *event_info EINA_UNUSED) +{ + SSH_Genlist *genlist = data; + + eina_list_free(genlist->items); + free(genlist); + + return EINA_TRUE; +} + +void +options_genlist_add(Evas_Object *gl) +{ + SSH_Genlist *genlist; + SSH_Server *srv; + Eina_List *l; + + genlist = calloc(1, sizeof(SSH_Genlist)); + if (!genlist) return ; + + genlist->gl = gl; + + EINA_LIST_FOREACH(announced_servers, l, srv) + _option_genlist_ssh_add(genlist, 1, srv); + EINA_LIST_FOREACH(ww_servers, l, srv) + _option_genlist_ssh_add(genlist, 0, srv); + + eo_do(gl, eo_event_callback_add(EO_EV_DEL, _options_genlist_death, genlist)); +} + +static char * +gl_text_get(void *data, Evas_Object *obj EINA_UNUSED, const char *part EINA_UNUSED) +{ + SSH_Server *srv = data; + Eina_Strbuf *buf; + char *r; + + buf = eina_strbuf_new(); + + if (srv->port != 22) + eina_strbuf_append_printf(buf, "%s:%i", srv->name, srv->port); + else + eina_strbuf_append_printf(buf, "%s", srv->name); + + r = eina_strbuf_string_steal(buf); + eina_strbuf_free(buf); + + return r; +} + +static char * +gl_group_get(void *data, Evas_Object *obj EINA_UNUSED, const char *part EINA_UNUSED) +{ + if (data) return strdup("Announced server"); + return strdup("World wide server"); +} + +#ifdef HAVE_AVAHI +static Ecore_Avahi *context = NULL; +static AvahiClient *client = NULL; +static AvahiServiceBrowser *sb = NULL; +static int error = 0; + +// FIXME: Check what it does +static void +_resolve_callback(AvahiServiceResolver *r, + AvahiIfIndex interface EINA_UNUSED, + AvahiProtocol protocol EINA_UNUSED, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name EINA_UNUSED, + const AvahiAddress *address, + uint16_t port, + AvahiStringList *txt EINA_UNUSED, + AvahiLookupResultFlags flags EINA_UNUSED, + void* userdata EINA_UNUSED) +{ + /* Called whenever a service has been resolved successfully or timed out */ + switch (event) + { + case AVAHI_RESOLVER_FAILURE: + ERR("Avahi failed to resolve service '%s' of type '%s' in domain '%s': %s\n", + name, type, domain, + avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + break; + + case AVAHI_RESOLVER_FOUND: + { + char a[AVAHI_ADDRESS_STR_MAX]; + + avahi_address_snprint(a, sizeof(a), address); + options_ssh_server_add(1, domain, name, a, port); + } + } + + avahi_service_resolver_free(r); +} + +static void +_browse_callback(AvahiServiceBrowser *b EINA_UNUSED, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AvahiLookupResultFlags flags EINA_UNUSED, + void* userdata) +{ + AvahiClient *c = userdata; + + /* Called whenever a new services becomes available on the LAN or is removed from the LAN */ + switch (event) + { + case AVAHI_BROWSER_FAILURE: + // FIXME: What to do in case of failure ? Schedule another browser ? + break; + + case AVAHI_BROWSER_NEW: + /* We ignore the returned resolver object. In the callback + function we free it. If the server is terminated before + the callback function is called the server will free + the resolver for us. */ + + avahi_service_resolver_new(c, interface, + protocol, name, type, domain, + AVAHI_PROTO_UNSPEC, 0, _resolve_callback, c); + + break; + + case AVAHI_BROWSER_REMOVE: + options_ssh_server_del(1, domain, name); + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + /* Nothing to do in our case */ + break; + } +} + +static void +_client_callback(AvahiClient *c, AvahiClientState state, void * userdata EINA_UNUSED) +{ + switch (state) + { + case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_COLLISION: + case AVAHI_CLIENT_S_RUNNING: + if (!sb) + { + sb = avahi_service_browser_new(c, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_ssh._tcp", NULL, 0, _browse_callback, c); + if (!sb) + { + WRN("Failed to setup an Avahi Service Browser."); + return ; + } + } + break; + + case AVAHI_CLIENT_FAILURE: + WRN("Server connection failure: %s.", avahi_strerror(avahi_client_errno(c))); + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) + { + FREE_CLEAN(sb, avahi_service_browser_free); + FREE_CLEAN(client, avahi_client_free); + + client = avahi_client_new(ecore_avahi_poll_get(context), AVAHI_CLIENT_NO_FAIL, _client_callback, NULL, &error); + if (!client) + { + WRN("Unable to setup an AvahiClient context."); + return ; + } + break; + } + + case AVAHI_CLIENT_CONNECTING: + FREE_CLEAN(sb, avahi_service_browser_free); + break; + } +} +#endif + +void +options_ssh_init(void) +{ + if (!itc) + { + itc = elm_genlist_item_class_new(); + itc->item_style = "default"; + itc->func.text_get = gl_text_get; + itc->func.content_get = NULL; + itc->func.state_get = NULL; + itc->func.del = NULL; + } + if (!itc_group) + { + itc_group = elm_genlist_item_class_new(); + itc_group->item_style = "group_index"; + itc_group->func.text_get = gl_group_get; + itc_group->func.content_get = NULL; + itc_group->func.state_get = NULL; + itc_group->func.del = NULL; + } + +#ifdef HAVE_AVAHI + if (context) return ; + + context = ecore_avahi_add(); + if (!context) + { + WRN("Unable to setup an Ecore_Avahi context."); + return ; + } + + client = avahi_client_new(ecore_avahi_poll_get(context), AVAHI_CLIENT_NO_FAIL, _client_callback, NULL, &error); + if (!client) + { + WRN("Unable to setup an AvahiClient context."); + options_ssh_shutdown(); + return ; + } +#endif +} + +void +options_ssh_shutdown(void) +{ + FREE_CLEAN(itc, elm_genlist_item_class_free); + FREE_CLEAN(itc_group, elm_genlist_item_class_free); +#ifdef HAVE_AVAHI + FREE_CLEAN(sb, avahi_service_browser_free); + FREE_CLEAN(client, avahi_client_free); + FREE_CLEAN(context, ecore_avahi_del); +#endif +} diff --git a/src/bin/options_ssh.h b/src/bin/options_ssh.h new file mode 100644 index 00000000..c4fab02a --- /dev/null +++ b/src/bin/options_ssh.h @@ -0,0 +1,14 @@ +#ifndef _OPTIONS_SSH_H__ +#define _OPTIONS_SSH_H__ + +typedef struct _SSH_Server SSH_Server; + +void options_ssh_server_add(unsigned char local, const char *domain, const char *name, const char *ip, unsigned int port); +void options_ssh_server_data_del(SSH_Server *server); +void options_ssh_server_del(unsigned char local, const char *domain, const char *name); +void options_genlist_add(Evas_Object *gl); + +void options_ssh_init(void); +void options_ssh_shutdown(void); + +#endif