Skip to content

Instantly share code, notes, and snippets.

@etscrivner
Created October 18, 2024 22:39
Show Gist options
  • Save etscrivner/bd6d87a16963244d64e5201ac2c4fb1c to your computer and use it in GitHub Desktop.
Save etscrivner/bd6d87a16963244d64e5201ac2c4fb1c to your computer and use it in GitHub Desktop.
Tiny Win32 Pomodoro Timer
/*
==============================================================================
Pomodoro.c - Tiny Win32 Pomodoro Timer
==============================================================================
*/
/*
==============================================================================
NOTE:
These defines are required in order for the compiler to properly use unicode
strings. Not defining these results in malformed wchar_t strings and a single
character window title
==============================================================================
*/
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
//==============================================================================
#include <windows.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <assert.h>
#include <stdio.h>
#include <wchar.h>
//==============================================================================
/* Define the windows libraries that we want to link against. */
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "shell32.lib") /* shellapi.h */
#pragma comment(lib, "shlwapi.lib")
//==============================================================================
#define kDefaultPomodoroDurationMins 25
const LPCWSTR kClassName = L"Pomodoro";
const LPCWSTR kWindowTitle = L"Pomodoro";
const int kTimerRectX = 10;
const int kTimerRectY = 10;
const int kTimerRectW = 130;
const int kTimerRectH = 50;
//==============================================================================
enum Element_ID {
ELEMENTID_invalid,
ELEMENTID_start_timer,
ELEMENTID_one_second_timer,
ELEMENTID_COUNT
};
typedef enum App_State App_State;
enum App_State {
APPSTATE_idle,
APPSTATE_timer_active,
APPSTATE_completed,
APPSTATE_COUNT
};
static App_State gAppState = APPSTATE_idle;
static int gPomodoroTimeLeftSeconds = 0;
static int gPomodoroDurationMins = kDefaultPomodoroDurationMins;
static int gCompletedCount = 0;
static HWND gStartTimerButton = NULL;
static wchar_t gTimerText[16] = {0};
static HFONT gTimerFont = NULL;
//==============================================================================
LRESULT CALLBACK WindowProc(HWND h_wnd, UINT u_msg, WPARAM w_param, LPARAM l_param);
//==============================================================================
int WINAPI wWinMain(HINSTANCE h_instance, HINSTANCE h_prev_instance, PWSTR p_cmd_line, int n_cmd_show) {
int return_value = 0;
int argc = 0;
LPWSTR* argv = NULL;
argv = CommandLineToArgvW(p_cmd_line, &argc);
if (argv != NULL) {
for (int i = 0; i < argc; ++i) {
if (wcscmp(argv[i], L"--duration") == 0) {
++i;
if (i < argc) {
gPomodoroDurationMins = StrToIntW(argv[i]);
wprintf(L"Default Duration: %d mins\n", gPomodoroDurationMins);
}
}
}
LocalFree(argv);
WNDCLASSW wc = { 0 };
wc.lpfnWndProc = WindowProc;
wc.hInstance = h_instance;
wc.lpszClassName = kClassName;
RegisterClassW(&wc);
HWND h_wnd = CreateWindowExW(
WS_EX_TOPMOST,
kClassName,
kWindowTitle,
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT,
165, 110,
NULL,
NULL,
h_instance,
NULL);
if (h_wnd != NULL) {
ShowWindow(h_wnd, n_cmd_show);
MSG msg = { 0 };
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} else {
MessageBoxW(NULL, L"Failed to create window", L"Error", MB_OK);
return_value = 1;
}
} else {
MessageBoxW(NULL, L"Failed to parse command-line", L"Error", MB_OK);
return_value = 1;
}
return(return_value);
}
//==============================================================================
void StartPomodoro(HWND h_wnd) {
assert(gAppState == APPSTATE_idle);
SetTimer(h_wnd, ELEMENTID_one_second_timer, 1000, NULL);
ShowWindow(gStartTimerButton, SW_HIDE);
gPomodoroTimeLeftSeconds = gPomodoroDurationMins * 60;
swprintf(gTimerText, 16, L"%02d:%02d:%02d", (gPomodoroTimeLeftSeconds / (60 * 60)), (gPomodoroTimeLeftSeconds / 60) % 60, gPomodoroTimeLeftSeconds % 60);
gAppState = APPSTATE_timer_active;
}
//==============================================================================
void StopPomodoro(HWND h_wnd, int did_complete) {
assert(gAppState == APPSTATE_timer_active || gAppState == APPSTATE_completed);
KillTimer(h_wnd, ELEMENTID_one_second_timer);
if (did_complete) {
++gCompletedCount;
gAppState = APPSTATE_completed;
} else {
ShowWindow(gStartTimerButton, SW_SHOW);
gAppState = APPSTATE_idle;
}
InvalidateRect(h_wnd, NULL, TRUE);
}
//==============================================================================
LRESULT CALLBACK WindowProc(HWND h_wnd, UINT u_msg, WPARAM w_param, LPARAM l_param) {
switch (u_msg) {
case WM_CREATE: {
gStartTimerButton = CreateWindow(
L"BUTTON", // Predefined class; Unicode assumed
L"Start Timer", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
kTimerRectX, // x position
kTimerRectY, // y position
kTimerRectW, // Button width
kTimerRectH, // Button height
h_wnd, // Parent window
(HMENU)ELEMENTID_start_timer, // Button ID.
(HINSTANCE) GetWindowLongPtr(h_wnd, GWLP_HINSTANCE),
NULL);
gTimerFont = CreateFont(
48, // Height of font
0, // Width of font
0, // Angle of escapement
0, // Orientation angle
FW_NORMAL, // Font weight
FALSE, // Italic attribute option
FALSE, // Underline attribute option
FALSE, // Strikeout attribute option
DEFAULT_CHARSET, // Character set identifier
OUT_DEFAULT_PRECIS, // Output precision
CLIP_DEFAULT_PRECIS, // Clipping precision
DEFAULT_QUALITY, // Output quality
DEFAULT_PITCH | FF_SWISS, // Pitch and family
L"Segoe UI"); // Font name
return(0);
} break;
case WM_COMMAND: {
switch (LOWORD(w_param)) {
case ELEMENTID_start_timer: {
StartPomodoro(h_wnd);
return(0);
} break;
default: break;
}
} break;
case WM_KEYDOWN: {
if (w_param == VK_ESCAPE) {
DestroyWindow(h_wnd);
} else if (w_param == VK_RETURN) {
if (gAppState == APPSTATE_idle) {
StartPomodoro(h_wnd);
} else if (gAppState == APPSTATE_timer_active) {
StopPomodoro(h_wnd, 0);
} else if (gAppState == APPSTATE_completed) {
StopPomodoro(h_wnd, 0);
}
return(0);
}
} break;
case WM_DESTROY: {
DeleteObject(gTimerFont);
PostQuitMessage(0);
return(0);
} break;
case WM_TIMER: {
if (w_param == ELEMENTID_one_second_timer &&
gAppState == APPSTATE_timer_active) {
--gPomodoroTimeLeftSeconds;
swprintf(gTimerText, 16, L"%02d:%02d:%02d", (gPomodoroTimeLeftSeconds / (60 * 60)), (gPomodoroTimeLeftSeconds / 60) % 60, gPomodoroTimeLeftSeconds % 60);
if (gPomodoroTimeLeftSeconds <= 0) {
StopPomodoro(h_wnd, 1);
} else {
InvalidateRect(h_wnd, NULL, TRUE);
}
}
return(0);
} break;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(h_wnd, &ps);
{
SetBkColor(hdc, RGB(255, 255, 255));
if (gAppState == APPSTATE_timer_active) {
if (gPomodoroTimeLeftSeconds <= (gPomodoroDurationMins * 60) / 4) {
HBRUSH dc_brush = (HBRUSH)GetStockObject(DC_BRUSH);
SelectObject(hdc, dc_brush);
SetDCBrushColor(hdc, RGB(255,255,0));
SetBkColor(hdc, RGB(255, 255, 0));
FillRect(hdc, &ps.rcPaint, dc_brush);
} else {
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW+1));
}
} else if (gAppState == APPSTATE_completed) {
HBRUSH dc_brush = (HBRUSH)GetStockObject(DC_BRUSH);
SelectObject(hdc, dc_brush);
SetDCBrushColor(hdc, RGB(0,255,0));
SetBkColor(hdc, RGB(0, 255, 0));
FillRect(hdc, &ps.rcPaint, dc_brush);
} else {
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW+1));
}
if (gAppState == APPSTATE_timer_active ||
gAppState == APPSTATE_completed) {
RECT timer_rect = { kTimerRectX, kTimerRectY, kTimerRectX + kTimerRectW, kTimerRectY + kTimerRectH };
HFONT old_font = (HFONT)SelectObject(hdc, gTimerFont);
DrawText(hdc, gTimerText, -1, &timer_rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
SelectObject(hdc, old_font);
}
}
EndPaint(h_wnd, &ps);
} break;
default: break;
}
return(DefWindowProc(h_wnd, u_msg, w_param, l_param));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment