June 01, 2024, 01:24:23 AM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


File Explorer right mouse click

Started by ckoehn, October 23, 2010, 07:12:02 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

ckoehn

I was wondering if it is possible (I'm sure it is) to add an item to the right click menu when running file explorer.  For instance,  several jpg files will be selected,  the user right clicks, the menu pops up and says "change size", they click on that and my program retrieves the file names and modifies the files.  Is this possible?

Thanks,
Clint

p.s. This may be a question for sapero  :)

sapero

October 23, 2010, 11:24:51 AM #1 Last Edit: October 23, 2010, 01:51:39 PM by sapero
Hi, there are several variants, and this is more registry based instalation.

1. Root key (later named as ROOT)
a) For all users: (single installation)
ROOT = HKEY_CLASSES_ROOT (this key merges two following keys, writing to HKCR is redirected to HKLM if the subkey part does not exists in HKCU\...\classes)
or
ROOT = HKEY_LOCAL_MACHINE\SOFTWARE\Classes

b) For current user (separate installation for each user account)
ROOT = HKEY_CURRENT_USER\Software\Classes

2. Subkey:
a) Read the default registry value from ROOT\.jpg    There will be "jpgfile", or "ACDC_JPG" (without quotes) if you have installed ACDSee (very old 32)
b) create a new key ROOT\X\shell\Y\command, where X is the value from ROOT\.jpg, and Y is the verb you want to add to context menu: Resize
c) in this new key, set the default value to the full path to your program (quoted if contains spaces) with quoted %1 after a space
"c:\my programs\resizer.exe" "%1"
or c:\resizer.exe "%1"

B and C can be done in a single step: just set the value in that key. This key will be automatically created. Use REGGETSTRING, or SHSetValue from shlwapi.inc.

The first variant is ready to use. After installing the keys, optionally press F5 after focusing your shell (desktop), or call a special shell function SHChangeNotify, to ensure that the new verb will be loaded.
Each selected file will execute a new instance of your program, with a parameter:
...\resizer.exe "full path.jpg"


If you want to pass all selected files automatically to single program, use some interprocess communication (like SETCONTROLTEXT/EM_SETTEXT/EM_REPLACESEL + @ENCHANGE, or even a pipe/socket server, WM_COPYDATA message), or create additional DDE subkeys in the registry (solution at request).

Second variant: if you select .jpg and other file types, the first solution will be not working. For this case you could install your program in ROOT\*\shell\Y\command (default value), or create a shell extension (with 3 COM based classes: IClassFactory, IShellExtInit and IContextMenu).

The following installer part is taken from my custom made jpg-resizer, designed for XP (HKCR key). The two doubles from command line are used to divide image width and height:
sub install()

istring jpgfile[256]
int iType, size = 256

if (!SHGetValue(HKEY_CLASSES_ROOT, ".jpg", NULL, &iType, &jpgfile, &size))

lstrcat(jpgfile, "\\shell\\")
pointer verb = &jpgfile + lstrlen(jpgfile)

istring commandline[MAX_PATH+32]
GetModuleFileName(0, &commandline, MAX_PATH)
PathQuoteSpaces(commandline)
pointer args = &commandline + lstrlen(commandline)

wsprintf(verb, "# Resize to 10%%\\command")
wsprintf(args, " \"%%1\" 10.0 10.0")
SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))

wsprintf(verb, "# Resize to 20%%\\command")
wsprintf(args, " \"%%1\" 5.0 5.0")
SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))

wsprintf(verb, "# Resize to 30%%\\command")
wsprintf(args, " \"%%1\" 3.3 3.3")
SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))

wsprintf(verb, "# Resize to 40%%\\command")
wsprintf(args, " \"%%1\" 2.5 2.5")
SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))

wsprintf(verb, "# Resize to 50%%\\command")
wsprintf(args, " \"%%1\" 2.0 2.0")
SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))

