[mousetweaks/wayland] Initial commit



commit 2da98c2caccdbd731613ffc54fd4e353284cf134
Author: Gerd Kohlberger <gerdk src gnome org>
Date:   Sun Oct 27 11:18:10 2013 +0100

    Initial commit
    
    - Use GApplication for single instance checking
    - Move the GUI code out of the daemon
    - Switch to at-spi2 for receiving and generation mouse events
    - Drop gtk+ dependency from the daemon
    - Add middle mouse button support
    - Add a simple backend system for mouse events

 Makefile.am                                   |    5 +-
 configure.ac                                  |   49 +-
 data/Makefile.am                              |   30 +-
 pixmaps/double-click.png => data/double.png   |  Bin 595 -> 595 bytes
 pixmaps/drag-click.png => data/drag.png       |  Bin 477 -> 477 bytes
 data/mousetweaks.1                            |   51 ++
 data/mousetweaks.convert                      |    2 -
 data/mousetweaks.gresource.xml                |   10 +
 data/mousetweaks.ui                           |  311 ---------
 data/org.gnome.mousetweaks.gschema.xml.in     |   23 +-
 pixmaps/single-click.png => data/primary.png  |  Bin 480 -> 480 bytes
 pixmaps/right-click.png => data/secondary.png |  Bin 476 -> 476 bytes
 data/window.ui                                |  151 +++++
 man/Makefile.am                               |    5 -
 man/mousetweaks.1                             |   73 ---
 pixmaps/Makefile.am                           |   10 -
 po/POTFILES.in                                |    6 +-
 po/POTFILES.skip                              |    2 -
 src/Makefile.am                               |  107 ++--
 src/hover_click_window.c                      |  401 ++++++++++++
 src/{mt-ctw.h => main.c}                      |   30 +-
 src/mt-common.c                               |  137 ----
 src/mt-common.h                               |  103 ---
 src/mt-ctw.c                                  |  349 ----------
 src/mt-cursor-manager.c                       |  327 ----------
 src/mt-cursor-manager.h                       |   55 --
 src/mt-cursor.c                               |  146 -----
 src/mt-cursor.h                               |   62 --
 src/mt-enum-types.c.template                  |   96 ---
 src/mt-enum-types.h.template                  |   53 --
 src/mt-listener.c                             |  376 -----------
 src/mt-listener.h                             |   67 --
 src/mt-main.c                                 |  843 -------------------------
 src/mt-pidfile.c                              |  260 --------
 src/mt-service.c                              |  289 ---------
 src/mt-service.h                              |   53 --
 src/mt-settings.c                             |  304 ---------
 src/mt-settings.h                             |   64 --
 src/mt-sig-handler.c                          |  209 ------
 src/mt-sig-handler.h                          |   51 --
 src/mt_application.c                          |  450 +++++++++++++
 src/{mt-pidfile.h => mt_application.h}        |   23 +-
 src/mt_click.c                                |  215 +++++++
 src/mt_click.h                                |   78 +++
 src/mt_hover_click.c                          |  305 +++++++++
 src/mt_hover_click.h                          |   48 ++
 src/mt_listener.c                             |  141 ++++
 src/mt_listener.h                             |   96 +++
 src/mt_listener_atspi.c                       |  201 ++++++
 src/{mt-ctw.h => mt_listener_atspi.h}         |   24 +-
 src/mt_secondary_click.c                      |  125 ++++
 src/{mt-ctw.h => mt_secondary_click.h}        |   22 +-
 src/{mt-timer.c => mt_timer.c}                |  124 ++---
 src/{mt-timer.h => mt_timer.h}                |   30 +-
 54 files changed, 2470 insertions(+), 4522 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 48a0bed..656a8ff 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = src data pixmaps po man
+SUBDIRS = src data po
 
 ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
 
@@ -6,8 +6,7 @@ DISTCHECK_CONFIGURE_FLAGS =         \
     --disable-schemas-compile
 
 EXTRA_DIST =                        \
-    $(srcdir)/m4                    \
-    $(srcdir)/MAINTAINERS
+    $(srcdir)/m4
 
 MAINTAINERCLEANFILES =              \
     $(srcdir)/INSTALL               \
