/*
 * FixScript GUI v0.4 - http://www.fixscript.org/
 * Copyright (c) 2019-2021 Martin Dvorak <jezek2@advel.cz>
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose, 
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <dlfcn.h>
#include "fixgui_common.h"

typedef enum {
   G_CONNECT_AFTER   = 1 << 0,
   G_CONNECT_SWAPPED = 1 << 1
} GConnectFlags;

typedef enum {
   CAIRO_FORMAT_INVALID   = -1,
   CAIRO_FORMAT_ARGB32    = 0,
   CAIRO_FORMAT_RGB24     = 1,
   CAIRO_FORMAT_A8        = 2,
   CAIRO_FORMAT_A1        = 3,
   CAIRO_FORMAT_RGB16_565 = 4,
   CAIRO_FORMAT_RGB30     = 5
} cairo_format_t;

typedef enum {
   CAIRO_FONT_SLANT_NORMAL,
   CAIRO_FONT_SLANT_ITALIC,
   CAIRO_FONT_SLANT_OBLIQUE
} cairo_font_slant_t;

typedef enum {
   CAIRO_FONT_WEIGHT_NORMAL,
   CAIRO_FONT_WEIGHT_BOLD
} cairo_font_weight_t;

typedef struct {
   double ascent;
   double descent;
   double height;
   double max_x_advance;
   double max_y_advance;
} cairo_font_extents_t;

typedef struct {
   double x_bearing;
   double y_bearing;
   double width;
   double height;
   double x_advance;
   double y_advance;
} cairo_text_extents_t;

typedef struct PangoContext PangoContext;
typedef struct PangoFontFamily PangoFontFamily; 

typedef enum {
   GTK_WINDOW_TOPLEVEL,
   GTK_WINDOW_POPUP
} GtkWindowType;

typedef enum {
   GDK_NOTHING           = -1,
   GDK_DELETE            = 0,
   GDK_DESTROY           = 1,
   GDK_EXPOSE            = 2,
   GDK_MOTION_NOTIFY     = 3,
   GDK_BUTTON_PRESS      = 4,
   GDK_2BUTTON_PRESS     = 5,
   GDK_3BUTTON_PRESS     = 6,
   GDK_BUTTON_RELEASE    = 7,
   GDK_KEY_PRESS         = 8,
   GDK_KEY_RELEASE       = 9,
   GDK_ENTER_NOTIFY      = 10,
   GDK_LEAVE_NOTIFY      = 11,
   GDK_FOCUS_CHANGE      = 12,
   GDK_CONFIGURE         = 13,
   GDK_MAP               = 14,
   GDK_UNMAP             = 15,
   GDK_PROPERTY_NOTIFY   = 16,
   GDK_SELECTION_CLEAR   = 17,
   GDK_SELECTION_REQUEST = 18,
   GDK_SELECTION_NOTIFY  = 19,
   GDK_PROXIMITY_IN      = 20,
   GDK_PROXIMITY_OUT     = 21,
   GDK_DRAG_ENTER        = 22,
   GDK_DRAG_LEAVE        = 23,
   GDK_DRAG_MOTION       = 24,
   GDK_DRAG_STATUS       = 25,
   GDK_DROP_START        = 26,
   GDK_DROP_FINISHED     = 27,
   GDK_CLIENT_EVENT      = 28,
   GDK_VISIBILITY_NOTIFY = 29,
   GDK_NO_EXPOSE         = 30,
   GDK_SCROLL            = 31,
   GDK_WINDOW_STATE      = 32,
   GDK_SETTING           = 33
} GdkEventType;

typedef enum {
   GTK_POLICY_ALWAYS,
   GTK_POLICY_AUTOMATIC,
   GTK_POLICY_NEVER
} GtkPolicyType;

typedef enum {
   GDK_HINT_POS         = 1 << 0,
   GDK_HINT_MIN_SIZE    = 1 << 1,
   GDK_HINT_MAX_SIZE    = 1 << 2,
   GDK_HINT_BASE_SIZE   = 1 << 3,
   GDK_HINT_ASPECT      = 1 << 4,
   GDK_HINT_RESIZE_INC  = 1 << 5,
   GDK_HINT_WIN_GRAVITY = 1 << 6,
   GDK_HINT_USER_POS    = 1 << 7,
   GDK_HINT_USER_SIZE   = 1 << 8
} GdkWindowHints;

typedef enum {
   GDK_GRAVITY_NORTH_WEST = 1,
   GDK_GRAVITY_NORTH,
   GDK_GRAVITY_NORTH_EAST,
   GDK_GRAVITY_WEST,
   GDK_GRAVITY_CENTER,
   GDK_GRAVITY_EAST,
   GDK_GRAVITY_SOUTH_WEST,
   GDK_GRAVITY_SOUTH,
   GDK_GRAVITY_SOUTH_EAST,
   GDK_GRAVITY_STATIC
} GdkGravity; 

typedef char gchar;
typedef short gshort;
typedef long glong;
typedef int gint;
typedef int8_t gint8;
typedef gint gboolean;
typedef unsigned char guchar;
typedef unsigned short gushort;
typedef unsigned long gulong;
typedef unsigned int guint;
typedef float gfloat;
typedef double gdouble;
typedef void *gpointer;
typedef void *GCallback;
typedef void *GClosureNotify;
typedef void *GdkWindow;
typedef void *GdkRegion;

typedef struct {
   void *class;
   guint ref_count;
   void *qdata;
} GObject;

typedef struct {
   gint x;
   gint y;
   gint width;
   gint height;
} GdkRectangle;

typedef struct {
   GdkEventType type;
   GdkWindow *window;
   gint8 send_event;
   GdkRectangle area;
   GdkRegion *region;
   gint count;
} GdkEventExpose;

typedef struct {
   GdkEventType type;
   GdkWindow *window;
   gint8 send_event;
   gint x, y;
   gint width;
   gint height;
} GdkEventConfigure;

typedef union {
   GdkEventType type;
   GdkEventExpose expose;
   GdkEventConfigure configure;
} GdkEvent;

typedef struct {
   gint min_width;
   gint min_height;
   gint max_width;
   gint max_height;
   gint base_width;
   gint base_height;
   gint width_inc;
   gint height_inc;
   gdouble min_aspect;
   gdouble max_aspect;
   GdkGravity win_gravity;
} GdkGeometry;

typedef struct cairo_t cairo_t;
typedef struct cairo_surface_t cairo_surface_t;
typedef struct cairo_pattern_t cairo_pattern_t;
typedef struct cairo_font_face_t cairo_font_face_t;

typedef struct {
   GObject parent_instance;
   uint32_t flags;
} GtkObject;

typedef struct {
   gint width;
   gint height;
} GtkRequisition;

typedef GdkRectangle GtkAllocation;

typedef struct GtkWidget {
   GtkObject object;
   uint16_t private_flags;
   uint8_t state;
   uint8_t saved_state;
   char *name;
   void *style;
   GtkRequisition requisition;
   GtkAllocation allocation;
   void *window;
   struct GtkWidget *parent;
} GtkWidget;

typedef struct {
   GtkWidget widget;
} GtkWindow;

typedef struct {
   GtkWidget widget;
} GtkFixed;

typedef struct {
   GtkWidget widget;
} GtkContainer;

typedef struct {
   GtkWidget widget;
} GtkButton;

typedef struct {
   GtkObject parent_instance;
   gdouble lower;
   gdouble upper;
   gdouble value;
   gdouble step_increment;
   gdouble page_increment;
   gdouble page_size;
} GtkAdjustment;

typedef struct {
   GtkWidget widget;
} GtkScrolledWindow;

typedef struct {
   GtkWidget widget;
} GtkRange;

typedef struct {
   GtkWidget widget;
} GtkLabel;

typedef struct {
   GtkWidget widget;
} GtkEntry;

typedef struct {
   GtkWidget widget;
} GtkMisc;

typedef struct {
   gulong g_type;
} GTypeClass;

typedef struct {
   GTypeClass g_type_class;
   void *priv[1];
   void *funcs[7];
   void *dummy[8];
} GObjectClass;

typedef struct {
   GObjectClass parent_class;
   guint activate_signal;
   void *funcs1[9];
   void (*size_allocate)(GtkWidget *widget, GtkAllocation *allocation);
   void *funcs2[10];
   void (*get_preferred_height)(GtkWidget *widget, gint *minimum_height, gint *natural_height);
   void (*get_preferred_width_for_height)(GtkWidget *widget, gint height, gint *minimum_width, gint *natural_width);
   void (*get_preferred_width)(GtkWidget *widget, gint *minimum_width, gint *natural_width);
   void (*get_preferred_height_for_width)(GtkWidget *widget, gint width, gint *minimum_height, gint *natural_height);
} GtkWidgetClass;

static int version;

static void (*g_free)(gpointer mem);

static gpointer (*g_object_ref)(gpointer object);
static void (*g_object_unref)(gpointer object);
static gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags);
#define g_signal_connect(instance, detailed_signal, c_handler, data) g_signal_connect_data(instance, detailed_signal, c_handler, data, NULL, 0)

static cairo_t *(*gdk_cairo_create)(GdkWindow *window);
static PangoContext *(*gdk_pango_context_get)();

static void (*pango_context_list_families)(PangoContext *context, PangoFontFamily ***families, int *n_families);
static const char *(*pango_font_family_get_name)(PangoFontFamily *family);

static cairo_t *(*cairo_create)(cairo_surface_t *target);
static void (*cairo_clip_extents)(cairo_t *cr, double *x1, double *y1, double *x2, double *y2);
static void (*cairo_translate)(cairo_t *cr, double tx, double ty);
static void (*cairo_set_source_rgba)(cairo_t *cr, double red, double green, double blue, double alpha);
static void (*cairo_set_source)(cairo_t *cr, cairo_pattern_t *source);
static void (*cairo_rectangle)(cairo_t *cr, double x, double y, double width, double height);
static void (*cairo_fill)(cairo_t *cr);
static void (*cairo_paint)(cairo_t *cr);
static void (*cairo_destroy)(cairo_t *cr);
static void (*cairo_select_font_face)(cairo_t *cr, const char *family, cairo_font_slant_t slant, cairo_font_weight_t weight);
static cairo_font_face_t *(*cairo_get_font_face)(cairo_t *cr);
static void (*cairo_set_font_face)(cairo_t *cr, cairo_font_face_t *font_face);
static void (*cairo_set_font_size)(cairo_t *cr, double size);
static void (*cairo_show_text)(cairo_t *cr, const char *utf8);
static void (*cairo_font_extents)(cairo_t *cr, cairo_font_extents_t *extents);
static void (*cairo_text_extents)(cairo_t *cr, const char *utf8, cairo_text_extents_t *extents);
static cairo_font_face_t *(*cairo_font_face_reference)(cairo_font_face_t *font_face);
static void (*cairo_font_face_destroy)(cairo_font_face_t *font_face);
static int (*cairo_format_stride_for_width)(cairo_format_t format, int width);
static cairo_surface_t *(*cairo_image_surface_create)(cairo_format_t format, int width, int height);
static cairo_surface_t *(*cairo_image_surface_create_for_data)(unsigned char *data, cairo_format_t format, int width, int height, int stride);
static unsigned char *(*cairo_image_surface_get_data)(cairo_surface_t *surface);
static int (*cairo_image_surface_get_stride)(cairo_surface_t *surface);
static void (*cairo_surface_mark_dirty)(cairo_surface_t *surface);
static void (*cairo_surface_destroy)(cairo_surface_t *surface);
static cairo_pattern_t *(*cairo_pattern_create_for_surface)(cairo_surface_t *surface);
static void (*cairo_pattern_destroy)(cairo_pattern_t *pattern);

static void (*gtk_init)(int *argc, char ***argv);
static gboolean (*gtk_init_check)(int *argc, char ***argv);
static void (*gtk_main)();
static GtkWidget *(*gtk_window_new)(GtkWindowType type);
static void (*gtk_window_set_default)(GtkWindow *window, GtkWidget *default_widget);
static void (*gtk_window_set_title)(GtkWindow *window, const char *title);
static const char *(*gtk_window_get_title)(GtkWindow *window);
static void (*gtk_window_set_resizable)(GtkWindow *window, gboolean resizable);
static void (*gtk_window_set_default_size)(GtkWindow *window, gint width, gint height);
static void (*gtk_window_resize)(GtkWindow *window, gint width, gint height);
static void (*gtk_widget_set_size_request)(GtkWidget *widget, gint width, gint height);
static void (*gtk_widget_set_usize)(GtkWidget *widget, gint width, gint height);
static void (*gtk_widget_size_request)(GtkWidget *widget, GtkRequisition *requisition);
static void (*gtk_widget_show)(GtkWidget *widget);
static void (*gtk_widget_hide)(GtkWidget *widget);
//static void (*gtk_widget_get_allocation)(GtkWidget *widget, GtkAllocation *allocation);
static void (*gtk_widget_size_allocate)(GtkWidget *widget, GtkAllocation *allocation);
static void (*gtk_widget_set_allocation)(GtkWidget *widget, const GtkAllocation *allocation);
static void (*gtk_widget_destroy)(GtkWidget *widget);
static GtkWidget *(*gtk_fixed_new)();
static void (*gtk_fixed_put)(GtkFixed *fixed, GtkWidget *widget, gint x, gint y);
static void (*gtk_fixed_move)(GtkFixed *fixed, GtkWidget *widget, gint x, gint y);
static void (*gtk_container_add)(GtkContainer *container, GtkWidget *widget);
static GtkWidget *(*gtk_button_new_with_label)(const gchar *label);
static const gchar *(*gtk_button_get_label)(GtkButton *button);
static void (*gtk_button_set_label)(GtkButton *button, const gchar *label);
static GtkWidget *(*gtk_drawing_area_new)();
static void (*gtk_widget_queue_draw)(GtkWidget *widget);
static void (*gtk_widget_queue_draw_area)(GtkWidget *widget, gint x, gint y, gint width, gint height);
static GtkWidget *(*gtk_scrolled_window_new)(GtkAdjustment *hadjustment, GtkAdjustment *vadjustment);
static void (*gtk_scrolled_window_add_with_viewport)(GtkScrolledWindow *scrolled_window, GtkWidget *child);
static GtkAdjustment *(*gtk_scrolled_window_get_hadjustment)(GtkScrolledWindow *scrolled_window);
static GtkAdjustment *(*gtk_scrolled_window_get_vadjustment)(GtkScrolledWindow *scrolled_window);
static void (*gtk_adjustment_configure)(GtkAdjustment *adjustment, gdouble value, gdouble lower, gdouble upper, gdouble step_increment, gdouble page_increment, gdouble page_size);
static void (*gtk_adjustment_value_changed)(GtkAdjustment *adjustment);
static void (*gtk_scrolled_window_get_policy)(GtkScrolledWindow *scrolled_window, GtkPolicyType *hscrollbar_policy, GtkPolicyType *vscrollbar_policy);
static void (*gtk_scrolled_window_set_policy)(GtkScrolledWindow *scrolled_window, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy);
static GtkWidget *(*gtk_viewport_new)(GtkAdjustment *hadjustment, GtkAdjustment *vadjustment);
static GtkObject *(*gtk_adjustment_new)(gdouble value, gdouble lower, gdouble upper, gdouble step_increment, gdouble page_increment, gdouble page_size);
static GtkWidget *(*gtk_scrolled_window_get_hscrollbar)(GtkScrolledWindow *scrolled_window);
static GtkWidget *(*gtk_scrolled_window_get_vscrollbar)(GtkScrolledWindow *scrolled_window);
static void (*gtk_range_set_adjustment)(GtkRange *range, GtkAdjustment *adjustment);
static int (*gtk_widget_get_allocated_width)(GtkWidget *widget);
static int (*gtk_widget_get_allocated_height)(GtkWidget *widget);
static void (*gtk_window_set_geometry_hints)(GtkWindow *window, GtkWidget *geometry_widget, GdkGeometry *geometry, GdkWindowHints geom_mask);
static gdouble (*gtk_adjustment_get_value)(GtkAdjustment *adjustment);
static GtkWidget *(*gtk_label_new)(const gchar *str);
static void (*gtk_label_set_text)(GtkLabel *label, const gchar *str);
static const gchar *(*gtk_label_get_text)(GtkLabel *label);
static GtkWidget *(*gtk_entry_new)();
static void (*gtk_entry_set_text)(GtkEntry *entry, const gchar *text);
static const gchar *(*gtk_entry_get_text)(GtkEntry *entry);
static void (*gtk_misc_set_alignment)(GtkMisc *misc, gfloat xalign, gfloat yalign);

struct View {
   ViewCommon common;
   GtkWidget *widget;
   Rect rect;
   union {
      struct {
         GtkWidget *fixed;
         int x, y, width, height;
      } window;
      struct {
         int flags;
         GtkWidget *area;
         GtkWidget *scroll;
         GtkWidget *viewport;
         GtkAdjustment *hadj, *vadj;
      } canvas;
   };
};

struct Menu {
   MenuCommon common;
};

struct Worker {
   WorkerCommon common;
};

struct NotifyIcon {
   NotifyIconCommon common;
};

struct SystemFont {
   cairo_font_face_t *font_face;
   float size;
   float ascent;
   float descent;
   float height;
};

typedef struct WidgetInfo {
   GtkWidget *widget;
   View *view;
   struct WidgetInfo *next;
} WidgetInfo;

static WidgetInfo *widget_infos = NULL;
static void (*orig_fixed_size_allocate)(GtkWidget *widget, GtkAllocation *allocation);
static void (*orig_fixed_preferred_width)(GtkWidget *widget, int *min, int *natural);
static void (*orig_fixed_preferred_height)(GtkWidget *widget, int *min, int *natural);
static cairo_t *tmp_cr;


void trigger_delayed_gc(Heap *heap)
{
}


void free_view(View *view)
{
   WidgetInfo *info, **prev;

   switch (view->common.type) {
      case TYPE_WINDOW:
         for (prev = &widget_infos, info = *prev; info; prev = &info->next, info = info->next) {
            if (info->widget == view->window.fixed) {
               *prev = info->next;
               free(info);
               break;
            }
         }
         gtk_widget_destroy(view->widget);
         break;

      case TYPE_CANVAS:
         if (view->canvas.flags & CANVAS_SCROLLABLE) {
            g_object_unref(view->canvas.hadj);
            g_object_unref(view->canvas.vadj);
         }
         g_object_unref(view->widget);
         break;

      default:
         g_object_unref(view->widget);
         break;
   }
   
   free(view);
}


void free_menu(Menu *menu)
{
   free(menu);
}


void free_notify_icon(NotifyIcon *icon)
{
   free(icon);
}


void view_destroy(View *view)
{
}


void view_get_rect(View *view, Rect *rect)
{
   //GdkRectangle alloc;

   if (view->common.type == TYPE_WINDOW) {
      /*
      //gtk_widget_get_allocation(view->widget, &alloc);
      alloc = view->window.fixed->allocation;
      rect->x1 = alloc.x;
      rect->y1 = alloc.y;
      rect->x2 = alloc.x + alloc.width;
      rect->y2 = alloc.y + alloc.height;
      */
      rect->x1 = view->window.x;
      rect->y1 = view->window.y;
      rect->x2 = view->window.x + view->window.width;
      rect->y2 = view->window.y + view->window.height;
      return;
   }
   *rect = view->rect;
}


