From 17725fba789c83855bbadd31c74b3e525489d136 Mon Sep 17 00:00:00 2001
From: Cameron Cawley <ccawley2011@gmail.com>
Date: Sat, 19 Feb 2022 15:47:36 +0000
Subject: [PATCH] windows: Improve keyboard and mouse input in core windows

---
 frontends/windows/corewindow.c | 302 ++++++++++++++++++++++++++++++++-
 frontends/windows/corewindow.h |  15 ++
 2 files changed, 309 insertions(+), 8 deletions(-)

diff --git a/frontends/windows/corewindow.c b/frontends/windows/corewindow.c
index 9a26efb43..1287966db 100644
--- a/frontends/windows/corewindow.c
+++ b/frontends/windows/corewindow.c
@@ -300,10 +300,11 @@ nsw32_corewindow_hscroll(struct nsw32_corewindow *nsw32_cw,
 
 static LRESULT
 nsw32_corewindow_mousedown(struct nsw32_corewindow *nsw32_cw,
-			 HWND hwnd,
+			   HWND hwnd,
 			   int x, int y,
 			   browser_mouse_state button)
 {
+	struct nsw32_corewindow_mouse *mouse = &nsw32_cw->mouse_state;
 	SCROLLINFO si; /* scroll information */
 
 	/* get scroll positions */
@@ -314,7 +315,21 @@ nsw32_corewindow_mousedown(struct nsw32_corewindow *nsw32_cw,
 	GetScrollInfo(hwnd, SB_VERT, &si);
 	y += si.nPos;
 
-	nsw32_cw->mouse(nsw32_cw, button, x, y);
+	/* record event information for potentially starting a drag. */
+	mouse->pressed_x = mouse->last_x = x;
+	mouse->pressed_y = mouse->last_y = y;
+	mouse->pressed = true;
+
+	mouse->state = button;
+	if ((GetKeyState(VK_SHIFT) & 0x8000) == 0x8000)
+		mouse->state |= BROWSER_MOUSE_MOD_1;
+	if ((GetKeyState(VK_CONTROL) & 0x8000) == 0x8000)
+		mouse->state |= BROWSER_MOUSE_MOD_2;
+	if ((GetKeyState(VK_MENU) & 0x8000) == 0x8000)
+		mouse->state |= BROWSER_MOUSE_MOD_3;
+
+	nsw32_cw->mouse(nsw32_cw, mouse->state, x, y);
+
 	return 0;
 }
 
@@ -324,6 +339,82 @@ nsw32_corewindow_mouseup(struct nsw32_corewindow *nsw32_cw,
 			 int x, int y,
 			 browser_mouse_state button)
 {
+	struct nsw32_corewindow_mouse *mouse = &nsw32_cw->mouse_state;
+	bool was_drag = false;
+	SCROLLINFO si; /* scroll information */
+
+	/* get scroll positions */
+	si.cbSize = sizeof(si);
+	si.fMask = SIF_POS;
+	GetScrollInfo(hwnd, SB_HORZ, &si);
+	x += si.nPos;
+	GetScrollInfo(hwnd, SB_VERT, &si);
+	y += si.nPos;
+
+	/* only button 1 clicks are considered double clicks. If the
+	 * mouse state is PRESS then we are waiting for a release to
+	 * emit a click event, otherwise just reset the state to nothing.
+	 */
+	if (mouse->state & BROWSER_MOUSE_DOUBLE_CLICK) {
+		if (mouse->state & BROWSER_MOUSE_PRESS_1) {
+			mouse->state ^= BROWSER_MOUSE_PRESS_1 |
+					BROWSER_MOUSE_CLICK_1;
+		} else if (mouse->state & BROWSER_MOUSE_PRESS_2) {
+			mouse->state ^= (BROWSER_MOUSE_PRESS_2 |
+					BROWSER_MOUSE_CLICK_2 |
+					BROWSER_MOUSE_DOUBLE_CLICK);
+		}
+	} else if (mouse->state & BROWSER_MOUSE_PRESS_1) {
+		mouse->state ^= (BROWSER_MOUSE_PRESS_1 |
+				 BROWSER_MOUSE_CLICK_1);
+	} else if (mouse->state & BROWSER_MOUSE_PRESS_2) {
+		mouse->state ^= (BROWSER_MOUSE_PRESS_2 |
+				 BROWSER_MOUSE_CLICK_2);
+	} else if (mouse->state & BROWSER_MOUSE_HOLDING_1) {
+		mouse->state ^= (BROWSER_MOUSE_HOLDING_1 |
+				 BROWSER_MOUSE_DRAG_ON);
+		was_drag = true;
+	} else if (mouse->state & BROWSER_MOUSE_HOLDING_2) {
+		mouse->state ^= (BROWSER_MOUSE_HOLDING_2 |
+				 BROWSER_MOUSE_DRAG_ON);
+		was_drag = true;
+	}
+
+	/* Handle modifiers being removed */
+	if ((mouse->state & BROWSER_MOUSE_MOD_1) &&
+	    !((GetKeyState(VK_SHIFT) & 0x8000) == 0x8000)) {
+		mouse->state ^= BROWSER_MOUSE_MOD_1;
+	}
+	if ((mouse->state & BROWSER_MOUSE_MOD_2) &&
+	    !((GetKeyState(VK_CONTROL) & 0x8000) == 0x8000)) {
+		mouse->state ^= BROWSER_MOUSE_MOD_2;
+	}
+	if ((mouse->state & BROWSER_MOUSE_MOD_3) &&
+	    !((GetKeyState(VK_MENU) & 0x8000) == 0x8000)) {
+		mouse->state ^= BROWSER_MOUSE_MOD_3;
+	}
+
+	/* end drag with modifiers */
+	if (was_drag && (mouse->state & (
+			BROWSER_MOUSE_MOD_1 |
+			BROWSER_MOUSE_MOD_2 |
+			BROWSER_MOUSE_MOD_3))) {
+		mouse->state = BROWSER_MOUSE_HOVER;
+	}
+
+	nsw32_cw->mouse(nsw32_cw, mouse->state, x, y);
+
+	mouse->pressed = false;
+
+	return 0;
+}
+
+static LRESULT
+nsw32_corewindow_mousemove(struct nsw32_corewindow *nsw32_cw,
+			   HWND hwnd,
+			   int x, int y)
+{
+	struct nsw32_corewindow_mouse *mouse = &nsw32_cw->mouse_state;
 	SCROLLINFO si; /* scroll information */
 
 	/* get scroll positions */
@@ -334,7 +425,175 @@ nsw32_corewindow_mouseup(struct nsw32_corewindow *nsw32_cw,
 	GetScrollInfo(hwnd, SB_VERT, &si);
 	y += si.nPos;
 
-	nsw32_cw->mouse(nsw32_cw, button, x, y);
+	if (!mouse->pressed) {
+		nsw32_cw->mouse(nsw32_cw,
+				BROWSER_MOUSE_HOVER,
+				x,
+				y);
+		return 0;
+	}
+
+	if ((abs(x - mouse->last_x) < 5) &&
+	    (abs(y - mouse->last_y) < 5)) {
+		/* Mouse hasn't moved far enough from press coordinate
+		 * for this to be considered a drag.
+		 */
+		return 1;
+	}
+
+	/* This is a drag, ensure it's always treated as such, even if
+	 * we drag back over the press location.
+	 */
+	mouse->last_x = INT_MIN;
+	mouse->last_y = INT_MIN;
+
+
+	if (mouse->state & BROWSER_MOUSE_PRESS_1) {
+		/* Start button 1 drag */
+		nsw32_cw->mouse(nsw32_cw,
+				BROWSER_MOUSE_DRAG_1,
+				mouse->pressed_x,
+				mouse->pressed_y);
+
+		/* Replace PRESS with HOLDING and declare drag in progress */
+		mouse->state ^= (BROWSER_MOUSE_PRESS_1 |
+				 BROWSER_MOUSE_HOLDING_1);
+		mouse->state |= BROWSER_MOUSE_DRAG_ON;
+
+	} else if (mouse->state & BROWSER_MOUSE_PRESS_2) {
+		/* Start button 2s drag */
+		nsw32_cw->mouse(nsw32_cw,
+				BROWSER_MOUSE_DRAG_2,
+				mouse->pressed_x,
+				mouse->pressed_y);
+
+		/* Replace PRESS with HOLDING and declare drag in progress */
+		mouse->state ^= (BROWSER_MOUSE_PRESS_2 |
+				 BROWSER_MOUSE_HOLDING_2);
+		mouse->state |= BROWSER_MOUSE_DRAG_ON;
+
+	} else {
+		/* continue drag */
+
+		/* Handle modifiers being removed */
+		if ((mouse->state & BROWSER_MOUSE_MOD_1) &&
+		    !((GetKeyState(VK_SHIFT) & 0x8000) == 0x8000)) {
+			mouse->state ^= BROWSER_MOUSE_MOD_1;
+		}
+		if ((mouse->state & BROWSER_MOUSE_MOD_2) &&
+		    !((GetKeyState(VK_CONTROL) & 0x8000) == 0x8000)) {
+			mouse->state ^= BROWSER_MOUSE_MOD_2;
+		}
+		if ((mouse->state & BROWSER_MOUSE_MOD_3) &&
+		    !((GetKeyState(VK_MENU) & 0x8000) == 0x8000)) {
+			mouse->state ^= BROWSER_MOUSE_MOD_3;
+		}
+
+		if (mouse->state &
+		    (BROWSER_MOUSE_HOLDING_1 | BROWSER_MOUSE_HOLDING_2)) {
+			nsw32_cw->mouse(nsw32_cw,
+					mouse->state,
+					x, y);
+		}
+	}
+
+	return 0;
+}
+
+static LRESULT
+nsw32_corewindow_unichar(struct nsw32_corewindow *nsw32_cw, WPARAM wparam)
+{
+	uint32_t nskey;
+
+	if (wparam == UNICODE_NOCHAR) {
+		return 1;
+	}
+
+	nskey = wparam;
+	nsw32_cw->key(nsw32_cw, nskey);
+	return 0;
+}
+
+static LRESULT
+nsw32_corewindow_char(struct nsw32_corewindow *nsw32_cw, WPARAM wparam)
+{
+	uint32_t nskey;
+
+	nskey = wparam;
+
+	const uint32_t utf16_hi_surrogate_start = 0xD800;
+	const uint32_t utf16_lo_surrogate_start = 0xDC00;
+	const uint32_t utf16_surrogate_end = 0xDFFF;
+
+	static uint32_t highSurrogate = 0;
+
+	if ((nskey >= utf16_hi_surrogate_start) &&
+	    (nskey < utf16_lo_surrogate_start) ) {
+		highSurrogate = nskey;
+	} else {
+		if ((nskey >= utf16_lo_surrogate_start) &&
+		    (nskey <= utf16_surrogate_end)) {
+			uint32_t lowSurrogate = nskey;
+			nskey = (highSurrogate - utf16_hi_surrogate_start) << 10;
+			nskey |= ( lowSurrogate - utf16_lo_surrogate_start );
+			nskey += 0x10000;
+		}
+		highSurrogate = 0;
+
+		nsw32_cw->key(nsw32_cw, nskey);
+	}
+
+	return 0;
+}
+
+static LRESULT
+nsw32_corewindow_keydown(struct nsw32_corewindow *nsw32_cw, WPARAM wparam)
+{
+	uint32_t i;
+
+	switch(wparam) {
+	case VK_LEFT:
+		i = NS_KEY_LEFT;
+		break;
+
+	case VK_RIGHT:
+		i = NS_KEY_RIGHT;
+		break;
+
+	case VK_UP:
+		i = NS_KEY_UP;
+		break;
+
+	case VK_DOWN:
+		i = NS_KEY_DOWN;
+		break;
+
+	case VK_HOME:
+		i = NS_KEY_LINE_START;
+		break;
+
+	case VK_END:
+		i = NS_KEY_LINE_END;
+		break;
+
+	case VK_DELETE:
+		i = NS_KEY_DELETE_RIGHT;
+		break;
+
+	case VK_NEXT:
+		i = NS_KEY_PAGE_DOWN;
+		break;
+
+	case VK_PRIOR:
+		i = NS_KEY_PAGE_UP;
+		break;
+
+	default:
+		return 1;
+	}
+
+	nsw32_cw->key(nsw32_cw, i);
+
 	return 0;
 }
 
@@ -383,6 +642,11 @@ nsw32_window_corewindow_event_callback(HWND hwnd,
 		case WM_HSCROLL:
 			return nsw32_corewindow_hscroll(nsw32_cw, hwnd, wparam);
 
+		case WM_MOUSEMOVE:
+			return nsw32_corewindow_mousemove(nsw32_cw, hwnd,
+							  GET_X_LPARAM(lparam),
+							  GET_Y_LPARAM(lparam));
+
 		case WM_LBUTTONDOWN:
 			return nsw32_corewindow_mousedown(nsw32_cw, hwnd,
 							  GET_X_LPARAM(lparam),
@@ -395,6 +659,18 @@ nsw32_window_corewindow_event_callback(HWND hwnd,
 							   GET_Y_LPARAM(lparam),
 							   BROWSER_MOUSE_PRESS_2);
 
+		case WM_LBUTTONDBLCLK:
+			return nsw32_corewindow_mousedown(nsw32_cw, hwnd,
+							  GET_X_LPARAM(lparam),
+							  GET_Y_LPARAM(lparam),
+							  BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_DOUBLE_CLICK);
+
+		case WM_RBUTTONDBLCLK:
+			return nsw32_corewindow_mousedown(nsw32_cw, hwnd,
+							   GET_X_LPARAM(lparam),
+							   GET_Y_LPARAM(lparam),
+							   BROWSER_MOUSE_PRESS_2 | BROWSER_MOUSE_DOUBLE_CLICK);
+
 		case WM_LBUTTONUP:
 			return nsw32_corewindow_mouseup(nsw32_cw, hwnd,
 							GET_X_LPARAM(lparam),
@@ -407,6 +683,18 @@ nsw32_window_corewindow_event_callback(HWND hwnd,
 							GET_Y_LPARAM(lparam),
 							BROWSER_MOUSE_CLICK_2);
 
+		case WM_KEYDOWN:
+			if (nsw32_corewindow_keydown(nsw32_cw, wparam) == 0) {
+				return 0;
+			}
+			break;
+
+		case WM_CHAR:
+			return nsw32_corewindow_char(nsw32_cw, wparam);
+
+		case WM_UNICHAR:
+			return nsw32_corewindow_unichar(nsw32_cw, wparam);
+
 		case WM_CLOSE:
 			return nsw32_corewindow_close(nsw32_cw);
 		}
@@ -550,15 +838,13 @@ nsw32_corewindow_init(HINSTANCE hInstance,
 
 	if (hWndParent != NULL) {
 		dwStyle = WS_CHILDWINDOW |
-			WS_VISIBLE |
-			CS_DBLCLKS;
+			WS_VISIBLE;
 	} else {
 		dwStyle = WS_OVERLAPPEDWINDOW |
 			WS_HSCROLL |
 			WS_VSCROLL |
 			WS_CLIPSIBLINGS |
-			WS_CLIPCHILDREN |
-			CS_DBLCLKS;
+			WS_CLIPCHILDREN;
 	}
 
 	NSLOG(netsurf, INFO, "creating hInstance %p core window", hInstance);
@@ -607,7 +893,7 @@ nserror nsw32_create_corewindow_class(HINSTANCE hInstance)
 
 	/* drawable area */
 	wc.cbSize = sizeof(WNDCLASSEX);
-	wc.style = 0;
+	wc.style = CS_DBLCLKS;
 	wc.lpfnWndProc = nsw32_window_corewindow_event_callback;
 	wc.cbClsExtra = 0;
 	wc.cbWndExtra = 0;
diff --git a/frontends/windows/corewindow.h b/frontends/windows/corewindow.h
index b91787a85..4678f5b50 100644
--- a/frontends/windows/corewindow.h
+++ b/frontends/windows/corewindow.h
@@ -21,6 +21,18 @@
 
 #include "netsurf/core_window.h"
 
+/**
+ * nsw32 core window mouse state
+ */
+struct nsw32_corewindow_mouse {
+	browser_mouse_state state; /**< last event status */
+	bool pressed;
+	int pressed_x;
+	int pressed_y;
+	int last_x;
+	int last_y;
+};
+
 /**
  * nsw32 core window state
  */
@@ -40,6 +52,9 @@ struct nsw32_corewindow {
 	/** window menu */
 	HMENU menu;
 
+	/** mouse state */
+	struct nsw32_corewindow_mouse mouse_state;
+
         /** drag status set by core */
         core_window_drag_status drag_status;
 
-- 
2.30.2