wsprintf(verb, "# Resize to 60%%\\command")
wsprintf(args, " \"%%1\" 1.6 1.6")
SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))

wsprintf(verb, "# Resize to 70%%\\command")
wsprintf(args, " \"%%1\" 1.42 1.42")
SHSetValue(HKEY_CLASSES_ROOT, jpgfile, NULL, REG_SZ, commandline, lstrlen(commandline))

endif
MessageBoxA(0,"Instalation completed for .jpg","",MB_ICONINFORMATION)
endsub

ckoehn

Thanks, sapero.  Will have a look at it.  I'll be back if I have questions.

Clint

ckoehn

Sapero,

Could you give me an example of what you mean by this or explain it a little more?  I have the single instance part working.

QuoteIf you want to pass all selected files automatically to single program, use some interprocess communication (like setcontroltext + @enchange)

Thanks,
Clint

sapero

October 28, 2010, 02:38:51 AM #4 Last Edit: October 28, 2010, 04:15:23 AM by sapero
ckoehn, this is the complete example:

On startup, it will register a temporary verb in the shell menu. The first instance will open a window. Each secondary instance will check for "already running", trying to find the window from first instance (for internal communication).

$define CAPTION - this can be any string, but it must be system (or session) wide unique.
$include "shlwapi.inc"

enum controls
IDC_REMOTEFILEPATH = 1000
IDC_UNREGISTER
endenum

DIALOG d1
' option 1: the caption of main window must be constant
$define CAPTION             "{ba644e29-f4f6-4876-b287-5a7d21522cf0}"
$define SINGLEINSTANCEMUTEX "{aa45a30d-86e0-4ac8-82dd-44793cebf08b}"
$define FILELOCKMUTEX       "{3117e49e-4d2e-455b-a251-9eefe31d7355}"


class CSingleInstance
declare CSingleInstance()
declare _CSingleInstance()
declare RegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
declare UnRegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
declare IsAlreadyRunning(),BOOL ' call once! CAPTION,SINGLEINSTANCEMUTEX

' use before and after processing new file
declare AcquireFileLock()   ' FILELOCKMUTEX
declare UnAcquireFileLock() ' FILELOCKMUTEX

HANDLE m_hSingleInstanceMutex
HANDLE m_hFileLockMutex
endclass


' single instance; mutex name is generated from CoCreateGuid+StringFromCLSID2
' GUID g
' iwstring wszGuid[42]
' CoCreateGuid(&g)
' StringFromCLSID2(&g, &wszGuid, 42)
' print(w2s(wszGuid))
CSingleInstance csi

csi.RegisterShellMenuVerb("# Temporary IWBasic Verb", ".jpg", TRUE)

if (csi.IsAlreadyRunning())
if (SendCommandLineToFirstInstance(CAPTION, IDC_REMOTEFILEPATH))
end
endif
' the first instance window was not found, it can open slowly or close
' TODO: create here a loop - ex. 10 tries in a second
' for ...
Sleep(500)
if (csi.m_hSingleInstanceMutex and !WaitForSingleObject(csi.m_hSingleInstanceMutex, 0))
ReleaseMutex(csi.m_hSingleInstanceMutex)
' first instance terminated. Jump over SendCommandLineToFirstInstance and create main window
endif
if (SendCommandLineToFirstInstance(CAPTION, IDC_REMOTEFILEPATH))
end
endif
' next ...
endif


CREATEDIALOG d1,0,0,369,221,0x80CA0080,0,CAPTION,&d1_handler
CONTROL d1,@EDIT,"",3,199,70,20,0x40880080,IDC_REMOTEFILEPATH
CONTROL d1,@BUTTON,"Unregister shell menu verb",132,137,150,20,0x50000000,IDC_UNREGISTER
DOMODAL d1

end


sub SendCommandLineToFirstInstance(string szCaption, int idControl),BOOL
int argc, a
pointer argv, env
BOOL result = TRUE

$undeclare __GetMainArgs
declare cdecl extern __GetMainArgs(pointer argc, pointer ppargv, pointer ppenv, int expand_wildcards)

