Skip to content

Commit 4dac5af

Browse files
Merge pull request #4 from austinnixholm/feature/drag-continuation
feature/drag-continuation
2 parents 4100459 + fefab0b commit 4dac5af

27 files changed

+1050
-680
lines changed

ThreeFingerDrag/ThreeFingerDrag.cpp

Lines changed: 115 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@ namespace
99
constexpr auto STARTUP_REGISTRY_KEY = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
1010
constexpr auto PROGRAM_NAME = L"ThreeFingerDrag";
1111
constexpr auto UPDATE_SETTINGS_PERIOD_MS = std::chrono::milliseconds(2000);
12-
constexpr auto TOUCH_ACTIVITY_PERIOD_MS = std::chrono::milliseconds(50);
12+
constexpr auto TOUCH_ACTIVITY_PERIOD_MS = std::chrono::milliseconds(1);
1313
constexpr auto MAX_LOAD_STRING_LENGTH = 100;
1414

1515
constexpr auto SETTINGS_WINDOW_WIDTH = 456;
1616
constexpr auto SETTINGS_WINDOW_HEIGHT = 170;
17-
constexpr auto MIN_SKIPPED_FRAMES = 3;
18-
constexpr auto MAX_SKIPPED_FRAMES = 25;
17+
constexpr auto MIN_CANCELLATION_DELAY_MS = 100;
18+
constexpr auto MAX_CANCELLATION_DELAY_MS = 2000;
1919
constexpr auto MIN_GESTURE_SPEED = 1;
2020
constexpr auto MAX_GESTURE_SPEED = 100;
2121
constexpr auto ID_SETTINGS_MENUITEM = 10000;
2222
constexpr auto ID_QUIT_MENUITEM = 10001;
2323
constexpr auto ID_RUN_ON_STARTUP_CHECKBOX = 10002;
2424
constexpr auto ID_GESTURE_SPEED_TRACKBAR = 10003;
2525
constexpr auto ID_TEXT_BOX = 10004;
26-
constexpr auto ID_SKIPPED_FRAMES_SPINNER = 10005;
26+
constexpr auto ID_CANCELLATION_DELAY_SPINNER = 10005;
2727
}
2828

2929
// Global Variables
@@ -56,73 +56,49 @@ LRESULT CALLBACK SettingsWndProc(HWND, UINT, WPARAM, LPARAM);
5656

5757
void CreateTrayMenu(HWND hWnd);
5858
void ShowSettingsWindow();
59-
void AddStartupRegistryKey();
59+
void AddStartupTask();
60+
void RemoveStartupTask();
6061
void RemoveStartupRegistryKey();
6162
void ReadPrecisionTouchPadInfo();
6263
void ReadCursorSpeed();
63-
void UpdateGestureProcessor();
6464
void StartPeriodicUpdateThreads();
6565
void HandleUncaughtExceptions();
66+
void PerformAdditionalSteps();
67+
void PromptUserForStartupPreference();
68+
void InitializeConfiguration();
6669
bool InitializeWindowsNotifications();
6770
bool StartupRegistryKeyExists();
6871
bool RegisterRawInputDevices();
72+
bool CheckSingleInstance();
6973
bool InitializeGUI();
7074

