[claws-mail-cvs-commit] Makefile.am

Holger Berndt <[email protected]> Tue, 15 Sep 2009 20:39:46 +0000
Newsgroups gmane.mail.sylpheed.claws.cvs
Message-ID <[email protected]>
Update of /srv/cvs/claws-mail/plugins/geolocation/src
In directory ran:/tmp/cvs-serv20339/src

Added Files:
      Tag: gtk2
	Makefile.am geolocation_plugin.c gettext.h pixmap_earth.h 
Log Message:
2009-09-15 [holger]	0.0.1cvs2

	* AUTHORS
	* COPYING
	* ChangeLog
	* INSTALL
	* Makefile.am
	* NEWS
	* README
	* autogen.sh
	* commitHelper
	* configure.ac
	* earth.svg
	* config/Makefile.am
	* config/config.rpath
	* po/Makefile.in.in
	* po/Makevars
	* po/POTFILES.in
	* po/remove-potcdate.sin
	* src/Makefile.am
	* src/geolocation_plugin.c
	* src/gettext.h
	* src/pixmap_earth.h
		Initial import, moving from github 

--- NEW FILE: pixmap_earth.h ---
/* GdkPixbuf RGBA C-Source image dump */

#ifdef __SUNPRO_C
#pragma align 4 (pixmap_earth)
#endif
#ifdef __GNUC__
static const guint8 pixmap_earth[] __attribute__ ((__aligned__ (4))) = 
#else
static const guint8 pixmap_earth[] = 
#endif
{ ""
  /* Pixbuf magic (0x47646b50) */
  "GdkP"
  /* length: header (24) + pixel_data (2304) */
  "\0\0\11\30"
  /* pixdata_type (0x1010002) */
  "\1\1\0\2"
  /* rowstride (96) */
  "\0\0\0`"
  /* width (24) */
  "\0\0\0\30"
  /* height (24) */
  "\0\0\0\30"
  /* pixel_data: */
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "99l\12""99m)99l077l\36++\200\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0""88k\15RR\200s\231\234\264\316\236\241\273\372"
  "\271\275\317\372\271\276\320\372\234\246\302\371\206\216\257\361_d\217"
  "\244:=oD\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0KM{W\227\233\266\351\331"
  "\340\350\377\311\327\347\377\356\360\361\377\376\376\376\377\353\354"
  "\355\377\253\302\332\377\245\301\337\377\244\277\335\377\227\250\306"
  "\374[b\215\270;;n\32\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0jl\222\202\324\325\336\375\302\313\324"
  "\377\305\314\324\377\307\322\335\377\350\354\357\377\313\322\330\377"
  "\271\306\323\377\273\314\337\377\264\314\346\377\306\325\344\377\327"
  "\332\335\377\315\322\331\377\207\213\244\344:=p.\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\\_\211l\343\344\352\375"
  "\267\310\332\377\337\353\366\377\325\332\337\377\341\357\376\377\322"
  "\335\351\377\331\350\370\377\320\342\364\377\305\331\356\377\273\316"
  "\341\377\320\327\336\377\264\276\311\377\360\361\361\377\347\347\347"
  "\377\210\214\243\343::m\30\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0@Bu)\306\310\326\371\376\376\376\377\360\362\363\377\364\364\364"
  "\377\327\332\335\377\323\336\351\377\342\361\377\377\335\355\374\377"
  "\323\345\367\377\310\331\354\377\307\316\326\377\355\357\360\377\371"
  "\372\372\377\370\370\370\377\346\346\346\377\334\334\335\377fi\212\262"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\211\213\250\255\376"
  "\376\376\377\376\376\376\377\376\376\376\377\340\343\346\377\323\337"
  "\353\377\333\351\367\377\341\360\377\377\337\356\375\377\324\346\370"
  "\377\315\335\356\377\345\347\352\377\321\325\333\377\355\356\360\377"
  "\372\372\372\377\352\352\352\377\330\330\330\377\262\264\274\3759<oA"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0""88n\33\304\305\324\373\376\376\376"
  "\377\376\376\376\377\320\325\333\377\325\345\365\377\337\357\377\377"
  "\337\357\377\377\340\357\377\377\335\355\375\377\322\345\367\377\315"
  "\334\353\377\303\313\323\377\266\310\334\377\241\265\313\377\242\265"
  "\312\377\323\326\332\377\330\330\330\377\321\321\321\377`c\200\247\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0BEvi\371\371\372\377\330\333\336\377\314"
  "\321\325\377\274\322\352\377\327\352\376\377\336\356\377\377\336\357"
  "\377\377\336\356\376\377\327\351\373\377\315\340\365\377\334\345\357"
  "\377\376\376\376\377\376\376\376\377\337\343\350\377\317\330\342\377"
  "\330\332\335\377\327\327\327\377\274\275\277\377\206\207\235\363@@j\3"
  "\0\0\0\0\0\0\0\0\0\0\0\0\200\201\240\216\323\327\333\377\240\277\337"
  "\377\254\310\346\377\274\326\362\377\306\336\367\377\325\350\373\377"
  "\330\352\373\377\324\347\372\377\315\341\366\377\314\334\356\377\376"
  "\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377\366\366"
  "\366\377\247\262\277\377\321\321\322\377\214\231\250\377t~\230\37199"
  "k\"\0\0\0\0\0\0\0\0\0\0\0\0ns\230\237\334\340\345\377\321\327\335\377"
  "\244\301\337\377\257\314\353\377\267\322\357\377\275\326\362\377\303"
  "\333\363\377\306\334\363\377\301\330\360\377\320\335\353\377\376\376"
  "\376\377\376\376\376\377\376\376\376\377\375\375\375\377\360\360\360"
  "\377\324\325\327\377\232\247\265\377\204\217\234\377ex\236\37388k7\0"
  "\0\0\0\0\0\0\0\0\0\0\0]c\215\227\241\275\334\377\232\262\314\377\254"
  "\273\313\377\335\345\354\377\323\342\361\377\251\306\345\377\254\312"
  "\351\377\251\307\347\377\246\304\345\377\247\300\333\377\367\367\370"
  "\377\376\376\376\377\376\376\376\377\367\367\367\377\350\350\350\377"
  "\330\330\330\377\262\265\271\377Pm\220\377dv\234\37288l/\0\0\0\0\0\0"
  "\0\0\0\0\0\0\77Ar}\242\275\333\377\201\247\321\377\241\273\327\377\376"
  "\376\376\377\376\376\376\377\320\327\340\377\234\274\337\377\231\273"
  "\337\377\224\267\334\377\214\261\330\377\230\262\317\377\206\241\275"
  "\377\251\271\314\377\354\354\354\377\336\336\336\377\317\317\317\377"
  "\251\254\257\377;^\213\377bn\224\37166l\17\0\0\0\0\0\0\0\0\0\0\0\0;="
  "pC\221\246\307\374s\235\312\377\310\327\351\377\376\376\376\377\376\376"
  "\376\377\376\376\376\377\345\351\355\377\245\275\327\377\204\253\323"
  "\377~\245\320\377v\236\313\377l\227\306\377v\231\300\377\340\340\340"
  "\377\322\322\322\377\304\304\304\377\\s\217\377Jj\225\377U]\206\324\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0..t\2r~\244\343t\234\310\377\232\262\315"
  "\377\367\367\367\377\373\373\373\377\375\375\375\377\375\375\375\377"
  "\256\274\313\377u\236\313\377o\231\310\377h\223\304\377_\214\277\377"
  "X\206\272\377\310\312\314\377\305\305\305\377\270\270\270\377Kd\204\377"
  "]t\230\377<>ov\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\77Cth\206\242"
  "\307\375`\215\277\377\255\273\312\377\352\352\352\377\355\355\355\377"
  "\357\357\357\377~\231\266\377e\221\302\377`\215\277\377Y\210\274\377"
  "Q\201\267\377L}\263\377\303\303\303\377\270\270\270\377\227\232\235\377"
  "Lc\203\377V`\207\351;;m\16\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\10"
  "\10\20\17V_\211\315v\234\305\377d\214\270\377\330\330\330\377\333\333"
  "\333\377\252\264\300\377W\201\256\377U\205\271\377Q\201\267\377J|\264"
  "\377Cv\257\377\77p\250\377\240\244\251\377\252\252\252\377bp\203\377"
  "`o\222\374/0Xm\0\0\0\12\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\0\0\0"
  "\32\24\25'I[f\220\346q\226\300\377\304\304\304\377\307\307\307\377Xz"
  "\240\377Hz\262\377Ex\261\377Bu\255\377\77p\250\377=k\242\377<f\231\377"
  "v\206\232\377\211\213\216\377eq\215\37546]\253\0\0\0""0\0\0\0\27\0\0"
  "\0\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\4\0\0\0\33\0\0\0""4\21\21\40aSY\177"
  "\336\265\267\273\376t\206\233\377=m\244\377>n\245\377=l\242\377=i\236"
  "\377<e\230\377;a\222\377;]\212\377Vl\215\377]h\214\375,.P\253\0\0\0J"
  "\0\0\0""1\0\0\0\30\0\0\0\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\0"
  "\0\0'\0\0\0>\2\2\5X)+K\252Ze\213\363g~\245\376b\202\252\377Xx\241\377"
  "Ts\234\377Yt\233\377_u\231\377^j\217\375@Dk\332\27\27,\205\0\0\0Q\0\0"
  "\0:\0\0\0#\0\0\0\13\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\15\0\0\0!\0\0\0""5\0\0\0G\2\2\5Z\35\36""9\215,-Q\262<@c\306@Dh\314"
  "68[\277%'H\250\20\20\40w\0\0\0U\0\0\0D\0\0\0""2\0\0\0\36\0\0\0\12\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\3"
  "\0\0\0\15\0\0\0\34\0\0\0(\0\0\0""3\0\0\0;\0\0\0\77\0\0\0A\0\0\0\77\0"
  "\0\0:\0\0\0""1\0\0\0'\0\0\0\31\0\0\0\13\0\0\0\2\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0\0\0\0\0\0\1\0\0\0\6\0\0\0\11\0\0\0\13\0\0\0\14\0\0\0\13\0\0\0\11\0"
  "\0\0\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
  "\0"};