void view_set_rect(View *view, Rect *rect)
{
   view->rect = *rect;

   if (view->widget && view->common.parent && view->common.parent->common.type == TYPE_WINDOW) {
      if (version == 2) {
         gtk_widget_set_usize(view->widget, rect->x2 - rect->x1, rect->y2 - rect->y1);
      }
      else {
         gtk_widget_set_size_request(view->widget, rect->x2 - rect->x1, rect->y2 - rect->y1);
      }
      gtk_fixed_move((GtkFixed *)view->common.parent->window.fixed, view->widget, rect->x1, rect->y1);
      gtk_widget_queue_draw(view->widget);
   }
}


void view_get_content_rect(View *view, Rect *rect)
{
   view_get_rect(view, rect);
   rect->x2 -= rect->x1;
   rect->y2 -= rect->y1;
   rect->x1 = 0;
   rect->y1 = 0;
}


void view_get_inner_rect(View *view, Rect *rect)
{
   view_get_rect(view, rect);
   rect->x2 -= rect->x1;
   rect->y2 -= rect->y1;
   rect->x1 = 0;
   rect->y1 = 0;
}


void view_set_visible(View *view, int visible)
{
   if (view->common.type == TYPE_WINDOW) {
      if (visible) {
         gtk_widget_show(view->widget);
      }
      else {
         gtk_widget_hide(view->widget);
      }
   }
}