7175
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
7276
_In_opt_ HINSTANCE hPrevInstance,
7377
_In_ LPWSTR lpCmdLine,
7478
_In_ int nCmdShow)
7579
{
80+
current_instance = hInstance;
7681
UNREFERENCED_PARAMETER(hPrevInstance);
7782
UNREFERENCED_PARAMETER(lpCmdLine);
7883

7984
std::set_terminate(HandleUncaughtExceptions);
8085

81-
// Single application instance check
82-
const HANDLE hMutex = CreateMutex(nullptr, TRUE, PROGRAM_NAME);
83-
if (GetLastError() == ERROR_ALREADY_EXISTS)
84-
{
85-
Popups::DisplayErrorMessage("Another instance of Three Finger Drag is already running.");
86-
CloseHandle(hMutex);
86+
if (!CheckSingleInstance()) {
8787
return FALSE;
8888
}
8989

90-
// Read user configuration values
91-
Application::ReadConfiguration();
92-
ReadPrecisionTouchPadInfo();
93-
ReadCursorSpeed();
94-
95-
// Initialize global strings
96-
LoadStringW(hInstance, IDS_APP_TITLE, title_bar_text, MAX_LOAD_STRING_LENGTH);
97-
LoadStringW(hInstance, IDS_SETTINGS_TITLE, settings_title_text, MAX_LOAD_STRING_LENGTH);
98-
LoadStringW(hInstance, IDC_THREEFINGERDRAG, main_window_class_name, MAX_LOAD_STRING_LENGTH);
99-
LoadStringW(hInstance, IDC_SETTINGS, settings_window_class_name, MAX_LOAD_STRING_LENGTH);
90+
InitializeConfiguration();
10091

101-
// Register window classes
102-
RegisterWindowClass(hInstance, main_window_class_name, WndProc);
103-
RegisterWindowClass(hInstance, settings_window_class_name, SettingsWndProc);
104-
105-
// Perform application initialization:
106-
if (!InitInstance(hInstance))
92+
if (!InitInstance(current_instance))
10793
{
10894
ERROR("Application initialization failed.");
10995
return FALSE;
11096
}
11197

112-
// Start threads
11398
StartPeriodicUpdateThreads();
114-
115-
// First time running application
116-
if (Application::IsInitialStartup()) {
117-
bool result = Popups::DisplayPrompt("Would you like run ThreeFingerDrag on startup of Windows?", "ThreeFingerDrag");
118-
if (result)
119-
AddStartupRegistryKey();
120-
Popups::ShowToastNotification(L"You can access the program in the system tray.", L"Welcome to ThreeFingerDrag!");
121-
}
122-
99+
PerformAdditionalSteps();
100+
123101
MSG msg;
124-
125-
// Enter message loop and process incoming messages until WM_QUIT is received
126102
while (GetMessage(&msg, nullptr, 0, 0))
127103
{
128104
TranslateMessage(&msg);
@@ -142,8 +118,6 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
142118

143119
BOOL InitInstance(const HINSTANCE hInstance)
144120
{
145-
current_instance = hInstance;
146-
147121
// Initialize WinToast notifications
148122
if (!InitializeWindowsNotifications()) {
149123
ERROR("Failed to initialize WinToast.");
@@ -235,9 +209,8 @@ LRESULT CALLBACK SettingsWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
235209
case ID_TEXT_BOX:
236210
wchar_t buffer[64];
237211
GetWindowText((HWND)lParam, buffer, 64); // get textbox text
238-
config->SetSkippedGestureFrames(_wtoi(buffer));// convert to integer (only numerical values are entered)
212+
config->SetCancellationDelayMs(_wtoi(buffer));// convert to integer (only numerical values are entered)
239213
Application::WriteConfiguration();
240-
UpdateGestureProcessor();
241214
break;
242215
}
243216
}
@@ -247,9 +220,9 @@ LRESULT CALLBACK SettingsWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
247220
case ID_RUN_ON_STARTUP_CHECKBOX:
248221
// Run on startup checkbox clicked
249222
if (SendMessage(GetDlgItem(hWnd, ID_RUN_ON_STARTUP_CHECKBOX), BM_GETCHECK, 0, 0) == BST_CHECKED)
250-
AddStartupRegistryKey();
223+
AddStartupTask();
251224
else
252-
RemoveStartupRegistryKey();
225+
RemoveStartupTask();
253226
break;
254227
}
255228
break;
@@ -264,7 +237,6 @@ LRESULT CALLBACK SettingsWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
264237

265238
config->SetGestureSpeed(tbPos);
266239
Application::WriteConfiguration();
267-
UpdateGestureProcessor();
268240
}
269241
}
270242
break;
@@ -295,7 +267,7 @@ LRESULT CALLBACK SettingsWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPar
295267
}
296268