--- NEW FILE: Makefile.am ---
plugindir = $(CLAWS_MAIL_PLUGINDIR)

plugin_LTLIBRARIES = geolocation_plugin.la

geolocation_plugin_la_SOURCES = \
	geolocation_plugin.c \
	gettext.h

geolocation_plugin_la_LDFLAGS = \
	-avoid-version -module \
	$(GTK_LIBS) \
	$(LIBXML_LIBS) \
	$(CHAMPLAIN_LIBS)


INCLUDES = \
	-I$(top_srcdir)/src \
	-I$(top_srcdir)/src/common \
	-I$(top_srcdir)/src/gtk

AM_CPPFLAGS = \
	-Wall \
	$(CLAWS_MAIL_CFLAGS) \
	$(GLIB_CFLAGS) \
	$(GTK_CFLAGS) \
	$(LIBXML_CFLAGS) \
	$(CHAMPLAIN_CFLAGS) \
	-DLOCALEDIR=\""$(localedir)"\"

--- NEW FILE: geolocation_plugin.c ---
/* GeoLocation plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include "pluginconfig.h"

#include <glib.h>

#include "pixmap_earth.h"

#include "common/plugin.h"
#include "common/version.h"
#include "common/utils.h"
#include "common/hooks.h"
#include "prefs_common.h"
#include "messageview.h"
#include "procheader.h"
#include "main.h"

#include "gettext.h"

#include <libxml/nanohttp.h>
#include <libxml/xpath.h>

#include <champlain/champlain.h>
#include <champlain-gtk/champlain-gtk.h>
#include <clutter-gtk/gtk-clutter-embed.h>

#include <string.h>


/* For now, make extracting the ip address string from the Received header
 * as simple as possible. This should be made more accurate, some day.
 * Logic:
 * Find four 1-3 digit numbers, separated by dots, encosed in word boundaries,
 * where the word "from" must be ahead, and the word "by" must follow, but must
 * not be between the "from" and the digits.
 * The result will be in backreference 2, the individual numbers in 3 to 6 */
#define EXTRACT_IP_FROM_RECEIVED_HEADER_REGEX "\\bfrom\\b((?!\\bby\\b).)*(\\b(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\b).*\\bby\\b"

#define EXTRACT_LAT_LON_FROM_HOSTIP_RESPONSE_REGEX "<gml:coordinates>(-?[\\d.]+),(-?[\\d.]+)</gml:coordinates>"

#define GEOLOCATION_CONTAINER_NAME "cm_geolocation_container"
#define GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON "togglebutton"
#define GEOLOCATION_CONTAINER_DATA_LABEL "label"

#define GEOLOCATION_TOGGLE_BUTTON_DATA_CB_ID "toggled_cb_id"
#define GEOLOCATION_TOGGLE_BUTTON_DATA_IP "ip"

#define GEOLOCATION_NOTEBOOK_DATA_MAPWIDGET "mapwidget"

#define GEOLOCATION_MAP_DATA_VIEW "view"
#define GEOLOCATION_MAP_DATA_ORIGIN_MARKER "originmarker"

#define BUFFSIZE 8192

#define HOSTIP_URL "http://api.hostip.info/"

static gboolean my_messageview_show_hook(gpointer, gpointer);
static guint hook_messageview_show;

static GRegex *ip_from_received_header_regex = NULL;
static GRegex *lat_lon_from_hostip_response_regex = NULL;

