Skip to content

Commit a4ee31c

Browse files
authored
Update to v2.16
Bug fixes - Icons on buttons below a duplicated entry were silently dropped; the shift loop in IDM_DUPLICATE_BTN now carries g_icons[i-1] forward correctly. - The separator and category branches in IDC_OK now explicitly zero launchMode, preventing a stale value from leaking if the slot previously held a normal button. - SwitchProfile now cancels any in-flight drag and releases mouse capture before tearing down the old layout, preventing a phantom BN_CLICKED from firing on the new profile's buttons. - The freed tail slot after a delete is now memset to zero, so no stale isSeparator, isCategory, or other fields linger beyond g_count. Security - WriteEscaped now also strips ] in addition to \r, \n, and [, fully closing the section-header injection vector for INI readers that treat an unmatched bracket as a section boundary. - A comment was added at the ShellExecute call site noting that the "open" verb accepts script types (.bat, .ps1, .vbs, etc.) and that the INI should be stored in a user-controlled location only. Optimizations - CreatePen/DeleteObject inside DrawDropLine (called twice per WM_MOUSEMOVE during drag) is replaced with a cached g_hpenDragLine managed alongside the other GDI objects in EnsureDarkGDI/FreeDarkGDI. - Three speculative EnsureDarkGDI calls inside DrawButton were removed; ApplyDarkBackground now calls EnsureDarkGDI unconditionally so light-mode pens and category brushes are always ready before any paint occurs. - Two cursor handles (g_curArrow, g_curSizeNS) are loaded once at startup and reused in WM_MOUSEMOVE, WM_LBUTTONUP, and WM_CAPTURECHANGED instead of calling LoadCursor on every message. - InitCommonControls was replaced with InitCommonControlsEx using ICC_STANDARD_CLASSES | ICC_BAR_CLASSES, which is the correct forward-compatible call and avoids linker warnings on modern SDKs.
1 parent 421a385 commit a4ee31c

1 file changed

Lines changed: 55 additions & 23 deletions

File tree

simple_launcher.c

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* simple_launcher.c - Simple Launcher v2.15
2+
* simple_launcher.c - Simple Launcher v2.16
33
*
44
* Features:
55
* - INI-configured buttons with optional icons, separators, admin elevation
@@ -21,7 +21,7 @@
2121
* - Environment variables - %VAR% expanded in path, args, and working dir
2222
* - Launch mode per button - Normal, Minimized, or Hidden
2323
* - Drag-and-drop button reordering in the normal list view
24-
* - Version 2.15
24+
* - Version 2.16
2525
*
2626
* Compile:
2727
*
@@ -69,6 +69,8 @@ static void RecreateFont(void);
6969
static void SetTitleBarDark(HWND hwnd, int dark);
7070
static void ScanProfiles(void);
7171
static void SwitchProfile(int idx);
72+
static int DropSlotFromClientY(int cy);
73+
static void DrawDropLine(int dropIdx);
7274
static LRESULT CALLBACK ButtonSubclassProc(HWND hwnd, UINT msg, WPARAM wParam,
7375
LPARAM lParam, UINT_PTR uIdSubclass,
7476
DWORD_PTR dwRefData);
@@ -211,6 +213,7 @@ static HBRUSH g_hbrLtCatPre = NULL; /* cached pressed light-mode categ
211213
static HPEN g_hpenDkBorder = NULL; /* cached DK_BORDER outline pen */
212214
static HPEN g_hpenDkSep = NULL; /* cached DK_SEP separator pen */
213215
static HPEN g_hpenLtSep = NULL; /* cached LT_SEP separator pen */
216+
static HPEN g_hpenDragLine = NULL; /* cached drag-drop indicator pen */
214217
static HPEN g_hpenAdminBorder = NULL; /* cached admin border pen */
215218
static COLORREF g_hpenAdminColor = (COLORREF)-1; /* tracks color used for the cached pen */
216219
static HFONT g_hFont = NULL;
@@ -248,6 +251,11 @@ static int g_dragSrcIdx = -1; /* g_buttons index of the button being dragged
248251
static int g_dragDropIdx = -1; /* insertion slot currently under the cursor */
249252
static POINT g_dragStart; /* cursor position when the button was pressed */
250253

254+
/* Cached system cursors - loaded once at startup to avoid repeated LoadCursor
255+
calls inside WM_MOUSEMOVE and WM_LBUTTONUP during drag operations. */
256+
static HCURSOR g_curArrow = NULL;
257+
static HCURSOR g_curSizeNS = NULL;
258+
251259
static const char *g_infoDlgTitle = NULL;
252260
static const char *g_infoDlgContent = NULL;
253261

