Browse Source

Add MagicExplorerButton initial implementation

pull/4527/head
croketworld 4 months ago
parent
commit
642a4595f8
  1. 349
      ExplorerPatcher/MagicExplorerButton/MagicExplorerButton.c
  2. 33
      ExplorerPatcher/MagicExplorerButton/MagicExplorerButton.h
  3. 34
      ExplorerPatcher/MagicExplorerButton/Readme.en.md
  4. 34
      ExplorerPatcher/MagicExplorerButton/Readme.es.md

349
ExplorerPatcher/MagicExplorerButton/MagicExplorerButton.c

@ -0,0 +1,349 @@ @@ -0,0 +1,349 @@
#include "MagicExplorerButton.h"
#include <shlobj.h>
#include <shobjidl.h>
#include <commctrl.h>
#include <shellapi.h>
#include <strsafe.h>
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Ole32.lib")
#pragma comment(lib, "Shell32.lib")
static BOOL g_MEB_Initialized = FALSE;
static BOOL g_MEB_ReadOnly = FALSE;
static HWND g_MEB_ReBar = NULL;
static HWND g_MEB_Toolbar = NULL;
static HIMAGELIST g_MEB_ImageList = NULL;
static MEB_Config g_MEB_Cfg;
static void MEB_GetModuleBasePath(wchar_t* buf, size_t cch) {
DWORD len = GetModuleFileNameW(NULL, buf, (DWORD)cch);
if (len && len < cch) {
for (DWORD i = len; i > 0; --i) {
if (buf[i-1] == L'\\' || buf[i-1] == L'/') { buf[i-1] = 0; break; }
}
} else if (cch) buf[0] = 0;
}
static void MEB_BuildRelativePath(const wchar_t* relative, wchar_t* out, size_t cchOut) {
wchar_t base[MAX_PATH];
MEB_GetModuleBasePath(base, MAX_PATH);
StringCchPrintfW(out, cchOut, L"%s\\%s", base, relative);
}
static WORD MEB_GetPrimaryLang(void) {
LANGID lid = GetUserDefaultUILanguage();
return PRIMARYLANGID(lid);
}
static void MEB_GetDefaultConfig(MEB_Config* cfg) {
ZeroMemory(cfg, sizeof(*cfg));
StringCchCopyW(cfg->label, 64, L"* Configurar *");
StringCchCopyW(cfg->command, MAX_PATH, L"notepad.exe");
if (MEB_GetPrimaryLang() == LANG_SPANISH) {
StringCchCopyW(cfg->args, 512, L"MagicExplorerButton\\Readme.es.md");
} else {
StringCchCopyW(cfg->args, 512, L"MagicExplorerButton\\Readme.en.md");
}
StringCchCopyW(cfg->iconPath, MAX_PATH, L"MagicExplorerButton\\favicon.ico");
StringCchCopyW(cfg->hotIconPath, MAX_PATH, L"MagicExplorerButton\\favicon2.ico");
}
static BOOL MEB_ReadRegistry(MEB_Config* cfg) {
HKEY hKey;
if (RegOpenKeyExW(HKEY_CURRENT_USER, MEB_REG_ROOT, 0, KEY_READ, &hKey) != ERROR_SUCCESS)
return FALSE;
DWORD type, cb;
wchar_t tmp[1024];
struct { const wchar_t* name; wchar_t* target; DWORD cch; } fields[] = {
{ L"Label", cfg->label, 64 },
{ L"Command", cfg->command, MAX_PATH },
{ L"Args", cfg->args, 512 },
{ L"Icon", cfg->iconPath, MAX_PATH },
{ L"HotIcon", cfg->hotIconPath, MAX_PATH },
};
for (int i=0;i<(int)(sizeof(fields)/sizeof(fields[0]));++i) {
cb = sizeof(tmp);
if (RegQueryValueExW(hKey, fields[i].name, NULL, &type, (LPBYTE)tmp, &cb) == ERROR_SUCCESS && type == REG_SZ) {
tmp[(cb/sizeof(wchar_t))-1] = 0;
StringCchCopyW(fields[i].target, fields[i].cch, tmp);
}
}
RegCloseKey(hKey);
return TRUE;
}
static BOOL MEB_WriteDefaultsIfMissing(const MEB_Config* cfg) {
HKEY hKey;
DWORD disp;
LONG st = RegCreateKeyExW(HKEY_CURRENT_USER, MEB_REG_ROOT, 0, NULL, 0, KEY_WRITE|KEY_READ, NULL, &hKey, &disp);
if (st != ERROR_SUCCESS) return FALSE;
if (disp == REG_CREATED_NEW_KEY) {
RegSetValueExW(hKey, L"Label", 0, REG_SZ, (const BYTE*)cfg->label, (DWORD)((wcslen(cfg->label)+1)*sizeof(wchar_t)));
RegSetValueExW(hKey, L"Command", 0, REG_SZ, (const BYTE*)cfg->command, (DWORD)((wcslen(cfg->command)+1)*sizeof(wchar_t)));
RegSetValueExW(hKey, L"Args", 0, REG_SZ, (const BYTE*)cfg->args, (DWORD)((wcslen(cfg->args)+1)*sizeof(wchar_t)));
RegSetValueExW(hKey, L"Icon", 0, REG_SZ, (const BYTE*)cfg->iconPath,(DWORD)((wcslen(cfg->iconPath)+1)*sizeof(wchar_t)));
RegSetValueExW(hKey, L"HotIcon", 0, REG_SZ, (const BYTE*)cfg->hotIconPath,(DWORD)((wcslen(cfg->hotIconPath)+1)*sizeof(wchar_t)));
}
RegCloseKey(hKey);
return TRUE;
}
static BOOL MEB_EnsureRegistryAndLoadConfig(MEB_Config* cfg, BOOL* readOnly) {
*readOnly = FALSE;
MEB_GetDefaultConfig(cfg);
if (!MEB_WriteDefaultsIfMissing(cfg)) {
*readOnly = TRUE;
return TRUE;
}
MEB_ReadRegistry(cfg);
return TRUE;
}
static HICON MEB_LoadIconFromCfg(const wchar_t* relPath, int dpi) {
if (!relPath || !relPath[0]) return NULL;
wchar_t full[MAX_PATH*2];
MEB_BuildRelativePath(relPath, full, _countof(full));
int size = MulDiv(32, dpi, 96);
return (HICON)LoadImageW(NULL, full, IMAGE_ICON, size, size, LR_LOADFROMFILE);
}
static void MEB_DestroyImages(void) {
if (g_MEB_ImageList) {
ImageList_Destroy(g_MEB_ImageList);
g_MEB_ImageList = NULL;
}
}
static void MEB_ApplyToolbarVisuals(void) {
if (!g_MEB_Toolbar) return;
int dpi = GetDpiForWindow(g_MEB_Toolbar);
MEB_DestroyImages();
g_MEB_ImageList = ImageList_Create(MulDiv(32,dpi,96), MulDiv(32,dpi,96), ILC_COLOR32|ILC_MASK, 2, 0);
HICON hNorm = MEB_LoadIconFromCfg(g_MEB_Cfg.iconPath, dpi);
HICON hHot = MEB_LoadIconFromCfg(g_MEB_Cfg.hotIconPath, dpi);
if (!hNorm && hHot) hNorm = hHot;
if (!hNorm) hNorm = LoadIcon(NULL, IDI_APPLICATION);
if (!hHot) hHot = hNorm;
int normIndex = ImageList_AddIcon(g_MEB_ImageList, hNorm);
int hotIndex = ImageList_AddIcon(g_MEB_ImageList, hHot);
if (hNorm) DestroyIcon(hNorm);
if (hHot && hotIndex != normIndex) DestroyIcon(hHot);
SendMessage(g_MEB_Toolbar, TB_SETIMAGELIST, 0, (LPARAM)g_MEB_ImageList);
SendMessage(g_MEB_Toolbar, TB_SETHOTIMAGELIST, 0, (LPARAM)g_MEB_ImageList);
TBBUTTON btn = {0};
btn.iBitmap = 0;
btn.idCommand = MEB_BUTTON_ID;
btn.fsState = TBSTATE_ENABLED;
btn.fsStyle = BTNS_BUTTON | BTNS_SHOWTEXT | BTNS_AUTOSIZE;
btn.iString = (INT_PTR)g_MEB_Cfg.label;
int count = (int)SendMessage(g_MEB_Toolbar, TB_BUTTONCOUNT, 0, 0);
for (int i = count - 1; i >= 0; --i) {
SendMessage(g_MEB_Toolbar, TB_DELETEBUTTON, i, 0);
}
SendMessage(g_MEB_Toolbar, TB_ADDSTRING, 0, (LPARAM)g_MEB_Cfg.label);
SendMessage(g_MEB_Toolbar, TB_ADDBUTTONS, 1, (LPARAM)&btn);
SendMessage(g_MEB_Toolbar, TB_AUTOSIZE, 0, 0);
}
static HWND MEB_FindReBarFromExplorer(HWND hExplorer) {
HWND result = NULL;
HWND child = NULL;
while ((child = FindWindowExW(hExplorer, child, NULL, NULL)) != NULL) {
wchar_t cls[64];
if (GetClassNameW(child, cls, 64) && _wcsicmp(cls, L"ReBarWindow32") == 0) {
result = child;
break;
}
HWND sub = FindWindowExW(child, NULL, L"ReBarWindow32", NULL);
if (sub) { result = sub; break; }
}
return result;
}
BOOL MEB_IsEnabled(void) {
return TRUE;
}
static BOOL MEB_GetActiveFolderPath(HWND hwndExplorer, wchar_t* out, size_t cchOut) {
if (!out || cchOut == 0) return FALSE;
out[0] = 0;
BOOL ok = FALSE;
CoInitialize(NULL);
IShellWindows* pSW = NULL;
if (SUCCEEDED(CoCreateInstance(&CLSID_ShellWindows, NULL, CLSCTX_ALL, &IID_IShellWindows, (void**)&pSW)) && pSW) {
long count=0;
if (SUCCEEDED(pSW->lpVtbl->get_Count(pSW, &count))) {
for (long i=0; i<count; ++i) {
VARIANT idx; VariantInit(&idx); idx.vt = VT_I4; idx.lVal = i;
IDispatch* disp = NULL;
if (SUCCEEDED(pSW->lpVtbl->Item(pSW, idx, &disp)) && disp) {
IWebBrowserApp* wba = NULL;
if (SUCCEEDED(disp->lpVtbl->QueryInterface(disp, &IID_IWebBrowserApp, (void**)&wba)) && wba) {
SHANDLE_PTR hwndW = 0;
if (SUCCEEDED(wba->lpVtbl->get_HWND(wba, &hwndW)) && (HWND)hwndW == hwndExplorer) {
IServiceProvider* prov = NULL;
if (SUCCEEDED(disp->lpVtbl->QueryInterface(disp, &IID_IServiceProvider, (void**)&prov)) && prov) {
IShellBrowser* browser = NULL;
if (SUCCEEDED(prov->lpVtbl->QueryService(prov, &SID_STopLevelBrowser, &IID_IShellBrowser, (void**)&browser)) && browser) {
IShellView* view = NULL;
if (SUCCEEDED(browser->lpVtbl->QueryActiveShellView(browser, &view)) && view) {
IPersistFolder2* pf2 = NULL;
if (SUCCEEDED(view->lpVtbl->GetFolder(view, &IID_IPersistFolder2, (void**)&pf2)) && pf2) {
PIDLIST_ABSOLUTE pidl = NULL;
if (SUCCEEDED(pf2->lpVtbl->GetCurFolder(pf2, &pidl)) && pidl) {
PWSTR pszPath = NULL;
if (SUCCEEDED(SHGetNameFromIDList(pidl, SIGDN_FILESYSPATH, &pszPath)) && pszPath) {
StringCchCopyW(out, cchOut, pszPath);
CoTaskMemFree(pszPath);
ok = TRUE;
}
CoTaskMemFree(pidl);
}
pf2->lpVtbl->Release(pf2);
}
view->lpVtbl->Release(view);
}
browser->lpVtbl->Release(browser);
}
prov->lpVtbl->Release(prov);
}
}
wba->lpVtbl->Release(wba);
}
disp->lpVtbl->Release(disp);
}
}
}
pSW->lpVtbl->Release(pSW);
}
CoUninitialize();
return ok;
}
static void MEB_LaunchCommand(HWND hExplorer) {
BOOL shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
BOOL ctrl = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
if (shift) {
MEB_EnsureRegistryAndLoadConfig(&g_MEB_Cfg, &g_MEB_ReadOnly);
MEB_ApplyToolbarVisuals();
return;
}
if (ctrl) {
g_MEB_ReadOnly = TRUE;
}
if (g_MEB_ReadOnly) {
wchar_t readmeRel[128];
if (MEB_GetPrimaryLang() == LANG_SPANISH)
StringCchCopyW(readmeRel, 128, L"MagicExplorerButton\\Readme.es.md");
else
StringCchCopyW(readmeRel, 128, L"MagicExplorerButton\\Readme.en.md");
wchar_t fullPath[MAX_PATH*2];
MEB_BuildRelativePath(readmeRel, fullPath, _countof(fullPath));
ShellExecuteW(hExplorer, L"open", L"notepad.exe", fullPath, NULL, SW_SHOWNORMAL);
return;
}
wchar_t argsExpanded[1024];
StringCchCopyW(argsExpanded, 1024, g_MEB_Cfg.args);
if (wcsstr(argsExpanded, L"%PATH%")) {
wchar_t currentPath[MAX_PATH];
if (MEB_GetActiveFolderPath(hExplorer, currentPath, MAX_PATH)) {
wchar_t* pos = wcsstr(argsExpanded, L"%PATH%");
if (pos) {
wchar_t buffer[1024];
size_t preLen = pos - argsExpanded;
StringCchPrintfW(buffer, 1024, L"%.*s\"%s\"%s",
(int)preLen, argsExpanded, currentPath, pos + 6);
StringCchCopyW(argsExpanded, 1024, buffer);
}
}
}
wchar_t commandFull[MAX_PATH*2];
if (wcschr(g_MEB_Cfg.command, L'\\') || wcschr(g_MEB_Cfg.command, L'/')) {
MEB_BuildRelativePath(g_MEB_Cfg.command, commandFull, _countof(commandFull));
} else {
StringCchCopyW(commandFull, _countof(commandFull), g_MEB_Cfg.command);
}
if (commandFull[0] == 0) {
MessageBoxW(hExplorer, L"El valor 'Command' está vacío. Edita el registro para configurarlo.", L"MagicExplorerButton", MB_OK|MB_ICONWARNING);
g_MEB_ReadOnly = TRUE;
MEB_LaunchCommand(hExplorer);
return;
}
ShellExecuteW(hExplorer, L"open", commandFull,
g_MEB_Cfg.args[0] ? argsExpanded : NULL,
NULL, SW_SHOWNORMAL);
}
BOOL MEB_HandleCommand(HWND hExplorer, WPARAM wParam, LPARAM lParam) {
if (LOWORD(wParam) == MEB_BUTTON_ID) {
MEB_LaunchCommand(hExplorer);
return TRUE;
}
return FALSE;
}
void MEB_HandleNotify(HWND hExplorer, LPNMHDR hdr) {
if (!hdr) return;
if (hdr->hwndFrom == g_MEB_Toolbar) {
if (hdr->code == NM_RCLICK) {
MEB_EnsureRegistryAndLoadConfig(&g_MEB_Cfg, &g_MEB_ReadOnly);
MEB_ApplyToolbarVisuals();
}
}
}
static void MEB_CreateToolbar(HWND rebar) {
if (g_MEB_Toolbar) return;
g_MEB_Toolbar = CreateWindowExW(
0, TOOLBARCLASSNAMEW, NULL,
WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | TBSTYLE_LIST | TBSTYLE_TRANSPARENT |
CCS_NOPARENTALIGN | CCS_NORESIZE,
0, 0, 0, 0, rebar, NULL, GetModuleHandleW(NULL), NULL
);
if (!g_MEB_Toolbar) return;
SendMessage(g_MEB_Toolbar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
MEB_ApplyToolbarVisuals();
}
static void MEB_InsertBand(HWND rebar) {
if (!g_MEB_Toolbar) return;
REBARBANDINFO rbbi = {0};
rbbi.cbSize = sizeof(rbbi);
rbbi.fMask = RBBIM_CHILD | RBBIM_STYLE | RBBIM_SIZE;
rbbi.fStyle = RBBS_NOGRIPPER | RBBS_VARIABLEHEIGHT;
rbbi.hwndChild = g_MEB_Toolbar;
rbbi.cx = 200;
SendMessage(rebar, RB_INSERTBAND, (WPARAM)-1, (LPARAM)&rbbi);
}
void MEB_InsertOrUpdate(HWND hExplorer) {
if (!MEB_IsEnabled()) return;
if (!g_MEB_Initialized) {
MEB_EnsureRegistryAndLoadConfig(&g_MEB_Cfg, &g_MEB_ReadOnly);
g_MEB_Initialized = TRUE;
}
if (!g_MEB_ReBar) {
g_MEB_ReBar = MEB_FindReBarFromExplorer(hExplorer);
}
if (!g_MEB_ReBar) return;
if (!g_MEB_Toolbar) {
MEB_CreateToolbar(g_MEB_ReBar);
MEB_InsertBand(g_MEB_ReBar);
} else {
MEB_ApplyToolbarVisuals();
}
}

33
ExplorerPatcher/MagicExplorerButton/MagicExplorerButton.h

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
#pragma once
#include <Windows.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MEB_BUTTON_ID 19001
#define MEB_REG_ROOT L"Software\\ExplorerPatcher\\MagicExplorerButton"
typedef struct _MEB_Config {
wchar_t label[64];
wchar_t command[MAX_PATH];
wchar_t args[512];
wchar_t iconPath[MAX_PATH];
wchar_t hotIconPath[MAX_PATH];
} MEB_Config;
BOOL MEB_IsEnabled(void); // Ahora siempre TRUE (placeholder)
void MEB_InsertOrUpdate(HWND hExplorer); // Inserta o actualiza la banda
void MEB_HandleNotify(HWND hExplorer, LPNMHDR hdr);
BOOL MEB_HandleCommand(HWND hExplorer, WPARAM wParam, LPARAM lParam);
// Función de conveniencia para tu código existente
static inline void IfMagicExplorerButtonEnabled(HWND hExplorer) {
if (MEB_IsEnabled()) {
MEB_InsertOrUpdate(hExplorer);
}
}
#ifdef __cplusplus
}
#endif