static GSList *container_list = NULL;


static gchar* get_ip_from_received_header(gchar *received)
{
  GMatchInfo *match_info;
  gchar *ip;
  gchar *n1;

  g_regex_match(ip_from_received_header_regex, received, 0, &match_info);
  ip = g_match_info_fetch(match_info, 2);

  if (ip) {
    /* check for loopback and local subnetworks */
    if(!strcmp(ip, "127.0.0.1")) {
      g_free(ip);
      ip = NULL;
    }
    else {
      n1 = g_match_info_fetch(match_info, 3);
      if(n1) {
        /* 10.x.x.x */
        if(!strcmp(n1, "10")) {
          g_free(ip);
          ip = NULL;
        }
        /* 192.168.x.x */
        else if(!strcmp(n1, "192")) {
          gchar *n2;
          n2 = g_match_info_fetch(match_info, 4);
          if(n2 && !strcmp(n2, "168")) {
            g_free(ip);
            ip = NULL;
          }
        }
        /* 172.16.xx to 172.31.x.x */
        else if(!strcmp(n1, "172")) {
          gchar *n2;
          int i2;
          n2 = g_match_info_fetch(match_info, 4);
          if(n2) {
            i2 = atoi(n2);
            if((i2 >= 16) && (i2 <= 31)) {
              g_free(ip);
              ip = NULL;
            }
          }
        }
      }
    }
  }

  g_match_info_free(match_info);
  return ip;
}

enum {
  H_RECEIVED = 0
};

static HeaderEntry hentry[] = {
    {"Received:", NULL, TRUE},
    {NULL, NULL, FALSE}};

static gchar* get_ip_from_msginfo(MsgInfo *msginfo)
{
  gchar *file;
  struct stat ss;
  FILE *fp;
  gchar buf[BUFFSIZE];
  gint hnum;
  gchar *hp;
  gchar *ip;
  gchar *new_ip;

  file = procmsg_get_message_file_path(msginfo);
  g_return_val_if_fail(file, NULL);

  if(g_stat(file, &ss) < 0) {
    FILE_OP_ERROR(file, "stat");
    return NULL;
  }
  if(!S_ISREG(ss.st_mode)) {
    g_warning("mail file is not a regular file\n");
    return NULL;
  }

  if((fp = g_fopen(file, "rb")) == NULL) {
    FILE_OP_ERROR(file, "fopen");
    return NULL;
  }

  ip = NULL;
  new_ip = NULL;
  while((hnum = procheader_get_one_field(buf, sizeof(buf), fp, hentry)) != -1) {
    switch(hnum) {
    case H_RECEIVED:
      hp = buf + strlen(hentry[hnum].name);
      while(*hp == ' ' || *hp == '\t') hp++;
      new_ip = get_ip_from_received_header(hp);
      if(new_ip) {
        g_free(ip);
        ip = new_ip;
      }
      break;
    default:
      break;
    }
  }

  fclose(fp);

  g_free(file);
  return ip;
}