@@ -385,6 +393,11 @@ static void ScanProfiles(void)
385393
static void SwitchProfile(int idx)
386394
{
387395
if (idx < 0 || idx >= g_profileCount || idx == g_activeProfile) return;
396+
/* Cancel any in-flight drag so the new profile's buttons do not receive
397+
a phantom BN_CLICKED or leftover drag state from the old layout. */
398+
if (g_dragging && g_dragDropIdx >= 0) DrawDropLine(g_dragDropIdx);
399+
g_dragging = 0; g_dragSrcIdx = -1; g_dragDropIdx = -1;
400+
if (GetCapture() == g_hwndMain) ReleaseCapture();
388401
SaveAll();
389402
g_activeProfile = idx;
390403
strcpy(g_iniPath, g_profilePaths[idx]);
@@ -488,15 +501,22 @@ static void LoadButtons(void)
488501
}
489502
}
490503

491-
/* Writes key=value to f, replacing any CR, LF, or '[' in value with a space.
492-
CR/LF prevent a user-supplied string from injecting extra INI lines.
493-
'[' prevents a value from being mis-parsed as a section header by some
494-
INI readers if it appears at the start of a line after line continuation. */
504+
/* Writes key=value to f, replacing any character that could corrupt the INI
505+
structure with a space. CR and LF are stripped to prevent a value from
506+
injecting extra key/value lines. '[' and ']' are stripped to prevent a
507+
value from being mis-parsed as a section header ([Button99]) by INI readers
508+
that do not quote values, even if the bracket appears mid-value after a
509+
newline injected through some other path. */
495510
static void WriteEscaped(FILE *f, const char *key, const char *val)
496511
{
497512
fprintf(f, "%s=", key);
498-
for (; *val; val++)
499-
fputc((*val == '\r' || *val == '\n' || *val == '[') ? ' ' : *val, f);
513+
for (; *val; val++) {
514+
char c = *val;
515+
if (c == '\r' || c == '\n' || c == '[' || c == ']')
516+
fputc(' ', f);
517+
else
518+
fputc(c, f);
519+
}
500520
fputs("\r\n", f);
501521
}
502522

@@ -608,6 +628,7 @@ static void EnsureDarkGDI(void)
608628
if (!g_hpenDkBorder) g_hpenDkBorder = CreatePen(PS_SOLID, 1, DK_BORDER);
609629
if (!g_hpenDkSep) g_hpenDkSep = CreatePen(PS_SOLID, 1, DK_SEP);
610630
if (!g_hpenLtSep) g_hpenLtSep = CreatePen(PS_SOLID, 1, LT_SEP);
631+
if (!g_hpenDragLine) g_hpenDragLine = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
611632
}
612633