int view_add(View *parent, View *view)
{
   if (parent->common.type != TYPE_WINDOW) return 0;
   if (!view->widget) return 1;

   if (version == 2) {
      gtk_widget_set_usize(view->widget, view->rect.x2 - view->rect.x1, view->rect.y2 - view->rect.y1);
   }
   else {
      gtk_widget_set_size_request(view->widget, view->rect.x2 - view->rect.x1, view->rect.y2 - view->rect.y1);
   }
   gtk_fixed_put((GtkFixed *)parent->window.fixed, view->widget, view->rect.x1, view->rect.y1);
   return 1;
}


void view_focus(View *view)
{
}


int view_has_focus(View *view)
{
   return 0;
}


void view_get_sizing(View *view, float *grid_x, float *grid_y, int *form_small, int *form_medium, int *form_large, int *view_small, int *view_medium, int *view_large)
{
   *grid_x = 5;
   *grid_y = 6;
   *form_small = 6;
   *form_medium = 12;
   *form_large = 24;
   *view_small = 6;
   *view_medium = 12;
   *view_large = 24;
}


void view_get_default_size(View *view, int *width, int *height)
{
   GtkRequisition requisition;

   gtk_widget_size_request(view->widget, &requisition);
   *width = requisition.width;
   *height = requisition.height;
}