/* inspired by GeoClue */
static gboolean get_lat_lon_from_ip(const gchar *ip, double *lat, double *lon)
{
  gchar *url;
  void* ctxt = NULL;
  xmlBuffer *output;
  gint len;
  guchar *response;
  gint response_length;
  xmlChar buf[1024];
  GMatchInfo *match_info;
  gchar *slat;
  gchar *slon;

  /* fetch data from hostip.info */
  url = g_strdup_printf("%s?ip=%s", HOSTIP_URL, ip);
  xmlNanoHTTPInit();
  ctxt = xmlNanoHTTPMethod(url, "GET", NULL, NULL, NULL, 0);
  g_free(url);
  if(!ctxt) {
    g_print("xmlNanoHTTPMethod did not get a response from %s\n", url);
    return FALSE;
  }

  output = xmlBufferCreate();
  while((len = xmlNanoHTTPRead(ctxt, buf, sizeof(buf))) > 0) {
    if(xmlBufferAdd(output, buf, len) != 0) {
      xmlNanoHTTPClose(ctxt);
      xmlBufferFree(output);
      g_print("xmlBufferAdd failed\n");
      return FALSE;
    }
  }
  xmlNanoHTTPClose(ctxt);

  response_length = xmlBufferLength(output);
  response = g_malloc(response_length+1);
  memcpy(response, xmlBufferContent(output), response_length);
  response[response_length] = '\0';
  xmlBufferFree(output);

  /* extract longitude and latitude from */
  g_regex_match(lat_lon_from_hostip_response_regex, response, 0, &match_info);
  slon = g_match_info_fetch(match_info, 1);
  slat = g_match_info_fetch(match_info, 2);
  g_match_info_free(match_info);

  if(!slat || !slon)
    return FALSE;

  *lat = g_ascii_strtod(slat, NULL);
  *lon = g_ascii_strtod(slon, NULL);

  return TRUE;
}

static GtkWidget* create_map_widget()
{
  GtkWidget *map;
  ChamplainLayer *layer;
  ChamplainView *view;
  ClutterActor *origin_marker;
  ClutterColor orange = { 0xf3, 0x94, 0x07, 0xbb };

  /* create map widget */
  map = gtk_champlain_embed_new();
  view = gtk_champlain_embed_get_view(GTK_CHAMPLAIN_EMBED(map));
  g_object_set(G_OBJECT(view), "scroll-mode", CHAMPLAIN_SCROLL_MODE_KINETIC, "zoom-level", 8, NULL);

  /* mail origin marker */
  layer = champlain_layer_new();
  origin_marker = champlain_marker_new_with_text("mail origin", "Serif 14", NULL, NULL);
  champlain_marker_set_color(CHAMPLAIN_MARKER(origin_marker), &orange);
  clutter_container_add(CLUTTER_CONTAINER(layer), origin_marker, NULL);
  champlain_view_add_layer(view, layer);

  clutter_actor_show(CLUTTER_ACTOR(view));
  clutter_actor_show(CLUTTER_ACTOR(layer));
  clutter_actor_show(origin_marker);

  g_object_set_data(G_OBJECT(map), GEOLOCATION_MAP_DATA_VIEW, view);
  g_object_set_data(G_OBJECT(map), GEOLOCATION_MAP_DATA_ORIGIN_MARKER, origin_marker);

  return map;
}

static int get_notebook_map_page_num(GtkWidget *notebook)
{
  GtkWidget *wid;
  int page_num = 0;

  wid = g_object_get_data(G_OBJECT(notebook), GEOLOCATION_NOTEBOOK_DATA_MAPWIDGET);

  if(!wid) {
    /* create page */
    GtkWidget *map;
    map = create_map_widget();
    page_num = gtk_notebook_append_page(GTK_NOTEBOOK(notebook), map, NULL);
    gtk_widget_show_all(map);
    if(page_num == -1)
      page_num = 0;
    else
      g_object_set_data(G_OBJECT(notebook), GEOLOCATION_NOTEBOOK_DATA_MAPWIDGET, map);
  }
  else {
    /* find page */
    page_num = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), wid);
    if(page_num == -1)
      page_num = 0;
  }

  return page_num;
}