613634
/* Releases all cached dark-mode GDI objects. Called when toggling dark mode
@@ -624,6 +645,7 @@ static void FreeDarkGDI(void)
624645
if (g_hpenDkBorder) { DeleteObject(g_hpenDkBorder); g_hpenDkBorder = NULL; }
625646
if (g_hpenDkSep) { DeleteObject(g_hpenDkSep); g_hpenDkSep = NULL; }
626647
if (g_hpenLtSep) { DeleteObject(g_hpenLtSep); g_hpenLtSep = NULL; }
648+
if (g_hpenDragLine) { DeleteObject(g_hpenDragLine); g_hpenDragLine = NULL; }
627649
if (g_hpenAdminBorder) { DeleteObject(g_hpenAdminBorder); g_hpenAdminBorder = NULL;
628650
g_hpenAdminColor = (COLORREF)-1; }
629651
}
@@ -672,7 +694,6 @@ static void DrawButton(LPDRAWITEMSTRUCT dis, int idx)
672694
/* ── Compact tile (non-Add) ── */
673695
if (g_compactMode && idx >= 0) {
674696
if (g_darkMode) {
675-
EnsureDarkGDI();
676697
FillRect(dis->hDC, &rc, pressed ? g_hbrDkBtnPre : g_hbrDkBtn);
677698
HPEN hop = (HPEN)SelectObject(dis->hDC, g_hpenDkBorder);
678699
HBRUSH hn = (HBRUSH)SelectObject(dis->hDC, GetStockObject(NULL_BRUSH));
@@ -729,7 +750,6 @@ static void DrawButton(LPDRAWITEMSTRUCT dis, int idx)
729750
HBRUSH hbr = g_darkMode ? g_hbrDkBg : (HBRUSH)(COLOR_BTNFACE + 1);
730751
FillRect(dis->hDC, &rc, hbr);
731752
int midY = (rc.top + rc.bottom) / 2;
732-
EnsureDarkGDI();
733753
HPEN hOld = (HPEN)SelectObject(dis->hDC,
734754
g_darkMode ? g_hpenDkSep : g_hpenLtSep);
735755
MoveToEx(dis->hDC, rc.left + 6, midY, NULL);
@@ -741,7 +761,6 @@ static void DrawButton(LPDRAWITEMSTRUCT dis, int idx)
741761
/* ── Normal button ── */
742762
int isAdmin = (idx >= 0 && idx < g_count) ? g_buttons[idx].admin : 0;
743763
if (g_darkMode) {
744-
EnsureDarkGDI();
745764
FillRect(dis->hDC, &rc, pressed ? g_hbrDkBtnPre : g_hbrDkBtn);
746765
HPEN hOldP = (HPEN)SelectObject(dis->hDC, g_hpenDkBorder);
747766
HBRUSH hNull = (HBRUSH)SelectObject(dis->hDC, GetStockObject(NULL_BRUSH));
@@ -793,8 +812,10 @@ static void ApplyDarkBackground(void)
793812
if (g_darkMode) {
794813
g_hbrDkBg = CreateSolidBrush(DK_BG);
795814
g_hbrSearchDk = CreateSolidBrush(DK_SEARCH);
796-
EnsureDarkGDI();
797815
}
816+
/* Recreate shared GDI objects unconditionally - category brushes and
817+
separator pens are used in both dark and light mode. */
818+
EnsureDarkGDI();
798819
if (g_hwndSearch) InvalidateRect(g_hwndSearch, NULL, TRUE);
799820
}
800821

@@ -1103,13 +1124,12 @@ static void DrawDropLine(int dropIdx)
11031124
HDC hdc = GetDC(g_hwndMain);
11041125
/* R2_NOT inverts pixels so drawing twice cancels out (XOR line). */
11051126
int oldRop = SetROP2(hdc, R2_NOT);
1106-
HPEN hpen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
1107-
HPEN hold = (HPEN)SelectObject(hdc, hpen);
1127+
/* Use the cached drag-line pen; always available via EnsureDarkGDI. */
1128+
HPEN hold = (HPEN)SelectObject(hdc, g_hpenDragLine);
11081129
RECT cr; GetClientRect(g_hwndMain, &cr);
11091130
MoveToEx(hdc, 10, lineY, NULL);
11101131
LineTo (hdc, cr.right - 10, lineY);
11111132
SelectObject(hdc, hold);
1112-
DeleteObject(hpen);
11131133
SetROP2(hdc, oldRop);
11141134
ReleaseDC(g_hwndMain, hdc);
11151135
}
@@ -1602,6 +1622,7 @@ static LRESULT CALLBACK AddDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
16021622
g_buttons[ti].admin = 0;
16031623
g_buttons[ti].showIcon = 0;
16041624
g_buttons[ti].isCategory = 0;
1625+
g_buttons[ti].launchMode = 0;
16051626
} else if (isCat) {
16061627
strcpy(g_buttons[ti].name, name);
16071628
g_buttons[ti].path[0] = 0;
@@ -1611,6 +1632,7 @@ static LRESULT CALLBACK AddDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lP
16111632
g_buttons[ti].admin = 0;
16121633
g_buttons[ti].showIcon = 0;
16131634
g_buttons[ti].isCategory = 1;
1635+
g_buttons[ti].launchMode = 0;
16141636
} else {
16151637
char iconPath[MAX_PATH], workDir[MAX_PATH];
16161638
GetDlgItemText(hwnd, IDC_ICON_PATH_EDIT, iconPath, MAX_PATH);
@@ -1805,7 +1827,7 @@ static LRESULT CALLBACK MainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar
18051827
abs(cur.y - g_dragStart.y) < DRAG_THRESHOLD) break;
18061828
/* Threshold exceeded: begin the visual drag. */
18071829
g_dragging = 1;
1808-
SetCursor(LoadCursor(NULL, IDC_SIZENS));
1830+
SetCursor(g_curSizeNS);
18091831
}
18101832