float view_get_scale(View *view)
{
   return 1.0f;
}


void view_set_cursor(View *view, int cursor)
{
}


int view_get_cursor(View *view)
{
   return CURSOR_DEFAULT;
}


static gboolean window_configure(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
   View *view = user_data;

   view->window.x = event->configure.x;
   view->window.y = event->configure.y;
   view->window.width = event->configure.width;
   view->window.height = event->configure.height;
   call_view_callback(view, CALLBACK_WINDOW_RESIZE);
   return 0;
}


static void fake_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
   WidgetInfo *info;
   View *view;
   GtkAllocation child_allocation;

   for (info = widget_infos; info; info = info->next) {
      if (info->widget == widget) {
         gtk_widget_set_allocation(widget, allocation);
         for (view = info->view->common.first_child; view; view = view->common.next) {
            child_allocation.x = view->rect.x1;
            child_allocation.y = view->rect.y1;
            child_allocation.width = view->rect.x2 - view->rect.x1;
            child_allocation.height = view->rect.y2 - view->rect.y1;
            gtk_widget_size_allocate(view->widget, &child_allocation);
         }
         return;
      }
   }

   orig_fixed_size_allocate(widget, allocation);
}


static void fake_preferred_width(GtkWidget *widget, int *min, int *natural)
{
   WidgetInfo *info;

   for (info = widget_infos; info; info = info->next) {
      if (info->widget == widget) {
         *min = 1;
         *natural = 1;
         return;
      }
   }

   orig_fixed_preferred_width(widget, min, natural);
}


static void fake_preferred_height(GtkWidget *widget, int *min, int *natural)
{
   WidgetInfo *info;

   for (info = widget_infos; info; info = info->next) {
      if (info->widget == widget) {
         *min = 1;
         *natural = 1;
         return;
      }
   }

   orig_fixed_preferred_height(widget, min, natural);
}


View *window_create(plat_char *title, int width, int height, int flags)
{
   View *view;
   //GdkGeometry geometry;
   GtkWidgetClass *class;
   WidgetInfo *info;
   
   view = calloc(1, sizeof(View));
   if (!view) return NULL;

   view->widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
   view->window.fixed = gtk_fixed_new();

   if (version >= 3) {
      if (!orig_fixed_size_allocate) {
         class = view->window.fixed->object.parent_instance.class;
         orig_fixed_size_allocate = class->size_allocate;
         orig_fixed_preferred_width = class->get_preferred_width;
         orig_fixed_preferred_height = class->get_preferred_height;
         class->size_allocate = fake_size_allocate;
         class->get_preferred_width = fake_preferred_width;
         class->get_preferred_height = fake_preferred_height;
      }

      info = calloc(1, sizeof(WidgetInfo));
      info->widget = view->window.fixed;
      info->view = view;
      info->next = widget_infos;
      widget_infos = info;
   }
   
   g_signal_connect(view->widget, "configure_event", window_configure, view);
   gtk_widget_set_size_request(view->window.fixed, 32, 32);
   gtk_window_set_default_size((GtkWindow *)view->widget, width, height);
   gtk_window_set_title((GtkWindow *)view->widget, title);
   gtk_window_set_resizable((GtkWindow *)view->widget, (flags & WIN_RESIZABLE) != 0);
   /*
   geometry.min_width = 320;
   geometry.min_height = 240;
   geometry.base_width = 640;
   geometry.base_height = 480;
   gtk_window_set_geometry_hints((GtkWindow *)view->widget, view->window.fixed, &geometry, GDK_HINT_MIN_SIZE | GDK_HINT_BASE_SIZE);
   */

   gtk_container_add((GtkContainer *)view->widget, view->window.fixed);
   gtk_widget_show(view->window.fixed);
   return view;
}


plat_char *window_get_title(View *view)
{
   return strdup(gtk_window_get_title((GtkWindow *)view->widget));
}


void window_set_title(View *view, plat_char *title)
{
   gtk_window_set_title((GtkWindow *)view->widget, title);
}


void window_set_minimum_size(View *view, int width, int height)
{
   gtk_widget_set_size_request(view->window.fixed, width, height);
}