static void geolocation_button_toggled_cb(GtkToggleButton *button, gpointer data)
{
  if(gtk_toggle_button_get_active(button)) {
    gchar *ip;
    double lat, lon;
    gchar *text = NULL;
    GtkWidget *container, *label;

    ip = g_object_get_data(G_OBJECT(button), GEOLOCATION_TOGGLE_BUTTON_DATA_IP);
    container = gtk_widget_get_parent(GTK_WIDGET(button));
    g_return_if_fail(container);
    label = g_object_get_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_LABEL);
    g_return_if_fail(label);

    if(ip && get_lat_lon_from_ip(ip, &lat, &lon)) {
      GtkWidget *notebook;
      GtkWidget *map;
      MessageView *messageview;
      ClutterActor *view;
      ClutterActor *origin_marker;
      gint page_num;

      /* TRANSLATORS: The two numbers are latitude and longitude coordinates */
      text = g_strdup_printf(_("Found location: (%.2f,%.2f)"), lat, lon);
      gtk_label_set_text(GTK_LABEL(label), text);
      debug_print("%s\n", text);

      /* switch to map widget, and center on found location */
      messageview = data;
      notebook = messageview->mimeview->mime_notebook;
      page_num = get_notebook_map_page_num(notebook);
      map = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), page_num);
      view = g_object_get_data(G_OBJECT(map), GEOLOCATION_MAP_DATA_VIEW);
      origin_marker = g_object_get_data(G_OBJECT(map), GEOLOCATION_MAP_DATA_ORIGIN_MARKER);
      champlain_view_center_on(CHAMPLAIN_VIEW(view), lat, lon);
      champlain_base_marker_set_position(CHAMPLAIN_BASE_MARKER(origin_marker), lat, lon);
      gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), page_num);
    }
    else {
      /* TRANSLATORS: The IP address is appended to the string */
      text = g_strconcat(_("Could not resolve location of IP address "), ip, NULL);
      gtk_label_set_text(GTK_LABEL(label), text);
      debug_print("%s\n", text);

      gtk_toggle_button_set_active(button, FALSE);
    }
    g_free(text);
  }
  else {
    /* swich to first notebook page */
    MessageView *messageview;
    messageview = data;
    gtk_notebook_set_current_page(GTK_NOTEBOOK(messageview->mimeview->mime_notebook), 0);
  }
}

static void container_finalize_cb(gpointer data, GObject *where_the_object_was)
{
  container_list = g_slist_remove(container_list, where_the_object_was);
}

static GtkWidget* get_geolocation_container_from_messageview(MessageView *messageview)
{
  GtkWidget *container = NULL;
  GtkWidget *vbox;
  GList *children, *walk;
  GtkWidget *button;
  gulong *cb_id;

  g_return_val_if_fail(messageview && messageview->mimeview && messageview->mimeview->paned, NULL);

  vbox = gtk_paned_get_child2(GTK_PANED(messageview->mimeview->paned));

  /* check if there is already a geolocation container */
  children = gtk_container_get_children(GTK_CONTAINER(vbox));
  for(walk = children; walk; walk = walk->next) {
    GtkWidget *child = walk->data;
    if(!strcmp(gtk_widget_get_name(child), GEOLOCATION_CONTAINER_NAME)) {
      container = child;
      break;
    }
  }
  g_list_free(children);

  /* if not, create one */
  if(!container) {
    GtkWidget *label;
    GtkWidget *image;
    GdkPixbuf *pixbuf, *scaled_pixbuf;

    container = gtk_hbox_new(FALSE, 0);
    gtk_widget_set_name(container, GEOLOCATION_CONTAINER_NAME);

    /* button */
    pixbuf = gdk_pixbuf_new_from_inline(-1, pixmap_earth, FALSE, NULL);
    scaled_pixbuf = gdk_pixbuf_scale_simple(pixbuf, 16, 16, GDK_INTERP_BILINEAR);
    g_object_unref(pixbuf);
    image = gtk_image_new_from_pixbuf(scaled_pixbuf);
    gtk_widget_show(image);
    g_object_unref(scaled_pixbuf);
    button = gtk_toggle_button_new();
    gtk_widget_show(button);
    gtk_container_add(GTK_CONTAINER(button), image);
    gtk_box_pack_start(GTK_BOX(container), button, FALSE, FALSE, 0);
    g_object_set_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON, button);

    /* label */
    label = gtk_label_new("");
    gtk_widget_show(label);
    gtk_box_pack_start(GTK_BOX(container), label, FALSE, FALSE, 0);
    g_object_set_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_LABEL, label);

    gtk_box_pack_start(GTK_BOX(vbox), container, FALSE, FALSE, 0);

    /* add new container to list */
    container_list = g_slist_prepend(container_list, container);
    g_object_weak_ref(G_OBJECT(container), container_finalize_cb, NULL);
  }
  /* if it existed, changed the button callback data */
  else {
    /* disconnect old signal handler from button */
    gulong *id;
    button = g_object_get_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON);
    g_return_val_if_fail(button, NULL);
    id = g_object_get_data(G_OBJECT(button), GEOLOCATION_TOGGLE_BUTTON_DATA_CB_ID);
    g_signal_handler_disconnect(button, *id);
  }

  /* connect new signal handler to button, and store its id */
  cb_id = g_new(gulong, 1);
  *cb_id = g_signal_connect(button, "toggled", G_CALLBACK(geolocation_button_toggled_cb), messageview);
  g_object_set_data_full(G_OBJECT(button), GEOLOCATION_TOGGLE_BUTTON_DATA_CB_ID, cb_id, g_free);

  return container;
}

