From 920301f6d4482eded58db287b0567ff59e6c3c1e Mon Sep 17 00:00:00 2001 From: Krzysztof Kowalczyk Date: Sat, 28 Oct 2023 18:32:14 +0200 Subject: [PATCH] refactor dde handling --- src/Commands.h | 2 +- src/SearchAndDDE.cpp | 249 ++++++++++++++++++++++++++++++----------- src/SearchAndDDE.h | 5 +- src/SumatraStartup.cpp | 2 +- src/utils/WinUtil.cpp | 43 +++++++ src/utils/WinUtil.h | 5 + 6 files changed, 239 insertions(+), 67 deletions(-) diff --git a/src/Commands.h b/src/Commands.h index 1084436a11e7..879e7cd1bcfc 100644 --- a/src/Commands.h +++ b/src/Commands.h @@ -175,7 +175,7 @@ Cmd* enum (e.g. CmdOpen) and a human-readable name (not used yet). V(CmdNextTab, "Next Tab") \ V(CmdPrevTab, "Previous Tab") \ V(CmdSelectNextTheme, "Select next theme") \ - V(CmdToggleFrequentlyRead, "Toggle Frequently Read") \ + V(CmdToggleFrequentlyRead, "Toggle Frequently Read") \ V(CmdNone, "Do nothing") //V(CmdSelectAnnotation, "Select Annotation") \ diff --git a/src/SearchAndDDE.cpp b/src/SearchAndDDE.cpp index 2cc9ca7876d6..5f461302fad3 100644 --- a/src/SearchAndDDE.cpp +++ b/src/SearchAndDDE.cpp @@ -602,21 +602,6 @@ void ShowForwardSearchResult(MainWindow* win, const char* fileName, int line, in // DDE commands handling -LRESULT OnDDEInitiate(HWND hwnd, WPARAM wp, LPARAM lp) { - ATOM aServer = GlobalAddAtom(PDFSYNC_DDE_SERVICE); - ATOM aTopic = GlobalAddAtom(PDFSYNC_DDE_TOPIC); - - if (LOWORD(lp) == aServer && HIWORD(lp) == aTopic) { - SendMessageW((HWND)wp, WM_DDE_ACK, (WPARAM)hwnd, MAKELPARAM(aServer, 0)); - } else { - GlobalDeleteAtom(aServer); - GlobalDeleteAtom(aTopic); - } - return 0; -} - -// DDE commands - /* Forward search (synchronization) DDE command @@ -628,7 +613,7 @@ if pdffilepath is provided, the file will be opened if no open window can be fou if newwindow = 1 then a new window is created even if the file is already open if focus = 1 then the focus is set to the window */ -static const char* HandleSyncCmd(const char* cmd, DDEACK& ack) { +static const char* HandleSyncCmd(const char* cmd, bool* ack) { AutoFreeStr pdfFile, srcFile; BOOL line = 0, col = 0, newWindow = 0, setFocus = 0; const char* next = str::Parse(cmd, "[ForwardSearch(\"%s\",%? \"%s\",%u,%u)]", &pdfFile, &srcFile, &line, &col); @@ -679,7 +664,6 @@ static const char* HandleSyncCmd(const char* cmd, DDEACK& ack) { return next; } - ack.fAck = 1; int page; Vec rects; int ret = dm->pdfSync->SourceToDoc(srcFile, line, col, &page, rects); @@ -688,6 +672,7 @@ static const char* HandleSyncCmd(const char* cmd, DDEACK& ack) { win->Focus(); } + *ack = true; return next; } @@ -696,7 +681,7 @@ Search DDE command [Search("","")] */ -static const char* HandleSearchCmd(const char* cmd, DDEACK& ack) { +static const char* HandleSearchCmd(const char* cmd, bool* ack) { AutoFreeStr pdfFile; AutoFreeStr term; const char* next = str::Parse(cmd, "[Search(\"%s\",\"%s\")]", &pdfFile, &term); @@ -720,11 +705,11 @@ static const char* HandleSearchCmd(const char* cmd, DDEACK& ack) { return next; } } - ack.fAck = 1; bool wasModified = true; bool showProgress = true; FindTextOnThread(win, TextSearchDirection::Forward, term, wasModified, showProgress); win->Focus(); + *ack = true; return next; } @@ -733,7 +718,7 @@ Open file DDE Command [Open(""[,,,])] */ -static const char* HandleOpenCmd(const char* cmd, DDEACK& ack) { +static const char* HandleOpenCmd(const char* cmd, bool* ack) { AutoFreeStr pdfFile; int newWindow = 0; BOOL setFocus = 0; @@ -785,7 +770,6 @@ static const char* HandleOpenCmd(const char* cmd, DDEACK& ack) { return next; } - ack.fAck = 1; if (forceRefresh) { ReloadDocument(win, true); } @@ -793,6 +777,7 @@ static const char* HandleOpenCmd(const char* cmd, DDEACK& ack) { win->Focus(); } + *ack = true; return next; } @@ -803,7 +788,7 @@ DDE command: jump to named destination in an already opened document. e.g.: [GoToNamedDest("c:\file.pdf", "chapter.1")] */ -static const char* HandleGotoCmd(const char* cmd, DDEACK& ack) { +static const char* HandleGotoCmd(const char* cmd, bool* ack) { AutoFreeStr pdfFile, destName; const char* next = str::Parse(cmd, "[GotoNamedDest(\"%s\",%? \"%s\")]", &pdfFile, &destName); if (!next) { @@ -822,8 +807,8 @@ static const char* HandleGotoCmd(const char* cmd, DDEACK& ack) { } win->linkHandler->GotoNamedDest(destName); - ack.fAck = 1; win->Focus(); + *ack = true; return next; } @@ -834,7 +819,7 @@ DDE command: jump to a page in an already opened document. eg: [GoToPage("c:\file.pdf",37)] */ -static const char* HandlePageCmd(HWND, const char* cmd, DDEACK& ack) { +static const char* HandlePageCmd(HWND, const char* cmd, bool* ack) { AutoFreeStr pdfFile; uint page = 0; const char* next = str::Parse(cmd, "[GotoPage(\"%S\",%u)]", &pdfFile, &page); @@ -861,7 +846,7 @@ static const char* HandlePageCmd(HWND, const char* cmd, DDEACK& ack) { } win->ctrl->GoToPage(page, true); - ack.fAck = 1; + *ack = true; win->Focus(); return next; } @@ -869,26 +854,26 @@ static const char* HandlePageCmd(HWND, const char* cmd, DDEACK& ack) { /* Set view mode and zoom level DDE command -[SetView("", "", [, , ])] +[SetView("", "", [, , ])] eg: [SetView("c:\file.pdf", "book view", -2)] use -1 for kZoomFitPage, -2 for kZoomFitWidth and -3 for kZoomFitContent */ -static const char* HandleSetViewCmd(const char* cmd, DDEACK& ack) { - AutoFreeStr pdfFile, viewMode; +static const char* HandleSetViewCmd(const char* cmd, bool* ack) { + AutoFreeStr filePath, viewMode; float zoom = kInvalidZoom; Point scroll(-1, -1); - const char* next = str::Parse(cmd, "[SetView(\"%s\",%? \"%s\",%f)]", &pdfFile, &viewMode, &zoom); + const char* next = str::Parse(cmd, "[SetView(\"%s\",%? \"%s\",%f)]", &filePath, &viewMode, &zoom); if (!next) { next = - str::Parse(cmd, "[SetView(\"%s\",%? \"%s\",%f,%d,%d)]", &pdfFile, &viewMode, &zoom, &scroll.x, &scroll.y); + str::Parse(cmd, "[SetView(\"%s\",%? \"%s\",%f,%d,%d)]", &filePath, &viewMode, &zoom, &scroll.x, &scroll.y); } if (!next) { return nullptr; } - MainWindow* win = FindMainWindowByFile(pdfFile, true); + MainWindow* win = FindMainWindowByFile(filePath, true); if (!win) { return next; } @@ -915,8 +900,60 @@ static const char* HandleSetViewCmd(const char* cmd, DDEACK& ack) { ss.y = scroll.y; dm->SetScrollState(ss); } + *ack = true; + return next; +} + +/* +[GetFileState("")] +[GetFileState()] +[GetFileState] +Return info about document or currently viewed document if no + given. +Returns info in the format: + +path: c:\file.pdf +zoom: 1.34 +view: continuous +sumver: 3.5 + +i.e. multiple lines, each line is +key: value +This should make parsing easy: +* split by `\n' to get the lines +* split each line by ':' to get key and value + +Returns: +error: +if file doesn't exist or no opened file +*/ +static const char* HandleGetFileStateCmd(HWND hwnd, const char* cmd, bool* ack, str::Str& res) { + AutoFreeStr filePath; + const char* next = str::Parse(cmd, "[GetFileState(\"%s\")]", &filePath); + if (!next) { + next = str::Parse(cmd, "[GetFileState()]"); + } + if (!next) { + next = str::Parse(cmd, "[GetFileState]"); + } + if (!next) { + return nullptr; + } - ack.fAck = 1; + res.Append("error: hello"); + MainWindow* win = FindMainWindowByFile(filePath, true); + if (!win) { + return next; + } + if (!win->IsDocLoaded()) { + ReloadDocument(win, false); + if (!win->IsDocLoaded()) { + return next; + } + } + + res.Append("error: hello"); + *ack = true; return next; } @@ -924,7 +961,7 @@ static const char* HandleSetViewCmd(const char* cmd, DDEACK& ack) { Handle all commands as defined in Commands.h eg: [CmdClose] */ -static const char* HandleCmdCommand(HWND hwnd, const char* cmd, DDEACK& ack) { +static const char* HandleCmdCommand(HWND hwnd, const char* cmd, bool* ack) { AutoFreeStr cmdName; const char* next = str::Parse(cmd, "[%s]", &cmdName); if (!next) { @@ -941,71 +978,145 @@ static const char* HandleCmdCommand(HWND hwnd, const char* cmd, DDEACK& ack) { } logfa("HandleCmdCommand: sending %d (%s) command\n", cmdId, cmdName.Get()); SendMessageW(win->hwndFrame, WM_COMMAND, cmdId, 0); - ack.fAck = 1; + *ack = true; return next; } -static void HandleDdeCmds(HWND hwnd, const char* cmd, DDEACK& ack) { +// returns true if did handle a message +static bool HandleExecuteCmds(HWND hwnd, const char* cmd) { + bool didHandle = false; while (!str::IsEmpty(cmd)) { { - logf("HandleDdeCmds: '%s'\n", cmd); + logf("HandleExecuteCmds: '%s'\n", cmd); } - const char* nextCmd = HandleSyncCmd(cmd, ack); + const char* nextCmd = HandleSyncCmd(cmd, &didHandle); if (!nextCmd) { - nextCmd = HandleOpenCmd(cmd, ack); + nextCmd = HandleOpenCmd(cmd, &didHandle); } if (!nextCmd) { - nextCmd = HandleGotoCmd(cmd, ack); + nextCmd = HandleGotoCmd(cmd, &didHandle); } if (!nextCmd) { - nextCmd = HandlePageCmd(hwnd, cmd, ack); + nextCmd = HandlePageCmd(hwnd, cmd, &didHandle); } if (!nextCmd) { - nextCmd = HandleSetViewCmd(cmd, ack); + nextCmd = HandleSetViewCmd(cmd, &didHandle); } if (!nextCmd) { - nextCmd = HandleSearchCmd(cmd, ack); + nextCmd = HandleSearchCmd(cmd, &didHandle); } if (!nextCmd) { - nextCmd = HandleCmdCommand(hwnd, cmd, ack); + nextCmd = HandleCmdCommand(hwnd, cmd, &didHandle); } if (!nextCmd) { + // backwards compatibility: ignore unknown commands (maybe from newer version) AutoFreeStr tmp; nextCmd = str::Parse(cmd, "%s]", &tmp); } cmd = nextCmd; } + return didHandle; } -LRESULT OnDDExecute(HWND hwnd, WPARAM wp, LPARAM lp) { - UINT_PTR lo = 0, hi = 0; - if (!UnpackDDElParam(WM_DDE_EXECUTE, lp, &lo, &hi)) { - return 0; +static bool HandleRequestCmds(HWND hwnd, const char* cmd, str::Str& rsp) { + bool didHandle = false; + while (!str::IsEmpty(cmd)) { + { + logf("HandleRequestCmds: '%s'\n", cmd); + } + + const char* nextCmd = HandleGetFileStateCmd(hwnd, cmd, &didHandle, rsp); + if (!nextCmd) { + AutoFreeStr tmp; + nextCmd = str::Parse(cmd, "%s]", &tmp); + } + cmd = nextCmd; } + return didHandle; +} - DDEACK ack{}; - LPVOID command = GlobalLock((HGLOBAL)hi); - if (!command) { +LRESULT OnDDERequest(HWND hwnd, WPARAM wp, LPARAM lp) { + // window that is sending us the message + HWND hwndClient = (HWND)wp; + + UINT fmt = LOWORD(lp); + switch (fmt) { + case CF_TEXT: + case CF_UNICODETEXT: + // we handle those + break; + default: + logf("OnDDERequest: invalid fmt '%s'\n", (int)fmt); + return 0; + } + ATOM a = HIWORD(lp); + TempStr cmd = AtomToStrTemp(a); + if (!cmd) { return 0; } - char* cmd; - if (IsWindowUnicode((HWND)wp)) { - cmd = ToUtf8Temp((WCHAR*)command); + str::Str str; + bool didHandle = HandleRequestCmds(hwnd, cmd, str); + if (!didHandle) { + str.Set("error: unknoqn command"); + } + + void* data; + int cbData; + if (fmt == CF_TEXT) { + data = (void*)str.Get(); + cbData = str.isize() + 1; + } else if (fmt == CF_UNICODETEXT) { + TempWStr tmp = ToWStrTemp(str.Get()); + data = (void*)tmp; + cbData = (str::Len(tmp) + 1) * 2; } else { - cmd = (char*)command; + CrashIf(true); + return 0; } - HandleDdeCmds(hwnd, cmd, ack); - GlobalUnlock((HGLOBAL)hi); - lp = ReuseDDElParam(lp, WM_DDE_EXECUTE, WM_DDE_ACK, *(WORD*)&ack, hi); - PostMessageW((HWND)wp, WM_DDE_ACK, (WPARAM)hwnd, lp); + int cbDdeData = sizeof(DDEDATA); + u8* res = (u8*)Allocator::AllocZero(GetTempAllocator(), cbDdeData + cbData); + DDEDATA* ddeData = (DDEDATA*)res; + ddeData->fRelease = 1; // tell client to free HGLOBAL + ddeData->cfFormat = fmt; + memcpy(res + cbDdeData, data, cbData); + + HGLOBAL h = MemToHGLOBAL(res, cbDdeData + cbData, GMEM_MOVEABLE | GMEM_DDESHARE); + LPARAM lpres = MAKELPARAM(h, a); + PostMessageW(hwndClient, WM_DDE_DATA, (WPARAM)hwnd, lpres); + return 0; +} + +LRESULT OnDDExecute(HWND hwnd, WPARAM wp, LPARAM lp) { + HWND hwndClient = (HWND)wp; + HGLOBAL hCommand = (HGLOBAL)lp; + bool isUnicode = IsWindowUnicode(hwndClient); + + TempStr cmd = HGLOBALToStrTemp((HGLOBAL)hCommand, isUnicode); + bool didHandle = HandleExecuteCmds(hwnd, cmd); + DDEACK ack{}; + ack.fAck = didHandle ? 0 : 1; + LPARAM lpres = PackDDElParam(WM_DDE_ACK, *(WORD*)&ack, (UINT_PTR)hCommand); + PostMessageW(hwndClient, WM_DDE_ACK, (WPARAM)hwnd, lpres); + return 0; +} + +LRESULT OnDDEInitiate(HWND hwnd, WPARAM wp, LPARAM lp) { + ATOM aServer = GlobalAddAtom(kSumatraDdeServer); + ATOM aTopic = GlobalAddAtom(kSumatraDdeTopic); + + if (LOWORD(lp) == aServer && HIWORD(lp) == aTopic) { + SendMessageW((HWND)wp, WM_DDE_ACK, (WPARAM)hwnd, MAKELPARAM(aServer, 0)); + } else { + GlobalDeleteAtom(aServer); + GlobalDeleteAtom(aTopic); + } return 0; } LRESULT OnDDETerminate(HWND hwnd, WPARAM wp, LPARAM) { - // Respond with another WM_DDE_TERMINATE message PostMessageW((HWND)wp, WM_DDE_TERMINATE, (WPARAM)hwnd, 0L); return 0; } @@ -1021,8 +1132,20 @@ LRESULT OnCopyData(HWND hwnd, WPARAM wp, LPARAM lp) { return FALSE; } - DDEACK ack{}; - char* cmd = ToUtf8Temp(cmdW); - HandleDdeCmds(hwnd, cmd, ack); - return ack.fAck ? TRUE : FALSE; + TempStr cmd = ToUtf8Temp(cmdW); + bool didHandle = HandleExecuteCmds(hwnd, cmd); + return didHandle ? TRUE : FALSE; +} + +#if 0 +bool RegisterDDeServer() { + DWORD ddeInst = (DWORD)-1; + auto err = DdeInitializeW(&ddeInst, nullptr, APPCMD_CLIENTONLY | CBF_FAIL_ADVISES, 0); + if (err != DMLERR_NO_ERROR) { + // Handle initialization error + logf("RegisterDDeServer: DdeInitializeW() failed with '%d'\n", (int)err); + return false; + } + return true; } +#endif diff --git a/src/SearchAndDDE.h b/src/SearchAndDDE.h index 637db179995d..60cf3a927d22 100644 --- a/src/SearchAndDDE.h +++ b/src/SearchAndDDE.h @@ -1,11 +1,12 @@ /* Copyright 2022 the SumatraPDF project authors (see AUTHORS file). License: GPLv3 */ -#define PDFSYNC_DDE_SERVICE L"SUMATRA" -#define PDFSYNC_DDE_TOPIC L"control" +#define kSumatraDdeServer L"SUMATRA" +#define kSumatraDdeTopic L"control" LRESULT OnDDEInitiate(HWND hwnd, WPARAM wp, LPARAM lp); LRESULT OnDDExecute(HWND hwnd, WPARAM wp, LPARAM lp); +LRESULT OnDDERequest(HWND hwnd, WPARAM wp, LPARAM lp); LRESULT OnDDETerminate(HWND hwnd, WPARAM wp, LPARAM lp); LRESULT OnCopyData(HWND hwnd, WPARAM wp, LPARAM lp); diff --git a/src/SumatraStartup.cpp b/src/SumatraStartup.cpp index 8852a3159bcd..0685e2a2307a 100644 --- a/src/SumatraStartup.cpp +++ b/src/SumatraStartup.cpp @@ -219,7 +219,7 @@ static void SendMyselfDDE(const char* cmdA, HWND targetHwnd) { } // fall-through to DDEExecute if wasn't handled } - DDEExecute(PDFSYNC_DDE_SERVICE, PDFSYNC_DDE_TOPIC, cmd); + DDEExecute(kSumatraDdeServer, kSumatraDdeTopic, cmd); } // delegate file opening to a previously running instance by sending a DDE message diff --git a/src/utils/WinUtil.cpp b/src/utils/WinUtil.cpp index bc26622d3894..d6c28f38d55a 100755 --- a/src/utils/WinUtil.cpp +++ b/src/utils/WinUtil.cpp @@ -2904,3 +2904,46 @@ void AddPathToRecentDocs(const char* path) { WCHAR* pathW = ToWStrTemp(path); SHAddToRecentDocs(SHARD_PATH, pathW); } + +TempStr HGLOBALToStrTemp(HGLOBAL h, bool isUnicode) { + void* mem = GlobalLock(h); + if (!mem) { + return nullptr; + } + + TempStr res; + if (isUnicode) { + res = ToUtf8Temp((WCHAR*)mem); + } else { + res = str::DupTemp((char*)mem); + } + GlobalUnlock(h); + return res; +} + +HGLOBAL MemToHGLOBAL(void* src, int n, UINT flags) { + HGLOBAL h = GlobalAlloc(flags, n); + if (!h) { + return 0; + } + void* d = GlobalLock(h); + if (d) { + memcpy(d, src, n); + } + GlobalUnlock(h); + return h; +} + +HGLOBAL StrToHGLOBAL(const char* s, UINT flags) { + int cb = str::Len(s) + 1; + return MemToHGLOBAL((void*)s, cb, flags); +} + +TempStr AtomToStrTemp(ATOM a) { + WCHAR buf[1024]; + UINT cch = GlobalGetAtomNameW(a, buf, dimofi(buf)); + if (cch == 0) { + return nullptr; + } + return ToUtf8Temp(buf, cch); +} diff --git a/src/utils/WinUtil.h b/src/utils/WinUtil.h index 209e85c1f8ce..cd9afd1e5696 100644 --- a/src/utils/WinUtil.h +++ b/src/utils/WinUtil.h @@ -338,3 +338,8 @@ void TbGetRect(HWND hwnd, int buttonId, RECT* rc); void TreeViewExpandRecursively(HWND hTree, HTREEITEM hItem, uint flag, bool subtree); void AddPathToRecentDocs(const char*); + +TempStr HGLOBALToStrTemp(HGLOBAL h, bool isUnicode); +HGLOBAL MemToHGLOBAL(void* src, int n, UINT flags = GMEM_MOVEABLE); +HGLOBAL StrToHGLOBAL(const char* s, UINT flags = GMEM_MOVEABLE); +TempStr AtomToStrTemp(ATOM a);