int window_is_maximized(View *view)
{
   return 0;
}


void window_set_status_text(View *view, plat_char *text)
{
}


int window_set_menu(View *view, Menu *old_menu, Menu *new_menu)
{
   return 1;
}


View *label_create(plat_char *label)
{
   View *view;
   
   view = calloc(1, sizeof(View));
   if (!view) return NULL;

   view->widget = gtk_label_new(label);
   gtk_widget_show(view->widget);
   g_object_ref(view->widget);
   gtk_misc_set_alignment((GtkMisc *)view->widget, 0.0f, 0.5f);
   return view;
}


plat_char *label_get_label(View *view)
{
   return strdup(gtk_label_get_text((GtkLabel *)view->widget));
}


void label_set_label(View *view, plat_char *label)
{
   gtk_label_set_text((GtkLabel *)view->widget, label);
}


View *text_field_create()
{
   View *view;
   
   view = calloc(1, sizeof(View));
   if (!view) return NULL;

   view->widget = gtk_entry_new();
   gtk_widget_show(view->widget);
   g_object_ref(view->widget);
   return view;
}


plat_char *text_field_get_text(View *view)
{
   return strdup(gtk_entry_get_text((GtkEntry *)view->widget));
}


void text_field_set_text(View *view, plat_char *text)
{
   gtk_entry_set_text((GtkEntry *)view->widget, text);
}


static void button_clicked(GtkButton *button, gpointer user_data)
{
   View *view = user_data;

   call_action_callback(view, CALLBACK_BUTTON_ACTION);
}


View *button_create(plat_char *label, int flags)
{
   View *view;
   
   view = calloc(1, sizeof(View));
   if (!view) return NULL;

   view->widget = gtk_button_new_with_label(label);
   gtk_widget_show(view->widget);
   g_object_ref(view->widget);

   g_signal_connect(view->widget, "clicked", button_clicked, view);
   return view;
}


plat_char *button_get_label(View *view)
{
   return strdup(gtk_button_get_label((GtkButton *)view->widget));
}


void button_set_label(View *view, plat_char *label)
{
   gtk_button_set_label((GtkButton *)view->widget, label);
}


static void free_surface(void *data)
{
   cairo_surface_t *surface = data;

   cairo_surface_destroy(surface);
}


static void canvas_paint(View *view, cairo_t *cr, int xoff, int yoff)
{
   Heap *heap = view->common.heap;
   cairo_surface_t *surface;
   cairo_pattern_t *pattern;
   Value image, painter, error;
   double x1, y1, x2, y2;
   uint32_t *pixels;
   int x, y, width, height, stride;

   cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
   x = (int)floor(x1);
   y = (int)floor(y1);
   width = (int)ceil(x2) - x;
   height = (int)ceil(y2) - y;
   if (width < 1 || height < 1) return;

   surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
   pixels = (uint32_t *)cairo_image_surface_get_data(surface);
   stride = cairo_image_surface_get_stride(surface) / 4;
   
   image = fiximage_create_from_pixels(heap, width, height, stride, pixels, free_surface, surface, -1);
   fixscript_ref(heap, image);

   if (!image.value) {
      fprintf(stderr, "error while painting:\n");
      fixscript_error(heap, &error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
      fixscript_dump_value(heap, error, 1);
   }
   else {
      painter = fiximage_create_painter(heap, image, xoff-x, yoff-y);
      if (!painter.value) {
         fprintf(stderr, "error while painting:\n");
         fixscript_error(heap, &error, FIXSCRIPT_ERR_OUT_OF_MEMORY);
         fixscript_dump_value(heap, error, 1);
      }
      else {
         call_view_callback_with_value(view, CALLBACK_CANVAS_PAINT, painter);
      }
   }
   
   cairo_surface_mark_dirty(surface);
   cairo_translate(cr, x, y);
   pattern = cairo_pattern_create_for_surface(surface);
   cairo_set_source(cr, pattern);
   cairo_paint(cr);
   cairo_pattern_destroy(pattern);
   fixscript_unref(heap, image);
}


static gboolean canvas_expose(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
   View *view = user_data;
   cairo_t *cr;
   int xoff, yoff;

   if (view->canvas.flags & CANVAS_SCROLLABLE) {
      xoff = -view->canvas.hadj->value;
      yoff = -view->canvas.vadj->value;
   }
   else {
      xoff = 0;
      yoff = 0;
   }

   cr = gdk_cairo_create(event->expose.window);
   canvas_paint(view, cr, xoff, yoff);
   cairo_destroy(cr);
   return 0;
}


static gboolean canvas_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
{
   View *view = user_data;
   int xoff, yoff;

   if (view->canvas.flags & CANVAS_SCROLLABLE) {
      xoff = -gtk_adjustment_get_value(view->canvas.hadj);
      yoff = -gtk_adjustment_get_value(view->canvas.vadj);
   }
   else {
      xoff = 0;
      yoff = 0;
   }

   canvas_paint(view, cr, xoff, yoff);
   return 0;
}


static void canvas_hscroll(GtkAdjustment *adjustment, gpointer user_data)
{
   View *view = user_data;

   gtk_widget_queue_draw(view->canvas.area);
}


static void canvas_vscroll(GtkAdjustment *adjustment, gpointer user_data)
{
   View *view = user_data;

   gtk_widget_queue_draw(view->canvas.area);
}


View *canvas_create(int flags)
{
   View *view;

   view = calloc(1, sizeof(View));
   if (!view) return NULL;

   view->canvas.flags = flags;
   view->canvas.area = gtk_drawing_area_new();
   gtk_widget_show(view->canvas.area);

   if (flags & (CANVAS_SCROLLABLE | CANVAS_BORDER)) {
      view->canvas.scroll = gtk_scrolled_window_new(NULL, NULL);
      gtk_scrolled_window_add_with_viewport((GtkScrolledWindow *)view->canvas.scroll, view->canvas.area);
      if (flags & CANVAS_SCROLLABLE) {
         gtk_scrolled_window_set_policy((GtkScrolledWindow *)view->canvas.scroll, GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS);
      }
      else {
         gtk_scrolled_window_set_policy((GtkScrolledWindow *)view->canvas.scroll, GTK_POLICY_NEVER, GTK_POLICY_NEVER);
      }

      if (flags & CANVAS_SCROLLABLE) {
         view->canvas.hadj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 0, 0, 0, 0);
         view->canvas.vadj = (GtkAdjustment *)gtk_adjustment_new(0, 0, 0, 0, 0, 0);
         g_object_ref(view->canvas.hadj);
         g_object_ref(view->canvas.vadj);
         gtk_range_set_adjustment((GtkRange *)gtk_scrolled_window_get_hscrollbar((GtkScrolledWindow *)view->canvas.scroll), view->canvas.hadj);
         gtk_range_set_adjustment((GtkRange *)gtk_scrolled_window_get_vscrollbar((GtkScrolledWindow *)view->canvas.scroll), view->canvas.vadj);
         g_signal_connect(view->canvas.hadj, "changed", canvas_hscroll, view);
         g_signal_connect(view->canvas.hadj, "value_changed", canvas_hscroll, view);
         g_signal_connect(view->canvas.vadj, "changed", canvas_vscroll, view);
         g_signal_connect(view->canvas.vadj, "value_changed", canvas_vscroll, view);
      }

      gtk_widget_show(view->canvas.scroll);
      view->widget = view->canvas.scroll;
   }
   else {
      view->widget = view->canvas.area;
   }
   g_object_ref(view->widget);

   if (version >= 3) {
      g_signal_connect(view->canvas.area, "draw", canvas_draw, view);
   }
   else {
      g_signal_connect(view->canvas.area, "expose_event", canvas_expose, view);
   }
   return view;
}