__GetMainArgs(&argc, &argv, &env, FALSE)
' quit if command line parameters are not provided
if (argc > 1)
result = FALSE ' assume failure in FindWindow and GetDlgItem
' find main window
HWND hwndMainWindow = FindWindow(NULL, szCaption)
if (hwndMainWindow)
' find the edit control
HWND hwndEdit = GetDlgItem(hwndMainWindow, idControl)
if (hwndEdit)

SetForegroundWindow(hwndMainWindow)
if (IsIconic(hwndMainWindow)) then _ShowWindow(hwndMainWindow, SW_RESTORE)

' acquire exclusive access to edit control
csi.AcquireFileLock()

' forward all command line arguments to edit control
for a=1 to argc-1
' select all
SENDMESSAGE hwndEdit, EM_SETSEL, 0, -1
' replace selection - this will execute @ENCHANGE handler
SENDMESSAGE hwndEdit, EM_REPLACESEL, TRUE, *<LPSTR>argv[a]
next a

csi.UnAcquireFileLock()
result = TRUE
endif
endif
endif
return result
endsub


SUB d1_handler(),int
SELECT @MESSAGE
CASE @IDINITDIALOG
CENTERWINDOW d1
' TODO: Initialize any controls here
$ifdef PARSE_CMD_LINE_IN_ONINITDIALOG
ParseCommandLine()
$else
' WM_USER is posted not only from user code,
' so we should initialize lParam and wParam to unique values.
PostMessage(d1.hWnd, WM_USER, &d1_handler, @IDINITDIALOG)
$endif

CASE WM_USER
if ((@WPARAM=&d1_handler) and (@LPARAM=@IDINITDIALOG))
ParseCommandLine()
' else WM_USER was send from other code part
endif

CASE @IDCLOSEWINDOW
CLOSEDIALOG d1,@IDOK

CASE @IDCONTROL
select @WPARAM
case (IDC_REMOTEFILEPATH | (@ENCHANGE<<16))
' read the path from edit IDC_REMOTEFILEPATH
MESSAGEBOX d1, GETCONTROLTEXT(d1, IDC_REMOTEFILEPATH), "File"

case IDC_UNREGISTER
EnableWindow(@LPARAM, FALSE)
csi.UnRegisterShellMenuVerb("# Temporary IWBasic Verb", ".jpg", TRUE)
endselect
'SELECT @CONTROLID
'ENDSELECT
ENDSELECT
RETURN 0
ENDSUB


sub ParseCommandLine()
int a, argc
pointer argv, env, pszFile
__GetMainArgs(&argc, &argv, &env, FALSE)

if (argc > 1)
csi.AcquireFileLock()
for a=1 to argc-1
pszFile = *<LPSTR>argv[a]
' TODO: process file *<string>pszFile
MESSAGEBOX d1, *<string>pszFile, "File"
next a
csi.UnAcquireFileLock()
endif
endsub


sub CSingleInstance::CSingleInstance()
m_hSingleInstanceMutex = 0
m_hFileLockMutex       = 0
endsub


sub CSingleInstance::_CSingleInstance()
if (m_hSingleInstanceMutex) then CloseHandle(m_hSingleInstanceMutex)
UnAcquireFileLock()
if (m_hFileLockMutex) then CloseHandle(m_hFileLockMutex)
endsub


sub CSingleInstance::AcquireFileLock()
if (!m_hFileLockMutex)
m_hFileLockMutex = CreateMutex(0, FALSE, FILELOCKMUTEX)
endif
WaitForSingleObject(m_hFileLockMutex, INFINITE)
endsub


sub CSingleInstance::UnAcquireFileLock()
if (m_hFileLockMutex)
ReleaseMutex(m_hFileLockMutex)
endif
endsub


sub CSingleInstance::IsAlreadyRunning(),BOOL
BOOL result = FALSE

