commit ff8fbe6e4afcc53735525087c305de3cbd35ea22 Author: Leif Middelschulte Date: Thu Jan 31 02:14:50 2013 +0000 Welcome 'Share', conviently share you clipboard content online. The uploaded text is syntax highlighted. Share places a link to the site that holds your content in your clipboard in return. SVN revision: 83496 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e684cdd --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Leif Middelschulte diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..9690c3f --- /dev/null +++ b/COPYING @@ -0,0 +1,32 @@ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies of the Software and its Copyright notices. In addition publicly +documented acknowledgment must be given that this software has been used if no +source code of this software is made available publicly. Making the source +available publicly means including the source for this software with the +distribution, or a method to get this software via some reasonable mechanism +(electronic transfer via a network or media) as well as making an offer to +supply the source on request. This Copyright notice serves as an offer to +supply the source on on request as well. Instead of this, supplying +acknowledgments of use of this software in either Copyright notices, Manuals, +Publicity and Marketing documents or any documentation provided with any +product containing this software. This License does not apply to any software +that links to the libraries provided by this software (statically or +dynamically), but only to the software provided. + +Please see the COPYING-PLAIN for a plain-english explanation of this notice +and its intent. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/COPYING-PLAIN b/COPYING-PLAIN new file mode 100644 index 0000000..3d7fe1a --- /dev/null +++ b/COPYING-PLAIN @@ -0,0 +1,33 @@ +Plain English Copyright Notice + +This file is not intended to be the actual License. The reason this file +exists is that we here are programmers and engineers. We aren't lawyers. We +provide licenses that we THINK say the right things, but we have our own +intentions at heart. This is a plain-english explanation of what those +intentions are, and if you follow them you will be within the "spirit" of +the license. + +The intent is for us to enjoy writing software that is useful to us (the +AUTHORS) and allow others to use it freely and also benefit from the work we +put into making it. We don't want to restrict others using it. They should +not *HAVE* to make the source code of the applications they write that +simply link to these libraries (be that statically or dynamically), or for +them to be limited as to what license they choose to use (be it open, closed +or anything else). But we would like to know you are using these libraries. +We simply would like to know that it has been useful to someone. This is why +we ask for acknowledgement of some sort. + +You can do what you want with the source of this software - it doesn't +matter. We still have it here for ourselves and it is open and free to use +and download and play with. It can't be taken away. We don't really mind what +you do with the source to your software. We would simply like to know that +you are using it - especially if it makes it to a commerical product. If you +simply e-mail all the AUTHORS (see COPYING and AUTHORS files) telling us, and +then make sure you include a paragraph or page in the manual for the product +with the copyright notice and state that you used this software, we will be +very happy. If you want to contribute back modifications and fixes you may have +made we will welcome those too with open arms (generally). If you want help +with changes needed, ports needed or features to be added, arrangements can +be easily made with some dialogue. + +Leif Middelschulte diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..8919fc2 --- /dev/null +++ b/INSTALL @@ -0,0 +1,11 @@ +COMPILING and INSTALLING: + +If you got a official release tar archive do: + ./autogen.sh + +Then to compile: + make + +To install: + make install + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..186fae4 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,25 @@ +MAINTAINERCLEANFILES = Makefile.in aclocal.m4 config.guess config.h.in \ + config.sub configure depcomp install-sh ltmain.sh \ + missing module.desktop config.rpath mkinstalldirs + +SUBDIRS = src + +EDJE_FLAGS = -v \ + -id $(top_srcdir)/images + +filesdir = $(datadir) +files_DATA = module.desktop e-module-share.edj + +EXTRA_DIST = module.desktop.in \ + e_modules-share.spec.in \ + e-module-share.edc \ + images/module_icon.png + +%.edj: %.edc + $(EDJE_CC) $(EDJE_FLAGS) $< $@ + +clean-local: + rm -rf e-module-share.edj module.desktop e_modules-share.spec *~ + +uninstall: + rm -rf $(DESTDIR)$(datadir) diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..6c6366c --- /dev/null +++ b/README @@ -0,0 +1,2 @@ +This module shall share data from the clipboard online. +The user shall be given a link to the share. diff --git a/TODO b/TODO new file mode 100644 index 0000000..f494dd8 --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +- Store and restore old links for every instance +- Enable HTTPS + +Future: +- Share files +- Extend to more services (1Click hosters and local p2p) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..0846992 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +rm -rf autom4te.cache +rm -f aclocal.m4 ltmain.sh + +touch README + +echo "Running aclocal..." ; aclocal $ACLOCAL_FLAGS || exit 1 +echo "Running autoheader..." ; autoheader || exit 1 +echo "Running autoconf..." ; autoconf || exit 1 +echo "Running libtoolize..." ; (libtoolize --copy --automake || glibtoolize --automake) || exit 1 +echo "Running automake..." ; automake --add-missing --copy --gnu || exit 1 + +if [ -z "$NOCONFIGURE" ]; then + ./configure "$@" +fi diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..4344630 --- /dev/null +++ b/configure.ac @@ -0,0 +1,59 @@ +dnl Process this file with autoconf to produce a configure script. + +# get rid of that stupid cache mechanism +rm -f config.cache + +AC_INIT(share, 0.0.1, enlightenment-devel@lists.sourceforge.net) +AC_PREREQ(2.52) +AC_CONFIG_SRCDIR(configure.ac) +AC_CANONICAL_BUILD +AC_CANONICAL_HOST +AC_ISC_POSIX + +AM_INIT_AUTOMAKE(1.6) +AC_CONFIG_HEADERS(config.h) + +AC_PROG_CC +AC_HEADER_STDC +AC_C_CONST + +define([AC_LIBTOOL_LANG_CXX_CONFIG], [:])dnl +define([AC_LIBTOOL_LANG_F77_CONFIG], [:])dnl +AC_PROG_LIBTOOL + +PKG_CHECK_MODULES(E, [enlightenment]) +release=$(pkg-config --variable=release enlightenment) +MODULE_ARCH="$host_os-$host_cpu-$release" +AC_SUBST(MODULE_ARCH) +AC_DEFINE_UNQUOTED(MODULE_ARCH, "$MODULE_ARCH", "Module architecture") + +PKG_CHECK_MODULES(JSON, [json]) + +# Find edje_cc +PKG_CHECK_MODULES(EDJE, [edje >= 0.5.0]) +AC_ARG_WITH(edje-cc, + AC_HELP_STRING([--with-edje-cc=PATH], [specify a specific path to edje_cc]), + [ + v=$withval; + EDJE_CC=$v + ],[ + EDJE_CC=$(pkg-config --variable=prefix edje)/bin/edje_cc + ] +) +AC_SUBST(EDJE_CC) +AC_MSG_CHECKING([Which edje_cc to use]) +AC_MSG_RESULT(${EDJE_CC}) + +datadir=$(pkg-config --variable=modules enlightenment)/${PACKAGE} +AC_ARG_ENABLE(homedir-install, + AS_HELP_STRING([--enable-homedir-install], [Install module in homedir]), + [ datadir="${HOME}/.e/e/modules/${PACKAGE}" ] +) + +AC_OUTPUT([ +Makefile +src/Makefile +e_modules-share.spec +module.desktop +], [ +]) diff --git a/e-module-share.edc b/e-module-share.edc new file mode 100644 index 0000000..28cd241 --- /dev/null +++ b/e-module-share.edc @@ -0,0 +1,74 @@ +collections { + + images.image: "module_icon.png" COMP; + + group { + name: "icon"; + max: 48 48; + parts { + part { + name: "image"; + type: IMAGE; + mouse_events: 0; + description { + state: "default" 0.00; + visible: 1; + aspect: 1.00 1.00; + aspect_preference: BOTH; + rel1 { + relative: 0.00 0.00; + offset: 0 0; + } + rel2 { + relative: 1.00 1.00; + offset: -1 -1; + } + image.normal: "module_icon.png"; + } + } + } + } + group { + name: "modules/share/main"; + max: 128 128; + parts { + part { name: "logo"; + type: IMAGE; + description { state: "default" 0.0; + aspect: 1.0 1.0; + aspect_preference: BOTH; + rel1.offset: 0 0; + rel2.offset: -1 -1; + image.normal: "module_icon.png"; + } + description { state: "active" 0.0; + inherit: "default" 0.0; + color: 255 255 255 128; + } + } + part { name: "inout"; + type: RECT; + mouse_events: 1; + description { state: "default" 0.0; + color: 255 255 255 0; + } + } + } + programs { + program { name: "go_active"; + signal: "active"; + source: ""; + action: STATE_SET "active" 0.0; + target: "logo"; + transition: LINEAR 0.5; + } + program { name: "go_passive"; + signal: "passive"; + source: ""; + action: STATE_SET "default" 0.0; + target: "logo"; + transition: LINEAR 0.5; + } + } + } +} diff --git a/e_modules-share.spec.in b/e_modules-share.spec.in new file mode 100644 index 0000000..95ab663 --- /dev/null +++ b/e_modules-share.spec.in @@ -0,0 +1,48 @@ +%define module_name share +%{!?_rel:%{expand:%%global _rel 0.enl%{?dist}}} + +Summary: %{module_name} module for the Enlightenment window manager +Name: e_modules-%{module_name} +Version: @VERSION@ +Release: %{_rel} +License: BSD +Group: User Interface/Desktops +URL: http://www.enlightenment.org/ +Source: ftp://ftp.enlightenment.org/pub/enlightenment/%{module_name}-%{version}.tar.gz +Packager: %{?_packager:%{_packager}}%{!?_packager:Michael Jennings } +Vendor: %{?_vendorinfo:%{_vendorinfo}}%{!?_vendorinfo:The Enlightenment Project (http://www.enlightenment.org/)} +Distribution: %{?_distribution:%{_distribution}}%{!?_distribution:%{_vendor}} +BuildRequires: ecore-devel, evas-devel, edje-bin +BuildRequires: edje-devel, eet-devel, enlightenment-devel >= 0.16.999 +Requires: enlightenment >= 0.16.999 +BuildRoot: %{_tmppath}/%{name}-%{version}-root + +%description +%{module_name} module for the Enlightenment window manager. + +%prep +%setup -q -n %{module_name}-%{version} + +%build +%{configure} +%{__make} %{?_smp_mflags} %{?mflags} + +%install +%{__make} %{?mflags_install} DESTDIR=$RPM_BUILD_ROOT install +%{find_lang} %{module_name} || true > %{module_name}.lang + +%clean +test "x$RPM_BUILD_ROOT" != "x/" && rm -rf $RPM_BUILD_ROOT + +%post +/sbin/ldconfig + +%postun +/sbin/ldconfig + +%files -f %{module_name}.lang +%defattr(-, root, root) +%doc AUTHORS ChangeLog COPYING* INSTALL NEWS README +%{_libdir}/enlightenment/modules/%{module_name}* + +%changelog diff --git a/images/module_icon.png b/images/module_icon.png new file mode 100644 index 0000000..42d4f00 Binary files /dev/null and b/images/module_icon.png differ diff --git a/images/module_icon.svg b/images/module_icon.svg new file mode 100755 index 0000000..9ae179f --- /dev/null +++ b/images/module_icon.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/m4/.svnignore b/m4/.svnignore new file mode 100644 index 0000000..e69de29 diff --git a/module.desktop.in b/module.desktop.in new file mode 100644 index 0000000..12bcee8 --- /dev/null +++ b/module.desktop.in @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=Link +Name=Share +Icon=e-module-share +X-Enlightenment-ModuleType=utils +Comment=Gadget that shares your clipboard content online and puts a link to it into your clipboard diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..87ed16d --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,21 @@ +MAINTAINERCLEANFILES = Makefile.in + +INCLUDES = -I. \ + -I$(top_srcdir) \ + -I$(includedir) \ + @E_CFLAGS@ \ + @JSON_CFLAGS@ + +pkgdir = $(datadir)/$(MODULE_ARCH) +pkg_LTLIBRARIES = module.la +module_la_SOURCES = e_mod_main.c \ + e_mod_main.h \ + e_share.h \ + sourcedrop.c + +module_la_LIBADD = @E_LIBS@ @JSON_LIBS@ -lm -lbsd +module_la_LDFLAGS = -module -avoid-version +module_la_DEPENDENCIES = $(top_builddir)/config.h + +clean-local: + rm -rf *~ diff --git a/src/e_mod_main.c b/src/e_mod_main.c new file mode 100644 index 0000000..479f2bd --- /dev/null +++ b/src/e_mod_main.c @@ -0,0 +1,387 @@ +#include "e_mod_main.h" +#include "e_share.h" +#include + +#define __UNUSED__ +#define _(S) S + +/* actual module specifics */ +typedef struct _Instance Instance; +struct _Instance +{ + E_Gadcon_Client *gcc; + E_Menu *menu; + Ecore_X_Window win; + Evas_Object *o_button; + Eina_List *handlers; + Eina_List *shares; +}; + +/* gadcon requirements */ +static E_Gadcon_Client *_gc_init(E_Gadcon * gc, const char *name, const char *id, const char *style); +static void _gc_shutdown(E_Gadcon_Client * gcc); +static void _gc_orient(E_Gadcon_Client * gcc, E_Gadcon_Orient orient); +static const char *_gc_label(const E_Gadcon_Client_Class *client_class); +static Evas_Object *_gc_icon(const E_Gadcon_Client_Class *client_class, Evas * evas); +static const char *_gc_id_new(const E_Gadcon_Client_Class *client_class); + +static void _share_button_cb_mouse_down(void *data, Evas *evas, Evas_Object *obj, Evas_Event_Mouse_Down *ev); +static Eina_Bool _share_x_selection_notify_handler(Instance *instance, int type, void *event); +static void _share_menu_post_cb(void *data, E_Menu *menu __UNUSED__); +static void _share_menu_share_request_click_cb(Instance *inst, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__); +static void _share_menu_share_item_click_cb(Share_Data *selected_share, E_Menu *m, E_Menu_Item *mi); +static void _share_open_link_in_browser(const Share_Data *sd); +static void _share_notify(const Share_Data *sd); +static void _share_notification_clicked_cb(void *data, unsigned int id); +static void _free_share_data(Share_Data *sd); + +static E_Module *share_module = NULL; + +/* and actually define the gadcon class that this module provides (just 1) */ +static const E_Gadcon_Client_Class _gadcon_class = { + GADCON_CLIENT_CLASS_VERSION, + "share", + { + _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL, + e_gadcon_site_is_not_toolbar + }, + E_GADCON_CLIENT_STYLE_PLAIN +}; + +static E_Gadcon_Client * +_gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style) +{ + Evas_Object *o; + E_Gadcon_Client *gcc; + Instance *inst; + + inst = E_NEW(Instance, 1); + + /* + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s/e-module-share.edj", e_module_dir_get(share_module)); + + o = edje_object_add(gc->evas); + if (!e_theme_edje_object_set(o, "base/theme/modules/share", "modules/share/main")) + edje_object_file_set(o, buf, "modules/share/main"); + edje_object_signal_emit(o, "passive", ""); + */ + + o = e_icon_add(gc->evas); + e_icon_fdo_icon_set(o, "emblem-shared"); + evas_object_show(o); + + gcc = e_gadcon_client_new(gc, name, id, style, o); + gcc->data = inst; + + inst->gcc = gcc; + inst->win = ecore_evas_window_get(gc->ecore_evas); + inst->o_button = o; + + e_gadcon_client_util_menu_attach(gcc); + + evas_object_event_callback_add(inst->o_button, EVAS_CALLBACK_MOUSE_DOWN, (Evas_Object_Event_Cb)_share_button_cb_mouse_down, inst); + E_LIST_HANDLER_APPEND(inst->handlers, ECORE_X_EVENT_SELECTION_NOTIFY, _share_x_selection_notify_handler, inst); + + return gcc; +} + +static void +_gc_shutdown(E_Gadcon_Client *gcc) +{ + Instance *inst; + + inst = gcc->data; + E_FREE_LIST(inst->handlers, ecore_event_handler_del); + inst->handlers = NULL; + + if (inst->menu) + { + e_menu_post_deactivate_callback_set(inst->menu, NULL, NULL); + e_object_del(E_OBJECT(inst->menu)); + inst->menu = NULL; + } + + E_FREE_LIST(inst->shares, _free_share_data); + inst->shares = NULL; + + evas_object_del(inst->o_button); + E_FREE(inst); +} + +static void +_gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient) +{ + e_gadcon_client_aspect_set (gcc, 16, 16); + e_gadcon_client_min_size_set (gcc, 16, 16); +} + +static const char * +_gc_label (const E_Gadcon_Client_Class *client_class) +{ + return "Share"; +} + +static Evas_Object * +_gc_icon(const E_Gadcon_Client_Class *client_class, Evas * evas) +{ + Evas_Object *o; + /* + char buf[PATH_MAX]; + + o = edje_object_add(evas); + snprintf (buf, sizeof(buf), "%s/e-module-share.edj", e_module_dir_get(share_module)); + edje_object_file_set(o, buf, "icon"); + */ + + o = e_icon_add(evas); + e_icon_fdo_icon_set(o, "emblem-shared"); + + return o; +} + +static const char * +_gc_id_new (const E_Gadcon_Client_Class *client_class) +{ + return _gadcon_class.name; +} + +static void +_share_button_cb_mouse_down(void *data, Evas *evas, Evas_Object *obj, Evas_Event_Mouse_Down *ev) +{ + Instance *inst = (Instance*)data; + Evas_Coord x, y, w, h; + int cx, cy; + int dir; + E_Menu_Item *mi; + Eina_List *it; + Share_Data *share; + + if (!inst) return; + + if ((ev->button == 1) && (!inst->menu)) + { + /* Coordinates and sizing */ + evas_object_geometry_get(inst->o_button, &x, &y, &w, &h); + e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &cx, &cy, + NULL, NULL); + x += cx; + y += cy; + + inst->menu = e_menu_new(); + + mi = e_menu_item_new(inst->menu); + e_menu_item_label_set(mi, _("Share Clipboard Content")); + e_menu_item_callback_set(mi, (E_Menu_Cb)_share_menu_share_request_click_cb, inst); + + mi = e_menu_item_new(inst->menu); + e_menu_item_separator_set(mi, EINA_TRUE); + + EINA_LIST_FOREACH(inst->shares, it, share) + { + mi = e_menu_item_new(inst->menu); + e_menu_item_label_set(mi, share->name); + e_menu_item_callback_set(mi, (E_Menu_Cb)_share_menu_share_item_click_cb, share); + } + + e_menu_post_deactivate_callback_set(inst->menu, + _share_menu_post_cb, inst); + + /* Proper menu orientation */ + switch (inst->gcc->gadcon->orient) + { + case E_GADCON_ORIENT_TOP: + case E_GADCON_ORIENT_CORNER_TL: + case E_GADCON_ORIENT_CORNER_TR: + dir = E_MENU_POP_DIRECTION_DOWN; + break; + + case E_GADCON_ORIENT_BOTTOM: + case E_GADCON_ORIENT_CORNER_BL: + case E_GADCON_ORIENT_CORNER_BR: + dir = E_MENU_POP_DIRECTION_UP; + break; + + case E_GADCON_ORIENT_LEFT: + case E_GADCON_ORIENT_CORNER_LT: + case E_GADCON_ORIENT_CORNER_LB: + dir = E_MENU_POP_DIRECTION_RIGHT; + break; + + case E_GADCON_ORIENT_RIGHT: + case E_GADCON_ORIENT_CORNER_RT: + case E_GADCON_ORIENT_CORNER_RB: + dir = E_MENU_POP_DIRECTION_LEFT; + break; + + case E_GADCON_ORIENT_FLOAT: + case E_GADCON_ORIENT_HORIZ: + case E_GADCON_ORIENT_VERT: + default: + dir = E_MENU_POP_DIRECTION_AUTO; + break; + } + + e_gadcon_locked_set(inst->gcc->gadcon, EINA_TRUE); + + /* We display not relatively to the gadget, but similarly to + * the start menu - thus the need for direction etc. + */ + e_menu_activate_mouse(inst->menu, + e_util_zone_current_get + (e_manager_current_get()), + x, y, w, h, dir, ev->timestamp); + } +} + +static Eina_Bool +_share_x_selection_notify_handler(Instance *instance, int type, void *event) +{ + Ecore_X_Event_Selection_Notify *ev; + Share_Data *sd = NULL; + + if (!instance) + return EINA_TRUE; + + ev = event; + if ((ev->selection == ECORE_X_SELECTION_CLIPBOARD) && + (strcmp(ev->target, ECORE_X_SELECTION_TARGET_UTF8_STRING) == 0)) + { + Ecore_X_Selection_Data_Text *text_data; + + text_data = ev->data; + if ((text_data->data.content == ECORE_X_SELECTION_CONTENT_TEXT) && + (text_data->text)) + { + char buf[20]; + if (text_data->data.length == 0) return EINA_TRUE; + + sd = E_NEW(Share_Data, 1); + sd->inst = instance; + snprintf(buf, ((text_data->data.length >= sizeof(buf)) ? (sizeof(buf) - 1) : text_data->data.length), text_data->text); + asprintf(&sd->name, "%s", buf); + asprintf(&sd->content, "%s", text_data->text); + + sourcedrop_share(sd); + } + } + + return ECORE_CALLBACK_PASS_ON; +} + +/* Updates the X selection with the selected text of the entry */ +void +_clipboard_update(const char *text, const Instance *inst) +{ + EINA_SAFETY_ON_NULL_RETURN(inst); + EINA_SAFETY_ON_NULL_RETURN(text); + + ecore_x_selection_clipboard_set(inst->win, text, strlen(text) + 1); +} + +void e_share_upload_completed(Share_Data *sd) +{ + if (!sd) return; + ((Instance*)sd->inst)->shares = eina_list_append(((Instance*)sd->inst)->shares, sd); + _share_notify(sd); + _clipboard_update(sd->url, sd->inst); +} + +static void +_share_menu_share_request_click_cb(Instance *inst, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + if (!inst) return; + ecore_x_selection_clipboard_request(inst->win, + ECORE_X_SELECTION_TARGET_UTF8_STRING); +} + +static void +_share_menu_share_item_click_cb(Share_Data *selected_share, E_Menu *m __UNUSED__, E_Menu_Item *mi __UNUSED__) +{ + _share_open_link_in_browser(selected_share); +} + +static void +_share_notify(const Share_Data *sd) +{ + char buf[256]; + E_Notification_Notify *n = E_NEW(E_Notification_Notify, 1); + + if (!sd || !n) + return; + n->id = 0; + n->app_name = _("E Share"); + n->sumary = _("Your content has been shared."); + snprintf(buf, (sizeof(buf) - 1), _("Your clipboard's content is now available at %s."), sd->url); + n->body = buf; + n->icon.icon = "e"; + n->timeout = 2; + e_notification_client_send(n, NULL /*_share_notification_clicked_cb*/, sd); +} + +static void +_share_open_link_in_browser(const Share_Data *sd) +{ + char buf[256]; + + if (!sd) + return; + snprintf(buf, (sizeof(buf) - 1), "xdg-open %s", sd->url); + e_exec(e_gadcon_client_zone_get(((Instance*)sd->inst)->gcc), NULL, buf, NULL, NULL); +} + +static void +_share_notification_clicked_cb(void *data, unsigned int id) +{ + if (!data) + return; + _share_open_link_in_browser((Share_Data*)data); +} + +static void +_share_menu_post_cb(void *data, E_Menu *menu __UNUSED__) +{ + Instance *inst = data; + + if (!inst) return; + e_gadcon_locked_set(inst->gcc->gadcon, EINA_FALSE); + inst->menu = NULL; +} + +static void _free_share_data(Share_Data *sd) +{ + free(sd->name); + free(sd->content); + free(sd->url); + free(sd); +} + +/* module setup */ +EAPI E_Module_Api e_modapi = { + E_MODULE_API_VERSION, + "Share" +}; + +EAPI void * +e_modapi_init (E_Module * m) +{ + share_module = m; + sourcedrop_init(); + e_gadcon_provider_register(&_gadcon_class); + return share_module; +} + +EAPI int +e_modapi_shutdown (E_Module * m) +{ + sourcedrop_shutdown(); + share_module = NULL; + e_gadcon_provider_unregister(&_gadcon_class); + return 1; +} + +EAPI int +e_modapi_save(E_Module * m) +{ + return 1; +} diff --git a/src/e_mod_main.h b/src/e_mod_main.h new file mode 100644 index 0000000..eabfb8d --- /dev/null +++ b/src/e_mod_main.h @@ -0,0 +1,12 @@ +#ifndef E_MOD_MAIN_H +#define E_MOD_MAIN_H + +#include + +EAPI extern E_Module_Api e_modapi; + +EAPI void *e_modapi_init (E_Module *m); +EAPI int e_modapi_shutdown (E_Module *m); +EAPI int e_modapi_save (E_Module *m); + +#endif diff --git a/src/e_share.h b/src/e_share.h new file mode 100644 index 0000000..8f2e8ae --- /dev/null +++ b/src/e_share.h @@ -0,0 +1,22 @@ +#ifndef __E_SHARE_H__ +#define __E_SHARE_H__ + +#undef LOG +#define LOG(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) + +typedef struct _Share_Data { + void *inst; + char *name; + char *content; + char *url; +} Share_Data; + +/* API used by submodules */ +void e_share_upload_completed(Share_Data *share_data); + +/* Submodules' functions */ +void sourcedrop_init(void); +void sourcedrop_share(Share_Data *share_data); +void sourcedrop_shutdown(void); + +#endif diff --git a/src/sourcedrop.c b/src/sourcedrop.c new file mode 100644 index 0000000..274457c --- /dev/null +++ b/src/sourcedrop.c @@ -0,0 +1,124 @@ +#include +#include +#include + +#include "e_mod_main.h" +#include "e_share.h" + +#define BUFFER_SIZE_MAX 4096 +#define DROPNAME "Sourcedrop" +#define SOURCEDROP_URL "http://www.sourcedrop.net/paste" +#define RECOGNITION_STRING "Location: " + +static Ecore_Event_Handler *handler = NULL; + +static Eina_Bool __upload_completed_cb(void *data, int type, Ecore_Con_Event_Url_Complete *ev); +static json_object *jarray, *jparent; + +/* Converts an integer value to its hex character*/ +char to_hex(const char code) { + static char hex[] = "0123456789abcdef"; + return hex[code & 15]; +} + +/* Returns a url-encoded version of str */ +/* IMPORTANT: be sure to free() the returned string after use */ +char *url_encode(const char *str) { + const char *pstr = str; + char *buf = malloc(strlen(str) * 3 + 1), *pbuf = buf; + while (*pstr) { + if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') + *pbuf++ = *pstr; + else if (*pstr == ' ') + *pbuf++ = '+'; + else + *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); + pstr++; + } + *pbuf = '\0'; + return buf; +} + +const char *json_encode(const char *title, const char *content) +{ + const char *encoded_string = NULL; + + jarray = json_object_new_array(); + jparent = json_object_new_object(); + json_object_object_add(jparent, "title", json_object_new_string(title)); + json_object_object_add(jparent, "content", json_object_new_string(content)); + json_object_array_add(jarray, jparent); + + encoded_string = json_object_to_json_string(jarray); + + return encoded_string; +} + +static Eina_Bool +__upload_completed_cb(void *data, int type, Ecore_Con_Event_Url_Complete *ev) +{ + char buf[BUFFER_SIZE_MAX]; + const Eina_List *headers = NULL, *it; + const char *header = NULL; + Share_Data *sd = NULL; + + json_object_put(jarray); + jarray = NULL; + json_object_put(jparent); + jparent = NULL; + + if (ev->status != 200) + { + LOG("E_SHARE/" DROPNAME ": This is not supposed to happen. The server returned status code: %d\n", ev->status); + return ECORE_CALLBACK_RENEW; + } + + headers = ecore_con_url_response_headers_get(ev->url_con); + EINA_LIST_FOREACH(headers, it, header) + { + if (strncmp(header, RECOGNITION_STRING, (sizeof(RECOGNITION_STRING) - 1)) == 0) + { + sd = ecore_con_url_data_get(ev->url_con); + strlcpy(buf, (char*)(header + sizeof(RECOGNITION_STRING) - 1), sizeof(buf)); + asprintf(&sd->url, "%s", buf); + e_share_upload_completed(sd); + ecore_con_url_free(ev->url_con); + break; + } + } + + return ECORE_CALLBACK_CANCEL; +} + +void sourcedrop_share(Share_Data *sd) +{ + Ecore_Con_Url *request_url = NULL; + char post_data[BUFFER_SIZE_MAX]; + const char *json_encoded = NULL; + char *url_encoded = NULL; + + EINA_SAFETY_ON_NULL_RETURN(sd); + + json_encoded = json_encode(sd->name, sd->content); + request_url = ecore_con_url_new(SOURCEDROP_URL); + url_encoded = url_encode(json_encoded); + snprintf(post_data, (sizeof(post_data) - 1), "data=%s", url_encoded); + free(url_encoded); + ecore_con_url_data_set(request_url, (void*)sd); + ecore_con_url_post(request_url, (void*)post_data, sizeof(post_data), "application/x-www-form-urlencoded"); +} + +void sourcedrop_init(void) +{ + ecore_init(); + ecore_con_init(); + handler = ecore_event_handler_add(ECORE_CON_EVENT_URL_COMPLETE, (Ecore_Event_Handler_Cb)__upload_completed_cb, NULL); +} + +void sourcedrop_shutdown(void) +{ + ecore_event_handler_del(handler); + handler = NULL; + ecore_con_shutdown(); + ecore_shutdown(); +}