297269
/**
298-
* \brief Initializes window handles for GUI.
270+
* \brief Create the main application window and the settings window.
299271
*/
300272
bool InitializeGUI() {
301273
tray_icon_hwnd = CreateWindowEx(
@@ -371,18 +343,18 @@ bool InitializeGUI() {
371343
HWND settings_spinner_hwnd = CreateWindowW(L"msctls_updown32", NULL,
372344
WS_CHILD | WS_VISIBLE | UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_SETBUDDYINT | UDS_NOTHOUSANDS,
373345
pos_x, pos_y, 0, label_height, // Adjust the height as needed
374-
settings_hwnd, (HMENU)ID_SKIPPED_FRAMES_SPINNER, current_instance, NULL); // Spinner Controls ID as per your request.
346+
settings_hwnd, (HMENU)ID_CANCELLATION_DELAY_SPINNER, current_instance, NULL);
375347

376348
SendMessage(settings_spinner_hwnd, UDM_SETBUDDY, (WPARAM)hwndTextBox, 0);
377-
SendMessage(settings_spinner_hwnd, UDM_SETRANGE, 0, MAKELONG(MAX_SKIPPED_FRAMES, MIN_SKIPPED_FRAMES));
378-
SendMessage(settings_spinner_hwnd, UDM_SETPOS, 0, MAKELONG(config->GetSkippedGestureFrames(), 0));
349+
SendMessage(settings_spinner_hwnd, UDM_SETRANGE, 0, MAKELONG(MAX_CANCELLATION_DELAY_MS, MIN_CANCELLATION_DELAY_MS));
350+
SendMessage(settings_spinner_hwnd, UDM_SETPOS, 0, MAKELONG(config->GetCancellationDelayMs(), 0));
379351

380352
pos_x += 60;
381353

382354
// Label for numeric textbox
383-
HWND hwnd_spinner_label = CreateWindowW(L"STATIC", L"Skipped initial frames of gesture movement",
355+
HWND hwnd_spinner_label = CreateWindowW(L"STATIC", L"Cancellation Delay (milliseconds)",
384356
WS_CHILD | WS_VISIBLE | SS_LEFT,
385-
pos_x, pos_y, SETTINGS_WINDOW_WIDTH - margin, label_height, // Adjust these values as per your UI layout requirement.
357+
pos_x, pos_y, SETTINGS_WINDOW_WIDTH - margin, label_height,
386358
settings_hwnd, NULL, NULL, NULL);
387359

388360
SendMessage(hwnd_spinner_label, WM_SETFONT, reinterpret_cast<WPARAM>(normal_font), TRUE);
@@ -458,7 +430,7 @@ void CreateTrayMenu(const HWND hWnd)
458430
void ShowSettingsWindow()
459431
{
460432
// Update run on startup checkbox
461-
if (StartupRegistryKeyExists())
433+
if (TaskScheduler::TaskExists("ThreeFingerDrag"))
462434
SendMessage(settings_checkbox_hwnd, BM_SETCHECK, BST_CHECKED, 0);
463435
else
464436
SendMessage(settings_checkbox_hwnd, BM_SETCHECK, BST_UNCHECKED, 0);
@@ -501,10 +473,18 @@ void StartPeriodicUpdateThreads()
501473
while (application_running)
502474
{
503475
std::this_thread::sleep_for(TOUCH_ACTIVITY_PERIOD_MS);
504-
if (!gesture_processor.IsDragging())
476+
if (!config->IsCancellationStarted()) {
505477
continue;
506-
507-
gesture_processor.CheckDragInactivity();
478+
}
479+
const auto now = std::chrono::high_resolution_clock::now();
480+
const std::chrono::duration<float> duration = now - config->GetCancellationTime();
481+
const float ms_since_cancellation = duration.count() * 1000.0f;
482+
if (ms_since_cancellation < config->GetCancellationDelayMs()) {
483+
continue;
484+
}
485+
Cursor::LeftMouseUp();
486+
config->SetDragging(false);
487+
config->SetCancellationStarted(false);
508488
}
509489
});
510490
}
@@ -541,7 +521,7 @@ void ReadPrecisionTouchPadInfo()
541521
return;
542522
}
543523
// Changes value from range [0, 20] to range [0.0 -> 1.0]
544-
gesture_processor.SetTouchSpeed(touch_speed * 5 / 100.0f);
524+
config->SetPrecisionTouchCursorSpeed(touch_speed * 5 / 100.0f);
545525
}
546526

547527
/**
@@ -561,15 +541,7 @@ void ReadCursorSpeed()
561541
}
562542

563543
// Changes value from range [0, 20] to range [0.0 -> 1.0]
564-
gesture_processor.SetMouseSpeed(mouse_speed * 5 / 100.0f);
565-
}
566-
567-
/**
568-
* \brief Updates the gesture processor with known config values
569-
*/
570-
void UpdateGestureProcessor() {
571-
gesture_processor.SetGestureSpeed(config->GetGestureSpeed());
572-
gesture_processor.SetSkippedFrameAmount(config->GetSkippedGestureFrames());
544+
config->SetMouseCursorSpeed(mouse_speed * 5 / 100.0f);
573545
}
574546

575547

@@ -614,8 +586,37 @@ bool InitializeWindowsNotifications() {
614586
}
615587

616588
/**
617-
* \brief Checks if the registry key for starting the program at system startup exists.
618-
* \return True if the registry key exists, false otherwise.
589+
* \brief Adds the registry key for starting the program at system startup.
590+
*/
591+
void AddStartupTask()
592+
{
593+
// Remove possible existing registry key from previous version of application
594+
if (StartupRegistryKeyExists())
595+
RemoveStartupRegistryKey();
596+
597+
if (TaskScheduler::CreateLoginTask("ThreeFingerDrag", Application::ExePath().u8string()))
598+
Popups::DisplayInfoMessage("Startup task has been created successfully.");
599+
else
600+
Popups::DisplayErrorMessage("An error occurred while trying to create the login task.");
601+
}
602+
603+
/**
604+
* \brief Removes the registry key for starting the program at system startup. Displays a message box on success.
605+
*/
606+
void RemoveStartupTask()
607+
{
608+
// Remove possible existing registry key from previous version of application
609+
if (StartupRegistryKeyExists())
610+
RemoveStartupRegistryKey();
611+
612+
TaskScheduler::DeleteTask("ThreeFingerDrag");
613+
if (!TaskScheduler::TaskExists("ThreeFingerDrag"))
614+
Popups::DisplayInfoMessage("Startup task has been removed successfully.");
615+
}
616+
617+
618+
/**
619+
* \return True if the registry key for the startup program name exists.
619620
*/
620621
bool StartupRegistryKeyExists()
621622
{
@@ -630,48 +631,63 @@ bool StartupRegistryKeyExists()
630631
}
631632

632633
/**
633-
* \brief Adds the registry key for starting the program at system startup.
634+
* \brief Removes the registry key for starting the program at system startup. Displays a message box on success.
634635
*/
635-
void AddStartupRegistryKey()
636+
void RemoveStartupRegistryKey()
636637
{
637638
HKEY hKey;
638-
WCHAR app_path[MAX_PATH];
639-
const DWORD path_len = GetModuleFileName(nullptr, app_path, MAX_PATH);
640-
if (path_len == 0 || path_len == MAX_PATH)
641-
{
642-
ERROR("Could not retrieve the application path!");
643-
return;
644-
}
645639
LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, STARTUP_REGISTRY_KEY, 0, KEY_WRITE, &hKey);
646640
if (result != ERROR_SUCCESS)
647641
return;
648-
result = RegSetValueEx(hKey, PROGRAM_NAME, 0, REG_SZ, (BYTE*)app_path,
649-
(DWORD)(wcslen(app_path) + 1) * sizeof(wchar_t));
650-
if (result == ERROR_SUCCESS)
651-
Popups::DisplayInfoMessage("Startup task has been created successfully.");
652-
else
653-
Popups::DisplayErrorMessage("An error occurred while trying to set the registry value.");
654-
642+
RegDeleteValue(hKey, PROGRAM_NAME);
655643
RegCloseKey(hKey);
656644
}
657645