diff --git a/configure.ac b/configure.ac
index 5fe0e11..853f75b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,3 @@
-AC_PREREQ([2.64])
 AC_INIT([mousetweaks],
         [3.10.0],
         [https://bugzilla.gnome.org/enter_bug.cgi?product=mousetweaks],
@@ -6,11 +5,11 @@ AC_INIT([mousetweaks],
         [https://live.gnome.org/Mousetweaks/Home])
 
 AC_CONFIG_SRCDIR([src])
-AC_CONFIG_HEADER([config.h])
+AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIR([m4])
 AC_PREFIX_DEFAULT([/usr])
 
-AM_INIT_AUTOMAKE([1.11 no-dist-gzip dist-bzip2 -Wno-portability])
+AM_INIT_AUTOMAKE([1.11 no-dist-gzip dist-xz])
 AM_SILENT_RULES([yes])
 AM_MAINTAINER_MODE([enable])
 
@@ -18,58 +17,36 @@ AC_PROG_CC
 AC_PROG_INSTALL
 AC_HEADER_STDC
 AC_C_CONST
-AM_PROG_CC_C_O
-
-dnl *** GNOME macros ***
 
 GNOME_COMPILE_WARNINGS([maximum])
-GNOME_MAINTAINER_MODE_DEFINES
-
-dnl *** i18n ***
 
-IT_PROG_INTLTOOL([0.40.0])
+IT_PROG_INTLTOOL([0.41.0])
 
-GETTEXT_PACKAGE=mousetweaks
-AC_SUBST(GETTEXT_PACKAGE)
-AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [Gettext Package])
-AM_GLIB_GNU_GETTEXT
+AC_SUBST([GETTEXT_PACKAGE], [mousetweaks])
+AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], "$GETTEXT_PACKAGE", [Gettext Package])
 
-dnl *** gsettings ***
+AC_PATH_PROG([GLIB_COMPILE_RESOURCES], [glib-compile-resources])
 
 GLIB_GSETTINGS
 
-dnl *** dependencies ***
-
-GLIB_REQUIRED=2.25.3
-GIO_REQUIRED=2.25.9
-GTK_REQUIRED=3.0.0
-GDS_REQUIRED=0.1.0
+GIO_REQUIRED=2.38
+GTK_REQUIRED=3.10
+GDS_REQUIRED=0.1
 
-PKG_CHECK_MODULES(DEPENDENCIES,
-    glib-2.0 >= $GLIB_REQUIRED
+PKG_CHECK_MODULES(DAEMON,
+    glib-2.0
     gio-2.0 >= $GIO_REQUIRED
     gtk+-3.0 >= $GTK_REQUIRED
     gsettings-desktop-schemas >= $GDS_REQUIRED
-    x11
-    xcursor
-    xfixes
-    xtst)
+    atspi-2)
 
-dnl *** GLib tools ***
-
-GLIB_MKENUMS="$($PKG_CONFIG --variable=glib_mkenums glib-2.0)"
-AC_SUBST([GLIB_MKENUMS])
-
-dnl *** output ***
+PKG_CHECK_MODULES(WINDOW, gtk+-3.0 >= $GTK_REQUIRED)
 
 AC_CONFIG_FILES([
     Makefile
     src/Makefile
     data/Makefile
-    pixmaps/Makefile
     po/Makefile.in
-    man/Makefile
 ])
 
 AC_OUTPUT
-
diff --git a/data/Makefile.am b/data/Makefile.am
index ab5fd14..1c225ec 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -1,24 +1,24 @@
-uidir = $(datadir)/mousetweaks
-ui_DATA = mousetweaks.ui
-
-convertdir = $(datadir)/GConf/gsettings
-convert_DATA = mousetweaks.convert
-
-gsettings_ENUM_NAMESPACE = org.gnome.mousetweaks
-gsettings_ENUM_FILES = $(top_srcdir)/src/mt-common.h
-
 gsettings_in_files = org.gnome.mousetweaks.gschema.xml.in
 gsettings_SCHEMAS = $(gsettings_in_files:.xml.in=.xml)
 @INTLTOOL_XML_NOMERGE_RULE@
 @GSETTINGS_RULES@
 
-EXTRA_DIST =                \
-    $(ui_DATA)              \
-    $(gsettings_in_files)   \
-    $(gsettings_ENUM_FILES) \
-    $(convert_DATA)
+man_MANS = mousetweaks.1
+
+RESOURCES =                     \
+    window.ui                   \
+    primary.png                 \
+    secondary.png               \
+    double.png                  \
+    drag.png
+
+EXTRA_DIST =                    \
+    mousetweaks.gresource.xml   \
+    $(RESOURCES)                \
+    $(gsettings_in_files)       \
+    $(man_MANS)
 
-DISTCLEANFILES =            \
+DISTCLEANFILES =                \
     $(gsettings_SCHEMAS)
 
 -include $(top_srcdir)/git.mk
diff --git a/data/mousetweaks.1 b/data/mousetweaks.1
new file mode 100644
index 0000000..6ee7280
--- /dev/null
+++ b/data/mousetweaks.1
@@ -0,0 +1,51 @@
+.TH "mousetweaks" 1
+.SH NAME
+mousetweaks \- Accessibility enhancements for pointing devices
+.SH SYNOPSIS
+.B mousetweaks
+[\-\-hover\-click] [\-\-hover\-click\-time=FLOAT] [\-\-hover\-click\-time=FLOAT] 
[\-\-hover\-click\-threshold=INT]
+[\-\-secondary\-click] [\-\-secondary\-click\-time=FLOAT] [\-\-secondary\-click\-time=FLOAT] 
[\-\-secondary\-click\-threshold=INT]
+[\-v|\-\-version] [\-s|\-\-shutdown] [\-?|\-h|\-\-help]
+.SH DESCRIPTION
+.B mousetweaks
+is a collection of accessibility enhancements for pointing devices. This
+manual page describes the mousetweaks daemon.
+
+.SH OPTIONS
+.TP
+.B \-\-hover\-click
+Automatically perform mouse clicks without using a physical button.
+.TP
+.B \-\-hover\-click\-time=FLOAT
+Time to keep the pointer motionless before a Hover Click is performed.
+Range: 0.2\-3.0 seconds.
+.TP
+.B \-\-hover\-click\-threshold=INT
+Ignore small pointer movements. Range: 0\-30 pixels.
+.TP
+.B \-\-secondary\-click
+Trigger the secondary mouse button when the primary mouse button is held down
+for a specified amount of time.
+.TP
+.B \-\-secondary\-click\-time=FLOAT
+Time to hold the primary mouse button before a simulated secondary
+click is performed. Range: 0.5\-3.0 seconds.
+.TP
+.B \-\-secondary\-click\-threshold=INT
+Ignore small pointer movements. Range: 0\-30 pixels.
+.TP
+.B \-v, \-\-version
+Print the mousetweaks version to stdout.
+.TP
+.B \-s, \-\-shutdown
+Stop the mousetweaks daemon.
+
+.SH BUGS
+Report bugs to
+.UR http://bugzilla.gnome.org/
+<http://bugzilla.gnome.org/>
+.UE
+.SH AUTHORS
+This manual page was written by Francesco Fumanti.
+.SH LICENSE
+Licenced under the GNU General Public License v3 or later.
diff --git a/data/mousetweaks.gresource.xml b/data/mousetweaks.gresource.xml
new file mode 100644
index 0000000..a470b7e
--- /dev/null
+++ b/data/mousetweaks.gresource.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/Mousetweaks/">
+    <file preprocess="xml-stripblanks">window.ui</file>
+    <file>primary.png</file>
+    <file>secondary.png</file>
+    <file>double.png</file>
+    <file>drag.png</file>
+  </gresource>
+</gresources>
diff --git a/data/org.gnome.mousetweaks.gschema.xml.in b/data/org.gnome.mousetweaks.gschema.xml.in
index 50edbaf..65ace93 100644
--- a/data/org.gnome.mousetweaks.gschema.xml.in
+++ b/data/org.gnome.mousetweaks.gschema.xml.in
@@ -1,19 +1,18 @@
 <schemalist>
-  <schema id="org.gnome.mousetweaks" path="/org/gnome/desktop/a11y/mouse/" gettext-domain="mousetweaks">
-    <key name="click-type-window-style" enum="org.gnome.mousetweaks.MtClickTypeWindowStyle">
-      <default>'both'</default>
-      <_summary>Click-type window style</_summary>
-      <_description>Button style of the click-type window.</_description>
-    </key>
-    <key name="click-type-window-orientation" enum="org.gnome.mousetweaks.MtClickTypeWindowOrientation">
+  <enum id="org.gnome.Mousetweaks.Orientation">
+    <value nick="vertical" value="0"/>
+    <value nick="horizontal" value="1"/>
+  </enum>
+  <schema id="org.gnome.Mousetweaks" path="/org/gnome/Mousetweaks/" gettext-domain="mousetweaks">
+    <key name="window-orientation" enum="org.gnome.Mousetweaks.Orientation">
       <default>'vertical'</default>
-      <_summary>Click-type window orientation</_summary>
-      <_description>Orientation of the click-type window.</_description>
+      <_summary>Hover Click window orientation</_summary>
+      <_description>Orientation of the Hover Click window.</_description>
     </key>
-    <key name="click-type-window-geometry" type="s">
+    <key name="window-geometry" type="s">
       <default>''</default>
-      <_summary>Click-type window geometry</_summary>
-      <_description>Size and position of the click-type window. The format is a standard X Window System 
geometry string.</_description>
+      <_summary>Hover Click window geometry</_summary>
+      <_description>Size and position of the Hover Click window.</_description>
     </key>
   </schema>
 </schemalist>
diff --git a/data/window.ui b/data/window.ui
new file mode 100644
index 0000000..c2f0b13
--- /dev/null
+++ b/data/window.ui
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.15.3 on Sun Oct 27 09:16:51 2013 -->
+<interface>
+  <!-- interface-requires gtk+ 3.10 -->
+  <template class="HoverClickWindow" parent="GtkApplicationWindow">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Hover Click</property>
+    <property name="resizable">False</property>
+    <property name="icon_name">input-mouse</property>
+    <property name="type_hint">dialog</property>
+    <property name="deletable">False</property>
+    <property name="has_resize_grip">False</property>
+    <property name="show_menubar">False</property>
+    <child>
+      <object class="GtkBox" id="box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="homogeneous">True</property>
+        <style>
+          <class name="linked"/>
+        </style>
+        <child>
+          <object class="GtkRadioButton" id="button_primary">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="tooltip_text" translatable="yes">Primary Click</property>
+            <property name="xalign">0</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">False</property>
+            <child>
+              <object class="GtkImage" id="primary">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="resource">/org/gnome/Mousetweaks/primary.png</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkRadioButton" id="button_double">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="tooltip_text" translatable="yes">Double Click</property>
+            <property name="xalign">0</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">False</property>
+            <property name="group">button_primary</property>
+            <child>
+              <object class="GtkImage" id="double">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="resource">/org/gnome/Mousetweaks/double.png</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkRadioButton" id="button_secondary">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="tooltip_text" translatable="yes">Secondary Click</property>
+            <property name="xalign">0</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">False</property>
+            <property name="group">button_primary</property>
+            <child>
+              <object class="GtkImage" id="secondary">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="resource">/org/gnome/Mousetweaks/secondary.png</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkRadioButton" id="button_drag">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="tooltip_text" translatable="yes">Drag</property>
+            <property name="xalign">0</property>
+            <property name="active">True</property>
+            <property name="draw_indicator">False</property>
+            <property name="group">button_primary</property>
+            <child>
+              <object class="GtkImage" id="drag">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="resource">/org/gnome/Mousetweaks/drag.png</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkMenu" id="menu">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkMenuItem" id="orientation">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">Window Orientation</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkRadioMenuItem" id="vertical">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">_Vertical</property>
+        <property name="use_underline">True</property>
+        <property name="draw_as_radio">True</property>
+        <property name="group">horizontal</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkRadioMenuItem" id="horizontal">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="label" translatable="yes">_Horizontal</property>
+        <property name="use_underline">True</property>
+        <property name="draw_as_radio">True</property>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 9cffe02..bcc8a3b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,5 +1,3 @@
-[type: gettext/glade]data/mousetweaks.ui
+[type: gettext/glade]data/window.ui
 data/org.gnome.mousetweaks.gschema.xml.in
-src/mt-main.c
-src/mt-ctw.c
-src/mt-common.c
+src/mt_application.c
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index f8f2691..e69de29 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1,2 +0,0 @@
-data/org.gnome.applets.DwellClickApplet.panel-applet.in
-data/org.gnome.applets.PointerCaptureApplet.panel-applet.in
diff --git a/src/Makefile.am b/src/Makefile.am
index 5e17341..e138358 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,66 +1,55 @@
-AM_CPPFLAGS =                               \
-    $(WARN_CFLAGS)                          \
-    -DDATADIR=\"$(datadir)/mousetweaks\"    \
-    -DGNOMELOCALEDIR=\"$(datadir)/locale\"
+AM_CPPFLAGS =                                   \
+    $(WARN_CFLAGS)                              \
+    -DGNOMELOCALEDIR=\""$(datadir)/locale"\"    \
+    -DLIBEXECDIR=\""$(libexecdir)"\"
 
 bin_PROGRAMS = mousetweaks
 
-mousetweaks_SOURCES =       \
-    $(BUILT_SOURCES)        \
-    mt-main.c               \
-    mt-common.c             \
-    mt-common.h             \
-    mt-pidfile.c            \
-    mt-pidfile.h            \
-    mt-service.c            \
-    mt-service.h            \
-    mt-ctw.c                \
-    mt-ctw.h                \
-    mt-timer.c              \
-    mt-timer.h              \
-    mt-cursor.c             \
-    mt-cursor.h             \
-    mt-cursor-manager.c     \
-    mt-cursor-manager.h     \
-    mt-listener.c           \
-    mt-listener.h           \
-    mt-sig-handler.c        \
-    mt-sig-handler.h        \
-    mt-settings.c           \
-    mt-settings.h
-
-mousetweaks_CFLAGS =        \
-    $(AM_CPPFLAGS)          \
-    $(DEPENDENCIES_CFLAGS)
-
-mousetweaks_LDADD =         \
-    $(DEPENDENCIES_LIBS)
-
-BUILT_SOURCES =                     \
-    mt-enum-types.c                 \
-    mt-enum-types.h
-
-EXTRA_DIST =                        \
-    mt-enum-types.c.template        \
-    mt-enum-types.h.template
-
-CLEANFILES =                        \
-    stamp-mt-enum-types.h           \
+mousetweaks_SOURCES =           \
+    main.c                      \
+    mt_application.c            \
+    mt_application.h            \
+    mt_click.c                  \
+    mt_click.h                  \
+    mt_hover_click.c            \
+    mt_hover_click.h            \
+    mt_listener.c               \
+    mt_listener.h               \
+    mt_listener_atspi.c         \
+    mt_listener_atspi.h         \
+    mt_secondary_click.c        \
+    mt_secondary_click.h        \
+    mt_timer.c                  \
+    mt_timer.h
+
+mousetweaks_CFLAGS =            \
+    $(AM_CFLAGS)                \
+    $(DAEMON_CFLAGS)
+
+mousetweaks_LDADD =             \
+    $(DAEMON_LIBS)
+
+libexec_PROGRAMS = hover-click-window
+
+hover_click_window_SOURCES =    \
+    $(BUILT_SOURCES)            \
+    hover_click_window.c
+
+hover_click_window_CFLAGS =     \
+    $(AM_CFLAGS)                \
+    $(WINDOW_CFLAGS)
+
+hover_click_window_LDADD =      \
+    $(WINDOW_LIBS)
+
+BUILT_SOURCES =                 \
+    mousetweaks_resources.c
+
+CLEANFILES =                    \
     $(BUILT_SOURCES)
 
-# GLib mkenum
-mt-enum-types.h: stamp-mt-enum-types.h
-       @true
-
-stamp-mt-enum-types.h: mt-enum-types.h.template mt-common.h
-       $(AM_V_GEN) $(GLIB_MKENUMS) --template $< $(filter-out $<,$^) > xgen-gtbh \
-       && (cmp -s xgen-gtbh mt-enum-types.h || cp xgen-gtbh mt-enum-types.h) \
-       && rm -f xgen-gtbh \
-       && echo timestamp > $(@F)
-
-mt-enum-types.c: mt-enum-types.c.template mt-common.h
-       $(AM_V_GEN) $(GLIB_MKENUMS) --template $< $(filter-out $<,$^) > xgen-gtbc \
-       && (cmp -s xgen-gtbc mt-enum-types.c || cp xgen-gtbc mt-enum-types.c) \
-       && rm -f xgen-gtbc
+mousetweaks_resources.c: $(top_srcdir)/data/mousetweaks.gresource.xml
+       $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ \
+       --sourcedir=$(top_srcdir)/data --generate-source $<
 
 -include $(top_srcdir)/git.mk
diff --git a/src/hover_click_window.c b/src/hover_click_window.c
new file mode 100644
index 0000000..d5b5329
--- /dev/null
+++ b/src/hover_click_window.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "config.h"
+
+#define HOVER_CLICK_TYPE_WINDOW (hover_click_window_get_type ())
+#define HOVER_CLICK_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), HOVER_CLICK_TYPE_WINDOW, HoverClickWindow))
+
+typedef GtkApplicationWindowClass HoverClickWindowClass;
+
+typedef struct
+{
+    GSettings        *settings;
+    GDBusProxy       *proxy;
+    GtkWidget        *box;
+
+    GtkMenu          *menu;
+    GtkMenuItem      *orientation;
+    GtkCheckMenuItem *vertical;
+    GtkCheckMenuItem *horizontal;
+
+    GtkToggleButton  *button_primary;
+    GtkToggleButton  *button_double;
+    GtkToggleButton  *button_secondary;
+    GtkToggleButton  *button_drag;
+} HoverClickWindowPrivate;
+
+typedef struct
+{
+    GtkApplicationWindow     parent;
+    HoverClickWindowPrivate *priv;
+} HoverClickWindow;
+
+enum
+{
+    MT_CLICK_TYPE_PRIMARY,
+    MT_CLICK_TYPE_MIDDLE,
+    MT_CLICK_TYPE_SECONDARY,
+    MT_CLICK_TYPE_DOUBLE,
+    MT_CLICK_TYPE_DRAG
+};
+
+static GType hover_click_window_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE_WITH_PRIVATE (HoverClickWindow, hover_click_window, GTK_TYPE_APPLICATION_WINDOW)
+
+static gboolean
+menu_popup (GtkWidget        *widget,
+            GdkEventButton   *ev,
+            HoverClickWindow *win)
+{
+    if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_SECONDARY)
+    {
+        gtk_menu_popup_for_device (win->priv->menu,
+                                   ev->device,
+                                   NULL, NULL, NULL, NULL, NULL,
+                                   ev->button,
+                                   ev->time);
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static void
+set_click_type (HoverClickWindow *win,
+                gint              click_type)
+{
+    g_dbus_proxy_call (win->priv->proxy,
+                       "org.freedesktop.DBus.Properties.Set",
+                       g_variant_new ("(ssv)",
+                                      "org.gnome.Mousetweaks",
+                                      "ClickType",
+                                      g_variant_new_int32 (click_type)),
+                       G_DBUS_PROXY_FLAGS_NONE,
+                       -1, NULL, NULL, NULL);
+}
+
+static void
+button_primary_toggled (GtkToggleButton  *button,
+                        HoverClickWindow *win)
+{
+    if (gtk_toggle_button_get_active (button))
+        set_click_type (win, MT_CLICK_TYPE_PRIMARY);
+}
+
+static void
+button_double_toggled (GtkToggleButton  *button,
+                       HoverClickWindow *win)
+{
+    if (gtk_toggle_button_get_active (button))
+        set_click_type (win, MT_CLICK_TYPE_DOUBLE);
+}
+
+static void
+button_secondary_toggled (GtkToggleButton  *button,
+                          HoverClickWindow *win)
+{
+    if (gtk_toggle_button_get_active (button))
+        set_click_type (win, MT_CLICK_TYPE_SECONDARY);
+}
+
+static void
+button_drag_toggled (GtkToggleButton  *button,
+                     HoverClickWindow *win)
+{
+    if (gtk_toggle_button_get_active (button))
+        set_click_type (win, MT_CLICK_TYPE_DRAG);
+}
+
+static void
+click_type_changed (GDBusProxy       *proxy,
+                    GVariant         *changed,
+                    GStrv             invalidated,
+                    HoverClickWindow *win)
+{
+    GtkToggleButton *b;
+    GVariant *prop;
+
+    if (!(prop = g_dbus_proxy_get_cached_property (proxy, "ClickType")))
+        return;
+
+    switch (g_variant_get_int32 (prop))
+    {
+        case MT_CLICK_TYPE_PRIMARY:
+            b = win->priv->button_primary;
+
+            g_signal_handlers_block_by_func (b, button_primary_toggled, win);
+            gtk_toggle_button_set_active (b, TRUE);
+            g_signal_handlers_unblock_by_func (b, button_primary_toggled, win);
+            break;
+
+        case MT_CLICK_TYPE_DOUBLE:
+            b = win->priv->button_double;
+
+            g_signal_handlers_block_by_func (b, button_double_toggled, win);
+            gtk_toggle_button_set_active (b, TRUE);
+            g_signal_handlers_unblock_by_func (b, button_double_toggled, win);
+            break;
+
+        case MT_CLICK_TYPE_SECONDARY:
+            b = win->priv->button_secondary;
+
+            g_signal_handlers_block_by_func (b, button_secondary_toggled, win);
+            gtk_toggle_button_set_active (b, TRUE);
+            g_signal_handlers_unblock_by_func (b, button_secondary_toggled, win);
+            break;
+
+        case MT_CLICK_TYPE_DRAG:
+            b = win->priv->button_drag;
+
+            g_signal_handlers_block_by_func (b, button_drag_toggled, win);
+            gtk_toggle_button_set_active (b, TRUE);
+            g_signal_handlers_unblock_by_func (b, button_drag_toggled, win);
+            break;
+
+        default:
+            g_warning ("Unknown 'ClickType' value received.");
+    }
+    g_variant_unref (prop);
+}
+
+static void
+mousetweaks_proxy_ready (GDBusConnection  *connection,
+                         GAsyncResult     *result,
+                         HoverClickWindow *win)
+{
+    GError *error = NULL;
+
+    win->priv->proxy = g_dbus_proxy_new_finish (result, &error);
+
+    if (error)
+    {
+        g_warning ("%s", error->message);
+        g_error_free (error);
+        g_object_set (win, "application", NULL, NULL);
+        return;
+    }
+
+    g_signal_connect (win->priv->proxy, "g-properties-changed",
+                      G_CALLBACK (click_type_changed), win);
+}
+
+static void
+vertical_toggled (GtkCheckMenuItem *item,
+                  HoverClickWindow *win)
+{
+    if (gtk_check_menu_item_get_active (item))
+        gtk_orientable_set_orientation (GTK_ORIENTABLE (win->priv->box),
+                                        GTK_ORIENTATION_VERTICAL);
+}
+
+static void
+horizontal_toggled (GtkCheckMenuItem *item,
+                    HoverClickWindow *win)
+{
+    if (gtk_check_menu_item_get_active (item))
+        gtk_orientable_set_orientation (GTK_ORIENTABLE (win->priv->box),
+                                        GTK_ORIENTATION_HORIZONTAL);
+}
+
+static void
+orientation_notify (GObject          *object,
+                    GParamSpec       *pspec,
+                    HoverClickWindow *win)
+{
+    HoverClickWindowPrivate *priv = win->priv;
+    GtkOrientation o;
+
+    o = gtk_orientable_get_orientation (GTK_ORIENTABLE (priv->box));
+    if (o == GTK_ORIENTATION_VERTICAL)
+        gtk_widget_set_size_request (GTK_WIDGET (priv->button_primary), 110, 50);
+    else
+        gtk_widget_set_size_request (GTK_WIDGET (priv->button_primary), 80, 50);
+}
+
+static gboolean
+orientation_get_mapping (GValue   *value,
+                         GVariant *variant,
+                         gpointer  data)
+{
+    if (g_strcmp0 ("vertical", g_variant_get_string (variant, NULL)) == 0)
+        g_value_set_enum (value, GTK_ORIENTATION_VERTICAL);
+    else
+        g_value_set_enum (value, GTK_ORIENTATION_HORIZONTAL);
+
+    return TRUE;
+}
+
+static GVariant *
+orientation_set_mapping (const GValue       *value,
+                         const GVariantType *expected,
+                         gpointer            data)
+{
+    if (g_value_get_enum (value) == GTK_ORIENTATION_VERTICAL)
+        return g_variant_new_string ("vertical");
+    else
+        return g_variant_new_string ("horizontal");
+}
+
+static void
+hover_click_window_dispose (GObject *object)
+{
+    HoverClickWindowPrivate *priv = HOVER_CLICK_WINDOW (object)->priv;
+
+    g_clear_object (&priv->settings);
+    g_clear_object (&priv->proxy);
+
+    G_OBJECT_CLASS (hover_click_window_parent_class)->dispose (object);
+}
+
+static void
+set_menu_header (HoverClickWindow *win)
+{
+    HoverClickWindowPrivate *priv = win->priv;
+    PangoAttribute *attr;
+    PangoAttrList *list;
+    GtkWidget *child;
+
+    child = gtk_bin_get_child (GTK_BIN (priv->orientation));
+    list = pango_attr_list_new ();
+    attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+    pango_attr_list_insert (list, attr);
+    gtk_label_set_attributes (GTK_LABEL (child), list);
+    pango_attr_list_unref (list);
+}
+
+static void
+hover_click_window_init (HoverClickWindow *win)
+{
+    HoverClickWindowPrivate *priv;
+
+    win->priv = priv = hover_click_window_get_instance_private (win);
+    win->priv->settings = g_settings_new ("org.gnome.Mousetweaks");
+
+    gtk_widget_init_template (GTK_WIDGET (win));
+
+    gtk_window_set_default_icon_name ("input-mouse");
+
+    g_object_set (gtk_settings_get_default (),
+                  "gtk-application-prefer-dark-theme", TRUE, NULL);
+
+    g_signal_connect (priv->box, "notify::orientation",
+                      G_CALLBACK (orientation_notify), win);
+
+    g_settings_bind_with_mapping (priv->settings, "window-orientation",
+                                  priv->box, "orientation",
+                                  G_SETTINGS_BIND_DEFAULT,
+                                  orientation_get_mapping,
+                                  orientation_set_mapping,
+                                  NULL, NULL);
+
+    g_signal_connect (priv->button_primary, "toggled", G_CALLBACK (button_primary_toggled), win);
+    g_signal_connect (priv->button_double, "toggled", G_CALLBACK (button_double_toggled), win);
+    g_signal_connect (priv->button_secondary, "toggled", G_CALLBACK (button_secondary_toggled), win);
+    g_signal_connect (priv->button_drag, "toggled", G_CALLBACK (button_drag_toggled), win);
+
+    g_signal_connect (priv->button_primary, "button-press-event", G_CALLBACK (menu_popup), win);
+    g_signal_connect (priv->button_double, "button-press-event", G_CALLBACK (menu_popup), win);
+    g_signal_connect (priv->button_secondary, "button-press-event", G_CALLBACK (menu_popup), win);
+    g_signal_connect (priv->button_drag, "button-press-event", G_CALLBACK (menu_popup), win);
+
+    if (!g_settings_get_enum (priv->settings, "window-orientation"))
+        gtk_check_menu_item_set_active (priv->vertical, TRUE);
+    else
+        gtk_check_menu_item_set_active (priv->horizontal, TRUE);
+
+    g_signal_connect (priv->vertical, "toggled", G_CALLBACK (vertical_toggled), win);
+    g_signal_connect (priv->horizontal, "toggled", G_CALLBACK (horizontal_toggled), win);
+
+    set_menu_header (win);
+
+    gtk_window_stick (GTK_WINDOW (win));
+    gtk_window_set_keep_above (GTK_WINDOW (win), TRUE);
+    gtk_window_move (GTK_WINDOW (win), 30, 30);
+}
+
+static void
+hover_click_window_class_init (HoverClickWindowClass *klass)
+{
+    GtkWidgetClass *wc = GTK_WIDGET_CLASS (klass);
+
+    G_OBJECT_CLASS (klass)->dispose = hover_click_window_dispose;
+
+    gtk_widget_class_set_template_from_resource (wc, "/org/gnome/Mousetweaks/window.ui");
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, box);
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, menu);
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, orientation);
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, vertical);
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, horizontal);
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, button_primary);
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, button_double);
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, button_secondary);
+    gtk_widget_class_bind_template_child_private (wc, HoverClickWindow, button_drag);
+}
+
+static void
+application_activate (GApplication *app,
+                      gpointer      data)
+{
+    GList *windows;
+    GtkWidget *win;
+
+    if ((windows = gtk_application_get_windows (GTK_APPLICATION (app))))
+    {
+        gtk_window_present (windows->data);
+        return;
+    }
+
+    win = g_object_new (HOVER_CLICK_TYPE_WINDOW, NULL);
+    gtk_application_add_window (GTK_APPLICATION (app), GTK_WINDOW (win));
+    gtk_widget_show (win);
+
+    g_dbus_proxy_new (g_application_get_dbus_connection (app),
+                      G_DBUS_PROXY_FLAGS_NONE,
+                      NULL,
+                      "org.gnome.Mousetweaks",
+                      "/org/gnome/Mousetweaks",
+                      "org.gnome.Mousetweaks",
+                      NULL,
+                      (GAsyncReadyCallback) mousetweaks_proxy_ready,
+                      win);
+}
+
+int
+main (int argc, char **argv)
+{
+    GtkApplication *app;
+    int status;
+
+    bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+    textdomain (GETTEXT_PACKAGE);
+
+    g_set_application_name (_("Hover Click"));
+
+    app = gtk_application_new ("org.gnome.Mousetweaks.HoverClick", 0);
+    g_signal_connect (app, "activate", G_CALLBACK (application_activate), NULL);
+    status = g_application_run (G_APPLICATION (app), argc, argv);
+    g_object_unref (app);
+
+    return status;
+}
diff --git a/src/mt-ctw.h b/src/main.c
similarity index 55%
copy from src/mt-ctw.h
copy to src/main.c
index 027a849..22f8092 100644
--- a/src/mt-ctw.h
+++ b/src/main.c
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2007-2010 Gerd Kohlberger <gerdko gmail com>
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
  *
  * This file is part of Mousetweaks.
  *