static gboolean my_messageview_show_hook(gpointer source, gpointer data)
{
  MessageView *messageview;
  GtkWidget *container;
  GtkWidget *button;
  gchar *ip;

  messageview = source;

  g_return_val_if_fail(messageview, FALSE);

  container = get_geolocation_container_from_messageview(messageview);
  g_return_val_if_fail(container, FALSE);

  /* make sure toggle button is turned off */
  button = g_object_get_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON);
  g_return_val_if_fail(button, FALSE);
  if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
  }

  /* do nothing except hiding the button if claws mail is in offline mode */
  if(prefs_common.work_offline) {
    gtk_widget_hide(container);
    return FALSE;
  }

  /* try to get ip address from message */
  if(!messageview->msginfo) {
    debug_print("Warning: Messageview does not have a MsgInfo\n");
    return FALSE;
  }
  ip = get_ip_from_msginfo(messageview->msginfo);
  if(!ip) {
    debug_print("Could not get IP address from message\n");
    gtk_widget_hide(container);
  }
  else {
    GtkWidget *label;
    debug_print("Found sender ip address: %s\n", ip);
    label = g_object_get_data(G_OBJECT(container), GEOLOCATION_CONTAINER_DATA_LABEL);
    if(label) {
      gchar *text;
      text = g_strconcat(_("Try to locate sender"), NULL);
      gtk_label_set_text(GTK_LABEL(label), text);
      g_free(text);
    }
    /* button takes ownership of ip string */
    g_object_set_data_full(G_OBJECT(button), GEOLOCATION_TOGGLE_BUTTON_DATA_IP, ip, g_free);
    gtk_widget_show(container);
  }

  return FALSE;
}

gint plugin_init(gchar **error)
{
  GError *err = NULL;

#ifdef G_OS_UNIX
	bindtextdomain(TEXTDOMAIN, LOCALEDIR);
#else
	bindtextdomain(TEXTDOMAIN, get_locale_dir());
#endif
  bind_textdomain_codeset(TEXTDOMAIN, "UTF-8");

  /* Version check */
  if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,1,55),
			   VERSION_NUMERIC, _("GeoLocation"), error))
    return -1;

  ip_from_received_header_regex = g_regex_new(EXTRACT_IP_FROM_RECEIVED_HEADER_REGEX, 0, 0, &err);
  if(err) {
    *error = g_strdup_printf(_("Could not create regular expression: %s\n"), err->message);
    g_error_free(err);
    return -1;
  }

  lat_lon_from_hostip_response_regex = g_regex_new(EXTRACT_LAT_LON_FROM_HOSTIP_RESPONSE_REGEX, 0, 0, &err);
  if(err) {
    *error = g_strdup_printf(_("Could not create regular expression: %s\n"), err->message);
    g_error_free(err);
    g_regex_unref(ip_from_received_header_regex);
    return -1;
  }

  hook_messageview_show = hooks_register_hook(MESSAGE_VIEW_SHOW_DONE_HOOKLIST, my_messageview_show_hook, NULL);
  if(hook_messageview_show == (guint) -1) {
    *error = g_strdup(_("Failed to register messageview_show hook in the GeoLocation plugin"));
    return -1;
  }

  gtk_clutter_init(0, NULL);

  debug_print("GeoLocation plugin loaded\n");

  return 0;
}