34
ExplorerPatcher/MagicExplorerButton/Readme.en.md

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
# Magic Explorer Button
This button is added by an extended ExplorerPatcher build.
## Configuration
Registry path:
`HKCU\Software\ExplorerPatcher\MagicExplorerButton`
Values (REG_SZ):
- Label
- Command
- Args
- Icon
- HotIcon (optional)
Example defaults:
- Label = * Configurar *
- Command = notepad.exe
- Args = MagicExplorerButton\Readme.en.md
- Icon = MagicExplorerButton\favicon.ico
- HotIcon = MagicExplorerButton\favicon2.ico
If the registry key cannot be created or read, the button falls back to opening this README (read-only mode).
Interactions:
- Left click: run command (with %PATH% expansion if present).
- Right click OR Shift+Left click: reload configuration.
- Ctrl+Left click: force open this README (ignores command).
- In read-only mode any left click opens this README.
You can use %PATH% inside Args to inject the active folder path (only the current tab, no multi-tab enumeration yet).
No timers or background threads are used.

34
ExplorerPatcher/MagicExplorerButton/Readme.es.md

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
# Magic Explorer Button
Este botón se añade mediante una versión extendida de ExplorerPatcher.
## Configuración
Ruta en el registro:
`HKCU\Software\ExplorerPatcher\MagicExplorerButton`
Valores (REG_SZ):
- Label
- Command
- Args
- Icon
- HotIcon (opcional)
Ejemplo de valores por defecto:
- Label = * Configurar *
- Command = notepad.exe
- Args = MagicExplorerButton\Readme.es.md
- Icon = MagicExplorerButton\favicon.ico
- HotIcon = MagicExplorerButton\favicon2.ico
Si la clave de registro no puede crearse o leerse, el botón abre este README (modo sólo lectura).
Interacciones:
- Clic izquierdo: ejecuta el comando (expandiendo %PATH% si aparece en Args).
- Clic derecho O Shift + clic izquierdo: recarga la configuración.
- Ctrl + clic izquierdo: abre este README (ignora el comando).
- En modo sólo lectura cualquier clic izquierdo abre este README.
Puedes usar %PATH% dentro de Args para inyectar la ruta de la carpeta activa (sólo pestaña actual).
No se emplean timers ni hilos en segundo plano.
Loading…
Cancel
Save