@@ -17,21 +17,29 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __MT_CTW_H__
-#define __MT_CTW_H__
+#include <config.h>
+#include <glib/gi18n.h>
 
-#include <gtk/gtk.h>
+#include "mt_application.h"
 
-G_BEGIN_DECLS
+int
+main (int argc, char **argv)
+{
+    MtApplication *app;
+    gint status;
 
-gboolean        mt_ctw_init               (void);
+    bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+    bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+    textdomain (GETTEXT_PACKAGE);
+    setlocale (LC_ALL, "");
 
-void            mt_ctw_fini               (void);
+    g_set_application_name ("Mousetweaks");
 
-GtkWidget *     mt_ctw_get_window         (void);
+    app = mt_application_new ();
 
-void            mt_ctw_save_geometry      (void);
+    status = g_application_run (G_APPLICATION (app), argc, argv);
 
-G_END_DECLS
+    g_object_unref (app);
 
-#endif /* __MT_CTW_H__ */
+    return status;
+}
diff --git a/src/mt_application.c b/src/mt_application.c
new file mode 100644
index 0000000..ef7b39a
--- /dev/null
+++ b/src/mt_application.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "config.h"
+#include "mt_application.h"
+#include "mt_hover_click.h"
+#include "mt_secondary_click.h"
+
+typedef struct
+{
+    MtHoverClick     *hover_click;
+    MtSecondaryClick *secondary_click;
+
+    guint             service_id;
+    guint             watch_id;
+
+    /* command-line arguments */
+    gboolean          sc_enabled;
+    gdouble           sc_time;
+    gint              sc_threshold;
+    gboolean          hc_enabled;
+    gdouble           hc_time;
+    gint              hc_threshold;
+    gboolean          shutdown;
+} MtApplicationPrivate;
+
+struct _MtApplication
+{
+    GApplication          parent;
+    MtApplicationPrivate *priv;
+};
+
+static const gchar introspection[] =
+    "<node>"
+    "  <interface name='org.gnome.Mousetweaks'>"
+    "    <property type='i' name='ClickType' access='readwrite'/>"
+    "  </interface>"
+    "</node>";
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (MtApplication, mt_application, G_TYPE_APPLICATION)
+
+static void
+mt_application_init (MtApplication *app)
+{
+    app->priv = mt_application_get_instance_private (app);
+}
+
+static void
+mt_application_dispose (GObject *object)
+{
+    MtApplicationPrivate *priv = MT_APPLICATION (object)->priv;
+
+    g_clear_object (&priv->hover_click);
+    g_clear_object (&priv->secondary_click);
+
+    G_OBJECT_CLASS (mt_application_parent_class)->dispose (object);
+}
+
+static GVariant *
+handle_get_property (GDBusConnection *connection,
+                     const gchar     *sender,
+                     const gchar     *path,
+                     const gchar     *interface,
+                     const gchar     *property,
+                     GError         **error,
+                     MtApplication   *app)
+{
+    if (g_strcmp0 (property, "ClickType") == 0)
+    {
+        gint click_type;
+
+        g_object_get (app->priv->hover_click, "click-type", &click_type, NULL);
+        return g_variant_new_int32 (click_type);
+    }
+    return NULL;
+}
+
+static gboolean
+handle_set_property (GDBusConnection *connection,
+                     const gchar     *sender,
+                     const gchar     *path,
+                     const gchar     *interface,
+                     const gchar     *property,
+                     GVariant        *value,
+                     GError         **error,
+                     MtApplication   *app)
+{
+    if (g_strcmp0 (property, "ClickType") == 0)
+        g_object_set (app->priv->hover_click,
+                      "click-type",
+                      g_variant_get_int32 (value), NULL);
+
+    return TRUE;
+}
+
+static const GDBusInterfaceVTable interface_vtable =
+{
+    (GDBusInterfaceMethodCallFunc) NULL,
+    (GDBusInterfaceGetPropertyFunc) handle_get_property,
+    (GDBusInterfaceSetPropertyFunc) handle_set_property
+};
+
+static void
+emit_property_changed (MtHoverClick *click,
+                       GParamSpec   *pspec,
+                       GApplication *application)
+{
+    GDBusConnection *connection;
+    GVariantBuilder builder;
+    GVariantBuilder inv_builder;
+    GVariant *prop_v;
+    const gchar *path;
+    gint click_type;
+    GError *error = NULL;
+
+    g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
+    g_variant_builder_init (&inv_builder, G_VARIANT_TYPE ("as"));
+
+    g_object_get (click, "click-type", &click_type, NULL);
+    g_variant_builder_add (&builder, "{sv}", "ClickType",
+                           g_variant_new_int32 (click_type));
+
+    prop_v = g_variant_new ("(sa{sv}as)",
+                            "org.gnome.Mousetweaks",
+                            &builder, &inv_builder);
+
+    connection = g_application_get_dbus_connection (application);
+    path = g_application_get_dbus_object_path (application);
+
+    if (!g_dbus_connection_emit_signal (connection, NULL, path,
+                                        "org.freedesktop.DBus.Properties",
+                                        "PropertiesChanged",
+                                        prop_v, &error))
+    {
+        g_warning ("%s", error->message);
+        g_error_free (error);
+    }
+}
+
+static void
+click_selection_appeared (GDBusConnection *connection,
+                          const gchar     *name,
+                          const gchar     *owner,
+                          gpointer         data)
+{
+    MtApplication *app = data;
+    g_object_set (app->priv->hover_click, "show-window", FALSE, NULL);
+}
+
+static void
+click_selection_vanished (GDBusConnection *connection,
+                          const gchar     *name,
+                          gpointer         data)
+{
+    MtApplication *app = data;
+    g_object_set (app->priv->hover_click, "show-window", TRUE, NULL);
+}
+
+static gboolean
+mt_application_dbus_register (GApplication    *application,
+                              GDBusConnection *connection,
+                              const gchar     *object_path,
+                              GError         **error)
+{
+    MtApplication *app = MT_APPLICATION (application);
+    MtApplicationPrivate *priv = app->priv;
+    GDBusNodeInfo *info;
+
+    if (!G_APPLICATION_CLASS (mt_application_parent_class)->dbus_register (application,
+                                                                           connection,
+                                                                           object_path,
+                                                                           error))
+        return FALSE;
+
+    if (!(info = g_dbus_node_info_new_for_xml (introspection, error)))
+        return FALSE;
+
+    priv->service_id =
+        g_dbus_connection_register_object (connection,
+                                           object_path,
+                                           info->interfaces[0],
+                                           &interface_vtable,
+                                           app, NULL, error);
+
+    g_dbus_node_info_unref (info);
+
+    if (!priv->service_id)
+        return FALSE;
+
+    priv->watch_id =
+        g_bus_watch_name_on_connection (connection,
+                                        "org.gnome.Mousetweaks.ClickSelection",
+                                        G_BUS_NAME_WATCHER_FLAGS_NONE,
+                                        click_selection_appeared,
+                                        click_selection_vanished,
+                                        app, NULL);
+    return TRUE;
+}
+
+static void
+mt_application_dbus_unregister (GApplication    *application,
+                                GDBusConnection *connection,
+                                const gchar     *object_path)
+{
+    MtApplication *app = MT_APPLICATION (application);
+    MtApplicationPrivate *priv = app->priv;
+
+    if (priv->watch_id)
+    {
+        g_bus_unwatch_name (priv->watch_id);
+        priv->watch_id = 0;
+    }
+
+    if (priv->service_id)
+    {
+        g_dbus_connection_unregister_object (connection, priv->service_id);
+        priv->service_id = 0;
+    }
+
+    G_APPLICATION_CLASS (mt_application_parent_class)->dbus_unregister (application,
+                                                                        connection,
+                                                                        object_path);
+}
+
+static int
+mt_application_command_line (GApplication            *application,
+                             GApplicationCommandLine *cmdline)
+{
+    MtApplicationPrivate *priv = MT_APPLICATION (application)->priv;
+    gchar **argv;
+    gint i, argc;
+
+    argv = g_application_command_line_get_arguments (cmdline, &argc);
+
+    for (i = 0; i < argc; i++)
+    {
+        if (g_strcmp0 (argv[i], "-s") == 0)
+        {
+            g_application_release (application);
+            g_application_command_line_print (cmdline, "%s\n", _("Shutdown successful."));
+
+            if (priv->sc_enabled)
+                g_object_set (priv->secondary_click, "enabled", FALSE, NULL);
+
+            if (priv->hc_enabled)
+                g_object_set (priv->hover_click, "enabled", FALSE, NULL);
+
+            break;
+        }
+    }
+
+    g_strfreev (argv);
+
+    return 0;
+}
+
+static gboolean
+mt_application_local_command_line (GApplication *application,
+                                   gchar      ***arguments,
+                                   gint         *exit_status)
+{
+    MtApplicationPrivate *priv = MT_APPLICATION (application)->priv;
+    GError *error = NULL;
+    GOptionContext *context;
+    gchar **argv, **optv;
+    gint argc, optc, i;
+    gboolean version;
+    GOptionEntry entries[] =
+    {
+        { "hover-click", 0, 0, G_OPTION_ARG_NONE, &priv->hc_enabled,
+          N_("Enable Hover Click"), NULL },
+
+        { "hover-click-time", 0, 0, G_OPTION_ARG_DOUBLE, &priv->hc_time,
+          N_("Time to wait before a hover click"), "[0.5-3.0]" },
+
+        { "hover-click-threshold", 0, 0, G_OPTION_ARG_INT, &priv->hc_threshold,
+          N_("Ignore small pointer movements"), "[0-50]" },
+
+        { "secondary-click", 0, 0, G_OPTION_ARG_NONE, &priv->sc_enabled,
+          N_("Enable Secondary Click Emulation"), NULL },
+
+        { "secondary-click-time", 0, 0, G_OPTION_ARG_DOUBLE, &priv->sc_time,
+          N_("Time to wait before a secondary click"), "[0.5-3.0]" },
+
+        { "secondary-click-threshold", 0, 0, G_OPTION_ARG_INT, &priv->sc_threshold,
+          N_("Ignore small pointer movements"), "[0-50]" },
+
+        { "version", 'v', 0, G_OPTION_ARG_NONE, &version,
+          N_("Print version"), NULL },
+
+        { "shutdown", 's', 0, G_OPTION_ARG_NONE, &priv->shutdown,
+          N_("Shut down mousetweaks"), NULL },
+
+        { NULL }
+    };
+
+    priv->hc_threshold = -1;
+    priv->sc_threshold = -1;
+    *exit_status = 0;
+
+    argv = *arguments;
+    argc = g_strv_length (argv);
+
+    optv = g_new0 (gchar *, argc + 1);
+    optc = argc;
+
+    for (i = 0; i < argc; i++)
+        optv[i] = argv[i];
+
+    context = g_option_context_new (_("- GNOME Mouse accessibility service"));
+    g_option_context_add_main_entries (context, entries, NULL);
+
+    if (!g_option_context_parse (context, &optc, &optv, &error))
+    {
+        g_printerr ("%s\n", error->message);
+        g_error_free (error);
+        *exit_status = 1;
+        goto out;
+    }
+
+    if (version)
+    {
+        g_print ("%s\n", VERSION);
+        goto out;
+    }
+
+    if (!g_application_register (application, NULL, &error))
+    {
+        g_printerr ("%s\n", error->message);
+        g_error_free (error);
+        *exit_status = 1;
+        goto out;
+    }
+
+    if (priv->shutdown)
+    {
+        if (g_application_get_is_remote (application))
+        {
+            /* forward to primary instance */
+            for (i = 1; i < argc; i++)
+            {
+                g_free (argv[i]);
+                argv[i] = NULL;
+            }
+
+            argv[1] = g_strdup ("-s");
+
+            g_option_context_free (context);
+            g_free (optv);
+
+            return FALSE;
+        }
+        else
+            g_print ("%s\n", _("Nothing to shut down."));
+    }
+
+    if (g_application_get_is_remote (application))
+        g_print ("%s\n", _("Mousetweaks is already running."));
+
+out:
+    g_option_context_free (context);
+    g_free (optv);
+
+    return TRUE;
+}
+
+static void
+mt_application_startup (GApplication *application)
+{
+    MtApplication *app = MT_APPLICATION (application);
+    MtApplicationPrivate *priv = app->priv;
+
+    G_APPLICATION_CLASS (mt_application_parent_class)->startup (application);
+
+    if (priv->shutdown)
+        return;
+
+    g_application_hold (application);
+
+    priv->hover_click = mt_hover_click_new ();
+    priv->secondary_click = mt_secondary_click_new ();
+
+    g_signal_connect (priv->hover_click, "notify::click-type",
+                      G_CALLBACK (emit_property_changed), app);
+
+    /* apply command-line options */
+    if (priv->hc_enabled)
+    {
+        if (priv->hc_time >= 0.2 && priv->hc_time <= 3.0)
+            g_object_set (priv->hover_click, "time", priv->hc_time, NULL);
+
+        if (priv->hc_threshold >= 0 && priv->hc_threshold <= 30)
+            g_object_set (priv->hover_click, "threshold", priv->hc_threshold, NULL);
+
+        g_object_set (priv->hover_click, "enabled", TRUE, NULL);
+    }
+
+    if (priv->sc_enabled)
+    {
+        if (priv->sc_time >= 0.5 && priv->sc_time <= 3.0)
+            g_object_set (priv->secondary_click, "time", priv->sc_time, NULL);
+
+        if (priv->sc_threshold >= 0 && priv->sc_threshold <= 30)
+            g_object_set (priv->secondary_click, "threshold", priv->sc_threshold, NULL);
+
+        g_object_set (priv->secondary_click, "enabled", TRUE, NULL);
+    }
+}
+
+static void
+mt_application_class_init (MtApplicationClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
+
+    object_class->dispose = mt_application_dispose;
+
+    app_class->command_line = mt_application_command_line;
+    app_class->local_command_line = mt_application_local_command_line;
+    app_class->dbus_register = mt_application_dbus_register;
+    app_class->dbus_unregister = mt_application_dbus_unregister;
+    app_class->startup = mt_application_startup;
+}
+
+MtApplication *
+mt_application_new (void)
+{
+    return g_object_new (MT_TYPE_APPLICATION,
+                         "application-id", "org.gnome.Mousetweaks",
+                         NULL);
+}
diff --git a/src/mt-pidfile.h b/src/mt_application.h
similarity index 53%
rename from src/mt-pidfile.h
rename to src/mt_application.h
index 6380e64..3ffd7ff 100644
--- a/src/mt-pidfile.h
+++ b/src/mt_application.h
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2007-2010 Gerd Kohlberger <gerdko gmail com>
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
  *
  * This file is part of Mousetweaks.
  *