' pass FALSE as the second parameter to CreateMutex
' because we don't need to own this mutex (don't wait for ownership)
m_hSingleInstanceMutex = CreateMutex(0, FALSE, SINGLEINSTANCEMUTEX)
DWORD dwLastError = GetLastError()

if (m_hSingleInstanceMutex)
if (dwLastError = ERROR_ALREADY_EXISTS) ' already running
result = TRUE
endif
else
' CreateMutex failed
if (dwLastError = ERROR_ACCESS_DENIED) ' already running under different credentials (runas)
result = TRUE
' OpenMutex should succeed, but we don't need it
endif
endif
return result
endsub


sub CSingleInstance::RegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
istring szRegPath[256]
istring commandline[MAX_PATH+8]
int iType, size = 256
DWORD hkRoot = IIF(CurrentUserOnly, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)

if (SHGetValue(HKEY_CLASSES_ROOT, szFileType, NULL, &iType, &szRegPath, &size))
' szFileType (.jpg) is probably not yet registered in HKEY_CLASSES_ROOT.
' If so, SHGetValue will return ERROR_PATH_NOT_FOUND error code.
SHSetValue(hkRoot, "Software\\Classes\\jpg", NULL, REG_SZ, "jpgfile", 7)
lstrcpy(&szRegPath, "jpgfile")
endif

' generate command line: "c:\program files\my stuff\jpg resizer.exe" "%1"
GetModuleFileName(0, &commandline, MAX_PATH)
PathQuoteSpaces(commandline)
lstrcat(commandline, " \"%1\"")

pointer p = AllocHeap(len(szRegPath) + len(szVerb) + 33) ' 33 = 1+len("Software\\Classes\\\\shell\\\\command")
if (p)
wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\command", szRegPath, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, commandline, len(commandline))
FreeHeap(p)
endif

endsub


sub CSingleInstance::UnRegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
istring szRegPath[256]
int iType, size = 256
DWORD hkRoot = IIF(CurrentUserOnly, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)

if (!SHGetValue(HKEY_CLASSES_ROOT, szFileType, NULL, &iType, &szRegPath, &size))

pointer p = AllocHeap(len(szRegPath) + len(szVerb) + 33)
if (p)
wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\command", szRegPath, szVerb)
SHDeleteKey(hkRoot, p)
wsprintf(p, "Software\\Classes\\%s\\shell\\%s", szRegPath, szVerb)
SHDeleteKey(hkRoot, p)
FreeHeap(p)
endif
endif
endsub


Added (Un)AcquireFileLock methods, for mutual initialization. Example: application is not running. You do select two images in explorer, and select the new verb from context menu. Two instances of this program are created, one of them is always first (IsAlreadyRunning returns false), so it opens the main window, calls ParseCommandLine where it acquires the lock (AcquireFileLock method) and processes the command line, showing a message box with image path. The second instance hungs at AcquireFileLock (inside SendCommandLineToFirstInstance subroutine), waiting for the first instance to release a mutex, what happens at the end of ParseCommandLine, so only single message box is displayed at a time.

ckoehn

October 28, 2010, 06:47:04 AM #5 Last Edit: October 28, 2010, 06:54:20 AM by ckoehn
Thanks, sapero.  I will study it.  I did have one working.  If I can find time today I'll post it and you can tell me why I shouldn't do what I did.  :)

Clint

ckoehn

This was the way I did it.  ;D

$INCLUDE "windowssdk.inc"

pointer ptxt
string txt

'find filename that was sent
ptxt=GetCommandLine()
txt=*<STRING>ptxt

IF INSTR(txt," "+chr$(34))>0
txt=MID$(txt,INSTR(txt," "+chr$(34))+2)
txt=LEFT$(txt,LEN(txt)-1)
ELSE
txt=""
ENDIF