void canvas_set_scroll_state(View *view, int type, int pos, int max, int page_size, int always_show)
{
   GtkAdjustment *adj;
   //GtkPolicyType hpolicy, vpolicy;

   if ((view->canvas.flags & CANVAS_SCROLLABLE) == 0) return;
   
   //gtk_scrolled_window_get_policy((GtkScrolledWindow *)view->canvas.scroll, &hpolicy, &vpolicy);
   if (type == SCROLL_HORIZ) {
      //adj = gtk_scrolled_window_get_hadjustment((GtkScrolledWindow *)view->canvas.scroll);
      adj = view->canvas.hadj;
      //hpolicy = always_show? GTK_POLICY_ALWAYS : GTK_POLICY_AUTOMATIC;
      //gtk_widget_set_size_request(view->canvas.area, 100, 2000);
   }
   else {
      //adj = gtk_scrolled_window_get_vadjustment((GtkScrolledWindow *)view->canvas.scroll);
      adj = view->canvas.vadj;
      //vpolicy = always_show? GTK_POLICY_ALWAYS : GTK_POLICY_AUTOMATIC;
   }
   //gtk_scrolled_window_set_policy((GtkScrolledWindow *)view->canvas.scroll, hpolicy, vpolicy);
   if (version >= 3) {
      gtk_adjustment_configure(adj, pos, 0, max, 1, page_size, page_size);
   }
   else {
      adj->lower = 0;
      adj->upper = max;
      adj->value = pos;
      adj->step_increment = 8;
      adj->page_increment = page_size;
      adj->page_size = page_size;
      gtk_adjustment_value_changed(adj);
   }
}


void canvas_set_scroll_position(View *view, int type, int pos)
{
}


int canvas_get_scroll_position(View *view, int type)
{
   return 0;
}


void canvas_set_active_rendering(View *view, int enable)
{
}


int canvas_get_active_rendering(View *view)
{
   return 0;
}


void canvas_set_relative_mode(View *view, int enable)
{
}


int canvas_get_relative_mode(View *view)
{
   return 0;
}


void canvas_set_overdraw_size(View *view, int size)
{
}


int canvas_get_overdraw_size(View *view)
{
   return 0;
}


void canvas_set_focusable(View *view, int enable)
{
}


int canvas_is_focusable(View *view)
{
   return 0;
}


void canvas_repaint(View *view, Rect *rect)
{
   if (rect) {
      gtk_widget_queue_draw_area(view->canvas.area, rect->x1, rect->y1, rect->x2 - rect->x1, rect->y2 - rect->y1);
   }
   else {
      gtk_widget_queue_draw(view->canvas.area);
   }
}


Menu *menu_create()
{
   Menu *menu;
   
   menu = calloc(1, sizeof(Menu));
   if (!menu) return NULL;

   return menu;
}


void menu_insert_item(Menu *menu, int idx, plat_char *title, MenuItem *item)
{
}


void menu_insert_separator(Menu *menu, int idx)
{
}


int menu_insert_submenu(Menu *menu, int idx, plat_char *title, Menu *submenu)
{
   return 1;
}


void menu_remove_item(Menu *menu, int idx, MenuItem *item)
{
}


void menu_show(Menu *menu, View *view, int x, int y)
{
}


int show_message(View *window, int type, plat_char *title, plat_char *msg)
{
   return 0;
}


Worker *worker_create()
{
   Worker *worker;
   
   worker = calloc(1, sizeof(Worker));
   if (!worker) return NULL;

   return worker;
}


int worker_start(Worker *worker)
{
   return 0;
}


void worker_notify(Worker *worker)
{
}


void worker_lock(Worker *worker)
{
}


void worker_wait(Worker *worker, int timeout)
{
}


void worker_unlock(Worker *worker)
{
}


void worker_destroy(Worker *worker)
{
}


uint32_t timer_get_time()
{
   return 0;
}


uint32_t timer_get_micro_time()
{
   return 0;
}


int timer_is_active(Heap *heap, Value instance)
{
   return 0;
}


void timer_start(Heap *heap, Value instance, int interval, int restart)
{
}


void timer_stop(Heap *heap, Value instance)
{
}


void clipboard_set_text(plat_char *text)
{
}


plat_char *clipboard_get_text()
{
   return NULL;
}


SystemFont *system_font_create(Heap *heap, plat_char *family, float size, int style)
{
   SystemFont *font;
   cairo_font_extents_t extents;
   
   font = calloc(1, sizeof(SystemFont));
   if (!font) return NULL;

   cairo_select_font_face(tmp_cr, family, style & FONT_ITALIC? CAIRO_FONT_SLANT_ITALIC : CAIRO_FONT_SLANT_NORMAL, style & FONT_BOLD? CAIRO_FONT_WEIGHT_BOLD : CAIRO_FONT_WEIGHT_NORMAL);
   cairo_set_font_size(tmp_cr, size);
   cairo_font_extents(tmp_cr, &extents);
   font->font_face = cairo_get_font_face(tmp_cr);
   font->size = size;
   font->ascent = extents.ascent;
   font->descent = extents.descent;
   font->height = extents.height;
   cairo_font_face_reference(font->font_face);
   return font;
}


void system_font_destroy(SystemFont *font)
{
   cairo_font_face_destroy(font->font_face);
   free(font);
}