@@ -17,16 +17,23 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __MT_PIDFILE_H__
-#define __MT_PIDFILE_H__
+#ifndef __MT_APPLICATION_H__
+#define __MT_APPLICATION_H__
+
+#include <gio/gio.h>
 
 G_BEGIN_DECLS
 
-int       mt_pidfile_create       (void);
-pid_t     mt_pidfile_is_running   (void);
-int       mt_pidfile_kill_wait    (int signal, int sec);
-int       mt_pidfile_remove       (void);
+#define MT_TYPE_APPLICATION  (mt_application_get_type ())
+#define MT_APPLICATION(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), MT_TYPE_APPLICATION, MtApplication))
+#define MT_IS_APPLICATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MT_TYPE_APPLICATION))
+
+typedef GApplicationClass     MtApplicationClass;
+typedef struct _MtApplication MtApplication;
+
+GType               mt_application_get_type     (void) G_GNUC_CONST;
+MtApplication *     mt_application_new          (void);
 
 G_END_DECLS
 
-#endif /* __MT_PIDFILE_H__ */
+#endif /* __MT_APPLICATION_H__ */
diff --git a/src/mt_click.c b/src/mt_click.c
new file mode 100644
index 0000000..b8708a1
--- /dev/null
+++ b/src/mt_click.c
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#include <gio/gio.h>
+
+#include "mt_click.h"
+
+struct _MtClickPrivate
+{
+    GSettings  *settings;
+    MtListener *listener;
+    MtTimer    *timer;
+
+    gint        threshold;
+    gdouble     time;
+    guint       enabled : 1;
+};
+
+enum
+{
+    PROP_0,
+    PROP_ENABLED,
+    PROP_TIME,
+    PROP_THRESHOLD
+};
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (MtClick, mt_click, G_TYPE_OBJECT)
+
+static void
+timer_finished (MtTimer *timer,
+                MtClick *click)
+{
+    MtClickClass *klass = MT_CLICK_GET_CLASS (click);
+
+    if (click->priv->enabled && klass->timer_finished)
+        klass->timer_finished (click, timer, click->priv->listener);
+}
+
+static void
+listener_motion_event (MtListener *listener,
+                       MtEvent    *event,
+                       MtClick    *click)
+{
+    MtClickClass *klass = MT_CLICK_GET_CLASS (click);
+
+    if (click->priv->enabled && klass->motion_event)
+        klass->motion_event (click, listener, event, click->priv->timer);
+}
+
+static void
+listener_button_event (MtListener *listener,
+                       MtEvent    *event,
+                       MtClick    *click)
+{
+    MtClickClass *klass = MT_CLICK_GET_CLASS (click);
+
+    if (click->priv->enabled && klass->button_event)
+        klass->button_event (click, listener, event, click->priv->timer);
+}
+
+static void
+mt_click_init (MtClick *click)
+{
+    click->priv = mt_click_get_instance_private (click);
+    click->priv->settings = g_settings_new ("org.gnome.desktop.a11y.mouse");
+    click->priv->listener = mt_listener_get_default ();
+    click->priv->timer = mt_timer_new ();
+
+    g_signal_connect (click->priv->listener, "motion-event",
+                      G_CALLBACK (listener_motion_event), click);
+    g_signal_connect (click->priv->listener, "button-event",
+                      G_CALLBACK (listener_button_event), click);
+    g_signal_connect (click->priv->timer, "finished",
+                      G_CALLBACK (timer_finished), click);
+
+    g_object_bind_property (click, "time", click->priv->timer, "target", 0);
+}
+
+static void
+mt_click_dispose (GObject *object)
+{
+    MtClickPrivate *priv = MT_CLICK (object)->priv;
+
+    g_clear_object (&priv->settings);
+    g_clear_object (&priv->listener);
+    g_clear_object (&priv->timer);
+
+    G_OBJECT_CLASS (mt_click_parent_class)->dispose (object);
+}
+
+static void
+mt_click_set_property (GObject      *object,
+                       guint         prop_id,
+                       const GValue *value,
+                       GParamSpec   *pspec)
+{
+    MtClickPrivate *priv = MT_CLICK (object)->priv;
+
+    switch (prop_id)
+    {
+        case PROP_ENABLED:
+            priv->enabled = g_value_get_boolean (value);
+            g_object_notify_by_pspec (object, pspec);
+            break;
+
+        case PROP_TIME:
+            priv->time = g_value_get_double (value);
+            g_object_notify_by_pspec (object, pspec);
+            break;
+
+        case PROP_THRESHOLD:
+            priv->threshold = g_value_get_int (value);
+            g_object_notify_by_pspec (object, pspec);
+            break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mt_click_get_property (GObject    *object,
+                       guint       prop_id,
+                       GValue     *value,
+                       GParamSpec *pspec)
+{
+    MtClickPrivate *priv = MT_CLICK (object)->priv;
+
+    switch (prop_id)
+    {
+        case PROP_ENABLED:
+            g_value_set_boolean (value, priv->enabled);
+            break;
+
+        case PROP_TIME:
+            g_value_set_double (value, (gdouble) priv->time);
+            break;
+
+        case PROP_THRESHOLD:
+            g_value_set_int (value, priv->threshold);
+            break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mt_click_class_init (MtClickClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    object_class->dispose = mt_click_dispose;
+    object_class->get_property = mt_click_get_property;
+    object_class->set_property = mt_click_set_property;
+
+    g_object_class_install_property (object_class, PROP_ENABLED,
+        g_param_spec_boolean ("enabled", "Enabled",
+                              "Enable click feature",
+                              FALSE,
+                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property (object_class, PROP_TIME,
+        g_param_spec_double ("time", "Time",
+                             "Acceptance delay for a click",
+                             0.2, 3.0, 1.2,
+                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property (object_class, PROP_THRESHOLD,
+        g_param_spec_int ("threshold", "Motion threshold",
+                          "Ignore pointer movement below this threshold",
+                          0, 30, 10,
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+void
+mt_click_bind_setting (MtClick     *click,
+                       const gchar *prop,
+                       const gchar *key)
+{
+    g_return_if_fail (MT_IS_CLICK (click));
+
+    g_settings_bind (click->priv->settings, key, click, prop,
+                     G_SETTINGS_BIND_DEFAULT | G_SETTINGS_BIND_NO_SENSITIVITY);
+}
+
+MtListener *
+mt_click_get_listener (MtClick *click)
+{
+    g_return_val_if_fail (MT_IS_CLICK (click), NULL);
+    return click->priv->listener;
+}
+
+MtTimer *
+mt_click_get_timer (MtClick *click)
+{
+    g_return_val_if_fail (MT_IS_CLICK (click), NULL);
+    return click->priv->timer;
+}
diff --git a/src/mt_click.h b/src/mt_click.h
new file mode 100644
index 0000000..36d3874
--- /dev/null
+++ b/src/mt_click.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#ifndef __MT_CLICK_H__
+#define __MT_CLICK_H__
+
+#include <glib-object.h>
+
+#include "mt_listener.h"
+#include "mt_timer.h"
+
+G_BEGIN_DECLS
+
+#define MT_TYPE_CLICK         (mt_click_get_type ())
+#define MT_CLICK(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), MT_TYPE_CLICK, MtClick))
+#define MT_CLICK_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), MT_TYPE_CLICK, MtClickClass))
+#define MT_IS_CLICK(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), MT_TYPE_CLICK))
+#define MT_IS_CLICK_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), MT_TYPE_CLICK))
+#define MT_CLICK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MT_TYPE_CLICK, MtClickClass))
+
+typedef struct _MtClickPrivate MtClickPrivate;
+typedef struct _MtClickClass   MtClickClass;
+typedef struct _MtClick        MtClick;
+
+struct _MtClickClass
+{
+    GObjectClass parent;
+
+    void (* button_event)   (MtClick    *click,
+                             MtListener *listener,
+                             MtEvent    *event,
+                             MtTimer    *timer);
+
+    void (* motion_event)   (MtClick    *click,
+                             MtListener *listener,
+                             MtEvent    *event,
+                             MtTimer    *timer);
+
+    void (* timer_finished) (MtClick    *click,
+                             MtTimer    *timer,
+                             MtListener *listener);
+};
+
+struct _MtClick
+{
+    GObject         parent;
+    MtClickPrivate *priv;
+};
+
+GType           mt_click_get_type       (void) G_GNUC_CONST;
+
+void            mt_click_bind_setting   (MtClick     *click,
+                                         const gchar *prop,
+                                         const gchar *key);
+
+MtListener *    mt_click_get_listener   (MtClick     *click);
+
+MtTimer *       mt_click_get_timer      (MtClick     *click);
+
+G_END_DECLS
+
+#endif /* __MT_CLICK_H__ */
diff --git a/src/mt_hover_click.c b/src/mt_hover_click.c
new file mode 100644
index 0000000..3619328
--- /dev/null
+++ b/src/mt_hover_click.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#include <signal.h>
+
+#include "config.h"
+#include "mt_hover_click.h"
+
+typedef struct
+{
+    gint  x;
+    gint  y;
+    gint  click_type;
+    GPid  window_pid;
+    guint show_window     : 1;
+    guint window_launched : 1;
+    guint drag_started    : 1;
+} MtHoverClickPrivate;
+
+struct _MtHoverClick
+{
+    MtClick              parent;
+    MtHoverClickPrivate *priv;
+};
+
+enum
+{
+    PROP_0,
+    PROP_CLICK_TYPE,
+    PROP_SHOW_WINDOW
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MtHoverClick, mt_hover_click, MT_TYPE_CLICK)
+
+static void
+mt_hover_click_init (MtHoverClick *click)
+{
+    click->priv = mt_hover_click_get_instance_private (click);
+}
+
+static void
+mt_hover_click_timer_finished (MtClick    *click,
+                               MtTimer    *timer,
+                               MtListener *listener)
+{
+    MtHoverClickPrivate *priv = MT_HOVER_CLICK (click)->priv;
+
+    if (priv->drag_started)
+    {
+        priv->drag_started = FALSE;
+        mt_listener_send_event (listener, 1, MT_SEND_BUTTON_RELEASE);
+        g_object_set (click, "click-type", MT_CLICK_TYPE_PRIMARY, NULL);
+        return;
+    }
+
+    switch (priv->click_type)
+    {
+        case MT_CLICK_TYPE_PRIMARY:
+            mt_listener_send_event (listener, 1, MT_SEND_CLICK);
+            break;
+
+        case MT_CLICK_TYPE_MIDDLE:
+            mt_listener_send_event (listener, 2, MT_SEND_CLICK);
+            g_object_set (click, "click-type", MT_CLICK_TYPE_PRIMARY, NULL);
+            break;
+
+        case MT_CLICK_TYPE_SECONDARY:
+            mt_listener_send_event (listener, 3, MT_SEND_CLICK);
+            g_object_set (click, "click-type", MT_CLICK_TYPE_PRIMARY, NULL);
+            break;
+
+        case MT_CLICK_TYPE_DOUBLE:
+            mt_listener_send_event (listener, 1, MT_SEND_CLICK);
+            mt_listener_send_event (listener, 1, MT_SEND_CLICK);
+            g_object_set (click, "click-type", MT_CLICK_TYPE_PRIMARY, NULL);
+            break;
+
+        case MT_CLICK_TYPE_DRAG:
+            mt_listener_send_event (listener, 1, MT_SEND_BUTTON_PRESS);
+            priv->drag_started = TRUE;
+            break;
+
+        default:
+            g_warning ("Unknown click-type selected.");
+            break;
+    }
+}
+
+static void
+mt_hover_click_motion_event (MtClick    *click,
+                             MtListener *listener,
+                             MtEvent    *event,
+                             MtTimer    *timer)
+{
+    MtHoverClickPrivate *priv = MT_HOVER_CLICK (click)->priv;
+    gint t;
+
+    g_object_get (click, "threshold", &t, NULL);
+
+    if (priv->x == -1 && priv->y == -1)
+    {
+        priv->x = event->x;
+        priv->y = event->y;
+    }
+    else if (ABS (priv->x - event->x) > t || ABS (priv->y - event->y) > t)
+    {
+        priv->x = event->x;
+        priv->y = event->y;
+        mt_timer_start (timer);
+    }
+}
+
+static void
+mt_hover_click_button_event (MtClick    *click,
+                             MtListener *listener,
+                             MtEvent    *event,
+                             MtTimer    *timer)
+{
+    MtHoverClickPrivate *priv = MT_HOVER_CLICK (click)->priv;
+
+    if (mt_timer_is_running (timer))
+    {
+        mt_timer_stop (timer);
+
+        if (priv->drag_started)
+            priv->drag_started = FALSE;
+
+        if (priv->click_type != MT_CLICK_TYPE_PRIMARY)
+            g_object_set (click, "click-type", MT_CLICK_TYPE_PRIMARY, NULL);
+    }
+}
+
+static void
+mt_hover_click_window_show (MtHoverClick *click)
+{
+    MtHoverClickPrivate *priv = click->priv;
+
+    if (!priv->window_launched)
+    {
+        gchar *args[2] = { LIBEXECDIR "/hover-click-window", NULL };
+        GError *error = NULL;
+
+        g_spawn_async (NULL, args, NULL, 0, NULL, NULL,
+                       &priv->window_pid, &error);
+        if (error)
+        {
+            g_warning ("%s", error->message);
+            g_error_free (error);
+            return;
+        }
+        priv->window_launched = TRUE;
+    }
+}
+
+static void
+mt_hover_click_window_hide (MtHoverClick *click)
+{
+    MtHoverClickPrivate *priv = click->priv;
+
+    if (priv->window_launched)
+    {
+        kill (priv->window_pid, SIGHUP);
+        g_spawn_close_pid (priv->window_pid);
+        priv->window_launched = FALSE;
+    }
+}
+
+static void
+mt_hover_click_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+    MtHoverClickPrivate *priv = MT_HOVER_CLICK (object)->priv;
+
+    switch (prop_id)
+    {
+        case PROP_CLICK_TYPE:
+            priv->click_type = g_value_get_int (value);
+            g_object_notify_by_pspec (object, pspec);
+            break;
+
+        case PROP_SHOW_WINDOW:
+            priv->show_window = g_value_get_boolean (value);
+            g_object_notify_by_pspec (object, pspec);
+            if (priv->show_window)
+            {
+                gboolean enabled;
+
+                g_object_get (object, "enabled", &enabled, NULL);
+
+                if (enabled)
+                    mt_hover_click_window_show (MT_HOVER_CLICK (object));
+            }
+            else
+                mt_hover_click_window_hide (MT_HOVER_CLICK (object));
+            break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mt_hover_click_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+    MtHoverClickPrivate *priv = MT_HOVER_CLICK (object)->priv;
+
+    switch (prop_id)
+    {
+        case PROP_CLICK_TYPE:
+            g_value_set_int (value, priv->click_type);
+            break;
+
+        case PROP_SHOW_WINDOW:
+            g_value_set_boolean (value, priv->show_window);
+            break;
+
+        default:
+            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mt_hover_click_class_init (MtHoverClickClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MtClickClass *click_class = MT_CLICK_CLASS (klass);
+
+    object_class->get_property = mt_hover_click_get_property;
+    object_class->set_property = mt_hover_click_set_property;
+
+    click_class->button_event = mt_hover_click_button_event;
+    click_class->motion_event = mt_hover_click_motion_event;
+    click_class->timer_finished = mt_hover_click_timer_finished;
+
+    g_object_class_install_property (object_class, PROP_CLICK_TYPE,
+        g_param_spec_int ("click-type", "Click Type",
+                          "The active click type",
+                          MT_CLICK_TYPE_PRIMARY, MT_CLICK_TYPE_DRAG, MT_CLICK_TYPE_PRIMARY,
+                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+    g_object_class_install_property (object_class, PROP_SHOW_WINDOW,
+        g_param_spec_boolean ("show-window", "Show Window",
+                              "Whether to show the click selection window",
+                              FALSE,
+                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+click_enabled_notify (MtClick    *click,
+                      GParamSpec *pspec,
+                      gpointer    data)
+{
+    MtHoverClickPrivate *priv = MT_HOVER_CLICK (click)->priv;
+    gboolean enabled;
+
+    g_object_get (click, "enabled", &enabled, NULL);
+
+    if (enabled)
+    {
+        priv->x = -1;
+        priv->y = -1;
+        if (priv->show_window)
+            mt_hover_click_window_show (MT_HOVER_CLICK (click));
+    }
+    else
+        mt_hover_click_window_hide (MT_HOVER_CLICK (click));
+}
+
+MtHoverClick *
+mt_hover_click_new (void)
+{
+    MtHoverClick *click;
+
+    click = g_object_new (MT_TYPE_HOVER_CLICK, NULL);
+
+    mt_click_bind_setting (MT_CLICK (click), "enabled", "dwell-click-enabled");
+    mt_click_bind_setting (MT_CLICK (click), "threshold", "dwell-threshold");
+    mt_click_bind_setting (MT_CLICK (click), "time", "dwell-time");
+
+    g_signal_connect (click, "notify::enabled",
+                      G_CALLBACK (click_enabled_notify), NULL);
+
+    return click;
+}
diff --git a/src/mt_hover_click.h b/src/mt_hover_click.h
new file mode 100644
index 0000000..e979a94
--- /dev/null
+++ b/src/mt_hover_click.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#ifndef __MT_HOVER_CLICK_H__
+#define __MT_HOVER_CLICK_H__
+
+#include "mt_click.h"
+
+G_BEGIN_DECLS
+
+#define MT_TYPE_HOVER_CLICK  (mt_hover_click_get_type ())
+#define MT_HOVER_CLICK(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), MT_TYPE_HOVER_CLICK, MtHoverClick))
+#define MT_IS_HOVER_CLICK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MT_TYPE_HOVER_CLICK))
+
+typedef MtClickClass         MtHoverClickClass;
+typedef struct _MtHoverClick MtHoverClick;
+
+enum
+{
+    MT_CLICK_TYPE_PRIMARY,
+    MT_CLICK_TYPE_MIDDLE,
+    MT_CLICK_TYPE_SECONDARY,
+    MT_CLICK_TYPE_DOUBLE,
+    MT_CLICK_TYPE_DRAG
+};
+
+GType           mt_hover_click_get_type     (void) G_GNUC_CONST;
+MtHoverClick *  mt_hover_click_new          (void);
+
+G_END_DECLS
+
+#endif /* __MT_HOVER_CLICK_H__ */
diff --git a/src/mt_listener.c b/src/mt_listener.c
new file mode 100644
index 0000000..8b0b71b
--- /dev/null
+++ b/src/mt_listener.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright, 2013 Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#include "mt_listener.h"
+#include "mt_listener_atspi.h"
+
+enum
+{
+    MOTION_EVENT,
+    BUTTON_EVENT,
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_ABSTRACT_TYPE (MtListener, mt_listener, G_TYPE_OBJECT)
+
+static void
+mt_listener_init (MtListener *listener)
+{
+}
+
+static void
+mt_listener_class_init (MtListenerClass *klass)
+{
+    signals[MOTION_EVENT] =
+        g_signal_new ("motion-event",
+                      G_OBJECT_CLASS_TYPE (klass),
+                      G_SIGNAL_RUN_LAST,
+                      0, NULL, NULL,
+                      g_cclosure_marshal_VOID__BOXED,
+                      G_TYPE_NONE,
+                      1, MT_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+    signals[BUTTON_EVENT] =
+        g_signal_new ("button-event",
+                      G_OBJECT_CLASS_TYPE (klass),
+                      G_SIGNAL_RUN_LAST,
+                      0, NULL, NULL,
+                      g_cclosure_marshal_VOID__BOXED,
+                      G_TYPE_NONE,
+                      1, MT_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+}
+
+static MtEvent *
+mt_event_copy (const MtEvent *event)
+{
+    return g_memdup (event, sizeof (MtEvent));
+}
+
+static void
+mt_event_free (MtEvent *event)
+{
+    g_free (event);
+}
+
+G_DEFINE_BOXED_TYPE (MtEvent, mt_event, mt_event_copy, mt_event_free)
+
+MtListener *
+mt_listener_get_default (void)
+{
+    static MtListener *listener = NULL;
+
+    if (listener == NULL)
+    {
+        listener = mt_listener_atspi_new ();
+        g_object_add_weak_pointer (G_OBJECT (listener), (gpointer *) &listener);
+        return listener;
+    }
+    return g_object_ref (listener);
+}
+
+void
+mt_listener_emit_button_event (MtListener *listener,
+                               MtEventType type,
+                               gint        button,
+                               gint        x,
+                               gint        y)
+{
+    MtEvent ev;
+
+    ev.type = type;
+    ev.button = button;
+    ev.x = x;
+    ev.y = y;
+
+    g_signal_emit (listener, signals[BUTTON_EVENT], 0, &ev);
+}
+
+void
+mt_listener_emit_motion_event (MtListener *listener,
+                               gint        x,
+                               gint        y)
+{
+    MtEvent ev;
+
+    ev.type = MT_EVENT_MOTION;
+    ev.button = 0;
+    ev.x = x;
+    ev.y = y;
+
+    g_signal_emit (listener, signals[MOTION_EVENT], 0, &ev);
+}
+
+void
+mt_listener_query_pointer (MtListener *listener,
+                           gint       *x,
+                           gint       *y)
+{
+    MtListenerClass *klass = MT_LISTENER_GET_CLASS (listener);
+
+    if (klass->query_pointer)
+        klass->query_pointer (listener, x, y);
+}
+
+void
+mt_listener_send_event (MtListener *listener,
+                        gint        button,
+                        MtSendType  type)
+{
+    MtListenerClass *klass = MT_LISTENER_GET_CLASS (listener);
+
+    if (klass->send_event)
+        klass->send_event (listener, button, type);
+}
diff --git a/src/mt_listener.h b/src/mt_listener.h
new file mode 100644
index 0000000..94e0fd8
--- /dev/null
+++ b/src/mt_listener.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#ifndef __MT_LISTENER_H__
+#define __MT_LISTENER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MT_TYPE_EVENT            (mt_event_get_type ())
+#define MT_TYPE_LISTENER         (mt_listener_get_type ())
+#define MT_LISTENER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), MT_TYPE_LISTENER, MtListener))
+#define MT_LISTENER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), MT_TYPE_LISTENER, MtListenerClass))
+#define MT_IS_LISTENER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), MT_TYPE_LISTENER))
+#define MT_IS_LISTENER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), MT_TYPE_LISTENER))
+#define MT_LISTENER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MT_TYPE_LISTENER, MtListenerClass))
+
+typedef GObject MtListener;
+
+typedef enum
+{
+    MT_SEND_CLICK,
+    MT_SEND_BUTTON_PRESS,
+    MT_SEND_BUTTON_RELEASE
+} MtSendType;
+
+typedef struct
+{
+    GObjectClass parent;
+
+    void (* query_pointer) (MtListener *listener,
+                            gint       *x,
+                            gint       *y);
+
+    void (* send_event)    (MtListener *listener,
+                            gint        button,
+                            MtSendType  type);
+} MtListenerClass;
+
+typedef enum
+{
+    MT_EVENT_MOTION,
+    MT_EVENT_BUTTON_PRESS,
+    MT_EVENT_BUTTON_RELEASE
+} MtEventType;
+
+typedef struct
+{
+    MtEventType type;
+    gint        x;
+    gint        y;
+    gint        button;
+} MtEvent;
+
+GType           mt_event_get_type               (void) G_GNUC_CONST;
+GType           mt_listener_get_type            (void) G_GNUC_CONST;
+MtListener *    mt_listener_get_default         (void);
+
+void            mt_listener_emit_button_event   (MtListener *listener,
+                                                 MtEventType type,
+                                                 gint        button,
+                                                 gint        x,
+                                                 gint        y);
+
+void            mt_listener_emit_motion_event   (MtListener *listener,
+                                                 gint        x,
+                                                 gint        y);
+
+void            mt_listener_query_pointer       (MtListener *listener,
+                                                 gint       *x,
+                                                 gint       *y);
+
+void            mt_listener_send_event          (MtListener *listener,
+                                                 gint        button,
+                                                 MtSendType  type);
+
+G_END_DECLS
+
+#endif /* __MT_LISTENER_H__ */
diff --git a/src/mt_listener_atspi.c b/src/mt_listener_atspi.c
new file mode 100644
index 0000000..c7a8526
--- /dev/null
+++ b/src/mt_listener_atspi.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#include <string.h>
+#include <atspi/atspi.h>
+
+#include "mt_listener_atspi.h"
+
+typedef struct
+{
+    AtspiEventListener *motion;
+    AtspiEventListener *button;
+    gint                x;
+    gint                y;
+} MtListenerAtspiPrivate;
+
+struct _MtListenerAtspi
+{
+    MtListener              parent;
+    MtListenerAtspiPrivate *priv;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MtListenerAtspi, mt_listener_atspi, MT_TYPE_LISTENER)
+
+static void
+atspi_motion_event (const AtspiEvent *event,
+                    MtListenerAtspi  *listener)
+{
+    listener->priv->x = event->detail1;
+    listener->priv->y = event->detail2;
+
+    mt_listener_emit_motion_event (MT_LISTENER (listener),
+                                   listener->priv->x,
+                                   listener->priv->y);
+}
+
+static void
+atspi_button_event (const AtspiEvent *event,
+                    MtListenerAtspi  *listener)
+{
+    if (strlen (event->type) != 15)
+        return;
+
+    listener->priv->x = event->detail1;
+    listener->priv->y = event->detail2;
+
+    mt_listener_emit_button_event (MT_LISTENER (listener),
+                                   *(event->type + 14) == 'p' ? MT_EVENT_BUTTON_PRESS :
+                                                                MT_EVENT_BUTTON_RELEASE,
+                                   *(event->type + 13) - 0x30,
+                                   listener->priv->x,
+                                   listener->priv->y);
+}
+
+static void
+mt_listener_atspi_init (MtListenerAtspi *listener)
+{
+    GError *error = NULL;
+
+    listener->priv = mt_listener_atspi_get_instance_private (listener);
+
+    atspi_init ();
+
+    listener->priv->motion = atspi_event_listener_new
+        ((AtspiEventListenerCB) atspi_motion_event, listener, NULL);
+
+    atspi_event_listener_register (listener->priv->motion, "mouse:abs", &error);
+    if (error)
+    {
+        g_warning ("%s", error->message);
+        g_clear_error (&error);
+    }
+
+    listener->priv->button = atspi_event_listener_new
+        ((AtspiEventListenerCB) atspi_button_event, listener, NULL);
+
+    atspi_event_listener_register (listener->priv->button, "mouse:button", &error);
+    if (error)
+    {
+        g_warning ("%s", error->message);
+        g_error_free (error);
+    }
+}
+
+static void
+mt_listener_atspi_dispose (GObject *object)
+{
+    MtListenerAtspiPrivate *priv = MT_LISTENER_ATSPI (object)->priv;
+
+    if (priv->motion)
+    {
+        atspi_event_listener_deregister (priv->motion, "mouse:abs", NULL);
+        g_clear_object (&priv->motion);
+    }
+
+    if (priv->button)
+    {
+        atspi_event_listener_deregister (priv->button, "mouse:button", NULL);
+        g_clear_object (&priv->motion);
+    }
+
+    G_OBJECT_CLASS (mt_listener_atspi_parent_class)->dispose (object);
+}
+
+static void
+mt_listener_atspi_finalize (GObject *object)
+{
+    gint leaks;
+
+    if ((leaks = atspi_exit ()))
+        g_warning ("AT-SPI reported %i leaks.", leaks);
+
+    G_OBJECT_CLASS (mt_listener_atspi_parent_class)->finalize (object);
+}
+
+static void
+mt_listener_atspi_query_pointer (MtListener *listener,
+                                 gint       *x,
+                                 gint       *y)
+{
+    MtListenerAtspiPrivate *priv = MT_LISTENER_ATSPI (listener)->priv;
+
+    if (x)
+        *x = priv->x;
+
+    if (y)
+        *y = priv->y;
+}
+
+static void
+mt_listener_atspi_send_event (MtListener *listener,
+                              gint        button,
+                              MtSendType  type)
+{
+    MtListenerAtspiPrivate *priv = MT_LISTENER_ATSPI (listener)->priv;
+    GError *error = NULL;
+    gchar name[4] = { 'b', '1', 'c', '\0' };
+
+    g_return_if_fail (button >= 1 && button <= 3);
+
+    switch (type)
+    {
+        case MT_SEND_CLICK:
+            name[2] = 'c';
+            break;
+        case MT_SEND_BUTTON_PRESS:
+            name[2] = 'p';
+            break;
+        case MT_SEND_BUTTON_RELEASE:
+            name[2] = 'r';
+            break;
+        default:
+            g_warning ("Unknown SendType.");
+            return;
+    }
+
+    name[1] = (gchar) button + 0x30;
+
+    atspi_generate_mouse_event (priv->x, priv->y, name, &error);
+
+    if (error)
+    {
+        g_warning ("%s", error->message);
+        g_error_free (error);
+    }
+}
+
+static void
+mt_listener_atspi_class_init (MtListenerAtspiClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MtListenerClass *listener_class = MT_LISTENER_CLASS (klass);
+
+    object_class->dispose = mt_listener_atspi_dispose;
+    object_class->finalize = mt_listener_atspi_finalize;
+
+    listener_class->query_pointer = mt_listener_atspi_query_pointer;
+    listener_class->send_event = mt_listener_atspi_send_event;
+}
+
+MtListener *
+mt_listener_atspi_new (void)
+{
+    return g_object_new (MT_TYPE_LISTENER_ATSPI, NULL);
+}
diff --git a/src/mt-ctw.h b/src/mt_listener_atspi.h
similarity index 51%
copy from src/mt-ctw.h
copy to src/mt_listener_atspi.h
index 027a849..da8cfd9 100644
--- a/src/mt-ctw.h
+++ b/src/mt_listener_atspi.h
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2007-2010 Gerd Kohlberger <gerdko gmail com>
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
  *
  * This file is part of Mousetweaks.
  *
@@ -17,21 +17,27 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __MT_CTW_H__
-#define __MT_CTW_H__
+#ifndef __MT_LISTENER_ATSPI_H__
+#define __MT_LISTENER_ATSPI_H__
 
-#include <gtk/gtk.h>
+#include "mt_listener.h"
 
 G_BEGIN_DECLS
 
-gboolean        mt_ctw_init               (void);
+#define MT_TYPE_LISTENER_ATSPI  (mt_listener_atspi_get_type ())
+#define MT_LISTENER_ATSPI(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), MT_TYPE_LISTENER_ATSPI, MtListenerAtspi))
+#define MT_IS_LISTENER_ATSPI(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MT_TYPE_LISTENER_ATSPI))
 
-void            mt_ctw_fini               (void);
+typedef struct _MtListenerAtspi MtListenerAtspi;
 
-GtkWidget *     mt_ctw_get_window         (void);
+typedef struct
+{
+    MtListenerClass parent;
+} MtListenerAtspiClass;
 
-void            mt_ctw_save_geometry      (void);
+GType           mt_listener_atspi_get_type  (void) G_GNUC_CONST;
+MtListener *    mt_listener_atspi_new       (void);
 
 G_END_DECLS
 
-#endif /* __MT_CTW_H__ */
+#endif /* __MT_LISTENER_ATSPI_H__ */
diff --git a/src/mt_secondary_click.c b/src/mt_secondary_click.c
new file mode 100644
index 0000000..0eb81fc
--- /dev/null
+++ b/src/mt_secondary_click.c
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
+ *
+ * This file is part of Mousetweaks.
+ *
+ * Mousetweaks 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.
+ *
+ * Mousetweaks 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/>.
+ */
+
+#include "mt_secondary_click.h"
+
+typedef struct
+{
+    gint  x;
+    gint  y;
+    guint activate : 1;
+} MtSecondaryClickPrivate;
+
+struct _MtSecondaryClick
+{
+    MtClick                  parent;
+    MtSecondaryClickPrivate *priv;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (MtSecondaryClick, mt_secondary_click, MT_TYPE_CLICK)
+
+static void
+mt_secondary_click_init (MtSecondaryClick *click)
+{
+    click->priv = mt_secondary_click_get_instance_private (click);
+}
+
+static void
+mt_secondary_click_timer_finished (MtClick    *click,
+                                   MtTimer    *timer,
+                                   MtListener *listener)
+{
+    MtSecondaryClickPrivate *priv = MT_SECONDARY_CLICK (click)->priv;
+
+    priv->activate = TRUE;
+}
+
+static void
+mt_secondary_click_motion_event (MtClick    *click,
+                                 MtListener *listener,
+                                 MtEvent    *event,
+                                 MtTimer    *timer)
+{
+    MtSecondaryClickPrivate *priv = MT_SECONDARY_CLICK (click)->priv;
+    gint t;
+
+    if (!mt_timer_is_running (timer))
+        return;
+
+    g_object_get (click, "threshold", &t, NULL);
+
+    if (ABS (priv->x - event->x) > t || ABS (priv->y - event->y) > t)
+    {
+        mt_timer_stop (timer);
+        priv->activate = FALSE;
+    }
+}
+
+static void
+mt_secondary_click_button_event (MtClick    *click,
+                                 MtListener *listener,
+                                 MtEvent    *event,
+                                 MtTimer    *timer)
+{
+    MtSecondaryClickPrivate *priv = MT_SECONDARY_CLICK (click)->priv;
+
+    if (event->button != 1)
+        return;
+
+    if (event->type == MT_EVENT_BUTTON_PRESS)
+    {
+        priv->x = event->x;
+        priv->y = event->y;
+        mt_timer_start (timer);
+    }
+    else
+    {
+        mt_timer_stop (timer);
+        if (priv->activate)
+        {
+            mt_listener_send_event (listener, 3, MT_SEND_CLICK);
+            priv->activate = FALSE;
+        }
+    }
+}
+
+static void
+mt_secondary_click_class_init (MtSecondaryClickClass *klass)
+{
+    MtClickClass *click_class = MT_CLICK_CLASS (klass);
+
+    click_class->button_event = mt_secondary_click_button_event;
+    click_class->motion_event = mt_secondary_click_motion_event;
+    click_class->timer_finished = mt_secondary_click_timer_finished;
+}
+
+MtSecondaryClick *
+mt_secondary_click_new (void)
+{
+    MtSecondaryClick *click;
+    const gint default_threshold = 10;
+
+    click = g_object_new (MT_TYPE_SECONDARY_CLICK, NULL);
+
+    mt_click_bind_setting (MT_CLICK (click), "enabled", "secondary-click-enabled");
+    mt_click_bind_setting (MT_CLICK (click), "time", "secondary-click-time");
+    g_object_set (click, "threshold", default_threshold, NULL);
+
+    return click;
+}
diff --git a/src/mt-ctw.h b/src/mt_secondary_click.h
similarity index 51%
rename from src/mt-ctw.h
rename to src/mt_secondary_click.h
index 027a849..21695ef 100644
--- a/src/mt-ctw.h
+++ b/src/mt_secondary_click.h
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2007-2010 Gerd Kohlberger <gerdko gmail com>
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
  *
  * This file is part of Mousetweaks.
  *
@@ -17,21 +17,23 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __MT_CTW_H__
-#define __MT_CTW_H__
+#ifndef __MT_SECONDARY_CLICK_H__
+#define __MT_SECONDARY_CLICK_H__
 
-#include <gtk/gtk.h>
+#include "mt_click.h"
 
 G_BEGIN_DECLS
 
-gboolean        mt_ctw_init               (void);
+#define MT_TYPE_SECONDARY_CLICK  (mt_secondary_click_get_type ())
+#define MT_SECONDARY_CLICK(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), MT_TYPE_SECONDARY_CLICK, 
MtSecondaryClick))
+#define MT_IS_SECONDARY_CLICK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MT_TYPE_SECONDARY_CLICK))
 
-void            mt_ctw_fini               (void);
+typedef MtClickClass             MtSecondaryClickClass;
+typedef struct _MtSecondaryClick MtSecondaryClick;
 
-GtkWidget *     mt_ctw_get_window         (void);
-
-void            mt_ctw_save_geometry      (void);
+GType               mt_secondary_click_get_type     (void) G_GNUC_CONST;
+MtSecondaryClick *  mt_secondary_click_new          (void);
 
 G_END_DECLS
 
-#endif /* __MT_CTW_H__ */
+#endif /* __MT_SECONDARY_CLICK_H__ */
diff --git a/src/mt-timer.c b/src/mt_timer.c
similarity index 53%
rename from src/mt-timer.c
rename to src/mt_timer.c
index 8c68154..fedfa1c 100644
--- a/src/mt-timer.c
+++ b/src/mt_timer.c
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2007-2010 Gerd Kohlberger <gerdko gmail com>
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
  *
  * This file is part of Mousetweaks.
  *
@@ -17,23 +17,23 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <glib.h>
+#include "mt_timer.h"
 
-#include "mt-timer.h"
-
-#define DEFAULT_TARGET_TIME 1.2f
-
-struct _MtTimerPrivate
+typedef struct
 {
-    GTimer *timer;
-    guint   tid;
-    gdouble elapsed;
+    guint   id;
+    gint64  stamp;
     gdouble target;
+} MtTimerPrivate;
+
+struct _MtTimer
+{
+    GObject         parent;
+    MtTimerPrivate *priv;
 };
 
 enum
 {
-    TICK,
     FINISHED,
     LAST_SIGNAL
 };
@@ -41,21 +41,17 @@ enum
 enum
 {
     PROP_0,
-    PROP_TARGET_TIME
+    PROP_TARGET
 };
 
 static guint signals[LAST_SIGNAL] = { 0, };
 
-G_DEFINE_TYPE (MtTimer, mt_timer, G_TYPE_OBJECT)
+G_DEFINE_TYPE_WITH_PRIVATE (MtTimer, mt_timer, G_TYPE_OBJECT)
 
 static void
 mt_timer_init (MtTimer *timer)
 {
-    timer->priv = G_TYPE_INSTANCE_GET_PRIVATE (timer,
-                                               MT_TYPE_TIMER,
-                                               MtTimerPrivate);
-    timer->priv->timer  = g_timer_new ();
-    timer->priv->target = DEFAULT_TARGET_TIME;
+    timer->priv = mt_timer_get_instance_private (timer);
 }
 
 static void
@@ -68,7 +64,7 @@ mt_timer_get_property (GObject    *object,
 
     switch (prop_id)
     {
-        case PROP_TARGET_TIME:
+        case PROP_TARGET:
             g_value_set_double (value, timer->priv->target);
             break;
         default:
@@ -86,8 +82,9 @@ mt_timer_set_property (GObject      *object,
 
     switch (prop_id)
     {
-        case PROP_TARGET_TIME:
+        case PROP_TARGET:
             timer->priv->target = g_value_get_double (value);
+            g_object_notify (object, pspec->name);
             break;
         default:
             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -97,12 +94,7 @@ mt_timer_set_property (GObject      *object,
 static void
 mt_timer_finalize (GObject *object)
 {
-    MtTimer *timer = MT_TIMER (object);
-
-    if (timer->priv->tid)
-        g_source_remove (timer->priv->tid);
-
-    g_timer_destroy (timer->priv->timer);
+    mt_timer_stop (MT_TIMER (object));
 
     G_OBJECT_CLASS (mt_timer_parent_class)->finalize (object);
 }
@@ -110,56 +102,42 @@ mt_timer_finalize (GObject *object)
 static void
 mt_timer_class_init (MtTimerClass *klass)
 {
-    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    GObjectClass *object_class;
 
+    object_class = G_OBJECT_CLASS (klass);
     object_class->get_property = mt_timer_get_property;
     object_class->set_property = mt_timer_set_property;
     object_class->finalize = mt_timer_finalize;
 
-    signals[TICK] =
-        g_signal_new (g_intern_static_string ("tick"),
-                      G_OBJECT_CLASS_TYPE (klass),
-                      G_SIGNAL_RUN_LAST,
-                      0, NULL, NULL,
-                      g_cclosure_marshal_VOID__DOUBLE,
-                      G_TYPE_NONE, 1, G_TYPE_DOUBLE);
-
     signals[FINISHED] =
-        g_signal_new (g_intern_static_string ("finished"),
+        g_signal_new ("finished",
                       G_OBJECT_CLASS_TYPE (klass),
                       G_SIGNAL_RUN_LAST,
                       0, NULL, NULL,
                       g_cclosure_marshal_VOID__VOID,
                       G_TYPE_NONE, 0);
 
-    g_object_class_install_property (object_class, PROP_TARGET_TIME,
-            g_param_spec_double ("target-time", "Target time",
+    g_object_class_install_property (object_class, PROP_TARGET,
+            g_param_spec_double ("target", "Target time",
                                  "Target time of the timer",
-                                 0.1, 3.0, DEFAULT_TARGET_TIME,
+                                 0.1, 3.0, 0.1,
                                  G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-    g_type_class_add_private (klass, sizeof (MtTimerPrivate));
 }
 
 static gboolean
-mt_timer_check_time (gpointer data)
+mt_timer_check (MtTimer *timer)
 {
-    MtTimer *timer = data;
-    MtTimerPrivate *priv = timer->priv;
+    gdouble elapsed;
 
-    priv->elapsed = g_timer_elapsed (priv->timer, NULL);
-    g_signal_emit (timer, signals[TICK], 0, priv->elapsed);
+    elapsed = (gdouble) (g_get_monotonic_time () - timer->priv->stamp) / 1e6;
 
-    if (priv->elapsed >= priv->target)
+    if (elapsed > timer->priv->target)
     {
-        priv->tid = 0;
-        priv->elapsed = 0.0;
+        timer->priv->id = 0;
         g_signal_emit (timer, signals[FINISHED], 0);
-
-        return FALSE;
+        return G_SOURCE_REMOVE;
     }
-
-    return TRUE;
+    return G_SOURCE_CONTINUE;
 }
 
 MtTimer *
@@ -173,10 +151,10 @@ mt_timer_start (MtTimer *timer)
 {
     g_return_if_fail (MT_IS_TIMER (timer));
 
-    g_timer_start (timer->priv->timer);
+    if (timer->priv->id == 0)
+        timer->priv->id = g_timeout_add (100, (GSourceFunc) mt_timer_check, timer);
 
-    if (timer->priv->tid == 0)
-        timer->priv->tid = g_timeout_add (100, mt_timer_check_time, timer);
+    timer->priv->stamp = g_get_monotonic_time ();
 }
 
 void
@@ -184,10 +162,10 @@ mt_timer_stop (MtTimer *timer)
 {
     g_return_if_fail (MT_IS_TIMER (timer));
 
-    if (timer->priv->tid)
+    if (timer->priv->id)
     {
-        g_source_remove (timer->priv->tid);
-        timer->priv->tid = 0;
+        g_source_remove (timer->priv->id);
+        timer->priv->id = 0;
     }
 }
 
@@ -196,31 +174,5 @@ mt_timer_is_running (MtTimer *timer)
 {
     g_return_val_if_fail (MT_IS_TIMER (timer), FALSE);
 
-    return timer->priv->tid != 0;
-}
-
-gdouble
-mt_timer_elapsed (MtTimer *timer)
-{
-    g_return_val_if_fail (MT_IS_TIMER (timer), 0.0);
-
-    return timer->priv->elapsed;
-}
-
-gdouble
-mt_timer_get_target (MtTimer *timer)
-{
-    g_return_val_if_fail (MT_IS_TIMER (timer), 0.0);
-
-    return timer->priv->target;
-}
-
-void
-mt_timer_set_target (MtTimer *timer, gdouble target)
-{
-    g_return_if_fail (MT_IS_TIMER (timer));
-    g_return_if_fail (target >= 0.1);
-
-    timer->priv->target = target;
-    g_object_notify (G_OBJECT (timer), "target-time");
+    return timer->priv->id != 0;
 }
diff --git a/src/mt-timer.h b/src/mt_timer.h
similarity index 57%
rename from src/mt-timer.h
rename to src/mt_timer.h
index 6abfc61..0ca69ba 100644
--- a/src/mt-timer.h
+++ b/src/mt_timer.h
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2007-2010 Gerd Kohlberger <gerdko gmail com>
+ * Copyright 2013, Gerd Kohlberger <gerdko gmail com>
  *
  * This file is part of Mousetweaks.
  *
@@ -28,28 +28,14 @@ G_BEGIN_DECLS
 #define MT_TIMER(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), MT_TYPE_TIMER, MtTimer))
 #define MT_IS_TIMER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), MT_TYPE_TIMER))
 
-typedef GObjectClass           MtTimerClass;
-typedef struct _MtTimer        MtTimer;
-typedef struct _MtTimerPrivate MtTimerPrivate;
+typedef GObjectClass    MtTimerClass;
+typedef struct _MtTimer MtTimer;
 
-struct _MtTimer
-{
-    GObject         parent;
-    MtTimerPrivate *priv;
-};
-
-GType        mt_timer_get_type        (void) G_GNUC_CONST;
-
-MtTimer *    mt_timer_new             (void);
-
-void         mt_timer_start           (MtTimer *timer);
-void         mt_timer_stop            (MtTimer *timer);
-gboolean     mt_timer_is_running      (MtTimer *timer);
-
-gdouble      mt_timer_elapsed         (MtTimer *timer);
-gdouble      mt_timer_get_target      (MtTimer *timer);
-void         mt_timer_set_target      (MtTimer *timer,
-                                       gdouble  target);
+GType       mt_timer_get_type       (void) G_GNUC_CONST;
+MtTimer *   mt_timer_new            (void);
+void        mt_timer_start          (MtTimer *timer);
+void        mt_timer_stop           (MtTimer *timer);
+gboolean    mt_timer_is_running     (MtTimer *timer);
 
 G_END_DECLS
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]