18111833
/* Erase old drop line, compute new slot, draw new drop line. */
@@ -1816,7 +1838,7 @@ static LRESULT CALLBACK MainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar
18161838
if (g_dragDropIdx == g_dragSrcIdx || g_dragDropIdx == g_dragSrcIdx + 1)
18171839
g_dragDropIdx = -1;
18181840
if (g_dragDropIdx >= 0) DrawDropLine(g_dragDropIdx);
1819-
SetCursor(LoadCursor(NULL, IDC_SIZENS));
1841+
SetCursor(g_curSizeNS);
18201842
return 0;
18211843
}
18221844

@@ -1847,7 +1869,7 @@ static LRESULT CALLBACK MainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar
18471869
g_dragDropIdx = -1;
18481870
if (dst >= 0) DrawDropLine(dst); /* erase the indicator line */
18491871
ReleaseCapture();
1850-
SetCursor(LoadCursor(NULL, IDC_ARROW));
1872+
SetCursor(g_curArrow);
18511873

18521874
if (dst >= 0 && dst != src && dst != src + 1) {
18531875
/* Move the button from src to dst (adjusting for removal offset). */
@@ -1888,7 +1910,7 @@ static LRESULT CALLBACK MainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar
18881910
if (g_dragDropIdx >= 0) DrawDropLine(g_dragDropIdx);
18891911
g_dragging = 0;
18901912
g_dragDropIdx = -1;
1891-
SetCursor(LoadCursor(NULL, IDC_ARROW));
1913+
SetCursor(g_curArrow);
18921914
}
18931915
g_dragSrcIdx = -1;
18941916
break;
@@ -2039,6 +2061,7 @@ static LRESULT CALLBACK MainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar
20392061
}
20402062
g_icons[g_count - 1] = NULL;
20412063
g_collapsed[g_count - 1] = 0;
2064+
memset(&g_buttons[g_count - 1], 0, sizeof(ButtonConfig));
20422065
g_count--;
20432066
SaveAll(); RefreshMainWindow();
20442067
}
@@ -2048,10 +2071,13 @@ static LRESULT CALLBACK MainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar
20482071
if (g_count >= MAX_BUTTONS) {
20492072
MessageBox(hwnd, "Maximum number of buttons reached.", "Error", MB_OK | MB_ICONWARNING);
20502073
} else {
2051-
/* Shift everything after the source down one slot */
2074+
/* Shift everything after the source down one slot.
2075+
Icons must be carried forward with their button data;
2076+
the old code set g_icons[i] = NULL which silently dropped
2077+
the icon for every button below the duplicate point. */
20522078
for (int i = g_count; i > g_ctxIndex + 1; i--) {
20532079
g_buttons[i] = g_buttons[i - 1];
2054-
g_icons[i] = NULL;
2080+
g_icons[i] = g_icons[i - 1];
20552081
g_collapsed[i] = g_collapsed[i - 1];
20562082
}
20572083
/* Copy the source into the slot immediately below it */
@@ -2249,7 +2275,7 @@ static LRESULT CALLBACK MainProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPar
22492275
} else if (id == ID_HELP_ABOUT) {
22502276
ShowInfoDialog(hwnd, "About Simple Launcher",
22512277
"Simple Launcher\r\n"
2252-
"Version 2.15\r\n"
2278+
"Version 2.16\r\n"
22532279
"\r\n"
22542280
"Author: UberGuidoZ\r\n"
22552281
"Contact: https://github.com/UberGuidoZ");
@@ -2322,7 +2348,13 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPSTR lpCmd, int nShow)
23222348
/* Initialize COM for the apartment so SHBrowseForFolder with
23232349
BIF_NEWDIALOGSTYLE and other shell operations work reliably. */
23242350
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
2325-
InitCommonControls(); /* required to register TOOLTIPS_CLASS and other common controls */
2351+
/* InitCommonControlsEx replaces the deprecated InitCommonControls and
2352+
explicitly registers the standard and tooltip window classes used here. */
2353+
INITCOMMONCONTROLSEX icc = { sizeof(icc), ICC_STANDARD_CLASSES | ICC_BAR_CLASSES };
2354+
InitCommonControlsEx(&icc);
2355+
/* Cache stock cursors once so drag handlers never call LoadCursor per message. */
2356+
g_curArrow = LoadCursor(NULL, IDC_ARROW);
2357+
g_curSizeNS = LoadCursor(NULL, IDC_SIZENS);
23262358
GetBasePath();
23272359
ScanProfiles();
23282360
LoadSettings();

0 commit comments

Comments
 (0)