plat_char **system_font_get_list()
{
   PangoContext *ctx;
   PangoFontFamily **families;
   char **list;
   int i, n_families;
   
   ctx = gdk_pango_context_get();
   pango_context_list_families(ctx, &families, &n_families);
   list = malloc((n_families+1)*sizeof(char *));
   for (i=0; i<n_families; i++) {
      list[i] = strdup(pango_font_family_get_name(families[i]));
   }
   list[n_families] = NULL;
   g_object_unref(ctx);
   return list;
}


int system_font_get_size(SystemFont *font)
{
   return font->size + 0.5f;
}


int system_font_get_ascent(SystemFont *font)
{
   return font->ascent + 0.5f;
}


int system_font_get_descent(SystemFont *font)
{
   return font->descent + 0.5f;
}


int system_font_get_height(SystemFont *font)
{
   return font->height + 0.5f;
}


int system_font_get_string_advance(SystemFont *font, plat_char *s)
{
   cairo_text_extents_t extents;
   
   cairo_set_font_face(tmp_cr, font->font_face);
   cairo_set_font_size(tmp_cr, font->size);
   cairo_text_extents(tmp_cr, s, &extents);
   return extents.x_advance + 0.5f;
}


float system_font_get_string_position(SystemFont *font, plat_char *text, int x)
{
   char *s;
   int width;
   int min, max, middle, w, w1, w2, pos;

   if (x < 0) return 0.0f;
   width = system_font_get_string_advance(font, text);
   if (x >= width) return (float)strlen(text);

   s = (char *)malloc(strlen(text)+1);

   min = 0;
   max = strlen(text);
   while (min < max) {
      middle = min+(max-min)/2;
      strcpy(s, text);
      s[middle] = 0;
      w = system_font_get_string_advance(font, s);
      if (w < x) {
         min = middle+1;
      }
      else {
         max = middle;
      }
   }

   pos = min-1;
   if (pos < 0) pos = 0;

   strcpy(s, text);
   s[pos+1] = 0;
   w2 = system_font_get_string_advance(font, s);
   s[pos] = 0;
   w1 = system_font_get_string_advance(font, s);

   free(s);

   return pos + (x - w1) / (float)(w2 - w1);
}


void system_font_draw_string(SystemFont *font, int x, int y, plat_char *text, uint32_t color, uint32_t *pixels, int width, int height, int stride)
{
   cairo_surface_t *surface;
   cairo_t *cr;
   float r, g, b, a;

   if (cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, 1) != 4) {
      // safeguard in case the implementation changes in incompatible way (possible, but not likely)
      return;
   }

   r = ((color >> 16) & 0xFF)/255.0f;
   g = ((color >>  8) & 0xFF)/255.0f;
   b = ((color >>  0) & 0xFF)/255.0f;
   a = ((color >> 24) & 0xFF)/255.0f;
   if (a != 0.0f) {
      r /= a;
      g /= a;
      b /= a;
   }
   
   surface = cairo_image_surface_create_for_data((unsigned char *)pixels, CAIRO_FORMAT_ARGB32, width, height, stride*4);
   cr = cairo_create(surface);
   cairo_set_font_face(cr, font->font_face);
   cairo_set_font_size(cr, font->size);
   cairo_translate(cr, x, y);
   cairo_set_source_rgba(cr, r, g, b, a);
   cairo_show_text(cr, text);
   cairo_destroy(cr);
   cairo_surface_destroy(surface);
}


NotifyIcon *notify_icon_create(Heap *heap, Value *images, int num_images, char **error_msg)
{
   NotifyIcon *icon;
   
   icon = calloc(1, sizeof(NotifyIcon));
   if (!icon) return NULL;

   return icon;
}


void notify_icon_get_sizes(int **sizes, int *cnt)
{
}


void notify_icon_destroy(NotifyIcon *icon)
{
}


int notify_icon_set_menu(NotifyIcon *icon, Menu *menu)
{
   return 1;
}


void io_notify()
{
}


void post_to_main_thread(void *data)
{
}


int modifiers_cmd_mask()
{
   return SCRIPT_MOD_CMD;
}


void quit_app()
{
}


static Value func_gtk_is_present(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   return fixscript_int(1);
}


static Value func_gtk_get_widget_handle(Heap *heap, Value *error, int num_params, Value *params, void *data)
{
   View *view;
   uint64_t ptr;
   
   view = view_get_native(heap, error, params[0], -1);
   if (!view) {
      return fixscript_int(0);
   }

   ptr = (uint64_t)(uintptr_t)view->widget;

   *error = fixscript_int((uint32_t)(ptr >> 32));
   return fixscript_int((uint32_t)ptr);
}


void register_platform_gui_functions(Heap *heap)
{
   fixscript_register_native_func(heap, "gtk_is_present#0", func_gtk_is_present, NULL);
   fixscript_register_native_func(heap, "gtk_get_widget_handle#1", func_gtk_get_widget_handle, NULL);
}


static void *load_symbol(void *lib, const char *name)
{
   void *ptr;

   ptr = dlsym(lib, name);
   if (!ptr) {
      #ifndef FIXGUI_CONSOLE_FALLBACK
         fprintf(stderr, "error: can't find symbol %s\n", name);
         exit(1);
      #endif
   }
   return ptr;
}

#define SYMBOL(lib, name) if (!(name = load_symbol(lib##_lib, #name))) goto fallback;