'create mutex and see if it already exists
INT Mutex=CreateMutex(NULL,TRUE,"TestForRightClick")
IF GetLastError()=ERROR_ALREADY_EXISTS
'get window handle to program
UINT wex=FindWindow(NULL,"Resize JPG")
while wex=0
wex=FindWindow(NULL,"Resize JPG")
ENDWHILE

'get list box handle
UINT eex=FindWindowEx(wex,0,"LISTBOX",0)
while eex=0
eex=FindWindowEx(wex,0,"LISTBOX",0)
ENDWHILE


'add message to listbox
SENDMESSAGE eex,LB_ADDSTRING,0,txt

END
ENDIF

'this is the first instance of the window
window w1
OPENWINDOW w1,0,0,320,240,@TOPMOST|@TOOLWINDOW,0,"Resize JPG",&handler
CONTROL w1,@LISTBOX,"LISTBOX",10,10,295,195,@VSCROLL|@HSCROLL|@TABSTOP|@CTLISTSORT,1
SETHORIZEXTENT w1,1,500
SETFONT w1,"Times New Roman",8,400,0,1

'add filename to listbox
IF txt<>""
ADDSTRING w1,1,txt
ENDIF

run=1
WAITUNTIL run=0

CloseHandle(Mutex)
CLOSEWINDOW w1
END

SUB handler() 'Windows handler
SELECT @MESSAGE 'message that window sent
case @IDCREATE 'new window created
CENTERWINDOW w1 'center the window in the screen

case @IDCLOSEWINDOW 'the 'X' was clicked,
run=0 'set run to 0 so the program will end

ENDSELECT
ENDSUB


Later,
Clint

sapero

October 28, 2010, 01:09:17 PM #7 Last Edit: October 28, 2010, 01:22:16 PM by sapero
Quotetell me why I shouldn't do what I did
Ok :)

Bug: Lines 3-15.
a) It is a very bad idea to copy a string of length up to 32768 bytes, to a 255 bytes buffer (line 12).
  example command line: resize{.exe} "32000 characters here"
b) If the full path to your program will contain spaces (or the file name), string txt will include the .exe part. To skip .exe part, set ptxt to PathGetArgs(GetCommandLine()) or skip it manually. PathGetArgs defined in shlwapi.inc will return a pointer to the space after exe part (if any), or (will return) the pointer to terminating NULL character
c) What if you, or someone else executes your program with two parameters?
  resize path1 path2

Lines 22-24 and 28-30 - heating loops should be reserved for low speed microprocessors, or extreme calculations ;) A simple Sleep(1) inside such loop is welcome, and drastically increases the overall performance.
The two loops are very primitive - do something while it fails. If you run a second instance while the first instance is closing (window is closed and the mutex is still allocated), one of the loops will never terminate. This is one of possible solutions:
UINT wex=FindWindow(NULL,"Resize JPG")
while wex=0
' sleep for ~1ms and check if the first instance closed
if (WaitForSingleObject(Mutex, 1) <> WAIT_TIMEOUT) ' 0 or WAIT_ABANDONED
' yup it closed! Now this thread is owning the mutex, so jump to OPENWINDOW (optionally).
endif
wex=FindWindow(NULL,"Resize JPG")
ENDWHILE

The same for the second loop. WaitForSingleObject (while the first instance is running) is working here like Sleep. It will return zero after the first instance closes handle to its mutex, WAIT_ABANDONED if the thread owning the mutex terminates (second argument in CreateMutes is TRUE), WAIT_FAILED if the handle passed to WaitForSingleObject is invalid, or WAIT_TIMEOUT if the timeout interval elapses.

Additional failure counter inside loops will prevent your loops from infinite waiting:
UINT wex=FindWindow(NULL,"Resize JPG")
int failureCounter = 0
while wex=0
' sleep for ~100 ms and check if the first app closed
if (WaitForSingleObject(Mutex, 100) <> WAIT_TIMEOUT) ' 0 or WAIT_ABANDONED
' yup it closed! Now this thread is owning the mutex, so jump to OPENWINDOW (optionally).
endif
wex=FindWindow(NULL,"Resize JPG")
failureCounter++
if (failureCounter >= 20)
' window is missing for two seconds, its thread must be hung
endif
ENDWHILE

