diff --git a/src/input/include/input_hook.hpp b/src/input/include/input_hook.hpp
index a257c04a..a8705210 100644
--- a/src/input/include/input_hook.hpp
+++ b/src/input/include/input_hook.hpp
@@ -45,9 +45,10 @@ class Hook {
 class TriggerHooks {
  public:
   TriggerHooks(std::string name, std::optional<HookCallback> cb)
-      : TriggerHooks(name, cb, cb, cb) {}
+      : TriggerHooks(name, cb, cb, cb, cb) {}
   TriggerHooks(std::string name,
                std::optional<HookCallback> click,
+               std::optional<HookCallback> double_click,
                std::optional<HookCallback> long_press,
                std::optional<HookCallback> repeat);
 
@@ -66,6 +67,7 @@ class TriggerHooks {
   Trigger trigger_;
 
   Hook click_;
+  Hook double_click_;
   Hook long_press_;
   Hook repeat_;
 };
diff --git a/src/input/include/input_trigger.hpp b/src/input/include/input_trigger.hpp
index 599b796b..bcafa8ad 100644
--- a/src/input/include/input_trigger.hpp
+++ b/src/input/include/input_trigger.hpp
@@ -13,6 +13,7 @@
 
 namespace input {
 
+const uint16_t kDoubleClickDelayMs = 500;
 const uint16_t kLongPressDelayMs = LV_INDEV_DEF_LONG_PRESS_TIME;
 const uint16_t kRepeatDelayMs = LV_INDEV_DEF_LONG_PRESS_REP_TIME;
 
@@ -21,6 +22,7 @@ class Trigger {
   enum class State {
     kNone,
     kClick,
+    kDoubleClick,
     kLongPress,
     kRepeatPress,
   };
@@ -31,7 +33,10 @@ class Trigger {
 
  private:
   std::optional<uint64_t> touch_time_ms_;
-  uint16_t times_fired_;
+  bool was_pressed_;
+
+  bool was_double_click_;
+  uint16_t times_long_pressed_;
 };
 
 }  // namespace input
diff --git a/src/input/input_hook.cpp b/src/input/input_hook.cpp
index 48d6c2a4..bf9f3596 100644
--- a/src/input/input_hook.cpp
+++ b/src/input/input_hook.cpp
@@ -39,10 +39,12 @@ auto Hook::callback() -> std::optional<HookCallback> {
 
 TriggerHooks::TriggerHooks(std::string name,
                            std::optional<HookCallback> click,
+                           std::optional<HookCallback> double_click,
                            std::optional<HookCallback> long_press,
                            std::optional<HookCallback> repeat)
     : name_(name),
       click_("click", click),
+      double_click_("double_click", double_click),
       long_press_("long_press", long_press),
       repeat_("repeat", repeat) {}
 
@@ -51,6 +53,9 @@ auto TriggerHooks::update(bool pressed, lv_indev_data_t* d) -> void {
     case Trigger::State::kClick:
       click_.invoke(d);
       break;
+    case Trigger::State::kDoubleClick:
+      double_click_.invoke(d);
+      break;
     case Trigger::State::kLongPress:
       long_press_.invoke(d);
       break;
diff --git a/src/input/input_nav_buttons.cpp b/src/input/input_nav_buttons.cpp
index 522f8d6e..61d80075 100644
--- a/src/input/input_nav_buttons.cpp
+++ b/src/input/input_nav_buttons.cpp
@@ -15,8 +15,8 @@ namespace input {
 
 NavButtons::NavButtons(drivers::IGpios& gpios)
     : gpios_(gpios),
-      up_("upper", actions::scrollUp(), actions::select(), {}),
-      down_("lower", actions::scrollDown(), actions::select(), {}) {}
+      up_("upper", {}, actions::scrollUp(), actions::select(), {}),
+      down_("lower", {}, actions::scrollDown(), actions::select(), {}) {}
 
 auto NavButtons::read(lv_indev_data_t* data) -> void {
   up_.update(!gpios_.Get(drivers::IGpios::Pin::kKeyUp), data);
diff --git a/src/input/input_touch_dpad.cpp b/src/input/input_touch_dpad.cpp
index d8eff09b..df17d766 100644
--- a/src/input/input_touch_dpad.cpp
+++ b/src/input/input_touch_dpad.cpp
@@ -21,9 +21,9 @@ namespace input {
 
 TouchDPad::TouchDPad(drivers::TouchWheel& wheel)
     : wheel_(wheel),
-      centre_("centre", actions::select(), {}, {}),
+      centre_("centre", actions::select(), {}, {}, {}),
       up_("up", actions::scrollUp()),
-      right_("right", {}, {}, {}),
+      right_("right", {}),
       down_("down", actions::scrollDown()),
       left_("left", actions::goBack()) {}
 
diff --git a/src/input/input_touch_wheel.cpp b/src/input/input_touch_wheel.cpp
index 67cab3bf..41fd73bc 100644
--- a/src/input/input_touch_wheel.cpp
+++ b/src/input/input_touch_wheel.cpp
@@ -39,11 +39,11 @@ TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel)
                      threshold_ = calculateThreshold(int_val);
                      return true;
                    }),
-      centre_("centre", actions::select(), {}, {}),
-      up_("up", {}, actions::scrollToTop(), actions::scrollUp()),
-      right_("right", {}, {}, {}),
-      down_("down", {}, actions::scrollToBottom(), actions::scrollDown()),
-      left_("left", {}, actions::goBack(), {}),
+      centre_("centre", actions::select(), {}, {}, {}),
+      up_("up", {}, actions::scrollToTop(), {}, {}),
+      right_("right", {}),
+      down_("down", {}, actions::scrollToBottom(), {}, {}),
+      left_("left", {}, actions::goBack(), {}, {}),
       is_scrolling_(false),
       threshold_(calculateThreshold(nvs.ScrollSensitivity())),
       is_first_read_(true),
@@ -68,7 +68,8 @@ auto TouchWheel::read(lv_indev_data_t* data) -> void {
     data->enc_diff = 0;
   }
 
-  centre_.update(!is_scrolling_ && wheel_data.is_button_touched, data);
+  centre_.update(wheel_data.is_button_touched && !wheel_data.is_wheel_touched,
+                 data);
 
   // If the user is touching the wheel but not scrolling, then they may be
   // clicking on one of the wheel's cardinal directions.
diff --git a/src/input/input_trigger.cpp b/src/input/input_trigger.cpp
index 9485ecb4..00d4a32d 100644
--- a/src/input/input_trigger.cpp
+++ b/src/input/input_trigger.cpp
@@ -8,34 +8,50 @@
 #include <sys/_stdint.h>
 
 #include <cstdint>
-#include "esp_log.h"
 #include "esp_timer.h"
 
 namespace input {
 
-Trigger::Trigger() : touch_time_ms_(), times_fired_(0) {}
+Trigger::Trigger()
+    : touch_time_ms_(),
+      was_pressed_(false),
+      was_double_click_(false),
+      times_long_pressed_(0) {}
 
 auto Trigger::update(bool is_pressed) -> State {
   // Bail out early if we're in a steady-state of not pressed.
-  if (!is_pressed && !touch_time_ms_) {
+  if (!is_pressed && !was_pressed_) {
+    was_double_click_ = false;
+    times_long_pressed_ = 0;
     return State::kNone;
   }
 
   uint64_t now_ms = esp_timer_get_time() / 1000;
 
-  // Initial press of this key: record the current time, and report that we
-  // haven't triggered yet.
-  if (is_pressed && !touch_time_ms_) {
+  // This key wasn't being pressed, but now it is.
+  if (is_pressed && !was_pressed_) {
+    // Is this a double click?
+    if (now_ms - *touch_time_ms_ < kDoubleClickDelayMs) {
+      // Don't update touch_time_ms_, since we don't want triple clicks to
+      // register as double clicks.
+      was_double_click_ = true;
+      was_pressed_ = true;
+      return State::kDoubleClick;
+    }
+    // Not a double click; update our accounting info and wait for the next
+    // call.
     touch_time_ms_ = now_ms;
-    times_fired_ = 0;
+    was_double_click_ = false;
+    times_long_pressed_ = 0;
+    was_pressed_ = true;
     return State::kNone;
   }
 
   // The key was released. If there were no long-press events fired during the
   // press, then this was a standard click.
-  if (!is_pressed && touch_time_ms_) {
-    touch_time_ms_.reset();
-    if (times_fired_ == 0) {
+  if (!is_pressed && was_pressed_) {
+    was_pressed_ = false;
+    if (!was_double_click_ && times_long_pressed_ == 0) {
       return State::kClick;
     } else {
       return State::kNone;
@@ -43,10 +59,10 @@ auto Trigger::update(bool is_pressed) -> State {
   }
 
   // Now the more complicated case: the user is continuing to press the button.
-  if (times_fired_ == 0) {
+  if (times_long_pressed_ == 0) {
     // We haven't fired yet, so we wait for the long-press event.
     if (now_ms - *touch_time_ms_ >= kLongPressDelayMs) {
-      times_fired_++;
+      times_long_pressed_++;
       return State::kLongPress;
     }
   } else {
@@ -60,8 +76,8 @@ auto Trigger::update(bool is_pressed) -> State {
     // kRepeatDelayMs since the long-press event.
     uint16_t expected_times_fired =
         1 + (time_since_long_press / kRepeatDelayMs);
-    if (times_fired_ < expected_times_fired) {
-      times_fired_++;
+    if (times_long_pressed_ < expected_times_fired) {
+      times_long_pressed_++;
       return State::kRepeatPress;
     }
   }