damn raster... I had to do so I could check. dlopen -> in git.
server and libproxy.so wrapper, attached with the basics, I'm not doing all the cumbersome details to get a single process running and spawn it from libproxy.so wrapper without a race condition. I'd simplify all of that by using dbus with acquiring a name in the session bus and let that entity control it. Also would let the dbus daemon set isolation, like not inheriting current processes limits, namespaces and all. however as you dislike that, be my guest :-D gcc -o efl-proxy-resolver libproxy-efl-server.c `pkg-config --cflags --libs ecore eina efl eo libproxy-1.0` gcc -shared -o libproxy-efl.so libproxy-efl.c `pkg-config --cflags eina` -lpthread (note libproxy.so doesn't link with eina) EINA_LOG_LEVELS=efl-proxy-resolver:4 ./efl-proxy-resolver & LD_PRELOAD=libproxy-efl.so proxy http://google.com /usr/bin/proxy is a test tool provided by libproxy. the server should print it's serving requests. On Tue, Sep 20, 2016 at 11:59 AM, Gustavo Sverzut Barbieri <barbi...@gmail.com> wrote: > On Tue, Sep 20, 2016 at 1:54 AM, Carsten Haitzler <ras...@rasterman.com> > wrote: >> On Tue, 20 Sep 2016 01:08:48 -0300 Gustavo Sverzut Barbieri >> <barbi...@gmail.com> said: >> >>> On Mon, Sep 19, 2016 at 8:44 PM, Carsten Haitzler <ras...@rasterman.com> >>> wrote: >>> > On Mon, 19 Sep 2016 11:31:58 -0300 Gustavo Sverzut Barbieri >>> > <barbi...@gmail.com> said: >>> > >>> >> Hi all, >>> >> >>> >> Today I did commit support for libproxy in ecore_con's new API, >>> >> Efl.Net.Dialer.Tcp, Efl.Net.Dialer.Http and Efl.Net.Dialer.Websocket >>> >> (implicit from HTTP). >>> > >>> > ummm i just am looking now (i have a lot of catching up to do). can you >>> > undo >>> > the changes to configure.ac and make this a dlopen/dlsym approach? well >>> > use >>> > eina_module. look a the old curl eina_module approach. look at what i did >>> > in >>> > ecore_audio too. in fact i need to look over all the efl net code, but you >>> > want to use emile for ssl stuff and eina_module for curl too there too (i >>> > haven't looked yet). there are very good reasons to do all of this. >>> >>> I've checked what you did for curl, since I use that and need more symbols. >>> >>> my question and concern are just: >>> - not being able to compile without libproxy. If we always detect, >>> should we remove --enable-libproxy from configure.ac? >> >> yes. it moves to runtime not compile-time. several reasons: >> >> 1. makes compiling simpler. people dont have to find dependencies and those >> -dev/devel pkgs too. if they add libproxy later after building efl magically >> the feature works without a rebuild. >> 2. speed up startup time with less linking going on for apps especially for >> features they may never use >> 3. saves memory - symbol tables and dirtying private pages to do the symbol >> fixups is expensive. i nuked like 320k or so before 1.18 release of actual >> real memory usage that was dirty pages from rarely used libraries.features. >> it's expensive and so only load in if/when needed to save this cost. this >> cost >> is private pages PER PROCESS using efl so it multiples and is not a one-off >> cost. >> 4. the idea of isolating such costs into a proxy daemon/process probably is a >> good thing and that saves adding the above cost for any process then needing >> to >> do proxy pac file parsing etc. and isolates the cost into a single daemon. > > I was asking if we should offer a way to not even try runtime detection. > > >>> - I ask the above because libproxy is a monster, it will pull in a JS >>> loader, glib, C++... all of that, per process. If using pacrunner >>> (from connman guys) you can just link with libdbus, less bad. >> >> in GENERAL go for a dlopen(eina_module) style approach, BUT if a daemon makes >> sense, then do that. > > It's not something that excludes the other. Actually the opposite if > we do the proper way. > > - efl does libproxy dlopen, so doesn't link to that library (simple > API but heavy in dependency) > > - efl could recommend pacrunner's libproxy until we do our own, a big > improvement - zero work > > - efl could provide its own libproxy.so, like > /usr/lib/enlightenment/libproxy.so which would talk to our own server > (which can be built to use system's libproxy, which in turn can be the > official one or pacrunner's drop-in replacement). Our dlopen() can > prefer that version. > > - we can employ LD_PRELOAD tricks in Enlightenment to force loading > our libproxy.so instead of the system. Our daemon must be started > without it, of course :-) > > >>it makes more sense for us to do our own daemon for efl >> like efreet. dbus is not a good solution for this. > > agreed, the API is simple so a pure unix socket can do, protocol is: > > send: <int:url_length><char[]:url> > recv: > <int:count><int:proxy1_len><char[]:proxy1><int:proxy2_len><char[]:proxy2>.... > > as libproxy IS blocking and they explicitly recommend to be called > from a thread, our code will be simple send/recv calls, no other > libraries to pollute our users. > > > >> supporting pacrunner later >> of course is doable, but i would not do this out of the box because pacrunner >> is not everywhere, dbus session buses do not exist when you do things like >> use >> the console (terminology using fbcon is a major problem when it comes to >> ethumb >> and was for efreet because no session bus is active thus everything bound to >> a >> session bus is broken). i will eventually rewrite ethumb features likely in >> elm >> and drop ethumb because of such issues. also ethumb design is broken for >> SMACK >> systems so we really do need a "fork off a slave process that is custom for >> your app only" method of working. > > you know my opinion on this: this is a nasty approach. The dbus is > simple to achieve if you start that, as you do in E if there was no > session. Or use systemd, it should be doing that from pam_systemd. > > SMACK is a different issue and the design of Ethumb came from a time > where we expected all processes to share thumbnails, like efm, your > elm file selector, etc. Private thumbnails as you mentioned started to > gain traction later... application isolation as well, it began with > iOS/Android, linux desktop still is not there. > > >> for proxies i see the value of a single efl.net.proxy daemon service to serve >> as a proxy handler - send relevant info to this daemon, let it decide and >> then >> answer back with what connection to make. so basically take the libproxy >> stuff >> you just committed, move it to a daemon (like efreet does now), and connect >> to >> it (execute then connect if connect fails). if you don't, then i'll >> eventually >> need to do this. > > see my plan above, sounds progressive and we don't need to redo > anything. It will work right now, if pacrunner is there it will work > and if we ever create a daemon, we can provide a libproxy.so to be > LD_PRELOAD'ed on apps (pure libC shim). > > > >> keep this in mind as a design point. for data that is "global" (efreet style >> stuff, proxy info etc.) then a single daemon doing the work will be the best >> solution. maybe in future we might/can merge these daemons into a single "efl >> daemon". > > indeed makes sense. > > >>> > emile too needs to switch to using this approach internally as well. >>> > >>> >> Libproxy (https://libproxy.github.io) tries to dynamically find a >>> >> proxy for a given URL, unlike plain $http_proxy, $no_proxy... which >>> >> needs a mess to get right in complex environments such as big >>> >> corporations. It will also use PAC - ProxyAutoConfiguration, those >>> >> JavaScript like files named wpad.dat, as well as the wpad host on the >>> >> local network, where it tries to find the JS file. >>> >> >>> >> More than that, libproxy uses the configuration from Desktop >>> >> Environments. There are none for E (some volunteer?), but we can use >>> >> the one from Gnome3/Gnome or KDE. It will also handle envvars and some >>> >> /etc/sysconf configuration. >>> > >>> > what kind of config? you mean like a dbus interface exposed from e to >>> > provide proxy info? i haven't looked... >>> >>> whatever you want, like >>> https://github.com/libproxy/libproxy/tree/master/libproxy/modules >>> >>> they have config_envvars that check http_proxy, no_proxy... but they >>> also have one for gnome that uses gsettings. >> >> err so you mean write code into libproxy to get proxy settings from e? a >> quick >> look at the above implies that... is there a way of hooking into this e.g. >> from >> the above "efl proxy daemon"? actually i'm sure there is... > > from what I understand, you create a module ("mm_info_" symbol), see > https://github.com/libproxy/libproxy/blob/master/libproxy/modules/config_gnome.cpp > then you put it in /usr/lib/libproxy/${VERSION}/modules/. That module > can provide a function to say if the module should be used, like based > on envvars. > > > >> either way a single daemon would solve a lot of the above issues of libproxy >> pulling in half the universe into every app needing to do network stuff. > > agreed. BUT looking at ArchLinux packaging, well-known software does > link to that... glib-networking qt5-base vlc. Definitely we can and > should improve, but it's not a blocker. > > >>> - push distros to package pacrunner (glib + dbus + curl, very simple) >> >> we pushed connman years ago. not again. distros just don't want to. we need >> to >> DIY and provide our own "pacrunner". that's easy enough for us as we do it >> with >> efreetd already. we have lots of infra to do this easily. if we just have an >> eflnet "proxy handler" and efl.net spawns and talks to it we have a solution >> that doesn't require pacrunner to be packaged and so on (going by history of >> connman i just dont want to go here again), and it's TRIVIAL for us to >> support. >> you have there all the relevant code already. just needs to be put into its >> own >> binary, with an ipc/whatever conn for the user and some code to connect >> and/or >> launch it. > > doing this way will not help, just creates another technology just our > apps will use... and frankly that would benefit what? just terminology > to get previews, E to check updates? > > most of the other software doing network uses that libproxy, like I > said, qt/glib, vlc... thus all of those would be missed. We need a > solution that works for all of them, so the libproxy drop-in repl. > > also, doing the pacrunner ourselves brings what to the table? Almost > nothing other than work. libproxy already talks to neworkmanager. > pacrunner talks to connman. We'd have to also monitor these. We'd need > to pick a JS, expose functions, do testing... It's not as simple as it > looks like, having a main loop and few C helpers won't help much :-/ > > >>> AFAIU (still trying to get more details on the working), pacrunner >>> will interact with connman's proxy configuration... I need to stop and >>> read/ask which one is the controller. But that's the ideal situation, >>> we already do connman, set property there and it spreads to the whole >>> system. >> >> the problem is 99% of users dont want or use connman. after years i've seen >> that and i don't see that binding ourselves even deeper is a good idea. >> connman >> works fine. i use it personally. but it's just not palatable for distros or >> users. providing our own little proxy daemon process like efreetd would be a >> self-sustained solution that is GUARANTEED to be installed and work in every >> environment. > > as per above, it's not solving any problems, just adding more. > > > -- > Gustavo Sverzut Barbieri > -------------------------------------- > Mobile: +55 (16) 99354-9890 -- Gustavo Sverzut Barbieri -------------------------------------- Mobile: +55 (16) 99354-9890
#include <eina_types.h> /* not full eina, do not use EFL in this file, only EINA_UNUSED, EAPI... */ #include <limits.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <pthread.h> typedef struct pxProxyFactory_ pxProxyFactory; #ifndef MSG_MORE #define MSG_MORE 0 #endif EAPI pxProxyFactory * px_proxy_factory_new(void EINA_UNUSED) { return (void *)0x3f1; } EAPI void px_proxy_factory_free(pxProxyFactory *self EINA_UNUSED) { } /* * Protocol is: * * - request (string url) * - len = uint32_t (host order) as strlen(url) * - char[len] = contents of url, without null-byte * * - reply (array of proxies strings): * - count = uint32_t (host order) as elements in array * - for each entry in count: * - len = uint32_t (host order) as strlen(proxy) * - char[len] = contents of proxy, without null-byte */ static char ** _efl_libproxy_proxies_get(int fd, const char *url) { uint32_t len = strlen(url); uint32_t i, count; ssize_t r; char **proxies; r = send(fd, &len, sizeof(len), MSG_MORE | MSG_NOSIGNAL); if (r != (ssize_t)sizeof(len)) return NULL; r = send(fd, url, len, MSG_NOSIGNAL); if (r != (ssize_t)len) return NULL; r = recv(fd, &count, sizeof(count), 0); if (r != (ssize_t)sizeof(count)) return NULL; proxies = malloc(sizeof(char *) * (count + 1)); if (!proxies) return NULL; pthread_cleanup_push(free, proxies); for (i = 0; i < count; i++) { char *p; r = recv(fd, &len, sizeof(len), 0); if (r != (ssize_t)sizeof(len)) break; p = malloc(len + 1); if (!p) break; pthread_cleanup_push(free, p); r = recv(fd, p, len, 0); if (r != (ssize_t)len) { free(p); p = NULL; } else { p[len] = '\0'; } pthread_cleanup_pop(0); /* need p on success */ proxies[i] = p; } pthread_cleanup_pop(0); /* need proxies on success */ proxies[i] = NULL; return proxies; } static void _efl_libproxy_cleanup_socket(void *data) { int fd = *(int *)data; close(fd); } EAPI char ** px_proxy_factory_get_proxies(pxProxyFactory *self, const char *url) { struct sockaddr_un addr = {}; const char *env; char **proxies = NULL; int fd, r; if (!url) goto end; addr.sun_family = AF_UNIX; env = getenv("XDG_RUNTIME_DIR"); if (env) { r = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/efl/proxy-resolver", env); } else { env = getenv("TMPDIR"); if (!env) env = "/tmp"; r = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/efl-proxy-resolver-%u", env, getuid()); } if (r >= sizeof(addr.sun_path)) goto end; fd = socket(AF_UNIX, SOCK_STREAM #ifdef SOCK_CLOEXEC | SOCK_CLOEXEC #endif , 0); if (fd >= 0) { #if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC) fcntl(fd, F_SETFD, FD_CLOEXEC); #endif pthread_cleanup_push(_efl_libproxy_cleanup_socket, &fd); r = connect(fd, (const struct sockaddr *)&addr, sizeof(addr)); if (r == 0) proxies = _efl_libproxy_proxies_get(fd, url); pthread_cleanup_pop(1); } end: if (!proxies) { proxies = malloc(sizeof(char *) * 2); if (proxies) { proxies[0] = strdup("direct://"); proxies[1] = NULL; } } return proxies; }
#define EFL_BETA_API_SUPPORT 1 #define EFL_EO_API_SUPPORT 1 #include <Ecore.h> #include <limits.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <pthread.h> #include <proxy.h> static int _efl_libproxy_server_log_dom; #ifdef ERR # undef ERR #endif #define ERR(...) EINA_LOG_DOM_ERR(_efl_libproxy_server_log_dom, __VA_ARGS__) #ifdef DBG # undef DBG #endif #define DBG(...) EINA_LOG_DOM_DBG(_efl_libproxy_server_log_dom, __VA_ARGS__) #ifdef INF # undef INF #endif #define INF(...) EINA_LOG_DOM_INFO(_efl_libproxy_server_log_dom, __VA_ARGS__) #ifdef WRN # undef WRN #endif #define WRN(...) EINA_LOG_DOM_WARN(_efl_libproxy_server_log_dom, __VA_ARGS__) #ifdef CRI # undef CRI #endif #define CRI(...) EINA_LOG_DOM_CRIT(_efl_libproxy_server_log_dom, __VA_ARGS__) static Eo *_efl_libproxy_server_fdh = NULL; static pxProxyFactory *_efl_libproxy_server_pf = NULL; static struct sockaddr_un _efl_libproxy_server_addr = {}; static Eina_List *_efl_libproxy_server_threads = NULL; #ifndef MSG_MORE #defined MSG_MORE 0 #endif /* * Protocol is: * * - request (string url) * - len = uint32_t (host order) as strlen(url) * - char[len] = contents of url, without null-byte * * - reply (array of proxies strings): * - count = uint32_t (host order) as elements in array * - for each entry in count: * - len = uint32_t (host order) as strlen(proxy) * - char[len] = contents of proxy, without null-byte */ static void _efl_libproxy_server_proxies_send(int fd, char * const *proxies, uint32_t count) { uint32_t i; ssize_t r; r = send(fd, &count, sizeof(count), MSG_MORE | MSG_NOSIGNAL); if (r != (ssize_t)sizeof(count)) { DBG("could not send count: %s", strerror(errno)); return; } for (i = 0; i < count; i++) { int flags = MSG_NOSIGNAL | MSG_MORE;; uint32_t len = strlen(proxies[i]); r = send(fd, &len, sizeof(len), flags); if (r != (ssize_t)sizeof(len)) { DBG("could not send proxy[%u] length: %s", i, strerror(errno)); return; } if (i + 1 == count) flags &= ~MSG_MORE; r = send(fd, proxies[i], len, flags); if (r != (ssize_t)len) { DBG("could not send proxy[%u]: %s", i, strerror(errno)); return; } } } static void _efl_libproxy_server_cleanup_proxies(void *data) { char **proxies = data; char **itr; for (itr = proxies; *itr != NULL; itr++) free(*itr); free(proxies); } static void _efl_libproxy_server_thread_run(void *data, Ecore_Thread *t EINA_UNUSED) { int fd = (intptr_t)data; uint32_t len; ssize_t r; DBG("new connection fd=%d...", fd); eina_thread_cancellable_set(EINA_TRUE, NULL); r = recv(fd, &len, sizeof(len), 0); if (r != (ssize_t)sizeof(len)) DBG("could not recv url length, need=%zu, got=%zd: %s", sizeof(len), r, strerror(errno)); else { char *url = malloc(len + 1); if (url) { EINA_THREAD_CLEANUP_PUSH(free, url); r = recv(fd, url, len, 0); if (r != (ssize_t)len) DBG("could not recv url, need=%u, got=%zd: %s", len, r, strerror(errno)); else { char **proxies; url[len] = '\0'; eina_thread_cancellable_set(EINA_FALSE, NULL); DBG("query proxies for %s", url); proxies = px_proxy_factory_get_proxies(_efl_libproxy_server_pf, url); if (proxies) { uint32_t count = 0; while (proxies[count]) count++; EINA_THREAD_CLEANUP_PUSH(_efl_libproxy_server_cleanup_proxies, proxies); eina_thread_cancellable_set(EINA_TRUE, NULL); _efl_libproxy_server_proxies_send(fd, proxies, count); EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free proxies */ } } EINA_THREAD_CLEANUP_POP(EINA_TRUE); /* free(url) */ } } } static void _efl_libproxy_server_thread_end(void *data EINA_UNUSED, Ecore_Thread *t) { int fd = (intptr_t)data; _efl_libproxy_server_threads = eina_list_remove(_efl_libproxy_server_threads, t); if ((!_efl_libproxy_server_fdh) && (!_efl_libproxy_server_threads)) { px_proxy_factory_free(_efl_libproxy_server_pf); _efl_libproxy_server_pf = NULL; } close(fd); } static void _efl_libproxy_server_thread_cancel(void *data EINA_UNUSED, Ecore_Thread *t) { int fd = (intptr_t)data; _efl_libproxy_server_threads = eina_list_remove(_efl_libproxy_server_threads, t); if ((!_efl_libproxy_server_fdh) && (!_efl_libproxy_server_threads)) { px_proxy_factory_free(_efl_libproxy_server_pf); _efl_libproxy_server_pf = NULL; } close(fd); } static void _efl_libproxy_server_on_read(void *data EINA_UNUSED, const Efl_Event *event) { Ecore_Thread *t; int fd; fd = accept(efl_loop_fd_get(event->object), NULL, NULL); if (fd < 0) { ERR("could not accept(%d): %s", fd, strerror(errno)); return; } t = ecore_thread_run(_efl_libproxy_server_thread_run, _efl_libproxy_server_thread_end, _efl_libproxy_server_thread_cancel, (void *)(intptr_t)fd); if (!t) { ERR("could not create worker thread for fd=%d", fd); close(fd); return; } _efl_libproxy_server_threads = eina_list_append(_efl_libproxy_server_threads, t); } static Eina_Bool _efl_libproxy_server_start(void) { const char *env; int fd, r, flag; _efl_libproxy_server_addr.sun_family = AF_UNIX; env = getenv("XDG_RUNTIME_DIR"); if (env) { r = snprintf(_efl_libproxy_server_addr.sun_path, sizeof(_efl_libproxy_server_addr.sun_path), "%s/efl/proxy-resolver", env); if (r >= sizeof(_efl_libproxy_server_addr.sun_path)) { ERR("sun_path=%s/efl/proxy-resolver is too long!", env); return EINA_FALSE; } _efl_libproxy_server_addr.sun_path[r - sizeof("proxy-resolver")] = '\0'; if ((mkdir(_efl_libproxy_server_addr.sun_path, 0700) != 0) && (errno != EEXIST)) { ERR("could not create socket directory: %s", _efl_libproxy_server_addr.sun_path); return EINA_FALSE; } _efl_libproxy_server_addr.sun_path[r - sizeof("proxy-resolver")] = '/'; } else { env = getenv("TMPDIR"); if (!env) env = "/tmp"; r = snprintf(_efl_libproxy_server_addr.sun_path, sizeof(_efl_libproxy_server_addr.sun_path), "%s/efl-proxy-resolver-%u", env, getuid()); if (r >= sizeof(_efl_libproxy_server_addr.sun_path)) { ERR("sun_path=%s/efl-proxy-resolver-%u is too long!", env, getuid()); return EINA_FALSE; } } fd = socket(AF_UNIX, SOCK_STREAM #ifdef SOCK_CLOEXEC | SOCK_CLOEXEC #endif , 0); if (fd < 0) { ERR("could not create unix socket: %s", strerror(errno)); return EINA_FALSE; } #if !defined(SOCK_CLOEXEC) && defined(FD_CLOEXEC) r = fcntl(fd, F_SETFD, FD_CLOEXEC); if (r < 0) WRN("could not set FD_CLOEXEC on socket=%d", fd); #endif flag = 1; r = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); if (r < 0) { ERR("could not set SO_REUSEADDR on %d: %s", fd, strerror(errno)); goto error; } r = bind(fd, (struct sockaddr *)&_efl_libproxy_server_addr, sizeof(_efl_libproxy_server_addr)); if (r < 0) { ERR("could not bind %d to %s: %s", fd, _efl_libproxy_server_addr.sun_path, strerror(errno)); goto error; } r = listen(fd, 128); if (r < 0) { ERR("could not listen %d on %s: %s", fd, _efl_libproxy_server_addr.sun_path, strerror(errno)); goto error; } if (!_efl_libproxy_server_pf) { _efl_libproxy_server_pf = px_proxy_factory_new(); if (!_efl_libproxy_server_pf) { ERR("could not create proxy factory"); goto error; } } _efl_libproxy_server_fdh = efl_add(EFL_LOOP_FD_CLASS, ecore_main_loop_get(), efl_loop_fd_set(efl_added, fd), efl_event_callback_add(efl_added, EFL_LOOP_FD_EVENT_READ, _efl_libproxy_server_on_read, NULL)); if (!_efl_libproxy_server_fdh) { ERR("could not add efl_loop_fd object"); goto error_fdh; } DBG("waiting for proxy requests at %s", _efl_libproxy_server_addr.sun_path); return EINA_TRUE; error_fdh: px_proxy_factory_free(_efl_libproxy_server_pf); _efl_libproxy_server_pf = NULL; error: close(fd); return EINA_FALSE; } static void _efl_libproxy_server_stop(void) { if (_efl_libproxy_server_threads) { const Eina_List *l, *next; Ecore_Thread *t; DBG("%u threads are running.", eina_list_count(_efl_libproxy_server_threads)); EINA_LIST_FOREACH_SAFE(_efl_libproxy_server_threads, l, next, t) ecore_thread_cancel(t); } if (!_efl_libproxy_server_threads) { px_proxy_factory_free(_efl_libproxy_server_pf); _efl_libproxy_server_pf = NULL; } unlink(_efl_libproxy_server_addr.sun_path); close(efl_loop_fd_get(_efl_libproxy_server_fdh)); efl_del(_efl_libproxy_server_fdh); _efl_libproxy_server_fdh = NULL; } int main(void) { ecore_init(); _efl_libproxy_server_log_dom = eina_log_domain_register ("efl-proxy-resolver", EINA_COLOR_CYAN); if (_efl_libproxy_server_start()) { ecore_main_loop_begin(); _efl_libproxy_server_stop(); } ecore_shutdown(); return EXIT_SUCCESS; }
------------------------------------------------------------------------------
_______________________________________________ enlightenment-devel mailing list enlightenment-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/enlightenment-devel