Note the increased timeout in WaitForSingleObject - 100ms instead 1ms. When passing 1 as the timeout, the system may use a greater timeout: 10-20 ms, depending on your hardware (timeGetDevCaps api). 100ms is a safe compromise where +- 20ms makes not a big difference in our waiting loops.

ckoehn

Thanks a bunch sapero.  I am printing that out and will study that too.

Later,
Clint

sapero

November 02, 2010, 02:52:40 AM #9 Last Edit: November 02, 2010, 02:58:21 AM by sapero
This is the DDE version - it does not require command line parsing, because parameters are passed via shell, DDE messages.
By desing it uses single instance (note that there is no mutex), but multiple instances are possible with low level CreateProcess function.
DDE (at least on XP and below) is very sensitive for hung GUI programs.

The registry part installs additionally this program in the Open With shell menu.

$include "shlwapi.inc"
autodefine "OFF"
$define SHELL_VERB "# Temporary IWBasic Verb DDE"
$define FILE_EXT   ".jpg"
$define REGISTER_FOR_CURRENT_USER TRUE

' exe file name should be unique to prevent collisions in HKCR\Applications

enum controls
IDC_FILES = 1000
IDC_UNREGISTER
endenum

DIALOG d1

RegisterShellMenuVerb(SHELL_VERB, FILE_EXT, REGISTER_FOR_CURRENT_USER) ' install registry keys

CREATEDIALOG d1,0,0,369,221,0x80CA0080,0,"Single Instance DDE",&d1_handler
CONTROL d1,@LISTBOX,"",6,6,356,182,0x50A10140,IDC_FILES
CONTROL d1,@BUTTON,"Unregister shell menu verb",132,194,150,20,0x50000000,IDC_UNREGISTER
DOMODAL d1

end

SUB d1_handler(),int
ddeHandleMessage(d1.hWnd, @MESSAGE, @WPARAM, @LPARAM)

SELECT @MESSAGE
CASE @IDINITDIALOG
CENTERWINDOW d1

CASE @IDCLOSEWINDOW
CLOSEDIALOG d1,@IDOK

CASE @IDCONTROL
select @WPARAM
' @WPARAM = @CONTROLID | (@NOTIFYCODE<<16);  is equal to  MAKELONG(@CONTROLID, @NOTIFYCODE)
' @LPARAM - control handle
case IDC_UNREGISTER ' @NOTIFYCODE is zero
EnableWindow(@LPARAM, FALSE)
UnRegisterShellMenuVerb(SHELL_VERB, FILE_EXT, REGISTER_FOR_CURRENT_USER)
endselect
'SELECT @CONTROLID
'ENDSELECT
ENDSELECT
RETURN 0
ENDSUB


sub LoadFileA(LPSTR path)
' ignore duplicates
if (SendDlgItemMessageA(d1.hWnd, IDC_FILES, LB_FINDSTRINGEXACT, -1, path) = -1)
SendDlgItemMessageA(d1.hWnd, IDC_FILES, LB_ADDSTRING, 0, path)
endif
endsub


sub LoadFileW(LPWSTR path)
' ignore duplicates
if (SendDlgItemMessageW(d1.hWnd, IDC_FILES, LB_FINDSTRINGEXACT, -1, path) = -1)
SendDlgItemMessageW(d1.hWnd, IDC_FILES, LB_ADDSTRING, 0, path)
endif
endsub

' DDE main handler
sub ddeHandleMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

select (uMsg)
case WM_DDE_INITIATE
OnDdeInitialize(hwnd, wParam, lParam&0xFFFF, lParam>>16)

case WM_DDE_EXECUTE
OnDdeExecute(hwnd, wParam, lParam)

case WM_DDE_TERMINATE
PostMessage(wParam, WM_DDE_TERMINATE, hwnd, 0)
endselect
endsub