658-
/**
659-
* \brief Removes the registry key for starting the program at system startup. Displays a message box on success.
660-
*/
661-
void RemoveStartupRegistryKey()
662-
{
663-
HKEY hKey;
664-
LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, STARTUP_REGISTRY_KEY, 0, KEY_WRITE, &hKey);
665-
if (result != ERROR_SUCCESS)
646+
bool CheckSingleInstance() {
647+
const HANDLE hMutex = CreateMutex(nullptr, TRUE, PROGRAM_NAME);
648+
if (GetLastError() == ERROR_ALREADY_EXISTS)
666649
{
667-
Popups::DisplayErrorMessage("Registry key could not be found.");
668-
return;
650+
Popups::DisplayErrorMessage("Another instance of Three Finger Drag is already running.");
651+
CloseHandle(hMutex);
652+
return false;
669653
}
670-
result = RegDeleteValue(hKey, PROGRAM_NAME);
671-
if (result == ERROR_SUCCESS)
672-
Popups::DisplayInfoMessage("Startup task has been removed successfully.");
654+
return true;
655+
}
673656

674-
RegCloseKey(hKey);
657+
void InitializeConfiguration() {
658+
// Read user configuration values
659+
Application::ReadConfiguration();
660+
ReadPrecisionTouchPadInfo();
661+
ReadCursorSpeed();
662+
663+
// Initialize global strings
664+
LoadStringW(current_instance, IDS_APP_TITLE, title_bar_text, MAX_LOAD_STRING_LENGTH);
665+
LoadStringW(current_instance, IDS_SETTINGS_TITLE, settings_title_text, MAX_LOAD_STRING_LENGTH);
666+
LoadStringW(current_instance, IDC_THREEFINGERDRAG, main_window_class_name, MAX_LOAD_STRING_LENGTH);
667+
LoadStringW(current_instance, IDC_SETTINGS, settings_window_class_name, MAX_LOAD_STRING_LENGTH);
668+
669+
// Register window classes
670+
RegisterWindowClass(current_instance, main_window_class_name, WndProc);
671+
RegisterWindowClass(current_instance, settings_window_class_name, SettingsWndProc);
672+
}
673+
674+
void PromptUserForStartupPreference() {
675+
bool result = Popups::DisplayPrompt("Would you like run ThreeFingerDrag on startup of Windows?", "ThreeFingerDrag");
676+
if (result)
677+
AddStartupTask();
678+
Popups::ShowToastNotification(L"You can access the program in the system tray.", L"Welcome to ThreeFingerDrag!");
679+
}
680+
681+
void PerformAdditionalSteps() {
682+
// First time running application
683+
if (Application::IsInitialStartup())
684+
PromptUserForStartupPreference();
685+
686+
// Replace legacy startup registry key with login task automatically for previous users
687+
if (StartupRegistryKeyExists()) {
688+
RemoveStartupRegistryKey();
689+
TaskScheduler::CreateLoginTask("ThreeFingerDrag", Application::ExePath().u8string());
690+
}
675691
}
676692

677693
ATOM RegisterWindowClass(HINSTANCE hInstance, WCHAR* className, WNDPROC wndProc)

ThreeFingerDrag/ThreeFingerDrag.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
#include <iostream>
55
#include <sstream>
66
#include <Windows.h>
7-
87
#include "resource.h"
9-
#include "logger.h"
8+
#include "logging/logger.h"
9+
#include "task/task_scheduler.h"
1010
#include "application.h"
11-
#include "wintoastlib.h"
12-
#include "popups.h"
11+
#include "notification/wintoastlib.h"
12+
#include "notification/popups.h"
1313
#include <CommCtrl.h>
1414

1515

0 commit comments

Comments
 (0)