gboolean plugin_done(void)
{

  hooks_unregister_hook(MESSAGE_VIEW_SHOW_DONE_HOOKLIST, hook_messageview_show);

  if(!claws_is_exiting()) {
    GSList *copy, *walk;

    copy = g_slist_copy(container_list);
    for(walk = copy; walk; walk = walk->next) {
      GtkWidget *wid = walk->data;
      GtkWidget *button = g_object_get_data(G_OBJECT(wid), GEOLOCATION_CONTAINER_DATA_TOGGLEBUTTON);
      if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
      gtk_widget_destroy(GTK_WIDGET(wid));
    }
    g_slist_free(copy);
    if(container_list) {
      g_slist_free(container_list);
      container_list = NULL;
    }

    if(ip_from_received_header_regex) {
      g_regex_unref(ip_from_received_header_regex);
      ip_from_received_header_regex = NULL;
    }

    if(lat_lon_from_hostip_response_regex) {
      g_regex_unref(lat_lon_from_hostip_response_regex);
      lat_lon_from_hostip_response_regex = NULL;
    }
  }

  debug_print("GeoLocation plugin unloaded\n");

  /* returning FALSE because dependant libraries may not be unload-safe. */
  return FALSE;
}

const gchar *plugin_name(void)
{
  return _("GeoLocation");
}

const gchar *plugin_desc(void)
{
  return _("This plugin provides GeoLocation functionality "
	   "for Claws Mail.\n\n"
	   "Warning: It is technically impossible to derive the geographic "
	   "location of senders from their E-Mails with any amount of certainty. "
	   "The results presented by "
	   "this plugin are only rough estimates. In particular, mailing list managers "
	   "often strip sender information from the mails, so mails from mailing lists "
	   "may be assigned to the location of the mailing list server instead of the "
	   "mail sender.\n"
	   "When in doubt, don't trust the results of this plugin, and don't rely on this "
	   "information to divorce your spouse.\n"
           "\nFeedback to <[email protected]> is welcome "
	   "(but only if it's not about marital quarrels).");
}

const gchar *plugin_type(void)
{
  return "GTK2";
}

const gchar *plugin_licence(void)
{
  return "GPL3+";
}

const gchar *plugin_version(void)
{
  return PLUGINVERSION;
}

struct PluginFeature *plugin_provides(void)
{
  static struct PluginFeature features[] =
    { {PLUGIN_UTILITY, N_("GeoLocation integration")},
      {PLUGIN_NOTHING, NULL}};
  return features;
}

--- NEW FILE: gettext.h ---
/* Convenience header for conditional use of GNU <libintl.h>.
   Copyright (C) 1995-1998, 2000-2002, 2004 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify it
   under the terms of the GNU Library General Public License as published
   by the Free Software Foundation; either version 2, 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library 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.  */

#ifndef _LIBGETTEXT_H
#define _LIBGETTEXT_H 1

/* NLS can be disabled through the configure --disable-nls option.  */
#if ENABLE_NLS

/* Get declarations of GNU message catalog functions.  */
# include <libintl.h>

#define _(str)		dgettext(TEXTDOMAIN, str)

#else

/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
   chokes if dcgettext is defined as a macro.  So include it now, to make
   later inclusions of <locale.h> a NOP.  We don't include <libintl.h>
   as well because people using "gettext.h" will not include <libintl.h>,
   and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
   is OK.  */
#if defined(__sun)
# include <locale.h>
#endif

/* Many header files from the libstdc++ coming with g++ 3.3 or newer include
   <libintl.h>, which chokes if dcgettext is defined as a macro.  So include
   it now, to make later inclusions of <libintl.h> a NOP.  */
#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3)
# include <cstdlib>
# if (__GLIBC__ >= 2) || _GLIBCXX_HAVE_LIBINTL_H
#  include <libintl.h>
# endif
#endif

/* Disabled NLS.
   The casts to 'const char *' serve the purpose of producing warnings
   for invalid uses of the value returned from these functions.
   On pre-ANSI systems without 'const', the config.h file is supposed to
   contain "#define const".  */
# define gettext(Msgid) ((const char *) (Msgid))
# define dgettext(Domainname, Msgid) ((const char *) (Msgid))
# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid))
# define ngettext(Msgid1, Msgid2, N) \
    ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
# define dngettext(Domainname, Msgid1, Msgid2, N) \
    ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
    ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
# define textdomain(Domainname) ((const char *) (Domainname))
# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname))
# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset))

#define _(str)		str

#endif

/* A pseudo function call that serves as a marker for the automated
   extraction of messages, but does not call gettext().  The run-time
   translation is done at a different place in the code.
   The argument, String, should be a literal string.  Concatenated strings
   and other string expressions won't work.
   The macro's expansion is not parenthesized, so that it is suitable as
   initializer for static 'char[]' or 'const char[]' variables.  */
#define gettext_noop(String) String

#define N_(str)		str

#endif /* _LIBGETTEXT_H */