' DDE helper
sub OnDdeInitialize(HWND hwnd, HWND hwndClient, ATOM atomApplication, ATOM atomTopic)

istring szPath[MAX_PATH]
GetModuleFileName(0, &szPath, MAX_PATH)
LPTSTR pName = PathFindFileName(szPath)

ATOM aApp = GlobalAddAtom(pName)
ATOM aTop = GlobalAddAtom("system")

if ((atomApplication = aApp) and (atomTopic = aTop))
SendMessageA(hwndClient, WM_DDE_ACK, hwnd, MAKELPARAM(aApp, aTop))
endif
GlobalDeleteAtom(aApp)
GlobalDeleteAtom(aTop)
endsub

' DDE helper
sub OnDdeExecute(HWND hwnd, HWND hwndClient, HGLOBAL hgl)

istring szPath[MAX_PATH]
iwstring szPathW[MAX_PATH]

USHORT code = 0x0000

if (hgl)

LPTSTR text = GlobalLock(hgl)
if (text)

' [Open("%1")]
if (!strnicmp(text, "[Open(\"", 7))

LPTSTR path = &*<UCHAR>text[7]
LPTSTR pend = strstr(path, "\")]")
if (pend)

int cch = pend - path
if (cch < MAX_PATH)

MoveMemory(&szPath, path, cch)
szPath[cch] = 0
BringMainWindowToTop(hwnd)
LoadFileA(szPath)
code = 0x8000
endif
endif

elseif (!_wcsnicmp(text, L"[Open(\"", 7))

LPWSTR pathW = *<WCHAR>text[7]
LPWSTR endW = wcsstr(pathW, L"\")]")
if (endW)

cch = (endW - pathW)/2
if (cch < MAX_PATH)

MoveMemory(&szPathW, pathW, cch<<1)
szPathW[cch] = 0
BringMainWindowToTop(hwnd)
LoadFileW(pathW)
code = 0x8000
endif
endif
endif
GlobalUnlock(text)
endif
endif
PostMessage(hwndClient, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, code, hgl))
endsub

' DDE helper
sub BringMainWindowToTop(HWND hwnd)
if (IsIconic(hwnd)) then _ShowWindow(hwnd, SW_RESTORE)
SetActiveWindow(hwnd)
SetForegroundWindow(hwnd)
endsub

' Registry - installation
sub RegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
istring szRegPath[256]
istring commandline[MAX_PATH+8]
istring szExeName[MAX_PATH]
int size = 256
HKEY hk
DWORD hkRoot = IIF(CurrentUserOnly, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)

if (SHGetValue(HKEY_CLASSES_ROOT, szFileType, NULL, NULL, &szRegPath, &size))
' szFileType (.jpg) is probably not yet registered in HKEY_CLASSES_ROOT.
' If so, SHGetValue will return ERROR_PATH_NOT_FOUND error code.
SHSetValue(hkRoot, "Software\\Classes\\jpg", NULL, REG_SZ, "jpgfile", 7)
lstrcpy(&szRegPath, "jpgfile")
endif

' generate command line: "c:\program files\my stuff\jpg resizer.exe"
int cchExeName
RegistryRegUnRegHelper(&commandline, &szExeName, &cchExeName)
PathQuoteSpaces(commandline)