int main(int argc, char **argv)
{
   cairo_surface_t *surface;
   void *cairo_lib;
   void *pango_lib;
   void *glib_lib;
   void *gobject2_lib;
   void *gdk2_lib;
   void *gtk2_lib;
#ifdef FIXGUI_USE_GTK3
   void *gdk3_lib;
   void *gtk3_lib;
#endif
   void *gdk_lib;
   void *gtk_lib;

   cairo_lib = dlopen("libcairo.so.2", RTLD_LAZY);
   pango_lib = dlopen("libpango-1.0.so.0", RTLD_LAZY);
   glib_lib = dlopen("libglib-2.0.so.0", RTLD_LAZY);
   gobject2_lib = dlopen("libgobject-2.0.so.0", RTLD_LAZY);

#ifdef FIXGUI_USE_GTK3
   gdk3_lib = dlopen("libgdk-3.so.0", RTLD_LAZY);
   gtk3_lib = dlopen("libgtk-3.so.0", RTLD_LAZY);
   if (gdk3_lib && gtk3_lib && glib_lib && gobject2_lib && cairo_lib && pango_lib) {
      version = 3;
      gdk_lib = gdk3_lib;
      gtk_lib = gtk3_lib;
   }
   else
#endif
   {
      gdk2_lib = dlopen("libgdk-x11-2.0.so.0", RTLD_LAZY);
      gtk2_lib = dlopen("libgtk-x11-2.0.so.0", RTLD_LAZY);
      if (!glib_lib || !gobject2_lib || !gdk2_lib || !cairo_lib || !gtk2_lib || !pango_lib) {
         #ifdef FIXGUI_CONSOLE_FALLBACK
            goto fallback;
         #else
            fprintf(stderr, "error: can't load libgtk-x11-2.0.so.0 library\n");
            return 1;
         #endif
      }
      version = 2;
      gdk_lib = gdk2_lib;
      gtk_lib = gtk2_lib;
   }

   SYMBOL(glib, g_free);

   SYMBOL(gobject2, g_object_ref);
   SYMBOL(gobject2, g_object_unref);
   SYMBOL(gobject2, g_signal_connect_data);

   if (version == 2) {
      SYMBOL(gdk, gdk_cairo_create);
   }
   SYMBOL(gdk, gdk_pango_context_get);

   SYMBOL(pango, pango_context_list_families);
   SYMBOL(pango, pango_font_family_get_name);

   SYMBOL(cairo, cairo_create);
   SYMBOL(cairo, cairo_clip_extents);
   SYMBOL(cairo, cairo_translate);
   SYMBOL(cairo, cairo_set_source_rgba);
   SYMBOL(cairo, cairo_set_source);
   SYMBOL(cairo, cairo_rectangle);
   SYMBOL(cairo, cairo_fill);
   SYMBOL(cairo, cairo_paint);
   SYMBOL(cairo, cairo_destroy);
   SYMBOL(cairo, cairo_select_font_face);
   SYMBOL(cairo, cairo_get_font_face);
   SYMBOL(cairo, cairo_set_font_face);
   SYMBOL(cairo, cairo_set_font_size);
   SYMBOL(cairo, cairo_show_text);
   SYMBOL(cairo, cairo_font_extents);
   SYMBOL(cairo, cairo_text_extents);
   SYMBOL(cairo, cairo_font_face_reference);
   SYMBOL(cairo, cairo_font_face_destroy);
   SYMBOL(cairo, cairo_format_stride_for_width);
   SYMBOL(cairo, cairo_image_surface_create);
   SYMBOL(cairo, cairo_image_surface_create_for_data);
   SYMBOL(cairo, cairo_image_surface_get_data);
   SYMBOL(cairo, cairo_image_surface_get_stride);
   SYMBOL(cairo, cairo_surface_mark_dirty);
   SYMBOL(cairo, cairo_surface_destroy);
   SYMBOL(cairo, cairo_pattern_create_for_surface);
   SYMBOL(cairo, cairo_pattern_destroy);

   SYMBOL(gtk, gtk_init);
   SYMBOL(gtk, gtk_init_check);
   SYMBOL(gtk, gtk_main);
   SYMBOL(gtk, gtk_window_new);
   SYMBOL(gtk, gtk_window_set_default);
   SYMBOL(gtk, gtk_window_set_title);
   SYMBOL(gtk, gtk_window_get_title);
   SYMBOL(gtk, gtk_window_set_resizable);
   SYMBOL(gtk, gtk_window_set_default_size);
   SYMBOL(gtk, gtk_window_resize);
   SYMBOL(gtk, gtk_widget_set_size_request);
   if (version == 2) {
      SYMBOL(gtk, gtk_widget_set_usize);
   }
   SYMBOL(gtk, gtk_widget_size_request);
   SYMBOL(gtk, gtk_widget_show);
   SYMBOL(gtk, gtk_widget_hide);
   //SYMBOL(gtk, gtk_widget_get_allocation);
   SYMBOL(gtk, gtk_widget_size_allocate);
   if (version >= 3) {
      SYMBOL(gtk, gtk_widget_set_allocation);
   }
   SYMBOL(gtk, gtk_widget_destroy);
   SYMBOL(gtk, gtk_fixed_new);
   SYMBOL(gtk, gtk_fixed_put);
   SYMBOL(gtk, gtk_fixed_move);
   SYMBOL(gtk, gtk_container_add);
   SYMBOL(gtk, gtk_button_new_with_label);
   SYMBOL(gtk, gtk_button_get_label);
   SYMBOL(gtk, gtk_button_set_label);
   SYMBOL(gtk, gtk_drawing_area_new);
   SYMBOL(gtk, gtk_widget_queue_draw);
   SYMBOL(gtk, gtk_widget_queue_draw_area);
   SYMBOL(gtk, gtk_scrolled_window_new);
   SYMBOL(gtk, gtk_scrolled_window_add_with_viewport);
   SYMBOL(gtk, gtk_scrolled_window_get_hadjustment);
   SYMBOL(gtk, gtk_scrolled_window_get_vadjustment);
   if (version >= 3) {
      SYMBOL(gtk, gtk_adjustment_configure);
   }
   SYMBOL(gtk, gtk_adjustment_value_changed);
   SYMBOL(gtk, gtk_scrolled_window_get_policy);
   SYMBOL(gtk, gtk_scrolled_window_set_policy);
   SYMBOL(gtk, gtk_viewport_new);
   SYMBOL(gtk, gtk_adjustment_new);
   SYMBOL(gtk, gtk_scrolled_window_get_hscrollbar);
   SYMBOL(gtk, gtk_scrolled_window_get_vscrollbar);
   SYMBOL(gtk, gtk_range_set_adjustment);
   if (version >= 3) {
      SYMBOL(gtk, gtk_widget_get_allocated_width);
      SYMBOL(gtk, gtk_widget_get_allocated_height);
   }
   SYMBOL(gtk, gtk_window_set_geometry_hints);
   if (version >= 3) {
      SYMBOL(gtk, gtk_adjustment_get_value);
   }
   SYMBOL(gtk, gtk_label_new);
   SYMBOL(gtk, gtk_label_set_text);
   SYMBOL(gtk, gtk_label_get_text);
   SYMBOL(gtk, gtk_entry_new);
   SYMBOL(gtk, gtk_entry_set_text);
   SYMBOL(gtk, gtk_entry_get_text);
   SYMBOL(gtk, gtk_misc_set_alignment);

   #ifdef FIXGUI_CONSOLE_FALLBACK
      if (!gtk_init_check(&argc, &argv)) {
         goto fallback;
      }
   #else
      gtk_init(&argc, &argv);
   #endif

   surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
   tmp_cr = cairo_create(surface);
   cairo_surface_destroy(surface);

   app_main(argc, argv);
   gtk_main();
   return 0;

fallback:
   #ifdef FIXGUI_CONSOLE_FALLBACK
      console_main(argc, argv);
      return 0;
   #else
      return 1;
   #endif
}
