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
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);
6969static void SetTitleBarDark (HWND hwnd , int dark );
7070static void ScanProfiles (void );
7171static void SwitchProfile (int idx );
72+ static int DropSlotFromClientY (int cy );
73+ static void DrawDropLine (int dropIdx );
7274static 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
211213static HPEN g_hpenDkBorder = NULL ; /* cached DK_BORDER outline pen */
212214static HPEN g_hpenDkSep = NULL ; /* cached DK_SEP separator pen */
213215static HPEN g_hpenLtSep = NULL ; /* cached LT_SEP separator pen */
216+ static HPEN g_hpenDragLine = NULL ; /* cached drag-drop indicator pen */
214217static HPEN g_hpenAdminBorder = NULL ; /* cached admin border pen */
215218static COLORREF g_hpenAdminColor = (COLORREF )- 1 ; /* tracks color used for the cached pen */
216219static HFONT g_hFont = NULL ;
@@ -248,6 +251,11 @@ static int g_dragSrcIdx = -1; /* g_buttons index of the button being dragged
248251static int g_dragDropIdx = -1 ; /* insertion slot currently under the cursor */
249252static 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+
251259static const char * g_infoDlgTitle = NULL ;
252260static const char * g_infoDlgContent = NULL ;
253261
@@ -385,6 +393,11 @@ static void ScanProfiles(void)
385393static 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. */
495510static 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