int cchVerb = len(szVerb)
int cchCommandline = len(commandline)
int cch1 = len(szRegPath) + cchVerb + 33
int cch2 = cchExeName + cchVerb + 58
int cch3 = len(szFileType) + cchExeName + 32
pointer p = AllocHeap(__max(__max(cch1, cch2), cch3))
' 33 = 1+len("Software\Classes\\shell\\command")
' 58 = 1+len("Software\Classes\Applications\\shell\\ddeexec\application")
' 32   1+len("Software\Classes\\OpenWithList\")
if (p)
' 1. shell verb:
' Software\Classes\jpgfile\shell\open\command
wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\command", szRegPath, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, commandline, cchCommandline)
' with DDE
' Software\Classes\jpgfile\shell\open\ddeexec
wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\ddeexec", szRegPath, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, "[open(\"%1\")]", 12)
' Software\Classes\jpgfile\shell\open\ddeexec\application
wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\ddeexec\\application", szRegPath, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, szExeName, cchExeName)
' Software\Classes\jpgfile\shell\open\ddeexec\topic
wsprintf(p, "Software\\Classes\\%s\\shell\\%s\\ddeexec\\topic", szRegPath, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, "system", 6)

' 2. DDE keys for Open With
' Software\Classes\Applications\jpgresizer.exe\shell\open\command
wsprintf(p, "Software\\Classes\\Applications\\%s\\shell\\%s\\command", szExeName, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, commandline, cchCommandline)
' Software\Classes\Applications\jpgresizer.exe\shell\open\ddeexec
wsprintf(p, "Software\\Classes\\Applications\\%s\\shell\\%s\\ddeexec", szExeName, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, "[open(\"%1\")]", 12)
' Software\Classes\Applications\jpgresizer.exe\shell\open\ddeexec\application
wsprintf(p, "Software\\Classes\\Applications\\%s\\shell\\%s\\ddeexec\\application", szExeName, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, szExeName, cchExeName)
' Software\Classes\Applications\jpgresizer.exe\shell\open\ddeexec\topic
wsprintf(p, "Software\\Classes\\Applications\\%s\\shell\\%s\\ddeexec\\topic", szExeName, szVerb)
SHSetValue(hkRoot, p, NULL, REG_SZ, "system", 6)

' 3. keys for Open With
' Software\Classes\jpgfile\OpenWithList\jpgresizer.exe
wsprintf(p, "Software\\Classes\\%s\\OpenWithList\\%s", szFileType, szExeName)
if (!RegCreateKeyEx(hkRoot, p, 0, 0, 0, KEY_WRITE, 0, &hk, 0))
RegCloseKey(hk)
endif
FreeHeap(p)
endif
endsub

' Registry - uninstallation
sub UnRegisterShellMenuVerb(string szVerb, string szFileType, BOOL CurrentUserOnly)
istring szRegPath[256]
istring commandline[MAX_PATH+8]
istring szExeName[MAX_PATH]
int size = 256
DWORD hkRoot = IIF(CurrentUserOnly, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)

if (!SHGetValue(HKEY_CLASSES_ROOT, szFileType, NULL, NULL, &szRegPath, &size))

int cchExeName
RegistryRegUnRegHelper(&commandline, &szExeName, &cchExeName)

int cch1 = len(szRegPath) + len(szVerb) + 25 ' 25 = 1+len("Software\Classes\\shell\")
int cch2 = cchExeName + 31                   ' 31 = 1+len("Software\Classes\Applications\")
int cch3 = len(szFileType) + cchExeName + 32 ' 32 = 1+len("Software\Classes\\OpenWithList\")
pointer p = AllocHeap(__max(__max(cch1, cch2), cch3))
if (p)
wsprintf(p, "Software\\Classes\\%s\\shell\\%s", szRegPath, szVerb)
SHDeleteKey(hkRoot, p)

wsprintf(p, "Software\\Classes\\Applications\\%s", szExeName)
SHDeleteKey(hkRoot, p)

wsprintf(p, "Software\\Classes\\%s\\OpenWithList\\%s", szFileType, szExeName)
SHDeleteKey(hkRoot, p)

FreeHeap(p)
endif
endif
endsub


sub RegistryRegUnRegHelper(LPSTR commandline, LPSTR pszExeName, pointer ppCchExeName)
GetModuleFileName(0, commandline, MAX_PATH)
lstrcpy(pszExeName, PathFindFileName(commandline))
*<int>ppCchExeName = len(*<string>pszExeName)
endsub

ckoehn

Thanks again Sapero.  I have a lot to learn.

Later,
Clint