diff options
Diffstat (limited to 'src/mir_app')
147 files changed, 18762 insertions, 18762 deletions
diff --git a/src/mir_app/src/CMPluginBase.cpp b/src/mir_app/src/CMPluginBase.cpp index bc768335a2..92c444e46c 100644 --- a/src/mir_app/src/CMPluginBase.cpp +++ b/src/mir_app/src/CMPluginBase.cpp @@ -1,343 +1,343 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "plugins.h" -#include "IcoLib.h" - -static int sttFakeID = -100; - -static int Compare(const CMPluginBase *p1, const CMPluginBase *p2) -{ - return INT_PTR(p1->getInst()) - INT_PTR(p2->getInst()); -} - -static LIST<CMPluginBase> g_arPlugins(10, Compare); - -void RegisterModule(CMPluginBase *pPlugin) -{ - g_arPlugins.insert(pPlugin); -} - -MIR_APP_DLL(HINSTANCE) GetInstByAddress(void *codePtr) -{ - if (g_arPlugins.getCount() == 0) - return nullptr; - - int idx; - void *boo[2] = { 0, codePtr }; - List_GetIndex((SortedList*)&g_arPlugins, (CMPluginBase*)&boo, &idx); - if (idx > 0) - idx--; - - HINSTANCE result = g_arPlugins[idx]->getInst(); - if (result < g_plugin.getInst() && codePtr > g_plugin.getInst()) - return g_plugin.getInst(); - - if (idx == 0 && codePtr < (void*)result) - return nullptr; - - return result; -} - -MIR_APP_DLL(int) GetPluginLangId(const MUUID &uuid, int _hLang) -{ - if (uuid == miid_last) - return --sttFakeID; - - for (auto &it : g_arPlugins) - if (it->getInfo().uuid == uuid) - return (_hLang) ? _hLang : --sttFakeID; - - return 0; -} - -MIR_APP_DLL(int) IsPluginLoaded(const MUUID &uuid) -{ - for (auto &it : g_arPlugins) - if (it->getInfo().uuid == uuid) - return it->getInst() != nullptr; - - return false; -} - -const char* GetPluginNameByInstance(HINSTANCE hInst) -{ - HINSTANCE boo[2] = { 0, hInst }; - CMPluginBase *pPlugin = g_arPlugins.find((CMPluginBase*)&boo); - return (pPlugin == nullptr) ? nullptr : pPlugin->getInfo().shortName; -} - -MIR_APP_DLL(CMPluginBase&) GetPluginByInstance(HINSTANCE hInst) -{ - HINSTANCE boo[2] = { 0, hInst }; - CMPluginBase *pPlugin = g_arPlugins.find((CMPluginBase*)&boo); - return (pPlugin == nullptr) ? g_plugin : *pPlugin; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// stubs for pascal plugins - -static void wipePluginData(CMPluginBase *pPlugin) -{ - if (Miranda_IsTerminated()) - return; - - KillModuleMenus(pPlugin); - KillModuleFonts(pPlugin); - KillModuleIcons(pPlugin); - KillModuleHotkeys(pPlugin); - KillModulePopups(pPlugin); - KillModuleSounds(pPlugin); - KillModuleExtraIcons(pPlugin); - KillModuleSrmmIcons(pPlugin); - KillModuleToolbarIcons(pPlugin); - KillModuleOptions(pPlugin); -} - -// emulates the call of CMPluginBase::CMPluginBase for Pascal plugins -EXTERN_C MIR_APP_DLL(void) RegisterPlugin(CMPluginBase *pPlugin) -{ - if (pPlugin->getInst() != nullptr) - g_arPlugins.insert(pPlugin); -} - -// emulates the call of CMPluginBase::~CMPluginBase for Pascal plugins -EXTERN_C MIR_APP_DLL(void) UnregisterPlugin(CMPluginBase *pPlugin) -{ - wipePluginData(pPlugin); - g_arPlugins.remove(pPlugin); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static int CompareIcons(const IcolibItem *p1, const IcolibItem *p2) -{ - return p1->default_indx - p2->default_indx; -} - -CMPluginBase::CMPluginBase(const char *moduleName, const PLUGININFOEX &pInfo) : - m_szModuleName(moduleName), - m_pInfo(pInfo), - m_arIcons(10, CompareIcons) -{ - if (m_hInst != nullptr) - g_arPlugins.insert(this); -} - -CMPluginBase::~CMPluginBase() -{ - wipePluginData(this); - - if (m_hLogger) { - mir_closeLog(m_hLogger); - m_hLogger = nullptr; - } - - g_arPlugins.remove(this); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMPluginBase::Load() -{ - return 0; -} - -int CMPluginBase::Unload() -{ - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMPluginBase::tryOpenLog() -{ - wchar_t path[MAX_PATH]; - mir_snwprintf(path, L"%s\\%S.txt", VARSW(L"%miranda_logpath%").get(), m_szModuleName); - m_hLogger = mir_createLog(m_szModuleName, nullptr, path, 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMPluginBase::addOptions(WPARAM wParam, OPTIONSDIALOGPAGE *odp) -{ - return ::Options_AddPage(wParam, odp, this); -} - -void CMPluginBase::openOptions(const wchar_t *pszGroup, const wchar_t *pszPage, const wchar_t *pszTab) -{ - ::Options_Open(pszGroup, pszPage, pszTab, this); -} - -void CMPluginBase::openOptionsPage(const wchar_t *pszGroup, const wchar_t *pszPage, const wchar_t *pszTab) -{ - ::Options_OpenPage(pszGroup, pszPage, pszTab, this); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMPluginBase::addFont(FontID *pFont) -{ - return Font_Register(pFont, this); -} - -int CMPluginBase::addFont(FontIDW *pFont) -{ - return Font_RegisterW(pFont, this); -} - -int CMPluginBase::addColor(ColourID *pColor) -{ - return Colour_Register(pColor, this); -} - -int CMPluginBase::addColor(ColourIDW *pColor) -{ - return Colour_RegisterW(pColor, this); -} - -int CMPluginBase::addEffect(EffectID *pEffect) -{ - return Effect_Register(pEffect, this); -} - -int CMPluginBase::addEffect(EffectIDW *pEffect) -{ - return Effect_RegisterW(pEffect, this); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMPluginBase::addFrame(const CLISTFrame *F) -{ - return (int)CallService(MS_CLIST_FRAMES_ADDFRAME, (WPARAM)F, (LPARAM)this); -} - -int CMPluginBase::addHotkey(const HOTKEYDESC *hk) -{ - return Hotkey_Register(hk, this); -} - -HANDLE CMPluginBase::addIcon(const SKINICONDESC *sid) -{ - return IcoLib_AddIcon(sid, this); -} - -HGENMENU CMPluginBase::addRootMenu(int hMenuObject, LPCWSTR ptszName, int position, HANDLE hIcoLib) -{ - return Menu_CreateRoot(hMenuObject, ptszName, position, hIcoLib, this); -} - -HANDLE CMPluginBase::addTTB(const struct TTBButton *pButton) -{ - return (HANDLE)CallService(MS_TTB_ADDBUTTON, (WPARAM)pButton, (LPARAM)this); -} - -int CMPluginBase::addUserInfo(WPARAM wParam, USERINFOPAGE *uip) -{ - uip->pPlugin = this; - return CallService("UserInfo/AddPage", wParam, (LPARAM)uip); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMPluginBase::debugLogA(LPCSTR szFormat, ...) -{ - if (m_hLogger == nullptr) - tryOpenLog(); - - va_list args; - va_start(args, szFormat); - mir_writeLogVA(m_hLogger, szFormat, args); - va_end(args); -} - -void CMPluginBase::debugLogW(LPCWSTR wszFormat, ...) -{ - if (m_hLogger == nullptr) - tryOpenLog(); - - va_list args; - va_start(args, wszFormat); - mir_writeLogVW(m_hLogger, wszFormat, args); - va_end(args); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -#ifdef _WINDOWS -int CMPluginBase::addImgListIcon(HIMAGELIST himl, int iconId) -{ - HICON hIcon = getIcon(iconId); - int ret = ::ImageList_AddIcon(himl, hIcon); - IcoLib_ReleaseIcon(hIcon); - return ret; -} -#endif - -HICON CMPluginBase::getIcon(int iconId, bool big) -{ - return IcoLib_GetIconByHandle(getIconHandle(iconId), big); -} - -HANDLE CMPluginBase::getIconHandle(int iconId) -{ - IcolibItem *p = (IcolibItem*)alloca(sizeof(IcolibItem)); - p->default_indx = -iconId; - return m_arIcons.find(p); -} - -void CMPluginBase::releaseIcon(int iconId, bool big) -{ - IcoLib_ReleaseIcon(getIcon(iconId), big); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMPluginBase::RegisterProtocol(int type, pfnInitProto fnInit, pfnUninitProto fnUninit) -{ - if (isPluginBanned(m_pInfo.uuid)) - return; - - if (type == PROTOTYPE_PROTOCOL && fnInit != nullptr) - type = PROTOTYPE_PROTOWITHACCS; - - MBaseProto *pd = (MBaseProto*)Proto_RegisterModule(type, m_szModuleName); - if (pd) { - pd->fnInit = fnInit; - pd->fnUninit = fnUninit; - pd->hInst = m_hInst; - } -} - -void CMPluginBase::SetUniqueId(const char *pszUniqueId) -{ - if (pszUniqueId == nullptr) - return; - - MBaseProto *pd = g_arProtos.find((MBaseProto*)&m_szModuleName); - if (pd != nullptr) - pd->szUniqueId = mir_strdup(pszUniqueId); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "plugins.h"
+#include "IcoLib.h"
+
+static int sttFakeID = -100;
+
+static int Compare(const CMPluginBase *p1, const CMPluginBase *p2)
+{
+ return INT_PTR(p1->getInst()) - INT_PTR(p2->getInst());
+}
+
+static LIST<CMPluginBase> g_arPlugins(10, Compare);
+
+void RegisterModule(CMPluginBase *pPlugin)
+{
+ g_arPlugins.insert(pPlugin);
+}
+
+MIR_APP_DLL(HINSTANCE) GetInstByAddress(void *codePtr)
+{
+ if (g_arPlugins.getCount() == 0)
+ return nullptr;
+
+ int idx;
+ void *boo[2] = { 0, codePtr };
+ List_GetIndex((SortedList*)&g_arPlugins, (CMPluginBase*)&boo, &idx);
+ if (idx > 0)
+ idx--;
+
+ HINSTANCE result = g_arPlugins[idx]->getInst();
+ if (result < g_plugin.getInst() && codePtr > g_plugin.getInst())
+ return g_plugin.getInst();
+
+ if (idx == 0 && codePtr < (void*)result)
+ return nullptr;
+
+ return result;
+}
+
+MIR_APP_DLL(int) GetPluginLangId(const MUUID &uuid, int _hLang)
+{
+ if (uuid == miid_last)
+ return --sttFakeID;
+
+ for (auto &it : g_arPlugins)
+ if (it->getInfo().uuid == uuid)
+ return (_hLang) ? _hLang : --sttFakeID;
+
+ return 0;
+}
+
+MIR_APP_DLL(int) IsPluginLoaded(const MUUID &uuid)
+{
+ for (auto &it : g_arPlugins)
+ if (it->getInfo().uuid == uuid)
+ return it->getInst() != nullptr;
+
+ return false;
+}
+
+const char* GetPluginNameByInstance(HINSTANCE hInst)
+{
+ HINSTANCE boo[2] = { 0, hInst };
+ CMPluginBase *pPlugin = g_arPlugins.find((CMPluginBase*)&boo);
+ return (pPlugin == nullptr) ? nullptr : pPlugin->getInfo().shortName;
+}
+
+MIR_APP_DLL(CMPluginBase&) GetPluginByInstance(HINSTANCE hInst)
+{
+ HINSTANCE boo[2] = { 0, hInst };
+ CMPluginBase *pPlugin = g_arPlugins.find((CMPluginBase*)&boo);
+ return (pPlugin == nullptr) ? g_plugin : *pPlugin;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// stubs for pascal plugins
+
+static void wipePluginData(CMPluginBase *pPlugin)
+{
+ if (Miranda_IsTerminated())
+ return;
+
+ KillModuleMenus(pPlugin);
+ KillModuleFonts(pPlugin);
+ KillModuleIcons(pPlugin);
+ KillModuleHotkeys(pPlugin);
+ KillModulePopups(pPlugin);
+ KillModuleSounds(pPlugin);
+ KillModuleExtraIcons(pPlugin);
+ KillModuleSrmmIcons(pPlugin);
+ KillModuleToolbarIcons(pPlugin);
+ KillModuleOptions(pPlugin);
+}
+
+// emulates the call of CMPluginBase::CMPluginBase for Pascal plugins
+EXTERN_C MIR_APP_DLL(void) RegisterPlugin(CMPluginBase *pPlugin)
+{
+ if (pPlugin->getInst() != nullptr)
+ g_arPlugins.insert(pPlugin);
+}
+
+// emulates the call of CMPluginBase::~CMPluginBase for Pascal plugins
+EXTERN_C MIR_APP_DLL(void) UnregisterPlugin(CMPluginBase *pPlugin)
+{
+ wipePluginData(pPlugin);
+ g_arPlugins.remove(pPlugin);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int CompareIcons(const IcolibItem *p1, const IcolibItem *p2)
+{
+ return p1->default_indx - p2->default_indx;
+}
+
+CMPluginBase::CMPluginBase(const char *moduleName, const PLUGININFOEX &pInfo) :
+ m_szModuleName(moduleName),
+ m_pInfo(pInfo),
+ m_arIcons(10, CompareIcons)
+{
+ if (m_hInst != nullptr)
+ g_arPlugins.insert(this);
+}
+
+CMPluginBase::~CMPluginBase()
+{
+ wipePluginData(this);
+
+ if (m_hLogger) {
+ mir_closeLog(m_hLogger);
+ m_hLogger = nullptr;
+ }
+
+ g_arPlugins.remove(this);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPluginBase::Load()
+{
+ return 0;
+}
+
+int CMPluginBase::Unload()
+{
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CMPluginBase::tryOpenLog()
+{
+ wchar_t path[MAX_PATH];
+ mir_snwprintf(path, L"%s\\%S.txt", VARSW(L"%miranda_logpath%").get(), m_szModuleName);
+ m_hLogger = mir_createLog(m_szModuleName, nullptr, path, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPluginBase::addOptions(WPARAM wParam, OPTIONSDIALOGPAGE *odp)
+{
+ return ::Options_AddPage(wParam, odp, this);
+}
+
+void CMPluginBase::openOptions(const wchar_t *pszGroup, const wchar_t *pszPage, const wchar_t *pszTab)
+{
+ ::Options_Open(pszGroup, pszPage, pszTab, this);
+}
+
+void CMPluginBase::openOptionsPage(const wchar_t *pszGroup, const wchar_t *pszPage, const wchar_t *pszTab)
+{
+ ::Options_OpenPage(pszGroup, pszPage, pszTab, this);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPluginBase::addFont(FontID *pFont)
+{
+ return Font_Register(pFont, this);
+}
+
+int CMPluginBase::addFont(FontIDW *pFont)
+{
+ return Font_RegisterW(pFont, this);
+}
+
+int CMPluginBase::addColor(ColourID *pColor)
+{
+ return Colour_Register(pColor, this);
+}
+
+int CMPluginBase::addColor(ColourIDW *pColor)
+{
+ return Colour_RegisterW(pColor, this);
+}
+
+int CMPluginBase::addEffect(EffectID *pEffect)
+{
+ return Effect_Register(pEffect, this);
+}
+
+int CMPluginBase::addEffect(EffectIDW *pEffect)
+{
+ return Effect_RegisterW(pEffect, this);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPluginBase::addFrame(const CLISTFrame *F)
+{
+ return (int)CallService(MS_CLIST_FRAMES_ADDFRAME, (WPARAM)F, (LPARAM)this);
+}
+
+int CMPluginBase::addHotkey(const HOTKEYDESC *hk)
+{
+ return Hotkey_Register(hk, this);
+}
+
+HANDLE CMPluginBase::addIcon(const SKINICONDESC *sid)
+{
+ return IcoLib_AddIcon(sid, this);
+}
+
+HGENMENU CMPluginBase::addRootMenu(int hMenuObject, LPCWSTR ptszName, int position, HANDLE hIcoLib)
+{
+ return Menu_CreateRoot(hMenuObject, ptszName, position, hIcoLib, this);
+}
+
+HANDLE CMPluginBase::addTTB(const struct TTBButton *pButton)
+{
+ return (HANDLE)CallService(MS_TTB_ADDBUTTON, (WPARAM)pButton, (LPARAM)this);
+}
+
+int CMPluginBase::addUserInfo(WPARAM wParam, USERINFOPAGE *uip)
+{
+ uip->pPlugin = this;
+ return CallService("UserInfo/AddPage", wParam, (LPARAM)uip);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CMPluginBase::debugLogA(LPCSTR szFormat, ...)
+{
+ if (m_hLogger == nullptr)
+ tryOpenLog();
+
+ va_list args;
+ va_start(args, szFormat);
+ mir_writeLogVA(m_hLogger, szFormat, args);
+ va_end(args);
+}
+
+void CMPluginBase::debugLogW(LPCWSTR wszFormat, ...)
+{
+ if (m_hLogger == nullptr)
+ tryOpenLog();
+
+ va_list args;
+ va_start(args, wszFormat);
+ mir_writeLogVW(m_hLogger, wszFormat, args);
+ va_end(args);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef _WINDOWS
+int CMPluginBase::addImgListIcon(HIMAGELIST himl, int iconId)
+{
+ HICON hIcon = getIcon(iconId);
+ int ret = ::ImageList_AddIcon(himl, hIcon);
+ IcoLib_ReleaseIcon(hIcon);
+ return ret;
+}
+#endif
+
+HICON CMPluginBase::getIcon(int iconId, bool big)
+{
+ return IcoLib_GetIconByHandle(getIconHandle(iconId), big);
+}
+
+HANDLE CMPluginBase::getIconHandle(int iconId)
+{
+ IcolibItem *p = (IcolibItem*)alloca(sizeof(IcolibItem));
+ p->default_indx = -iconId;
+ return m_arIcons.find(p);
+}
+
+void CMPluginBase::releaseIcon(int iconId, bool big)
+{
+ IcoLib_ReleaseIcon(getIcon(iconId), big);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CMPluginBase::RegisterProtocol(int type, pfnInitProto fnInit, pfnUninitProto fnUninit)
+{
+ if (isPluginBanned(m_pInfo.uuid))
+ return;
+
+ if (type == PROTOTYPE_PROTOCOL && fnInit != nullptr)
+ type = PROTOTYPE_PROTOWITHACCS;
+
+ MBaseProto *pd = (MBaseProto*)Proto_RegisterModule(type, m_szModuleName);
+ if (pd) {
+ pd->fnInit = fnInit;
+ pd->fnUninit = fnUninit;
+ pd->hInst = m_hInst;
+ }
+}
+
+void CMPluginBase::SetUniqueId(const char *pszUniqueId)
+{
+ if (pszUniqueId == nullptr)
+ return;
+
+ MBaseProto *pd = g_arProtos.find((MBaseProto*)&m_szModuleName);
+ if (pd != nullptr)
+ pd->szUniqueId = mir_strdup(pszUniqueId);
+}
diff --git a/src/mir_app/src/Docking.cpp b/src/mir_app/src/Docking.cpp index 9ea5cd9f58..c8dad68b95 100644 --- a/src/mir_app/src/Docking.cpp +++ b/src/mir_app/src/Docking.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/FontOptions.cpp b/src/mir_app/src/FontOptions.cpp index 071da22914..5a8fd4aff8 100644 --- a/src/mir_app/src/FontOptions.cpp +++ b/src/mir_app/src/FontOptions.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/FontService.cpp b/src/mir_app/src/FontService.cpp index 2f4f300ff2..27f16e2ce2 100644 --- a/src/mir_app/src/FontService.cpp +++ b/src/mir_app/src/FontService.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/FontService.h b/src/mir_app/src/FontService.h index e86799ab35..2e562bd185 100644 --- a/src/mir_app/src/FontService.h +++ b/src/mir_app/src/FontService.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/IcoLib.h b/src/mir_app/src/IcoLib.h index 385d1acfc4..78dffe4cf8 100644 --- a/src/mir_app/src/IcoLib.h +++ b/src/mir_app/src/IcoLib.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/MDatabaseCommon.cpp b/src/mir_app/src/MDatabaseCommon.cpp index 033cbea2b9..323954a516 100644 --- a/src/mir_app/src/MDatabaseCommon.cpp +++ b/src/mir_app/src/MDatabaseCommon.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows* -Copyright (C) 2012-22 Miranda NG team, +Copyright (C) 2012-23 Miranda NG team, all portions of this codebase are copyrighted to the people listed in contributors.txt. diff --git a/src/mir_app/src/MDatabaseCommonCrypt.cpp b/src/mir_app/src/MDatabaseCommonCrypt.cpp index bbfd18f399..88ec00a1cd 100644 --- a/src/mir_app/src/MDatabaseCommonCrypt.cpp +++ b/src/mir_app/src/MDatabaseCommonCrypt.cpp @@ -1,449 +1,449 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "database.h" -#include "encrypt.h" - -///////////////////////////////////////////////////////////////////////////////////////// -// Provider selection dialog - -class CSelectCryptoDialog : public CDlgBase -{ - CCtrlCombo m_combo; - CCtrlData m_descr; - CRYPTO_PROVIDER *m_selected = nullptr; - - CRYPTO_PROVIDER *getCurrent() - { - return (CRYPTO_PROVIDER*)m_combo.GetCurData(); - } - - bool OnInitDialog() override - { - for (auto &p : arCryptoProviders) - m_combo.AddStringA(p->pszName, LPARAM(p)); - - m_combo.SetCurSel(0); - m_descr.SetText(arCryptoProviders[0].szDescr.w); - return true; - } - - bool OnApply() override - { - m_selected = getCurrent(); - return true; - } - - void OnComboChanged(CCtrlCombo*) - { - m_descr.SetText(getCurrent()->szDescr.w); - } - -public: - CSelectCryptoDialog() : - CDlgBase(g_plugin, IDD_SELECT_CRYPTOPROVIDER), - m_combo(this, IDC_SELECTCRYPT_COMBO), - m_descr(this, IDC_CRYPTOPROVIDER_DESCR) - { - m_combo.OnChange = Callback(this, &CSelectCryptoDialog::OnComboChanged); - } - - inline CRYPTO_PROVIDER* GetSelected() - { return m_selected; - } -}; - -CRYPTO_PROVIDER* MDatabaseCommon::SelectProvider() -{ - if (arCryptoProviders.getCount() == 0) - return nullptr; - - CRYPTO_PROVIDER *pProv; - if (arCryptoProviders.getCount() > 1) { - CSelectCryptoDialog dlg; - dlg.DoModal(); - pProv = dlg.GetSelected(); - } - else pProv = &arCryptoProviders[0]; - - return (StoreProvider(pProv)) ? pProv : nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -BOOL MDatabaseCommon::IsSettingEncrypted(LPCSTR szModule, LPCSTR szSetting) -{ - if (!_strnicmp(szSetting, "password", 8)) return true; - if (!mir_strcmp(szSetting, "NLProxyAuthPassword")) return true; - if (!mir_strcmp(szSetting, "LNPassword")) return true; - if (!mir_strcmp(szSetting, "FileProxyPassword")) return true; - if (!mir_strcmp(szSetting, "TokenSecret")) return true; - - if (!mir_strcmp(szModule, "SecureIM")) { - if (!mir_strcmp(szSetting, "pgp")) return true; - if (!mir_strcmp(szSetting, "pgpPrivKey")) return true; - } - return false; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static HGENMENU hSetPwdMenu; - -__forceinline wchar_t *GetMenuTitle(bool bUsesPassword) -{ - return bUsesPassword ? LPGENW("Change/remove password") : LPGENW("Set password"); -} - -void MDatabaseCommon::SetPassword(const wchar_t *ptszPassword) -{ - if (mir_wstrlen(ptszPassword) == 0) { - m_bUsesPassword = false; - m_crypto->setPassword(nullptr); - } - else { - m_bUsesPassword = true; - m_crypto->setPassword(pass_ptrA(mir_utf8encodeW(ptszPassword))); - } - - Menu_ModifyItem(hSetPwdMenu, GetMenuTitle(m_bUsesPassword), Skin_GetIconHandle(SKINICON_OTHER_KEYS)); -} - -static UINT oldLangID; -void LanguageChanged(HWND hwndDlg) -{ - UINT_PTR LangID = (UINT_PTR)GetKeyboardLayout(0); - char Lang[3] = { 0 }; - if (LangID != oldLangID) { - oldLangID = LangID; - GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2); - Lang[0] = toupper(Lang[0]); - Lang[1] = tolower(Lang[1]); - SetDlgItemTextA(hwndDlg, IDC_LANG, Lang); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static bool CheckOldPassword(HWND hwndDlg, MDatabaseCommon *db) -{ - if (db->usesPassword()) { - wchar_t buf[100]; - GetDlgItemText(hwndDlg, IDC_OLDPASS, buf, _countof(buf)); - pass_ptrA oldPass(mir_utf8encodeW(buf)); - if (!db->getCrypt()->checkPassword(oldPass)) { - SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Wrong old password entered!")); - return false; - } - } - return true; -} - -static INT_PTR CALLBACK sttChangePassword(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - MDatabaseCommon *db = (MDatabaseCommon *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); - - switch (uMsg) { - case WM_INITDIALOG: - TranslateDialogDefault(hwndDlg); - SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_SETICON, ICON_SMALL, (LPARAM)g_plugin.getIcon(IDI_DATABASE, true)); - SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); - - oldLangID = 0; - SetTimer(hwndDlg, 1, 200, nullptr); - LanguageChanged(hwndDlg); - return TRUE; - - case WM_CTLCOLORSTATIC: - if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_LANG)) { - SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT)); - SetBkMode((HDC)wParam, TRANSPARENT); - return (INT_PTR)GetSysColorBrush(COLOR_HIGHLIGHT); - } - return FALSE; - - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDCANCEL: - EndDialog(hwndDlg, IDCANCEL); - break; - - case IDREMOVE: - if (!CheckOldPassword(hwndDlg, db)) { -LBL_Error: - SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_NCPAINT, 0, 0); - SetDlgItemTextA(hwndDlg, IDC_USERPASS1, ""); - SetDlgItemTextA(hwndDlg, IDC_USERPASS2, ""); - } - else { - db->SetPassword(nullptr); - db->StoreCryptoKey(); - EndDialog(hwndDlg, IDREMOVE); - } - break; - - case IDOK: - wchar_t buf2[100]; - GetDlgItemText(hwndDlg, IDC_USERPASS1, buf2, _countof(buf2)); - if (wcslen(buf2) < 3) { - SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Password is too short!")); - goto LBL_Error; - } - - wchar_t buf[100]; - GetDlgItemText(hwndDlg, IDC_USERPASS2, buf, _countof(buf)); - if (wcscmp(buf2, buf)) { - SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Passwords do not match!")); - goto LBL_Error; - } - - if (!CheckOldPassword(hwndDlg, db)) - goto LBL_Error; - - db->SetPassword(buf2); - db->StoreCryptoKey(); - SecureZeroMemory(buf, sizeof(buf)); - SecureZeroMemory(buf2, sizeof(buf2)); - EndDialog(hwndDlg, IDOK); - } - break; - - case WM_TIMER: - LanguageChanged(hwndDlg); - return FALSE; - - case WM_DESTROY: - KillTimer(hwndDlg, 1); - Window_FreeIcon_IcoLib(GetDlgItem(hwndDlg, IDC_HEADERBAR)); - } - - return FALSE; -} - -static INT_PTR ChangePassword(void* obj, WPARAM, LPARAM) -{ - MDatabaseCommon *db = (MDatabaseCommon*)obj; - DialogBoxParam(g_plugin.getInst(), MAKEINTRESOURCE(db->usesPassword() ? IDD_CHANGEPASS : IDD_NEWPASS), nullptr, sttChangePassword, (LPARAM)db); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Options - -class CDatabaseOptionsDialog : public CDlgBase -{ - CCtrlCheck m_chkStandart, m_chkTotal; - CCtrlButton m_btnChangePass; - MDatabaseCommon *m_db; - -public: - CDatabaseOptionsDialog(MDatabaseCommon *db) : - CDlgBase(g_plugin, IDD_OPT_DATABASE), - m_db(db), - m_chkTotal(this, IDC_TOTAL), - m_chkStandart(this, IDC_STANDARD), - m_btnChangePass(this, IDC_USERPASS1) - { - m_btnChangePass.OnClick = Callback(this, &CDatabaseOptionsDialog::onClick_ChangePass); - } - - bool OnInitDialog() override - { - m_chkStandart.SetState(!m_db->isEncrypted()); - m_chkTotal.SetState(m_db->isEncrypted()); - return true; - } - - bool OnApply() override - { - SetCursor(LoadCursor(nullptr, IDC_WAIT)); - m_db->EnableEncryption(m_chkTotal.GetState() != 0); - SetCursor(LoadCursor(nullptr, IDC_ARROW)); - m_chkStandart.SetState(!m_db->isEncrypted()); - m_chkTotal.SetState(m_db->isEncrypted()); - return true; - } - - void onClick_ChangePass(CCtrlButton *) - { - ChangePassword(m_db, 0, 0); - } -}; - -static int OnOptionsInit(PVOID obj, WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = { sizeof(odp) }; - odp.position = -790000000; - odp.flags = ODPF_BOLDGROUPS; - odp.szTitle.a = LPGEN("Database"); - odp.pDialog = new CDatabaseOptionsDialog((MDatabaseCommon*)obj); - g_plugin.addOptions(wParam, &odp); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void InitCryptMenuItem(CMenuItem &mi) -{ - auto *pDb = db_get_current(); - - HookEventObj(ME_OPT_INITIALISE, OnOptionsInit, pDb); - - SET_UID(mi, 0x50321866, 0xba1, 0x46dd, 0xb3, 0xa6, 0xc3, 0xcc, 0x55, 0xf2, 0x42, 0x9e); - mi.flags = CMIF_UNICODE; - mi.position = 1000000001; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_KEYS); - mi.name.w = GetMenuTitle(pDb->usesPassword()); - mi.pszService = "DB/UI/ChangePassword"; - hSetPwdMenu = Menu_AddMainMenuItem(&mi); - CreateServiceFunctionObj(mi.pszService, ChangePassword, pDb); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -class CEnterPasswordDialog : public CDlgBase -{ - friend class MDatabaseCommon; - - CTimer m_timer; - CCtrlData m_header; - CCtrlData m_language; - CCtrlEdit m_passwordEdit; - - int m_wrongPass = 0; - wchar_t m_newPass[100]; - MDatabaseCommon *m_db; - - void OnTimer(CTimer*) - { - UINT_PTR LangID = (UINT_PTR)GetKeyboardLayout(0); - char Lang[3] = { 0 }; - GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2); - Lang[0] = toupper(Lang[0]); - Lang[1] = tolower(Lang[1]); - m_language.SetTextA(Lang); - } - - INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override - { - if (msg == WM_CTLCOLORSTATIC) { - if ((HWND)lParam == m_language.GetHwnd()) { - SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT)); - SetBkMode((HDC)wParam, TRANSPARENT); - return (INT_PTR)GetSysColorBrush(COLOR_HIGHLIGHT); - } - } - return CDlgBase::DlgProc(msg, wParam, lParam); - } - -public: - CEnterPasswordDialog(MDatabaseCommon *db) : - CDlgBase(g_plugin, IDD_LOGIN), - m_timer(this, 1), - m_header(this, IDC_HEADERBAR), - m_language(this, IDC_LANG), - m_passwordEdit(this, IDC_USERPASS), - m_db(db) - { - m_newPass[0] = 0; - m_timer.OnEvent = Callback(this, &CEnterPasswordDialog::OnTimer); - } - - ~CEnterPasswordDialog() - { - SecureZeroMemory(m_newPass, sizeof(m_newPass)); - } - - bool OnInitDialog() override - { - m_header.SendMsg(WM_SETICON, ICON_SMALL, (LPARAM)g_plugin.getIcon(IDI_DATABASE, true)); - - if (m_wrongPass) { - if (m_wrongPass > 2) { - m_passwordEdit.Disable(); - EnableWindow(GetDlgItem(m_hwnd, IDOK), false); - m_header.SetText(TranslateT("Too many errors!")); - } - else m_header.SetText(TranslateT("Password is not correct!")); - } - else m_header.SetText(TranslateT("Please type in your password")); - - m_timer.Start(200); - return true; - } - - bool OnApply() override - { - m_passwordEdit.GetText(m_newPass, _countof(m_newPass)); - return true; - } - - void OnDestroy() override - { - Window_FreeIcon_IcoLib(m_header.GetHwnd()); - } -}; - -int MDatabaseCommon::InitCrypt() -{ - if (m_crypto != nullptr) - return 0; - - CRYPTO_PROVIDER *pProvider = ReadProvider(); - if (pProvider == nullptr) - pProvider = SelectProvider(); - if (pProvider == nullptr) - return 1; - - if ((m_crypto = pProvider->pFactory()) == nullptr) - return 3; - - MBinBuffer key; - BOOL bSuccess = ReadCryptoKey(key); - if (bSuccess && (key.length() == m_crypto->getKeyLength())) { - // first try to set a key without password - if (!m_crypto->setKey(nullptr, (const uint8_t*)key.data(), key.length())) { - CEnterPasswordDialog dlg(this); - while (true) { - if (!dlg.DoModal()) - return 4; - - pass_ptrA szPassword(mir_utf8encodeW(dlg.m_newPass)); - if (m_crypto->setKey(szPassword, (const uint8_t*)key.data(), key.length())) { - m_bUsesPassword = true; - break; - } - dlg.m_wrongPass++; - } - } - } - else { - if (!m_crypto->generateKey()) - return 6; - StoreCryptoKey(); - } - - m_bEncrypted = ReadEncryption(); - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "database.h"
+#include "encrypt.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Provider selection dialog
+
+class CSelectCryptoDialog : public CDlgBase
+{
+ CCtrlCombo m_combo;
+ CCtrlData m_descr;
+ CRYPTO_PROVIDER *m_selected = nullptr;
+
+ CRYPTO_PROVIDER *getCurrent()
+ {
+ return (CRYPTO_PROVIDER*)m_combo.GetCurData();
+ }
+
+ bool OnInitDialog() override
+ {
+ for (auto &p : arCryptoProviders)
+ m_combo.AddStringA(p->pszName, LPARAM(p));
+
+ m_combo.SetCurSel(0);
+ m_descr.SetText(arCryptoProviders[0].szDescr.w);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ m_selected = getCurrent();
+ return true;
+ }
+
+ void OnComboChanged(CCtrlCombo*)
+ {
+ m_descr.SetText(getCurrent()->szDescr.w);
+ }
+
+public:
+ CSelectCryptoDialog() :
+ CDlgBase(g_plugin, IDD_SELECT_CRYPTOPROVIDER),
+ m_combo(this, IDC_SELECTCRYPT_COMBO),
+ m_descr(this, IDC_CRYPTOPROVIDER_DESCR)
+ {
+ m_combo.OnChange = Callback(this, &CSelectCryptoDialog::OnComboChanged);
+ }
+
+ inline CRYPTO_PROVIDER* GetSelected()
+ { return m_selected;
+ }
+};
+
+CRYPTO_PROVIDER* MDatabaseCommon::SelectProvider()
+{
+ if (arCryptoProviders.getCount() == 0)
+ return nullptr;
+
+ CRYPTO_PROVIDER *pProv;
+ if (arCryptoProviders.getCount() > 1) {
+ CSelectCryptoDialog dlg;
+ dlg.DoModal();
+ pProv = dlg.GetSelected();
+ }
+ else pProv = &arCryptoProviders[0];
+
+ return (StoreProvider(pProv)) ? pProv : nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+BOOL MDatabaseCommon::IsSettingEncrypted(LPCSTR szModule, LPCSTR szSetting)
+{
+ if (!_strnicmp(szSetting, "password", 8)) return true;
+ if (!mir_strcmp(szSetting, "NLProxyAuthPassword")) return true;
+ if (!mir_strcmp(szSetting, "LNPassword")) return true;
+ if (!mir_strcmp(szSetting, "FileProxyPassword")) return true;
+ if (!mir_strcmp(szSetting, "TokenSecret")) return true;
+
+ if (!mir_strcmp(szModule, "SecureIM")) {
+ if (!mir_strcmp(szSetting, "pgp")) return true;
+ if (!mir_strcmp(szSetting, "pgpPrivKey")) return true;
+ }
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static HGENMENU hSetPwdMenu;
+
+__forceinline wchar_t *GetMenuTitle(bool bUsesPassword)
+{
+ return bUsesPassword ? LPGENW("Change/remove password") : LPGENW("Set password");
+}
+
+void MDatabaseCommon::SetPassword(const wchar_t *ptszPassword)
+{
+ if (mir_wstrlen(ptszPassword) == 0) {
+ m_bUsesPassword = false;
+ m_crypto->setPassword(nullptr);
+ }
+ else {
+ m_bUsesPassword = true;
+ m_crypto->setPassword(pass_ptrA(mir_utf8encodeW(ptszPassword)));
+ }
+
+ Menu_ModifyItem(hSetPwdMenu, GetMenuTitle(m_bUsesPassword), Skin_GetIconHandle(SKINICON_OTHER_KEYS));
+}
+
+static UINT oldLangID;
+void LanguageChanged(HWND hwndDlg)
+{
+ UINT_PTR LangID = (UINT_PTR)GetKeyboardLayout(0);
+ char Lang[3] = { 0 };
+ if (LangID != oldLangID) {
+ oldLangID = LangID;
+ GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2);
+ Lang[0] = toupper(Lang[0]);
+ Lang[1] = tolower(Lang[1]);
+ SetDlgItemTextA(hwndDlg, IDC_LANG, Lang);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool CheckOldPassword(HWND hwndDlg, MDatabaseCommon *db)
+{
+ if (db->usesPassword()) {
+ wchar_t buf[100];
+ GetDlgItemText(hwndDlg, IDC_OLDPASS, buf, _countof(buf));
+ pass_ptrA oldPass(mir_utf8encodeW(buf));
+ if (!db->getCrypt()->checkPassword(oldPass)) {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Wrong old password entered!"));
+ return false;
+ }
+ }
+ return true;
+}
+
+static INT_PTR CALLBACK sttChangePassword(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ MDatabaseCommon *db = (MDatabaseCommon *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_SETICON, ICON_SMALL, (LPARAM)g_plugin.getIcon(IDI_DATABASE, true));
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ oldLangID = 0;
+ SetTimer(hwndDlg, 1, 200, nullptr);
+ LanguageChanged(hwndDlg);
+ return TRUE;
+
+ case WM_CTLCOLORSTATIC:
+ if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_LANG)) {
+ SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ SetBkMode((HDC)wParam, TRANSPARENT);
+ return (INT_PTR)GetSysColorBrush(COLOR_HIGHLIGHT);
+ }
+ return FALSE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+
+ case IDREMOVE:
+ if (!CheckOldPassword(hwndDlg, db)) {
+LBL_Error:
+ SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_NCPAINT, 0, 0);
+ SetDlgItemTextA(hwndDlg, IDC_USERPASS1, "");
+ SetDlgItemTextA(hwndDlg, IDC_USERPASS2, "");
+ }
+ else {
+ db->SetPassword(nullptr);
+ db->StoreCryptoKey();
+ EndDialog(hwndDlg, IDREMOVE);
+ }
+ break;
+
+ case IDOK:
+ wchar_t buf2[100];
+ GetDlgItemText(hwndDlg, IDC_USERPASS1, buf2, _countof(buf2));
+ if (wcslen(buf2) < 3) {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Password is too short!"));
+ goto LBL_Error;
+ }
+
+ wchar_t buf[100];
+ GetDlgItemText(hwndDlg, IDC_USERPASS2, buf, _countof(buf));
+ if (wcscmp(buf2, buf)) {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Passwords do not match!"));
+ goto LBL_Error;
+ }
+
+ if (!CheckOldPassword(hwndDlg, db))
+ goto LBL_Error;
+
+ db->SetPassword(buf2);
+ db->StoreCryptoKey();
+ SecureZeroMemory(buf, sizeof(buf));
+ SecureZeroMemory(buf2, sizeof(buf2));
+ EndDialog(hwndDlg, IDOK);
+ }
+ break;
+
+ case WM_TIMER:
+ LanguageChanged(hwndDlg);
+ return FALSE;
+
+ case WM_DESTROY:
+ KillTimer(hwndDlg, 1);
+ Window_FreeIcon_IcoLib(GetDlgItem(hwndDlg, IDC_HEADERBAR));
+ }
+
+ return FALSE;
+}
+
+static INT_PTR ChangePassword(void* obj, WPARAM, LPARAM)
+{
+ MDatabaseCommon *db = (MDatabaseCommon*)obj;
+ DialogBoxParam(g_plugin.getInst(), MAKEINTRESOURCE(db->usesPassword() ? IDD_CHANGEPASS : IDD_NEWPASS), nullptr, sttChangePassword, (LPARAM)db);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Options
+
+class CDatabaseOptionsDialog : public CDlgBase
+{
+ CCtrlCheck m_chkStandart, m_chkTotal;
+ CCtrlButton m_btnChangePass;
+ MDatabaseCommon *m_db;
+
+public:
+ CDatabaseOptionsDialog(MDatabaseCommon *db) :
+ CDlgBase(g_plugin, IDD_OPT_DATABASE),
+ m_db(db),
+ m_chkTotal(this, IDC_TOTAL),
+ m_chkStandart(this, IDC_STANDARD),
+ m_btnChangePass(this, IDC_USERPASS1)
+ {
+ m_btnChangePass.OnClick = Callback(this, &CDatabaseOptionsDialog::onClick_ChangePass);
+ }
+
+ bool OnInitDialog() override
+ {
+ m_chkStandart.SetState(!m_db->isEncrypted());
+ m_chkTotal.SetState(m_db->isEncrypted());
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ SetCursor(LoadCursor(nullptr, IDC_WAIT));
+ m_db->EnableEncryption(m_chkTotal.GetState() != 0);
+ SetCursor(LoadCursor(nullptr, IDC_ARROW));
+ m_chkStandart.SetState(!m_db->isEncrypted());
+ m_chkTotal.SetState(m_db->isEncrypted());
+ return true;
+ }
+
+ void onClick_ChangePass(CCtrlButton *)
+ {
+ ChangePassword(m_db, 0, 0);
+ }
+};
+
+static int OnOptionsInit(PVOID obj, WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { sizeof(odp) };
+ odp.position = -790000000;
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.szTitle.a = LPGEN("Database");
+ odp.pDialog = new CDatabaseOptionsDialog((MDatabaseCommon*)obj);
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void InitCryptMenuItem(CMenuItem &mi)
+{
+ auto *pDb = db_get_current();
+
+ HookEventObj(ME_OPT_INITIALISE, OnOptionsInit, pDb);
+
+ SET_UID(mi, 0x50321866, 0xba1, 0x46dd, 0xb3, 0xa6, 0xc3, 0xcc, 0x55, 0xf2, 0x42, 0x9e);
+ mi.flags = CMIF_UNICODE;
+ mi.position = 1000000001;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_KEYS);
+ mi.name.w = GetMenuTitle(pDb->usesPassword());
+ mi.pszService = "DB/UI/ChangePassword";
+ hSetPwdMenu = Menu_AddMainMenuItem(&mi);
+ CreateServiceFunctionObj(mi.pszService, ChangePassword, pDb);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class CEnterPasswordDialog : public CDlgBase
+{
+ friend class MDatabaseCommon;
+
+ CTimer m_timer;
+ CCtrlData m_header;
+ CCtrlData m_language;
+ CCtrlEdit m_passwordEdit;
+
+ int m_wrongPass = 0;
+ wchar_t m_newPass[100];
+ MDatabaseCommon *m_db;
+
+ void OnTimer(CTimer*)
+ {
+ UINT_PTR LangID = (UINT_PTR)GetKeyboardLayout(0);
+ char Lang[3] = { 0 };
+ GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2);
+ Lang[0] = toupper(Lang[0]);
+ Lang[1] = tolower(Lang[1]);
+ m_language.SetTextA(Lang);
+ }
+
+ INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override
+ {
+ if (msg == WM_CTLCOLORSTATIC) {
+ if ((HWND)lParam == m_language.GetHwnd()) {
+ SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ SetBkMode((HDC)wParam, TRANSPARENT);
+ return (INT_PTR)GetSysColorBrush(COLOR_HIGHLIGHT);
+ }
+ }
+ return CDlgBase::DlgProc(msg, wParam, lParam);
+ }
+
+public:
+ CEnterPasswordDialog(MDatabaseCommon *db) :
+ CDlgBase(g_plugin, IDD_LOGIN),
+ m_timer(this, 1),
+ m_header(this, IDC_HEADERBAR),
+ m_language(this, IDC_LANG),
+ m_passwordEdit(this, IDC_USERPASS),
+ m_db(db)
+ {
+ m_newPass[0] = 0;
+ m_timer.OnEvent = Callback(this, &CEnterPasswordDialog::OnTimer);
+ }
+
+ ~CEnterPasswordDialog()
+ {
+ SecureZeroMemory(m_newPass, sizeof(m_newPass));
+ }
+
+ bool OnInitDialog() override
+ {
+ m_header.SendMsg(WM_SETICON, ICON_SMALL, (LPARAM)g_plugin.getIcon(IDI_DATABASE, true));
+
+ if (m_wrongPass) {
+ if (m_wrongPass > 2) {
+ m_passwordEdit.Disable();
+ EnableWindow(GetDlgItem(m_hwnd, IDOK), false);
+ m_header.SetText(TranslateT("Too many errors!"));
+ }
+ else m_header.SetText(TranslateT("Password is not correct!"));
+ }
+ else m_header.SetText(TranslateT("Please type in your password"));
+
+ m_timer.Start(200);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ m_passwordEdit.GetText(m_newPass, _countof(m_newPass));
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ Window_FreeIcon_IcoLib(m_header.GetHwnd());
+ }
+};
+
+int MDatabaseCommon::InitCrypt()
+{
+ if (m_crypto != nullptr)
+ return 0;
+
+ CRYPTO_PROVIDER *pProvider = ReadProvider();
+ if (pProvider == nullptr)
+ pProvider = SelectProvider();
+ if (pProvider == nullptr)
+ return 1;
+
+ if ((m_crypto = pProvider->pFactory()) == nullptr)
+ return 3;
+
+ MBinBuffer key;
+ BOOL bSuccess = ReadCryptoKey(key);
+ if (bSuccess && (key.length() == m_crypto->getKeyLength())) {
+ // first try to set a key without password
+ if (!m_crypto->setKey(nullptr, (const uint8_t*)key.data(), key.length())) {
+ CEnterPasswordDialog dlg(this);
+ while (true) {
+ if (!dlg.DoModal())
+ return 4;
+
+ pass_ptrA szPassword(mir_utf8encodeW(dlg.m_newPass));
+ if (m_crypto->setKey(szPassword, (const uint8_t*)key.data(), key.length())) {
+ m_bUsesPassword = true;
+ break;
+ }
+ dlg.m_wrongPass++;
+ }
+ }
+ }
+ else {
+ if (!m_crypto->generateKey())
+ return 6;
+ StoreCryptoKey();
+ }
+
+ m_bEncrypted = ReadEncryption();
+ return 0;
+}
diff --git a/src/mir_app/src/MDatabaseReadonly.cpp b/src/mir_app/src/MDatabaseReadonly.cpp index f3ed3da7ef..f022d5e5fb 100644 --- a/src/mir_app/src/MDatabaseReadonly.cpp +++ b/src/mir_app/src/MDatabaseReadonly.cpp @@ -1,180 +1,180 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "database.h" - -MDatabaseReadonly::MDatabaseReadonly() -{ -} - -BOOL MDatabaseReadonly::IsRelational(void) -{ - return FALSE; -} - -void MDatabaseReadonly::SetCacheSafetyMode(BOOL) -{ -} - -BOOL MDatabaseReadonly::EnumModuleNames(DBMODULEENUMPROC, void*) -{ - return FALSE; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -STDMETHODIMP_(BOOL) MDatabaseReadonly::ReadCryptoKey(MBinBuffer&) -{ - return FALSE; -} - -STDMETHODIMP_(BOOL) MDatabaseReadonly::StoreCryptoKey() -{ - return FALSE; -} - -STDMETHODIMP_(CRYPTO_PROVIDER*) MDatabaseReadonly::ReadProvider() -{ - return nullptr; -} - -STDMETHODIMP_(BOOL) MDatabaseReadonly::StoreProvider(CRYPTO_PROVIDER *) -{ - return FALSE; -} - -STDMETHODIMP_(BOOL) MDatabaseReadonly::EnableEncryption(BOOL) -{ - return FALSE; -} - -STDMETHODIMP_(BOOL) MDatabaseReadonly::ReadEncryption() -{ - return FALSE; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MCONTACT MDatabaseReadonly::AddContact(void) -{ - return 0; -} - -int MDatabaseReadonly::DeleteContact(MCONTACT) -{ - return 1; -} - -BOOL MDatabaseReadonly::IsDbContact(MCONTACT contactID) -{ - return contactID == 1; -} - -int MDatabaseReadonly::GetContactSize(void) -{ - return sizeof(DBCachedContact); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MEVENT MDatabaseReadonly::AddEvent(MCONTACT, const DBEVENTINFO*) -{ - return 0; -} - -BOOL MDatabaseReadonly::DeleteEvent(MEVENT) -{ - return 1; -} - -BOOL MDatabaseReadonly::EditEvent(MCONTACT, MEVENT, const DBEVENTINFO*) -{ - return 1; -} - -int MDatabaseReadonly::GetBlobSize(MEVENT) -{ - return 0; -} - -MEVENT MDatabaseReadonly::FindFirstUnreadEvent(MCONTACT) -{ - return 0; -} - -BOOL MDatabaseReadonly::MarkEventRead(MCONTACT, MEVENT) -{ - return 1; -} - -MCONTACT MDatabaseReadonly::GetEventContact(MEVENT) -{ - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -BOOL MDatabaseReadonly::GetContactSettingWorker(MCONTACT, LPCSTR, LPCSTR, DBVARIANT*, int) -{ - return 1; -} - -BOOL MDatabaseReadonly::WriteContactSettingWorker(MCONTACT, DBCONTACTWRITESETTING&) -{ - return 1; -} - -BOOL MDatabaseReadonly::DeleteContactSetting(MCONTACT, LPCSTR, LPCSTR) -{ - return 1; -} - -BOOL MDatabaseReadonly::EnumContactSettings(MCONTACT, DBSETTINGENUMPROC, const char*, void*) -{ - return 1; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -BOOL MDatabaseReadonly::MetaMergeHistory(DBCachedContact*, DBCachedContact*) -{ - return 1; -} - -BOOL MDatabaseReadonly::MetaSplitHistory(DBCachedContact*, DBCachedContact*) -{ - return 1; -} - -BOOL MDatabaseReadonly::MetaRemoveSubHistory(DBCachedContact*) -{ - return 1; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MEVENT MDatabaseReadonly::GetEventById(LPCSTR, LPCSTR) -{ - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "database.h"
+
+MDatabaseReadonly::MDatabaseReadonly()
+{
+}
+
+BOOL MDatabaseReadonly::IsRelational(void)
+{
+ return FALSE;
+}
+
+void MDatabaseReadonly::SetCacheSafetyMode(BOOL)
+{
+}
+
+BOOL MDatabaseReadonly::EnumModuleNames(DBMODULEENUMPROC, void*)
+{
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+STDMETHODIMP_(BOOL) MDatabaseReadonly::ReadCryptoKey(MBinBuffer&)
+{
+ return FALSE;
+}
+
+STDMETHODIMP_(BOOL) MDatabaseReadonly::StoreCryptoKey()
+{
+ return FALSE;
+}
+
+STDMETHODIMP_(CRYPTO_PROVIDER*) MDatabaseReadonly::ReadProvider()
+{
+ return nullptr;
+}
+
+STDMETHODIMP_(BOOL) MDatabaseReadonly::StoreProvider(CRYPTO_PROVIDER *)
+{
+ return FALSE;
+}
+
+STDMETHODIMP_(BOOL) MDatabaseReadonly::EnableEncryption(BOOL)
+{
+ return FALSE;
+}
+
+STDMETHODIMP_(BOOL) MDatabaseReadonly::ReadEncryption()
+{
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MCONTACT MDatabaseReadonly::AddContact(void)
+{
+ return 0;
+}
+
+int MDatabaseReadonly::DeleteContact(MCONTACT)
+{
+ return 1;
+}
+
+BOOL MDatabaseReadonly::IsDbContact(MCONTACT contactID)
+{
+ return contactID == 1;
+}
+
+int MDatabaseReadonly::GetContactSize(void)
+{
+ return sizeof(DBCachedContact);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MEVENT MDatabaseReadonly::AddEvent(MCONTACT, const DBEVENTINFO*)
+{
+ return 0;
+}
+
+BOOL MDatabaseReadonly::DeleteEvent(MEVENT)
+{
+ return 1;
+}
+
+BOOL MDatabaseReadonly::EditEvent(MCONTACT, MEVENT, const DBEVENTINFO*)
+{
+ return 1;
+}
+
+int MDatabaseReadonly::GetBlobSize(MEVENT)
+{
+ return 0;
+}
+
+MEVENT MDatabaseReadonly::FindFirstUnreadEvent(MCONTACT)
+{
+ return 0;
+}
+
+BOOL MDatabaseReadonly::MarkEventRead(MCONTACT, MEVENT)
+{
+ return 1;
+}
+
+MCONTACT MDatabaseReadonly::GetEventContact(MEVENT)
+{
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+BOOL MDatabaseReadonly::GetContactSettingWorker(MCONTACT, LPCSTR, LPCSTR, DBVARIANT*, int)
+{
+ return 1;
+}
+
+BOOL MDatabaseReadonly::WriteContactSettingWorker(MCONTACT, DBCONTACTWRITESETTING&)
+{
+ return 1;
+}
+
+BOOL MDatabaseReadonly::DeleteContactSetting(MCONTACT, LPCSTR, LPCSTR)
+{
+ return 1;
+}
+
+BOOL MDatabaseReadonly::EnumContactSettings(MCONTACT, DBSETTINGENUMPROC, const char*, void*)
+{
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+BOOL MDatabaseReadonly::MetaMergeHistory(DBCachedContact*, DBCachedContact*)
+{
+ return 1;
+}
+
+BOOL MDatabaseReadonly::MetaSplitHistory(DBCachedContact*, DBCachedContact*)
+{
+ return 1;
+}
+
+BOOL MDatabaseReadonly::MetaRemoveSubHistory(DBCachedContact*)
+{
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MEVENT MDatabaseReadonly::GetEventById(LPCSTR, LPCSTR)
+{
+ return 0;
+}
diff --git a/src/mir_app/src/MHttpRequest.cpp b/src/mir_app/src/MHttpRequest.cpp index cefe4d551c..7935974463 100644 --- a/src/mir_app/src/MHttpRequest.cpp +++ b/src/mir_app/src/MHttpRequest.cpp @@ -1,93 +1,93 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -MHttpRequest::MHttpRequest() -{ - cbSize = sizeof(NETLIBHTTPREQUEST); - requestType = REQUEST_GET; -} - -MHttpRequest::~MHttpRequest() -{ - for (int i = 0; i < headersCount; i++) { - mir_free(headers[i].szName); - mir_free(headers[i].szValue); - } - mir_free(headers); - mir_free(pData); -} - -void MHttpRequest::AddHeader(LPCSTR szName, LPCSTR szValue) -{ - for (int i = 0; i < headersCount; i++) - if (!mir_strcmp(headers[i].szName, szName)) { - replaceStr(headers[i].szValue, szValue); - return; - } - - headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1)); - headers[headersCount].szName = mir_strdup(szName); - headers[headersCount].szValue = mir_strdup(szValue); - headersCount++; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(MHttpRequest*) operator<<(MHttpRequest *pReq, const INT_PARAM ¶m) -{ - CMStringA &s = pReq->m_szParam; - if (!s.IsEmpty()) - s.AppendChar('&'); - s.AppendFormat("%s=%ld", param.szName, param.iValue); - return pReq; -} - -MIR_APP_DLL(MHttpRequest*) operator<<(MHttpRequest *pReq, const INT64_PARAM ¶m) -{ - CMStringA &s = pReq->m_szParam; - if (!s.IsEmpty()) - s.AppendChar('&'); - s.AppendFormat("%s=%lld", param.szName, param.iValue); - return pReq; -} - -MIR_APP_DLL(MHttpRequest*) operator<<(MHttpRequest *pReq, const CHAR_PARAM ¶m) -{ - CMStringA &s = pReq->m_szParam; - if (!s.IsEmpty()) - s.AppendChar('&'); - s.AppendFormat("%s=%s", param.szName, mir_urlEncode(param.szValue).c_str()); - return pReq; -} - -MIR_APP_DLL(MHttpRequest*) operator<<(MHttpRequest *pReq, const WCHAR_PARAM ¶m) -{ - T2Utf szValue(param.wszValue); - CMStringA &s = pReq->m_szParam; - if (!s.IsEmpty()) - s.AppendChar('&'); - s.AppendFormat("%s=%s", param.szName, mir_urlEncode(szValue).c_str()); - return pReq; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+MHttpRequest::MHttpRequest()
+{
+ cbSize = sizeof(NETLIBHTTPREQUEST);
+ requestType = REQUEST_GET;
+}
+
+MHttpRequest::~MHttpRequest()
+{
+ for (int i = 0; i < headersCount; i++) {
+ mir_free(headers[i].szName);
+ mir_free(headers[i].szValue);
+ }
+ mir_free(headers);
+ mir_free(pData);
+}
+
+void MHttpRequest::AddHeader(LPCSTR szName, LPCSTR szValue)
+{
+ for (int i = 0; i < headersCount; i++)
+ if (!mir_strcmp(headers[i].szName, szName)) {
+ replaceStr(headers[i].szValue, szValue);
+ return;
+ }
+
+ headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
+ headers[headersCount].szName = mir_strdup(szName);
+ headers[headersCount].szValue = mir_strdup(szValue);
+ headersCount++;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(MHttpRequest*) operator<<(MHttpRequest *pReq, const INT_PARAM ¶m)
+{
+ CMStringA &s = pReq->m_szParam;
+ if (!s.IsEmpty())
+ s.AppendChar('&');
+ s.AppendFormat("%s=%ld", param.szName, param.iValue);
+ return pReq;
+}
+
+MIR_APP_DLL(MHttpRequest*) operator<<(MHttpRequest *pReq, const INT64_PARAM ¶m)
+{
+ CMStringA &s = pReq->m_szParam;
+ if (!s.IsEmpty())
+ s.AppendChar('&');
+ s.AppendFormat("%s=%lld", param.szName, param.iValue);
+ return pReq;
+}
+
+MIR_APP_DLL(MHttpRequest*) operator<<(MHttpRequest *pReq, const CHAR_PARAM ¶m)
+{
+ CMStringA &s = pReq->m_szParam;
+ if (!s.IsEmpty())
+ s.AppendChar('&');
+ s.AppendFormat("%s=%s", param.szName, mir_urlEncode(param.szValue).c_str());
+ return pReq;
+}
+
+MIR_APP_DLL(MHttpRequest*) operator<<(MHttpRequest *pReq, const WCHAR_PARAM ¶m)
+{
+ T2Utf szValue(param.wszValue);
+ CMStringA &s = pReq->m_szParam;
+ if (!s.IsEmpty())
+ s.AppendChar('&');
+ s.AppendFormat("%s=%s", param.szName, mir_urlEncode(szValue).c_str());
+ return pReq;
+}
diff --git a/src/mir_app/src/addcontact.cpp b/src/mir_app/src/addcontact.cpp index e065bf94df..2eda1400ca 100644 --- a/src/mir_app/src/addcontact.cpp +++ b/src/mir_app/src/addcontact.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/auth.cpp b/src/mir_app/src/auth.cpp index c40053819f..0520c20a11 100644 --- a/src/mir_app/src/auth.cpp +++ b/src/mir_app/src/auth.cpp @@ -1,367 +1,367 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// -// Auth Request dialog - -class CAuthReqDlg : public CDlgBase -{ - MEVENT m_hDbEvent; - MCONTACT m_hContact; - const char *m_szProto; - - CCtrlBase fldHeader, fldReason; - CCtrlEdit edtReason; - CCtrlCheck chkAdd; - CCtrlButton btnAdd, btnDetails, btnLater; - -public: - CAuthReqDlg(MEVENT hEvent) : - CDlgBase(g_plugin, IDD_AUTHREQ), - m_hDbEvent(hEvent), - fldHeader(this, IDC_HEADERBAR), - fldReason(this, IDC_REASON), - edtReason(this, IDC_DENYREASON), - btnAdd(this, IDC_ADD), - btnLater(this, IDC_DECIDELATER), - btnDetails(this, IDC_DETAILS), - chkAdd(this, IDC_ADDCHECK) - { - btnLater.OnClick = Callback(this, &CAuthReqDlg::onClick_Later); - btnDetails.OnClick = Callback(this, &CAuthReqDlg::onClick_Details); - } - - bool OnInitDialog() override - { - Button_SetSkin_IcoLib(m_hwnd, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View user's details")); - Button_SetSkin_IcoLib(m_hwnd, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add contact permanently to list")); - - // blob is: uin(uint32_t), hcontact(uint32_t), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ) - DBEVENTINFO dbei = {}; - dbei.cbBlob = -1; - if (db_event_get(m_hDbEvent, &dbei)) - return false; - - m_szProto = dbei.szModule; - PROTOACCOUNT *acc = Proto_GetAccount(dbei.szModule); - - uint32_t uin = *(uint32_t*)dbei.pBlob; - m_hContact = DbGetAuthEventContact(&dbei); - char *nick = (char*)dbei.pBlob + sizeof(uint32_t) * 2; - char *first = nick + mir_strlen(nick) + 1; - char *last = first + mir_strlen(first) + 1; - char *email = last + mir_strlen(last) + 1; - char *reason = email + mir_strlen(email) + 1; - - #ifdef _WINDOWS - SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0)); - SendMessage(m_hwnd, WM_SETICON, ICON_BIG, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_LARGE, 0)); - #endif - - ptrW lastT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(last) : mir_a2u(last)); - ptrW firstT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(first) : mir_a2u(first)); - ptrW nickT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(nick) : mir_a2u(nick)); - ptrW emailT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(email) : mir_a2u(email)); - ptrW reasonT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(reason) : mir_a2u(reason)); - - CMStringW wszHeader; - if (firstT[0] && lastT[0]) - wszHeader.Format(L"%s %s", (wchar_t*)firstT, (wchar_t*)lastT); - else if (firstT[0]) - wszHeader = firstT.get(); - else if (lastT[0]) - wszHeader = lastT.get(); - - if (mir_wstrlen(nickT)) { - if (wszHeader.IsEmpty()) - wszHeader = nickT.get(); - else - wszHeader.AppendFormat(L" %s", nickT.get()); - } - if (wszHeader.IsEmpty()) - wszHeader = TranslateT("<Unknown>"); - - if (uin && emailT[0]) - wszHeader.AppendFormat(TranslateT(" requested authorization\n%u (%s) on %s"), uin, emailT.get(), acc->tszAccountName); - else if (uin) - wszHeader.AppendFormat(TranslateT(" requested authorization\n%u on %s"), uin, acc->tszAccountName); - else - wszHeader.AppendFormat(TranslateT(" requested authorization\n%s on %s"), emailT[0] ? emailT.get() : TranslateT("(Unknown)"), acc->tszAccountName); - fldHeader.SetText(wszHeader); - - fldReason.SetText(reasonT); - - if (m_hContact == INVALID_CONTACT_ID || Contact::OnList(m_hContact)) - btnAdd.Hide(); - - edtReason.SetMaxLength(255); - if (CallProtoService(dbei.szModule, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_NOAUTHDENYREASON) { - edtReason.Disable(); - edtReason.SetText(TranslateT("Feature is not supported by protocol")); - } - - if (Contact::OnList(m_hContact)) { - chkAdd.Disable(); - chkAdd.SetState(false); - } - else chkAdd.SetState(true); - return true; - } - - bool OnApply() override - { - CallProtoService(m_szProto, PS_AUTHALLOW, m_hDbEvent, 0); - - if (chkAdd.GetState()) - Contact::AddByEvent(m_hDbEvent, m_hwnd); - return true; - } - - void OnDestroy() override - { - if (!m_bSucceeded) { - if (edtReason.Enabled()) - CallProtoService(m_szProto, PS_AUTHDENY, m_hDbEvent, (LPARAM)ptrW(edtReason.GetText())); - else - CallProtoService(m_szProto, PS_AUTHDENY, m_hDbEvent, 0); - } - - Button_FreeIcon_IcoLib(m_hwnd, IDC_ADD); - Button_FreeIcon_IcoLib(m_hwnd, IDC_DETAILS); - - #ifdef _WINDOWS - DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_BIG, 0)); - DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, 0)); - #endif - } - - void onClick_Later(CCtrlButton*) - { - m_bSucceeded = true; - Close(); - } - - void onClick_Details(CCtrlButton*) - { - CallService(MS_USERINFO_SHOWDIALOG, m_hContact, 0); - } -}; - -static INT_PTR ShowReqWindow(WPARAM, LPARAM lParam) -{ - (new CAuthReqDlg(((CLISTEVENT *)lParam)->hDbEvent))->Show(); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -class CAddedDlg : public CDlgBase -{ - MEVENT m_hDbEvent; - MCONTACT m_hContact; - - CCtrlBase fldHeader; - CCtrlButton btnDetails, btnAdd; - -public: - CAddedDlg(MEVENT hEvent) : - CDlgBase(g_plugin, IDD_ADDED), - m_hDbEvent(hEvent), - btnAdd(this, IDC_ADD), - btnDetails(this, IDC_DETAILS), - fldHeader(this, IDC_HEADERBAR) - { - btnAdd.OnClick = Callback(this, &CAddedDlg::onClick_Add); - btnDetails.OnClick = Callback(this, &CAddedDlg::onClick_Details); - } - - bool OnInitDialog() override - { - Button_SetSkin_IcoLib(m_hwnd, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View user's details")); - Button_SetSkin_IcoLib(m_hwnd, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add contact permanently to list")); - - // blob is: uin(uint32_t), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ) - DB::EventInfo dbei; - dbei.cbBlob = -1; - db_event_get(m_hDbEvent, &dbei); - - m_hContact = DbGetAuthEventContact(&dbei); - - uint32_t uin = *(uint32_t*)dbei.pBlob; - char* nick = (char*)dbei.pBlob + sizeof(uint32_t) * 2; - char* first = nick + mir_strlen(nick) + 1; - char* last = first + mir_strlen(first) + 1; - char* email = last + mir_strlen(last) + 1; - - #ifdef _WINDOWS - SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0)); - SendMessage(m_hwnd, WM_SETICON, ICON_BIG, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_LARGE, 0)); - #endif - - PROTOACCOUNT* acc = Proto_GetAccount(dbei.szModule); - - ptrW lastT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(last) : mir_a2u(last)); - ptrW firstT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(first) : mir_a2u(first)); - ptrW nickT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(nick) : mir_a2u(nick)); - ptrW emailT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(email) : mir_a2u(email)); - - wchar_t name[128] = L""; - int off = 0; - if (firstT[0] && lastT[0]) - off = mir_snwprintf(name, L"%s %s", firstT.get(), lastT.get()); - else if (firstT[0]) - off = mir_snwprintf(name, L"%s", firstT.get()); - else if (lastT[0]) - off = mir_snwprintf(name, L"%s", lastT.get()); - if (nickT[0]) { - if (off) - mir_snwprintf(name + off, _countof(name) - off, L" (%s)", nickT.get()); - else - wcsncpy_s(name, nickT, _TRUNCATE); - } - if (!name[0]) - wcsncpy_s(name, TranslateT("<Unknown>"), _TRUNCATE); - - wchar_t hdr[256]; - if (uin && emailT[0]) - mir_snwprintf(hdr, TranslateT("%s added you to the contact list\n%u (%s) on %s"), name, uin, emailT.get(), acc->tszAccountName); - else if (uin) - mir_snwprintf(hdr, TranslateT("%s added you to the contact list\n%u on %s"), name, uin, acc->tszAccountName); - else - mir_snwprintf(hdr, TranslateT("%s added you to the contact list\n%s on %s"), name, emailT[0] ? emailT.get() : TranslateT("(Unknown)"), acc->tszAccountName); - fldHeader.SetText(hdr); - - if (m_hContact == INVALID_CONTACT_ID || Contact::OnList(m_hContact)) - btnAdd.Hide(); - return true; - } - - bool OnApply() - { - Contact::AddByEvent(m_hDbEvent, m_hwnd); - return true; - } - - void OnDestroy() override - { - Button_FreeIcon_IcoLib(m_hwnd, IDC_ADD); - Button_FreeIcon_IcoLib(m_hwnd, IDC_DETAILS); - - #ifdef _WINDOWS - DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_BIG, 0)); - DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, 0)); - #endif - } - - void onClick_Add(CCtrlButton*) - { - Contact::AddByEvent(m_hDbEvent, m_hwnd); - - if (m_hContact == INVALID_CONTACT_ID || Contact::OnList(m_hContact)) - btnAdd.Hide(); - } - - void onClick_Details(CCtrlButton*) - { - CallService(MS_USERINFO_SHOWDIALOG, m_hContact, 0); - } -}; - -static INT_PTR ShowAddedWindow(WPARAM, LPARAM lParam) -{ - (new CAddedDlg(((CLISTEVENT *)lParam)->hDbEvent))->Show(); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -#define MS_AUTH_SHOWADDED "Auth/ShowAdded" -#define MS_AUTH_SHOWREQUEST "Auth/ShowRequest" - -static int AuthEventAdded(WPARAM, LPARAM lParam) -{ - wchar_t szTooltip[256]; - MEVENT hDbEvent = (MEVENT)lParam; - - DB::EventInfo dbei; - db_event_get(lParam, &dbei); - if (dbei.flags & (DBEF_SENT | DBEF_READ) || (dbei.eventType != EVENTTYPE_AUTHREQUEST && dbei.eventType != EVENTTYPE_ADDED)) - return 0; - - dbei.cbBlob = -1; - db_event_get(hDbEvent, &dbei); - - MCONTACT hContact = DbGetAuthEventContact(&dbei); - - CLISTEVENT cle = {}; - cle.hContact = hContact; - cle.szTooltip.w = szTooltip; - cle.flags = CLEF_UNICODE; - cle.lParam = lParam; - cle.hDbEvent = hDbEvent; - - ptrW szUid(Contact::GetInfo(CNF_UNIQUEID, hContact)); - - if (dbei.eventType == EVENTTYPE_AUTHREQUEST) { - Skin_PlaySound("AuthRequest"); - if (szUid) - mir_snwprintf(szTooltip, TranslateT("%s requests authorization"), szUid.get()); - else - mir_snwprintf(szTooltip, TranslateT("%u requests authorization"), *(uint32_t*)dbei.pBlob); - - cle.hIcon = Skin_LoadIcon(SKINICON_AUTH_REQUEST); - cle.pszService = MS_AUTH_SHOWREQUEST; - g_clistApi.pfnAddEvent(&cle); - } - else if (dbei.eventType == EVENTTYPE_ADDED) { - Skin_PlaySound("AddedEvent"); - if (szUid) - mir_snwprintf(szTooltip, TranslateT("%s added you to their contact list"), szUid.get()); - else - mir_snwprintf(szTooltip, TranslateT("%u added you to their contact list"), *(uint32_t*)dbei.pBlob); - - cle.hIcon = Skin_LoadIcon(SKINICON_AUTH_ADD); - cle.pszService = MS_AUTH_SHOWADDED; - g_clistApi.pfnAddEvent(&cle); - } - return 0; -} - -static void CALLBACK LaunchAuth() -{ - HookEvent(ME_DB_EVENT_ADDED, AuthEventAdded); -} - -int LoadSendRecvAuthModule(void) -{ - CreateServiceFunction(MS_AUTH_SHOWREQUEST, ShowReqWindow); - CreateServiceFunction(MS_AUTH_SHOWADDED, ShowAddedWindow); - Miranda_WaitOnHandle(LaunchAuth); - - g_plugin.addSound("AuthRequest", LPGENW("Alerts"), LPGENW("Authorization request")); - g_plugin.addSound("AddedEvent", LPGENW("Alerts"), LPGENW("Added event")); - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Auth Request dialog
+
+class CAuthReqDlg : public CDlgBase
+{
+ MEVENT m_hDbEvent;
+ MCONTACT m_hContact;
+ const char *m_szProto;
+
+ CCtrlBase fldHeader, fldReason;
+ CCtrlEdit edtReason;
+ CCtrlCheck chkAdd;
+ CCtrlButton btnAdd, btnDetails, btnLater;
+
+public:
+ CAuthReqDlg(MEVENT hEvent) :
+ CDlgBase(g_plugin, IDD_AUTHREQ),
+ m_hDbEvent(hEvent),
+ fldHeader(this, IDC_HEADERBAR),
+ fldReason(this, IDC_REASON),
+ edtReason(this, IDC_DENYREASON),
+ btnAdd(this, IDC_ADD),
+ btnLater(this, IDC_DECIDELATER),
+ btnDetails(this, IDC_DETAILS),
+ chkAdd(this, IDC_ADDCHECK)
+ {
+ btnLater.OnClick = Callback(this, &CAuthReqDlg::onClick_Later);
+ btnDetails.OnClick = Callback(this, &CAuthReqDlg::onClick_Details);
+ }
+
+ bool OnInitDialog() override
+ {
+ Button_SetSkin_IcoLib(m_hwnd, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View user's details"));
+ Button_SetSkin_IcoLib(m_hwnd, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add contact permanently to list"));
+
+ // blob is: uin(uint32_t), hcontact(uint32_t), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ)
+ DBEVENTINFO dbei = {};
+ dbei.cbBlob = -1;
+ if (db_event_get(m_hDbEvent, &dbei))
+ return false;
+
+ m_szProto = dbei.szModule;
+ PROTOACCOUNT *acc = Proto_GetAccount(dbei.szModule);
+
+ uint32_t uin = *(uint32_t*)dbei.pBlob;
+ m_hContact = DbGetAuthEventContact(&dbei);
+ char *nick = (char*)dbei.pBlob + sizeof(uint32_t) * 2;
+ char *first = nick + mir_strlen(nick) + 1;
+ char *last = first + mir_strlen(first) + 1;
+ char *email = last + mir_strlen(last) + 1;
+ char *reason = email + mir_strlen(email) + 1;
+
+ #ifdef _WINDOWS
+ SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0));
+ SendMessage(m_hwnd, WM_SETICON, ICON_BIG, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_LARGE, 0));
+ #endif
+
+ ptrW lastT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(last) : mir_a2u(last));
+ ptrW firstT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(first) : mir_a2u(first));
+ ptrW nickT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(nick) : mir_a2u(nick));
+ ptrW emailT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(email) : mir_a2u(email));
+ ptrW reasonT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(reason) : mir_a2u(reason));
+
+ CMStringW wszHeader;
+ if (firstT[0] && lastT[0])
+ wszHeader.Format(L"%s %s", (wchar_t*)firstT, (wchar_t*)lastT);
+ else if (firstT[0])
+ wszHeader = firstT.get();
+ else if (lastT[0])
+ wszHeader = lastT.get();
+
+ if (mir_wstrlen(nickT)) {
+ if (wszHeader.IsEmpty())
+ wszHeader = nickT.get();
+ else
+ wszHeader.AppendFormat(L" %s", nickT.get());
+ }
+ if (wszHeader.IsEmpty())
+ wszHeader = TranslateT("<Unknown>");
+
+ if (uin && emailT[0])
+ wszHeader.AppendFormat(TranslateT(" requested authorization\n%u (%s) on %s"), uin, emailT.get(), acc->tszAccountName);
+ else if (uin)
+ wszHeader.AppendFormat(TranslateT(" requested authorization\n%u on %s"), uin, acc->tszAccountName);
+ else
+ wszHeader.AppendFormat(TranslateT(" requested authorization\n%s on %s"), emailT[0] ? emailT.get() : TranslateT("(Unknown)"), acc->tszAccountName);
+ fldHeader.SetText(wszHeader);
+
+ fldReason.SetText(reasonT);
+
+ if (m_hContact == INVALID_CONTACT_ID || Contact::OnList(m_hContact))
+ btnAdd.Hide();
+
+ edtReason.SetMaxLength(255);
+ if (CallProtoService(dbei.szModule, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_NOAUTHDENYREASON) {
+ edtReason.Disable();
+ edtReason.SetText(TranslateT("Feature is not supported by protocol"));
+ }
+
+ if (Contact::OnList(m_hContact)) {
+ chkAdd.Disable();
+ chkAdd.SetState(false);
+ }
+ else chkAdd.SetState(true);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ CallProtoService(m_szProto, PS_AUTHALLOW, m_hDbEvent, 0);
+
+ if (chkAdd.GetState())
+ Contact::AddByEvent(m_hDbEvent, m_hwnd);
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ if (!m_bSucceeded) {
+ if (edtReason.Enabled())
+ CallProtoService(m_szProto, PS_AUTHDENY, m_hDbEvent, (LPARAM)ptrW(edtReason.GetText()));
+ else
+ CallProtoService(m_szProto, PS_AUTHDENY, m_hDbEvent, 0);
+ }
+
+ Button_FreeIcon_IcoLib(m_hwnd, IDC_ADD);
+ Button_FreeIcon_IcoLib(m_hwnd, IDC_DETAILS);
+
+ #ifdef _WINDOWS
+ DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_BIG, 0));
+ DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, 0));
+ #endif
+ }
+
+ void onClick_Later(CCtrlButton*)
+ {
+ m_bSucceeded = true;
+ Close();
+ }
+
+ void onClick_Details(CCtrlButton*)
+ {
+ CallService(MS_USERINFO_SHOWDIALOG, m_hContact, 0);
+ }
+};
+
+static INT_PTR ShowReqWindow(WPARAM, LPARAM lParam)
+{
+ (new CAuthReqDlg(((CLISTEVENT *)lParam)->hDbEvent))->Show();
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class CAddedDlg : public CDlgBase
+{
+ MEVENT m_hDbEvent;
+ MCONTACT m_hContact;
+
+ CCtrlBase fldHeader;
+ CCtrlButton btnDetails, btnAdd;
+
+public:
+ CAddedDlg(MEVENT hEvent) :
+ CDlgBase(g_plugin, IDD_ADDED),
+ m_hDbEvent(hEvent),
+ btnAdd(this, IDC_ADD),
+ btnDetails(this, IDC_DETAILS),
+ fldHeader(this, IDC_HEADERBAR)
+ {
+ btnAdd.OnClick = Callback(this, &CAddedDlg::onClick_Add);
+ btnDetails.OnClick = Callback(this, &CAddedDlg::onClick_Details);
+ }
+
+ bool OnInitDialog() override
+ {
+ Button_SetSkin_IcoLib(m_hwnd, IDC_DETAILS, SKINICON_OTHER_USERDETAILS, LPGEN("View user's details"));
+ Button_SetSkin_IcoLib(m_hwnd, IDC_ADD, SKINICON_OTHER_ADDCONTACT, LPGEN("Add contact permanently to list"));
+
+ // blob is: uin(uint32_t), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ)
+ DB::EventInfo dbei;
+ dbei.cbBlob = -1;
+ db_event_get(m_hDbEvent, &dbei);
+
+ m_hContact = DbGetAuthEventContact(&dbei);
+
+ uint32_t uin = *(uint32_t*)dbei.pBlob;
+ char* nick = (char*)dbei.pBlob + sizeof(uint32_t) * 2;
+ char* first = nick + mir_strlen(nick) + 1;
+ char* last = first + mir_strlen(first) + 1;
+ char* email = last + mir_strlen(last) + 1;
+
+ #ifdef _WINDOWS
+ SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0));
+ SendMessage(m_hwnd, WM_SETICON, ICON_BIG, CallProtoService(dbei.szModule, PS_LOADICON, PLI_PROTOCOL | PLIF_LARGE, 0));
+ #endif
+
+ PROTOACCOUNT* acc = Proto_GetAccount(dbei.szModule);
+
+ ptrW lastT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(last) : mir_a2u(last));
+ ptrW firstT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(first) : mir_a2u(first));
+ ptrW nickT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(nick) : mir_a2u(nick));
+ ptrW emailT(dbei.flags & DBEF_UTF ? mir_utf8decodeW(email) : mir_a2u(email));
+
+ wchar_t name[128] = L"";
+ int off = 0;
+ if (firstT[0] && lastT[0])
+ off = mir_snwprintf(name, L"%s %s", firstT.get(), lastT.get());
+ else if (firstT[0])
+ off = mir_snwprintf(name, L"%s", firstT.get());
+ else if (lastT[0])
+ off = mir_snwprintf(name, L"%s", lastT.get());
+ if (nickT[0]) {
+ if (off)
+ mir_snwprintf(name + off, _countof(name) - off, L" (%s)", nickT.get());
+ else
+ wcsncpy_s(name, nickT, _TRUNCATE);
+ }
+ if (!name[0])
+ wcsncpy_s(name, TranslateT("<Unknown>"), _TRUNCATE);
+
+ wchar_t hdr[256];
+ if (uin && emailT[0])
+ mir_snwprintf(hdr, TranslateT("%s added you to the contact list\n%u (%s) on %s"), name, uin, emailT.get(), acc->tszAccountName);
+ else if (uin)
+ mir_snwprintf(hdr, TranslateT("%s added you to the contact list\n%u on %s"), name, uin, acc->tszAccountName);
+ else
+ mir_snwprintf(hdr, TranslateT("%s added you to the contact list\n%s on %s"), name, emailT[0] ? emailT.get() : TranslateT("(Unknown)"), acc->tszAccountName);
+ fldHeader.SetText(hdr);
+
+ if (m_hContact == INVALID_CONTACT_ID || Contact::OnList(m_hContact))
+ btnAdd.Hide();
+ return true;
+ }
+
+ bool OnApply()
+ {
+ Contact::AddByEvent(m_hDbEvent, m_hwnd);
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ Button_FreeIcon_IcoLib(m_hwnd, IDC_ADD);
+ Button_FreeIcon_IcoLib(m_hwnd, IDC_DETAILS);
+
+ #ifdef _WINDOWS
+ DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_BIG, 0));
+ DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, 0));
+ #endif
+ }
+
+ void onClick_Add(CCtrlButton*)
+ {
+ Contact::AddByEvent(m_hDbEvent, m_hwnd);
+
+ if (m_hContact == INVALID_CONTACT_ID || Contact::OnList(m_hContact))
+ btnAdd.Hide();
+ }
+
+ void onClick_Details(CCtrlButton*)
+ {
+ CallService(MS_USERINFO_SHOWDIALOG, m_hContact, 0);
+ }
+};
+
+static INT_PTR ShowAddedWindow(WPARAM, LPARAM lParam)
+{
+ (new CAddedDlg(((CLISTEVENT *)lParam)->hDbEvent))->Show();
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#define MS_AUTH_SHOWADDED "Auth/ShowAdded"
+#define MS_AUTH_SHOWREQUEST "Auth/ShowRequest"
+
+static int AuthEventAdded(WPARAM, LPARAM lParam)
+{
+ wchar_t szTooltip[256];
+ MEVENT hDbEvent = (MEVENT)lParam;
+
+ DB::EventInfo dbei;
+ db_event_get(lParam, &dbei);
+ if (dbei.flags & (DBEF_SENT | DBEF_READ) || (dbei.eventType != EVENTTYPE_AUTHREQUEST && dbei.eventType != EVENTTYPE_ADDED))
+ return 0;
+
+ dbei.cbBlob = -1;
+ db_event_get(hDbEvent, &dbei);
+
+ MCONTACT hContact = DbGetAuthEventContact(&dbei);
+
+ CLISTEVENT cle = {};
+ cle.hContact = hContact;
+ cle.szTooltip.w = szTooltip;
+ cle.flags = CLEF_UNICODE;
+ cle.lParam = lParam;
+ cle.hDbEvent = hDbEvent;
+
+ ptrW szUid(Contact::GetInfo(CNF_UNIQUEID, hContact));
+
+ if (dbei.eventType == EVENTTYPE_AUTHREQUEST) {
+ Skin_PlaySound("AuthRequest");
+ if (szUid)
+ mir_snwprintf(szTooltip, TranslateT("%s requests authorization"), szUid.get());
+ else
+ mir_snwprintf(szTooltip, TranslateT("%u requests authorization"), *(uint32_t*)dbei.pBlob);
+
+ cle.hIcon = Skin_LoadIcon(SKINICON_AUTH_REQUEST);
+ cle.pszService = MS_AUTH_SHOWREQUEST;
+ g_clistApi.pfnAddEvent(&cle);
+ }
+ else if (dbei.eventType == EVENTTYPE_ADDED) {
+ Skin_PlaySound("AddedEvent");
+ if (szUid)
+ mir_snwprintf(szTooltip, TranslateT("%s added you to their contact list"), szUid.get());
+ else
+ mir_snwprintf(szTooltip, TranslateT("%u added you to their contact list"), *(uint32_t*)dbei.pBlob);
+
+ cle.hIcon = Skin_LoadIcon(SKINICON_AUTH_ADD);
+ cle.pszService = MS_AUTH_SHOWADDED;
+ g_clistApi.pfnAddEvent(&cle);
+ }
+ return 0;
+}
+
+static void CALLBACK LaunchAuth()
+{
+ HookEvent(ME_DB_EVENT_ADDED, AuthEventAdded);
+}
+
+int LoadSendRecvAuthModule(void)
+{
+ CreateServiceFunction(MS_AUTH_SHOWREQUEST, ShowReqWindow);
+ CreateServiceFunction(MS_AUTH_SHOWADDED, ShowAddedWindow);
+ Miranda_WaitOnHandle(LaunchAuth);
+
+ g_plugin.addSound("AuthRequest", LPGENW("Alerts"), LPGENW("Authorization request"));
+ g_plugin.addSound("AddedEvent", LPGENW("Alerts"), LPGENW("Added event"));
+ return 0;
+}
diff --git a/src/mir_app/src/button.cpp b/src/mir_app/src/button.cpp index 4a9b548c83..b97685716e 100644 --- a/src/mir_app/src/button.cpp +++ b/src/mir_app/src/button.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/chat_manager.cpp b/src/mir_app/src/chat_manager.cpp index 3e0f2b8b5d..7019e3a2a7 100644 --- a/src/mir_app/src/chat_manager.cpp +++ b/src/mir_app/src/chat_manager.cpp @@ -1,7 +1,7 @@ /*
Chat module plugin for Miranda IM
-Copyright 2000-12 Miranda IM, 2012-22 Miranda NG team,
+Copyright 2000-12 Miranda IM, 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/chat_rtf.cpp b/src/mir_app/src/chat_rtf.cpp index 82545dc363..508fc1a86d 100644 --- a/src/mir_app/src/chat_rtf.cpp +++ b/src/mir_app/src/chat_rtf.cpp @@ -1,204 +1,204 @@ -/* -Chat module plugin for Miranda IM - -Copyright 2000-12 Miranda IM, 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "chat.h" - -///////////////////////////////////////////////////////////////////////////////////////// -// convert rich edit code to bbcode (if wanted). Otherwise, strip all RTF formatting -// tags and return plain text - -static wchar_t tszRtfBreaks[] = L" \\\n\r"; - -static void CreateColorMap(CMStringW &Text, int iCount, COLORREF *pSrc, int *pDst) -{ - const wchar_t *pszText = Text; - int iIndex = 1; - - static const wchar_t *lpszFmt = L"\\red%[^ \x5b\\]\\green%[^ \x5b\\]\\blue%[^ \x5b;];"; - wchar_t szRed[10], szGreen[10], szBlue[10]; - - const wchar_t *p1 = wcsstr(pszText, L"\\colortbl"); - if (!p1) - return; - - const wchar_t *pEnd = wcschr(p1, '}'); - - const wchar_t *p2 = wcsstr(p1, L"\\red"); - - for (int i = 0; i < iCount; i++) - pDst[i] = -1; - - while (p2 && p2 < pEnd) { - if (swscanf(p2, lpszFmt, &szRed, &szGreen, &szBlue) > 0) { - for (int i = 0; i < iCount; i++) { - if (pSrc[i] == RGB(_wtoi(szRed), _wtoi(szGreen), _wtoi(szBlue))) - pDst[i] = iIndex; - } - } - iIndex++; - p1 = p2; - p1++; - - p2 = wcsstr(p1, L"\\red"); - } -} - -static int GetRtfIndex(int iCol, int iCount, int *pIndex) -{ - for (int i = 0; i < iCount; i++) - if (pIndex[i] == iCol) - return i; - - return -1; -} - -int DoRtfToTags(CMStringW &pszText, int iNumColors, COLORREF *pColors) -{ - if (pszText.IsEmpty()) - return FALSE; - - // create an index of colors in the module and map them to - // corresponding colors in the RTF color table - int *pIndex = (int*)_alloca(iNumColors * sizeof(int)); - CreateColorMap(pszText, iNumColors, pColors, pIndex); - - // scan the file for rtf commands and remove or parse them - int idx = pszText.Find(L"\\pard"); - if (idx == -1) { - if ((idx = pszText.Find(L"\\ltrpar")) == -1) - return FALSE; - idx += 7; - } - else idx += 5; - - bool bInsideColor = false, bInsideUl = false; - CMStringW res; - - // iterate through all characters, if rtf control character found then take action - for (const wchar_t *p = pszText.GetString() + idx; *p;) { - switch (*p) { - case '\\': - if (p[1] == '\\' || p[1] == '{' || p[1] == '}') { // escaped characters - res.AppendChar(p[1]); - p += 2; break; - } - if (p[1] == '~') { // non-breaking space - res.AppendChar(0xA0); - p += 2; break; - } - - if (!wcsncmp(p, L"\\cf", 3)) { // foreground color - int iCol = _wtoi(p + 3); - int iInd = GetRtfIndex(iCol, iNumColors, pIndex); - bInsideColor = iInd > 0; - } - else if (!wcsncmp(p, L"\\highlight", 10)) { //background color - wchar_t szTemp[20]; - int iCol = _wtoi(p + 10); - mir_snwprintf(szTemp, L"%d", iCol); - } - else if (!wcsncmp(p, L"\\line", 5)) { // soft line break; - res.AppendChar('\n'); - } - else if (!wcsncmp(p, L"\\endash", 7)) { - res.AppendChar(0x2013); - } - else if (!wcsncmp(p, L"\\emdash", 7)) { - res.AppendChar(0x2014); - } - else if (!wcsncmp(p, L"\\bullet", 7)) { - res.AppendChar(0x2022); - } - else if (!wcsncmp(p, L"\\ldblquote", 10)) { - res.AppendChar(0x201C); - } - else if (!wcsncmp(p, L"\\rdblquote", 10)) { - res.AppendChar(0x201D); - } - else if (!wcsncmp(p, L"\\lquote", 7)) { - res.AppendChar(0x2018); - } - else if (!wcsncmp(p, L"\\rquote", 7)) { - res.AppendChar(0x2019); - } - else if (!wcsncmp(p, L"\\b", 2)) { //bold - res.Append((p[2] != '0') ? L"[b]" : L"[/b]"); - } - else if (!wcsncmp(p, L"\\i", 2)) { // italics - res.Append((p[2] != '0') ? L"[i]" : L"[/i]"); - } - else if (!wcsncmp(p, L"\\strike", 7)) { // strike-out - res.Append((p[7] != '0') ? L"[s]" : L"[/s]"); - } - else if (!wcsncmp(p, L"\\ul", 3)) { // underlined - if (p[3] == 0 || wcschr(tszRtfBreaks, p[3])) { - res.Append(L"[u]"); - bInsideUl = true; - } - else if (!wcsncmp(p + 3, L"none", 4)) { - if (bInsideUl) - res.Append(L"[/u]"); - bInsideUl = false; - } - } - else if (!wcsncmp(p, L"\\tab", 4)) { // tab - res.AppendChar('\t'); - } - else if (p[1] == '\'') { // special character - if (p[2] != ' ' && p[2] != '\\') { - wchar_t tmp[10], *t = tmp; - *t++ = p[2]; - if (p[3] != ' ' && p[3] != '\\') - *t++ = p[3]; - *t = 0; - - // convert string containing char in hex format to int. - wchar_t *stoppedHere; - res.AppendChar(wcstol(tmp, &stoppedHere, 16)); - } - } - - p++; // skip initial slash - p += wcscspn(p, tszRtfBreaks); - if (*p == ' ') - p++; - break; - - case '{': // other RTF control characters - case '}': - p++; - break; - - default: // other text that should not be touched - res.AppendChar(*p++); - break; - } - } - - if (bInsideUl) - res.Append(L"[/u]"); - - pszText = res; - return TRUE; -} +/*
+Chat module plugin for Miranda IM
+
+Copyright 2000-12 Miranda IM, 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "chat.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// convert rich edit code to bbcode (if wanted). Otherwise, strip all RTF formatting
+// tags and return plain text
+
+static wchar_t tszRtfBreaks[] = L" \\\n\r";
+
+static void CreateColorMap(CMStringW &Text, int iCount, COLORREF *pSrc, int *pDst)
+{
+ const wchar_t *pszText = Text;
+ int iIndex = 1;
+
+ static const wchar_t *lpszFmt = L"\\red%[^ \x5b\\]\\green%[^ \x5b\\]\\blue%[^ \x5b;];";
+ wchar_t szRed[10], szGreen[10], szBlue[10];
+
+ const wchar_t *p1 = wcsstr(pszText, L"\\colortbl");
+ if (!p1)
+ return;
+
+ const wchar_t *pEnd = wcschr(p1, '}');
+
+ const wchar_t *p2 = wcsstr(p1, L"\\red");
+
+ for (int i = 0; i < iCount; i++)
+ pDst[i] = -1;
+
+ while (p2 && p2 < pEnd) {
+ if (swscanf(p2, lpszFmt, &szRed, &szGreen, &szBlue) > 0) {
+ for (int i = 0; i < iCount; i++) {
+ if (pSrc[i] == RGB(_wtoi(szRed), _wtoi(szGreen), _wtoi(szBlue)))
+ pDst[i] = iIndex;
+ }
+ }
+ iIndex++;
+ p1 = p2;
+ p1++;
+
+ p2 = wcsstr(p1, L"\\red");
+ }
+}
+
+static int GetRtfIndex(int iCol, int iCount, int *pIndex)
+{
+ for (int i = 0; i < iCount; i++)
+ if (pIndex[i] == iCol)
+ return i;
+
+ return -1;
+}
+
+int DoRtfToTags(CMStringW &pszText, int iNumColors, COLORREF *pColors)
+{
+ if (pszText.IsEmpty())
+ return FALSE;
+
+ // create an index of colors in the module and map them to
+ // corresponding colors in the RTF color table
+ int *pIndex = (int*)_alloca(iNumColors * sizeof(int));
+ CreateColorMap(pszText, iNumColors, pColors, pIndex);
+
+ // scan the file for rtf commands and remove or parse them
+ int idx = pszText.Find(L"\\pard");
+ if (idx == -1) {
+ if ((idx = pszText.Find(L"\\ltrpar")) == -1)
+ return FALSE;
+ idx += 7;
+ }
+ else idx += 5;
+
+ bool bInsideColor = false, bInsideUl = false;
+ CMStringW res;
+
+ // iterate through all characters, if rtf control character found then take action
+ for (const wchar_t *p = pszText.GetString() + idx; *p;) {
+ switch (*p) {
+ case '\\':
+ if (p[1] == '\\' || p[1] == '{' || p[1] == '}') { // escaped characters
+ res.AppendChar(p[1]);
+ p += 2; break;
+ }
+ if (p[1] == '~') { // non-breaking space
+ res.AppendChar(0xA0);
+ p += 2; break;
+ }
+
+ if (!wcsncmp(p, L"\\cf", 3)) { // foreground color
+ int iCol = _wtoi(p + 3);
+ int iInd = GetRtfIndex(iCol, iNumColors, pIndex);
+ bInsideColor = iInd > 0;
+ }
+ else if (!wcsncmp(p, L"\\highlight", 10)) { //background color
+ wchar_t szTemp[20];
+ int iCol = _wtoi(p + 10);
+ mir_snwprintf(szTemp, L"%d", iCol);
+ }
+ else if (!wcsncmp(p, L"\\line", 5)) { // soft line break;
+ res.AppendChar('\n');
+ }
+ else if (!wcsncmp(p, L"\\endash", 7)) {
+ res.AppendChar(0x2013);
+ }
+ else if (!wcsncmp(p, L"\\emdash", 7)) {
+ res.AppendChar(0x2014);
+ }
+ else if (!wcsncmp(p, L"\\bullet", 7)) {
+ res.AppendChar(0x2022);
+ }
+ else if (!wcsncmp(p, L"\\ldblquote", 10)) {
+ res.AppendChar(0x201C);
+ }
+ else if (!wcsncmp(p, L"\\rdblquote", 10)) {
+ res.AppendChar(0x201D);
+ }
+ else if (!wcsncmp(p, L"\\lquote", 7)) {
+ res.AppendChar(0x2018);
+ }
+ else if (!wcsncmp(p, L"\\rquote", 7)) {
+ res.AppendChar(0x2019);
+ }
+ else if (!wcsncmp(p, L"\\b", 2)) { //bold
+ res.Append((p[2] != '0') ? L"[b]" : L"[/b]");
+ }
+ else if (!wcsncmp(p, L"\\i", 2)) { // italics
+ res.Append((p[2] != '0') ? L"[i]" : L"[/i]");
+ }
+ else if (!wcsncmp(p, L"\\strike", 7)) { // strike-out
+ res.Append((p[7] != '0') ? L"[s]" : L"[/s]");
+ }
+ else if (!wcsncmp(p, L"\\ul", 3)) { // underlined
+ if (p[3] == 0 || wcschr(tszRtfBreaks, p[3])) {
+ res.Append(L"[u]");
+ bInsideUl = true;
+ }
+ else if (!wcsncmp(p + 3, L"none", 4)) {
+ if (bInsideUl)
+ res.Append(L"[/u]");
+ bInsideUl = false;
+ }
+ }
+ else if (!wcsncmp(p, L"\\tab", 4)) { // tab
+ res.AppendChar('\t');
+ }
+ else if (p[1] == '\'') { // special character
+ if (p[2] != ' ' && p[2] != '\\') {
+ wchar_t tmp[10], *t = tmp;
+ *t++ = p[2];
+ if (p[3] != ' ' && p[3] != '\\')
+ *t++ = p[3];
+ *t = 0;
+
+ // convert string containing char in hex format to int.
+ wchar_t *stoppedHere;
+ res.AppendChar(wcstol(tmp, &stoppedHere, 16));
+ }
+ }
+
+ p++; // skip initial slash
+ p += wcscspn(p, tszRtfBreaks);
+ if (*p == ' ')
+ p++;
+ break;
+
+ case '{': // other RTF control characters
+ case '}':
+ p++;
+ break;
+
+ default: // other text that should not be touched
+ res.AppendChar(*p++);
+ break;
+ }
+ }
+
+ if (bInsideUl)
+ res.Append(L"[/u]");
+
+ pszText = res;
+ return TRUE;
+}
diff --git a/src/mir_app/src/chat_svc.cpp b/src/mir_app/src/chat_svc.cpp index 0043df7ae6..021079ba46 100644 --- a/src/mir_app/src/chat_svc.cpp +++ b/src/mir_app/src/chat_svc.cpp @@ -1,7 +1,7 @@ /*
Chat module plugin for Miranda IM
-Copyright 2000-12 Miranda IM, 2012-22 Miranda NG team,
+Copyright 2000-12 Miranda IM, 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/chat_tools.cpp b/src/mir_app/src/chat_tools.cpp index 4c2bcc7698..cb962183de 100644 --- a/src/mir_app/src/chat_tools.cpp +++ b/src/mir_app/src/chat_tools.cpp @@ -1,7 +1,7 @@ /*
Chat module plugin for Miranda IM
-Copyright 2000-12 Miranda IM, 2012-22 Miranda NG team,
+Copyright 2000-12 Miranda IM, 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/chat_ui.cpp b/src/mir_app/src/chat_ui.cpp index 789f6fd075..4c5c21b6cd 100644 --- a/src/mir_app/src/chat_ui.cpp +++ b/src/mir_app/src/chat_ui.cpp @@ -1,220 +1,220 @@ -/* -Chat module plugin for Miranda IM - -Copyright 2000-12 Miranda IM, 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "chat.h" - -CMOption<bool> g_bChatPopupInactive(CHAT_MODULE, "PopupInactiveOnly", true); -CMOption<bool> g_bChatTrayInactive(CHAT_MODULE, "TrayIconInactiveOnly", true); - -///////////////////////////////////////////////////////////////////////////////////////// -// Group chat - Events - -#define NR_GC_EVENTS 12 - -static UINT _eventorder[] = -{ - GC_EVENT_ACTION, - GC_EVENT_MESSAGE, - GC_EVENT_NICK, - GC_EVENT_JOIN, - GC_EVENT_PART, - GC_EVENT_TOPIC, - GC_EVENT_ADDSTATUS, - GC_EVENT_INFORMATION, - GC_EVENT_QUIT, - GC_EVENT_KICK, - GC_EVENT_NOTICE, - GC_EVENT_HIGHLIGHT -}; - -class CChatEventOptionDlg : public CDlgBase -{ - CCtrlCheck chkTray, chkPopup, chkRightClick; - CCtrlMButton btn1, btn2, btn3, btn4; - - void InvertColumn(int ctrlId) - { - int enabled = !IsDlgButtonChecked(m_hwnd, ctrlId); - for (int i = 0; i < _countof(_eventorder); i++) - CheckDlgButton(m_hwnd, ctrlId + i, enabled); - NotifyChange(); - } - -public: - CChatEventOptionDlg() : - CDlgBase(g_plugin, IDD_OPT_CHAT_EVENTS), - chkTray(this, IDC_TRAYONLYFORINACTIVE), - chkPopup(this, IDC_POPUPONLYFORINACTIVE), - chkRightClick(this, IDC_RIGHTCLICK), - btn1(this, IDC_ICON1, SKINICON_OTHER_POPUP, LPGEN("Popup")), - btn2(this, IDC_ICON2, SKINICON_OTHER_MIRANDA, LPGEN("Tray")), - btn3(this, IDC_ICON3, SKINICON_OTHER_SOUND, LPGEN("Sound")), - btn4(this, IDC_ICON4, SKINICON_EVENT_FILE, LPGEN("Log to file")) - { - CreateLink(chkTray, g_bChatTrayInactive); - CreateLink(chkPopup, g_bChatPopupInactive); - CreateLink(chkRightClick, g_chatApi.bRightClickFilter); - - btn1.OnClick = Callback(this, &CChatEventOptionDlg::onClick_Popup); - btn2.OnClick = Callback(this, &CChatEventOptionDlg::onClick_Tray); - btn3.OnClick = Callback(this, &CChatEventOptionDlg::onClick_Sound); - btn4.OnClick = Callback(this, &CChatEventOptionDlg::onClick_Log); - } - - bool OnInitDialog() override - { - btn1.MakeFlat(); btn2.MakeFlat(); btn3.MakeFlat(); btn4.MakeFlat(); - - uint32_t dwFilterFlags = db_get_dw(0, CHAT_MODULE, "FilterFlags", GC_EVENT_ALL); - uint32_t dwTrayFlags = db_get_dw(0, CHAT_MODULE, "TrayIconFlags", GC_EVENT_HIGHLIGHT); - uint32_t dwPopupFlags = db_get_dw(0, CHAT_MODULE, "PopupFlags", GC_EVENT_HIGHLIGHT); - uint32_t dwSoundFlags = db_get_dw(0, CHAT_MODULE, "SoundFlags", GC_EVENT_HIGHLIGHT); - uint32_t dwLogFlags = db_get_dw(0, CHAT_MODULE, "DiskLogFlags", GC_EVENT_ALL); - - for (int i = 0; i < _countof(_eventorder); i++) { - if (_eventorder[i] != GC_EVENT_HIGHLIGHT) { - CheckDlgButton(m_hwnd, IDC_1 + i, dwFilterFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_L1 + i, dwLogFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED); - } - CheckDlgButton(m_hwnd, IDC_P1 + i, dwPopupFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_T1 + i, dwTrayFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_S1 + i, dwSoundFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED); - } - return true; - } - - bool OnApply() override - { - uint32_t dwFilterFlags = 0, dwTrayFlags = 0, dwPopupFlags = 0, dwSoundFlags = 0, dwLogFlags = 0; - - for (int i = 0; i < _countof(_eventorder); i++) { - if (_eventorder[i] != GC_EVENT_HIGHLIGHT) { - dwFilterFlags |= (IsDlgButtonChecked(m_hwnd, IDC_1 + i) ? _eventorder[i] : 0); - dwLogFlags |= (IsDlgButtonChecked(m_hwnd, IDC_L1 + i) ? _eventorder[i] : 0); - } - dwSoundFlags |= (IsDlgButtonChecked(m_hwnd, IDC_S1 + i) ? _eventorder[i] : 0); - dwPopupFlags |= (IsDlgButtonChecked(m_hwnd, IDC_P1 + i) ? _eventorder[i] : 0); - dwTrayFlags |= (IsDlgButtonChecked(m_hwnd, IDC_T1 + i) ? _eventorder[i] : 0); - } - db_set_dw(0, CHAT_MODULE, "FilterFlags", dwFilterFlags); - db_set_dw(0, CHAT_MODULE, "PopupFlags", dwPopupFlags); - db_set_dw(0, CHAT_MODULE, "SoundFlags", dwSoundFlags); - db_set_dw(0, CHAT_MODULE, "TrayIconFlags", dwTrayFlags); - db_set_dw(0, CHAT_MODULE, "DiskLogFlags", dwLogFlags); - - LoadGlobalSettings(); - return true; - } - - void onClick_Popup(CCtrlButton *) { InvertColumn(IDC_P1); } - void onClick_Sound(CCtrlButton *) { InvertColumn(IDC_S1); } - void onClick_Tray(CCtrlButton *) { InvertColumn(IDC_T1); } - void onClick_Log(CCtrlButton *) { InvertColumn(IDC_L1); } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -///////////////////////////////////////////////////////////////////////////////////////// -// Popup options - -class COptPopupDlg : public CDlgBase -{ - CCtrlSpin spinTimeout; - CCtrlCheck chkRadio1, chkRadio2, chkRadio3; - CCtrlColor clrBack, clrText; - -public: - COptPopupDlg() : - CDlgBase(g_plugin, IDD_OPTIONSPOPUP), - clrBack(this, IDC_BKG), - clrText(this, IDC_TEXT), - chkRadio1(this, IDC_RADIO1), - chkRadio2(this, IDC_RADIO2), - chkRadio3(this, IDC_RADIO3), - spinTimeout(this, IDC_SPIN1, 100, -1) - { - chkRadio1.OnChange = chkRadio2.OnChange = chkRadio3.OnChange = Callback(this, &COptPopupDlg::onChange_Radio); - } - - bool OnInitDialog() override - { - clrBack.SetColor(g_Settings->crPUBkgColour); - clrText.SetColor(g_Settings->crPUTextColour); - - if (g_Settings->iPopupStyle == 2) - CheckDlgButton(m_hwnd, IDC_RADIO2, BST_CHECKED); - else if (g_Settings->iPopupStyle == 3) - CheckDlgButton(m_hwnd, IDC_RADIO3, BST_CHECKED); - else - CheckDlgButton(m_hwnd, IDC_RADIO1, BST_CHECKED); - onChange_Radio(0); - - spinTimeout.SetPosition(g_Settings->iPopupTimeout); - return true; - } - - bool OnApply() override - { - if (IsDlgButtonChecked(m_hwnd, IDC_RADIO2) == BST_CHECKED) - g_Settings->iPopupStyle = 2; - else if (IsDlgButtonChecked(m_hwnd, IDC_RADIO3) == BST_CHECKED) - g_Settings->iPopupStyle = 3; - else - g_Settings->iPopupStyle = 1; - db_set_b(0, CHAT_MODULE, "PopupStyle", g_Settings->iPopupStyle); - - db_set_w(0, CHAT_MODULE, "PopupTimeout", g_Settings->iPopupTimeout = spinTimeout.GetPosition()); - db_set_dw(0, CHAT_MODULE, "PopupColorBG", g_Settings->crPUBkgColour = clrBack.GetColor()); - db_set_dw(0, CHAT_MODULE, "PopupColorText", g_Settings->crPUTextColour = clrText.GetColor()); - return true; - } - - void onChange_Radio(CCtrlCheck *) - { - bool bStatus = chkRadio3.GetState(); - clrBack.Enable(bStatus); - clrText.Enable(bStatus); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -void ChatOptionsInit(WPARAM wParam) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.flags = ODPF_BOLDGROUPS; - odp.position = 910000000; - odp.szGroup.a = LPGEN("Message sessions"); - odp.szTitle.a = LPGEN("Group chats"); - odp.szTab.a = LPGEN("Events and filters"); - odp.pDialog = new CChatEventOptionDlg(); - g_plugin.addOptions(wParam, &odp); - - odp.position = 910000002; - odp.szTitle.a = LPGEN("Group chats"); - odp.szGroup.a = LPGEN("Popups"); - odp.szTab.a = nullptr; - odp.pDialog = new COptPopupDlg(); - g_plugin.addOptions(wParam, &odp); -} +/*
+Chat module plugin for Miranda IM
+
+Copyright 2000-12 Miranda IM, 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "chat.h"
+
+CMOption<bool> g_bChatPopupInactive(CHAT_MODULE, "PopupInactiveOnly", true);
+CMOption<bool> g_bChatTrayInactive(CHAT_MODULE, "TrayIconInactiveOnly", true);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Group chat - Events
+
+#define NR_GC_EVENTS 12
+
+static UINT _eventorder[] =
+{
+ GC_EVENT_ACTION,
+ GC_EVENT_MESSAGE,
+ GC_EVENT_NICK,
+ GC_EVENT_JOIN,
+ GC_EVENT_PART,
+ GC_EVENT_TOPIC,
+ GC_EVENT_ADDSTATUS,
+ GC_EVENT_INFORMATION,
+ GC_EVENT_QUIT,
+ GC_EVENT_KICK,
+ GC_EVENT_NOTICE,
+ GC_EVENT_HIGHLIGHT
+};
+
+class CChatEventOptionDlg : public CDlgBase
+{
+ CCtrlCheck chkTray, chkPopup, chkRightClick;
+ CCtrlMButton btn1, btn2, btn3, btn4;
+
+ void InvertColumn(int ctrlId)
+ {
+ int enabled = !IsDlgButtonChecked(m_hwnd, ctrlId);
+ for (int i = 0; i < _countof(_eventorder); i++)
+ CheckDlgButton(m_hwnd, ctrlId + i, enabled);
+ NotifyChange();
+ }
+
+public:
+ CChatEventOptionDlg() :
+ CDlgBase(g_plugin, IDD_OPT_CHAT_EVENTS),
+ chkTray(this, IDC_TRAYONLYFORINACTIVE),
+ chkPopup(this, IDC_POPUPONLYFORINACTIVE),
+ chkRightClick(this, IDC_RIGHTCLICK),
+ btn1(this, IDC_ICON1, SKINICON_OTHER_POPUP, LPGEN("Popup")),
+ btn2(this, IDC_ICON2, SKINICON_OTHER_MIRANDA, LPGEN("Tray")),
+ btn3(this, IDC_ICON3, SKINICON_OTHER_SOUND, LPGEN("Sound")),
+ btn4(this, IDC_ICON4, SKINICON_EVENT_FILE, LPGEN("Log to file"))
+ {
+ CreateLink(chkTray, g_bChatTrayInactive);
+ CreateLink(chkPopup, g_bChatPopupInactive);
+ CreateLink(chkRightClick, g_chatApi.bRightClickFilter);
+
+ btn1.OnClick = Callback(this, &CChatEventOptionDlg::onClick_Popup);
+ btn2.OnClick = Callback(this, &CChatEventOptionDlg::onClick_Tray);
+ btn3.OnClick = Callback(this, &CChatEventOptionDlg::onClick_Sound);
+ btn4.OnClick = Callback(this, &CChatEventOptionDlg::onClick_Log);
+ }
+
+ bool OnInitDialog() override
+ {
+ btn1.MakeFlat(); btn2.MakeFlat(); btn3.MakeFlat(); btn4.MakeFlat();
+
+ uint32_t dwFilterFlags = db_get_dw(0, CHAT_MODULE, "FilterFlags", GC_EVENT_ALL);
+ uint32_t dwTrayFlags = db_get_dw(0, CHAT_MODULE, "TrayIconFlags", GC_EVENT_HIGHLIGHT);
+ uint32_t dwPopupFlags = db_get_dw(0, CHAT_MODULE, "PopupFlags", GC_EVENT_HIGHLIGHT);
+ uint32_t dwSoundFlags = db_get_dw(0, CHAT_MODULE, "SoundFlags", GC_EVENT_HIGHLIGHT);
+ uint32_t dwLogFlags = db_get_dw(0, CHAT_MODULE, "DiskLogFlags", GC_EVENT_ALL);
+
+ for (int i = 0; i < _countof(_eventorder); i++) {
+ if (_eventorder[i] != GC_EVENT_HIGHLIGHT) {
+ CheckDlgButton(m_hwnd, IDC_1 + i, dwFilterFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_L1 + i, dwLogFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED);
+ }
+ CheckDlgButton(m_hwnd, IDC_P1 + i, dwPopupFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_T1 + i, dwTrayFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_S1 + i, dwSoundFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED);
+ }
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ uint32_t dwFilterFlags = 0, dwTrayFlags = 0, dwPopupFlags = 0, dwSoundFlags = 0, dwLogFlags = 0;
+
+ for (int i = 0; i < _countof(_eventorder); i++) {
+ if (_eventorder[i] != GC_EVENT_HIGHLIGHT) {
+ dwFilterFlags |= (IsDlgButtonChecked(m_hwnd, IDC_1 + i) ? _eventorder[i] : 0);
+ dwLogFlags |= (IsDlgButtonChecked(m_hwnd, IDC_L1 + i) ? _eventorder[i] : 0);
+ }
+ dwSoundFlags |= (IsDlgButtonChecked(m_hwnd, IDC_S1 + i) ? _eventorder[i] : 0);
+ dwPopupFlags |= (IsDlgButtonChecked(m_hwnd, IDC_P1 + i) ? _eventorder[i] : 0);
+ dwTrayFlags |= (IsDlgButtonChecked(m_hwnd, IDC_T1 + i) ? _eventorder[i] : 0);
+ }
+ db_set_dw(0, CHAT_MODULE, "FilterFlags", dwFilterFlags);
+ db_set_dw(0, CHAT_MODULE, "PopupFlags", dwPopupFlags);
+ db_set_dw(0, CHAT_MODULE, "SoundFlags", dwSoundFlags);
+ db_set_dw(0, CHAT_MODULE, "TrayIconFlags", dwTrayFlags);
+ db_set_dw(0, CHAT_MODULE, "DiskLogFlags", dwLogFlags);
+
+ LoadGlobalSettings();
+ return true;
+ }
+
+ void onClick_Popup(CCtrlButton *) { InvertColumn(IDC_P1); }
+ void onClick_Sound(CCtrlButton *) { InvertColumn(IDC_S1); }
+ void onClick_Tray(CCtrlButton *) { InvertColumn(IDC_T1); }
+ void onClick_Log(CCtrlButton *) { InvertColumn(IDC_L1); }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popup options
+
+class COptPopupDlg : public CDlgBase
+{
+ CCtrlSpin spinTimeout;
+ CCtrlCheck chkRadio1, chkRadio2, chkRadio3;
+ CCtrlColor clrBack, clrText;
+
+public:
+ COptPopupDlg() :
+ CDlgBase(g_plugin, IDD_OPTIONSPOPUP),
+ clrBack(this, IDC_BKG),
+ clrText(this, IDC_TEXT),
+ chkRadio1(this, IDC_RADIO1),
+ chkRadio2(this, IDC_RADIO2),
+ chkRadio3(this, IDC_RADIO3),
+ spinTimeout(this, IDC_SPIN1, 100, -1)
+ {
+ chkRadio1.OnChange = chkRadio2.OnChange = chkRadio3.OnChange = Callback(this, &COptPopupDlg::onChange_Radio);
+ }
+
+ bool OnInitDialog() override
+ {
+ clrBack.SetColor(g_Settings->crPUBkgColour);
+ clrText.SetColor(g_Settings->crPUTextColour);
+
+ if (g_Settings->iPopupStyle == 2)
+ CheckDlgButton(m_hwnd, IDC_RADIO2, BST_CHECKED);
+ else if (g_Settings->iPopupStyle == 3)
+ CheckDlgButton(m_hwnd, IDC_RADIO3, BST_CHECKED);
+ else
+ CheckDlgButton(m_hwnd, IDC_RADIO1, BST_CHECKED);
+ onChange_Radio(0);
+
+ spinTimeout.SetPosition(g_Settings->iPopupTimeout);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ if (IsDlgButtonChecked(m_hwnd, IDC_RADIO2) == BST_CHECKED)
+ g_Settings->iPopupStyle = 2;
+ else if (IsDlgButtonChecked(m_hwnd, IDC_RADIO3) == BST_CHECKED)
+ g_Settings->iPopupStyle = 3;
+ else
+ g_Settings->iPopupStyle = 1;
+ db_set_b(0, CHAT_MODULE, "PopupStyle", g_Settings->iPopupStyle);
+
+ db_set_w(0, CHAT_MODULE, "PopupTimeout", g_Settings->iPopupTimeout = spinTimeout.GetPosition());
+ db_set_dw(0, CHAT_MODULE, "PopupColorBG", g_Settings->crPUBkgColour = clrBack.GetColor());
+ db_set_dw(0, CHAT_MODULE, "PopupColorText", g_Settings->crPUTextColour = clrText.GetColor());
+ return true;
+ }
+
+ void onChange_Radio(CCtrlCheck *)
+ {
+ bool bStatus = chkRadio3.GetState();
+ clrBack.Enable(bStatus);
+ clrText.Enable(bStatus);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void ChatOptionsInit(WPARAM wParam)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.position = 910000000;
+ odp.szGroup.a = LPGEN("Message sessions");
+ odp.szTitle.a = LPGEN("Group chats");
+ odp.szTab.a = LPGEN("Events and filters");
+ odp.pDialog = new CChatEventOptionDlg();
+ g_plugin.addOptions(wParam, &odp);
+
+ odp.position = 910000002;
+ odp.szTitle.a = LPGEN("Group chats");
+ odp.szGroup.a = LPGEN("Popups");
+ odp.szTab.a = nullptr;
+ odp.pDialog = new COptPopupDlg();
+ g_plugin.addOptions(wParam, &odp);
+}
diff --git a/src/mir_app/src/clc.cpp b/src/mir_app/src/clc.cpp index d44673425e..0266b7bcf3 100644 --- a/src/mir_app/src/clc.cpp +++ b/src/mir_app/src/clc.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clc.h b/src/mir_app/src/clc.h index d57ec3b1ab..15eae7d75c 100644 --- a/src/mir_app/src/clc.h +++ b/src/mir_app/src/clc.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clccontact.cpp b/src/mir_app/src/clccontact.cpp index 3dbf578cd6..06639ba8d0 100644 --- a/src/mir_app/src/clccontact.cpp +++ b/src/mir_app/src/clccontact.cpp @@ -1,72 +1,72 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "clc.h" - -extern HANDLE hGroupChangeEvent; - -MIR_APP_DLL(void) Clist_LoadContactTree(void) -{ - bool hideOffline = Clist::HideOffline; - for (auto &hContact : Contacts()) { - int status = Contact::GetStatus(hContact); - if ((!hideOffline || status != ID_STATUS_OFFLINE) && !Contact::IsHidden(hContact)) - Clist_ChangeContactIcon(hContact, g_clistApi.pfnIconFromStatusMode(Proto_GetBaseAccountName(hContact), status, hContact)); - } - Clist_EndRebuild(); -} - -MIR_APP_DLL(int) Clist_ContactChangeGroup(MCONTACT hContact, MGROUP hGroup) -{ - CLISTGROUPCHANGE grpChg = {}; - - if (hGroup == 0) - db_unset(hContact, "CList", "Group"); - else { - grpChg.pszNewName = Clist_GroupGetName(hGroup, nullptr); - db_set_ws(hContact, "CList", "Group", grpChg.pszNewName); - } - - NotifyEventHooks(hGroupChangeEvent, hContact, (LPARAM)&grpChg); - return 0; -} - -int fnSetHideOffline(int iValue) -{ - if (iValue == -1) // invert the current value - iValue = !Clist::HideOffline; - - switch (iValue) { - case 0: - case 1: - Clist::HideOffline = iValue; - break; - - default: - return -1; - } - Clist_LoadContactTree(); - return iValue; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "clc.h"
+
+extern HANDLE hGroupChangeEvent;
+
+MIR_APP_DLL(void) Clist_LoadContactTree(void)
+{
+ bool hideOffline = Clist::HideOffline;
+ for (auto &hContact : Contacts()) {
+ int status = Contact::GetStatus(hContact);
+ if ((!hideOffline || status != ID_STATUS_OFFLINE) && !Contact::IsHidden(hContact))
+ Clist_ChangeContactIcon(hContact, g_clistApi.pfnIconFromStatusMode(Proto_GetBaseAccountName(hContact), status, hContact));
+ }
+ Clist_EndRebuild();
+}
+
+MIR_APP_DLL(int) Clist_ContactChangeGroup(MCONTACT hContact, MGROUP hGroup)
+{
+ CLISTGROUPCHANGE grpChg = {};
+
+ if (hGroup == 0)
+ db_unset(hContact, "CList", "Group");
+ else {
+ grpChg.pszNewName = Clist_GroupGetName(hGroup, nullptr);
+ db_set_ws(hContact, "CList", "Group", grpChg.pszNewName);
+ }
+
+ NotifyEventHooks(hGroupChangeEvent, hContact, (LPARAM)&grpChg);
+ return 0;
+}
+
+int fnSetHideOffline(int iValue)
+{
+ if (iValue == -1) // invert the current value
+ iValue = !Clist::HideOffline;
+
+ switch (iValue) {
+ case 0:
+ case 1:
+ Clist::HideOffline = iValue;
+ break;
+
+ default:
+ return -1;
+ }
+ Clist_LoadContactTree();
+ return iValue;
+}
diff --git a/src/mir_app/src/clcfiledrop.cpp b/src/mir_app/src/clcfiledrop.cpp index 25501d7dac..840a046eae 100644 --- a/src/mir_app/src/clcfiledrop.cpp +++ b/src/mir_app/src/clcfiledrop.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clcidents.cpp b/src/mir_app/src/clcidents.cpp index 98ca64a451..83497cf50d 100644 --- a/src/mir_app/src/clcidents.cpp +++ b/src/mir_app/src/clcidents.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clcitems.cpp b/src/mir_app/src/clcitems.cpp index 42c3f37d2f..eef6b57d56 100644 --- a/src/mir_app/src/clcitems.cpp +++ b/src/mir_app/src/clcitems.cpp @@ -1,730 +1,730 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "clc.h" - -static ClcCacheEntry nullpce = {}; - -// routines for managing adding/removal of items in the list, including sorting - -ClcContact* fnAddItemToGroup(ClcGroup *group, int iAboveItem) -{ - ClcContact* newItem = g_clistApi.pfnCreateClcContact(); - newItem->type = CLCIT_DIVIDER; - newItem->flags = 0; - newItem->szText[0] = '\0'; - memset(newItem->iExtraImage, 0xFF, sizeof(newItem->iExtraImage)); - group->cl.insert(newItem, iAboveItem); - return newItem; -} - -ClcGroup* fnAddGroup(HWND hwnd, ClcData *dat, const wchar_t *szName, uint32_t flags, int groupId, int calcTotalMembers) -{ - dat->bNeedsResort = true; - if (!(GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_USEGROUPS)) - return &dat->list; - - ClcGroup *group = &dat->list; - wchar_t *pNextField = NEWWSTR_ALLOCA(szName); - do { - wchar_t *pBackslash = wcschr(pNextField, '\\'), *pThisField = pNextField; - if (pBackslash == nullptr) { - pNextField = nullptr; - } - else { - *pBackslash = 0; - pNextField = pBackslash + 1; - } - - int i, compareResult = 1; - for (i = 0; i < group->cl.getCount(); i++) { - ClcContact *cc = group->cl[i]; - if (cc->type == CLCIT_CONTACT) - break; - if (cc->type != CLCIT_GROUP) - continue; - compareResult = mir_wstrcmp(pThisField, cc->szText); - if (compareResult == 0) { - if (pNextField == nullptr && flags != (uint32_t)-1) { - cc->groupId = (uint16_t)groupId; - group = cc->group; - group->expanded = (flags & GROUPF_EXPANDED) != 0; - group->hideOffline = (flags & GROUPF_HIDEOFFLINE) != 0; - group->groupId = groupId; - } - else group = cc->group; - break; - } - if (pNextField == nullptr && cc->groupId == 0) - break; - if (!(dat->exStyle & CLS_EX_SORTGROUPSALPHA) && groupId && cc->groupId > groupId) - break; - } - - if (compareResult) { // not found - if (groupId == 0) - return nullptr; - - ClcContact *cc = g_clistApi.pfnAddItemToGroup(group, i); - cc->type = CLCIT_GROUP; - mir_wstrncpy(cc->szText, pThisField, _countof(cc->szText)); - cc->groupId = (uint16_t)(pNextField ? 0 : groupId); - cc->group = new ClcGroup(10); - cc->group->parent = group; - group = cc->group; - - if (flags == (uint32_t)-1 || pNextField != nullptr) { - group->expanded = 0; - group->hideOffline = 0; - } - else { - group->expanded = (flags & GROUPF_EXPANDED) != 0; - group->hideOffline = (flags & GROUPF_HIDEOFFLINE) != 0; - } - group->groupId = pNextField ? 0 : groupId; - group->totalMembers = 0; - if (flags != (uint32_t)-1 && pNextField == nullptr && calcTotalMembers) { - uint32_t style = GetWindowLongPtr(hwnd, GWL_STYLE); - for (auto &hContact : Contacts()) { - ClcCacheEntry *cache = Clist_GetCacheEntry(hContact); - if (!mir_wstrcmp(cache->tszGroup, szName) && (style & CLS_SHOWHIDDEN || !cache->bIsHidden)) - group->totalMembers++; - } - } - } - } while (pNextField); - return group; -} - -void fnFreeContact(ClcContact* p) -{ - if (p->type == CLCIT_GROUP) { - FreeGroup(p->group); - delete p->group; p->group = nullptr; - } - - mir_free(p); -} - -void FreeGroup(ClcGroup *group) -{ - if (!group) - return; - - for (auto &it : group->cl) - g_clistApi.pfnFreeContact(it); - - group->cl.destroy(); -} - -static int iInfoItemUniqueHandle = 0; -ClcContact* fnAddInfoItemToGroup(ClcGroup *group, int flags, const wchar_t *pszText) -{ - int i = 0; - - if (flags & CLCIIF_BELOWCONTACTS) - i = group->cl.getCount(); - else if (flags & CLCIIF_BELOWGROUPS) { - for (; i < group->cl.getCount(); i++) - if (group->cl[i]->type == CLCIT_CONTACT) - break; - } - else - for (; i < group->cl.getCount(); i++) - if (group->cl[i]->type != CLCIT_INFO) - break; - - ClcContact *cc = g_clistApi.pfnAddItemToGroup(group, i); - iInfoItemUniqueHandle = LOWORD(iInfoItemUniqueHandle + 1); - if (iInfoItemUniqueHandle == 0) - ++iInfoItemUniqueHandle; - cc->type = CLCIT_INFO; - cc->flags = (uint8_t)flags; - cc->hContact = (MCONTACT)++iInfoItemUniqueHandle; - mir_wstrncpy(cc->szText, pszText, _countof(cc->szText)); - return cc; -} - -ClcContact* fnAddContactToGroup(ClcData *dat, ClcGroup *group, MCONTACT hContact) -{ - int i, index = -1; - - dat->bNeedsResort = true; - for (i = group->cl.getCount() - 1; i >= 0; i--) { - ClcContact *cc = group->cl[i]; - if (cc->hContact == hContact) - return cc; - - if (index == -1) - if (cc->type != CLCIT_INFO || !(cc->flags & CLCIIF_BELOWCONTACTS)) - index = i; - } - - char *szProto = Proto_GetBaseAccountName(hContact); - - ClcCacheEntry *pce = Clist_GetCacheEntry(hContact); - replaceStrW(pce->tszGroup, nullptr); - - ClcContact *cc = g_clistApi.pfnAddItemToGroup(group, index + 1); - cc->type = CLCIT_CONTACT; - cc->iImage = Clist_GetContactIcon(hContact); - cc->hContact = hContact; - cc->pce = pce; - if (szProto != nullptr && !Clist_IsHiddenMode(dat, db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE))) - cc->flags |= CONTACTF_ONLINE; - uint16_t apparentMode = szProto != nullptr ? db_get_w(hContact, szProto, "ApparentMode", 0) : 0; - if (apparentMode == ID_STATUS_OFFLINE) - cc->flags |= CONTACTF_INVISTO; - else if (apparentMode == ID_STATUS_ONLINE) - cc->flags |= CONTACTF_VISTO; - else if (apparentMode) - cc->flags |= CONTACTF_VISTO | CONTACTF_INVISTO; - if (!Contact::OnList(hContact)) - cc->flags |= CONTACTF_NOTONLIST; - uint32_t idleMode = szProto != nullptr ? db_get_dw(hContact, szProto, "IdleTS", 0) : 0; - if (idleMode) - cc->flags |= CONTACTF_IDLE; - mir_wstrncpy(cc->szText, Clist_GetContactDisplayName(hContact), _countof(cc->szText)); - return cc; -} - -void fnAddContactToTree(HWND hwnd, ClcData *dat, MCONTACT hContact, int updateTotalCount, int checkHideOffline) -{ - uint32_t style = GetWindowLongPtr(hwnd, GWL_STYLE); - uint16_t status = ID_STATUS_OFFLINE; - char *szProto = Proto_GetBaseAccountName(hContact); - - dat->bNeedsResort = true; - if (style & CLS_NOHIDEOFFLINE) - checkHideOffline = 0; - if (checkHideOffline) - if (szProto != nullptr) - status = db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE); - - int i; - uint32_t groupFlags; - ClcGroup *group; - ptrW tszGroup(Clist_GetGroup(hContact)); - if (tszGroup == nullptr) - group = &dat->list; - else { - group = g_clistApi.pfnAddGroup(hwnd, dat, tszGroup, (uint32_t)-1, 0, 0); - if (group == nullptr) { - if (!(style & CLS_HIDEEMPTYGROUPS)) - return; - - if (checkHideOffline && Clist_IsHiddenMode(dat, status)) { - for (i = 1;; i++) { - wchar_t *szGroupName = Clist_GroupGetName(i, &groupFlags); - if (szGroupName == nullptr) - return; - - if (!mir_wstrcmp(szGroupName, tszGroup)) - break; - } - if (groupFlags & GROUPF_HIDEOFFLINE) - return; - } - for (i = 1;; i++) { - wchar_t *szGroupName = Clist_GroupGetName(i, &groupFlags); - if (szGroupName == nullptr) - return; - - if (!mir_wstrcmp(szGroupName, tszGroup)) - break; - - size_t len = mir_wstrlen(szGroupName); - if (!wcsncmp(szGroupName, tszGroup, len) && tszGroup[len] == '\\') - g_clistApi.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 1); - } - group = g_clistApi.pfnAddGroup(hwnd, dat, tszGroup, groupFlags, i, 1); - } - } - - if (checkHideOffline) { - if (Clist_IsHiddenMode(dat, status) && (style & CLS_HIDEOFFLINE || group->hideOffline)) { - if (updateTotalCount) - group->totalMembers++; - return; - } - } - g_clistApi.pfnAddContactToGroup(dat, group, hContact); - if (updateTotalCount) - group->totalMembers++; -} - -MIR_APP_DLL(ClcGroup*) Clist_RemoveItemFromGroup(HWND hwnd, ClcGroup *group, ClcContact *contact, int updateTotalCount) -{ - int iContact = group->cl.indexOf(contact); - if (iContact == -1) - return group; - - if (contact->type == CLCIT_CONTACT) { - if (updateTotalCount) - group->totalMembers--; - - g_clistApi.pfnInvalidateDisplayNameCacheEntry(contact->hContact); - } - - g_clistApi.pfnFreeContact(group->cl[iContact]); - group->cl.remove(iContact); - - if ((GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_HIDEEMPTYGROUPS) && group->cl.getCount() == 0 && group->parent != nullptr) - for (auto &cc : group->parent->cl) - if (cc->type == CLCIT_GROUP && cc->groupId == group->groupId) - return Clist_RemoveItemFromGroup(hwnd, group->parent, cc, 0); - - return group; -} - -MIR_APP_DLL(void) Clist_DeleteItemFromTree(HWND hwnd, MCONTACT hItem) -{ - ClcData *dat = (ClcData*)GetWindowLongPtr(hwnd, 0); - dat->bNeedsResort = true; - - // if a contact is found in our contact list, remove it from its group and detach from cache - ClcGroup *group; - ClcContact *contact; - if (Clist_FindItem(hwnd, dat, hItem, &contact, &group)) { - Clist_RemoveItemFromGroup(hwnd, group, contact, 1); - contact->pce = &nullpce; - return; - } - - // if we don't have this contact, simply try to update the number of contacts in a group - if (!IsHContactContact(hItem)) - return; - - ptrW wszGroup(Clist_GetGroup(hItem)); - if (wszGroup == nullptr) - return; - - // decrease member counts of all parent groups too - group = &dat->list; - int nameOffset = 0; - for (int i = 0;; i++) { - if (group->scanIndex == group->cl.getCount()) - break; - - ClcContact *cc = group->cl[i]; - if (cc->type == CLCIT_GROUP) { - size_t len = mir_wstrlen(cc->szText); - if (!wcsncmp(cc->szText, wszGroup.get() + nameOffset, len) && (wszGroup[nameOffset + len] == '\\' || wszGroup[nameOffset + len] == '\0')) { - group->totalMembers--; - if (wszGroup[nameOffset + len] == '\0') - break; - } - } - } -} - -int fnGetContactHiddenStatus(MCONTACT hContact, char*, ClcData*) -{ - return Contact::IsHidden(hContact); -} - -void fnRebuildEntireList(HWND hwnd, ClcData *dat) -{ - uint32_t style = GetWindowLongPtr(hwnd, GWL_STYLE); - - dat->list.expanded = 1; - dat->list.hideOffline = db_get_b(0, "CLC", "HideOfflineRoot", 0) && (style & CLS_USEGROUPS); - dat->list.cl.destroy(); - dat->list.totalMembers = 0; - dat->selection = -1; - - for (int i = 1;; i++) { - uint32_t groupFlags; - wchar_t *szGroupName = Clist_GroupGetName(i, &groupFlags); - if (szGroupName == nullptr) - break; - g_clistApi.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 0); - } - - for (auto &hContact : Contacts()) { - int nHiddenStatus = g_clistApi.pfnGetContactHiddenStatus(hContact, nullptr, dat); - if (((style & CLS_SHOWHIDDEN) && nHiddenStatus != -1) || !nHiddenStatus) { - ClcCacheEntry *pce = Clist_GetCacheEntry(hContact); - if (pce->szProto == nullptr) - continue; - - ClcGroup *group; - ptrW tszGroupName(Clist_GetGroup(hContact)); - if (tszGroupName == nullptr) - group = &dat->list; - else { - group = g_clistApi.pfnAddGroup(hwnd, dat, tszGroupName, (uint32_t)-1, 0, 0); - if (group == nullptr && style & CLS_SHOWHIDDEN) - group = &dat->list; - } - - if (group != nullptr) { - group->totalMembers++; - - if (dat->bFilterSearch && dat->szQuickSearch[0] != '\0') { - wchar_t *name = Clist_GetContactDisplayName(hContact); - wchar_t *lowered_name = CharLowerW(NEWWSTR_ALLOCA(name)); - wchar_t *lowered_search = CharLowerW(NEWWSTR_ALLOCA(dat->szQuickSearch)); - - if (wcsstr(lowered_name, lowered_search)) - g_clistApi.pfnAddContactToGroup(dat, group, hContact); - } - else if (!(style & CLS_NOHIDEOFFLINE) && (style & CLS_HIDEOFFLINE || group->hideOffline)) { - char *szProto = Proto_GetBaseAccountName(hContact); - if (szProto == nullptr) { - if (!Clist_IsHiddenMode(dat, ID_STATUS_OFFLINE) || g_clistApi.pfnIsVisibleContact(pce, group)) - g_clistApi.pfnAddContactToGroup(dat, group, hContact); - } - else if (!Clist_IsHiddenMode(dat, db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE)) || g_clistApi.pfnIsVisibleContact(pce, group)) - g_clistApi.pfnAddContactToGroup(dat, group, hContact); - } - else g_clistApi.pfnAddContactToGroup(dat, group, hContact); - } - } - } - - if (style & CLS_HIDEEMPTYGROUPS) { - ClcGroup *group = &dat->list; - group->scanIndex = 0; - for (;;) { - if (group->scanIndex == group->cl.getCount()) { - if ((group = group->parent) == nullptr) - break; - group->scanIndex++; - continue; - } - - ClcContact *cc = group->cl[group->scanIndex]; - if (cc->type == CLCIT_GROUP) { - if (cc->group->cl.getCount() == 0) { - group = Clist_RemoveItemFromGroup(hwnd, group, cc, 0); - } - else { - group = cc->group; - group->scanIndex = 0; - } - continue; - } - group->scanIndex++; - } - } - - g_clistApi.pfnSortCLC(hwnd, dat, 0); - ExtraIcon_SetAll(); -} - -int fnGetGroupContentsCount(ClcGroup *group, int visibleOnly) -{ - int count = group->cl.getCount(); - ClcGroup *topgroup = group; - - group->scanIndex = 0; - for (;;) { - if (group->scanIndex == group->cl.getCount()) { - if (group == topgroup) - break; - group = group->parent; - group->scanIndex++; - continue; - } - - ClcContact *cc = group->cl[group->scanIndex]; - if (cc->type == CLCIT_GROUP && (!visibleOnly || cc->group->expanded)) { - group = cc->group; - group->scanIndex = 0; - count += group->cl.getCount(); - continue; - } - group->scanIndex++; - } - return count; -} - -static int __cdecl GroupSortProc(const void* p1, const void* p2) -{ - ClcContact **contact1 = (ClcContact**)p1, **contact2 = (ClcContact**)p2; - - return mir_wstrcmpi(contact1[0]->szText, contact2[0]->szText); -} - -static int __cdecl ContactSortProc(const void* p1, const void* p2) -{ - ClcContact **contact1 = (ClcContact**)p1, **contact2 = (ClcContact**)p2; - - int result = g_clistApi.pfnCompareContacts(contact1[0], contact2[0]); - if (result) - return result; - //nothing to distinguish them, so make sure they stay in the same order - return (int)((INT_PTR)contact2[0]->hContact - (INT_PTR)contact1[0]->hContact); -} - -static void InsertionSort(ClcContact **pContactArray, int nArray, int(*CompareProc) (const void *, const void *)) -{ - int i, j; - ClcContact* testElement; - - for (i = 1; i < nArray; i++) { - if (CompareProc(&pContactArray[i - 1], &pContactArray[i]) > 0) { - testElement = pContactArray[i]; - for (j = i - 2; j >= 0; j--) - if (CompareProc(&pContactArray[j], &testElement) <= 0) - break; - j++; - memmove(&pContactArray[j + 1], &pContactArray[j], sizeof(void*) * (i - j)); - pContactArray[j] = testElement; - } - } -} - -static void SortGroup(ClcData *dat, ClcGroup *group, int useInsertionSort) -{ - int i, sortCount; - - for (i = group->cl.getCount() - 1; i >= 0; i--) { - if (group->cl[i]->type == CLCIT_DIVIDER) { - mir_free(group->cl[i]); - group->cl.remove(i); - } - } - - for (i = 0; i < group->cl.getCount(); i++) - if (group->cl[i]->type != CLCIT_INFO) - break; - if (i > group->cl.getCount() - 2) - return; - if (group->cl[i]->type == CLCIT_GROUP) { - if (dat->exStyle & CLS_EX_SORTGROUPSALPHA) { - for (sortCount = 0; i + sortCount < group->cl.getCount(); sortCount++) - if (group->cl[i + sortCount]->type != CLCIT_GROUP) - break; - qsort(group->cl.getArray() + i, sortCount, sizeof(void*), GroupSortProc); - i = i + sortCount; - } - for (; i < group->cl.getCount(); i++) - if (group->cl[i]->type == CLCIT_CONTACT) - break; - if (group->cl.getCount() - i < 2) - return; - } - for (sortCount = 0; i + sortCount < group->cl.getCount(); sortCount++) - if (group->cl[i + sortCount]->type != CLCIT_CONTACT) - break; - if (useInsertionSort) - InsertionSort(group->cl.getArray() + i, sortCount, ContactSortProc); - else - qsort(group->cl.getArray() + i, sortCount, sizeof(void*), ContactSortProc); - if (dat->exStyle & CLS_EX_DIVIDERONOFF) { - int prevContactOnline = 0; - for (i = 0; i < group->cl.getCount(); i++) { - if (group->cl[i]->type != CLCIT_CONTACT) - continue; - if (group->cl[i]->flags & CONTACTF_ONLINE) - prevContactOnline = 1; - else { - if (prevContactOnline) { - ClcContact *cc = g_clistApi.pfnAddItemToGroup(group, i); - cc->type = CLCIT_DIVIDER; - mir_wstrcpy(cc->szText, TranslateT("Offline")); - } - break; - } - } - } -} - -void fnSortCLC(HWND hwnd, ClcData *dat, int useInsertionSort) -{ - ClcGroup *group = &dat->list; - - if (dat->bNeedsResort) { - MCONTACT hSelItem; - ClcContact *selcontact; - if (g_clistApi.pfnGetRowByIndex(dat, dat->selection, &selcontact, nullptr) == -1) - hSelItem = 0; - else - hSelItem = Clist_ContactToHItem(selcontact); - group->scanIndex = 0; - SortGroup(dat, group, useInsertionSort); - for (;;) { - if (group->scanIndex == group->cl.getCount()) { - if ((group = group->parent) == nullptr) - break; - group->scanIndex++; - continue; - } - - ClcContact *cc = group->cl[group->scanIndex]; - if (cc->type == CLCIT_GROUP) { - group = cc->group; - group->scanIndex = 0; - SortGroup(dat, group, useInsertionSort); - continue; - } - group->scanIndex++; - } - - if (hSelItem) { - ClcGroup *selgroup; - if (Clist_FindItem(hwnd, dat, hSelItem, &selcontact, &selgroup)) - dat->selection = g_clistApi.pfnGetRowsPriorTo(&dat->list, selgroup, selgroup->cl.indexOf(selcontact)); - } - - g_clistApi.pfnRecalcScrollBar(hwnd, dat); - } - dat->bNeedsResort = false; - g_clistApi.pfnInvalidateRect(hwnd, nullptr, FALSE); -} - -struct SavedContactState_t -{ - MCONTACT hContact; - uint16_t iExtraImage[EXTRA_ICON_COUNT]; - int checked; -}; - -struct SavedGroupState_t -{ - int groupId, expanded; -}; - -struct SavedInfoState_t -{ - int parentId; - ClcContact contact; -}; - -MIR_APP_DLL(void) Clist_SaveStateAndRebuildList(HWND hwnd, ClcData *dat) -{ - Clist_HideInfoTip(dat); - KillTimer(hwnd, TIMERID_INFOTIP); - KillTimer(hwnd, TIMERID_RENAME); - Clist_EndRename(dat, 1); - - dat->bLockScrollbar = true; - - OBJLIST<SavedContactState_t> saveContact(10, NumericKeySortT); - OBJLIST<SavedGroupState_t> saveGroup(100, NumericKeySortT); - OBJLIST<SavedInfoState_t> saveInfo(10, NumericKeySortT); - - dat->bNeedsResort = true; - ClcGroup *group = &dat->list; - group->scanIndex = 0; - for (;;) { - if (group->scanIndex == group->cl.getCount()) { - if ((group = group->parent) == nullptr) - break; - group->scanIndex++; - continue; - } - - ClcContact *cc = group->cl[group->scanIndex]; - if (cc->type == CLCIT_GROUP) { - group = cc->group; - group->scanIndex = 0; - - SavedGroupState_t *p = new SavedGroupState_t; - p->groupId = group->groupId; - p->expanded = group->expanded; - saveGroup.insert(p); - continue; - } - else if (cc->type == CLCIT_CONTACT) { - SavedContactState_t *p = new SavedContactState_t; - p->hContact = cc->hContact; - memcpy(p->iExtraImage, cc->iExtraImage, sizeof(p->iExtraImage)); - p->checked = cc->flags & CONTACTF_CHECKED; - saveContact.insert(p); - } - else if (cc->type == CLCIT_INFO) { - SavedInfoState_t *p = new SavedInfoState_t; - p->parentId = (group->parent == nullptr) ? -1 : group->groupId; - p->contact = *cc; - saveInfo.insert(p); - } - group->scanIndex++; - } - - FreeGroup(&dat->list); - g_clistApi.pfnRebuildEntireList(hwnd, dat); - - group = &dat->list; - group->scanIndex = 0; - for (;;) { - if (group->scanIndex == group->cl.getCount()) { - if ((group = group->parent) == nullptr) - break; - group->scanIndex++; - continue; - } - - ClcContact *cc = group->cl[group->scanIndex]; - if (cc->type == CLCIT_GROUP) { - group = cc->group; - group->scanIndex = 0; - - SavedGroupState_t tmp, *p; - tmp.groupId = group->groupId; - if ((p = saveGroup.find(&tmp)) != nullptr) - group->expanded = p->expanded; - continue; - } - else if (cc->type == CLCIT_CONTACT) { - SavedContactState_t tmp, *p; - tmp.hContact = cc->hContact; - if ((p = saveContact.find(&tmp)) != nullptr) { - memcpy(cc->iExtraImage, p->iExtraImage, sizeof(p->iExtraImage)); - if (p->checked) - cc->flags |= CONTACTF_CHECKED; - } - } - - group->scanIndex++; - } - - for (auto &it : saveInfo) { - if (it->parentId == -1) - group = &dat->list; - else { - ClcContact *contact; - if (!Clist_FindItem(hwnd, dat, it->parentId | HCONTACT_ISGROUP, &contact)) - continue; - group = contact->group; - } - - ClcContact *cc = g_clistApi.pfnAddInfoItemToGroup(group, it->contact.flags, L""); - *cc = it->contact; - } - - dat->bLockScrollbar = false; - Clist_RecalculateGroupCheckboxes(dat); - - g_clistApi.pfnRecalcScrollBar(hwnd, dat); - - NMCLISTCONTROL nm; - nm.hdr.code = CLN_LISTREBUILT; - nm.hdr.hwndFrom = hwnd; - nm.hdr.idFrom = GetDlgCtrlID(hwnd); - SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "clc.h"
+
+static ClcCacheEntry nullpce = {};
+
+// routines for managing adding/removal of items in the list, including sorting
+
+ClcContact* fnAddItemToGroup(ClcGroup *group, int iAboveItem)
+{
+ ClcContact* newItem = g_clistApi.pfnCreateClcContact();
+ newItem->type = CLCIT_DIVIDER;
+ newItem->flags = 0;
+ newItem->szText[0] = '\0';
+ memset(newItem->iExtraImage, 0xFF, sizeof(newItem->iExtraImage));
+ group->cl.insert(newItem, iAboveItem);
+ return newItem;
+}
+
+ClcGroup* fnAddGroup(HWND hwnd, ClcData *dat, const wchar_t *szName, uint32_t flags, int groupId, int calcTotalMembers)
+{
+ dat->bNeedsResort = true;
+ if (!(GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_USEGROUPS))
+ return &dat->list;
+
+ ClcGroup *group = &dat->list;
+ wchar_t *pNextField = NEWWSTR_ALLOCA(szName);
+ do {
+ wchar_t *pBackslash = wcschr(pNextField, '\\'), *pThisField = pNextField;
+ if (pBackslash == nullptr) {
+ pNextField = nullptr;
+ }
+ else {
+ *pBackslash = 0;
+ pNextField = pBackslash + 1;
+ }
+
+ int i, compareResult = 1;
+ for (i = 0; i < group->cl.getCount(); i++) {
+ ClcContact *cc = group->cl[i];
+ if (cc->type == CLCIT_CONTACT)
+ break;
+ if (cc->type != CLCIT_GROUP)
+ continue;
+ compareResult = mir_wstrcmp(pThisField, cc->szText);
+ if (compareResult == 0) {
+ if (pNextField == nullptr && flags != (uint32_t)-1) {
+ cc->groupId = (uint16_t)groupId;
+ group = cc->group;
+ group->expanded = (flags & GROUPF_EXPANDED) != 0;
+ group->hideOffline = (flags & GROUPF_HIDEOFFLINE) != 0;
+ group->groupId = groupId;
+ }
+ else group = cc->group;
+ break;
+ }
+ if (pNextField == nullptr && cc->groupId == 0)
+ break;
+ if (!(dat->exStyle & CLS_EX_SORTGROUPSALPHA) && groupId && cc->groupId > groupId)
+ break;
+ }
+
+ if (compareResult) { // not found
+ if (groupId == 0)
+ return nullptr;
+
+ ClcContact *cc = g_clistApi.pfnAddItemToGroup(group, i);
+ cc->type = CLCIT_GROUP;
+ mir_wstrncpy(cc->szText, pThisField, _countof(cc->szText));
+ cc->groupId = (uint16_t)(pNextField ? 0 : groupId);
+ cc->group = new ClcGroup(10);
+ cc->group->parent = group;
+ group = cc->group;
+
+ if (flags == (uint32_t)-1 || pNextField != nullptr) {
+ group->expanded = 0;
+ group->hideOffline = 0;
+ }
+ else {
+ group->expanded = (flags & GROUPF_EXPANDED) != 0;
+ group->hideOffline = (flags & GROUPF_HIDEOFFLINE) != 0;
+ }
+ group->groupId = pNextField ? 0 : groupId;
+ group->totalMembers = 0;
+ if (flags != (uint32_t)-1 && pNextField == nullptr && calcTotalMembers) {
+ uint32_t style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ for (auto &hContact : Contacts()) {
+ ClcCacheEntry *cache = Clist_GetCacheEntry(hContact);
+ if (!mir_wstrcmp(cache->tszGroup, szName) && (style & CLS_SHOWHIDDEN || !cache->bIsHidden))
+ group->totalMembers++;
+ }
+ }
+ }
+ } while (pNextField);
+ return group;
+}
+
+void fnFreeContact(ClcContact* p)
+{
+ if (p->type == CLCIT_GROUP) {
+ FreeGroup(p->group);
+ delete p->group; p->group = nullptr;
+ }
+
+ mir_free(p);
+}
+
+void FreeGroup(ClcGroup *group)
+{
+ if (!group)
+ return;
+
+ for (auto &it : group->cl)
+ g_clistApi.pfnFreeContact(it);
+
+ group->cl.destroy();
+}
+
+static int iInfoItemUniqueHandle = 0;
+ClcContact* fnAddInfoItemToGroup(ClcGroup *group, int flags, const wchar_t *pszText)
+{
+ int i = 0;
+
+ if (flags & CLCIIF_BELOWCONTACTS)
+ i = group->cl.getCount();
+ else if (flags & CLCIIF_BELOWGROUPS) {
+ for (; i < group->cl.getCount(); i++)
+ if (group->cl[i]->type == CLCIT_CONTACT)
+ break;
+ }
+ else
+ for (; i < group->cl.getCount(); i++)
+ if (group->cl[i]->type != CLCIT_INFO)
+ break;
+
+ ClcContact *cc = g_clistApi.pfnAddItemToGroup(group, i);
+ iInfoItemUniqueHandle = LOWORD(iInfoItemUniqueHandle + 1);
+ if (iInfoItemUniqueHandle == 0)
+ ++iInfoItemUniqueHandle;
+ cc->type = CLCIT_INFO;
+ cc->flags = (uint8_t)flags;
+ cc->hContact = (MCONTACT)++iInfoItemUniqueHandle;
+ mir_wstrncpy(cc->szText, pszText, _countof(cc->szText));
+ return cc;
+}
+
+ClcContact* fnAddContactToGroup(ClcData *dat, ClcGroup *group, MCONTACT hContact)
+{
+ int i, index = -1;
+
+ dat->bNeedsResort = true;
+ for (i = group->cl.getCount() - 1; i >= 0; i--) {
+ ClcContact *cc = group->cl[i];
+ if (cc->hContact == hContact)
+ return cc;
+
+ if (index == -1)
+ if (cc->type != CLCIT_INFO || !(cc->flags & CLCIIF_BELOWCONTACTS))
+ index = i;
+ }
+
+ char *szProto = Proto_GetBaseAccountName(hContact);
+
+ ClcCacheEntry *pce = Clist_GetCacheEntry(hContact);
+ replaceStrW(pce->tszGroup, nullptr);
+
+ ClcContact *cc = g_clistApi.pfnAddItemToGroup(group, index + 1);
+ cc->type = CLCIT_CONTACT;
+ cc->iImage = Clist_GetContactIcon(hContact);
+ cc->hContact = hContact;
+ cc->pce = pce;
+ if (szProto != nullptr && !Clist_IsHiddenMode(dat, db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE)))
+ cc->flags |= CONTACTF_ONLINE;
+ uint16_t apparentMode = szProto != nullptr ? db_get_w(hContact, szProto, "ApparentMode", 0) : 0;
+ if (apparentMode == ID_STATUS_OFFLINE)
+ cc->flags |= CONTACTF_INVISTO;
+ else if (apparentMode == ID_STATUS_ONLINE)
+ cc->flags |= CONTACTF_VISTO;
+ else if (apparentMode)
+ cc->flags |= CONTACTF_VISTO | CONTACTF_INVISTO;
+ if (!Contact::OnList(hContact))
+ cc->flags |= CONTACTF_NOTONLIST;
+ uint32_t idleMode = szProto != nullptr ? db_get_dw(hContact, szProto, "IdleTS", 0) : 0;
+ if (idleMode)
+ cc->flags |= CONTACTF_IDLE;
+ mir_wstrncpy(cc->szText, Clist_GetContactDisplayName(hContact), _countof(cc->szText));
+ return cc;
+}
+
+void fnAddContactToTree(HWND hwnd, ClcData *dat, MCONTACT hContact, int updateTotalCount, int checkHideOffline)
+{
+ uint32_t style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ uint16_t status = ID_STATUS_OFFLINE;
+ char *szProto = Proto_GetBaseAccountName(hContact);
+
+ dat->bNeedsResort = true;
+ if (style & CLS_NOHIDEOFFLINE)
+ checkHideOffline = 0;
+ if (checkHideOffline)
+ if (szProto != nullptr)
+ status = db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE);
+
+ int i;
+ uint32_t groupFlags;
+ ClcGroup *group;
+ ptrW tszGroup(Clist_GetGroup(hContact));
+ if (tszGroup == nullptr)
+ group = &dat->list;
+ else {
+ group = g_clistApi.pfnAddGroup(hwnd, dat, tszGroup, (uint32_t)-1, 0, 0);
+ if (group == nullptr) {
+ if (!(style & CLS_HIDEEMPTYGROUPS))
+ return;
+
+ if (checkHideOffline && Clist_IsHiddenMode(dat, status)) {
+ for (i = 1;; i++) {
+ wchar_t *szGroupName = Clist_GroupGetName(i, &groupFlags);
+ if (szGroupName == nullptr)
+ return;
+
+ if (!mir_wstrcmp(szGroupName, tszGroup))
+ break;
+ }
+ if (groupFlags & GROUPF_HIDEOFFLINE)
+ return;
+ }
+ for (i = 1;; i++) {
+ wchar_t *szGroupName = Clist_GroupGetName(i, &groupFlags);
+ if (szGroupName == nullptr)
+ return;
+
+ if (!mir_wstrcmp(szGroupName, tszGroup))
+ break;
+
+ size_t len = mir_wstrlen(szGroupName);
+ if (!wcsncmp(szGroupName, tszGroup, len) && tszGroup[len] == '\\')
+ g_clistApi.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 1);
+ }
+ group = g_clistApi.pfnAddGroup(hwnd, dat, tszGroup, groupFlags, i, 1);
+ }
+ }
+
+ if (checkHideOffline) {
+ if (Clist_IsHiddenMode(dat, status) && (style & CLS_HIDEOFFLINE || group->hideOffline)) {
+ if (updateTotalCount)
+ group->totalMembers++;
+ return;
+ }
+ }
+ g_clistApi.pfnAddContactToGroup(dat, group, hContact);
+ if (updateTotalCount)
+ group->totalMembers++;
+}
+
+MIR_APP_DLL(ClcGroup*) Clist_RemoveItemFromGroup(HWND hwnd, ClcGroup *group, ClcContact *contact, int updateTotalCount)
+{
+ int iContact = group->cl.indexOf(contact);
+ if (iContact == -1)
+ return group;
+
+ if (contact->type == CLCIT_CONTACT) {
+ if (updateTotalCount)
+ group->totalMembers--;
+
+ g_clistApi.pfnInvalidateDisplayNameCacheEntry(contact->hContact);
+ }
+
+ g_clistApi.pfnFreeContact(group->cl[iContact]);
+ group->cl.remove(iContact);
+
+ if ((GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_HIDEEMPTYGROUPS) && group->cl.getCount() == 0 && group->parent != nullptr)
+ for (auto &cc : group->parent->cl)
+ if (cc->type == CLCIT_GROUP && cc->groupId == group->groupId)
+ return Clist_RemoveItemFromGroup(hwnd, group->parent, cc, 0);
+
+ return group;
+}
+
+MIR_APP_DLL(void) Clist_DeleteItemFromTree(HWND hwnd, MCONTACT hItem)
+{
+ ClcData *dat = (ClcData*)GetWindowLongPtr(hwnd, 0);
+ dat->bNeedsResort = true;
+
+ // if a contact is found in our contact list, remove it from its group and detach from cache
+ ClcGroup *group;
+ ClcContact *contact;
+ if (Clist_FindItem(hwnd, dat, hItem, &contact, &group)) {
+ Clist_RemoveItemFromGroup(hwnd, group, contact, 1);
+ contact->pce = &nullpce;
+ return;
+ }
+
+ // if we don't have this contact, simply try to update the number of contacts in a group
+ if (!IsHContactContact(hItem))
+ return;
+
+ ptrW wszGroup(Clist_GetGroup(hItem));
+ if (wszGroup == nullptr)
+ return;
+
+ // decrease member counts of all parent groups too
+ group = &dat->list;
+ int nameOffset = 0;
+ for (int i = 0;; i++) {
+ if (group->scanIndex == group->cl.getCount())
+ break;
+
+ ClcContact *cc = group->cl[i];
+ if (cc->type == CLCIT_GROUP) {
+ size_t len = mir_wstrlen(cc->szText);
+ if (!wcsncmp(cc->szText, wszGroup.get() + nameOffset, len) && (wszGroup[nameOffset + len] == '\\' || wszGroup[nameOffset + len] == '\0')) {
+ group->totalMembers--;
+ if (wszGroup[nameOffset + len] == '\0')
+ break;
+ }
+ }
+ }
+}
+
+int fnGetContactHiddenStatus(MCONTACT hContact, char*, ClcData*)
+{
+ return Contact::IsHidden(hContact);
+}
+
+void fnRebuildEntireList(HWND hwnd, ClcData *dat)
+{
+ uint32_t style = GetWindowLongPtr(hwnd, GWL_STYLE);
+
+ dat->list.expanded = 1;
+ dat->list.hideOffline = db_get_b(0, "CLC", "HideOfflineRoot", 0) && (style & CLS_USEGROUPS);
+ dat->list.cl.destroy();
+ dat->list.totalMembers = 0;
+ dat->selection = -1;
+
+ for (int i = 1;; i++) {
+ uint32_t groupFlags;
+ wchar_t *szGroupName = Clist_GroupGetName(i, &groupFlags);
+ if (szGroupName == nullptr)
+ break;
+ g_clistApi.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 0);
+ }
+
+ for (auto &hContact : Contacts()) {
+ int nHiddenStatus = g_clistApi.pfnGetContactHiddenStatus(hContact, nullptr, dat);
+ if (((style & CLS_SHOWHIDDEN) && nHiddenStatus != -1) || !nHiddenStatus) {
+ ClcCacheEntry *pce = Clist_GetCacheEntry(hContact);
+ if (pce->szProto == nullptr)
+ continue;
+
+ ClcGroup *group;
+ ptrW tszGroupName(Clist_GetGroup(hContact));
+ if (tszGroupName == nullptr)
+ group = &dat->list;
+ else {
+ group = g_clistApi.pfnAddGroup(hwnd, dat, tszGroupName, (uint32_t)-1, 0, 0);
+ if (group == nullptr && style & CLS_SHOWHIDDEN)
+ group = &dat->list;
+ }
+
+ if (group != nullptr) {
+ group->totalMembers++;
+
+ if (dat->bFilterSearch && dat->szQuickSearch[0] != '\0') {
+ wchar_t *name = Clist_GetContactDisplayName(hContact);
+ wchar_t *lowered_name = CharLowerW(NEWWSTR_ALLOCA(name));
+ wchar_t *lowered_search = CharLowerW(NEWWSTR_ALLOCA(dat->szQuickSearch));
+
+ if (wcsstr(lowered_name, lowered_search))
+ g_clistApi.pfnAddContactToGroup(dat, group, hContact);
+ }
+ else if (!(style & CLS_NOHIDEOFFLINE) && (style & CLS_HIDEOFFLINE || group->hideOffline)) {
+ char *szProto = Proto_GetBaseAccountName(hContact);
+ if (szProto == nullptr) {
+ if (!Clist_IsHiddenMode(dat, ID_STATUS_OFFLINE) || g_clistApi.pfnIsVisibleContact(pce, group))
+ g_clistApi.pfnAddContactToGroup(dat, group, hContact);
+ }
+ else if (!Clist_IsHiddenMode(dat, db_get_w(hContact, szProto, "Status", ID_STATUS_OFFLINE)) || g_clistApi.pfnIsVisibleContact(pce, group))
+ g_clistApi.pfnAddContactToGroup(dat, group, hContact);
+ }
+ else g_clistApi.pfnAddContactToGroup(dat, group, hContact);
+ }
+ }
+ }
+
+ if (style & CLS_HIDEEMPTYGROUPS) {
+ ClcGroup *group = &dat->list;
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.getCount()) {
+ if ((group = group->parent) == nullptr)
+ break;
+ group->scanIndex++;
+ continue;
+ }
+
+ ClcContact *cc = group->cl[group->scanIndex];
+ if (cc->type == CLCIT_GROUP) {
+ if (cc->group->cl.getCount() == 0) {
+ group = Clist_RemoveItemFromGroup(hwnd, group, cc, 0);
+ }
+ else {
+ group = cc->group;
+ group->scanIndex = 0;
+ }
+ continue;
+ }
+ group->scanIndex++;
+ }
+ }
+
+ g_clistApi.pfnSortCLC(hwnd, dat, 0);
+ ExtraIcon_SetAll();
+}
+
+int fnGetGroupContentsCount(ClcGroup *group, int visibleOnly)
+{
+ int count = group->cl.getCount();
+ ClcGroup *topgroup = group;
+
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.getCount()) {
+ if (group == topgroup)
+ break;
+ group = group->parent;
+ group->scanIndex++;
+ continue;
+ }
+
+ ClcContact *cc = group->cl[group->scanIndex];
+ if (cc->type == CLCIT_GROUP && (!visibleOnly || cc->group->expanded)) {
+ group = cc->group;
+ group->scanIndex = 0;
+ count += group->cl.getCount();
+ continue;
+ }
+ group->scanIndex++;
+ }
+ return count;
+}
+
+static int __cdecl GroupSortProc(const void* p1, const void* p2)
+{
+ ClcContact **contact1 = (ClcContact**)p1, **contact2 = (ClcContact**)p2;
+
+ return mir_wstrcmpi(contact1[0]->szText, contact2[0]->szText);
+}
+
+static int __cdecl ContactSortProc(const void* p1, const void* p2)
+{
+ ClcContact **contact1 = (ClcContact**)p1, **contact2 = (ClcContact**)p2;
+
+ int result = g_clistApi.pfnCompareContacts(contact1[0], contact2[0]);
+ if (result)
+ return result;
+ //nothing to distinguish them, so make sure they stay in the same order
+ return (int)((INT_PTR)contact2[0]->hContact - (INT_PTR)contact1[0]->hContact);
+}
+
+static void InsertionSort(ClcContact **pContactArray, int nArray, int(*CompareProc) (const void *, const void *))
+{
+ int i, j;
+ ClcContact* testElement;
+
+ for (i = 1; i < nArray; i++) {
+ if (CompareProc(&pContactArray[i - 1], &pContactArray[i]) > 0) {
+ testElement = pContactArray[i];
+ for (j = i - 2; j >= 0; j--)
+ if (CompareProc(&pContactArray[j], &testElement) <= 0)
+ break;
+ j++;
+ memmove(&pContactArray[j + 1], &pContactArray[j], sizeof(void*) * (i - j));
+ pContactArray[j] = testElement;
+ }
+ }
+}
+
+static void SortGroup(ClcData *dat, ClcGroup *group, int useInsertionSort)
+{
+ int i, sortCount;
+
+ for (i = group->cl.getCount() - 1; i >= 0; i--) {
+ if (group->cl[i]->type == CLCIT_DIVIDER) {
+ mir_free(group->cl[i]);
+ group->cl.remove(i);
+ }
+ }
+
+ for (i = 0; i < group->cl.getCount(); i++)
+ if (group->cl[i]->type != CLCIT_INFO)
+ break;
+ if (i > group->cl.getCount() - 2)
+ return;
+ if (group->cl[i]->type == CLCIT_GROUP) {
+ if (dat->exStyle & CLS_EX_SORTGROUPSALPHA) {
+ for (sortCount = 0; i + sortCount < group->cl.getCount(); sortCount++)
+ if (group->cl[i + sortCount]->type != CLCIT_GROUP)
+ break;
+ qsort(group->cl.getArray() + i, sortCount, sizeof(void*), GroupSortProc);
+ i = i + sortCount;
+ }
+ for (; i < group->cl.getCount(); i++)
+ if (group->cl[i]->type == CLCIT_CONTACT)
+ break;
+ if (group->cl.getCount() - i < 2)
+ return;
+ }
+ for (sortCount = 0; i + sortCount < group->cl.getCount(); sortCount++)
+ if (group->cl[i + sortCount]->type != CLCIT_CONTACT)
+ break;
+ if (useInsertionSort)
+ InsertionSort(group->cl.getArray() + i, sortCount, ContactSortProc);
+ else
+ qsort(group->cl.getArray() + i, sortCount, sizeof(void*), ContactSortProc);
+ if (dat->exStyle & CLS_EX_DIVIDERONOFF) {
+ int prevContactOnline = 0;
+ for (i = 0; i < group->cl.getCount(); i++) {
+ if (group->cl[i]->type != CLCIT_CONTACT)
+ continue;
+ if (group->cl[i]->flags & CONTACTF_ONLINE)
+ prevContactOnline = 1;
+ else {
+ if (prevContactOnline) {
+ ClcContact *cc = g_clistApi.pfnAddItemToGroup(group, i);
+ cc->type = CLCIT_DIVIDER;
+ mir_wstrcpy(cc->szText, TranslateT("Offline"));
+ }
+ break;
+ }
+ }
+ }
+}
+
+void fnSortCLC(HWND hwnd, ClcData *dat, int useInsertionSort)
+{
+ ClcGroup *group = &dat->list;
+
+ if (dat->bNeedsResort) {
+ MCONTACT hSelItem;
+ ClcContact *selcontact;
+ if (g_clistApi.pfnGetRowByIndex(dat, dat->selection, &selcontact, nullptr) == -1)
+ hSelItem = 0;
+ else
+ hSelItem = Clist_ContactToHItem(selcontact);
+ group->scanIndex = 0;
+ SortGroup(dat, group, useInsertionSort);
+ for (;;) {
+ if (group->scanIndex == group->cl.getCount()) {
+ if ((group = group->parent) == nullptr)
+ break;
+ group->scanIndex++;
+ continue;
+ }
+
+ ClcContact *cc = group->cl[group->scanIndex];
+ if (cc->type == CLCIT_GROUP) {
+ group = cc->group;
+ group->scanIndex = 0;
+ SortGroup(dat, group, useInsertionSort);
+ continue;
+ }
+ group->scanIndex++;
+ }
+
+ if (hSelItem) {
+ ClcGroup *selgroup;
+ if (Clist_FindItem(hwnd, dat, hSelItem, &selcontact, &selgroup))
+ dat->selection = g_clistApi.pfnGetRowsPriorTo(&dat->list, selgroup, selgroup->cl.indexOf(selcontact));
+ }
+
+ g_clistApi.pfnRecalcScrollBar(hwnd, dat);
+ }
+ dat->bNeedsResort = false;
+ g_clistApi.pfnInvalidateRect(hwnd, nullptr, FALSE);
+}
+
+struct SavedContactState_t
+{
+ MCONTACT hContact;
+ uint16_t iExtraImage[EXTRA_ICON_COUNT];
+ int checked;
+};
+
+struct SavedGroupState_t
+{
+ int groupId, expanded;
+};
+
+struct SavedInfoState_t
+{
+ int parentId;
+ ClcContact contact;
+};
+
+MIR_APP_DLL(void) Clist_SaveStateAndRebuildList(HWND hwnd, ClcData *dat)
+{
+ Clist_HideInfoTip(dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ Clist_EndRename(dat, 1);
+
+ dat->bLockScrollbar = true;
+
+ OBJLIST<SavedContactState_t> saveContact(10, NumericKeySortT);
+ OBJLIST<SavedGroupState_t> saveGroup(100, NumericKeySortT);
+ OBJLIST<SavedInfoState_t> saveInfo(10, NumericKeySortT);
+
+ dat->bNeedsResort = true;
+ ClcGroup *group = &dat->list;
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.getCount()) {
+ if ((group = group->parent) == nullptr)
+ break;
+ group->scanIndex++;
+ continue;
+ }
+
+ ClcContact *cc = group->cl[group->scanIndex];
+ if (cc->type == CLCIT_GROUP) {
+ group = cc->group;
+ group->scanIndex = 0;
+
+ SavedGroupState_t *p = new SavedGroupState_t;
+ p->groupId = group->groupId;
+ p->expanded = group->expanded;
+ saveGroup.insert(p);
+ continue;
+ }
+ else if (cc->type == CLCIT_CONTACT) {
+ SavedContactState_t *p = new SavedContactState_t;
+ p->hContact = cc->hContact;
+ memcpy(p->iExtraImage, cc->iExtraImage, sizeof(p->iExtraImage));
+ p->checked = cc->flags & CONTACTF_CHECKED;
+ saveContact.insert(p);
+ }
+ else if (cc->type == CLCIT_INFO) {
+ SavedInfoState_t *p = new SavedInfoState_t;
+ p->parentId = (group->parent == nullptr) ? -1 : group->groupId;
+ p->contact = *cc;
+ saveInfo.insert(p);
+ }
+ group->scanIndex++;
+ }
+
+ FreeGroup(&dat->list);
+ g_clistApi.pfnRebuildEntireList(hwnd, dat);
+
+ group = &dat->list;
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.getCount()) {
+ if ((group = group->parent) == nullptr)
+ break;
+ group->scanIndex++;
+ continue;
+ }
+
+ ClcContact *cc = group->cl[group->scanIndex];
+ if (cc->type == CLCIT_GROUP) {
+ group = cc->group;
+ group->scanIndex = 0;
+
+ SavedGroupState_t tmp, *p;
+ tmp.groupId = group->groupId;
+ if ((p = saveGroup.find(&tmp)) != nullptr)
+ group->expanded = p->expanded;
+ continue;
+ }
+ else if (cc->type == CLCIT_CONTACT) {
+ SavedContactState_t tmp, *p;
+ tmp.hContact = cc->hContact;
+ if ((p = saveContact.find(&tmp)) != nullptr) {
+ memcpy(cc->iExtraImage, p->iExtraImage, sizeof(p->iExtraImage));
+ if (p->checked)
+ cc->flags |= CONTACTF_CHECKED;
+ }
+ }
+
+ group->scanIndex++;
+ }
+
+ for (auto &it : saveInfo) {
+ if (it->parentId == -1)
+ group = &dat->list;
+ else {
+ ClcContact *contact;
+ if (!Clist_FindItem(hwnd, dat, it->parentId | HCONTACT_ISGROUP, &contact))
+ continue;
+ group = contact->group;
+ }
+
+ ClcContact *cc = g_clistApi.pfnAddInfoItemToGroup(group, it->contact.flags, L"");
+ *cc = it->contact;
+ }
+
+ dat->bLockScrollbar = false;
+ Clist_RecalculateGroupCheckboxes(dat);
+
+ g_clistApi.pfnRecalcScrollBar(hwnd, dat);
+
+ NMCLISTCONTROL nm;
+ nm.hdr.code = CLN_LISTREBUILT;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)& nm);
+}
diff --git a/src/mir_app/src/clcmsgs.cpp b/src/mir_app/src/clcmsgs.cpp index 49267f5cdb..5b92d1a0f0 100644 --- a/src/mir_app/src/clcmsgs.cpp +++ b/src/mir_app/src/clcmsgs.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clcutils.cpp b/src/mir_app/src/clcutils.cpp index c3849572cb..1cd238ffc6 100644 --- a/src/mir_app/src/clcutils.cpp +++ b/src/mir_app/src/clcutils.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clistcore.cpp b/src/mir_app/src/clistcore.cpp index 27372c0101..ebfa53dda6 100644 --- a/src/mir_app/src/clistcore.cpp +++ b/src/mir_app/src/clistcore.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clistevents.cpp b/src/mir_app/src/clistevents.cpp index 0533b5b1c7..4e5455ebc2 100644 --- a/src/mir_app/src/clistevents.cpp +++ b/src/mir_app/src/clistevents.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clistgroups.cpp b/src/mir_app/src/clistgroups.cpp index e91b5686d1..79d320515b 100644 --- a/src/mir_app/src/clistgroups.cpp +++ b/src/mir_app/src/clistgroups.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clistmod.cpp b/src/mir_app/src/clistmod.cpp index 6a205db148..19b9a8a860 100644 --- a/src/mir_app/src/clistmod.cpp +++ b/src/mir_app/src/clistmod.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clistopts.cpp b/src/mir_app/src/clistopts.cpp index 169ee85b6b..8df408c45e 100644 --- a/src/mir_app/src/clistopts.cpp +++ b/src/mir_app/src/clistopts.cpp @@ -1,162 +1,162 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "clc.h" - -CMOption<bool> Clist::UseGroups(MODULENAME, "UseGroups", true); -CMOption<bool> Clist::HideOffline(MODULENAME, "HideOffline", false); -CMOption<bool> Clist::ConfirmDelete(MODULENAME, "ConfirmDelete", true); -CMOption<bool> Clist::EnableIconBlink(MODULENAME, "EnableIconBlink", true); -CMOption<bool> Clist::EnableTrayFlash(MODULENAME, "EnableTrayFlash", true); -CMOption<bool> Clist::HideEmptyGroups(MODULENAME, "HideEmptyGroups", false); -CMOption<bool> Clist::RemoveTempContacts(MODULENAME, "RemoveTempContacts", true); - -CMOption<bool> Clist::Tray1Click(MODULENAME, "Tray1Click", IsWinVer7Plus()); -CMOption<bool> Clist::TrayAlwaysStatus(MODULENAME, "AlwaysStatus", false); - -CMOption<bool> Clist::FilterSearch("CLC", "FilterSearch", false); -CMOption<uint32_t> Clist::OfflineModes("CLC", "OfflineModes", MODEF_OFFLINE); - -struct -{ - uint32_t style; - wchar_t *szDescr; -} -static const offlineValues[] = -{ - { MODEF_OFFLINE, LPGENW("Offline") }, - { PF2_ONLINE, LPGENW("Online") }, - { PF2_SHORTAWAY, LPGENW("Away") }, - { PF2_LONGAWAY, LPGENW("Not available") }, - { PF2_LIGHTDND, LPGENW("Occupied") }, - { PF2_HEAVYDND, LPGENW("Do not disturb") }, - { PF2_FREECHAT, LPGENW("Free for chat") }, - { PF2_INVISIBLE, LPGENW("Invisible") } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -class ClistCommonOptsDlg : public CDlgBase -{ - CCtrlSpin blink; - CCtrlCheck chkUseGroups, chkHideOffline, chkConfirmDelete, chkHideEmptyGroups, chkRemoveTempContacts, chkEnableIconBlink, chkFilterSearch; - CCtrlCheck chkAlwaysStatus, chkOneClick, chkEnableTrayBlink; - CCtrlTreeView hideStatuses; - -public: - ClistCommonOptsDlg() : - CDlgBase(g_plugin, IDD_OPT_CLIST), - blink(this, IDC_BLINKSPIN, 0x3FFF, 250), - hideStatuses(this, IDC_HIDEOFFLINEOPTS), - chkOneClick(this, IDC_ONECLK), - chkUseGroups(this, IDC_USEGROUPS), - chkHideOffline(this, IDC_HIDEOFFLINE), - chkFilterSearch(this, IDC_FILTER_SEARCH), - chkAlwaysStatus(this, IDC_ALWAYSSTATUS), - chkConfirmDelete(this, IDC_CONFIRMDELETE), - chkHideEmptyGroups(this, IDC_HIDEEMPTYGROUPS), - chkEnableIconBlink(this, IDC_ENABLE_ICON_BLINK), - chkEnableTrayBlink(this, IDC_ENABLE_TRAY_BLINK), - chkRemoveTempContacts(this, IDC_REMOVETEMP) - { - chkEnableTrayBlink.OnChange = Callback(this, &ClistCommonOptsDlg::onChange_TrayBlink); - - CreateLink(chkOneClick, Clist::Tray1Click); - CreateLink(chkUseGroups, Clist::UseGroups); - CreateLink(chkHideOffline, Clist::HideOffline); - CreateLink(chkFilterSearch, Clist::FilterSearch); - CreateLink(chkAlwaysStatus, Clist::TrayAlwaysStatus); - CreateLink(chkConfirmDelete, Clist::ConfirmDelete); - CreateLink(chkHideEmptyGroups, Clist::HideEmptyGroups); - CreateLink(chkEnableIconBlink, Clist::EnableIconBlink); - CreateLink(chkEnableTrayBlink, Clist::EnableTrayFlash); - CreateLink(chkRemoveTempContacts, Clist::RemoveTempContacts); - } - - bool OnInitDialog() override - { - blink.SetPosition(db_get_w(0, MODULENAME, "IconFlashTime", 550)); - - SetWindowLongPtr(hideStatuses.GetHwnd(), GWL_STYLE, - GetWindowLongPtr(hideStatuses.GetHwnd(), GWL_STYLE) | TVS_NOHSCROLL | TVS_CHECKBOXES); - - int style = Clist::OfflineModes; - - TVINSERTSTRUCT tvis; - tvis.hParent = nullptr; - tvis.hInsertAfter = TVI_LAST; - tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_STATE; - for (auto &it : offlineValues) { - tvis.item.lParam = it.style; - tvis.item.pszText = TranslateW(it.szDescr); - tvis.item.stateMask = TVIS_STATEIMAGEMASK; - tvis.item.state = INDEXTOSTATEIMAGEMASK((style & it.style) != 0 ? 2 : 1); - hideStatuses.InsertItem(&tvis); - } - return true; - } - - bool OnApply() override - { - db_set_w(0, MODULENAME, "IconFlashTime", blink.GetPosition()); - - uint32_t flags = 0; - - TVITEMEX tvi; - tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE; - tvi.hItem = hideStatuses.GetRoot(); - while (tvi.hItem) { - hideStatuses.GetItem(&tvi); - if ((tvi.state & TVIS_STATEIMAGEMASK) >> 12 == 2) - flags |= tvi.lParam; - tvi.hItem = hideStatuses.GetNextSibling(tvi.hItem); - } - Clist::OfflineModes = flags; - - Clist_ClcOptionsChanged(); - Clist_LoadContactTree(); - Clist_InitAutoRebuild(g_clistApi.hwndContactTree); - return true; - } - - void onChange_TrayBlink(CCtrlCheck*) - { - bool bEnabled = chkEnableTrayBlink.IsChecked(); - EnableWindow(GetDlgItem(m_hwnd, IDC_BLINKTIME), bEnabled); - EnableWindow(GetDlgItem(m_hwnd, IDC_BLINKSPIN), bEnabled); - } -}; - -int ClcOptInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.position = -200000000; - odp.szGroup.a = LPGEN("Contact list"); - odp.szTitle.a = LPGEN("Common"); - odp.flags = ODPF_BOLDGROUPS; - odp.pDialog = new ClistCommonOptsDlg(); - g_plugin.addOptions(wParam, &odp); - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "clc.h"
+
+CMOption<bool> Clist::UseGroups(MODULENAME, "UseGroups", true);
+CMOption<bool> Clist::HideOffline(MODULENAME, "HideOffline", false);
+CMOption<bool> Clist::ConfirmDelete(MODULENAME, "ConfirmDelete", true);
+CMOption<bool> Clist::EnableIconBlink(MODULENAME, "EnableIconBlink", true);
+CMOption<bool> Clist::EnableTrayFlash(MODULENAME, "EnableTrayFlash", true);
+CMOption<bool> Clist::HideEmptyGroups(MODULENAME, "HideEmptyGroups", false);
+CMOption<bool> Clist::RemoveTempContacts(MODULENAME, "RemoveTempContacts", true);
+
+CMOption<bool> Clist::Tray1Click(MODULENAME, "Tray1Click", IsWinVer7Plus());
+CMOption<bool> Clist::TrayAlwaysStatus(MODULENAME, "AlwaysStatus", false);
+
+CMOption<bool> Clist::FilterSearch("CLC", "FilterSearch", false);
+CMOption<uint32_t> Clist::OfflineModes("CLC", "OfflineModes", MODEF_OFFLINE);
+
+struct
+{
+ uint32_t style;
+ wchar_t *szDescr;
+}
+static const offlineValues[] =
+{
+ { MODEF_OFFLINE, LPGENW("Offline") },
+ { PF2_ONLINE, LPGENW("Online") },
+ { PF2_SHORTAWAY, LPGENW("Away") },
+ { PF2_LONGAWAY, LPGENW("Not available") },
+ { PF2_LIGHTDND, LPGENW("Occupied") },
+ { PF2_HEAVYDND, LPGENW("Do not disturb") },
+ { PF2_FREECHAT, LPGENW("Free for chat") },
+ { PF2_INVISIBLE, LPGENW("Invisible") }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class ClistCommonOptsDlg : public CDlgBase
+{
+ CCtrlSpin blink;
+ CCtrlCheck chkUseGroups, chkHideOffline, chkConfirmDelete, chkHideEmptyGroups, chkRemoveTempContacts, chkEnableIconBlink, chkFilterSearch;
+ CCtrlCheck chkAlwaysStatus, chkOneClick, chkEnableTrayBlink;
+ CCtrlTreeView hideStatuses;
+
+public:
+ ClistCommonOptsDlg() :
+ CDlgBase(g_plugin, IDD_OPT_CLIST),
+ blink(this, IDC_BLINKSPIN, 0x3FFF, 250),
+ hideStatuses(this, IDC_HIDEOFFLINEOPTS),
+ chkOneClick(this, IDC_ONECLK),
+ chkUseGroups(this, IDC_USEGROUPS),
+ chkHideOffline(this, IDC_HIDEOFFLINE),
+ chkFilterSearch(this, IDC_FILTER_SEARCH),
+ chkAlwaysStatus(this, IDC_ALWAYSSTATUS),
+ chkConfirmDelete(this, IDC_CONFIRMDELETE),
+ chkHideEmptyGroups(this, IDC_HIDEEMPTYGROUPS),
+ chkEnableIconBlink(this, IDC_ENABLE_ICON_BLINK),
+ chkEnableTrayBlink(this, IDC_ENABLE_TRAY_BLINK),
+ chkRemoveTempContacts(this, IDC_REMOVETEMP)
+ {
+ chkEnableTrayBlink.OnChange = Callback(this, &ClistCommonOptsDlg::onChange_TrayBlink);
+
+ CreateLink(chkOneClick, Clist::Tray1Click);
+ CreateLink(chkUseGroups, Clist::UseGroups);
+ CreateLink(chkHideOffline, Clist::HideOffline);
+ CreateLink(chkFilterSearch, Clist::FilterSearch);
+ CreateLink(chkAlwaysStatus, Clist::TrayAlwaysStatus);
+ CreateLink(chkConfirmDelete, Clist::ConfirmDelete);
+ CreateLink(chkHideEmptyGroups, Clist::HideEmptyGroups);
+ CreateLink(chkEnableIconBlink, Clist::EnableIconBlink);
+ CreateLink(chkEnableTrayBlink, Clist::EnableTrayFlash);
+ CreateLink(chkRemoveTempContacts, Clist::RemoveTempContacts);
+ }
+
+ bool OnInitDialog() override
+ {
+ blink.SetPosition(db_get_w(0, MODULENAME, "IconFlashTime", 550));
+
+ SetWindowLongPtr(hideStatuses.GetHwnd(), GWL_STYLE,
+ GetWindowLongPtr(hideStatuses.GetHwnd(), GWL_STYLE) | TVS_NOHSCROLL | TVS_CHECKBOXES);
+
+ int style = Clist::OfflineModes;
+
+ TVINSERTSTRUCT tvis;
+ tvis.hParent = nullptr;
+ tvis.hInsertAfter = TVI_LAST;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_STATE;
+ for (auto &it : offlineValues) {
+ tvis.item.lParam = it.style;
+ tvis.item.pszText = TranslateW(it.szDescr);
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+ tvis.item.state = INDEXTOSTATEIMAGEMASK((style & it.style) != 0 ? 2 : 1);
+ hideStatuses.InsertItem(&tvis);
+ }
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ db_set_w(0, MODULENAME, "IconFlashTime", blink.GetPosition());
+
+ uint32_t flags = 0;
+
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE;
+ tvi.hItem = hideStatuses.GetRoot();
+ while (tvi.hItem) {
+ hideStatuses.GetItem(&tvi);
+ if ((tvi.state & TVIS_STATEIMAGEMASK) >> 12 == 2)
+ flags |= tvi.lParam;
+ tvi.hItem = hideStatuses.GetNextSibling(tvi.hItem);
+ }
+ Clist::OfflineModes = flags;
+
+ Clist_ClcOptionsChanged();
+ Clist_LoadContactTree();
+ Clist_InitAutoRebuild(g_clistApi.hwndContactTree);
+ return true;
+ }
+
+ void onChange_TrayBlink(CCtrlCheck*)
+ {
+ bool bEnabled = chkEnableTrayBlink.IsChecked();
+ EnableWindow(GetDlgItem(m_hwnd, IDC_BLINKTIME), bEnabled);
+ EnableWindow(GetDlgItem(m_hwnd, IDC_BLINKSPIN), bEnabled);
+ }
+};
+
+int ClcOptInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.position = -200000000;
+ odp.szGroup.a = LPGEN("Contact list");
+ odp.szTitle.a = LPGEN("Common");
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pDialog = new ClistCommonOptsDlg();
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/clistsettings.cpp b/src/mir_app/src/clistsettings.cpp index 6b160e0046..4d3e60b9d4 100644 --- a/src/mir_app/src/clistsettings.cpp +++ b/src/mir_app/src/clistsettings.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clisttray.cpp b/src/mir_app/src/clisttray.cpp index 9d4137748b..c10100a1b3 100644 --- a/src/mir_app/src/clisttray.cpp +++ b/src/mir_app/src/clisttray.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/clui.cpp b/src/mir_app/src/clui.cpp index e5201fb244..39015a1819 100644 --- a/src/mir_app/src/clui.cpp +++ b/src/mir_app/src/clui.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/cluiservices.cpp b/src/mir_app/src/cluiservices.cpp index dc17de4dfb..ae6d2c66de 100644 --- a/src/mir_app/src/cluiservices.cpp +++ b/src/mir_app/src/cluiservices.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/colorchooser.cpp b/src/mir_app/src/colorchooser.cpp index 2931d9f3cf..6a93e61f54 100644 --- a/src/mir_app/src/colorchooser.cpp +++ b/src/mir_app/src/colorchooser.cpp @@ -1,7 +1,7 @@ /*
Chat module plugin for Miranda IM
-Copyright 2000-12 Miranda IM, 2012-22 Miranda NG team,
+Copyright 2000-12 Miranda IM, 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/contacts.cpp b/src/mir_app/src/contacts.cpp index 3a4c32b18b..8cec2a273a 100644 --- a/src/mir_app/src/contacts.cpp +++ b/src/mir_app/src/contacts.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/copyright.h b/src/mir_app/src/copyright.h index 29b0f97206..641ed66f54 100644 --- a/src/mir_app/src/copyright.h +++ b/src/mir_app/src/copyright.h @@ -1,2 +1,2 @@ - -#define LEGAL_COPYRIGHT "Copyright © 2000-11 Miranda IM, 2012-22 Miranda NG team. This software is released under the terms of the GNU General Public License.\0" +
+#define LEGAL_COPYRIGHT "Copyright © 2000-11 Miranda IM, 2012-23 Miranda NG team. This software is released under the terms of the GNU General Public License.\0"
diff --git a/src/mir_app/src/database.cpp b/src/mir_app/src/database.cpp index d20361c3b4..149377bc27 100644 --- a/src/mir_app/src/database.cpp +++ b/src/mir_app/src/database.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/database.h b/src/mir_app/src/database.h index 11d5b1b58b..07f32d6bad 100644 --- a/src/mir_app/src/database.h +++ b/src/mir_app/src/database.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright 2012-22 Miranda NG team,
+Copyright 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/db_events.cpp b/src/mir_app/src/db_events.cpp index 68049037f1..b6bce698d4 100644 --- a/src/mir_app/src/db_events.cpp +++ b/src/mir_app/src/db_events.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/db_ini.cpp b/src/mir_app/src/db_ini.cpp index 8ce8f88385..6953345787 100644 --- a/src/mir_app/src/db_ini.cpp +++ b/src/mir_app/src/db_ini.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/db_intf.cpp b/src/mir_app/src/db_intf.cpp index fc9467d8fd..a422ab7b25 100644 --- a/src/mir_app/src/db_intf.cpp +++ b/src/mir_app/src/db_intf.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team,
+Copyright (C) 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/db_upgrade.cpp b/src/mir_app/src/db_upgrade.cpp index c96f638125..2b6f6d7755 100644 --- a/src/mir_app/src/db_upgrade.cpp +++ b/src/mir_app/src/db_upgrade.cpp @@ -1,71 +1,71 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#define CONVERT_MSG LPGEN("This database is in the old format that isn't supported anymore. Press Yes to convert it to the new format or No to return") -#define MISSING_DB_MSG LPGEN("To open this database you need to install the Dbx_sqlite plugin. Click Yes to download it from Miranda NG's site or No to return") -#define MISSING_PLUG_MSG LPGEN("To open this database you need to install the Import plugin. Click Yes to download it from Miranda NG's site or No to return") - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(MDatabaseCommon*) DB::Upgrade(const wchar_t *profile) -{ - DATABASELINK *pLink = GetDatabasePlugin("dbx_sqlite"); - if (pLink == nullptr) { - if (IDYES == MessageBoxW(nullptr, TranslateT(MISSING_DB_MSG), L"Miranda NG", MB_YESNO)) - Utils_OpenUrl("https://miranda-ng.org/p/Dbx_sqlite"); - return nullptr; - } - - if (!Profile_GetSettingInt(L"Database/SilentUpgrade")) - if (IDYES != MessageBoxW(nullptr, TranslateT(CONVERT_MSG), L"Miranda NG", MB_YESNO)) - return nullptr; - - int errorCode; - CMStringW wszBackupName(profile); - wszBackupName.Append(L".bak"); - DeleteFileW(wszBackupName); - if (!MoveFileW(profile, wszBackupName)) { - uint32_t dwError = GetLastError(); - CMStringW wszError(FORMAT, TranslateT("Cannot move old profile '%s' to '%s': error %d"), profile, wszBackupName.c_str(), dwError); - MessageBoxW(nullptr, wszError, L"Miranda NG", MB_ICONERROR | MB_OK); - return nullptr; - } - - if ((errorCode = pLink->makeDatabase(profile)) != 0) { - MessageBoxW(nullptr, CMStringW(FORMAT, TranslateT("Attempt to create database '%s' failed with error code %d"), profile, errorCode), L"Miranda NG", MB_ICONERROR | MB_OK); -LBL_Error: - DeleteFileW(profile); - MoveFileW(wszBackupName, profile); - return nullptr; - } - - if (SetServiceModePlugin("import", 1) != ERROR_SUCCESS) { - if (IDYES == MessageBoxW(nullptr, TranslateT(MISSING_PLUG_MSG), L"Miranda NG", MB_YESNO)) - Utils_OpenUrl("https://miranda-ng.org/p/Import"); - goto LBL_Error; - } - - return pLink->Load(profile, false); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#define CONVERT_MSG LPGEN("This database is in the old format that isn't supported anymore. Press Yes to convert it to the new format or No to return")
+#define MISSING_DB_MSG LPGEN("To open this database you need to install the Dbx_sqlite plugin. Click Yes to download it from Miranda NG's site or No to return")
+#define MISSING_PLUG_MSG LPGEN("To open this database you need to install the Import plugin. Click Yes to download it from Miranda NG's site or No to return")
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(MDatabaseCommon*) DB::Upgrade(const wchar_t *profile)
+{
+ DATABASELINK *pLink = GetDatabasePlugin("dbx_sqlite");
+ if (pLink == nullptr) {
+ if (IDYES == MessageBoxW(nullptr, TranslateT(MISSING_DB_MSG), L"Miranda NG", MB_YESNO))
+ Utils_OpenUrl("https://miranda-ng.org/p/Dbx_sqlite");
+ return nullptr;
+ }
+
+ if (!Profile_GetSettingInt(L"Database/SilentUpgrade"))
+ if (IDYES != MessageBoxW(nullptr, TranslateT(CONVERT_MSG), L"Miranda NG", MB_YESNO))
+ return nullptr;
+
+ int errorCode;
+ CMStringW wszBackupName(profile);
+ wszBackupName.Append(L".bak");
+ DeleteFileW(wszBackupName);
+ if (!MoveFileW(profile, wszBackupName)) {
+ uint32_t dwError = GetLastError();
+ CMStringW wszError(FORMAT, TranslateT("Cannot move old profile '%s' to '%s': error %d"), profile, wszBackupName.c_str(), dwError);
+ MessageBoxW(nullptr, wszError, L"Miranda NG", MB_ICONERROR | MB_OK);
+ return nullptr;
+ }
+
+ if ((errorCode = pLink->makeDatabase(profile)) != 0) {
+ MessageBoxW(nullptr, CMStringW(FORMAT, TranslateT("Attempt to create database '%s' failed with error code %d"), profile, errorCode), L"Miranda NG", MB_ICONERROR | MB_OK);
+LBL_Error:
+ DeleteFileW(profile);
+ MoveFileW(wszBackupName, profile);
+ return nullptr;
+ }
+
+ if (SetServiceModePlugin("import", 1) != ERROR_SUCCESS) {
+ if (IDYES == MessageBoxW(nullptr, TranslateT(MISSING_PLUG_MSG), L"Miranda NG", MB_YESNO))
+ Utils_OpenUrl("https://miranda-ng.org/p/Import");
+ goto LBL_Error;
+ }
+
+ return pLink->Load(profile, false);
+}
diff --git a/src/mir_app/src/db_util.cpp b/src/mir_app/src/db_util.cpp index fc23a10050..6fd23cb4f5 100644 --- a/src/mir_app/src/db_util.cpp +++ b/src/mir_app/src/db_util.cpp @@ -1,129 +1,129 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "profilemanager.h" - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Profile_GetPathA(size_t cbLen, char *pszDest) -{ - if (!pszDest || !cbLen) - return 1; - - strncpy_s(pszDest, cbLen, _T2A(g_profileDir), _TRUNCATE); - return 0; -} - -MIR_APP_DLL(int) Profile_GetPathW(size_t cbLen, wchar_t *pwszDest) -{ - if (!pwszDest || !cbLen) - return 1; - - wcsncpy_s(pwszDest, cbLen, g_profileDir, _TRUNCATE); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Profile_GetNameA(size_t cbLen, char *pszDest) -{ - if (!cbLen || !pszDest) - return 1; - - strncpy_s(pszDest, cbLen, ptrA(makeFileName(g_profileName)), _TRUNCATE); - return 0; -} - -MIR_APP_DLL(int) Profile_GetNameW(size_t cbLen, wchar_t *pwszDest) -{ - if (!cbLen || !pwszDest) - return 1; - - wcsncpy_s(pwszDest, cbLen, g_profileName, _TRUNCATE); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(bool) Profile_GetSetting(const wchar_t *pwszSetting, wchar_t *pwszBuf, size_t cbLen, const wchar_t *pwszDefault) -{ - if (pwszSetting == nullptr) { - *pwszBuf = 0; - return false; - } - - if (pwszDefault == nullptr) - pwszDefault = L""; - - wchar_t *pBuf = NEWWSTR_ALLOCA(pwszSetting); - - wchar_t *p = wcschr(pBuf, '/'); - if (p) { - *p = 0; p++; - GetPrivateProfileStringW(pBuf, p, pwszDefault, pwszBuf, (uint32_t)cbLen, mirandabootini); - } - else GetPrivateProfileStringW(pBuf, L"", pwszDefault, pwszBuf, (uint32_t)cbLen, mirandabootini); - - return pwszBuf[0] != 0; -} - -MIR_APP_DLL(int) Profile_GetSettingInt(const wchar_t *pwszSetting, int iDefault) -{ - if (pwszSetting == nullptr) - return iDefault; - - wchar_t *pBuf = NEWWSTR_ALLOCA(pwszSetting); - - wchar_t *p = wcschr(pBuf, '/'); - if (p) { - *p = 0; p++; - return GetPrivateProfileIntW(pBuf, p, iDefault, mirandabootini); - } - - return GetPrivateProfileIntW(pBuf, L"", iDefault, mirandabootini); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(void) Profile_SetDefault(const wchar_t *pwszPath) -{ - extern wchar_t* g_defaultProfile; - replaceStrW(g_defaultProfile, pwszPath); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(bool) Profile_CheckOpened(const wchar_t *pwszProfileName) -{ - CMStringW wszPhysName(pwszProfileName); - wszPhysName.Replace(L"\\", L"_"); - wszPhysName.Insert(0, L"Global\\"); - - HANDLE hMutex = ::OpenMutexW(SYNCHRONIZE, false, wszPhysName); - if (hMutex == nullptr) - return false; - - ::CloseHandle(hMutex); - return true; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "profilemanager.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Profile_GetPathA(size_t cbLen, char *pszDest)
+{
+ if (!pszDest || !cbLen)
+ return 1;
+
+ strncpy_s(pszDest, cbLen, _T2A(g_profileDir), _TRUNCATE);
+ return 0;
+}
+
+MIR_APP_DLL(int) Profile_GetPathW(size_t cbLen, wchar_t *pwszDest)
+{
+ if (!pwszDest || !cbLen)
+ return 1;
+
+ wcsncpy_s(pwszDest, cbLen, g_profileDir, _TRUNCATE);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Profile_GetNameA(size_t cbLen, char *pszDest)
+{
+ if (!cbLen || !pszDest)
+ return 1;
+
+ strncpy_s(pszDest, cbLen, ptrA(makeFileName(g_profileName)), _TRUNCATE);
+ return 0;
+}
+
+MIR_APP_DLL(int) Profile_GetNameW(size_t cbLen, wchar_t *pwszDest)
+{
+ if (!cbLen || !pwszDest)
+ return 1;
+
+ wcsncpy_s(pwszDest, cbLen, g_profileName, _TRUNCATE);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(bool) Profile_GetSetting(const wchar_t *pwszSetting, wchar_t *pwszBuf, size_t cbLen, const wchar_t *pwszDefault)
+{
+ if (pwszSetting == nullptr) {
+ *pwszBuf = 0;
+ return false;
+ }
+
+ if (pwszDefault == nullptr)
+ pwszDefault = L"";
+
+ wchar_t *pBuf = NEWWSTR_ALLOCA(pwszSetting);
+
+ wchar_t *p = wcschr(pBuf, '/');
+ if (p) {
+ *p = 0; p++;
+ GetPrivateProfileStringW(pBuf, p, pwszDefault, pwszBuf, (uint32_t)cbLen, mirandabootini);
+ }
+ else GetPrivateProfileStringW(pBuf, L"", pwszDefault, pwszBuf, (uint32_t)cbLen, mirandabootini);
+
+ return pwszBuf[0] != 0;
+}
+
+MIR_APP_DLL(int) Profile_GetSettingInt(const wchar_t *pwszSetting, int iDefault)
+{
+ if (pwszSetting == nullptr)
+ return iDefault;
+
+ wchar_t *pBuf = NEWWSTR_ALLOCA(pwszSetting);
+
+ wchar_t *p = wcschr(pBuf, '/');
+ if (p) {
+ *p = 0; p++;
+ return GetPrivateProfileIntW(pBuf, p, iDefault, mirandabootini);
+ }
+
+ return GetPrivateProfileIntW(pBuf, L"", iDefault, mirandabootini);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(void) Profile_SetDefault(const wchar_t *pwszPath)
+{
+ extern wchar_t* g_defaultProfile;
+ replaceStrW(g_defaultProfile, pwszPath);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(bool) Profile_CheckOpened(const wchar_t *pwszProfileName)
+{
+ CMStringW wszPhysName(pwszProfileName);
+ wszPhysName.Replace(L"\\", L"_");
+ wszPhysName.Insert(0, L"Global\\");
+
+ HANDLE hMutex = ::OpenMutexW(SYNCHRONIZE, false, wszPhysName);
+ if (hMutex == nullptr)
+ return false;
+
+ ::CloseHandle(hMutex);
+ return true;
+}
diff --git a/src/mir_app/src/descbutton.cpp b/src/mir_app/src/descbutton.cpp index ad198bdaf1..4fdd74f856 100644 --- a/src/mir_app/src/descbutton.cpp +++ b/src/mir_app/src/descbutton.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
Copyright (c) 2007 Artem Shpynov
all portions of this codebase are copyrighted to the people
diff --git a/src/mir_app/src/dll_sniffer.cpp b/src/mir_app/src/dll_sniffer.cpp index 037ca328e5..7f2aca1955 100644 --- a/src/mir_app/src/dll_sniffer.cpp +++ b/src/mir_app/src/dll_sniffer.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/ei_baseIcon.cpp b/src/mir_app/src/ei_baseIcon.cpp index 5ddce65bab..7244d20edb 100644 --- a/src/mir_app/src/ei_baseIcon.cpp +++ b/src/mir_app/src/ei_baseIcon.cpp @@ -1,70 +1,70 @@ -/* - -Copyright (C) 2009 Ricardo Pescuma Domenecci -Copyright (C) 2012-22 Miranda NG team - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this file; see the file license.txt. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "extraicons.h" - -BaseExtraIcon::BaseExtraIcon(const char *name, const wchar_t *description, HANDLE descIcon, MIRANDAHOOKPARAM OnClick, LPARAM param) : - ExtraIcon(name), - m_OnClick(OnClick), - m_onClickParam(param), - m_tszDescription(mir_wstrdup(description)), - m_hDescIcon(descIcon) -{ - if (!IcoLib_IsManaged((HICON)descIcon)) - m_hDescIcon = IcoLib_GetIconHandle((const char *)descIcon); - - m_id = registeredExtraIcons.getCount() + 1; -} - -BaseExtraIcon::~BaseExtraIcon() -{ -} - -void BaseExtraIcon::setOnClick(MIRANDAHOOKPARAM pFunc, LPARAM pParam) -{ - m_OnClick = pFunc; - m_onClickParam = pParam; -} - -const wchar_t* BaseExtraIcon::getDescription() const -{ - return TranslateW_LP(m_tszDescription, m_pPlugin); -} - -HANDLE BaseExtraIcon::getDescIcon() const -{ - return m_hDescIcon; -} - -void BaseExtraIcon::onClick(MCONTACT hContact) -{ - if (m_OnClick != nullptr) - m_OnClick(hContact, (LPARAM)ConvertToClistSlot(m_slot), m_onClickParam); -} - -int BaseExtraIcon::ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage) -{ - if (m_pParent) - return m_pParent->ClistSetExtraIcon(hContact, hImage); - return Clist_SetExtraIcon(hContact, m_slot, hImage); -} +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-23 Miranda NG team
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+BaseExtraIcon::BaseExtraIcon(const char *name, const wchar_t *description, HANDLE descIcon, MIRANDAHOOKPARAM OnClick, LPARAM param) :
+ ExtraIcon(name),
+ m_OnClick(OnClick),
+ m_onClickParam(param),
+ m_tszDescription(mir_wstrdup(description)),
+ m_hDescIcon(descIcon)
+{
+ if (!IcoLib_IsManaged((HICON)descIcon))
+ m_hDescIcon = IcoLib_GetIconHandle((const char *)descIcon);
+
+ m_id = registeredExtraIcons.getCount() + 1;
+}
+
+BaseExtraIcon::~BaseExtraIcon()
+{
+}
+
+void BaseExtraIcon::setOnClick(MIRANDAHOOKPARAM pFunc, LPARAM pParam)
+{
+ m_OnClick = pFunc;
+ m_onClickParam = pParam;
+}
+
+const wchar_t* BaseExtraIcon::getDescription() const
+{
+ return TranslateW_LP(m_tszDescription, m_pPlugin);
+}
+
+HANDLE BaseExtraIcon::getDescIcon() const
+{
+ return m_hDescIcon;
+}
+
+void BaseExtraIcon::onClick(MCONTACT hContact)
+{
+ if (m_OnClick != nullptr)
+ m_OnClick(hContact, (LPARAM)ConvertToClistSlot(m_slot), m_onClickParam);
+}
+
+int BaseExtraIcon::ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage)
+{
+ if (m_pParent)
+ return m_pParent->ClistSetExtraIcon(hContact, hImage);
+ return Clist_SetExtraIcon(hContact, m_slot, hImage);
+}
diff --git a/src/mir_app/src/ei_callbackIcon.cpp b/src/mir_app/src/ei_callbackIcon.cpp index a28bd24ed0..205003d054 100644 --- a/src/mir_app/src/ei_callbackIcon.cpp +++ b/src/mir_app/src/ei_callbackIcon.cpp @@ -1,75 +1,75 @@ -/* - -Copyright (C) 2009 Ricardo Pescuma Domenecci -Copyright (C) 2012-22 Miranda NG team - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this file; see the file license.txt. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "extraicons.h" - -CallbackExtraIcon::CallbackExtraIcon(const char *_name, const wchar_t *_description, HANDLE _descIcon, - MIRANDAHOOK _RebuildIcons, MIRANDAHOOK _ApplyIcon, MIRANDAHOOKPARAM _OnClick, LPARAM _param) : - BaseExtraIcon(_name, _description, _descIcon, _OnClick, _param), - m_pfnRebuildIcons(_RebuildIcons), m_pfnApplyIcon(_ApplyIcon), m_needToRebuild(true) -{ -} - -CallbackExtraIcon::~CallbackExtraIcon() -{ -} - -int CallbackExtraIcon::getType() const -{ - return EXTRAICON_TYPE_CALLBACK; -} - -void CallbackExtraIcon::rebuildIcons() -{ - if (!isEnabled()) { - m_needToRebuild = true; - return; - } - - m_needToRebuild = false; - m_pfnRebuildIcons(0, 0); -} - -void CallbackExtraIcon::applyIcon(MCONTACT hContact) -{ - if (!isEnabled() || hContact == 0) - return; - - if (m_needToRebuild) - rebuildIcons(); - - m_pfnApplyIcon(hContact, 0); -} - -int CallbackExtraIcon::setIcon(MCONTACT hContact, HANDLE icon) -{ - if (!isEnabled() || hContact == 0) - return -1; - - return ClistSetExtraIcon(hContact, icon); -} - -int CallbackExtraIcon::setIconByName(MCONTACT, const char*) -{ - return -1; -} +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-23 Miranda NG team
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+CallbackExtraIcon::CallbackExtraIcon(const char *_name, const wchar_t *_description, HANDLE _descIcon,
+ MIRANDAHOOK _RebuildIcons, MIRANDAHOOK _ApplyIcon, MIRANDAHOOKPARAM _OnClick, LPARAM _param) :
+ BaseExtraIcon(_name, _description, _descIcon, _OnClick, _param),
+ m_pfnRebuildIcons(_RebuildIcons), m_pfnApplyIcon(_ApplyIcon), m_needToRebuild(true)
+{
+}
+
+CallbackExtraIcon::~CallbackExtraIcon()
+{
+}
+
+int CallbackExtraIcon::getType() const
+{
+ return EXTRAICON_TYPE_CALLBACK;
+}
+
+void CallbackExtraIcon::rebuildIcons()
+{
+ if (!isEnabled()) {
+ m_needToRebuild = true;
+ return;
+ }
+
+ m_needToRebuild = false;
+ m_pfnRebuildIcons(0, 0);
+}
+
+void CallbackExtraIcon::applyIcon(MCONTACT hContact)
+{
+ if (!isEnabled() || hContact == 0)
+ return;
+
+ if (m_needToRebuild)
+ rebuildIcons();
+
+ m_pfnApplyIcon(hContact, 0);
+}
+
+int CallbackExtraIcon::setIcon(MCONTACT hContact, HANDLE icon)
+{
+ if (!isEnabled() || hContact == 0)
+ return -1;
+
+ return ClistSetExtraIcon(hContact, icon);
+}
+
+int CallbackExtraIcon::setIconByName(MCONTACT, const char*)
+{
+ return -1;
+}
diff --git a/src/mir_app/src/ei_defaulticons.cpp b/src/mir_app/src/ei_defaulticons.cpp index 14c1b2b359..e2eb20cbf0 100644 --- a/src/mir_app/src/ei_defaulticons.cpp +++ b/src/mir_app/src/ei_defaulticons.cpp @@ -1,339 +1,339 @@ -/* - -Copyright (C) 2009 Ricardo Pescuma Domenecci -Copyright (C) 2012-22 Miranda NG team - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this file; see the file license.txt. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "m_cluiframes.h" - -#include "extraicons.h" -#include "chat.h" - -//////////////////////////////////////////////////////////////////////////////////////// -// DB extra icons - -HANDLE hExtraVisibility, hExtraChat, hExtraChatMute, hExtraGender, hExtraProto; - -static void SetVisibility(MCONTACT hContact, int apparentMode, bool clear) -{ - if (hContact == 0) - return; - - char *proto = Proto_GetBaseAccountName(hContact); - if (IsEmpty(proto)) - return; - - if (apparentMode <= 0) - apparentMode = db_get_w(hContact, proto, "ApparentMode", 0); - - HANDLE hExtraIcon, hIcolib = nullptr; - - // Is chat - if (Contact::IsGroupChat(hContact, proto)) { - hExtraIcon = hExtraChat; - if (apparentMode == ID_STATUS_OFFLINE) - hIcolib = IcoLib_GetIconHandle("ChatActivity"); - } - else { // Not chat - hExtraIcon = hExtraVisibility; - if (apparentMode == ID_STATUS_OFFLINE) - hIcolib = Skin_GetIconHandle(SKINICON_OTHER_INVISIBLE_ALL); - else if (apparentMode == ID_STATUS_ONLINE) - hIcolib = Skin_GetIconHandle(SKINICON_OTHER_VISIBLE_ALL); - } - - if (hIcolib != nullptr || clear) - ExtraIcon_SetIcon(hExtraIcon, hContact, hIcolib); -} - -static void SetGender(MCONTACT hContact, int gender, bool clear) -{ - if (hContact == 0) - return; - - char *proto = Proto_GetBaseAccountName(hContact); - if (IsEmpty(proto)) - return; - - if (gender <= 0) - gender = db_get_b(hContact, proto, "Gender", 0); - if (gender <= 0) - gender = db_get_b(hContact, "UserInfo", "Gender", 0); - - const char *ico; - if (gender == 'M') - ico = "gender_male"; - else if (gender == 'F') - ico = "gender_female"; - else - ico = nullptr; - - if (ico != nullptr || clear) - ExtraIcon_SetIconByName(hExtraGender, hContact, ico); -} - -static void SetChatMute(MCONTACT hContact, int mode) -{ - if (hContact == 0) - return; - - if (mode == -1) - mode = db_get_b(hContact, "SRMM", "MuteMode", CHATMODE_NORMAL); - - HANDLE hIcon; - switch (mode) { - case CHATMODE_MUTE: hIcon = Skin_GetIconHandle(SKINICON_OTHER_OFF); break; - case CHATMODE_UNMUTE: hIcon = Skin_GetIconHandle(SKINICON_OTHER_ON); break; - default: - hIcon = nullptr; break; - } - - ExtraIcon_SetIcon(hExtraChatMute, hContact, hIcon); -} - -struct Info -{ - const char *name; - const char *desc; - int iSkinIcon; - const char *db[8]; - void(*OnClick)(Info *info, const char *text); - int flags; - - HANDLE hIcolib, hExtraIcon; -}; - -static void EmailOnClick(Info*, const char *text) -{ - char cmd[1024]; - mir_snprintf(cmd, "mailto:%s", text); - ShellExecuteA(nullptr, "open", cmd, nullptr, nullptr, SW_SHOW); -} - -static void HomepageOnClick(Info*, const char *text) -{ - ShellExecuteA(nullptr, "open", text, nullptr, nullptr, SW_SHOW); -} - -static Info infos[] = -{ - { "homepage", LPGEN("Homepage"), SKINICON_OTHER_MIRANDAWEB, - { nullptr, "Homepage", "UserInfo", "Homepage" }, - &HomepageOnClick, EIF_DISABLED_BY_DEFAULT }, - { "sms", LPGEN("Phone/SMS"), SKINICON_OTHER_SMS, - { nullptr, "Cellular", "UserInfo", "Cellular", "UserInfo", "Phone", "UserInfo", "MyPhone0" }, - nullptr, EIF_DISABLED_BY_DEFAULT }, - { "email", LPGEN("E-mail"), SKINICON_OTHER_SENDEMAIL, - { nullptr, "e-mail", "UserInfo", "e-mail", "UserInfo", "Mye-mail0" }, - &EmailOnClick, EIF_DISABLED_BY_DEFAULT }, -}; - -static void SetExtraIcons(MCONTACT hContact) -{ - if (hContact == 0) - return; - - char *proto = Proto_GetBaseAccountName(hContact); - if ( IsEmpty(proto)) - return; - - for (auto &p : infos) { - for (unsigned int j = 0; j < _countof(p.db); j += 2) { - if (p.db[j + 1] == nullptr) - break; - - ptrA szValue(db_get_sa(hContact, p.db[j] == nullptr ? proto : p.db[j], p.db[j + 1])); - if (!IsEmpty(szValue)) { - ExtraIcon_SetIcon(p.hExtraIcon, hContact, p.hIcolib); - break; - } - } - } -} - -static int SettingChanged(WPARAM hContact, LPARAM lParam) -{ - if (hContact == 0) - return 0; - - char *proto = Proto_GetBaseAccountName(hContact); - if (IsEmpty(proto)) - return 0; - - DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING*)lParam; - bool isProto = (strcmp(cws->szModule, proto) == 0); - if (isProto && !strcmp(cws->szSetting, "ApparentMode")) { - SetVisibility(hContact, cws->value.type == DBVT_DELETED ? 0 : cws->value.wVal, true); - return 0; - } - - if (!strcmp(cws->szModule, "SRMM") && !strcmp(cws->szSetting, "MuteMode")) { - SetChatMute(hContact, cws->value.type == DBVT_DELETED ? CHATMODE_NORMAL : cws->value.bVal); - return 0; - } - - if (!strcmp(cws->szSetting, "Gender") && (isProto || !strcmp(cws->szModule, "UserInfo"))) { - SetGender(hContact, cws->value.type == DBVT_DELETED ? 0 : cws->value.bVal, true); - return 0; - } - - for (auto &p : infos) { - for (int j = 0; j < _countof(p.db); j += 2) { - if (p.db[j + 1] == nullptr) - break; - if (p.db[j] == nullptr && !isProto) - continue; - if (p.db[j] != nullptr && strcmp(cws->szModule, p.db[j])) - continue; - if (strcmp(cws->szSetting, p.db[j + 1])) - continue; - - bool show = (cws->value.type != DBVT_DELETED && !IsEmpty(cws->value.pszVal)); - ExtraIcon_SetIcon(p.hExtraIcon, hContact, show ? p.hIcolib : nullptr); - break; - } - } - - return 0; -} - -static int DefaultOnClick(WPARAM hContact, LPARAM, LPARAM param) -{ - Info *p = (Info*)param; - if (p == nullptr) - return 0; - - if (hContact == 0) - return 0; - - char *proto = Proto_GetBaseAccountName(hContact); - if (IsEmpty(proto)) - return 0; - - bool found = false; - for (int j = 0; !found && j < _countof(p->db); j += 2) { - if (p->db[j + 1] == nullptr) - break; - - ptrA szValue(db_get_sa(hContact, p->db[j] == nullptr ? proto : p->db[j], p->db[j + 1])); - if (!IsEmpty(szValue)) { - p->OnClick(p, szValue); - found = true; - } - } - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// Protocol icon - -struct ProtoInfo -{ - ProtoInfo(LPCSTR _proto, HANDLE _image) : - proto(mir_strdup(_proto)), - hImage(_image) - {} - - ptrA proto; - HANDLE hImage; -}; - -static int CompareProtos(const ProtoInfo *p1, const ProtoInfo *p2) -{ return mir_strcmp(p1->proto, p2->proto); -} - -OBJLIST<ProtoInfo> arProtos(10, CompareProtos); - -static int ProtocolRebuildIcons(WPARAM, LPARAM) -{ - arProtos.destroy(); - return 0; -} - -static ProtoInfo* FindProto(const char *proto) -{ - ProtoInfo *p = arProtos.find((ProtoInfo*)&proto); - if (p) - return p; - - HICON hIcon = Skin_LoadProtoIcon(proto, ID_STATUS_ONLINE); - if (hIcon == nullptr) - return nullptr; - - HANDLE hImage = ExtraIcon_AddIcon(hIcon); - if (hImage == INVALID_HANDLE_VALUE) - return nullptr; - - p = new ProtoInfo(proto, hImage); - arProtos.insert(p); - return p; -} - -static int ProtocolApplyIcon(WPARAM hContact, LPARAM) -{ - char *proto = Proto_GetBaseAccountName(hContact); - if (IsEmpty(proto)) - return 0; - - HANDLE hImage = INVALID_HANDLE_VALUE; - ProtoInfo *pi = FindProto(proto); - if (pi != nullptr) - hImage = pi->hImage; - - ExtraIcon_SetIcon(hExtraProto, hContact, hImage); - return 0; -} - -static int ProtocolOnClick(WPARAM wParam, LPARAM, LPARAM) -{ - if (wParam) - CallService(MS_USERINFO_SHOWDIALOG, wParam, 0); - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////// - -void DefaultExtraIcons_Load() -{ - hExtraChat = ExtraIcon_RegisterIcolib("chat_activity", LPGEN("Chat activity"), "ChatActivity"); - hExtraChatMute = ExtraIcon_RegisterIcolib("chat_mute", LPGEN("Chat mute mode"), "ChatMute"); - hExtraVisibility = ExtraIcon_RegisterIcolib("visibility", "Visibility", Skin_GetIconHandle(SKINICON_OTHER_VISIBLE_ALL)); - hExtraGender = ExtraIcon_RegisterIcolib("gender", "Gender", "gender_male", nullptr, 0, EIF_DISABLED_BY_DEFAULT); - hExtraProto = ExtraIcon_RegisterCallback("protocol", "Account", Skin_GetIconHandle(SKINICON_OTHER_ACCMGR), - &ProtocolRebuildIcons, &ProtocolApplyIcon, &ProtocolOnClick, 0, EIF_DISABLED_BY_DEFAULT); - - for (auto &p : infos) { - p.hIcolib = Skin_GetIconHandle(p.iSkinIcon); - if (p.OnClick) - p.hExtraIcon = ExtraIcon_RegisterIcolib(p.name, p.desc, Skin_GetIconHandle(p.iSkinIcon), DefaultOnClick, (LPARAM)&p, p.flags); - else - p.hExtraIcon = ExtraIcon_RegisterIcolib(p.name, p.desc, Skin_GetIconHandle(p.iSkinIcon), nullptr, 0, p.flags); - } - - for (auto &hContact : Contacts()) { - SetChatMute(hContact, -1); - SetExtraIcons(hContact); - SetVisibility(hContact, -1, false); - SetGender(hContact, -1, false); - } - - HookEvent(ME_DB_CONTACT_SETTINGCHANGED, SettingChanged); -} +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-23 Miranda NG team
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "m_cluiframes.h"
+
+#include "extraicons.h"
+#include "chat.h"
+
+////////////////////////////////////////////////////////////////////////////////////////
+// DB extra icons
+
+HANDLE hExtraVisibility, hExtraChat, hExtraChatMute, hExtraGender, hExtraProto;
+
+static void SetVisibility(MCONTACT hContact, int apparentMode, bool clear)
+{
+ if (hContact == 0)
+ return;
+
+ char *proto = Proto_GetBaseAccountName(hContact);
+ if (IsEmpty(proto))
+ return;
+
+ if (apparentMode <= 0)
+ apparentMode = db_get_w(hContact, proto, "ApparentMode", 0);
+
+ HANDLE hExtraIcon, hIcolib = nullptr;
+
+ // Is chat
+ if (Contact::IsGroupChat(hContact, proto)) {
+ hExtraIcon = hExtraChat;
+ if (apparentMode == ID_STATUS_OFFLINE)
+ hIcolib = IcoLib_GetIconHandle("ChatActivity");
+ }
+ else { // Not chat
+ hExtraIcon = hExtraVisibility;
+ if (apparentMode == ID_STATUS_OFFLINE)
+ hIcolib = Skin_GetIconHandle(SKINICON_OTHER_INVISIBLE_ALL);
+ else if (apparentMode == ID_STATUS_ONLINE)
+ hIcolib = Skin_GetIconHandle(SKINICON_OTHER_VISIBLE_ALL);
+ }
+
+ if (hIcolib != nullptr || clear)
+ ExtraIcon_SetIcon(hExtraIcon, hContact, hIcolib);
+}
+
+static void SetGender(MCONTACT hContact, int gender, bool clear)
+{
+ if (hContact == 0)
+ return;
+
+ char *proto = Proto_GetBaseAccountName(hContact);
+ if (IsEmpty(proto))
+ return;
+
+ if (gender <= 0)
+ gender = db_get_b(hContact, proto, "Gender", 0);
+ if (gender <= 0)
+ gender = db_get_b(hContact, "UserInfo", "Gender", 0);
+
+ const char *ico;
+ if (gender == 'M')
+ ico = "gender_male";
+ else if (gender == 'F')
+ ico = "gender_female";
+ else
+ ico = nullptr;
+
+ if (ico != nullptr || clear)
+ ExtraIcon_SetIconByName(hExtraGender, hContact, ico);
+}
+
+static void SetChatMute(MCONTACT hContact, int mode)
+{
+ if (hContact == 0)
+ return;
+
+ if (mode == -1)
+ mode = db_get_b(hContact, "SRMM", "MuteMode", CHATMODE_NORMAL);
+
+ HANDLE hIcon;
+ switch (mode) {
+ case CHATMODE_MUTE: hIcon = Skin_GetIconHandle(SKINICON_OTHER_OFF); break;
+ case CHATMODE_UNMUTE: hIcon = Skin_GetIconHandle(SKINICON_OTHER_ON); break;
+ default:
+ hIcon = nullptr; break;
+ }
+
+ ExtraIcon_SetIcon(hExtraChatMute, hContact, hIcon);
+}
+
+struct Info
+{
+ const char *name;
+ const char *desc;
+ int iSkinIcon;
+ const char *db[8];
+ void(*OnClick)(Info *info, const char *text);
+ int flags;
+
+ HANDLE hIcolib, hExtraIcon;
+};
+
+static void EmailOnClick(Info*, const char *text)
+{
+ char cmd[1024];
+ mir_snprintf(cmd, "mailto:%s", text);
+ ShellExecuteA(nullptr, "open", cmd, nullptr, nullptr, SW_SHOW);
+}
+
+static void HomepageOnClick(Info*, const char *text)
+{
+ ShellExecuteA(nullptr, "open", text, nullptr, nullptr, SW_SHOW);
+}
+
+static Info infos[] =
+{
+ { "homepage", LPGEN("Homepage"), SKINICON_OTHER_MIRANDAWEB,
+ { nullptr, "Homepage", "UserInfo", "Homepage" },
+ &HomepageOnClick, EIF_DISABLED_BY_DEFAULT },
+ { "sms", LPGEN("Phone/SMS"), SKINICON_OTHER_SMS,
+ { nullptr, "Cellular", "UserInfo", "Cellular", "UserInfo", "Phone", "UserInfo", "MyPhone0" },
+ nullptr, EIF_DISABLED_BY_DEFAULT },
+ { "email", LPGEN("E-mail"), SKINICON_OTHER_SENDEMAIL,
+ { nullptr, "e-mail", "UserInfo", "e-mail", "UserInfo", "Mye-mail0" },
+ &EmailOnClick, EIF_DISABLED_BY_DEFAULT },
+};
+
+static void SetExtraIcons(MCONTACT hContact)
+{
+ if (hContact == 0)
+ return;
+
+ char *proto = Proto_GetBaseAccountName(hContact);
+ if ( IsEmpty(proto))
+ return;
+
+ for (auto &p : infos) {
+ for (unsigned int j = 0; j < _countof(p.db); j += 2) {
+ if (p.db[j + 1] == nullptr)
+ break;
+
+ ptrA szValue(db_get_sa(hContact, p.db[j] == nullptr ? proto : p.db[j], p.db[j + 1]));
+ if (!IsEmpty(szValue)) {
+ ExtraIcon_SetIcon(p.hExtraIcon, hContact, p.hIcolib);
+ break;
+ }
+ }
+ }
+}
+
+static int SettingChanged(WPARAM hContact, LPARAM lParam)
+{
+ if (hContact == 0)
+ return 0;
+
+ char *proto = Proto_GetBaseAccountName(hContact);
+ if (IsEmpty(proto))
+ return 0;
+
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING*)lParam;
+ bool isProto = (strcmp(cws->szModule, proto) == 0);
+ if (isProto && !strcmp(cws->szSetting, "ApparentMode")) {
+ SetVisibility(hContact, cws->value.type == DBVT_DELETED ? 0 : cws->value.wVal, true);
+ return 0;
+ }
+
+ if (!strcmp(cws->szModule, "SRMM") && !strcmp(cws->szSetting, "MuteMode")) {
+ SetChatMute(hContact, cws->value.type == DBVT_DELETED ? CHATMODE_NORMAL : cws->value.bVal);
+ return 0;
+ }
+
+ if (!strcmp(cws->szSetting, "Gender") && (isProto || !strcmp(cws->szModule, "UserInfo"))) {
+ SetGender(hContact, cws->value.type == DBVT_DELETED ? 0 : cws->value.bVal, true);
+ return 0;
+ }
+
+ for (auto &p : infos) {
+ for (int j = 0; j < _countof(p.db); j += 2) {
+ if (p.db[j + 1] == nullptr)
+ break;
+ if (p.db[j] == nullptr && !isProto)
+ continue;
+ if (p.db[j] != nullptr && strcmp(cws->szModule, p.db[j]))
+ continue;
+ if (strcmp(cws->szSetting, p.db[j + 1]))
+ continue;
+
+ bool show = (cws->value.type != DBVT_DELETED && !IsEmpty(cws->value.pszVal));
+ ExtraIcon_SetIcon(p.hExtraIcon, hContact, show ? p.hIcolib : nullptr);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int DefaultOnClick(WPARAM hContact, LPARAM, LPARAM param)
+{
+ Info *p = (Info*)param;
+ if (p == nullptr)
+ return 0;
+
+ if (hContact == 0)
+ return 0;
+
+ char *proto = Proto_GetBaseAccountName(hContact);
+ if (IsEmpty(proto))
+ return 0;
+
+ bool found = false;
+ for (int j = 0; !found && j < _countof(p->db); j += 2) {
+ if (p->db[j + 1] == nullptr)
+ break;
+
+ ptrA szValue(db_get_sa(hContact, p->db[j] == nullptr ? proto : p->db[j], p->db[j + 1]));
+ if (!IsEmpty(szValue)) {
+ p->OnClick(p, szValue);
+ found = true;
+ }
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Protocol icon
+
+struct ProtoInfo
+{
+ ProtoInfo(LPCSTR _proto, HANDLE _image) :
+ proto(mir_strdup(_proto)),
+ hImage(_image)
+ {}
+
+ ptrA proto;
+ HANDLE hImage;
+};
+
+static int CompareProtos(const ProtoInfo *p1, const ProtoInfo *p2)
+{ return mir_strcmp(p1->proto, p2->proto);
+}
+
+OBJLIST<ProtoInfo> arProtos(10, CompareProtos);
+
+static int ProtocolRebuildIcons(WPARAM, LPARAM)
+{
+ arProtos.destroy();
+ return 0;
+}
+
+static ProtoInfo* FindProto(const char *proto)
+{
+ ProtoInfo *p = arProtos.find((ProtoInfo*)&proto);
+ if (p)
+ return p;
+
+ HICON hIcon = Skin_LoadProtoIcon(proto, ID_STATUS_ONLINE);
+ if (hIcon == nullptr)
+ return nullptr;
+
+ HANDLE hImage = ExtraIcon_AddIcon(hIcon);
+ if (hImage == INVALID_HANDLE_VALUE)
+ return nullptr;
+
+ p = new ProtoInfo(proto, hImage);
+ arProtos.insert(p);
+ return p;
+}
+
+static int ProtocolApplyIcon(WPARAM hContact, LPARAM)
+{
+ char *proto = Proto_GetBaseAccountName(hContact);
+ if (IsEmpty(proto))
+ return 0;
+
+ HANDLE hImage = INVALID_HANDLE_VALUE;
+ ProtoInfo *pi = FindProto(proto);
+ if (pi != nullptr)
+ hImage = pi->hImage;
+
+ ExtraIcon_SetIcon(hExtraProto, hContact, hImage);
+ return 0;
+}
+
+static int ProtocolOnClick(WPARAM wParam, LPARAM, LPARAM)
+{
+ if (wParam)
+ CallService(MS_USERINFO_SHOWDIALOG, wParam, 0);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+
+void DefaultExtraIcons_Load()
+{
+ hExtraChat = ExtraIcon_RegisterIcolib("chat_activity", LPGEN("Chat activity"), "ChatActivity");
+ hExtraChatMute = ExtraIcon_RegisterIcolib("chat_mute", LPGEN("Chat mute mode"), "ChatMute");
+ hExtraVisibility = ExtraIcon_RegisterIcolib("visibility", "Visibility", Skin_GetIconHandle(SKINICON_OTHER_VISIBLE_ALL));
+ hExtraGender = ExtraIcon_RegisterIcolib("gender", "Gender", "gender_male", nullptr, 0, EIF_DISABLED_BY_DEFAULT);
+ hExtraProto = ExtraIcon_RegisterCallback("protocol", "Account", Skin_GetIconHandle(SKINICON_OTHER_ACCMGR),
+ &ProtocolRebuildIcons, &ProtocolApplyIcon, &ProtocolOnClick, 0, EIF_DISABLED_BY_DEFAULT);
+
+ for (auto &p : infos) {
+ p.hIcolib = Skin_GetIconHandle(p.iSkinIcon);
+ if (p.OnClick)
+ p.hExtraIcon = ExtraIcon_RegisterIcolib(p.name, p.desc, Skin_GetIconHandle(p.iSkinIcon), DefaultOnClick, (LPARAM)&p, p.flags);
+ else
+ p.hExtraIcon = ExtraIcon_RegisterIcolib(p.name, p.desc, Skin_GetIconHandle(p.iSkinIcon), nullptr, 0, p.flags);
+ }
+
+ for (auto &hContact : Contacts()) {
+ SetChatMute(hContact, -1);
+ SetExtraIcons(hContact);
+ SetVisibility(hContact, -1, false);
+ SetGender(hContact, -1, false);
+ }
+
+ HookEvent(ME_DB_CONTACT_SETTINGCHANGED, SettingChanged);
+}
diff --git a/src/mir_app/src/ei_extraIcon.cpp b/src/mir_app/src/ei_extraIcon.cpp index ecec626b7e..fbdfc901ec 100644 --- a/src/mir_app/src/ei_extraIcon.cpp +++ b/src/mir_app/src/ei_extraIcon.cpp @@ -1,83 +1,83 @@ -/* - -Copyright (C) 2009 Ricardo Pescuma Domenecci -Copyright (C) 2012-22 Miranda NG team - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this file; see the file license.txt. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "extraicons.h" - -ExtraIcon::ExtraIcon(const char *name) : - m_szName(mir_strdup(name)) -{ -} - -ExtraIcon::~ExtraIcon() -{ -} - -const char *ExtraIcon::getName() const -{ - return m_szName; -} - -int ExtraIcon::getSlot() const -{ - return m_slot; -} - -void ExtraIcon::setSlot(int slot) -{ - m_slot = slot; -} - -int ExtraIcon::getPosition() const -{ - return m_position; -} - -void ExtraIcon::setPosition(int position) -{ - m_position = position; -} - -bool ExtraIcon::isEnabled() const -{ - return m_slot >= 0; -} - -void ExtraIcon::doApply(MCONTACT hContact) -{ - if (m_pParent) - m_pParent->applyIcon(hContact); - else - applyIcon(hContact); -} - -void ExtraIcon::applyIcons() -{ - if (!isEnabled()) - return; - - for (auto &hContact : Contacts()) { - // Clear to assert that it will be cleared - Clist_SetExtraIcon(hContact, m_slot, INVALID_HANDLE_VALUE); - doApply(hContact); - } -} +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-23 Miranda NG team
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+ExtraIcon::ExtraIcon(const char *name) :
+ m_szName(mir_strdup(name))
+{
+}
+
+ExtraIcon::~ExtraIcon()
+{
+}
+
+const char *ExtraIcon::getName() const
+{
+ return m_szName;
+}
+
+int ExtraIcon::getSlot() const
+{
+ return m_slot;
+}
+
+void ExtraIcon::setSlot(int slot)
+{
+ m_slot = slot;
+}
+
+int ExtraIcon::getPosition() const
+{
+ return m_position;
+}
+
+void ExtraIcon::setPosition(int position)
+{
+ m_position = position;
+}
+
+bool ExtraIcon::isEnabled() const
+{
+ return m_slot >= 0;
+}
+
+void ExtraIcon::doApply(MCONTACT hContact)
+{
+ if (m_pParent)
+ m_pParent->applyIcon(hContact);
+ else
+ applyIcon(hContact);
+}
+
+void ExtraIcon::applyIcons()
+{
+ if (!isEnabled())
+ return;
+
+ for (auto &hContact : Contacts()) {
+ // Clear to assert that it will be cleared
+ Clist_SetExtraIcon(hContact, m_slot, INVALID_HANDLE_VALUE);
+ doApply(hContact);
+ }
+}
diff --git a/src/mir_app/src/ei_groupIcon.cpp b/src/mir_app/src/ei_groupIcon.cpp index 50bea072e0..44dcbbd686 100644 --- a/src/mir_app/src/ei_groupIcon.cpp +++ b/src/mir_app/src/ei_groupIcon.cpp @@ -1,200 +1,200 @@ -/* - -Copyright (C) 2009 Ricardo Pescuma Domenecci -Copyright (C) 2012-22 Miranda NG team - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this file; see the file license.txt. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "extraicons.h" - -ExtraIconGroup::ExtraIconGroup(const char *_name) : - ExtraIcon(_name), - m_items(1) -{ - db_set_resident(EI_MODULE_NAME, _name); -} - -ExtraIconGroup::~ExtraIconGroup() -{ -} - -void ExtraIconGroup::addExtraIcon(BaseExtraIcon *extra) -{ - m_items.insert(extra); - - CMStringW description; - for (auto &p : m_items) { - if (!description.IsEmpty()) - description.Append(L" / "); - description += p->getDescription(); - } - - m_tszDescription = mir_wstrdup(description); -} - -void ExtraIconGroup::rebuildIcons() -{ - for (auto &p : m_items) - p->rebuildIcons(); -} - -void ExtraIconGroup::applyIcon(MCONTACT hContact) -{ - if (!isEnabled() || hContact == 0) - return; - - m_setValidExtraIcon = false; - m_insideApply = true; - - for (auto &p : m_items) { - p->applyIcon(hContact); - if (m_setValidExtraIcon) { - m_pCurrentItem = p; - break; - } - } - - m_insideApply = false; -} - -int ExtraIconGroup::getPosition() const -{ - int pos = INT_MAX; - for (auto &p : m_items) - pos = min(pos, p->getPosition()); - return pos; -} - -void ExtraIconGroup::setSlot(int slot) -{ - ExtraIcon::setSlot(slot); - - for (auto &p : m_items) - p->setSlot(slot); -} - -void ExtraIconGroup::onClick(MCONTACT hContact) -{ - if (m_pCurrentItem != nullptr) - m_pCurrentItem->onClick(hContact); -} - -int ExtraIconGroup::setIcon(MCONTACT, HANDLE) -{ - return -1; - // return internalSetIcon(hContact, (void*)value, false); -} - -int ExtraIconGroup::setIconByName(MCONTACT, const char*) -{ - return -1; - // return internalSetIcon(hContact, (void*)value, true); -} - -int ExtraIconGroup::internalSetIcon(ExtraIcon *pChild, MCONTACT hContact, HANDLE value, bool bByName) -{ - if (m_insideApply) { - for (auto &p : m_items) - if (p == pChild) { - if (bByName) - return p->setIconByName(hContact, (const char*)value); - return p->setIcon(hContact, value); - } - - return -1; - } - - int currentPos = m_items.getCount(); - int storePos = m_items.getCount(); - for (int i = 0; i < m_items.getCount(); i++) { - if (m_items[i] == pChild) - storePos = i; - - if (m_items[i] == m_pCurrentItem) - currentPos = i; - } - - if (storePos == m_items.getCount()) - return -1; - - if (storePos > currentPos) { - m_items[storePos]->storeIcon(hContact, value); - return 0; - } - - // Ok, we have to set the icon, but we have to assert it is a valid icon - m_setValidExtraIcon = false; - - int ret; - if (bByName) - ret = m_items[storePos]->setIconByName(hContact, (const char*)value); - else - ret = m_items[storePos]->setIcon(hContact, (HANDLE)value); - - if (storePos < currentPos) { - if (m_setValidExtraIcon) - m_pCurrentItem = m_items[storePos]; - } - else if (storePos == currentPos) { - if (!m_setValidExtraIcon) { - m_pCurrentItem = nullptr; - - m_insideApply = true; - - for (++storePos; storePos < m_items.getCount(); ++storePos) { - m_items[storePos]->applyIcon(hContact); - if (m_setValidExtraIcon) { - m_pCurrentItem = m_items[storePos]; - break; - } - } - - m_insideApply = false; - } - } - - return ret; -} - -const wchar_t* ExtraIconGroup::getDescription() const -{ - return m_tszDescription; -} - -HANDLE ExtraIconGroup::getDescIcon() const -{ - for (auto &p : m_items) - if (HANDLE ret = p->getDescIcon()) - return ret; - - return nullptr; -} - -int ExtraIconGroup::getType() const -{ - return EXTRAICON_TYPE_GROUP; -} - -int ExtraIconGroup::ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage) -{ - if (hImage != INVALID_HANDLE_VALUE) - m_setValidExtraIcon = true; - - return Clist_SetExtraIcon(hContact, m_slot, hImage); -} +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-23 Miranda NG team
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+ExtraIconGroup::ExtraIconGroup(const char *_name) :
+ ExtraIcon(_name),
+ m_items(1)
+{
+ db_set_resident(EI_MODULE_NAME, _name);
+}
+
+ExtraIconGroup::~ExtraIconGroup()
+{
+}
+
+void ExtraIconGroup::addExtraIcon(BaseExtraIcon *extra)
+{
+ m_items.insert(extra);
+
+ CMStringW description;
+ for (auto &p : m_items) {
+ if (!description.IsEmpty())
+ description.Append(L" / ");
+ description += p->getDescription();
+ }
+
+ m_tszDescription = mir_wstrdup(description);
+}
+
+void ExtraIconGroup::rebuildIcons()
+{
+ for (auto &p : m_items)
+ p->rebuildIcons();
+}
+
+void ExtraIconGroup::applyIcon(MCONTACT hContact)
+{
+ if (!isEnabled() || hContact == 0)
+ return;
+
+ m_setValidExtraIcon = false;
+ m_insideApply = true;
+
+ for (auto &p : m_items) {
+ p->applyIcon(hContact);
+ if (m_setValidExtraIcon) {
+ m_pCurrentItem = p;
+ break;
+ }
+ }
+
+ m_insideApply = false;
+}
+
+int ExtraIconGroup::getPosition() const
+{
+ int pos = INT_MAX;
+ for (auto &p : m_items)
+ pos = min(pos, p->getPosition());
+ return pos;
+}
+
+void ExtraIconGroup::setSlot(int slot)
+{
+ ExtraIcon::setSlot(slot);
+
+ for (auto &p : m_items)
+ p->setSlot(slot);
+}
+
+void ExtraIconGroup::onClick(MCONTACT hContact)
+{
+ if (m_pCurrentItem != nullptr)
+ m_pCurrentItem->onClick(hContact);
+}
+
+int ExtraIconGroup::setIcon(MCONTACT, HANDLE)
+{
+ return -1;
+ // return internalSetIcon(hContact, (void*)value, false);
+}
+
+int ExtraIconGroup::setIconByName(MCONTACT, const char*)
+{
+ return -1;
+ // return internalSetIcon(hContact, (void*)value, true);
+}
+
+int ExtraIconGroup::internalSetIcon(ExtraIcon *pChild, MCONTACT hContact, HANDLE value, bool bByName)
+{
+ if (m_insideApply) {
+ for (auto &p : m_items)
+ if (p == pChild) {
+ if (bByName)
+ return p->setIconByName(hContact, (const char*)value);
+ return p->setIcon(hContact, value);
+ }
+
+ return -1;
+ }
+
+ int currentPos = m_items.getCount();
+ int storePos = m_items.getCount();
+ for (int i = 0; i < m_items.getCount(); i++) {
+ if (m_items[i] == pChild)
+ storePos = i;
+
+ if (m_items[i] == m_pCurrentItem)
+ currentPos = i;
+ }
+
+ if (storePos == m_items.getCount())
+ return -1;
+
+ if (storePos > currentPos) {
+ m_items[storePos]->storeIcon(hContact, value);
+ return 0;
+ }
+
+ // Ok, we have to set the icon, but we have to assert it is a valid icon
+ m_setValidExtraIcon = false;
+
+ int ret;
+ if (bByName)
+ ret = m_items[storePos]->setIconByName(hContact, (const char*)value);
+ else
+ ret = m_items[storePos]->setIcon(hContact, (HANDLE)value);
+
+ if (storePos < currentPos) {
+ if (m_setValidExtraIcon)
+ m_pCurrentItem = m_items[storePos];
+ }
+ else if (storePos == currentPos) {
+ if (!m_setValidExtraIcon) {
+ m_pCurrentItem = nullptr;
+
+ m_insideApply = true;
+
+ for (++storePos; storePos < m_items.getCount(); ++storePos) {
+ m_items[storePos]->applyIcon(hContact);
+ if (m_setValidExtraIcon) {
+ m_pCurrentItem = m_items[storePos];
+ break;
+ }
+ }
+
+ m_insideApply = false;
+ }
+ }
+
+ return ret;
+}
+
+const wchar_t* ExtraIconGroup::getDescription() const
+{
+ return m_tszDescription;
+}
+
+HANDLE ExtraIconGroup::getDescIcon() const
+{
+ for (auto &p : m_items)
+ if (HANDLE ret = p->getDescIcon())
+ return ret;
+
+ return nullptr;
+}
+
+int ExtraIconGroup::getType() const
+{
+ return EXTRAICON_TYPE_GROUP;
+}
+
+int ExtraIconGroup::ClistSetExtraIcon(MCONTACT hContact, HANDLE hImage)
+{
+ if (hImage != INVALID_HANDLE_VALUE)
+ m_setValidExtraIcon = true;
+
+ return Clist_SetExtraIcon(hContact, m_slot, hImage);
+}
diff --git a/src/mir_app/src/ei_icolibIcon.cpp b/src/mir_app/src/ei_icolibIcon.cpp index 07f4a2b3df..7095c96902 100644 --- a/src/mir_app/src/ei_icolibIcon.cpp +++ b/src/mir_app/src/ei_icolibIcon.cpp @@ -1,118 +1,118 @@ -/* - -Copyright (C) 2009 Ricardo Pescuma Domenecci -Copyright (C) 2012-22 Miranda NG team - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this file; see the file license.txt. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "extraicons.h" -#include "usedIcons.h" - -#include "IcoLib.h" - -IcolibExtraIcon::IcolibExtraIcon(const char *_name, const wchar_t *_description, HANDLE _descIcon, MIRANDAHOOKPARAM _OnClick, LPARAM _param) : - BaseExtraIcon(_name, _description, _descIcon, _OnClick, _param) -{ - db_set_resident(EI_MODULE_NAME, _name); -} - -IcolibExtraIcon::~IcolibExtraIcon() -{ -} - -int IcolibExtraIcon::getType() const -{ - return EXTRAICON_TYPE_ICOLIB; -} - -void IcolibExtraIcon::rebuildIcons() -{ -} - -void IcolibExtraIcon::applyIcon(MCONTACT hContact) -{ - if (!isEnabled() || hContact == 0) - return; - - HANDLE hImage = INVALID_HANDLE_VALUE; - - ptrA szIconName(db_get_sa(hContact, EI_MODULE_NAME, m_szName)); - if (!IsEmpty(szIconName)) - hImage = GetIcon(szIconName); - - ClistSetExtraIcon(hContact, hImage); -} - -int IcolibExtraIcon::setIcon(MCONTACT hContact, HANDLE hIcoLib) -{ - if (hContact == 0) - return -1; - - if (hIcoLib == INVALID_HANDLE_VALUE) - hIcoLib = nullptr; - - if (isEnabled()) { - ptrA szIconName(db_get_sa(hContact, EI_MODULE_NAME, m_szName)); - if (!IsEmpty(szIconName)) - RemoveIcon(szIconName); - } - - IcolibItem *p = (IcolibItem*)hIcoLib; - char *szName = (p) ? p->name : nullptr; - storeIcon(hContact, szName); - - if (isEnabled()) - return ClistSetExtraIcon(hContact, (hIcoLib == nullptr) ? INVALID_HANDLE_VALUE : AddIcon(szName)); - - return 0; -} - -int IcolibExtraIcon::setIconByName(MCONTACT hContact, const char *icon) -{ - if (hContact == 0) - return -1; - - if (icon == INVALID_HANDLE_VALUE) - icon = nullptr; - - if (isEnabled()) { - ptrA szIconName(db_get_sa(hContact, EI_MODULE_NAME, m_szName)); - if (!IsEmpty(szIconName)) - RemoveIcon(szIconName); - } - - storeIcon(hContact, (char*)icon); - - if (isEnabled()) - return ClistSetExtraIcon(hContact, (IsEmpty(icon)) ? INVALID_HANDLE_VALUE : AddIcon(icon)); - - return 0; -} - -void IcolibExtraIcon::storeIcon(MCONTACT hContact, void *icon) -{ - if (hContact == 0) - return; - - const char *icolibName = (const char *)icon; - if (IsEmpty(icolibName)) - db_unset(hContact, EI_MODULE_NAME, m_szName); - else - db_set_s(hContact, EI_MODULE_NAME, m_szName, icolibName); -} +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-23 Miranda NG team
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+#include "usedIcons.h"
+
+#include "IcoLib.h"
+
+IcolibExtraIcon::IcolibExtraIcon(const char *_name, const wchar_t *_description, HANDLE _descIcon, MIRANDAHOOKPARAM _OnClick, LPARAM _param) :
+ BaseExtraIcon(_name, _description, _descIcon, _OnClick, _param)
+{
+ db_set_resident(EI_MODULE_NAME, _name);
+}
+
+IcolibExtraIcon::~IcolibExtraIcon()
+{
+}
+
+int IcolibExtraIcon::getType() const
+{
+ return EXTRAICON_TYPE_ICOLIB;
+}
+
+void IcolibExtraIcon::rebuildIcons()
+{
+}
+
+void IcolibExtraIcon::applyIcon(MCONTACT hContact)
+{
+ if (!isEnabled() || hContact == 0)
+ return;
+
+ HANDLE hImage = INVALID_HANDLE_VALUE;
+
+ ptrA szIconName(db_get_sa(hContact, EI_MODULE_NAME, m_szName));
+ if (!IsEmpty(szIconName))
+ hImage = GetIcon(szIconName);
+
+ ClistSetExtraIcon(hContact, hImage);
+}
+
+int IcolibExtraIcon::setIcon(MCONTACT hContact, HANDLE hIcoLib)
+{
+ if (hContact == 0)
+ return -1;
+
+ if (hIcoLib == INVALID_HANDLE_VALUE)
+ hIcoLib = nullptr;
+
+ if (isEnabled()) {
+ ptrA szIconName(db_get_sa(hContact, EI_MODULE_NAME, m_szName));
+ if (!IsEmpty(szIconName))
+ RemoveIcon(szIconName);
+ }
+
+ IcolibItem *p = (IcolibItem*)hIcoLib;
+ char *szName = (p) ? p->name : nullptr;
+ storeIcon(hContact, szName);
+
+ if (isEnabled())
+ return ClistSetExtraIcon(hContact, (hIcoLib == nullptr) ? INVALID_HANDLE_VALUE : AddIcon(szName));
+
+ return 0;
+}
+
+int IcolibExtraIcon::setIconByName(MCONTACT hContact, const char *icon)
+{
+ if (hContact == 0)
+ return -1;
+
+ if (icon == INVALID_HANDLE_VALUE)
+ icon = nullptr;
+
+ if (isEnabled()) {
+ ptrA szIconName(db_get_sa(hContact, EI_MODULE_NAME, m_szName));
+ if (!IsEmpty(szIconName))
+ RemoveIcon(szIconName);
+ }
+
+ storeIcon(hContact, (char*)icon);
+
+ if (isEnabled())
+ return ClistSetExtraIcon(hContact, (IsEmpty(icon)) ? INVALID_HANDLE_VALUE : AddIcon(icon));
+
+ return 0;
+}
+
+void IcolibExtraIcon::storeIcon(MCONTACT hContact, void *icon)
+{
+ if (hContact == 0)
+ return;
+
+ const char *icolibName = (const char *)icon;
+ if (IsEmpty(icolibName))
+ db_unset(hContact, EI_MODULE_NAME, m_szName);
+ else
+ db_set_s(hContact, EI_MODULE_NAME, m_szName, icolibName);
+}
diff --git a/src/mir_app/src/ei_options.cpp b/src/mir_app/src/ei_options.cpp index 442c120fbb..11e98abd18 100644 --- a/src/mir_app/src/ei_options.cpp +++ b/src/mir_app/src/ei_options.cpp @@ -1,481 +1,481 @@ -/* - -Copyright (C) 2009 Ricardo Pescuma Domenecci -Copyright (C) 2012-22 Miranda NG team - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this file; see the file license.txt. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "extraicons.h" - -static class CExtraIconOptsDlg *pGlgOptions; - -int SortFunc(const ExtraIcon *p1, const ExtraIcon *p2); - -struct intlist -{ - intlist() : count(0), data(nullptr) {} - ~intlist() { mir_free(data); } - - void add(int val) - { - data = (int*)mir_realloc(data, sizeof(int)*(count + 1)); - data[count++] = val; - } - - int count; - int *data; -}; - -static int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM) -{ - intlist *a = (intlist*)lParam1; - intlist *b = (intlist*)lParam2; - return SortFunc(registeredExtraIcons[a->data[0] - 1], registeredExtraIcons[b->data[0] - 1]); -} - -// Functions ////////////////////////////////////////////////////////////////////////////////////// - -BOOL ScreenToClient(HWND hWnd, LPRECT lpRect) -{ - POINT pt; - pt.x = lpRect->left; - pt.y = lpRect->top; - - BOOL ret = ScreenToClient(hWnd, &pt); - if (!ret) - return ret; - - lpRect->left = pt.x; - lpRect->top = pt.y; - - pt.x = lpRect->right; - pt.y = lpRect->bottom; - - ret = ScreenToClient(hWnd, &pt); - - lpRect->right = pt.x; - lpRect->bottom = pt.y; - - return ret; -} - -static void RemoveExtraIcons(int slot) -{ - for (auto &hContact : Contacts()) - Clist_SetExtraIcon(hContact, slot, INVALID_HANDLE_VALUE); -} - -class CExtraIconOptsDlg : public CDlgBase -{ - intlist* Tree_GetIDs(HTREEITEM hItem) - { - TVITEMEX tvi; - tvi.mask = TVIF_HANDLE | TVIF_PARAM; - tvi.hItem = hItem; - m_tree.GetItem(&tvi); - return (intlist*)tvi.lParam; - } - - HTREEITEM Tree_AddExtraIcon(BaseExtraIcon *extra, bool selected, HTREEITEM hAfter = TVI_LAST) - { - intlist *ids = new intlist(); - ids->add(extra->getID()); - - TVINSERTSTRUCT tvis = {}; - tvis.hInsertAfter = hAfter; - tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE; - tvis.item.stateMask = TVIS_STATEIMAGEMASK; - tvis.item.iSelectedImage = tvis.item.iImage = extra->getID(); - tvis.item.lParam = (LPARAM)ids; - tvis.item.pszText = (LPTSTR)extra->getDescription(); - tvis.item.state = INDEXTOSTATEIMAGEMASK(selected ? 2 : 1); - return m_tree.InsertItem(&tvis); - } - - HTREEITEM Tree_AddExtraIconGroup(intlist &group, bool selected, HTREEITEM hAfter = TVI_LAST) - { - intlist *ids = new intlist(); - CMStringW desc; - int img = 0; - for (int i = 0; i < group.count; i++) { - BaseExtraIcon *extra = registeredExtraIcons[group.data[i] - 1]; - ids->add(extra->getID()); - - if (img == 0 && extra->getDescIcon() != nullptr) - img = extra->getID(); - - if (i > 0) - desc += L" / "; - desc += extra->getDescription(); - } - - TVINSERTSTRUCT tvis = {}; - tvis.hInsertAfter = hAfter; - tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE; - tvis.item.stateMask = TVIS_STATEIMAGEMASK; - tvis.item.iSelectedImage = tvis.item.iImage = img; - tvis.item.lParam = (LPARAM)ids; - tvis.item.pszText = (wchar_t*)desc.c_str(); - tvis.item.state = INDEXTOSTATEIMAGEMASK(selected ? 2 : 1); - return m_tree.InsertItem(&tvis); - } - - void GroupSelectedItems() - { - LIST<_TREEITEM> toRemove(1); - intlist ids; - bool selected = false; - HTREEITEM hPlace = nullptr; - - // Find items - HTREEITEM hItem = m_tree.GetRoot(); - TVITEMEX tvi = { 0 }; - tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT | TVIF_STATE; - while (hItem) { - if (m_tree.IsSelected(hItem)) { - if (hPlace == nullptr) - hPlace = hItem; - - tvi.hItem = hItem; - m_tree.GetItem(&tvi); - - intlist *iids = (intlist*)tvi.lParam; - for (int i = 0; i < iids->count; i++) - ids.add(iids->data[i]); - - if ((tvi.state & INDEXTOSTATEIMAGEMASK(3)) == INDEXTOSTATEIMAGEMASK(2)) - selected = true; - - toRemove.insert(hItem); - } - - hItem = m_tree.GetNextSibling(hItem); - } - - if (hPlace != nullptr) { - // Add new - HTREEITEM hNew = Tree_AddExtraIconGroup(ids, selected, hPlace); - - // Remove old - for (auto &p : toRemove) { - delete Tree_GetIDs(p); - m_tree.DeleteItem(p); - } - - // Select - m_tree.UnselectAll(); - m_tree.SelectItem(hNew); - } - } - - void UngroupSelectedItems() - { - HTREEITEM hItem = m_tree.GetSelection(); - if (hItem == nullptr) - return; - - intlist *ids = Tree_GetIDs(hItem); - if (ids->count < 2) - return; - - bool selected = m_tree.IsSelected(hItem); - - for (int i = ids->count - 1; i >= 0; i--) { - BaseExtraIcon *extra = registeredExtraIcons[ids->data[i] - 1]; - Tree_AddExtraIcon(extra, selected, hItem); - } - - delete ids; - m_tree.DeleteItem(hItem); - - m_tree.UnselectAll(); - } - - int ShowPopup(int popup) - { - // Fix selection - HTREEITEM hSelected = m_tree.GetDropHilight(); - HTREEITEM hItem = m_tree.GetRoot(); - while (hItem) { - if (hItem != hSelected && m_tree.IsSelected(hItem)) - m_tree.DropHilite(hItem); - - hItem = m_tree.GetNextSibling(hItem); - } - - HMENU menu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_OPT_POPUP)); - HMENU submenu = GetSubMenu(menu, popup); - TranslateMenu(submenu); - - uint32_t pos = GetMessagePos(); - int ret = TrackPopupMenu(submenu, TPM_TOPALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_LEFTALIGN, LOWORD(pos), HIWORD(pos), 0, m_hwnd, nullptr); - - DestroyMenu(menu); - - // Revert selection - hItem = m_tree.GetRoot(); - while (hItem) { - if (hItem != hSelected && m_tree.IsSelected(hItem)) - m_tree.DropUnhilite(hItem); - hItem = m_tree.GetNextSibling(hItem); - } - - return ret; - } - - CCtrlTreeView m_tree; - CTimer m_timer; - -public: - CExtraIconOptsDlg() : - CDlgBase(g_plugin, IDD_EI_OPTIONS), - m_tree(this, IDC_EXTRAORDER), - m_timer(this, 1) - { - m_tree.SetFlags(MTREE_DND | MTREE_MULTISELECT); - m_tree.OnRightClick = Callback(this, &CExtraIconOptsDlg::onRClick); - - m_timer.OnEvent = Callback(this, &CExtraIconOptsDlg::onTimer); - } - - bool OnInitDialog() override - { - pGlgOptions = this; - - int numSlots = EXTRA_ICON_COUNT; - if (numSlots < (int)registeredExtraIcons.getCount()) { - HWND label = GetDlgItem(m_hwnd, IDC_MAX_ICONS_L); - SetWindowText(label, CMStringW(FORMAT, TranslateT("*only the first %d icons will be shown"), numSlots)); - ShowWindow(label, SW_SHOW); - } - - BuildIconList(); - return true; - } - - bool OnApply() override - { - // Store old slots - int *oldSlots = new int[registeredExtraIcons.getCount()]; - int lastUsedSlot = -1; - for (int i = 0; i < registeredExtraIcons.getCount(); i++) { - if (registeredExtraIcons[i]->getType() != EXTRAICON_TYPE_GROUP) - oldSlots[i] = registeredExtraIcons[i]->getSlot(); - else // Remove old slot for groups to re-set images - oldSlots[i] = -1; - lastUsedSlot = max(lastUsedSlot, registeredExtraIcons[i]->getSlot()); - } - lastUsedSlot = min(lastUsedSlot, EXTRA_ICON_COUNT); - - // Get user data and create new groups - LIST<ExtraIconGroup> groups(1); - - uint8_t pos = 0; - int firstEmptySlot = 0; - HTREEITEM ht = m_tree.GetRoot(); - TVITEMEX tvi; - tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE; - tvi.stateMask = TVIS_STATEIMAGEMASK; - while (ht) { - tvi.hItem = ht; - m_tree.GetItem(&tvi); - - intlist *ids = (intlist*)tvi.lParam; - if (ids == nullptr || ids->count < 1) - continue; // ??? - - bool enabled = ((tvi.state & INDEXTOSTATEIMAGEMASK(3)) == INDEXTOSTATEIMAGEMASK(2)); - int slot = (enabled ? firstEmptySlot++ : -1); - if (slot >= EXTRA_ICON_COUNT) - slot = -1; - - if (ids->count == 1) { - BaseExtraIcon *extra = registeredExtraIcons[ids->data[0] - 1]; - extra->setPosition(pos++); - extra->setSlot(slot); - } - else { - char name[128]; - mir_snprintf(name, "__group_%d", groups.getCount()); - - ExtraIconGroup *group = new ExtraIconGroup(name); - - for (int i = 0; i < ids->count; i++) { - BaseExtraIcon *extra = registeredExtraIcons[ids->data[i] - 1]; - extra->setPosition(pos++); - - group->addExtraIcon(extra); - } - - group->setSlot(slot); - groups.insert(group); - } - - ht = m_tree.GetNextSibling(ht); - } - - // Store data - for (auto &extra : registeredExtraIcons) { - char setting[512]; - mir_snprintf(setting, "Position_%s", extra->getName()); - db_set_w(0, EI_MODULE_NAME, setting, extra->getPosition()); - - mir_snprintf(setting, "Slot_%s", extra->getName()); - db_set_w(0, EI_MODULE_NAME, setting, extra->getSlot()); - } - - db_delete_module(0, EI_MODULE_NAME "Groups"); - db_set_w(0, EI_MODULE_NAME "Groups", "Count", groups.getCount()); - for (int k = 0; k < groups.getCount(); k++) { - ExtraIconGroup *group = groups[k]; - - char setting[512]; - mir_snprintf(setting, "%d_count", k); - db_set_w(0, EI_MODULE_NAME "Groups", setting, (uint16_t)group->m_items.getCount()); - - for (int j = 0; j < group->m_items.getCount(); j++) { - BaseExtraIcon *extra = group->m_items[j]; - - mir_snprintf(setting, "%d_%d", k, j); - db_set_s(0, EI_MODULE_NAME "Groups", setting, extra->getName()); - } - } - - // Clean removed slots - for (int j = firstEmptySlot; j <= lastUsedSlot; j++) - RemoveExtraIcons(j); - - // Apply icons to new slots - RebuildListsBasedOnGroups(groups); - for (auto &extra : extraIconsBySlot) - if (extra->isEnabled()) - extra->applyIcons(); - - delete[] oldSlots; - return true; - } - - void OnDestroy() override - { - pGlgOptions = nullptr; - - HTREEITEM hItem = m_tree.GetRoot(); - while (hItem) { - delete Tree_GetIDs(hItem); - hItem = m_tree.GetNextSibling(hItem); - } - - ImageList_Destroy(m_tree.GetImageList(TVSIL_NORMAL)); - } - - void onRClick(CCtrlTreeView::TEventInfo*) - { - HTREEITEM hSelected = m_tree.GetDropHilight(); - if (hSelected != nullptr && !m_tree.IsSelected(hSelected)) { - m_tree.UnselectAll(); - m_tree.SelectItem(hSelected); - } - - int sels = m_tree.GetNumSelected(); - if (sels > 1) { - if (ShowPopup(0) == ID_GROUP) { - GroupSelectedItems(); - NotifyChange(); - } - } - else if (sels == 1) { - HTREEITEM hItem = m_tree.GetSelection(); - intlist *ids = Tree_GetIDs(hItem); - if (ids->count > 1) { - if (ShowPopup(1) == ID_UNGROUP) { - UngroupSelectedItems(); - NotifyChange(); - } - } - } - } - - void BuildIconList() - { - HIMAGELIST hImageList = ImageList_Create(g_iIconSX, g_iIconSX, ILC_COLOR32 | ILC_MASK, 2, 2); - ImageList_AddIcon_NotShared(hImageList, MAKEINTRESOURCE(IDI_BLANK)); - - for (auto &extra : registeredExtraIcons) { - extra->setID(registeredExtraIcons.indexOf(&extra)+1); - - HICON hIcon = IcoLib_GetIconByHandle(extra->getDescIcon()); - if (hIcon == nullptr) - ImageList_AddIcon_NotShared(hImageList, MAKEINTRESOURCE(IDI_BLANK)); - else { - ImageList_AddIcon(hImageList, hIcon); - IcoLib_ReleaseIcon(hIcon); - } - } - m_tree.SetImageList(hImageList, TVSIL_NORMAL); - - for (auto &extra : extraIconsBySlot) { - if (extra->getType() == EXTRAICON_TYPE_GROUP) { - ExtraIconGroup *group = (ExtraIconGroup *)extra; - intlist ids; - for (auto &p : group->m_items) - ids.add(p->getID()); - Tree_AddExtraIconGroup(ids, extra->isEnabled()); - } - else Tree_AddExtraIcon((BaseExtraIcon*)extra, extra->isEnabled()); - } - - TVSORTCB sort = {}; - sort.hParent = nullptr; - sort.lParam = 0; - sort.lpfnCompare = CompareFunc; - m_tree.SortChildrenCB(&sort, 0); - } - - void onTimer(CTimer*) - { - m_timer.Stop(); - m_tree.DeleteAllItems(); - BuildIconList(); - } - - void ResetIconList() - { - m_timer.Start(100); - } -}; - -void eiOptionsRefresh() -{ - if (pGlgOptions) - pGlgOptions->ResetIconList(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int InitOptionsCallback(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.szGroup.a = LPGEN("Contact list"); - odp.szTitle.a = LPGEN("Extra icons"); - odp.szTab.a = LPGEN("General"); - odp.flags = ODPF_BOLDGROUPS; - odp.pDialog = new CExtraIconOptsDlg(); - g_plugin.addOptions(wParam, &odp); - return 0; -} +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-23 Miranda NG team
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "extraicons.h"
+
+static class CExtraIconOptsDlg *pGlgOptions;
+
+int SortFunc(const ExtraIcon *p1, const ExtraIcon *p2);
+
+struct intlist
+{
+ intlist() : count(0), data(nullptr) {}
+ ~intlist() { mir_free(data); }
+
+ void add(int val)
+ {
+ data = (int*)mir_realloc(data, sizeof(int)*(count + 1));
+ data[count++] = val;
+ }
+
+ int count;
+ int *data;
+};
+
+static int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM)
+{
+ intlist *a = (intlist*)lParam1;
+ intlist *b = (intlist*)lParam2;
+ return SortFunc(registeredExtraIcons[a->data[0] - 1], registeredExtraIcons[b->data[0] - 1]);
+}
+
+// Functions //////////////////////////////////////////////////////////////////////////////////////
+
+BOOL ScreenToClient(HWND hWnd, LPRECT lpRect)
+{
+ POINT pt;
+ pt.x = lpRect->left;
+ pt.y = lpRect->top;
+
+ BOOL ret = ScreenToClient(hWnd, &pt);
+ if (!ret)
+ return ret;
+
+ lpRect->left = pt.x;
+ lpRect->top = pt.y;
+
+ pt.x = lpRect->right;
+ pt.y = lpRect->bottom;
+
+ ret = ScreenToClient(hWnd, &pt);
+
+ lpRect->right = pt.x;
+ lpRect->bottom = pt.y;
+
+ return ret;
+}
+
+static void RemoveExtraIcons(int slot)
+{
+ for (auto &hContact : Contacts())
+ Clist_SetExtraIcon(hContact, slot, INVALID_HANDLE_VALUE);
+}
+
+class CExtraIconOptsDlg : public CDlgBase
+{
+ intlist* Tree_GetIDs(HTREEITEM hItem)
+ {
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ m_tree.GetItem(&tvi);
+ return (intlist*)tvi.lParam;
+ }
+
+ HTREEITEM Tree_AddExtraIcon(BaseExtraIcon *extra, bool selected, HTREEITEM hAfter = TVI_LAST)
+ {
+ intlist *ids = new intlist();
+ ids->add(extra->getID());
+
+ TVINSERTSTRUCT tvis = {};
+ tvis.hInsertAfter = hAfter;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+ tvis.item.iSelectedImage = tvis.item.iImage = extra->getID();
+ tvis.item.lParam = (LPARAM)ids;
+ tvis.item.pszText = (LPTSTR)extra->getDescription();
+ tvis.item.state = INDEXTOSTATEIMAGEMASK(selected ? 2 : 1);
+ return m_tree.InsertItem(&tvis);
+ }
+
+ HTREEITEM Tree_AddExtraIconGroup(intlist &group, bool selected, HTREEITEM hAfter = TVI_LAST)
+ {
+ intlist *ids = new intlist();
+ CMStringW desc;
+ int img = 0;
+ for (int i = 0; i < group.count; i++) {
+ BaseExtraIcon *extra = registeredExtraIcons[group.data[i] - 1];
+ ids->add(extra->getID());
+
+ if (img == 0 && extra->getDescIcon() != nullptr)
+ img = extra->getID();
+
+ if (i > 0)
+ desc += L" / ";
+ desc += extra->getDescription();
+ }
+
+ TVINSERTSTRUCT tvis = {};
+ tvis.hInsertAfter = hAfter;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_STATE;
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+ tvis.item.iSelectedImage = tvis.item.iImage = img;
+ tvis.item.lParam = (LPARAM)ids;
+ tvis.item.pszText = (wchar_t*)desc.c_str();
+ tvis.item.state = INDEXTOSTATEIMAGEMASK(selected ? 2 : 1);
+ return m_tree.InsertItem(&tvis);
+ }
+
+ void GroupSelectedItems()
+ {
+ LIST<_TREEITEM> toRemove(1);
+ intlist ids;
+ bool selected = false;
+ HTREEITEM hPlace = nullptr;
+
+ // Find items
+ HTREEITEM hItem = m_tree.GetRoot();
+ TVITEMEX tvi = { 0 };
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT | TVIF_STATE;
+ while (hItem) {
+ if (m_tree.IsSelected(hItem)) {
+ if (hPlace == nullptr)
+ hPlace = hItem;
+
+ tvi.hItem = hItem;
+ m_tree.GetItem(&tvi);
+
+ intlist *iids = (intlist*)tvi.lParam;
+ for (int i = 0; i < iids->count; i++)
+ ids.add(iids->data[i]);
+
+ if ((tvi.state & INDEXTOSTATEIMAGEMASK(3)) == INDEXTOSTATEIMAGEMASK(2))
+ selected = true;
+
+ toRemove.insert(hItem);
+ }
+
+ hItem = m_tree.GetNextSibling(hItem);
+ }
+
+ if (hPlace != nullptr) {
+ // Add new
+ HTREEITEM hNew = Tree_AddExtraIconGroup(ids, selected, hPlace);
+
+ // Remove old
+ for (auto &p : toRemove) {
+ delete Tree_GetIDs(p);
+ m_tree.DeleteItem(p);
+ }
+
+ // Select
+ m_tree.UnselectAll();
+ m_tree.SelectItem(hNew);
+ }
+ }
+
+ void UngroupSelectedItems()
+ {
+ HTREEITEM hItem = m_tree.GetSelection();
+ if (hItem == nullptr)
+ return;
+
+ intlist *ids = Tree_GetIDs(hItem);
+ if (ids->count < 2)
+ return;
+
+ bool selected = m_tree.IsSelected(hItem);
+
+ for (int i = ids->count - 1; i >= 0; i--) {
+ BaseExtraIcon *extra = registeredExtraIcons[ids->data[i] - 1];
+ Tree_AddExtraIcon(extra, selected, hItem);
+ }
+
+ delete ids;
+ m_tree.DeleteItem(hItem);
+
+ m_tree.UnselectAll();
+ }
+
+ int ShowPopup(int popup)
+ {
+ // Fix selection
+ HTREEITEM hSelected = m_tree.GetDropHilight();
+ HTREEITEM hItem = m_tree.GetRoot();
+ while (hItem) {
+ if (hItem != hSelected && m_tree.IsSelected(hItem))
+ m_tree.DropHilite(hItem);
+
+ hItem = m_tree.GetNextSibling(hItem);
+ }
+
+ HMENU menu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_OPT_POPUP));
+ HMENU submenu = GetSubMenu(menu, popup);
+ TranslateMenu(submenu);
+
+ uint32_t pos = GetMessagePos();
+ int ret = TrackPopupMenu(submenu, TPM_TOPALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_LEFTALIGN, LOWORD(pos), HIWORD(pos), 0, m_hwnd, nullptr);
+
+ DestroyMenu(menu);
+
+ // Revert selection
+ hItem = m_tree.GetRoot();
+ while (hItem) {
+ if (hItem != hSelected && m_tree.IsSelected(hItem))
+ m_tree.DropUnhilite(hItem);
+ hItem = m_tree.GetNextSibling(hItem);
+ }
+
+ return ret;
+ }
+
+ CCtrlTreeView m_tree;
+ CTimer m_timer;
+
+public:
+ CExtraIconOptsDlg() :
+ CDlgBase(g_plugin, IDD_EI_OPTIONS),
+ m_tree(this, IDC_EXTRAORDER),
+ m_timer(this, 1)
+ {
+ m_tree.SetFlags(MTREE_DND | MTREE_MULTISELECT);
+ m_tree.OnRightClick = Callback(this, &CExtraIconOptsDlg::onRClick);
+
+ m_timer.OnEvent = Callback(this, &CExtraIconOptsDlg::onTimer);
+ }
+
+ bool OnInitDialog() override
+ {
+ pGlgOptions = this;
+
+ int numSlots = EXTRA_ICON_COUNT;
+ if (numSlots < (int)registeredExtraIcons.getCount()) {
+ HWND label = GetDlgItem(m_hwnd, IDC_MAX_ICONS_L);
+ SetWindowText(label, CMStringW(FORMAT, TranslateT("*only the first %d icons will be shown"), numSlots));
+ ShowWindow(label, SW_SHOW);
+ }
+
+ BuildIconList();
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ // Store old slots
+ int *oldSlots = new int[registeredExtraIcons.getCount()];
+ int lastUsedSlot = -1;
+ for (int i = 0; i < registeredExtraIcons.getCount(); i++) {
+ if (registeredExtraIcons[i]->getType() != EXTRAICON_TYPE_GROUP)
+ oldSlots[i] = registeredExtraIcons[i]->getSlot();
+ else // Remove old slot for groups to re-set images
+ oldSlots[i] = -1;
+ lastUsedSlot = max(lastUsedSlot, registeredExtraIcons[i]->getSlot());
+ }
+ lastUsedSlot = min(lastUsedSlot, EXTRA_ICON_COUNT);
+
+ // Get user data and create new groups
+ LIST<ExtraIconGroup> groups(1);
+
+ uint8_t pos = 0;
+ int firstEmptySlot = 0;
+ HTREEITEM ht = m_tree.GetRoot();
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE;
+ tvi.stateMask = TVIS_STATEIMAGEMASK;
+ while (ht) {
+ tvi.hItem = ht;
+ m_tree.GetItem(&tvi);
+
+ intlist *ids = (intlist*)tvi.lParam;
+ if (ids == nullptr || ids->count < 1)
+ continue; // ???
+
+ bool enabled = ((tvi.state & INDEXTOSTATEIMAGEMASK(3)) == INDEXTOSTATEIMAGEMASK(2));
+ int slot = (enabled ? firstEmptySlot++ : -1);
+ if (slot >= EXTRA_ICON_COUNT)
+ slot = -1;
+
+ if (ids->count == 1) {
+ BaseExtraIcon *extra = registeredExtraIcons[ids->data[0] - 1];
+ extra->setPosition(pos++);
+ extra->setSlot(slot);
+ }
+ else {
+ char name[128];
+ mir_snprintf(name, "__group_%d", groups.getCount());
+
+ ExtraIconGroup *group = new ExtraIconGroup(name);
+
+ for (int i = 0; i < ids->count; i++) {
+ BaseExtraIcon *extra = registeredExtraIcons[ids->data[i] - 1];
+ extra->setPosition(pos++);
+
+ group->addExtraIcon(extra);
+ }
+
+ group->setSlot(slot);
+ groups.insert(group);
+ }
+
+ ht = m_tree.GetNextSibling(ht);
+ }
+
+ // Store data
+ for (auto &extra : registeredExtraIcons) {
+ char setting[512];
+ mir_snprintf(setting, "Position_%s", extra->getName());
+ db_set_w(0, EI_MODULE_NAME, setting, extra->getPosition());
+
+ mir_snprintf(setting, "Slot_%s", extra->getName());
+ db_set_w(0, EI_MODULE_NAME, setting, extra->getSlot());
+ }
+
+ db_delete_module(0, EI_MODULE_NAME "Groups");
+ db_set_w(0, EI_MODULE_NAME "Groups", "Count", groups.getCount());
+ for (int k = 0; k < groups.getCount(); k++) {
+ ExtraIconGroup *group = groups[k];
+
+ char setting[512];
+ mir_snprintf(setting, "%d_count", k);
+ db_set_w(0, EI_MODULE_NAME "Groups", setting, (uint16_t)group->m_items.getCount());
+
+ for (int j = 0; j < group->m_items.getCount(); j++) {
+ BaseExtraIcon *extra = group->m_items[j];
+
+ mir_snprintf(setting, "%d_%d", k, j);
+ db_set_s(0, EI_MODULE_NAME "Groups", setting, extra->getName());
+ }
+ }
+
+ // Clean removed slots
+ for (int j = firstEmptySlot; j <= lastUsedSlot; j++)
+ RemoveExtraIcons(j);
+
+ // Apply icons to new slots
+ RebuildListsBasedOnGroups(groups);
+ for (auto &extra : extraIconsBySlot)
+ if (extra->isEnabled())
+ extra->applyIcons();
+
+ delete[] oldSlots;
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ pGlgOptions = nullptr;
+
+ HTREEITEM hItem = m_tree.GetRoot();
+ while (hItem) {
+ delete Tree_GetIDs(hItem);
+ hItem = m_tree.GetNextSibling(hItem);
+ }
+
+ ImageList_Destroy(m_tree.GetImageList(TVSIL_NORMAL));
+ }
+
+ void onRClick(CCtrlTreeView::TEventInfo*)
+ {
+ HTREEITEM hSelected = m_tree.GetDropHilight();
+ if (hSelected != nullptr && !m_tree.IsSelected(hSelected)) {
+ m_tree.UnselectAll();
+ m_tree.SelectItem(hSelected);
+ }
+
+ int sels = m_tree.GetNumSelected();
+ if (sels > 1) {
+ if (ShowPopup(0) == ID_GROUP) {
+ GroupSelectedItems();
+ NotifyChange();
+ }
+ }
+ else if (sels == 1) {
+ HTREEITEM hItem = m_tree.GetSelection();
+ intlist *ids = Tree_GetIDs(hItem);
+ if (ids->count > 1) {
+ if (ShowPopup(1) == ID_UNGROUP) {
+ UngroupSelectedItems();
+ NotifyChange();
+ }
+ }
+ }
+ }
+
+ void BuildIconList()
+ {
+ HIMAGELIST hImageList = ImageList_Create(g_iIconSX, g_iIconSX, ILC_COLOR32 | ILC_MASK, 2, 2);
+ ImageList_AddIcon_NotShared(hImageList, MAKEINTRESOURCE(IDI_BLANK));
+
+ for (auto &extra : registeredExtraIcons) {
+ extra->setID(registeredExtraIcons.indexOf(&extra)+1);
+
+ HICON hIcon = IcoLib_GetIconByHandle(extra->getDescIcon());
+ if (hIcon == nullptr)
+ ImageList_AddIcon_NotShared(hImageList, MAKEINTRESOURCE(IDI_BLANK));
+ else {
+ ImageList_AddIcon(hImageList, hIcon);
+ IcoLib_ReleaseIcon(hIcon);
+ }
+ }
+ m_tree.SetImageList(hImageList, TVSIL_NORMAL);
+
+ for (auto &extra : extraIconsBySlot) {
+ if (extra->getType() == EXTRAICON_TYPE_GROUP) {
+ ExtraIconGroup *group = (ExtraIconGroup *)extra;
+ intlist ids;
+ for (auto &p : group->m_items)
+ ids.add(p->getID());
+ Tree_AddExtraIconGroup(ids, extra->isEnabled());
+ }
+ else Tree_AddExtraIcon((BaseExtraIcon*)extra, extra->isEnabled());
+ }
+
+ TVSORTCB sort = {};
+ sort.hParent = nullptr;
+ sort.lParam = 0;
+ sort.lpfnCompare = CompareFunc;
+ m_tree.SortChildrenCB(&sort, 0);
+ }
+
+ void onTimer(CTimer*)
+ {
+ m_timer.Stop();
+ m_tree.DeleteAllItems();
+ BuildIconList();
+ }
+
+ void ResetIconList()
+ {
+ m_timer.Start(100);
+ }
+};
+
+void eiOptionsRefresh()
+{
+ if (pGlgOptions)
+ pGlgOptions->ResetIconList();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int InitOptionsCallback(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.szGroup.a = LPGEN("Contact list");
+ odp.szTitle.a = LPGEN("Extra icons");
+ odp.szTab.a = LPGEN("General");
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pDialog = new CExtraIconOptsDlg();
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/ei_services.cpp b/src/mir_app/src/ei_services.cpp index 9f41eb4679..f55551980d 100644 --- a/src/mir_app/src/ei_services.cpp +++ b/src/mir_app/src/ei_services.cpp @@ -1,504 +1,504 @@ -/* - -Copyright (C) 2009 Ricardo Pescuma Domenecci -Copyright (C) 2012-22 Miranda NG team - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU Library General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Library General Public License for more details. - -You should have received a copy of the GNU Library General Public -License along with this file; see the file license.txt. If -not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, -Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "m_cluiframes.h" - -#include "extraicons.h" -#include "usedIcons.h" -#include "clc.h" - -// Prototypes /////////////////////////////////////////////////////////////////////////// - -int SortFunc(const ExtraIcon *p1, const ExtraIcon *p2) -{ - int ret = p1->getPosition() - p2->getPosition(); - if (ret != 0) - return ret; - - return p1->getID() - p2->getID(); -} - -LIST<ExtraIcon> extraIconsBySlot(10, SortFunc); -LIST<BaseExtraIcon> registeredExtraIcons(10, PtrKeySortT); - -static bool clistRebuildAlreadyCalled = false, clistApplyAlreadyCalled = false; - -// Functions //////////////////////////////////////////////////////////////////////////// - -int InitOptionsCallback(WPARAM wParam, LPARAM lParam); - -int ConvertToClistSlot(int slot) -{ - if (slot < 0) - return slot; - - return slot + 1; -} - -int ExtraImage_ExtraIDToColumnNum(int extra) -{ - return (extra < 1 || extra > EXTRA_ICON_COUNT) ? -1 : extra - 1; -} - -int Clist_SetExtraIcon(MCONTACT hContact, int slot, HANDLE hImage) -{ - if (g_clistApi.hwndContactTree == nullptr) - return -1; - - int icol = ExtraImage_ExtraIDToColumnNum(ConvertToClistSlot(slot)); - if (icol == -1) - return -1; - - SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRAIMAGE, hContact, MAKELPARAM(icol, hImage)); - return 0; -} - -BaseExtraIcon* GetExtraIconByName(const char *name) -{ - for (auto &extra : registeredExtraIcons) - if (mir_strcmp(name, extra->getName()) == 0) - return extra; - - return nullptr; -} - -static void LoadGroups(LIST<ExtraIconGroup> &groups) -{ - int count = db_get_w(0, EI_MODULE_NAME "Groups", "Count", 0); - for (int i = 0; i < count; i++) { - char setting[512]; - mir_snprintf(setting, "%d_count", i); - unsigned int items = db_get_w(0, EI_MODULE_NAME "Groups", setting, 0); - if (items < 1) - continue; - - mir_snprintf(setting, "__group_%d", i); - ExtraIconGroup *group = new ExtraIconGroup(setting); - - for (unsigned int j = 0; j < items; j++) { - mir_snprintf(setting, "%d_%d", i, j); - ptrA szIconName(db_get_sa(0, EI_MODULE_NAME "Groups", setting)); - if (IsEmpty(szIconName)) - continue; - - BaseExtraIcon *extra = GetExtraIconByName(szIconName); - if (extra == nullptr) - continue; - - group->m_items.insert(extra); - if (extra->getSlot() >= 0) - group->setSlot(extra->getSlot()); - } - - if (group->m_items.getCount() < 2) { - delete group; - continue; - } - - groups.insert(group); - } -} - -static ExtraIconGroup* IsInGroup(LIST<ExtraIconGroup> &groups, BaseExtraIcon *extra) -{ - for (auto &group : groups) - for (auto &it : group->m_items) - if (extra == it) - return group; - - return nullptr; -} - -void RebuildListsBasedOnGroups(LIST<ExtraIconGroup> &groups) -{ - for (auto &extra : registeredExtraIcons) - extra->setParent(nullptr); - - for (auto &extra : extraIconsBySlot) - if (extra->getType() == EXTRAICON_TYPE_GROUP) - delete extra; - extraIconsBySlot.destroy(); - - for (auto &group : groups) { - for (auto &it : group->m_items) - it->setParent(group); - - extraIconsBySlot.insert(group); - } - - for (auto &extra : registeredExtraIcons) - if (extra->getParent() == nullptr) - extraIconsBySlot.insert(extra); -} - -/////////////////////////////////////////////////////////////////////////////// - -static void ResetSlots(BaseExtraIcon *extra, ExtraIconGroup *group, int iOldSlot = -1) -{ - int slot = 0, oldMaxSlot = -1; - for (auto &ex : extraIconsBySlot) { - if (ex->getSlot() < 0) - continue; - - int oldSlot = ex->getSlot(); - if (oldSlot > oldMaxSlot) - oldMaxSlot = oldSlot+1; - - ex->setSlot(slot++); - - if (clistApplyAlreadyCalled && (ex == group || ex == extra || oldSlot != slot)) - ex->applyIcons(); - } - - if (iOldSlot > oldMaxSlot) - oldMaxSlot = iOldSlot + 1; - - // slots were freed, we need to clear one or more items - if (extra == nullptr) - for (int i = slot; i < oldMaxSlot; i++) - for (auto &hContact : Contacts()) - Clist_SetExtraIcon(hContact, i, INVALID_HANDLE_VALUE); - - if (!Miranda_IsTerminated()) { - Clist_InitAutoRebuild(g_clistApi.hwndContactTree); - eiOptionsRefresh(); - } -} - -void KillModuleExtraIcons(CMPluginBase *pPlugin) -{ - LIST<ExtraIcon> arIcons(1); - - for (auto &it : registeredExtraIcons.rev_iter()) - if (it->m_pPlugin == pPlugin) { - arIcons.insert(it); - registeredExtraIcons.removeItem(&it); - } - - if (arIcons.getCount() == 0) - return; - - int iOldSlot = -1; - for (auto &it : arIcons) - if (it->getSlot() > iOldSlot) - iOldSlot = it->getSlot() + 1; - - LIST<ExtraIconGroup> groups(1); - LoadGroups(groups); - RebuildListsBasedOnGroups(groups); - ResetSlots(0, 0, iOldSlot); - - for (auto &it : arIcons) - delete it; -} - -/////////////////////////////////////////////////////////////////////////////// - -int ClistExtraListRebuild(WPARAM, LPARAM) -{ - clistRebuildAlreadyCalled = true; - - ResetIcons(); - - for (auto &it : extraIconsBySlot) - it->rebuildIcons(); - - return 0; -} - -int ClistExtraImageApply(WPARAM hContact, LPARAM) -{ - if (hContact == 0) - return 0; - - clistApplyAlreadyCalled = true; - - for (auto &it : extraIconsBySlot) - it->doApply(hContact); - - return 0; -} - -int ClistExtraClick(WPARAM hContact, LPARAM lParam) -{ - if (hContact == 0) - return 0; - - int clistSlot = (int)lParam; - - for (auto &extra : extraIconsBySlot) { - if (ConvertToClistSlot(extra->getSlot()) == clistSlot) { - extra->onClick(hContact); - break; - } - } - - return 0; -} - -/////////////////////////////////////////////////////////////////////////////// -// Extra image list functions - -HANDLE hEventExtraImageListRebuilding, hEventExtraImageApplying, hEventExtraClick; - -static bool bImageCreated = false; -static HIMAGELIST hExtraImageList = nullptr; - -MIR_APP_DLL(HANDLE) ExtraIcon_AddIcon(HICON hIcon) -{ - if (hExtraImageList == nullptr || hIcon == nullptr) - return INVALID_HANDLE_VALUE; - - int res = ImageList_AddIcon(hExtraImageList, hIcon); - return (res > 0xFFFE) ? INVALID_HANDLE_VALUE : (HANDLE)res; -} - -MIR_APP_DLL(void) ExtraIcon_Reload() -{ - SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRASPACE, db_get_b(0, "CLUI", "ExtraColumnSpace", 18), 0); - SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRAIMAGELIST, 0, 0); - - if (hExtraImageList) - ImageList_Destroy(hExtraImageList); - - hExtraImageList = ImageList_Create(g_iIconSX, g_iIconSY, ILC_COLOR32 | ILC_MASK, 1, 256); - - SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRAIMAGELIST, 0, (LPARAM)hExtraImageList); - SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRACOLUMNS, EXTRA_ICON_COUNT, 0); - NotifyEventHooks(hEventExtraImageListRebuilding, 0, 0); - bImageCreated = true; -} - -MIR_APP_DLL(void) ExtraIcon_SetAll(MCONTACT hContact) -{ - if (g_clistApi.hwndContactTree == nullptr) - return; - - if (!bImageCreated) - ExtraIcon_Reload(); - - SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRACOLUMNS, EXTRA_ICON_COUNT, 0); - - if (hContact == 0) { - for (auto &it : Contacts()) - NotifyEventHooks(hEventExtraImageApplying, it, 0); - } - else NotifyEventHooks(hEventExtraImageApplying, hContact, 0); - - g_clistApi.pfnInvalidateRect(g_clistApi.hwndContactTree, nullptr, FALSE); -} - -/////////////////////////////////////////////////////////////////////////////// -// external functions - -static void EI_PostCreate(BaseExtraIcon *extra, int flags) -{ - char setting[512]; - mir_snprintf(setting, "Position_%s", extra->getName()); - extra->setPosition(db_get_w(0, EI_MODULE_NAME, setting, 1000)); - - mir_snprintf(setting, "Slot_%s", extra->getName()); - int slot = db_get_w(0, EI_MODULE_NAME, setting, -100); - if (slot == EMPTY_EXTRA_ICON) - slot = -1; - else if (slot == -100) { - if (flags & EIF_DISABLED_BY_DEFAULT) { - db_set_w(0, EI_MODULE_NAME, setting, EMPTY_EXTRA_ICON); - slot = -1; - } - else slot = 1; - } - extra->setSlot(slot); - - registeredExtraIcons.insert(extra); - - LIST<ExtraIconGroup> groups(1); - LoadGroups(groups); - - ExtraIconGroup *group = IsInGroup(groups, extra); - if (group != nullptr) - RebuildListsBasedOnGroups(groups); - else { - for (auto &it : groups) - delete it; - - extraIconsBySlot.insert(extra); - } - - if (slot >= 0 || group != nullptr) { - if (clistRebuildAlreadyCalled) - extra->rebuildIcons(); - - ResetSlots(extra, group); - } -} - -EXTERN_C MIR_APP_DLL(HANDLE) ExtraIcon_RegisterCallback(const char *name, const char *description, HANDLE descIcon, - MIRANDAHOOK RebuildIcons, MIRANDAHOOK ApplyIcon, MIRANDAHOOKPARAM OnClick, LPARAM onClickParam, int flags) -{ - // EXTRAICON_TYPE_CALLBACK - if (IsEmpty(name) || IsEmpty(description)) - return nullptr; - - if (ApplyIcon == nullptr || RebuildIcons == nullptr) - return nullptr; - - // no way to merge - if (GetExtraIconByName(name) != nullptr) - return nullptr; - - ptrW tszDesc(mir_a2u(description)); - - BaseExtraIcon *extra = new CallbackExtraIcon(name, tszDesc, descIcon, RebuildIcons, ApplyIcon, OnClick, onClickParam); - extra->m_pPlugin = &GetPluginByInstance(GetInstByAddress(RebuildIcons)); - EI_PostCreate(extra, flags); - return extra; -} - -EXTERN_C MIR_APP_DLL(HANDLE) ExtraIcon_RegisterIcolib(const char *name, const char *description, HANDLE descIcon, MIRANDAHOOKPARAM OnClick, LPARAM onClickParam, int flags) -{ - if (IsEmpty(name) || IsEmpty(description)) - return nullptr; - - ptrW tszDesc(mir_a2u(description)); - - BaseExtraIcon *extra = GetExtraIconByName(name); - if (extra != nullptr) { - if (extra->getType() != EXTRAICON_TYPE_ICOLIB) - return nullptr; - - // Found one, now merge it - if (descIcon) - extra->setDescIcon(descIcon); - - if (OnClick != nullptr) - extra->setOnClick(OnClick, onClickParam); - - if (extra->getSlot() > 0) { - if (clistRebuildAlreadyCalled) - extra->rebuildIcons(); - if (clistApplyAlreadyCalled) - extra->applyIcons(); - } - } - else { - extra = new IcolibExtraIcon(name, tszDesc, descIcon, OnClick, onClickParam); - extra->m_pPlugin = &GetPluginByInstance(GetInstByAddress((void*)name)); - EI_PostCreate(extra, flags); - } - - return extra; -} - -/////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) ExtraIcon_SetIcon(HANDLE hExtraIcon, MCONTACT hContact, HANDLE hImage) -{ - if (hExtraIcon == nullptr || hContact == 0) - return -1; - - BaseExtraIcon *extra = registeredExtraIcons.find((BaseExtraIcon*)hExtraIcon); - if (extra == nullptr) - return -1; - - if (extra->getParent()) - return extra->getParent()->internalSetIcon(extra, hContact, hImage, false); - - return extra->setIcon(hContact, hImage); -} - -MIR_APP_DLL(int) ExtraIcon_SetIconByName(HANDLE hExtraIcon, MCONTACT hContact, const char *icoName) -{ - if (hExtraIcon == nullptr || hContact == 0) - return -1; - - BaseExtraIcon *extra = registeredExtraIcons.find((BaseExtraIcon*)hExtraIcon); - if (extra == nullptr) - return -1; - - if (extra->getParent()) - return extra->getParent()->internalSetIcon(extra, hContact, (HANDLE)icoName, true); - - return extra->setIconByName(hContact, icoName); -} - -MIR_APP_DLL(int) ExtraIcon_Clear(HANDLE hExtraIcon, MCONTACT hContact) -{ - if (hExtraIcon == nullptr || hContact == 0) - return -1; - - BaseExtraIcon *extra = registeredExtraIcons.find((BaseExtraIcon*)hExtraIcon); - if (extra == nullptr) - return -1; - - if (extra->getParent()) - return extra->getParent()->internalSetIcon(extra, hContact, nullptr, false); - - return extra->setIcon(hContact, nullptr); -} - -/////////////////////////////////////////////////////////////////////////////// - -static IconItem iconList[] = -{ - { LPGEN("Chat activity"), "ChatActivity", IDI_CHAT }, - { LPGEN("Mute chat"), "ChatMute", IDI_OFF }, - { LPGEN("Male"), "gender_male", IDI_MALE }, - { LPGEN("Female"), "gender_female", IDI_FEMALE }, - { LPGEN("Database"), "database", IDI_DATABASE }, -}; - -void LoadExtraIconsModule() -{ - // Events - hEventExtraClick = CreateHookableEvent(ME_CLIST_EXTRA_CLICK); - hEventExtraImageApplying = CreateHookableEvent(ME_CLIST_EXTRA_IMAGE_APPLY); - hEventExtraImageListRebuilding = CreateHookableEvent(ME_CLIST_EXTRA_LIST_REBUILD); - - // Icons - g_plugin.registerIcon(LPGEN("Contact list"), iconList); - - // Hooks - HookEvent(ME_OPT_INITIALISE, InitOptionsCallback); - - HookEvent(ME_CLIST_EXTRA_LIST_REBUILD, ClistExtraListRebuild); - HookEvent(ME_CLIST_EXTRA_IMAGE_APPLY, ClistExtraImageApply); - HookEvent(ME_CLIST_EXTRA_CLICK, ClistExtraClick); - - DefaultExtraIcons_Load(); -} - -void UnloadExtraIconsModule(void) -{ - for (auto &extra : extraIconsBySlot) - if (extra->getType() == EXTRAICON_TYPE_GROUP) - delete extra; - - for (auto &it : registeredExtraIcons) - delete it; - - if (hExtraImageList) { - ImageList_Destroy(hExtraImageList); - hExtraImageList = nullptr; - } -} +/*
+
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+Copyright (C) 2012-23 Miranda NG team
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "m_cluiframes.h"
+
+#include "extraicons.h"
+#include "usedIcons.h"
+#include "clc.h"
+
+// Prototypes ///////////////////////////////////////////////////////////////////////////
+
+int SortFunc(const ExtraIcon *p1, const ExtraIcon *p2)
+{
+ int ret = p1->getPosition() - p2->getPosition();
+ if (ret != 0)
+ return ret;
+
+ return p1->getID() - p2->getID();
+}
+
+LIST<ExtraIcon> extraIconsBySlot(10, SortFunc);
+LIST<BaseExtraIcon> registeredExtraIcons(10, PtrKeySortT);
+
+static bool clistRebuildAlreadyCalled = false, clistApplyAlreadyCalled = false;
+
+// Functions ////////////////////////////////////////////////////////////////////////////
+
+int InitOptionsCallback(WPARAM wParam, LPARAM lParam);
+
+int ConvertToClistSlot(int slot)
+{
+ if (slot < 0)
+ return slot;
+
+ return slot + 1;
+}
+
+int ExtraImage_ExtraIDToColumnNum(int extra)
+{
+ return (extra < 1 || extra > EXTRA_ICON_COUNT) ? -1 : extra - 1;
+}
+
+int Clist_SetExtraIcon(MCONTACT hContact, int slot, HANDLE hImage)
+{
+ if (g_clistApi.hwndContactTree == nullptr)
+ return -1;
+
+ int icol = ExtraImage_ExtraIDToColumnNum(ConvertToClistSlot(slot));
+ if (icol == -1)
+ return -1;
+
+ SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRAIMAGE, hContact, MAKELPARAM(icol, hImage));
+ return 0;
+}
+
+BaseExtraIcon* GetExtraIconByName(const char *name)
+{
+ for (auto &extra : registeredExtraIcons)
+ if (mir_strcmp(name, extra->getName()) == 0)
+ return extra;
+
+ return nullptr;
+}
+
+static void LoadGroups(LIST<ExtraIconGroup> &groups)
+{
+ int count = db_get_w(0, EI_MODULE_NAME "Groups", "Count", 0);
+ for (int i = 0; i < count; i++) {
+ char setting[512];
+ mir_snprintf(setting, "%d_count", i);
+ unsigned int items = db_get_w(0, EI_MODULE_NAME "Groups", setting, 0);
+ if (items < 1)
+ continue;
+
+ mir_snprintf(setting, "__group_%d", i);
+ ExtraIconGroup *group = new ExtraIconGroup(setting);
+
+ for (unsigned int j = 0; j < items; j++) {
+ mir_snprintf(setting, "%d_%d", i, j);
+ ptrA szIconName(db_get_sa(0, EI_MODULE_NAME "Groups", setting));
+ if (IsEmpty(szIconName))
+ continue;
+
+ BaseExtraIcon *extra = GetExtraIconByName(szIconName);
+ if (extra == nullptr)
+ continue;
+
+ group->m_items.insert(extra);
+ if (extra->getSlot() >= 0)
+ group->setSlot(extra->getSlot());
+ }
+
+ if (group->m_items.getCount() < 2) {
+ delete group;
+ continue;
+ }
+
+ groups.insert(group);
+ }
+}
+
+static ExtraIconGroup* IsInGroup(LIST<ExtraIconGroup> &groups, BaseExtraIcon *extra)
+{
+ for (auto &group : groups)
+ for (auto &it : group->m_items)
+ if (extra == it)
+ return group;
+
+ return nullptr;
+}
+
+void RebuildListsBasedOnGroups(LIST<ExtraIconGroup> &groups)
+{
+ for (auto &extra : registeredExtraIcons)
+ extra->setParent(nullptr);
+
+ for (auto &extra : extraIconsBySlot)
+ if (extra->getType() == EXTRAICON_TYPE_GROUP)
+ delete extra;
+ extraIconsBySlot.destroy();
+
+ for (auto &group : groups) {
+ for (auto &it : group->m_items)
+ it->setParent(group);
+
+ extraIconsBySlot.insert(group);
+ }
+
+ for (auto &extra : registeredExtraIcons)
+ if (extra->getParent() == nullptr)
+ extraIconsBySlot.insert(extra);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void ResetSlots(BaseExtraIcon *extra, ExtraIconGroup *group, int iOldSlot = -1)
+{
+ int slot = 0, oldMaxSlot = -1;
+ for (auto &ex : extraIconsBySlot) {
+ if (ex->getSlot() < 0)
+ continue;
+
+ int oldSlot = ex->getSlot();
+ if (oldSlot > oldMaxSlot)
+ oldMaxSlot = oldSlot+1;
+
+ ex->setSlot(slot++);
+
+ if (clistApplyAlreadyCalled && (ex == group || ex == extra || oldSlot != slot))
+ ex->applyIcons();
+ }
+
+ if (iOldSlot > oldMaxSlot)
+ oldMaxSlot = iOldSlot + 1;
+
+ // slots were freed, we need to clear one or more items
+ if (extra == nullptr)
+ for (int i = slot; i < oldMaxSlot; i++)
+ for (auto &hContact : Contacts())
+ Clist_SetExtraIcon(hContact, i, INVALID_HANDLE_VALUE);
+
+ if (!Miranda_IsTerminated()) {
+ Clist_InitAutoRebuild(g_clistApi.hwndContactTree);
+ eiOptionsRefresh();
+ }
+}
+
+void KillModuleExtraIcons(CMPluginBase *pPlugin)
+{
+ LIST<ExtraIcon> arIcons(1);
+
+ for (auto &it : registeredExtraIcons.rev_iter())
+ if (it->m_pPlugin == pPlugin) {
+ arIcons.insert(it);
+ registeredExtraIcons.removeItem(&it);
+ }
+
+ if (arIcons.getCount() == 0)
+ return;
+
+ int iOldSlot = -1;
+ for (auto &it : arIcons)
+ if (it->getSlot() > iOldSlot)
+ iOldSlot = it->getSlot() + 1;
+
+ LIST<ExtraIconGroup> groups(1);
+ LoadGroups(groups);
+ RebuildListsBasedOnGroups(groups);
+ ResetSlots(0, 0, iOldSlot);
+
+ for (auto &it : arIcons)
+ delete it;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ClistExtraListRebuild(WPARAM, LPARAM)
+{
+ clistRebuildAlreadyCalled = true;
+
+ ResetIcons();
+
+ for (auto &it : extraIconsBySlot)
+ it->rebuildIcons();
+
+ return 0;
+}
+
+int ClistExtraImageApply(WPARAM hContact, LPARAM)
+{
+ if (hContact == 0)
+ return 0;
+
+ clistApplyAlreadyCalled = true;
+
+ for (auto &it : extraIconsBySlot)
+ it->doApply(hContact);
+
+ return 0;
+}
+
+int ClistExtraClick(WPARAM hContact, LPARAM lParam)
+{
+ if (hContact == 0)
+ return 0;
+
+ int clistSlot = (int)lParam;
+
+ for (auto &extra : extraIconsBySlot) {
+ if (ConvertToClistSlot(extra->getSlot()) == clistSlot) {
+ extra->onClick(hContact);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Extra image list functions
+
+HANDLE hEventExtraImageListRebuilding, hEventExtraImageApplying, hEventExtraClick;
+
+static bool bImageCreated = false;
+static HIMAGELIST hExtraImageList = nullptr;
+
+MIR_APP_DLL(HANDLE) ExtraIcon_AddIcon(HICON hIcon)
+{
+ if (hExtraImageList == nullptr || hIcon == nullptr)
+ return INVALID_HANDLE_VALUE;
+
+ int res = ImageList_AddIcon(hExtraImageList, hIcon);
+ return (res > 0xFFFE) ? INVALID_HANDLE_VALUE : (HANDLE)res;
+}
+
+MIR_APP_DLL(void) ExtraIcon_Reload()
+{
+ SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRASPACE, db_get_b(0, "CLUI", "ExtraColumnSpace", 18), 0);
+ SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRAIMAGELIST, 0, 0);
+
+ if (hExtraImageList)
+ ImageList_Destroy(hExtraImageList);
+
+ hExtraImageList = ImageList_Create(g_iIconSX, g_iIconSY, ILC_COLOR32 | ILC_MASK, 1, 256);
+
+ SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRAIMAGELIST, 0, (LPARAM)hExtraImageList);
+ SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRACOLUMNS, EXTRA_ICON_COUNT, 0);
+ NotifyEventHooks(hEventExtraImageListRebuilding, 0, 0);
+ bImageCreated = true;
+}
+
+MIR_APP_DLL(void) ExtraIcon_SetAll(MCONTACT hContact)
+{
+ if (g_clistApi.hwndContactTree == nullptr)
+ return;
+
+ if (!bImageCreated)
+ ExtraIcon_Reload();
+
+ SendMessage(g_clistApi.hwndContactTree, CLM_SETEXTRACOLUMNS, EXTRA_ICON_COUNT, 0);
+
+ if (hContact == 0) {
+ for (auto &it : Contacts())
+ NotifyEventHooks(hEventExtraImageApplying, it, 0);
+ }
+ else NotifyEventHooks(hEventExtraImageApplying, hContact, 0);
+
+ g_clistApi.pfnInvalidateRect(g_clistApi.hwndContactTree, nullptr, FALSE);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// external functions
+
+static void EI_PostCreate(BaseExtraIcon *extra, int flags)
+{
+ char setting[512];
+ mir_snprintf(setting, "Position_%s", extra->getName());
+ extra->setPosition(db_get_w(0, EI_MODULE_NAME, setting, 1000));
+
+ mir_snprintf(setting, "Slot_%s", extra->getName());
+ int slot = db_get_w(0, EI_MODULE_NAME, setting, -100);
+ if (slot == EMPTY_EXTRA_ICON)
+ slot = -1;
+ else if (slot == -100) {
+ if (flags & EIF_DISABLED_BY_DEFAULT) {
+ db_set_w(0, EI_MODULE_NAME, setting, EMPTY_EXTRA_ICON);
+ slot = -1;
+ }
+ else slot = 1;
+ }
+ extra->setSlot(slot);
+
+ registeredExtraIcons.insert(extra);
+
+ LIST<ExtraIconGroup> groups(1);
+ LoadGroups(groups);
+
+ ExtraIconGroup *group = IsInGroup(groups, extra);
+ if (group != nullptr)
+ RebuildListsBasedOnGroups(groups);
+ else {
+ for (auto &it : groups)
+ delete it;
+
+ extraIconsBySlot.insert(extra);
+ }
+
+ if (slot >= 0 || group != nullptr) {
+ if (clistRebuildAlreadyCalled)
+ extra->rebuildIcons();
+
+ ResetSlots(extra, group);
+ }
+}
+
+EXTERN_C MIR_APP_DLL(HANDLE) ExtraIcon_RegisterCallback(const char *name, const char *description, HANDLE descIcon,
+ MIRANDAHOOK RebuildIcons, MIRANDAHOOK ApplyIcon, MIRANDAHOOKPARAM OnClick, LPARAM onClickParam, int flags)
+{
+ // EXTRAICON_TYPE_CALLBACK
+ if (IsEmpty(name) || IsEmpty(description))
+ return nullptr;
+
+ if (ApplyIcon == nullptr || RebuildIcons == nullptr)
+ return nullptr;
+
+ // no way to merge
+ if (GetExtraIconByName(name) != nullptr)
+ return nullptr;
+
+ ptrW tszDesc(mir_a2u(description));
+
+ BaseExtraIcon *extra = new CallbackExtraIcon(name, tszDesc, descIcon, RebuildIcons, ApplyIcon, OnClick, onClickParam);
+ extra->m_pPlugin = &GetPluginByInstance(GetInstByAddress(RebuildIcons));
+ EI_PostCreate(extra, flags);
+ return extra;
+}
+
+EXTERN_C MIR_APP_DLL(HANDLE) ExtraIcon_RegisterIcolib(const char *name, const char *description, HANDLE descIcon, MIRANDAHOOKPARAM OnClick, LPARAM onClickParam, int flags)
+{
+ if (IsEmpty(name) || IsEmpty(description))
+ return nullptr;
+
+ ptrW tszDesc(mir_a2u(description));
+
+ BaseExtraIcon *extra = GetExtraIconByName(name);
+ if (extra != nullptr) {
+ if (extra->getType() != EXTRAICON_TYPE_ICOLIB)
+ return nullptr;
+
+ // Found one, now merge it
+ if (descIcon)
+ extra->setDescIcon(descIcon);
+
+ if (OnClick != nullptr)
+ extra->setOnClick(OnClick, onClickParam);
+
+ if (extra->getSlot() > 0) {
+ if (clistRebuildAlreadyCalled)
+ extra->rebuildIcons();
+ if (clistApplyAlreadyCalled)
+ extra->applyIcons();
+ }
+ }
+ else {
+ extra = new IcolibExtraIcon(name, tszDesc, descIcon, OnClick, onClickParam);
+ extra->m_pPlugin = &GetPluginByInstance(GetInstByAddress((void*)name));
+ EI_PostCreate(extra, flags);
+ }
+
+ return extra;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) ExtraIcon_SetIcon(HANDLE hExtraIcon, MCONTACT hContact, HANDLE hImage)
+{
+ if (hExtraIcon == nullptr || hContact == 0)
+ return -1;
+
+ BaseExtraIcon *extra = registeredExtraIcons.find((BaseExtraIcon*)hExtraIcon);
+ if (extra == nullptr)
+ return -1;
+
+ if (extra->getParent())
+ return extra->getParent()->internalSetIcon(extra, hContact, hImage, false);
+
+ return extra->setIcon(hContact, hImage);
+}
+
+MIR_APP_DLL(int) ExtraIcon_SetIconByName(HANDLE hExtraIcon, MCONTACT hContact, const char *icoName)
+{
+ if (hExtraIcon == nullptr || hContact == 0)
+ return -1;
+
+ BaseExtraIcon *extra = registeredExtraIcons.find((BaseExtraIcon*)hExtraIcon);
+ if (extra == nullptr)
+ return -1;
+
+ if (extra->getParent())
+ return extra->getParent()->internalSetIcon(extra, hContact, (HANDLE)icoName, true);
+
+ return extra->setIconByName(hContact, icoName);
+}
+
+MIR_APP_DLL(int) ExtraIcon_Clear(HANDLE hExtraIcon, MCONTACT hContact)
+{
+ if (hExtraIcon == nullptr || hContact == 0)
+ return -1;
+
+ BaseExtraIcon *extra = registeredExtraIcons.find((BaseExtraIcon*)hExtraIcon);
+ if (extra == nullptr)
+ return -1;
+
+ if (extra->getParent())
+ return extra->getParent()->internalSetIcon(extra, hContact, nullptr, false);
+
+ return extra->setIcon(hContact, nullptr);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static IconItem iconList[] =
+{
+ { LPGEN("Chat activity"), "ChatActivity", IDI_CHAT },
+ { LPGEN("Mute chat"), "ChatMute", IDI_OFF },
+ { LPGEN("Male"), "gender_male", IDI_MALE },
+ { LPGEN("Female"), "gender_female", IDI_FEMALE },
+ { LPGEN("Database"), "database", IDI_DATABASE },
+};
+
+void LoadExtraIconsModule()
+{
+ // Events
+ hEventExtraClick = CreateHookableEvent(ME_CLIST_EXTRA_CLICK);
+ hEventExtraImageApplying = CreateHookableEvent(ME_CLIST_EXTRA_IMAGE_APPLY);
+ hEventExtraImageListRebuilding = CreateHookableEvent(ME_CLIST_EXTRA_LIST_REBUILD);
+
+ // Icons
+ g_plugin.registerIcon(LPGEN("Contact list"), iconList);
+
+ // Hooks
+ HookEvent(ME_OPT_INITIALISE, InitOptionsCallback);
+
+ HookEvent(ME_CLIST_EXTRA_LIST_REBUILD, ClistExtraListRebuild);
+ HookEvent(ME_CLIST_EXTRA_IMAGE_APPLY, ClistExtraImageApply);
+ HookEvent(ME_CLIST_EXTRA_CLICK, ClistExtraClick);
+
+ DefaultExtraIcons_Load();
+}
+
+void UnloadExtraIconsModule(void)
+{
+ for (auto &extra : extraIconsBySlot)
+ if (extra->getType() == EXTRAICON_TYPE_GROUP)
+ delete extra;
+
+ for (auto &it : registeredExtraIcons)
+ delete it;
+
+ if (hExtraImageList) {
+ ImageList_Destroy(hExtraImageList);
+ hExtraImageList = nullptr;
+ }
+}
diff --git a/src/mir_app/src/encrypt.cpp b/src/mir_app/src/encrypt.cpp index 79298a9221..d08d8e24c6 100644 --- a/src/mir_app/src/encrypt.cpp +++ b/src/mir_app/src/encrypt.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/encrypt.h b/src/mir_app/src/encrypt.h index bcb37cd799..034e38804e 100644 --- a/src/mir_app/src/encrypt.h +++ b/src/mir_app/src/encrypt.h @@ -1,47 +1,47 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#pragma once - -struct MCryptoProvider : public CRYPTO_PROVIDER -{ - MCryptoProvider(const CRYPTO_PROVIDER *pProvider) - { - memcpy(this, pProvider, sizeof(*pProvider)); - pszName = mir_strdup(pszName); - if (dwFlags & CPF_UNICODE) - szDescr.w = mir_wstrdup(TranslateW_LP(pProvider->szDescr.w, pProvider->pPlugin)); - else - szDescr.w = mir_a2u(TranslateA_LP(pProvider->szDescr.a, pProvider->pPlugin)); - } - - ~MCryptoProvider() - { - mir_free(pszName); - mir_free(szDescr.w); - } -}; - -extern OBJLIST<MCryptoProvider> arCryptoProviders; - -void InitCryptMenuItem(CMenuItem &mi); +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#pragma once
+
+struct MCryptoProvider : public CRYPTO_PROVIDER
+{
+ MCryptoProvider(const CRYPTO_PROVIDER *pProvider)
+ {
+ memcpy(this, pProvider, sizeof(*pProvider));
+ pszName = mir_strdup(pszName);
+ if (dwFlags & CPF_UNICODE)
+ szDescr.w = mir_wstrdup(TranslateW_LP(pProvider->szDescr.w, pProvider->pPlugin));
+ else
+ szDescr.w = mir_a2u(TranslateA_LP(pProvider->szDescr.a, pProvider->pPlugin));
+ }
+
+ ~MCryptoProvider()
+ {
+ mir_free(pszName);
+ mir_free(szDescr.w);
+ }
+};
+
+extern OBJLIST<MCryptoProvider> arCryptoProviders;
+
+void InitCryptMenuItem(CMenuItem &mi);
diff --git a/src/mir_app/src/enterstring.cpp b/src/mir_app/src/enterstring.cpp index adfed7e805..69897231c1 100644 --- a/src/mir_app/src/enterstring.cpp +++ b/src/mir_app/src/enterstring.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/extracticon.cpp b/src/mir_app/src/extracticon.cpp index 5bd7a1cd76..415eaee2f1 100644 --- a/src/mir_app/src/extracticon.cpp +++ b/src/mir_app/src/extracticon.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/extraicons.h b/src/mir_app/src/extraicons.h index d0b4b8a711..b22075a467 100644 --- a/src/mir_app/src/extraicons.h +++ b/src/mir_app/src/extraicons.h @@ -1,7 +1,7 @@ /*
Copyright (C) 2009 Ricardo Pescuma Domenecci
-Copyright (C) 2012-22 Miranda NG team
+Copyright (C) 2012-23 Miranda NG team
This is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
diff --git a/src/mir_app/src/filter.cpp b/src/mir_app/src/filter.cpp index 8e6af687b5..27bf9cd3b0 100644 --- a/src/mir_app/src/filter.cpp +++ b/src/mir_app/src/filter.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/filter.h b/src/mir_app/src/filter.h index a583915eba..8c8964bfa1 100644 --- a/src/mir_app/src/filter.h +++ b/src/mir_app/src/filter.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/findadd.cpp b/src/mir_app/src/findadd.cpp index 88850481d8..bd7613b444 100644 --- a/src/mir_app/src/findadd.cpp +++ b/src/mir_app/src/findadd.cpp @@ -1,1035 +1,1035 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "findadd.h" - -#define TIMERID_THROBBER 111 - -#define HM_SEARCHACK (WM_USER+10) -#define M_SETGROUPVISIBILITIES (WM_USER+11) - -static HWND hwndFindAdd = nullptr; -static HANDLE hHookModulesLoaded = nullptr; -static HGENMENU hMainMenuItem = nullptr; -static int OnSystemModulesLoaded(WPARAM wParam, LPARAM lParam); - -static int FindAddDlgResizer(HWND, LPARAM lParam, UTILRESIZECONTROL *urc) -{ - static int y, nextY, oldTop; - FindAddDlgData *dat = (FindAddDlgData*)lParam; - - switch (urc->wId) { - case IDC_RESULTS: - return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT; - - case IDOK: - dat->minDlgHeight = nextY + urc->rcItem.bottom - urc->rcItem.top; - return RD_ANCHORX_LEFT | RD_ANCHORY_BOTTOM; - - case IDC_ADD: - case IDC_MOREOPTIONS: - return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM; - - case IDC_STATUSBAR: - return RD_ANCHORX_WIDTH | RD_ANCHORY_BOTTOM; - - case IDC_PROTOIDGROUP: //the resize is always processed in template order - nextY = y = urc->rcItem.top; - if (dat->showProtoId) nextY = y + urc->rcItem.bottom - urc->rcItem.top + 7; - break; - - case IDC_EMAILGROUP: - oldTop = urc->rcItem.top; - y = nextY; - if (dat->showEmail) nextY = y + urc->rcItem.bottom - urc->rcItem.top + 7; - OffsetRect(&urc->rcItem, 0, y - oldTop); - return RD_ANCHORX_LEFT | RD_ANCHORY_CUSTOM; - - case IDC_NAMEGROUP: - oldTop = urc->rcItem.top; - y = nextY; - if (dat->showName) nextY = y + urc->rcItem.bottom - urc->rcItem.top + 7; - OffsetRect(&urc->rcItem, 0, y - oldTop); - return RD_ANCHORX_LEFT | RD_ANCHORY_CUSTOM; - - case IDC_ADVANCEDGROUP: - oldTop = urc->rcItem.top; - y = nextY; - if (dat->showAdvanced) nextY = y + urc->rcItem.bottom - urc->rcItem.top + 7; - OffsetRect(&urc->rcItem, 0, y - oldTop); - return RD_ANCHORX_LEFT | RD_ANCHORY_CUSTOM; - - case IDC_TINYEXTENDEDGROUP: - oldTop = urc->rcItem.top; - y = nextY; - if (dat->showTiny) { - int height = urc->dlgNewSize.cy - y - (urc->dlgOriginalSize.cy - urc->rcItem.bottom); - nextY = y + 200; //min height for custom dialog - urc->rcItem.top = urc->rcItem.bottom - height; - } - return RD_ANCHORX_LEFT | RD_ANCHORY_BOTTOM; - - case IDC_BYEMAIL: - case IDC_EMAIL: - case IDC_BYNAME: - case IDC_STNAMENICK: - case IDC_STNAMEFIRST: - case IDC_STNAMELAST: - case IDC_NAMENICK: - case IDC_NAMEFIRST: - case IDC_NAMELAST: - case IDC_BYADVANCED: - case IDC_BYCUSTOM: - case IDC_ADVANCED: - OffsetRect(&urc->rcItem, 0, y - oldTop); - return RD_ANCHORX_LEFT | RD_ANCHORY_CUSTOM; - - case IDC_HEADERBAR: - return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORX_WIDTH; - } - return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; -} - -static void RenderThrobber(HDC hdc, RECT *rcItem, int *throbbing, int *pivot) -{ - InflateRect(rcItem, -1, 0); - int width = rcItem->right - rcItem->left; - int height = rcItem->bottom - rcItem->top; - int height2 = height / 2; - - if (*throbbing) { - /* create memdc */ - HDC hMemDC = CreateCompatibleDC(nullptr); - HBITMAP hBitmap = (HBITMAP)SelectObject(hMemDC, CreateCompatibleBitmap(hdc, width, height)); - /* flush it */ - RECT rc; - rc.left = rc.top = 0; - rc.right = width; - rc.bottom = height; - HBRUSH hBr = GetSysColorBrush(COLOR_BTNFACE); - FillRect(hMemDC, &rc, hBr); - DeleteObject(hBr); - /* set up the pen */ - HPEN hPen = (HPEN)SelectObject(hMemDC, CreatePen(PS_SOLID, 4, GetSysColor(COLOR_BTNSHADOW))); - /* draw everything before the pivot */ - int x = *pivot; - while (x > (-height)) { - MoveToEx(hMemDC, x + height2, 0, nullptr); - LineTo(hMemDC, x - height2, height); - x -= 12; - } - - /* draw everything after the pivot */ - x = *pivot; - while (x < width + height) { - MoveToEx(hMemDC, x + height2, 0, nullptr); - LineTo(hMemDC, x - height2, height); - x += 12; - } - - /* move the pivot */ - *pivot += 2; - /* reset the pivot point if it gets past the rect */ - if (*pivot > width) *pivot = 0; - /* put back the old pen and delete the new one */ - DeleteObject(SelectObject(hMemDC, hPen)); - /* cap the top and bottom */ - hPen = (HPEN)SelectObject(hMemDC, CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNFACE))); - MoveToEx(hMemDC, 0, 0, nullptr); - LineTo(hMemDC, width, 0); - MoveToEx(hMemDC, 0, height - 1, nullptr); - LineTo(hMemDC, width, height - 1); - /* select in the old pen and delete the new pen */ - DeleteObject(SelectObject(hMemDC, hPen)); - /* paint to screen */ - BitBlt(hdc, rcItem->left, rcItem->top, width, height, hMemDC, 0, 0, SRCCOPY); - /* select back in the old bitmap and delete the created one, as well as freeing the mem dc. */ - hBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); - DeleteObject(hBitmap); - DeleteDC(hMemDC); - } - else { - /* just flush the DC */ - HBRUSH hBr = GetSysColorBrush(COLOR_BTNFACE); - FillRect(hdc, rcItem, hBr); - DeleteObject(hBr); - } -} - -static void StartThrobber(HWND hwndDlg, FindAddDlgData *dat) -{ - dat->throbbing = 1; - SetTimer(hwndDlg, TIMERID_THROBBER, 25, nullptr); -} - -static void StopThrobber(HWND hwndDlg, FindAddDlgData *dat) -{ - KillTimer(hwndDlg, TIMERID_THROBBER); - dat->throbbing = 0; - dat->pivot = 0; - InvalidateRect(GetDlgItem(hwndDlg, IDC_STATUSBAR), nullptr, FALSE); -} - -static LRESULT CALLBACK AdvancedSearchDlgSubclassProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - if (msg == WM_COMMAND) { - HWND parentHwnd = GetParent(hwndDlg); - switch (LOWORD(wParam)) { - case IDOK: - SendMessage(parentHwnd, WM_COMMAND, MAKEWPARAM(IDOK, BN_CLICKED), (LPARAM)GetDlgItem(parentHwnd, IDOK)); - SetFocus(GetDlgItem(parentHwnd, IDC_ADVANCED)); - break; - - case IDCANCEL: - CheckDlgButton(parentHwnd, IDC_ADVANCED, BST_UNCHECKED); - SendMessage(parentHwnd, WM_COMMAND, MAKEWPARAM(IDC_ADVANCED, BN_CLICKED), (LPARAM)GetDlgItem(parentHwnd, IDC_ADVANCED)); - SetFocus(GetDlgItem(parentHwnd, IDC_ADVANCED)); - break; - } - } - return mir_callNextSubclass(hwndDlg, AdvancedSearchDlgSubclassProc, msg, wParam, lParam); -} - -static void ShowAdvancedSearchDlg(HWND hwndDlg, FindAddDlgData *dat) -{ - char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0); - if (szProto == nullptr) - return; - - if (dat->hwndAdvSearch == nullptr) { - RECT rc; - dat->hwndAdvSearch = (HWND)CallProtoServiceInt(0, szProto, PS_CREATEADVSEARCHUI, 0, (LPARAM)hwndDlg); - if (dat->hwndAdvSearch != nullptr) - mir_subclassWindow(dat->hwndAdvSearch, AdvancedSearchDlgSubclassProc); - GetWindowRect(GetDlgItem(hwndDlg, IDC_RESULTS), &rc); - SetWindowPos(dat->hwndAdvSearch, nullptr, rc.left, rc.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE); - } - - AnimateWindow(dat->hwndAdvSearch, 150, AW_ACTIVATE | AW_SLIDE | AW_HOR_POSITIVE); - RedrawWindow(dat->hwndAdvSearch, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN); - - CheckDlgButton(hwndDlg, IDC_ADVANCED, BST_CHECKED); -} - -static void ReposTinySearchDlg(HWND hwndDlg, FindAddDlgData *dat) -{ - if (dat->hwndTinySearch == nullptr) - return; - - RECT rc; - RECT clientRect; - POINT pt = { 0, 0 }; - GetWindowRect(GetDlgItem(hwndDlg, IDC_TINYEXTENDEDGROUP), &rc); - GetWindowRect(dat->hwndTinySearch, &clientRect); - pt.x = rc.left; - pt.y = rc.top; - ScreenToClient(hwndDlg, &pt); - SetWindowPos(dat->hwndTinySearch, nullptr, pt.x + 5, pt.y + 15, rc.right - rc.left - 10, rc.bottom - rc.top - 30, SWP_NOZORDER); -} - -static void ShowTinySearchDlg(HWND hwndDlg, FindAddDlgData *dat) -{ - char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0); - if (szProto == nullptr) - return; - - if (dat->hwndTinySearch == nullptr) { - dat->hwndTinySearch = (HWND)CallProtoServiceInt(0, szProto, PS_CREATEADVSEARCHUI, 0, (LPARAM)/*GetDlgItem(*/hwndDlg/*, IDC_TINYEXTENDEDGROUP)*/); - if (dat->hwndTinySearch) - ReposTinySearchDlg(hwndDlg, dat); - else - dat->showTiny = false; - } - ShowWindow(dat->hwndTinySearch, SW_SHOW); -} - -static void HideAdvancedSearchDlg(HWND hwndDlg, FindAddDlgData *dat) -{ - if (dat->hwndAdvSearch == nullptr) - return; - - AnimateWindow(dat->hwndAdvSearch, 150, AW_HIDE | AW_BLEND); - CheckDlgButton(hwndDlg, IDC_ADVANCED, BST_UNCHECKED); -} - -void EnableResultButtons(HWND hwndDlg, int enable) -{ - EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), enable); - EnableWindow(GetDlgItem(hwndDlg, IDC_MOREOPTIONS), enable); -} - -static const int controls[] = { IDC_BYPROTOID, IDC_BYEMAIL, IDC_BYNAME, IDC_BYADVANCED, IDC_BYCUSTOM }; - -static void CheckSearchTypeRadioButton(HWND hwndDlg, int idControl) -{ - for (auto &it : controls) - CheckDlgButton(hwndDlg, it, idControl == it ? BST_CHECKED : BST_UNCHECKED); -} - -#define sttErrMsg TranslateT("You haven't filled in the search field. Please enter a search term and try again.") -#define sttErrTitle TranslateT("Search") - -static void SetListItemText(HWND hwndList, int idx, int col, wchar_t *szText) -{ - if (szText == nullptr || *szText == 0) - szText = TranslateT("<not specified>"); - - ListView_SetItemText(hwndList, idx, col, szText); -} - -static wchar_t* sttDecodeString(uint32_t dwFlags, MAllStrings &src) -{ - if (dwFlags & PSR_UNICODE) - return mir_wstrdup(src.w); - - if (dwFlags & PSR_UTF8) - return mir_utf8decodeW(src.a); - - return mir_a2u(src.a); -} - -static INT_PTR CALLBACK DlgProcFindAdd(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - FindAddDlgData *dat = (FindAddDlgData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); - HWND hwndList = GetDlgItem(hwndDlg, IDC_RESULTS); - RECT rc, rc2; - - switch (msg) { - case WM_INITDIALOG: - TranslateDialogDefault(hwndDlg); - Window_SetSkinIcon_IcoLib(hwndDlg, SKINICON_OTHER_FINDUSER); - dat = (FindAddDlgData*)mir_calloc(sizeof(FindAddDlgData)); - SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); - dat->notSearchedYet = 1; - dat->iLastColumnSortIndex = 1; - dat->bSortAscending = 1; - SendDlgItemMessage(hwndDlg, IDC_MOREOPTIONS, BUTTONSETARROW, 1, 0); - SendDlgItemMessage(hwndDlg, IDOK, BUTTONADDTOOLTIP, (WPARAM)LPGENW("Ctrl+Search add contact"), BATF_UNICODE); - - ListView_SetExtendedListViewStyle(hwndList, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP); - - GetClientRect(hwndList, &rc); - { - LVCOLUMN lvc; - lvc.mask = LVCF_TEXT | LVCF_WIDTH; - lvc.pszText = TranslateT("Results"); - lvc.cx = rc.right - 1; - ListView_InsertColumn(hwndList, 0, &lvc); - - LVITEM lvi; - lvi.mask = LVIF_TEXT; - lvi.iItem = 0; - lvi.iSubItem = 0; - lvi.pszText = TranslateT("There are no results to display."); - ListView_InsertItem(hwndList, &lvi); - - // Allocate a reasonable amount of space in the status bar - HDC hdc = GetDC(GetDlgItem(hwndDlg, IDC_STATUSBAR)); - SelectObject(hdc, (HFONT)SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, WM_GETFONT, 0, 0)); - - SIZE textSize; - GetTextExtentPoint32(hdc, TranslateT("Searching"), (int)mir_wstrlen(TranslateT("Searching")), &textSize); - - int partWidth[3]; - partWidth[0] = textSize.cx; - GetTextExtentPoint32(hdc, L"01234567890123456789", 20, &textSize); - partWidth[0] += textSize.cx; - ReleaseDC(GetDlgItem(hwndDlg, IDC_STATUSBAR), hdc); - partWidth[1] = partWidth[0] + 150; - partWidth[2] = -1; - SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_SETPARTS, _countof(partWidth), (LPARAM)partWidth); - SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_SETTEXT, 1 | SBT_OWNERDRAW, 0); - SetStatusBarSearchInfo(GetDlgItem(hwndDlg, IDC_STATUSBAR), dat); - - wchar_t *szProto = nullptr; - ptrW tszLast(db_get_wsa(0, "FindAdd", "LastSearched")); - if (tszLast) - szProto = NEWWSTR_ALLOCA(tszLast); - - int index = 0, cbwidth = 0, netProtoCount = 0; - for (auto &pa : g_arAccounts) { - if (!pa->IsEnabled()) - continue; - - uint32_t caps = (uint32_t)CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); - if (caps & PF1_ANYSEARCH) - netProtoCount++; - } - dat->himlComboIcons = ImageList_Create(g_iIconSX, g_iIconSY, ILC_COLOR32 | ILC_MASK, netProtoCount + 1, netProtoCount + 1); - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_SETIMAGELIST, 0, (LPARAM)dat->himlComboIcons); - - COMBOBOXEXITEM cbei; - cbei.mask = CBEIF_IMAGE | CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_LPARAM; - cbei.iItem = 0; - hdc = GetDC(hwndDlg); - SelectObject(hdc, (HFONT)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, WM_GETFONT, 0, 0)); - if (netProtoCount > 1) { - cbei.pszText = TranslateT("All networks"); - GetTextExtentPoint32(hdc, cbei.pszText, (int)mir_wstrlen(cbei.pszText), &textSize); - if (textSize.cx > cbwidth) - cbwidth = textSize.cx; - cbei.iImage = cbei.iSelectedImage = ImageList_AddSkinIcon(dat->himlComboIcons, SKINICON_OTHER_SEARCHALL); - cbei.lParam = 0; - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_INSERTITEM, 0, (LPARAM)&cbei); - cbei.iItem++; - } - - for (auto &pa : g_arAccounts) { - if (!pa->IsEnabled()) - continue; - - uint32_t caps = (uint32_t)CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); - if (!(caps & PF1_ANYSEARCH)) - continue; - - cbei.pszText = pa->tszAccountName; - GetTextExtentPoint32(hdc, cbei.pszText, (int)mir_wstrlen(cbei.pszText), &textSize); - if (textSize.cx > cbwidth) - cbwidth = textSize.cx; - - HICON hIcon = (HICON)CallProtoServiceInt(0, pa->szModuleName, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0); - cbei.iImage = cbei.iSelectedImage = ImageList_AddIcon(dat->himlComboIcons, hIcon); - DestroyIcon(hIcon); - cbei.lParam = (LPARAM)pa->szModuleName; - SendDlgItemMessageA(hwndDlg, IDC_PROTOLIST, CBEM_INSERTITEM, 0, (LPARAM)&cbei); - if (szProto && cbei.pszText && !mir_wstrcmp(szProto, pa->tszAccountName)) - index = cbei.iItem; - cbei.iItem++; - } - cbwidth += 32; - - RECT rect; - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&rect); - if ((rect.right - rect.left) < cbwidth) - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_SETDROPPEDWIDTH, cbwidth, 0); - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_SETCURSEL, index, 0); - - SendMessage(hwndDlg, M_SETGROUPVISIBILITIES, 0, 0); - Utils_RestoreWindowPosition(hwndDlg, 0, "FindAdd", ""); - } - return TRUE; - - case WM_SIZE: - Utils_ResizeDialog(hwndDlg, g_plugin.getInst(), MAKEINTRESOURCEA(IDD_FINDADD), FindAddDlgResizer, (LPARAM)dat); - ReposTinySearchDlg(hwndDlg, dat); - SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, WM_SIZE, 0, 0); - if (dat->notSearchedYet) { - GetClientRect(hwndList, &rc); - ListView_SetColumnWidth(hwndList, 0, rc.right); - } - __fallthrough; - - case WM_MOVE: - if (dat && dat->hwndAdvSearch) { - GetWindowRect(hwndList, &rc); - SetWindowPos(dat->hwndAdvSearch, nullptr, rc.left, rc.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE); - } - break; - - case WM_GETMINMAXINFO: - GetWindowRect(hwndList, &rc); - GetWindowRect(hwndDlg, &rc2); - { - MINMAXINFO *mmi = (MINMAXINFO*)lParam; - mmi->ptMinTrackSize.x = rc.left - rc2.left + 10 + GetSystemMetrics(SM_CXFRAME); - GetClientRect(GetDlgItem(hwndDlg, IDC_MOREOPTIONS), &rc); - mmi->ptMinTrackSize.x += rc.right + 5; - GetClientRect(GetDlgItem(hwndDlg, IDC_ADD), &rc); - mmi->ptMinTrackSize.x += rc.right + 5; - GetClientRect(GetDlgItem(hwndDlg, IDC_STATUSBAR), &rc); - mmi->ptMinTrackSize.y = dat->minDlgHeight + 20 + GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFRAME); - GetClientRect(GetDlgItem(hwndDlg, IDC_STATUSBAR), &rc); - mmi->ptMinTrackSize.y += rc.bottom; - } - return 0; - - case M_SETGROUPVISIBILITIES: - dat->showAdvanced = dat->showEmail = dat->showName = dat->showProtoId = dat->showTiny = 0; - { - char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0); - if (szProto == (char *)CB_ERR) - break; - if (szProto == nullptr) { - for (auto &pa : g_arAccounts) { - if (pa->IsEnabled()) { - uint32_t protoCaps = (uint32_t)CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); - if (protoCaps & PF1_SEARCHBYEMAIL) dat->showEmail = 1; - if (protoCaps & PF1_SEARCHBYNAME) dat->showName = 1; - } - } - } - else { - uint32_t protoCaps = (uint32_t)CallProtoServiceInt(0, szProto, PS_GETCAPS, PFLAGNUM_1, 0); - if (protoCaps & PF1_BASICSEARCH) dat->showProtoId = 1; - if (protoCaps & PF1_SEARCHBYEMAIL) dat->showEmail = 1; - if (protoCaps & PF1_SEARCHBYNAME) dat->showName = 1; - - if (protoCaps & PF1_EXTSEARCHUI) dat->showAdvanced = 1; - else if (protoCaps & PF1_EXTSEARCH) dat->showTiny = 1; - - if (protoCaps & PF1_USERIDISEMAIL && dat->showProtoId) { dat->showProtoId = 0; dat->showEmail = 1; } - if (dat->showProtoId) { - wchar_t *wszUniqueId = (wchar_t *)CallProtoServiceInt(0, szProto, PS_GETCAPS, PFLAG_UNIQUEIDTEXT, 0); - if (wszUniqueId) - SetDlgItemTextW(hwndDlg, IDC_BYPROTOID, wszUniqueId); - else - SetDlgItemTextW(hwndDlg, IDC_BYPROTOID, TranslateT("Handle")); - - if (protoCaps & PF1_NUMERICUSERID) - SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PROTOID), GWL_STYLE, GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PROTOID), GWL_STYLE) | ES_NUMBER); - else - SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PROTOID), GWL_STYLE, GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PROTOID), GWL_STYLE)&~ES_NUMBER); - } - } - - if (dat->showTiny) - ShowTinySearchDlg(hwndDlg, dat); - else if (dat->hwndTinySearch) { - DestroyWindow(dat->hwndTinySearch); - dat->hwndTinySearch = nullptr; - } - -#define en(id, t) ShowWindow( GetDlgItem(hwndDlg, IDC_##id), dat->show##t?SW_SHOW:SW_HIDE) - en(PROTOIDGROUP, ProtoId); en(BYPROTOID, ProtoId); en(PROTOID, ProtoId); - en(EMAILGROUP, Email); en(BYEMAIL, Email); en(EMAIL, Email); - en(NAMEGROUP, Name); en(BYNAME, Name); - en(STNAMENICK, Name); en(NAMENICK, Name); - en(STNAMEFIRST, Name); en(NAMEFIRST, Name); - en(STNAMELAST, Name); en(NAMELAST, Name); - en(ADVANCEDGROUP, Advanced); en(BYADVANCED, Advanced); en(ADVANCED, Advanced); - en(BYCUSTOM, Tiny); en(TINYEXTENDEDGROUP, Tiny); -#undef en - int checkmarkVisible = (dat->showAdvanced && IsDlgButtonChecked(hwndDlg, IDC_BYADVANCED)) || - (dat->showEmail && IsDlgButtonChecked(hwndDlg, IDC_BYEMAIL)) || - (dat->showTiny && IsDlgButtonChecked(hwndDlg, IDC_BYCUSTOM)) || - (dat->showName && IsDlgButtonChecked(hwndDlg, IDC_BYNAME)) || - (dat->showProtoId && IsDlgButtonChecked(hwndDlg, IDC_BYPROTOID)); - if (!checkmarkVisible) { - if (dat->showProtoId) CheckSearchTypeRadioButton(hwndDlg, IDC_BYPROTOID); - else if (dat->showEmail) CheckSearchTypeRadioButton(hwndDlg, IDC_BYEMAIL); - else if (dat->showName) CheckSearchTypeRadioButton(hwndDlg, IDC_BYNAME); - else if (dat->showAdvanced) CheckSearchTypeRadioButton(hwndDlg, IDC_BYADVANCED); - else if (dat->showTiny) CheckSearchTypeRadioButton(hwndDlg, IDC_BYCUSTOM); - } - - SendMessage(hwndDlg, WM_SIZE, 0, 0); - - MINMAXINFO mmi; - SendMessage(hwndDlg, WM_GETMINMAXINFO, 0, (LPARAM)&mmi); - - GetWindowRect(hwndDlg, &rc); - if (rc.bottom - rc.top < mmi.ptMinTrackSize.y) - SetWindowPos(hwndDlg, nullptr, 0, 0, rc.right - rc.left, mmi.ptMinTrackSize.y, SWP_NOZORDER | SWP_NOMOVE); - } - break; - - case WM_TIMER: - if (wParam == TIMERID_THROBBER) { - int borders[3]; - SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_GETBORDERS, 0, (LPARAM)borders); - - SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_GETRECT, 1, (LPARAM)&rc); - InflateRect(&rc, -borders[2] / 2, -borders[1] / 2); - HDC hdc = GetDC(GetDlgItem(hwndDlg, IDC_STATUSBAR)); - RenderThrobber(hdc, &rc, &dat->throbbing, &dat->pivot); - ReleaseDC(GetDlgItem(hwndDlg, IDC_STATUSBAR), hdc); - } - break; - - case WM_DRAWITEM: - { - DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT*)lParam; - if (dis->CtlID == IDC_STATUSBAR && dis->itemID == 1) { - RenderThrobber(dis->hDC, &dis->rcItem, &dat->throbbing, &dat->pivot); - return TRUE; - } - } - break; - - case WM_NOTIFY: - if (wParam == IDC_RESULTS) { - switch (((LPNMHDR)lParam)->code) { - case LVN_ITEMCHANGED: - { - int count = ListView_GetSelectedCount(hwndList); - if (dat->notSearchedYet) - count = 0; - EnableResultButtons(hwndDlg, count); - } - break; - - case LVN_COLUMNCLICK: - HDITEM hdi; - hdi.mask = HDI_FORMAT; - hdi.fmt = HDF_LEFT | HDF_STRING; - Header_SetItem(ListView_GetHeader(hwndList), dat->iLastColumnSortIndex, &hdi); - - LPNMLISTVIEW nmlv = (LPNMLISTVIEW)lParam; - if (nmlv->iSubItem != dat->iLastColumnSortIndex) { - dat->bSortAscending = TRUE; - dat->iLastColumnSortIndex = nmlv->iSubItem; - } - else dat->bSortAscending = !dat->bSortAscending; - - hdi.fmt = HDF_LEFT | HDF_STRING | (dat->bSortAscending ? HDF_SORTDOWN : HDF_SORTUP); - Header_SetItem(ListView_GetHeader(hwndList), dat->iLastColumnSortIndex, &hdi); - - ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg); - } - } - break; - - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDC_PROTOLIST: - if (HIWORD(wParam) == CBN_SELCHANGE) { - HideAdvancedSearchDlg(hwndDlg, dat); - if (dat->hwndAdvSearch) { - DestroyWindow(dat->hwndAdvSearch); - dat->hwndAdvSearch = nullptr; - } - if (dat->hwndTinySearch) { - DestroyWindow(dat->hwndTinySearch); - dat->hwndTinySearch = nullptr; - } - SendMessage(hwndDlg, M_SETGROUPVISIBILITIES, 0, 0); - } - break; - - case IDC_BYPROTOID: - case IDC_BYEMAIL: - case IDC_BYNAME: - { - int count = ListView_GetSelectedCount(hwndList); - if (dat->notSearchedYet) - count = 0; - EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), count); - HideAdvancedSearchDlg(hwndDlg, dat); - } - break; - - case IDC_PROTOID: - if (HIWORD(wParam) == EN_CHANGE) { - HideAdvancedSearchDlg(hwndDlg, dat); - CheckSearchTypeRadioButton(hwndDlg, IDC_BYPROTOID); - } - break; - - case IDC_EMAIL: - if (HIWORD(wParam) == EN_CHANGE) { - HideAdvancedSearchDlg(hwndDlg, dat); - CheckSearchTypeRadioButton(hwndDlg, IDC_BYEMAIL); - } - break; - - case IDC_NAMENICK: - case IDC_NAMEFIRST: - case IDC_NAMELAST: - if (HIWORD(wParam) == EN_CHANGE) { - HideAdvancedSearchDlg(hwndDlg, dat); - CheckSearchTypeRadioButton(hwndDlg, IDC_BYNAME); - } - break; - - case IDC_ADVANCED: - EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), ListView_GetSelectedCount(hwndList) > 0); - if (IsDlgButtonChecked(hwndDlg, IDC_ADVANCED)) - ShowAdvancedSearchDlg(hwndDlg, dat); - else - HideAdvancedSearchDlg(hwndDlg, dat); - CheckSearchTypeRadioButton(hwndDlg, IDC_BYADVANCED); - break; - - case IDCANCEL: - DestroyWindow(hwndDlg); - break; - - case IDOK: - HideAdvancedSearchDlg(hwndDlg, dat); - if (dat->searchCount) { //cancel search - SetDlgItemText(hwndDlg, IDOK, TranslateT("&Search")); - if (dat->hResultHook) { UnhookEvent(dat->hResultHook); dat->hResultHook = nullptr; } - if (dat->search) { mir_free(dat->search); dat->search = nullptr; } - dat->searchCount = 0; - StopThrobber(hwndDlg, dat); - SetStatusBarSearchInfo(GetDlgItem(hwndDlg, IDC_STATUSBAR), dat); - } - else { - char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0); - if (dat->search) - mir_free(dat->search), dat->search = nullptr; - dat->searchCount = 0; - dat->hResultHook = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_SEARCHACK); - if (IsDlgButtonChecked(hwndDlg, IDC_BYCUSTOM)) - BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYADVANCED, PF1_EXTSEARCHUI, dat->hwndTinySearch); - else if (IsDlgButtonChecked(hwndDlg, IDC_BYPROTOID)) { - wchar_t str[256]; - GetDlgItemText(hwndDlg, IDC_PROTOID, str, _countof(str)); - rtrimw(str); - if (str[0] == 0) - MessageBoxW(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK); - else - BeginSearch(hwndDlg, dat, szProto, PS_BASICSEARCH, PF1_BASICSEARCH, str); - } - else if (IsDlgButtonChecked(hwndDlg, IDC_BYEMAIL)) { - wchar_t str[256]; - GetDlgItemText(hwndDlg, IDC_EMAIL, str, _countof(str)); - rtrimw(str); - if (str[0] == 0) - MessageBoxW(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK); - else - BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYEMAIL, PF1_SEARCHBYEMAIL, str); - } - else if (IsDlgButtonChecked(hwndDlg, IDC_BYNAME)) { - wchar_t nick[256], first[256], last[256]; - PROTOSEARCHBYNAME psbn; - GetDlgItemText(hwndDlg, IDC_NAMENICK, nick, _countof(nick)); - GetDlgItemText(hwndDlg, IDC_NAMEFIRST, first, _countof(first)); - GetDlgItemText(hwndDlg, IDC_NAMELAST, last, _countof(last)); - psbn.pszFirstName = first; - psbn.pszLastName = last; - psbn.pszNick = nick; - if (nick[0] == 0 && first[0] == 0 && last[0] == 0) - MessageBoxW(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK); - else - BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYNAME, PF1_SEARCHBYNAME, &psbn); - } - else if (IsDlgButtonChecked(hwndDlg, IDC_BYADVANCED)) { - if (dat->hwndAdvSearch == nullptr) - MessageBoxW(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK); - else - BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYADVANCED, PF1_EXTSEARCHUI, dat->hwndAdvSearch); - } - - if (dat->searchCount == 0) { - if (dat->hResultHook) { - UnhookEvent(dat->hResultHook); - dat->hResultHook = nullptr; - } - break; - } - - dat->notSearchedYet = 0; - FreeSearchResults(hwndList); - - CreateResultsColumns(hwndList, dat, szProto); - SetStatusBarSearchInfo(GetDlgItem(hwndDlg, IDC_STATUSBAR), dat); - SetStatusBarResultInfo(hwndDlg); - StartThrobber(hwndDlg, dat); - SetDlgItemText(hwndDlg, IDOK, TranslateT("Cancel")); - } - break; - - case IDC_ADD: - if (ListView_GetSelectedCount(hwndList) == 1) { - LVITEM lvi; - lvi.mask = LVIF_PARAM; - lvi.iItem = ListView_GetNextItem(hwndList, -1, LVNI_ALL | LVNI_SELECTED); - ListView_GetItem(hwndList, &lvi); - ListSearchResult *lsr = (ListSearchResult*)lvi.lParam; - Contact::AddBySearch(lsr->szProto, &lsr->psr, hwndDlg); - } - else { - wchar_t str[256]; - GetDlgItemText(hwndDlg, IDC_PROTOID, str, _countof(str)); - if (*rtrimw(str) == 0) - break; - - char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0); - - PROTOSEARCHRESULT psr = { 0 }; - psr.cbSize = sizeof(psr); - psr.flags = PSR_UNICODE; - psr.id.w = str; - Contact::AddBySearch(szProto, &psr, hwndDlg); - } - break; - - case IDC_MOREOPTIONS: - GetWindowRect(GetDlgItem(hwndDlg, IDC_MOREOPTIONS), &rc); - ShowMoreOptionsMenu(hwndDlg, rc.left, rc.bottom); - break; - } - - if (lParam && dat->hwndTinySearch == (HWND)lParam && - HIWORD(wParam) == EN_SETFOCUS && LOWORD(wParam) == 0 && - BST_UNCHECKED == IsDlgButtonChecked(hwndDlg, IDC_BYCUSTOM)) { - CheckSearchTypeRadioButton(hwndDlg, IDC_BYCUSTOM); - } - break; - - case WM_CONTEXTMENU: - { - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - - LVHITTESTINFO lvhti; - lvhti.pt = pt; - ScreenToClient(hwndDlg, &pt); - switch (GetDlgCtrlID(ChildWindowFromPoint(hwndDlg, pt))) { - case IDC_RESULTS: - if (dat->notSearchedYet) - return TRUE; - ScreenToClient(hwndList, &lvhti.pt); - if (ListView_HitTest(hwndList, &lvhti) == -1) - break; - ShowMoreOptionsMenu(hwndDlg, (short)LOWORD(lParam), (short)HIWORD(lParam)); - return TRUE; - } - } - break; - - case HM_SEARCHACK: - { - ACKDATA *ack = (ACKDATA*)lParam; - if (ack->type != ACKTYPE_SEARCH) - break; - - int i; - for (i = 0; i < dat->searchCount; i++) - if (dat->search[i].hProcess == ack->hProcess && dat->search[i].hProcess != nullptr && !mir_strcmp(dat->search[i].szProto, ack->szModule)) break; - if (i == dat->searchCount) - break; - - if (ack->result == ACKRESULT_SUCCESS || ack->result == ACKRESULT_FAILED) { - dat->searchCount--; - memmove(dat->search + i, dat->search + i + 1, sizeof(struct ProtoSearchInfo)*(dat->searchCount - i)); - if (dat->searchCount == 0) { - mir_free(dat->search); - dat->search = nullptr; - UnhookEvent(dat->hResultHook); - dat->hResultHook = nullptr; - SetDlgItemText(hwndDlg, IDOK, TranslateT("&Search")); - StopThrobber(hwndDlg, dat); - } - ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg); - SetStatusBarSearchInfo(GetDlgItem(hwndDlg, IDC_STATUSBAR), dat); - } - else if (ack->result == ACKRESULT_SEARCHRESULT && ack->lParam) { - CUSTOMSEARCHRESULTS *csr = (CUSTOMSEARCHRESULTS*)ack->lParam; - dat->bFlexSearchResult = TRUE; - PROTOSEARCHRESULT *psr = &csr->psr; - // check if this is column names data (psr->cbSize == 0) - if (psr->cbSize == 0) { // blob contain info about columns - // first remove all exist items - FreeSearchResults(hwndList); - ListView_DeleteAllItems(hwndList); //not sure if previous delete list items too - - //second remove all columns - while (ListView_DeleteColumn(hwndList, 1)); //will delete fist column till it possible - - // now will add columns and captions; - LVCOLUMN lvc = { 0 }; - lvc.mask = LVCF_TEXT; - for (int iColumn = 0; iColumn < csr->nFieldCount; iColumn++) { - lvc.pszText = TranslateW(csr->pszFields[iColumn]); - ListView_InsertColumn(hwndList, iColumn + 1, &lvc); - } - } - else { // blob contain info about found contacts - ListSearchResult *lsr = (ListSearchResult*)mir_alloc(offsetof(ListSearchResult, psr) + psr->cbSize); - lsr->szProto = ack->szModule; - memcpy(&lsr->psr, psr, psr->cbSize); - - /* Next block is not needed but behavior will be kept */ - lsr->psr.id.w = sttDecodeString(psr->flags, psr->id); - lsr->psr.nick.w = sttDecodeString(psr->flags, psr->nick); - lsr->psr.firstName.w = sttDecodeString(psr->flags, psr->firstName); - lsr->psr.lastName.w = sttDecodeString(psr->flags, psr->lastName); - lsr->psr.email.w = sttDecodeString(psr->flags, psr->email); - lsr->psr.flags = psr->flags & ~PSR_UNICODE | PSR_UNICODE; - - LVITEM lvi = { 0 }; - lvi.mask = LVIF_PARAM | LVIF_IMAGE; - lvi.lParam = (LPARAM)lsr; - for (i = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCOUNT, 0, 0); i--;) { - char *szComboProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, i, 0); - if (szComboProto == nullptr) continue; - if (!mir_strcmp(szComboProto, ack->szModule)) { - COMBOBOXEXITEM cbei = { 0 }; - cbei.mask = CBEIF_IMAGE; - cbei.iItem = i; - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_GETITEM, 0, (LPARAM)&cbei); - lvi.iImage = cbei.iImage; - } - } - int iItem = ListView_InsertItem(hwndList, &lvi); - for (int col = 0; col < csr->nFieldCount; col++) - SetListItemText(hwndList, iItem, col + 1, csr->pszFields[col]); - - ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg); - iItem = 0; - while (ListView_SetColumnWidth(hwndList, iItem++, LVSCW_AUTOSIZE_USEHEADER)); - SetStatusBarResultInfo(hwndDlg); - } - break; - } - else if (ack->result == ACKRESULT_DATA) { - PROTOSEARCHRESULT *psr = (PROTOSEARCHRESULT*)ack->lParam; - ListSearchResult *lsr = (ListSearchResult*)mir_alloc(offsetof(ListSearchResult, psr) + psr->cbSize); - lsr->szProto = ack->szModule; - - dat->bFlexSearchResult = FALSE; - - memcpy(&lsr->psr, psr, psr->cbSize); - lsr->psr.nick.w = sttDecodeString(psr->flags, psr->nick); - lsr->psr.firstName.w = sttDecodeString(psr->flags, psr->firstName); - lsr->psr.lastName.w = sttDecodeString(psr->flags, psr->lastName); - lsr->psr.email.w = sttDecodeString(psr->flags, psr->email); - lsr->psr.id.w = sttDecodeString(psr->flags, psr->id); - lsr->psr.flags = psr->flags & ~PSR_UNICODE | PSR_UNICODE; - - LVITEM lvi = { 0 }; - lvi.mask = LVIF_PARAM | LVIF_IMAGE; - lvi.lParam = (LPARAM)lsr; - for (i = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCOUNT, 0, 0); i--;) { - char *szComboProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, i, 0); - if (szComboProto == nullptr) continue; - if (!mir_strcmp(szComboProto, ack->szModule)) { - COMBOBOXEXITEM cbei = { 0 }; - cbei.mask = CBEIF_IMAGE; - cbei.iItem = i; - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_GETITEM, 0, (LPARAM)&cbei); - lvi.iImage = cbei.iImage; - break; - } - } - - int iItem = ListView_InsertItem(hwndList, &lvi); - SetListItemText(hwndList, iItem, 1, lsr->psr.id.w); - SetListItemText(hwndList, iItem, 2, lsr->psr.nick.w); - SetListItemText(hwndList, iItem, 3, lsr->psr.firstName.w); - SetListItemText(hwndList, iItem, 4, lsr->psr.lastName.w); - SetListItemText(hwndList, iItem, 5, lsr->psr.email.w); - SetStatusBarResultInfo(hwndDlg); - } - } - break; - - case WM_CLOSE: - DestroyWindow(hwndDlg); - break; - - case WM_DESTROY: - int len = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETLBTEXTLEN, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0); - wchar_t *szProto = (wchar_t*)alloca(sizeof(wchar_t)*(len + 1)); - if (szProto != nullptr) { - *szProto = '\0'; - SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETLBTEXT, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), (LPARAM)szProto); - db_set_ws(0, "FindAdd", "LastSearched", szProto); - } - - SaveColumnSizes(hwndList); - if (dat->hResultHook != nullptr) - UnhookEvent(dat->hResultHook); - FreeSearchResults(hwndList); - ImageList_Destroy(dat->himlComboIcons); - mir_free(dat->search); - if (dat->hwndAdvSearch) { - DestroyWindow(dat->hwndAdvSearch); - dat->hwndAdvSearch = nullptr; - } - if (dat->hwndTinySearch) { - DestroyWindow(dat->hwndTinySearch); - dat->hwndTinySearch = nullptr; - } - mir_free(dat); - Window_FreeIcon_IcoLib(hwndDlg); - Utils_SaveWindowPosition(hwndDlg, 0, "FindAdd", ""); - break; - } - return FALSE; -} - -static INT_PTR FindAddCommand(WPARAM, LPARAM) -{ - if (IsWindow(hwndFindAdd)) { - ShowWindow(hwndFindAdd, SW_SHOWNORMAL); - SetForegroundWindow(hwndFindAdd); - SetFocus(hwndFindAdd); - } - else { - int netProtoCount = 0; - - // Make sure we have some networks to search on. This is not ideal since - // this check will be repeated every time the dialog is requested, but it - // must be done since this service can be called from other places than the menu. - // One alternative would be to only create the service if we have network - // protocols loaded but that would delay the creation until MODULE_LOADED and - // that is not good either... - for (auto &pa : g_arAccounts) { - if (!pa->IsEnabled()) - continue; - - int protoCaps = CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); - if (protoCaps & PF1_ANYSEARCH) - netProtoCount++; - } - if (netProtoCount > 0) - hwndFindAdd = CreateDialog(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FINDADD), nullptr, DlgProcFindAdd); - } - return 0; -} - -int FindAddPreShutdown(WPARAM, LPARAM) -{ - if (IsWindow(hwndFindAdd)) - DestroyWindow(hwndFindAdd); - hwndFindAdd = nullptr; - return 0; -} - -int LoadFindAddModule(void) -{ - CreateServiceFunction(MS_FINDADD_FINDADD, FindAddCommand); - - HookEvent(ME_SYSTEM_MODULESLOADED, OnSystemModulesLoaded); - HookEvent(ME_PROTO_ACCLISTCHANGED, OnSystemModulesLoaded); - HookEvent(ME_SYSTEM_PRESHUTDOWN, FindAddPreShutdown); - - CMenuItem mi(&g_plugin); - SET_UID(mi, 0x860556b9, 0x1577, 0x4f6f, 0x8c, 0xb0, 0x93, 0x24, 0xa8, 0x2e, 0x20, 0x92); - mi.position = 500020000; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_FINDUSER); - mi.name.a = LPGEN("&Find/add contacts..."); - mi.pszService = MS_FINDADD_FINDADD; - hMainMenuItem = Menu_AddMainMenuItem(&mi); - return 0; -} - -static int OnSystemModulesLoaded(WPARAM, LPARAM) -{ - int netProtoCount = 0; - - // Make sure we have some networks to search on. - for (auto &pa : g_arAccounts) { - int protoCaps = CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0); - if (protoCaps & PF1_ANYSEARCH) - netProtoCount++; - } - - Menu_ShowItem(hMainMenuItem, netProtoCount != 0); - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "findadd.h"
+
+#define TIMERID_THROBBER 111
+
+#define HM_SEARCHACK (WM_USER+10)
+#define M_SETGROUPVISIBILITIES (WM_USER+11)
+
+static HWND hwndFindAdd = nullptr;
+static HANDLE hHookModulesLoaded = nullptr;
+static HGENMENU hMainMenuItem = nullptr;
+static int OnSystemModulesLoaded(WPARAM wParam, LPARAM lParam);
+
+static int FindAddDlgResizer(HWND, LPARAM lParam, UTILRESIZECONTROL *urc)
+{
+ static int y, nextY, oldTop;
+ FindAddDlgData *dat = (FindAddDlgData*)lParam;
+
+ switch (urc->wId) {
+ case IDC_RESULTS:
+ return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT;
+
+ case IDOK:
+ dat->minDlgHeight = nextY + urc->rcItem.bottom - urc->rcItem.top;
+ return RD_ANCHORX_LEFT | RD_ANCHORY_BOTTOM;
+
+ case IDC_ADD:
+ case IDC_MOREOPTIONS:
+ return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM;
+
+ case IDC_STATUSBAR:
+ return RD_ANCHORX_WIDTH | RD_ANCHORY_BOTTOM;
+
+ case IDC_PROTOIDGROUP: //the resize is always processed in template order
+ nextY = y = urc->rcItem.top;
+ if (dat->showProtoId) nextY = y + urc->rcItem.bottom - urc->rcItem.top + 7;
+ break;
+
+ case IDC_EMAILGROUP:
+ oldTop = urc->rcItem.top;
+ y = nextY;
+ if (dat->showEmail) nextY = y + urc->rcItem.bottom - urc->rcItem.top + 7;
+ OffsetRect(&urc->rcItem, 0, y - oldTop);
+ return RD_ANCHORX_LEFT | RD_ANCHORY_CUSTOM;
+
+ case IDC_NAMEGROUP:
+ oldTop = urc->rcItem.top;
+ y = nextY;
+ if (dat->showName) nextY = y + urc->rcItem.bottom - urc->rcItem.top + 7;
+ OffsetRect(&urc->rcItem, 0, y - oldTop);
+ return RD_ANCHORX_LEFT | RD_ANCHORY_CUSTOM;
+
+ case IDC_ADVANCEDGROUP:
+ oldTop = urc->rcItem.top;
+ y = nextY;
+ if (dat->showAdvanced) nextY = y + urc->rcItem.bottom - urc->rcItem.top + 7;
+ OffsetRect(&urc->rcItem, 0, y - oldTop);
+ return RD_ANCHORX_LEFT | RD_ANCHORY_CUSTOM;
+
+ case IDC_TINYEXTENDEDGROUP:
+ oldTop = urc->rcItem.top;
+ y = nextY;
+ if (dat->showTiny) {
+ int height = urc->dlgNewSize.cy - y - (urc->dlgOriginalSize.cy - urc->rcItem.bottom);
+ nextY = y + 200; //min height for custom dialog
+ urc->rcItem.top = urc->rcItem.bottom - height;
+ }
+ return RD_ANCHORX_LEFT | RD_ANCHORY_BOTTOM;
+
+ case IDC_BYEMAIL:
+ case IDC_EMAIL:
+ case IDC_BYNAME:
+ case IDC_STNAMENICK:
+ case IDC_STNAMEFIRST:
+ case IDC_STNAMELAST:
+ case IDC_NAMENICK:
+ case IDC_NAMEFIRST:
+ case IDC_NAMELAST:
+ case IDC_BYADVANCED:
+ case IDC_BYCUSTOM:
+ case IDC_ADVANCED:
+ OffsetRect(&urc->rcItem, 0, y - oldTop);
+ return RD_ANCHORX_LEFT | RD_ANCHORY_CUSTOM;
+
+ case IDC_HEADERBAR:
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORX_WIDTH;
+ }
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP;
+}
+
+static void RenderThrobber(HDC hdc, RECT *rcItem, int *throbbing, int *pivot)
+{
+ InflateRect(rcItem, -1, 0);
+ int width = rcItem->right - rcItem->left;
+ int height = rcItem->bottom - rcItem->top;
+ int height2 = height / 2;
+
+ if (*throbbing) {
+ /* create memdc */
+ HDC hMemDC = CreateCompatibleDC(nullptr);
+ HBITMAP hBitmap = (HBITMAP)SelectObject(hMemDC, CreateCompatibleBitmap(hdc, width, height));
+ /* flush it */
+ RECT rc;
+ rc.left = rc.top = 0;
+ rc.right = width;
+ rc.bottom = height;
+ HBRUSH hBr = GetSysColorBrush(COLOR_BTNFACE);
+ FillRect(hMemDC, &rc, hBr);
+ DeleteObject(hBr);
+ /* set up the pen */
+ HPEN hPen = (HPEN)SelectObject(hMemDC, CreatePen(PS_SOLID, 4, GetSysColor(COLOR_BTNSHADOW)));
+ /* draw everything before the pivot */
+ int x = *pivot;
+ while (x > (-height)) {
+ MoveToEx(hMemDC, x + height2, 0, nullptr);
+ LineTo(hMemDC, x - height2, height);
+ x -= 12;
+ }
+
+ /* draw everything after the pivot */
+ x = *pivot;
+ while (x < width + height) {
+ MoveToEx(hMemDC, x + height2, 0, nullptr);
+ LineTo(hMemDC, x - height2, height);
+ x += 12;
+ }
+
+ /* move the pivot */
+ *pivot += 2;
+ /* reset the pivot point if it gets past the rect */
+ if (*pivot > width) *pivot = 0;
+ /* put back the old pen and delete the new one */
+ DeleteObject(SelectObject(hMemDC, hPen));
+ /* cap the top and bottom */
+ hPen = (HPEN)SelectObject(hMemDC, CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNFACE)));
+ MoveToEx(hMemDC, 0, 0, nullptr);
+ LineTo(hMemDC, width, 0);
+ MoveToEx(hMemDC, 0, height - 1, nullptr);
+ LineTo(hMemDC, width, height - 1);
+ /* select in the old pen and delete the new pen */
+ DeleteObject(SelectObject(hMemDC, hPen));
+ /* paint to screen */
+ BitBlt(hdc, rcItem->left, rcItem->top, width, height, hMemDC, 0, 0, SRCCOPY);
+ /* select back in the old bitmap and delete the created one, as well as freeing the mem dc. */
+ hBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap);
+ DeleteObject(hBitmap);
+ DeleteDC(hMemDC);
+ }
+ else {
+ /* just flush the DC */
+ HBRUSH hBr = GetSysColorBrush(COLOR_BTNFACE);
+ FillRect(hdc, rcItem, hBr);
+ DeleteObject(hBr);
+ }
+}
+
+static void StartThrobber(HWND hwndDlg, FindAddDlgData *dat)
+{
+ dat->throbbing = 1;
+ SetTimer(hwndDlg, TIMERID_THROBBER, 25, nullptr);
+}
+
+static void StopThrobber(HWND hwndDlg, FindAddDlgData *dat)
+{
+ KillTimer(hwndDlg, TIMERID_THROBBER);
+ dat->throbbing = 0;
+ dat->pivot = 0;
+ InvalidateRect(GetDlgItem(hwndDlg, IDC_STATUSBAR), nullptr, FALSE);
+}
+
+static LRESULT CALLBACK AdvancedSearchDlgSubclassProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (msg == WM_COMMAND) {
+ HWND parentHwnd = GetParent(hwndDlg);
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ SendMessage(parentHwnd, WM_COMMAND, MAKEWPARAM(IDOK, BN_CLICKED), (LPARAM)GetDlgItem(parentHwnd, IDOK));
+ SetFocus(GetDlgItem(parentHwnd, IDC_ADVANCED));
+ break;
+
+ case IDCANCEL:
+ CheckDlgButton(parentHwnd, IDC_ADVANCED, BST_UNCHECKED);
+ SendMessage(parentHwnd, WM_COMMAND, MAKEWPARAM(IDC_ADVANCED, BN_CLICKED), (LPARAM)GetDlgItem(parentHwnd, IDC_ADVANCED));
+ SetFocus(GetDlgItem(parentHwnd, IDC_ADVANCED));
+ break;
+ }
+ }
+ return mir_callNextSubclass(hwndDlg, AdvancedSearchDlgSubclassProc, msg, wParam, lParam);
+}
+
+static void ShowAdvancedSearchDlg(HWND hwndDlg, FindAddDlgData *dat)
+{
+ char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0);
+ if (szProto == nullptr)
+ return;
+
+ if (dat->hwndAdvSearch == nullptr) {
+ RECT rc;
+ dat->hwndAdvSearch = (HWND)CallProtoServiceInt(0, szProto, PS_CREATEADVSEARCHUI, 0, (LPARAM)hwndDlg);
+ if (dat->hwndAdvSearch != nullptr)
+ mir_subclassWindow(dat->hwndAdvSearch, AdvancedSearchDlgSubclassProc);
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_RESULTS), &rc);
+ SetWindowPos(dat->hwndAdvSearch, nullptr, rc.left, rc.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
+ }
+
+ AnimateWindow(dat->hwndAdvSearch, 150, AW_ACTIVATE | AW_SLIDE | AW_HOR_POSITIVE);
+ RedrawWindow(dat->hwndAdvSearch, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
+
+ CheckDlgButton(hwndDlg, IDC_ADVANCED, BST_CHECKED);
+}
+
+static void ReposTinySearchDlg(HWND hwndDlg, FindAddDlgData *dat)
+{
+ if (dat->hwndTinySearch == nullptr)
+ return;
+
+ RECT rc;
+ RECT clientRect;
+ POINT pt = { 0, 0 };
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_TINYEXTENDEDGROUP), &rc);
+ GetWindowRect(dat->hwndTinySearch, &clientRect);
+ pt.x = rc.left;
+ pt.y = rc.top;
+ ScreenToClient(hwndDlg, &pt);
+ SetWindowPos(dat->hwndTinySearch, nullptr, pt.x + 5, pt.y + 15, rc.right - rc.left - 10, rc.bottom - rc.top - 30, SWP_NOZORDER);
+}
+
+static void ShowTinySearchDlg(HWND hwndDlg, FindAddDlgData *dat)
+{
+ char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0);
+ if (szProto == nullptr)
+ return;
+
+ if (dat->hwndTinySearch == nullptr) {
+ dat->hwndTinySearch = (HWND)CallProtoServiceInt(0, szProto, PS_CREATEADVSEARCHUI, 0, (LPARAM)/*GetDlgItem(*/hwndDlg/*, IDC_TINYEXTENDEDGROUP)*/);
+ if (dat->hwndTinySearch)
+ ReposTinySearchDlg(hwndDlg, dat);
+ else
+ dat->showTiny = false;
+ }
+ ShowWindow(dat->hwndTinySearch, SW_SHOW);
+}
+
+static void HideAdvancedSearchDlg(HWND hwndDlg, FindAddDlgData *dat)
+{
+ if (dat->hwndAdvSearch == nullptr)
+ return;
+
+ AnimateWindow(dat->hwndAdvSearch, 150, AW_HIDE | AW_BLEND);
+ CheckDlgButton(hwndDlg, IDC_ADVANCED, BST_UNCHECKED);
+}
+
+void EnableResultButtons(HWND hwndDlg, int enable)
+{
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), enable);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MOREOPTIONS), enable);
+}
+
+static const int controls[] = { IDC_BYPROTOID, IDC_BYEMAIL, IDC_BYNAME, IDC_BYADVANCED, IDC_BYCUSTOM };
+
+static void CheckSearchTypeRadioButton(HWND hwndDlg, int idControl)
+{
+ for (auto &it : controls)
+ CheckDlgButton(hwndDlg, it, idControl == it ? BST_CHECKED : BST_UNCHECKED);
+}
+
+#define sttErrMsg TranslateT("You haven't filled in the search field. Please enter a search term and try again.")
+#define sttErrTitle TranslateT("Search")
+
+static void SetListItemText(HWND hwndList, int idx, int col, wchar_t *szText)
+{
+ if (szText == nullptr || *szText == 0)
+ szText = TranslateT("<not specified>");
+
+ ListView_SetItemText(hwndList, idx, col, szText);
+}
+
+static wchar_t* sttDecodeString(uint32_t dwFlags, MAllStrings &src)
+{
+ if (dwFlags & PSR_UNICODE)
+ return mir_wstrdup(src.w);
+
+ if (dwFlags & PSR_UTF8)
+ return mir_utf8decodeW(src.a);
+
+ return mir_a2u(src.a);
+}
+
+static INT_PTR CALLBACK DlgProcFindAdd(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ FindAddDlgData *dat = (FindAddDlgData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_RESULTS);
+ RECT rc, rc2;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ Window_SetSkinIcon_IcoLib(hwndDlg, SKINICON_OTHER_FINDUSER);
+ dat = (FindAddDlgData*)mir_calloc(sizeof(FindAddDlgData));
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat);
+ dat->notSearchedYet = 1;
+ dat->iLastColumnSortIndex = 1;
+ dat->bSortAscending = 1;
+ SendDlgItemMessage(hwndDlg, IDC_MOREOPTIONS, BUTTONSETARROW, 1, 0);
+ SendDlgItemMessage(hwndDlg, IDOK, BUTTONADDTOOLTIP, (WPARAM)LPGENW("Ctrl+Search add contact"), BATF_UNICODE);
+
+ ListView_SetExtendedListViewStyle(hwndList, LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
+
+ GetClientRect(hwndList, &rc);
+ {
+ LVCOLUMN lvc;
+ lvc.mask = LVCF_TEXT | LVCF_WIDTH;
+ lvc.pszText = TranslateT("Results");
+ lvc.cx = rc.right - 1;
+ ListView_InsertColumn(hwndList, 0, &lvc);
+
+ LVITEM lvi;
+ lvi.mask = LVIF_TEXT;
+ lvi.iItem = 0;
+ lvi.iSubItem = 0;
+ lvi.pszText = TranslateT("There are no results to display.");
+ ListView_InsertItem(hwndList, &lvi);
+
+ // Allocate a reasonable amount of space in the status bar
+ HDC hdc = GetDC(GetDlgItem(hwndDlg, IDC_STATUSBAR));
+ SelectObject(hdc, (HFONT)SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, WM_GETFONT, 0, 0));
+
+ SIZE textSize;
+ GetTextExtentPoint32(hdc, TranslateT("Searching"), (int)mir_wstrlen(TranslateT("Searching")), &textSize);
+
+ int partWidth[3];
+ partWidth[0] = textSize.cx;
+ GetTextExtentPoint32(hdc, L"01234567890123456789", 20, &textSize);
+ partWidth[0] += textSize.cx;
+ ReleaseDC(GetDlgItem(hwndDlg, IDC_STATUSBAR), hdc);
+ partWidth[1] = partWidth[0] + 150;
+ partWidth[2] = -1;
+ SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_SETPARTS, _countof(partWidth), (LPARAM)partWidth);
+ SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_SETTEXT, 1 | SBT_OWNERDRAW, 0);
+ SetStatusBarSearchInfo(GetDlgItem(hwndDlg, IDC_STATUSBAR), dat);
+
+ wchar_t *szProto = nullptr;
+ ptrW tszLast(db_get_wsa(0, "FindAdd", "LastSearched"));
+ if (tszLast)
+ szProto = NEWWSTR_ALLOCA(tszLast);
+
+ int index = 0, cbwidth = 0, netProtoCount = 0;
+ for (auto &pa : g_arAccounts) {
+ if (!pa->IsEnabled())
+ continue;
+
+ uint32_t caps = (uint32_t)CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (caps & PF1_ANYSEARCH)
+ netProtoCount++;
+ }
+ dat->himlComboIcons = ImageList_Create(g_iIconSX, g_iIconSY, ILC_COLOR32 | ILC_MASK, netProtoCount + 1, netProtoCount + 1);
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_SETIMAGELIST, 0, (LPARAM)dat->himlComboIcons);
+
+ COMBOBOXEXITEM cbei;
+ cbei.mask = CBEIF_IMAGE | CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_LPARAM;
+ cbei.iItem = 0;
+ hdc = GetDC(hwndDlg);
+ SelectObject(hdc, (HFONT)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, WM_GETFONT, 0, 0));
+ if (netProtoCount > 1) {
+ cbei.pszText = TranslateT("All networks");
+ GetTextExtentPoint32(hdc, cbei.pszText, (int)mir_wstrlen(cbei.pszText), &textSize);
+ if (textSize.cx > cbwidth)
+ cbwidth = textSize.cx;
+ cbei.iImage = cbei.iSelectedImage = ImageList_AddSkinIcon(dat->himlComboIcons, SKINICON_OTHER_SEARCHALL);
+ cbei.lParam = 0;
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_INSERTITEM, 0, (LPARAM)&cbei);
+ cbei.iItem++;
+ }
+
+ for (auto &pa : g_arAccounts) {
+ if (!pa->IsEnabled())
+ continue;
+
+ uint32_t caps = (uint32_t)CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (!(caps & PF1_ANYSEARCH))
+ continue;
+
+ cbei.pszText = pa->tszAccountName;
+ GetTextExtentPoint32(hdc, cbei.pszText, (int)mir_wstrlen(cbei.pszText), &textSize);
+ if (textSize.cx > cbwidth)
+ cbwidth = textSize.cx;
+
+ HICON hIcon = (HICON)CallProtoServiceInt(0, pa->szModuleName, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0);
+ cbei.iImage = cbei.iSelectedImage = ImageList_AddIcon(dat->himlComboIcons, hIcon);
+ DestroyIcon(hIcon);
+ cbei.lParam = (LPARAM)pa->szModuleName;
+ SendDlgItemMessageA(hwndDlg, IDC_PROTOLIST, CBEM_INSERTITEM, 0, (LPARAM)&cbei);
+ if (szProto && cbei.pszText && !mir_wstrcmp(szProto, pa->tszAccountName))
+ index = cbei.iItem;
+ cbei.iItem++;
+ }
+ cbwidth += 32;
+
+ RECT rect;
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&rect);
+ if ((rect.right - rect.left) < cbwidth)
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_SETDROPPEDWIDTH, cbwidth, 0);
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_SETCURSEL, index, 0);
+
+ SendMessage(hwndDlg, M_SETGROUPVISIBILITIES, 0, 0);
+ Utils_RestoreWindowPosition(hwndDlg, 0, "FindAdd", "");
+ }
+ return TRUE;
+
+ case WM_SIZE:
+ Utils_ResizeDialog(hwndDlg, g_plugin.getInst(), MAKEINTRESOURCEA(IDD_FINDADD), FindAddDlgResizer, (LPARAM)dat);
+ ReposTinySearchDlg(hwndDlg, dat);
+ SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, WM_SIZE, 0, 0);
+ if (dat->notSearchedYet) {
+ GetClientRect(hwndList, &rc);
+ ListView_SetColumnWidth(hwndList, 0, rc.right);
+ }
+ __fallthrough;
+
+ case WM_MOVE:
+ if (dat && dat->hwndAdvSearch) {
+ GetWindowRect(hwndList, &rc);
+ SetWindowPos(dat->hwndAdvSearch, nullptr, rc.left, rc.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
+ }
+ break;
+
+ case WM_GETMINMAXINFO:
+ GetWindowRect(hwndList, &rc);
+ GetWindowRect(hwndDlg, &rc2);
+ {
+ MINMAXINFO *mmi = (MINMAXINFO*)lParam;
+ mmi->ptMinTrackSize.x = rc.left - rc2.left + 10 + GetSystemMetrics(SM_CXFRAME);
+ GetClientRect(GetDlgItem(hwndDlg, IDC_MOREOPTIONS), &rc);
+ mmi->ptMinTrackSize.x += rc.right + 5;
+ GetClientRect(GetDlgItem(hwndDlg, IDC_ADD), &rc);
+ mmi->ptMinTrackSize.x += rc.right + 5;
+ GetClientRect(GetDlgItem(hwndDlg, IDC_STATUSBAR), &rc);
+ mmi->ptMinTrackSize.y = dat->minDlgHeight + 20 + GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFRAME);
+ GetClientRect(GetDlgItem(hwndDlg, IDC_STATUSBAR), &rc);
+ mmi->ptMinTrackSize.y += rc.bottom;
+ }
+ return 0;
+
+ case M_SETGROUPVISIBILITIES:
+ dat->showAdvanced = dat->showEmail = dat->showName = dat->showProtoId = dat->showTiny = 0;
+ {
+ char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0);
+ if (szProto == (char *)CB_ERR)
+ break;
+ if (szProto == nullptr) {
+ for (auto &pa : g_arAccounts) {
+ if (pa->IsEnabled()) {
+ uint32_t protoCaps = (uint32_t)CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (protoCaps & PF1_SEARCHBYEMAIL) dat->showEmail = 1;
+ if (protoCaps & PF1_SEARCHBYNAME) dat->showName = 1;
+ }
+ }
+ }
+ else {
+ uint32_t protoCaps = (uint32_t)CallProtoServiceInt(0, szProto, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (protoCaps & PF1_BASICSEARCH) dat->showProtoId = 1;
+ if (protoCaps & PF1_SEARCHBYEMAIL) dat->showEmail = 1;
+ if (protoCaps & PF1_SEARCHBYNAME) dat->showName = 1;
+
+ if (protoCaps & PF1_EXTSEARCHUI) dat->showAdvanced = 1;
+ else if (protoCaps & PF1_EXTSEARCH) dat->showTiny = 1;
+
+ if (protoCaps & PF1_USERIDISEMAIL && dat->showProtoId) { dat->showProtoId = 0; dat->showEmail = 1; }
+ if (dat->showProtoId) {
+ wchar_t *wszUniqueId = (wchar_t *)CallProtoServiceInt(0, szProto, PS_GETCAPS, PFLAG_UNIQUEIDTEXT, 0);
+ if (wszUniqueId)
+ SetDlgItemTextW(hwndDlg, IDC_BYPROTOID, wszUniqueId);
+ else
+ SetDlgItemTextW(hwndDlg, IDC_BYPROTOID, TranslateT("Handle"));
+
+ if (protoCaps & PF1_NUMERICUSERID)
+ SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PROTOID), GWL_STYLE, GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PROTOID), GWL_STYLE) | ES_NUMBER);
+ else
+ SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PROTOID), GWL_STYLE, GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PROTOID), GWL_STYLE)&~ES_NUMBER);
+ }
+ }
+
+ if (dat->showTiny)
+ ShowTinySearchDlg(hwndDlg, dat);
+ else if (dat->hwndTinySearch) {
+ DestroyWindow(dat->hwndTinySearch);
+ dat->hwndTinySearch = nullptr;
+ }
+
+#define en(id, t) ShowWindow( GetDlgItem(hwndDlg, IDC_##id), dat->show##t?SW_SHOW:SW_HIDE)
+ en(PROTOIDGROUP, ProtoId); en(BYPROTOID, ProtoId); en(PROTOID, ProtoId);
+ en(EMAILGROUP, Email); en(BYEMAIL, Email); en(EMAIL, Email);
+ en(NAMEGROUP, Name); en(BYNAME, Name);
+ en(STNAMENICK, Name); en(NAMENICK, Name);
+ en(STNAMEFIRST, Name); en(NAMEFIRST, Name);
+ en(STNAMELAST, Name); en(NAMELAST, Name);
+ en(ADVANCEDGROUP, Advanced); en(BYADVANCED, Advanced); en(ADVANCED, Advanced);
+ en(BYCUSTOM, Tiny); en(TINYEXTENDEDGROUP, Tiny);
+#undef en
+ int checkmarkVisible = (dat->showAdvanced && IsDlgButtonChecked(hwndDlg, IDC_BYADVANCED)) ||
+ (dat->showEmail && IsDlgButtonChecked(hwndDlg, IDC_BYEMAIL)) ||
+ (dat->showTiny && IsDlgButtonChecked(hwndDlg, IDC_BYCUSTOM)) ||
+ (dat->showName && IsDlgButtonChecked(hwndDlg, IDC_BYNAME)) ||
+ (dat->showProtoId && IsDlgButtonChecked(hwndDlg, IDC_BYPROTOID));
+ if (!checkmarkVisible) {
+ if (dat->showProtoId) CheckSearchTypeRadioButton(hwndDlg, IDC_BYPROTOID);
+ else if (dat->showEmail) CheckSearchTypeRadioButton(hwndDlg, IDC_BYEMAIL);
+ else if (dat->showName) CheckSearchTypeRadioButton(hwndDlg, IDC_BYNAME);
+ else if (dat->showAdvanced) CheckSearchTypeRadioButton(hwndDlg, IDC_BYADVANCED);
+ else if (dat->showTiny) CheckSearchTypeRadioButton(hwndDlg, IDC_BYCUSTOM);
+ }
+
+ SendMessage(hwndDlg, WM_SIZE, 0, 0);
+
+ MINMAXINFO mmi;
+ SendMessage(hwndDlg, WM_GETMINMAXINFO, 0, (LPARAM)&mmi);
+
+ GetWindowRect(hwndDlg, &rc);
+ if (rc.bottom - rc.top < mmi.ptMinTrackSize.y)
+ SetWindowPos(hwndDlg, nullptr, 0, 0, rc.right - rc.left, mmi.ptMinTrackSize.y, SWP_NOZORDER | SWP_NOMOVE);
+ }
+ break;
+
+ case WM_TIMER:
+ if (wParam == TIMERID_THROBBER) {
+ int borders[3];
+ SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_GETBORDERS, 0, (LPARAM)borders);
+
+ SendDlgItemMessage(hwndDlg, IDC_STATUSBAR, SB_GETRECT, 1, (LPARAM)&rc);
+ InflateRect(&rc, -borders[2] / 2, -borders[1] / 2);
+ HDC hdc = GetDC(GetDlgItem(hwndDlg, IDC_STATUSBAR));
+ RenderThrobber(hdc, &rc, &dat->throbbing, &dat->pivot);
+ ReleaseDC(GetDlgItem(hwndDlg, IDC_STATUSBAR), hdc);
+ }
+ break;
+
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT*)lParam;
+ if (dis->CtlID == IDC_STATUSBAR && dis->itemID == 1) {
+ RenderThrobber(dis->hDC, &dis->rcItem, &dat->throbbing, &dat->pivot);
+ return TRUE;
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (wParam == IDC_RESULTS) {
+ switch (((LPNMHDR)lParam)->code) {
+ case LVN_ITEMCHANGED:
+ {
+ int count = ListView_GetSelectedCount(hwndList);
+ if (dat->notSearchedYet)
+ count = 0;
+ EnableResultButtons(hwndDlg, count);
+ }
+ break;
+
+ case LVN_COLUMNCLICK:
+ HDITEM hdi;
+ hdi.mask = HDI_FORMAT;
+ hdi.fmt = HDF_LEFT | HDF_STRING;
+ Header_SetItem(ListView_GetHeader(hwndList), dat->iLastColumnSortIndex, &hdi);
+
+ LPNMLISTVIEW nmlv = (LPNMLISTVIEW)lParam;
+ if (nmlv->iSubItem != dat->iLastColumnSortIndex) {
+ dat->bSortAscending = TRUE;
+ dat->iLastColumnSortIndex = nmlv->iSubItem;
+ }
+ else dat->bSortAscending = !dat->bSortAscending;
+
+ hdi.fmt = HDF_LEFT | HDF_STRING | (dat->bSortAscending ? HDF_SORTDOWN : HDF_SORTUP);
+ Header_SetItem(ListView_GetHeader(hwndList), dat->iLastColumnSortIndex, &hdi);
+
+ ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg);
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_PROTOLIST:
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ HideAdvancedSearchDlg(hwndDlg, dat);
+ if (dat->hwndAdvSearch) {
+ DestroyWindow(dat->hwndAdvSearch);
+ dat->hwndAdvSearch = nullptr;
+ }
+ if (dat->hwndTinySearch) {
+ DestroyWindow(dat->hwndTinySearch);
+ dat->hwndTinySearch = nullptr;
+ }
+ SendMessage(hwndDlg, M_SETGROUPVISIBILITIES, 0, 0);
+ }
+ break;
+
+ case IDC_BYPROTOID:
+ case IDC_BYEMAIL:
+ case IDC_BYNAME:
+ {
+ int count = ListView_GetSelectedCount(hwndList);
+ if (dat->notSearchedYet)
+ count = 0;
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), count);
+ HideAdvancedSearchDlg(hwndDlg, dat);
+ }
+ break;
+
+ case IDC_PROTOID:
+ if (HIWORD(wParam) == EN_CHANGE) {
+ HideAdvancedSearchDlg(hwndDlg, dat);
+ CheckSearchTypeRadioButton(hwndDlg, IDC_BYPROTOID);
+ }
+ break;
+
+ case IDC_EMAIL:
+ if (HIWORD(wParam) == EN_CHANGE) {
+ HideAdvancedSearchDlg(hwndDlg, dat);
+ CheckSearchTypeRadioButton(hwndDlg, IDC_BYEMAIL);
+ }
+ break;
+
+ case IDC_NAMENICK:
+ case IDC_NAMEFIRST:
+ case IDC_NAMELAST:
+ if (HIWORD(wParam) == EN_CHANGE) {
+ HideAdvancedSearchDlg(hwndDlg, dat);
+ CheckSearchTypeRadioButton(hwndDlg, IDC_BYNAME);
+ }
+ break;
+
+ case IDC_ADVANCED:
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), ListView_GetSelectedCount(hwndList) > 0);
+ if (IsDlgButtonChecked(hwndDlg, IDC_ADVANCED))
+ ShowAdvancedSearchDlg(hwndDlg, dat);
+ else
+ HideAdvancedSearchDlg(hwndDlg, dat);
+ CheckSearchTypeRadioButton(hwndDlg, IDC_BYADVANCED);
+ break;
+
+ case IDCANCEL:
+ DestroyWindow(hwndDlg);
+ break;
+
+ case IDOK:
+ HideAdvancedSearchDlg(hwndDlg, dat);
+ if (dat->searchCount) { //cancel search
+ SetDlgItemText(hwndDlg, IDOK, TranslateT("&Search"));
+ if (dat->hResultHook) { UnhookEvent(dat->hResultHook); dat->hResultHook = nullptr; }
+ if (dat->search) { mir_free(dat->search); dat->search = nullptr; }
+ dat->searchCount = 0;
+ StopThrobber(hwndDlg, dat);
+ SetStatusBarSearchInfo(GetDlgItem(hwndDlg, IDC_STATUSBAR), dat);
+ }
+ else {
+ char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0);
+ if (dat->search)
+ mir_free(dat->search), dat->search = nullptr;
+ dat->searchCount = 0;
+ dat->hResultHook = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_SEARCHACK);
+ if (IsDlgButtonChecked(hwndDlg, IDC_BYCUSTOM))
+ BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYADVANCED, PF1_EXTSEARCHUI, dat->hwndTinySearch);
+ else if (IsDlgButtonChecked(hwndDlg, IDC_BYPROTOID)) {
+ wchar_t str[256];
+ GetDlgItemText(hwndDlg, IDC_PROTOID, str, _countof(str));
+ rtrimw(str);
+ if (str[0] == 0)
+ MessageBoxW(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK);
+ else
+ BeginSearch(hwndDlg, dat, szProto, PS_BASICSEARCH, PF1_BASICSEARCH, str);
+ }
+ else if (IsDlgButtonChecked(hwndDlg, IDC_BYEMAIL)) {
+ wchar_t str[256];
+ GetDlgItemText(hwndDlg, IDC_EMAIL, str, _countof(str));
+ rtrimw(str);
+ if (str[0] == 0)
+ MessageBoxW(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK);
+ else
+ BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYEMAIL, PF1_SEARCHBYEMAIL, str);
+ }
+ else if (IsDlgButtonChecked(hwndDlg, IDC_BYNAME)) {
+ wchar_t nick[256], first[256], last[256];
+ PROTOSEARCHBYNAME psbn;
+ GetDlgItemText(hwndDlg, IDC_NAMENICK, nick, _countof(nick));
+ GetDlgItemText(hwndDlg, IDC_NAMEFIRST, first, _countof(first));
+ GetDlgItemText(hwndDlg, IDC_NAMELAST, last, _countof(last));
+ psbn.pszFirstName = first;
+ psbn.pszLastName = last;
+ psbn.pszNick = nick;
+ if (nick[0] == 0 && first[0] == 0 && last[0] == 0)
+ MessageBoxW(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK);
+ else
+ BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYNAME, PF1_SEARCHBYNAME, &psbn);
+ }
+ else if (IsDlgButtonChecked(hwndDlg, IDC_BYADVANCED)) {
+ if (dat->hwndAdvSearch == nullptr)
+ MessageBoxW(hwndDlg, sttErrMsg, sttErrTitle, MB_ICONERROR | MB_OK);
+ else
+ BeginSearch(hwndDlg, dat, szProto, PS_SEARCHBYADVANCED, PF1_EXTSEARCHUI, dat->hwndAdvSearch);
+ }
+
+ if (dat->searchCount == 0) {
+ if (dat->hResultHook) {
+ UnhookEvent(dat->hResultHook);
+ dat->hResultHook = nullptr;
+ }
+ break;
+ }
+
+ dat->notSearchedYet = 0;
+ FreeSearchResults(hwndList);
+
+ CreateResultsColumns(hwndList, dat, szProto);
+ SetStatusBarSearchInfo(GetDlgItem(hwndDlg, IDC_STATUSBAR), dat);
+ SetStatusBarResultInfo(hwndDlg);
+ StartThrobber(hwndDlg, dat);
+ SetDlgItemText(hwndDlg, IDOK, TranslateT("Cancel"));
+ }
+ break;
+
+ case IDC_ADD:
+ if (ListView_GetSelectedCount(hwndList) == 1) {
+ LVITEM lvi;
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = ListView_GetNextItem(hwndList, -1, LVNI_ALL | LVNI_SELECTED);
+ ListView_GetItem(hwndList, &lvi);
+ ListSearchResult *lsr = (ListSearchResult*)lvi.lParam;
+ Contact::AddBySearch(lsr->szProto, &lsr->psr, hwndDlg);
+ }
+ else {
+ wchar_t str[256];
+ GetDlgItemText(hwndDlg, IDC_PROTOID, str, _countof(str));
+ if (*rtrimw(str) == 0)
+ break;
+
+ char *szProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA,
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0);
+
+ PROTOSEARCHRESULT psr = { 0 };
+ psr.cbSize = sizeof(psr);
+ psr.flags = PSR_UNICODE;
+ psr.id.w = str;
+ Contact::AddBySearch(szProto, &psr, hwndDlg);
+ }
+ break;
+
+ case IDC_MOREOPTIONS:
+ GetWindowRect(GetDlgItem(hwndDlg, IDC_MOREOPTIONS), &rc);
+ ShowMoreOptionsMenu(hwndDlg, rc.left, rc.bottom);
+ break;
+ }
+
+ if (lParam && dat->hwndTinySearch == (HWND)lParam &&
+ HIWORD(wParam) == EN_SETFOCUS && LOWORD(wParam) == 0 &&
+ BST_UNCHECKED == IsDlgButtonChecked(hwndDlg, IDC_BYCUSTOM)) {
+ CheckSearchTypeRadioButton(hwndDlg, IDC_BYCUSTOM);
+ }
+ break;
+
+ case WM_CONTEXTMENU:
+ {
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+
+ LVHITTESTINFO lvhti;
+ lvhti.pt = pt;
+ ScreenToClient(hwndDlg, &pt);
+ switch (GetDlgCtrlID(ChildWindowFromPoint(hwndDlg, pt))) {
+ case IDC_RESULTS:
+ if (dat->notSearchedYet)
+ return TRUE;
+ ScreenToClient(hwndList, &lvhti.pt);
+ if (ListView_HitTest(hwndList, &lvhti) == -1)
+ break;
+ ShowMoreOptionsMenu(hwndDlg, (short)LOWORD(lParam), (short)HIWORD(lParam));
+ return TRUE;
+ }
+ }
+ break;
+
+ case HM_SEARCHACK:
+ {
+ ACKDATA *ack = (ACKDATA*)lParam;
+ if (ack->type != ACKTYPE_SEARCH)
+ break;
+
+ int i;
+ for (i = 0; i < dat->searchCount; i++)
+ if (dat->search[i].hProcess == ack->hProcess && dat->search[i].hProcess != nullptr && !mir_strcmp(dat->search[i].szProto, ack->szModule)) break;
+ if (i == dat->searchCount)
+ break;
+
+ if (ack->result == ACKRESULT_SUCCESS || ack->result == ACKRESULT_FAILED) {
+ dat->searchCount--;
+ memmove(dat->search + i, dat->search + i + 1, sizeof(struct ProtoSearchInfo)*(dat->searchCount - i));
+ if (dat->searchCount == 0) {
+ mir_free(dat->search);
+ dat->search = nullptr;
+ UnhookEvent(dat->hResultHook);
+ dat->hResultHook = nullptr;
+ SetDlgItemText(hwndDlg, IDOK, TranslateT("&Search"));
+ StopThrobber(hwndDlg, dat);
+ }
+ ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg);
+ SetStatusBarSearchInfo(GetDlgItem(hwndDlg, IDC_STATUSBAR), dat);
+ }
+ else if (ack->result == ACKRESULT_SEARCHRESULT && ack->lParam) {
+ CUSTOMSEARCHRESULTS *csr = (CUSTOMSEARCHRESULTS*)ack->lParam;
+ dat->bFlexSearchResult = TRUE;
+ PROTOSEARCHRESULT *psr = &csr->psr;
+ // check if this is column names data (psr->cbSize == 0)
+ if (psr->cbSize == 0) { // blob contain info about columns
+ // first remove all exist items
+ FreeSearchResults(hwndList);
+ ListView_DeleteAllItems(hwndList); //not sure if previous delete list items too
+
+ //second remove all columns
+ while (ListView_DeleteColumn(hwndList, 1)); //will delete fist column till it possible
+
+ // now will add columns and captions;
+ LVCOLUMN lvc = { 0 };
+ lvc.mask = LVCF_TEXT;
+ for (int iColumn = 0; iColumn < csr->nFieldCount; iColumn++) {
+ lvc.pszText = TranslateW(csr->pszFields[iColumn]);
+ ListView_InsertColumn(hwndList, iColumn + 1, &lvc);
+ }
+ }
+ else { // blob contain info about found contacts
+ ListSearchResult *lsr = (ListSearchResult*)mir_alloc(offsetof(ListSearchResult, psr) + psr->cbSize);
+ lsr->szProto = ack->szModule;
+ memcpy(&lsr->psr, psr, psr->cbSize);
+
+ /* Next block is not needed but behavior will be kept */
+ lsr->psr.id.w = sttDecodeString(psr->flags, psr->id);
+ lsr->psr.nick.w = sttDecodeString(psr->flags, psr->nick);
+ lsr->psr.firstName.w = sttDecodeString(psr->flags, psr->firstName);
+ lsr->psr.lastName.w = sttDecodeString(psr->flags, psr->lastName);
+ lsr->psr.email.w = sttDecodeString(psr->flags, psr->email);
+ lsr->psr.flags = psr->flags & ~PSR_UNICODE | PSR_UNICODE;
+
+ LVITEM lvi = { 0 };
+ lvi.mask = LVIF_PARAM | LVIF_IMAGE;
+ lvi.lParam = (LPARAM)lsr;
+ for (i = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCOUNT, 0, 0); i--;) {
+ char *szComboProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, i, 0);
+ if (szComboProto == nullptr) continue;
+ if (!mir_strcmp(szComboProto, ack->szModule)) {
+ COMBOBOXEXITEM cbei = { 0 };
+ cbei.mask = CBEIF_IMAGE;
+ cbei.iItem = i;
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_GETITEM, 0, (LPARAM)&cbei);
+ lvi.iImage = cbei.iImage;
+ }
+ }
+ int iItem = ListView_InsertItem(hwndList, &lvi);
+ for (int col = 0; col < csr->nFieldCount; col++)
+ SetListItemText(hwndList, iItem, col + 1, csr->pszFields[col]);
+
+ ListView_SortItemsEx(hwndList, SearchResultsCompareFunc, (LPARAM)hwndDlg);
+ iItem = 0;
+ while (ListView_SetColumnWidth(hwndList, iItem++, LVSCW_AUTOSIZE_USEHEADER));
+ SetStatusBarResultInfo(hwndDlg);
+ }
+ break;
+ }
+ else if (ack->result == ACKRESULT_DATA) {
+ PROTOSEARCHRESULT *psr = (PROTOSEARCHRESULT*)ack->lParam;
+ ListSearchResult *lsr = (ListSearchResult*)mir_alloc(offsetof(ListSearchResult, psr) + psr->cbSize);
+ lsr->szProto = ack->szModule;
+
+ dat->bFlexSearchResult = FALSE;
+
+ memcpy(&lsr->psr, psr, psr->cbSize);
+ lsr->psr.nick.w = sttDecodeString(psr->flags, psr->nick);
+ lsr->psr.firstName.w = sttDecodeString(psr->flags, psr->firstName);
+ lsr->psr.lastName.w = sttDecodeString(psr->flags, psr->lastName);
+ lsr->psr.email.w = sttDecodeString(psr->flags, psr->email);
+ lsr->psr.id.w = sttDecodeString(psr->flags, psr->id);
+ lsr->psr.flags = psr->flags & ~PSR_UNICODE | PSR_UNICODE;
+
+ LVITEM lvi = { 0 };
+ lvi.mask = LVIF_PARAM | LVIF_IMAGE;
+ lvi.lParam = (LPARAM)lsr;
+ for (i = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCOUNT, 0, 0); i--;) {
+ char *szComboProto = (char*)SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETITEMDATA, i, 0);
+ if (szComboProto == nullptr) continue;
+ if (!mir_strcmp(szComboProto, ack->szModule)) {
+ COMBOBOXEXITEM cbei = { 0 };
+ cbei.mask = CBEIF_IMAGE;
+ cbei.iItem = i;
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CBEM_GETITEM, 0, (LPARAM)&cbei);
+ lvi.iImage = cbei.iImage;
+ break;
+ }
+ }
+
+ int iItem = ListView_InsertItem(hwndList, &lvi);
+ SetListItemText(hwndList, iItem, 1, lsr->psr.id.w);
+ SetListItemText(hwndList, iItem, 2, lsr->psr.nick.w);
+ SetListItemText(hwndList, iItem, 3, lsr->psr.firstName.w);
+ SetListItemText(hwndList, iItem, 4, lsr->psr.lastName.w);
+ SetListItemText(hwndList, iItem, 5, lsr->psr.email.w);
+ SetStatusBarResultInfo(hwndDlg);
+ }
+ }
+ break;
+
+ case WM_CLOSE:
+ DestroyWindow(hwndDlg);
+ break;
+
+ case WM_DESTROY:
+ int len = SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETLBTEXTLEN, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), 0);
+ wchar_t *szProto = (wchar_t*)alloca(sizeof(wchar_t)*(len + 1));
+ if (szProto != nullptr) {
+ *szProto = '\0';
+ SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETLBTEXT, SendDlgItemMessage(hwndDlg, IDC_PROTOLIST, CB_GETCURSEL, 0, 0), (LPARAM)szProto);
+ db_set_ws(0, "FindAdd", "LastSearched", szProto);
+ }
+
+ SaveColumnSizes(hwndList);
+ if (dat->hResultHook != nullptr)
+ UnhookEvent(dat->hResultHook);
+ FreeSearchResults(hwndList);
+ ImageList_Destroy(dat->himlComboIcons);
+ mir_free(dat->search);
+ if (dat->hwndAdvSearch) {
+ DestroyWindow(dat->hwndAdvSearch);
+ dat->hwndAdvSearch = nullptr;
+ }
+ if (dat->hwndTinySearch) {
+ DestroyWindow(dat->hwndTinySearch);
+ dat->hwndTinySearch = nullptr;
+ }
+ mir_free(dat);
+ Window_FreeIcon_IcoLib(hwndDlg);
+ Utils_SaveWindowPosition(hwndDlg, 0, "FindAdd", "");
+ break;
+ }
+ return FALSE;
+}
+
+static INT_PTR FindAddCommand(WPARAM, LPARAM)
+{
+ if (IsWindow(hwndFindAdd)) {
+ ShowWindow(hwndFindAdd, SW_SHOWNORMAL);
+ SetForegroundWindow(hwndFindAdd);
+ SetFocus(hwndFindAdd);
+ }
+ else {
+ int netProtoCount = 0;
+
+ // Make sure we have some networks to search on. This is not ideal since
+ // this check will be repeated every time the dialog is requested, but it
+ // must be done since this service can be called from other places than the menu.
+ // One alternative would be to only create the service if we have network
+ // protocols loaded but that would delay the creation until MODULE_LOADED and
+ // that is not good either...
+ for (auto &pa : g_arAccounts) {
+ if (!pa->IsEnabled())
+ continue;
+
+ int protoCaps = CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (protoCaps & PF1_ANYSEARCH)
+ netProtoCount++;
+ }
+ if (netProtoCount > 0)
+ hwndFindAdd = CreateDialog(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FINDADD), nullptr, DlgProcFindAdd);
+ }
+ return 0;
+}
+
+int FindAddPreShutdown(WPARAM, LPARAM)
+{
+ if (IsWindow(hwndFindAdd))
+ DestroyWindow(hwndFindAdd);
+ hwndFindAdd = nullptr;
+ return 0;
+}
+
+int LoadFindAddModule(void)
+{
+ CreateServiceFunction(MS_FINDADD_FINDADD, FindAddCommand);
+
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnSystemModulesLoaded);
+ HookEvent(ME_PROTO_ACCLISTCHANGED, OnSystemModulesLoaded);
+ HookEvent(ME_SYSTEM_PRESHUTDOWN, FindAddPreShutdown);
+
+ CMenuItem mi(&g_plugin);
+ SET_UID(mi, 0x860556b9, 0x1577, 0x4f6f, 0x8c, 0xb0, 0x93, 0x24, 0xa8, 0x2e, 0x20, 0x92);
+ mi.position = 500020000;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_FINDUSER);
+ mi.name.a = LPGEN("&Find/add contacts...");
+ mi.pszService = MS_FINDADD_FINDADD;
+ hMainMenuItem = Menu_AddMainMenuItem(&mi);
+ return 0;
+}
+
+static int OnSystemModulesLoaded(WPARAM, LPARAM)
+{
+ int netProtoCount = 0;
+
+ // Make sure we have some networks to search on.
+ for (auto &pa : g_arAccounts) {
+ int protoCaps = CallProtoServiceInt(0, pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (protoCaps & PF1_ANYSEARCH)
+ netProtoCount++;
+ }
+
+ Menu_ShowItem(hMainMenuItem, netProtoCount != 0);
+ return 0;
+}
diff --git a/src/mir_app/src/findadd.h b/src/mir_app/src/findadd.h index 81a546fa1a..e0993d696b 100644 --- a/src/mir_app/src/findadd.h +++ b/src/mir_app/src/findadd.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/genmenu.h b/src/mir_app/src/genmenu.h index 693a5bf9a7..8ed7a83359 100644 --- a/src/mir_app/src/genmenu.h +++ b/src/mir_app/src/genmenu.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/headerbar.cpp b/src/mir_app/src/headerbar.cpp index 319334f7a9..f312b22145 100644 --- a/src/mir_app/src/headerbar.cpp +++ b/src/mir_app/src/headerbar.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
Copyright (c) 2007 Artem Shpynov
all portions of this codebase are copyrighted to the people
diff --git a/src/mir_app/src/help.cpp b/src/mir_app/src/help.cpp index 30bd95bcbf..dca9b784b6 100644 --- a/src/mir_app/src/help.cpp +++ b/src/mir_app/src/help.cpp @@ -1,189 +1,189 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "resource.h" - -static class CAboutDlg *pAboutDialog; - -class CAboutDlg : public CDlgBase -{ - int m_iState = 0; - - CCtrlBase ctrlHeaderBar, ctrlDevelopers, ctrlCredits, ctrlWhiteRect; - CCtrlButton btnLink; - -public: - CAboutDlg() : - CDlgBase(g_plugin, IDD_ABOUT), - btnLink(this, IDC_CONTRIBLINK), - ctrlCredits(this, IDC_CREDITSFILE), - ctrlHeaderBar(this, IDC_HEADERBAR), - ctrlWhiteRect(this, IDC_WHITERECT), - ctrlDevelopers(this, IDC_DEVS) - { - btnLink.OnClick = Callback(this, &CAboutDlg::onClick); - - ctrlCredits.UseSystemColors(); - ctrlWhiteRect.UseSystemColors(); - ctrlDevelopers.UseSystemColors(); - } - - bool OnInitDialog() override - { - ptrW wszCopyright(mir_utf8decodeW(LEGAL_COPYRIGHT)); - if (wszCopyright == nullptr) - wszCopyright = mir_a2u(LEGAL_COPYRIGHT); - ctrlDevelopers.SetText(wszCopyright); - - char productVersion[56]; - Miranda_GetVersionText(productVersion, _countof(productVersion)); - ctrlHeaderBar.SetText(CMStringW(FORMAT, L"Miranda NG\nv%S", productVersion)); - - HRSRC hResInfo = FindResource(g_plugin.getInst(), MAKEINTRESOURCE(IDR_CREDITS), L"TEXT"); - uint32_t ResSize = SizeofResource(g_plugin.getInst(), hResInfo); - HGLOBAL hRes = LoadResource(g_plugin.getInst(), hResInfo); - char *pszMsg = (char*)LockResource(hRes); - if (pszMsg) { - char *pszMsgt = (char*)alloca(ResSize + 1); - memcpy(pszMsgt, pszMsg, ResSize); pszMsgt[ResSize] = 0; - - ptrW ptszMsg; - if (ResSize >= 3 && pszMsgt[0] == '\xef' && pszMsgt[1] == '\xbb' && pszMsgt[2] == '\xbf') - ptszMsg = mir_utf8decodeW(pszMsgt + 3); - else - ptszMsg = mir_a2u_cp(pszMsgt, 1252); - ctrlCredits.SetText(ptszMsg); - - UnlockResource(pszMsg); - } - FreeResource(hRes); - ctrlCredits.Hide(); - - Window_SetSkinIcon_IcoLib(m_hwnd, SKINICON_OTHER_MIRANDA); - return true; - } - - void OnDestroy() override - { - pAboutDialog = nullptr; - Window_FreeIcon_IcoLib(m_hwnd); - } - - void onClick(CCtrlButton*) - { - if (m_iState) { - btnLink.SetText(TranslateT("Credits >")); - ctrlDevelopers.Show(); - ctrlCredits.Hide(); - } - else { - btnLink.SetText(TranslateT("< Copyright")); - ctrlDevelopers.Hide(); - ctrlCredits.Show(); - } - m_iState = !m_iState; - } -}; - -static INT_PTR AboutCommand(WPARAM wParam, LPARAM) -{ - if (pAboutDialog) { - SetForegroundWindow(pAboutDialog->GetHwnd()); - SetFocus(pAboutDialog->GetHwnd()); - } - else { - pAboutDialog = new CAboutDlg(); - pAboutDialog->SetParent((HWND)wParam); - pAboutDialog->Show(); - } - return 0; -} - -static INT_PTR IndexCommand(WPARAM, LPARAM) -{ - Utils_OpenUrl("https://wiki.miranda-ng.org"); - return 0; -} - -static INT_PTR WebsiteCommand(WPARAM, LPARAM) -{ - Utils_OpenUrl("https://miranda-ng.org"); - return 0; -} - -static INT_PTR BugCommand(WPARAM, LPARAM) -{ - Utils_OpenUrl("https://github.com/miranda-ng/miranda-ng/issues/new"); - return 0; -} - -int ShutdownHelpModule(WPARAM, LPARAM) -{ - if (pAboutDialog) - pAboutDialog->Close(); - return 0; -} - -int LoadHelpModule(void) -{ - HookEvent(ME_SYSTEM_PRESHUTDOWN, ShutdownHelpModule); - - CMenuItem mi(&g_plugin); - mi.root = g_plugin.addRootMenu(MO_MAIN, LPGENW("&Help"), 2000090000); - Menu_ConfigureItem(mi.root, MCI_OPT_UID, "8824ECA5-6942-46D7-9D07-1BA600E0D02E"); - - SET_UID(mi, 0xf3ebf1fa, 0x587c, 0x494d, 0xbd, 0x33, 0x7f, 0x88, 0xb3, 0x61, 0x1e, 0xd3); - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_MIRANDA); - mi.position = 2000090000; - mi.name.a = LPGEN("&About..."); - mi.pszService = "Help/AboutCommand"; - Menu_AddMainMenuItem(&mi); - CreateServiceFunction(mi.pszService, AboutCommand); - - SET_UID(mi, 0x495df66f, 0x844e, 0x479a, 0xaf, 0x21, 0x3e, 0x42, 0xc5, 0x14, 0x7c, 0x7e); - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_HELP); - mi.position = -500050000; - mi.name.a = LPGEN("&Support"); - mi.pszService = "Help/IndexCommand"; - Menu_AddMainMenuItem(&mi); - CreateServiceFunction(mi.pszService, IndexCommand); - - SET_UID(mi, 0x15e18b58, 0xec73, 0x45c2, 0xb9, 0xf4, 0x2a, 0xfe, 0xc2, 0xb7, 0xd3, 0x25); - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_MIRANDAWEB); - mi.position = 2000050000; - mi.name.a = LPGEN("&Miranda NG homepage"); - mi.pszService = "Help/WebsiteCommand"; - Menu_AddMainMenuItem(&mi); - CreateServiceFunction(mi.pszService, WebsiteCommand); - - SET_UID(mi, 0xe7d0fe8b, 0xfdeb, 0x45b3, 0xba, 0x83, 0x3, 0x1e, 0x15, 0xda, 0x7e, 0x52); - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_EVENT_URL); - mi.position = 2000040000; - mi.name.a = LPGEN("&Report bug"); - mi.pszService = "Help/BugCommand"; - Menu_AddMainMenuItem(&mi); - CreateServiceFunction(mi.pszService, BugCommand); - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "resource.h"
+
+static class CAboutDlg *pAboutDialog;
+
+class CAboutDlg : public CDlgBase
+{
+ int m_iState = 0;
+
+ CCtrlBase ctrlHeaderBar, ctrlDevelopers, ctrlCredits, ctrlWhiteRect;
+ CCtrlButton btnLink;
+
+public:
+ CAboutDlg() :
+ CDlgBase(g_plugin, IDD_ABOUT),
+ btnLink(this, IDC_CONTRIBLINK),
+ ctrlCredits(this, IDC_CREDITSFILE),
+ ctrlHeaderBar(this, IDC_HEADERBAR),
+ ctrlWhiteRect(this, IDC_WHITERECT),
+ ctrlDevelopers(this, IDC_DEVS)
+ {
+ btnLink.OnClick = Callback(this, &CAboutDlg::onClick);
+
+ ctrlCredits.UseSystemColors();
+ ctrlWhiteRect.UseSystemColors();
+ ctrlDevelopers.UseSystemColors();
+ }
+
+ bool OnInitDialog() override
+ {
+ ptrW wszCopyright(mir_utf8decodeW(LEGAL_COPYRIGHT));
+ if (wszCopyright == nullptr)
+ wszCopyright = mir_a2u(LEGAL_COPYRIGHT);
+ ctrlDevelopers.SetText(wszCopyright);
+
+ char productVersion[56];
+ Miranda_GetVersionText(productVersion, _countof(productVersion));
+ ctrlHeaderBar.SetText(CMStringW(FORMAT, L"Miranda NG\nv%S", productVersion));
+
+ HRSRC hResInfo = FindResource(g_plugin.getInst(), MAKEINTRESOURCE(IDR_CREDITS), L"TEXT");
+ uint32_t ResSize = SizeofResource(g_plugin.getInst(), hResInfo);
+ HGLOBAL hRes = LoadResource(g_plugin.getInst(), hResInfo);
+ char *pszMsg = (char*)LockResource(hRes);
+ if (pszMsg) {
+ char *pszMsgt = (char*)alloca(ResSize + 1);
+ memcpy(pszMsgt, pszMsg, ResSize); pszMsgt[ResSize] = 0;
+
+ ptrW ptszMsg;
+ if (ResSize >= 3 && pszMsgt[0] == '\xef' && pszMsgt[1] == '\xbb' && pszMsgt[2] == '\xbf')
+ ptszMsg = mir_utf8decodeW(pszMsgt + 3);
+ else
+ ptszMsg = mir_a2u_cp(pszMsgt, 1252);
+ ctrlCredits.SetText(ptszMsg);
+
+ UnlockResource(pszMsg);
+ }
+ FreeResource(hRes);
+ ctrlCredits.Hide();
+
+ Window_SetSkinIcon_IcoLib(m_hwnd, SKINICON_OTHER_MIRANDA);
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ pAboutDialog = nullptr;
+ Window_FreeIcon_IcoLib(m_hwnd);
+ }
+
+ void onClick(CCtrlButton*)
+ {
+ if (m_iState) {
+ btnLink.SetText(TranslateT("Credits >"));
+ ctrlDevelopers.Show();
+ ctrlCredits.Hide();
+ }
+ else {
+ btnLink.SetText(TranslateT("< Copyright"));
+ ctrlDevelopers.Hide();
+ ctrlCredits.Show();
+ }
+ m_iState = !m_iState;
+ }
+};
+
+static INT_PTR AboutCommand(WPARAM wParam, LPARAM)
+{
+ if (pAboutDialog) {
+ SetForegroundWindow(pAboutDialog->GetHwnd());
+ SetFocus(pAboutDialog->GetHwnd());
+ }
+ else {
+ pAboutDialog = new CAboutDlg();
+ pAboutDialog->SetParent((HWND)wParam);
+ pAboutDialog->Show();
+ }
+ return 0;
+}
+
+static INT_PTR IndexCommand(WPARAM, LPARAM)
+{
+ Utils_OpenUrl("https://wiki.miranda-ng.org");
+ return 0;
+}
+
+static INT_PTR WebsiteCommand(WPARAM, LPARAM)
+{
+ Utils_OpenUrl("https://miranda-ng.org");
+ return 0;
+}
+
+static INT_PTR BugCommand(WPARAM, LPARAM)
+{
+ Utils_OpenUrl("https://github.com/miranda-ng/miranda-ng/issues/new");
+ return 0;
+}
+
+int ShutdownHelpModule(WPARAM, LPARAM)
+{
+ if (pAboutDialog)
+ pAboutDialog->Close();
+ return 0;
+}
+
+int LoadHelpModule(void)
+{
+ HookEvent(ME_SYSTEM_PRESHUTDOWN, ShutdownHelpModule);
+
+ CMenuItem mi(&g_plugin);
+ mi.root = g_plugin.addRootMenu(MO_MAIN, LPGENW("&Help"), 2000090000);
+ Menu_ConfigureItem(mi.root, MCI_OPT_UID, "8824ECA5-6942-46D7-9D07-1BA600E0D02E");
+
+ SET_UID(mi, 0xf3ebf1fa, 0x587c, 0x494d, 0xbd, 0x33, 0x7f, 0x88, 0xb3, 0x61, 0x1e, 0xd3);
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_MIRANDA);
+ mi.position = 2000090000;
+ mi.name.a = LPGEN("&About...");
+ mi.pszService = "Help/AboutCommand";
+ Menu_AddMainMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, AboutCommand);
+
+ SET_UID(mi, 0x495df66f, 0x844e, 0x479a, 0xaf, 0x21, 0x3e, 0x42, 0xc5, 0x14, 0x7c, 0x7e);
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_HELP);
+ mi.position = -500050000;
+ mi.name.a = LPGEN("&Support");
+ mi.pszService = "Help/IndexCommand";
+ Menu_AddMainMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, IndexCommand);
+
+ SET_UID(mi, 0x15e18b58, 0xec73, 0x45c2, 0xb9, 0xf4, 0x2a, 0xfe, 0xc2, 0xb7, 0xd3, 0x25);
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_MIRANDAWEB);
+ mi.position = 2000050000;
+ mi.name.a = LPGEN("&Miranda NG homepage");
+ mi.pszService = "Help/WebsiteCommand";
+ Menu_AddMainMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, WebsiteCommand);
+
+ SET_UID(mi, 0xe7d0fe8b, 0xfdeb, 0x45b3, 0xba, 0x83, 0x3, 0x1e, 0x15, 0xda, 0x7e, 0x52);
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_EVENT_URL);
+ mi.position = 2000040000;
+ mi.name.a = LPGEN("&Report bug");
+ mi.pszService = "Help/BugCommand";
+ Menu_AddMainMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, BugCommand);
+ return 0;
+}
diff --git a/src/mir_app/src/hotkey_opts.cpp b/src/mir_app/src/hotkey_opts.cpp index 7221e4df60..04d78d2bf8 100644 --- a/src/mir_app/src/hotkey_opts.cpp +++ b/src/mir_app/src/hotkey_opts.cpp @@ -1,1026 +1,1026 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ -#include "stdafx.h" - -#include <m_hotkeys.h> -#include "skin.h" - -static wchar_t *sttHokeyVkToName(uint16_t vkKey) -{ - static wchar_t buf[256] = {}; - uint32_t code = MapVirtualKey(vkKey, 0) << 16; - - switch (vkKey) { - case 0: - case VK_CONTROL: - case VK_SHIFT: - case VK_MENU: - case VK_LWIN: - case VK_RWIN: - case VK_PAUSE: - case VK_CANCEL: - case VK_NUMLOCK: - case VK_CAPITAL: - case VK_SCROLL: - return L""; - case VK_BROWSER_BACK: - return TranslateT("Browser: Back"); - case VK_BROWSER_FORWARD: - return TranslateT("Browser: Forward"); - case VK_BROWSER_REFRESH: - return TranslateT("Browser: Refresh"); - case VK_BROWSER_STOP: - return TranslateT("Browser: Stop"); - case VK_BROWSER_SEARCH: - return TranslateT("Browser: Search"); - case VK_BROWSER_FAVORITES: - return TranslateT("Browser: Fav"); - case VK_BROWSER_HOME: - return TranslateT("Browser: Home"); - case VK_VOLUME_MUTE: - return TranslateT("Mute"); - case VK_VOLUME_DOWN: - return TranslateT("Vol-"); - case VK_VOLUME_UP: - return TranslateT("Vol+"); - case VK_MEDIA_NEXT_TRACK: - return TranslateT("Media: Next Track"); - case VK_MEDIA_PREV_TRACK: - return TranslateT("Media: Prev. Track"); - case VK_MEDIA_STOP: - return TranslateT("Media: Stop"); - case VK_MEDIA_PLAY_PAUSE: - return TranslateT("Media: Play/Pause"); - case VK_LAUNCH_MAIL: - return TranslateT("Mail"); - case VK_LAUNCH_MEDIA_SELECT: - return TranslateT("Media: Select"); - case VK_LAUNCH_APP1: - return TranslateT("App 1"); - case VK_LAUNCH_APP2: - return TranslateT("App 2"); - - case VK_DIVIDE: - case VK_INSERT: - case VK_HOME: - case VK_PRIOR: - case VK_DELETE: - case VK_END: - case VK_NEXT: - case VK_LEFT: - case VK_RIGHT: - case VK_UP: - case VK_DOWN: - code |= (1UL << 24); - } - - GetKeyNameText(code, buf, 256); - return buf; -} - -void HotkeyToName(wchar_t *buf, int size, uint8_t shift, uint8_t key) -{ - mir_snwprintf(buf, size, L"%s%s%s%s%s", - (shift & HOTKEYF_CONTROL) ? TranslateT("Ctrl + ") : L"", - (shift & HOTKEYF_ALT) ? TranslateT("Alt + ") : L"", - (shift & HOTKEYF_SHIFT) ? TranslateT("Shift + ") : L"", - (shift & HOTKEYF_EXT) ? TranslateT("Win + ") : L"", - sttHokeyVkToName(key)); -} - -/////////////////////////////////////////////////////////////////////////////// -// Hotkey control - -static LRESULT CALLBACK sttHotkeyEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - THotkeyBoxData *data = (THotkeyBoxData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (!data) - return 0; - - bool bKeyDown = false; - - switch (msg) { - case HKM_GETHOTKEY: - return data->key ? MAKEWORD(data->key, data->shift) : 0; - - case HKM_SETHOTKEY: - { - wchar_t buf[256] = {}; - data->key = (uint8_t)LOWORD(wParam); - data->shift = (uint8_t)HIWORD(wParam); - HotkeyToName(buf, _countof(buf), data->shift, data->key); - SetWindowText(hwnd, buf); - } - return 0; - - case WM_GETDLGCODE: - return DLGC_WANTALLKEYS; - - case WM_KILLFOCUS: - break; - - case WM_CHAR: - case WM_SYSCHAR: - case WM_PASTE: - case WM_CONTEXTMENU: - return TRUE; - - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - bKeyDown = true; - - case WM_KEYUP: - case WM_SYSKEYUP: - { - wchar_t buf[256] = {}; - - uint8_t shift = 0; - uint8_t key = wParam; - wchar_t *name = sttHokeyVkToName(key); - if (!*name || !bKeyDown) - key = 0; - - if (GetAsyncKeyState(VK_CONTROL)) shift |= HOTKEYF_CONTROL; - if (GetAsyncKeyState(VK_MENU)) shift |= HOTKEYF_ALT; - if (GetAsyncKeyState(VK_SHIFT)) shift |= HOTKEYF_SHIFT; - if (GetAsyncKeyState(VK_LWIN) || GetAsyncKeyState(VK_RWIN)) shift |= HOTKEYF_EXT; - - if (bKeyDown || !data->key) { - data->shift = shift; - data->key = key; - } - - HotkeyToName(buf, _countof(buf), data->shift, data->key); - SetWindowText(hwnd, buf); - - if (bKeyDown && data->key) - SendMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(GetWindowLongPtr(hwnd, GWL_ID), 0), (LPARAM)hwnd); - } - return TRUE; - - case WM_DESTROY: - SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); - mir_free(data); - } - - return mir_callNextSubclass(hwnd, sttHotkeyEditProc, msg, wParam, lParam); -} - -MIR_APP_DLL(void) Hotkey_Subclass(HWND hwnd) -{ - THotkeyBoxData *data = (THotkeyBoxData *)mir_alloc(sizeof(THotkeyBoxData)); - SetWindowLongPtr(hwnd, GWLP_USERDATA, (ULONG_PTR)data); - mir_subclassWindow(hwnd, sttHotkeyEditProc); -} - -MIR_APP_DLL(void) Hotkey_Unsubclass(HWND hwnd) -{ - THotkeyBoxData *data = (THotkeyBoxData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); - SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); - mir_free(data); -} - -/////////////////////////////////////////////////////////////////////////////// -// Options - -enum { COL_NAME, COL_TYPE, COL_KEY, COL_RESET, COL_ADDREMOVE }; - -static void sttOptionsSetupItem(HWND hwndList, int idx, THotkeyItem *item) -{ - wchar_t buf[256]; - LVITEM lvi = {}; - lvi.iItem = idx; - - if (!item->rootHotkey) { - lvi.mask = LVIF_TEXT | LVIF_IMAGE; - lvi.iSubItem = COL_NAME; - lvi.pszText = item->getDescr(); - lvi.iImage = item->OptType; - ListView_SetItem(hwndList, &lvi); - - ListView_SetCheckState(hwndList, lvi.iItem, item->Enabled); - } - - lvi.mask = LVIF_TEXT; - lvi.iSubItem = COL_KEY; - HotkeyToName(buf, _countof(buf), HIBYTE(item->OptHotkey), LOBYTE(item->OptHotkey)); - lvi.pszText = buf; - ListView_SetItem(hwndList, &lvi); - - if (item->rootHotkey) { - lvi.mask = LVIF_IMAGE; - lvi.iSubItem = COL_TYPE; - lvi.iImage = item->OptType; - ListView_SetItem(hwndList, &lvi); - } - - lvi.mask = LVIF_IMAGE; - lvi.iSubItem = COL_RESET; - lvi.iImage = (item->Hotkey != item->OptHotkey) ? 5 : -1; - ListView_SetItem(hwndList, &lvi); - - lvi.mask = LVIF_IMAGE | LVIF_TEXT; - lvi.iSubItem = COL_ADDREMOVE; - if (item->rootHotkey) { - lvi.iImage = 4; - lvi.pszText = TranslateT("Remove shortcut"); - } - else { - lvi.iImage = 3; - lvi.pszText = TranslateT("Add another shortcut"); - } - ListView_SetItem(hwndList, &lvi); -} - -static void sttOptionsDeleteHotkey(HWND hwndList, int idx, THotkeyItem *item) -{ - item->OptDeleted = true; - ListView_DeleteItem(hwndList, idx); - if (item->rootHotkey) - item->rootHotkey->OptChanged = true; -} - -static int CALLBACK sttOptionsSortList(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) -{ - wchar_t title1[256] = {}, title2[256] = {}; - THotkeyItem *item1 = nullptr, *item2 = nullptr; - LVITEM lvi = {}; - int res; - - lvi.mask = LVIF_TEXT | LVIF_PARAM; - lvi.iItem = lParam1; - lvi.pszText = title1; - lvi.cchTextMax = _countof(title1); - if (ListView_GetItem((HWND)lParamSort, &lvi)) - item1 = (THotkeyItem *)lvi.lParam; - - lvi.mask = LVIF_TEXT | LVIF_PARAM; - lvi.iItem = lParam2; - lvi.pszText = title2; - lvi.cchTextMax = _countof(title2); - if (ListView_GetItem((HWND)lParamSort, &lvi)) - item2 = (THotkeyItem *)lvi.lParam; - - if (!item1 && !item2) - return mir_wstrcmp(title1, title2); - - if (!item1 && item2) { - if (res = mir_wstrcmp(title1, item2->getSection())) - return res; - return -1; - } - - if (!item2 && item1) { - if (res = mir_wstrcmp(item1->getSection(), title2)) - return res; - return 1; - } - /* item1 != nullptr && item2 != nullptr */ - - if (res = mir_wstrcmp(item1->getSection(), item2->getSection())) return res; - if (res = mir_wstrcmp(item1->getDescr(), item2->getDescr())) return res; - if (!item1->rootHotkey && item2->rootHotkey) return -1; - if (item1->rootHotkey && !item2->rootHotkey) return 1; - return 0; -} - -static void sttOptionsAddHotkey(HWND hwndList, THotkeyItem *item) -{ - char buf[256]; - mir_snprintf(buf, "mir_hotkey_%d_%d", g_pid, g_hkid++); - - THotkeyItem *newItem = (THotkeyItem *)mir_calloc(sizeof(THotkeyItem)); - newItem->pPlugin = item->pPlugin; - newItem->pszService = mir_strdup(item->pszService); - newItem->pwszSection = mir_wstrdup(item->pwszSection); - newItem->pwszDescription = mir_wstrdup(item->pwszDescription); - newItem->lParam = item->lParam; - newItem->idHotkey = GlobalAddAtomA(buf); - newItem->rootHotkey = item; - newItem->type = newItem->OptType = item->OptType; - newItem->Enabled = newItem->OptEnabled = newItem->OptNew = true; - hotkeys.insert(newItem); - - SendMessage(hwndList, WM_SETREDRAW, FALSE, 0); - - LVITEM lvi = {}; - lvi.mask |= LVIF_PARAM; - lvi.lParam = (LPARAM)newItem; - sttOptionsSetupItem(hwndList, ListView_InsertItem(hwndList, &lvi), newItem); - ListView_SortItemsEx(hwndList, sttOptionsSortList, (LPARAM)hwndList); - - SendMessage(hwndList, WM_SETREDRAW, TRUE, 0); - RedrawWindow(hwndList, nullptr, nullptr, RDW_INVALIDATE); - - item->OptChanged = true; -} - -static void sttOptionsSetChanged(THotkeyItem *item) -{ - item->OptChanged = true; - if (item->rootHotkey) - item->rootHotkey->OptChanged = true; -} - -static void sttOptionsSaveItem(THotkeyItem *item) -{ - char buf[MAXMODULELABELLENGTH]; - - if (item->rootHotkey) return; - if (!item->OptChanged) return; - - item->Hotkey = item->OptHotkey; - item->type = item->OptType; - item->Enabled = item->OptEnabled; - - db_set_w(0, DBMODULENAME, item->pszName, item->Hotkey); - db_set_b(0, DBMODULENAME "Off", item->pszName, (uint8_t)!item->Enabled); - if (item->type != HKT_MANUAL) - db_set_b(0, DBMODULENAME "Types", item->pszName, (uint8_t)item->type); - - item->nSubHotkeys = 0; - for (auto &it : hotkeys) { - if (it->rootHotkey == item) { - it->Hotkey = it->OptHotkey; - it->type = it->OptType; - - mir_snprintf(buf, "%s$%d", item->pszName, item->nSubHotkeys); - db_set_w(0, DBMODULENAME, buf, it->Hotkey); - if (it->type != HKT_MANUAL) - db_set_b(0, DBMODULENAME "Types", buf, (uint8_t)it->type); - - ++item->nSubHotkeys; - } - } - - mir_snprintf(buf, "%s$count", item->pszName); - db_set_dw(0, DBMODULENAME, buf, item->nSubHotkeys); -} - -static void sttBuildHotkeyList(HWND hwndList) -{ - ListView_DeleteAllItems(hwndList); - - int nItems = 0; - THotkeyItem *prevItem = nullptr; - for (auto &item : hotkeys) { - LVITEM lvi = {}; - - if (!item->OptDeleted) { - if (!prevItem || mir_wstrcmp(item->pwszSection, prevItem->pwszSection)) { - lvi.mask = LVIF_TEXT | LVIF_PARAM; - lvi.iItem = nItems++; - lvi.iSubItem = 0; - lvi.lParam = 0; - lvi.pszText = item->getSection(); - ListView_InsertItem(hwndList, &lvi); - ListView_SetCheckState(hwndList, lvi.iItem, TRUE); - - lvi.mask = LVIF_TEXT; - lvi.iSubItem = 1; - lvi.pszText = item->pwszSection; - ListView_SetItem(hwndList, &lvi); - - lvi.iSubItem = 0; - } - - lvi.mask = LVIF_PARAM | LVIF_INDENT; - lvi.iIndent = 1; - lvi.iItem = nItems++; - lvi.lParam = (LPARAM)item; - ListView_InsertItem(hwndList, &lvi); - sttOptionsSetupItem(hwndList, nItems - 1, item); - } - - prevItem = item; - } - - ListView_SortItemsEx(hwndList, sttOptionsSortList, (LPARAM)hwndList); -} - -static void sttOptionsStartEdit(HWND hwndDlg, HWND hwndHotkey) -{ - LVITEM lvi; - THotkeyItem *item; - int iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED); - if (iItem < 0) - return; - - lvi.mask = LVIF_PARAM; - lvi.iItem = iItem; - ListView_GetItem(hwndHotkey, &lvi); - - if (item = (THotkeyItem *)lvi.lParam) { - RECT rc; - ListView_GetSubItemRect(hwndHotkey, iItem, COL_KEY, LVIR_BOUNDS, &rc); - MapWindowPoints(hwndHotkey, hwndDlg, (LPPOINT)&rc, 2); - SendDlgItemMessage(hwndDlg, IDC_HOTKEY, HKM_SETHOTKEY, MAKELONG(LOBYTE(item->OptHotkey), HIBYTE(item->OptHotkey)), 0); - - SetWindowPos(hwndHotkey, HWND_BOTTOM, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); - SetWindowPos(GetDlgItem(hwndDlg, IDC_HOTKEY), HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); - RedrawWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), nullptr, nullptr, RDW_INVALIDATE); - - SetFocus(GetDlgItem(hwndDlg, IDC_HOTKEY)); - RedrawWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), nullptr, nullptr, RDW_INVALIDATE); - } -} - -static void sttOptionsDrawTextChunk(HDC hdc, wchar_t *text, RECT *rc) -{ - DrawText(hdc, text, -1, rc, DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS); - - SIZE sz; - GetTextExtentPoint32(hdc, text, (int)mir_wstrlen(text), &sz); - rc->left += sz.cx; -} - -static INT_PTR CALLBACK sttOptionsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - static bool initialized = false; - static int colWidth = 0; - static uint16_t currentLanguage = 0; - - HWND hwndHotkey = GetDlgItem(hwndDlg, IDC_LV_HOTKEYS); - - switch (msg) { - case WM_INITDIALOG: - initialized = false; - - TranslateDialogDefault(hwndDlg); - - Hotkey_Subclass(GetDlgItem(hwndDlg, IDC_HOTKEY)); - { - HIMAGELIST hIml = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 3, 1); - ImageList_AddSkinIcon(hIml, SKINICON_OTHER_WINDOWS); - ImageList_AddSkinIcon(hIml, SKINICON_OTHER_MIRANDA); - ImageList_AddSkinIcon(hIml, SKINICON_OTHER_WINDOW); - ImageList_AddSkinIcon(hIml, SKINICON_OTHER_ADDCONTACT); - ImageList_AddSkinIcon(hIml, SKINICON_OTHER_DELETE); - ImageList_AddSkinIcon(hIml, SKINICON_OTHER_UNDO); - ImageList_AddSkinIcon(hIml, SKINICON_OTHER_GROUPOPEN); - ImageList_AddSkinIcon(hIml, SKINICON_OTHER_GROUPSHUT); - ListView_SetImageList(hwndHotkey, hIml, LVSIL_SMALL); - } - ListView_SetExtendedListViewStyle(hwndHotkey, LVS_EX_CHECKBOXES | LVS_EX_SUBITEMIMAGES | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP); - { - RECT rc; - GetClientRect(hwndHotkey, &rc); - colWidth = rc.right - GetSystemMetrics(SM_CXHTHUMB) - 3 * g_iIconSX - 5; - - LVCOLUMN lvc; - lvc.mask = LVCF_WIDTH; - lvc.cx = colWidth * 2 / 3; - ListView_InsertColumn(hwndHotkey, COL_NAME, &lvc); - lvc.cx = g_iIconSX; - ListView_InsertColumn(hwndHotkey, COL_TYPE, &lvc); - lvc.cx = colWidth / 3; - ListView_InsertColumn(hwndHotkey, COL_KEY, &lvc); - lvc.cx = g_iIconSX; - ListView_InsertColumn(hwndHotkey, COL_RESET, &lvc); - lvc.cx = g_iIconSX; - ListView_InsertColumn(hwndHotkey, COL_ADDREMOVE, &lvc); - - for (auto &it : hotkeys) { - it->OptChanged = false; - it->OptDeleted = it->OptNew = false; - it->OptEnabled = it->Enabled; - it->OptHotkey = it->Hotkey; - it->OptType = it->type; - } - - currentLanguage = LOWORD(GetKeyboardLayout(0)); - sttBuildHotkeyList(hwndHotkey); - } - SetTimer(hwndDlg, 1024, 1000, nullptr); - initialized = TRUE; - { - /* load group states */ - int count = ListView_GetItemCount(hwndHotkey); - wchar_t buf[128]; - LVITEM lvi = {}; - lvi.pszText = buf; - lvi.cchTextMax = _countof(buf); - for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { - lvi.mask = LVIF_PARAM; - lvi.iSubItem = 0; - ListView_GetItem(hwndHotkey, &lvi); - if (lvi.lParam) - continue; - - lvi.mask = LVIF_TEXT; - lvi.iSubItem = 1; - ListView_GetItem(hwndHotkey, &lvi); - - ListView_SetCheckState(hwndHotkey, lvi.iItem, db_get_b(0, DBMODULENAME "UI", _T2A(lvi.pszText), TRUE)); - } - } - - g_hwndHkOptions = hwndDlg; - break; - - case WM_TIMER: - if (initialized) { - uint16_t newLanguage = LOWORD(GetKeyboardLayout(0)); - if (newLanguage == currentLanguage) - break; - - int count = ListView_GetItemCount(hwndHotkey); - - LVITEM lvi = {}; - lvi.mask = LVIF_PARAM; - for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { - ListView_GetItem(hwndHotkey, &lvi); - if (lvi.lParam) - sttOptionsSetupItem(hwndHotkey, lvi.iItem, (THotkeyItem *)lvi.lParam); - } - currentLanguage = newLanguage; - } - break; - - case WM_HOTKEYUNREGISTERED: - { - int count = ListView_GetItemCount(hwndHotkey); - - LVITEM lvi = {}; - lvi.mask = LVIF_PARAM; - for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { - ListView_GetItem(hwndHotkey, &lvi); - if (!lvi.lParam) continue; - - if (((THotkeyItem *)lvi.lParam)->UnregisterHotkey) { - ListView_DeleteItem(hwndHotkey, lvi.iItem); - --lvi.iItem; - --count; - } - } - } - break; - - case WM_DRAWITEM: - { - LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam; - RECT rc = lpdis->rcItem; - int prefix = 65; - int width = (lpdis->rcItem.right - lpdis->rcItem.left - prefix) / 3; - rc.left += 5; - - HIMAGELIST hIml = ListView_GetImageList(hwndHotkey, LVSIL_SMALL); - if (lpdis->CtlID == IDC_CANVAS2) { - sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Scope:"), &rc); - - rc.left = prefix; - ImageList_Draw(hIml, 0, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT); - rc.left += 20; - sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("System"), &rc); - - rc.left = prefix + width; - ImageList_Draw(hIml, 1, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT); - rc.left += 20; - sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Miranda"), &rc); - - rc.left = prefix + width * 2; - ImageList_Draw(hIml, 2, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT); - rc.left += 20; - sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Window"), &rc); - return TRUE; - } - - if (lpdis->CtlID == IDC_CANVAS) { - sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Actions:"), &rc); - - rc.left = prefix; - ImageList_Draw(hIml, 5, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT); - rc.left += 20; - sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Undo"), &rc); - - rc.left = prefix + width * 1; - ImageList_Draw(hIml, 3, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT); - rc.left += 20; - sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Add binding"), &rc); - - rc.left = prefix + width * 2; - ImageList_Draw(hIml, 4, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT); - rc.left += 20; - sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Remove"), &rc); - return TRUE; - } - } - break; - - case WM_COMMAND: - if ((LOWORD(wParam) == IDC_HOTKEY) && ((HIWORD(wParam) == EN_KILLFOCUS) || (HIWORD(wParam) == 0))) { - LVITEM lvi; - THotkeyItem *item; - uint16_t wHotkey = (uint16_t)SendDlgItemMessage(hwndDlg, IDC_HOTKEY, HKM_GETHOTKEY, 0, 0); - - ShowWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), SW_HIDE); - SetFocus(hwndHotkey); - if (!wHotkey || (wHotkey == VK_ESCAPE) || (HIWORD(wParam) != 0)) - break; - - lvi.mask = LVIF_PARAM; - lvi.iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED); - if (lvi.iItem >= 0) { - ListView_GetItem(hwndHotkey, &lvi); - if (item = (THotkeyItem *)lvi.lParam) { - item->OptHotkey = wHotkey; - - sttOptionsSetupItem(hwndHotkey, lvi.iItem, item); - sttOptionsSetChanged(item); - SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); - } - } - } - break; - - case WM_CONTEXTMENU: - if (GetWindowLongPtr((HWND)wParam, GWL_ID) == IDC_LV_HOTKEYS) { - HWND hwndList = (HWND)wParam; - POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - LVITEM lvi = {}; - THotkeyItem *item = nullptr; - - lvi.iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED); - if (lvi.iItem < 0) - return FALSE; - - lvi.mask = LVIF_PARAM; - ListView_GetItem(hwndList, &lvi); - if (!(item = (THotkeyItem *)lvi.lParam)) - return FALSE; - - if (pt.x == -1 && pt.y == -1) { - RECT rc; - ListView_GetItemRect(hwndList, lvi.iItem, &rc, LVIR_LABEL); - pt.x = rc.left; - pt.y = rc.bottom; - ClientToScreen(hwndList, &pt); - } - - enum { MI_CANCEL, MI_CHANGE, MI_SYSTEM, MI_LOCAL, MI_ADD, MI_REMOVE, MI_REVERT }; - - MENUITEMINFO mii = {}; - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_STATE; - mii.fState = MFS_DEFAULT; - - HMENU hMenu = CreatePopupMenu(); - AppendMenu(hMenu, MF_STRING, MI_CHANGE, TranslateT("Modify")); - SetMenuItemInfo(hMenu, MI_CHANGE, FALSE, &mii); - if (item->type != HKT_MANUAL) { - AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); - AppendMenu(hMenu, MF_STRING | - ((item->OptType == HKT_GLOBAL) ? MF_CHECKED : 0), - (UINT_PTR)MI_SYSTEM, TranslateT("System scope")); - AppendMenu(hMenu, MF_STRING | - ((item->OptType == HKT_LOCAL) ? MF_CHECKED : 0), - (UINT_PTR)MI_LOCAL, TranslateT("Miranda scope")); - } - AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); - if (!item->rootHotkey) - AppendMenu(hMenu, MF_STRING, MI_ADD, TranslateT("Add binding")); - else - AppendMenu(hMenu, MF_STRING, MI_REMOVE, TranslateT("Remove")); - if (item->Hotkey != item->OptHotkey) { - AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); - AppendMenu(hMenu, MF_STRING, MI_REVERT, TranslateT("Undo")); - } - - switch (TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, nullptr)) { - case MI_CHANGE: - sttOptionsStartEdit(hwndDlg, hwndHotkey); - break; - case MI_SYSTEM: - item->OptType = HKT_GLOBAL; - sttOptionsSetupItem(hwndList, lvi.iItem, item); - sttOptionsSetChanged(item); - SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); - break; - case MI_LOCAL: - item->OptType = HKT_LOCAL; - sttOptionsSetupItem(hwndList, lvi.iItem, item); - sttOptionsSetChanged(item); - SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); - break; - case MI_ADD: - initialized = false; - sttOptionsAddHotkey(hwndList, item); - initialized = true; - break; - case MI_REMOVE: - sttOptionsDeleteHotkey(hwndList, lvi.iItem, item); - break; - case MI_REVERT: - item->OptHotkey = item->Hotkey; - sttOptionsSetupItem(hwndList, lvi.iItem, item); - break; - } - DestroyMenu(hMenu); - break; - } - break; - - case WM_NOTIFY: - { - LPNMHDR lpnmhdr = (LPNMHDR)lParam; - switch (lpnmhdr->idFrom) { - case 0: - if ((lpnmhdr->code != PSN_APPLY) && (lpnmhdr->code != PSN_RESET)) - break; - - UnregisterHotkeys(); - - for (auto &p : hotkeys.rev_iter()) - if (p->OptNew && p->OptDeleted || p->rootHotkey && !p->OptHotkey || (lpnmhdr->code == PSN_APPLY) && p->OptDeleted || (lpnmhdr->code == PSN_RESET) && p->OptNew) - FreeHotkey(hotkeys.removeItem(&p)); - - if (lpnmhdr->code == PSN_APPLY) { - LVITEM lvi = {}; - int count = ListView_GetItemCount(hwndHotkey); - - for (auto &it : hotkeys) - sttOptionsSaveItem(it); - - lvi.mask = LVIF_IMAGE; - lvi.iSubItem = COL_RESET; - lvi.iImage = -1; - for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) - ListView_SetItem(hwndHotkey, &lvi); - } - - RegisterHotkeys(); - - NotifyEventHooks(hEvChanged, 0, 0); - break; - - case IDC_LV_HOTKEYS: - switch (lpnmhdr->code) { - case NM_CLICK: - { - LPNMITEMACTIVATE lpnmia = (LPNMITEMACTIVATE)lParam; - - LVITEM lvi = {}; - lvi.mask = LVIF_PARAM | LVIF_IMAGE; - lvi.iItem = lpnmia->iItem; - ListView_GetItem(lpnmia->hdr.hwndFrom, &lvi); - - auto *item = (THotkeyItem *)lvi.lParam; - if (item == nullptr) - break; - - LVHITTESTINFO lvhti = {}; - lvhti.pt = lpnmia->ptAction; - lvhti.iItem = lpnmia->iItem; - lvhti.iSubItem = lpnmia->iSubItem; - ListView_HitTest(lpnmia->hdr.hwndFrom, &lvhti); - - if ((!item->rootHotkey && (lpnmia->iSubItem == COL_NAME) && ((lvhti.flags & LVHT_ONITEM) == LVHT_ONITEMICON) || - item->rootHotkey && (lpnmia->iSubItem == COL_TYPE)) && - ((item->OptType == HKT_GLOBAL) || (item->OptType == HKT_LOCAL))) { - item->OptType = (item->OptType == HKT_GLOBAL) ? HKT_LOCAL : HKT_GLOBAL; - sttOptionsSetupItem(lpnmia->hdr.hwndFrom, lpnmia->iItem, item); - sttOptionsSetChanged(item); - SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); - } - else if (lpnmia->iSubItem == COL_RESET) { - item->OptHotkey = item->Hotkey; - sttOptionsSetupItem(lpnmia->hdr.hwndFrom, lpnmia->iItem, item); - } - else if (lpnmia->iSubItem == COL_ADDREMOVE) { - if (item->rootHotkey) - sttOptionsDeleteHotkey(lpnmia->hdr.hwndFrom, lpnmia->iItem, item); - else { - initialized = false; - sttOptionsAddHotkey(lpnmia->hdr.hwndFrom, item); - initialized = true; - } - SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); - } - } - break; - - case LVN_KEYDOWN: - { - LPNMLVKEYDOWN param = (LPNMLVKEYDOWN)lParam; - if (param->wVKey == VK_SUBTRACT || param->wVKey == VK_LEFT || param->wVKey == VK_ADD || param->wVKey == VK_RIGHT) { - LVITEM lvi = {}; - lvi.mask = LVIF_PARAM; - lvi.iItem = ListView_GetNextItem(lpnmhdr->hwndFrom, -1, LVNI_SELECTED); - if (lvi.iItem < 0) - break; - - ListView_GetItem(lpnmhdr->hwndFrom, &lvi); - if (lvi.lParam) - break; - - if (param->wVKey == VK_ADD || param->wVKey == VK_RIGHT) { - ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, TRUE); - } - else { - ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, FALSE); - } - } - else if (param->wVKey == VK_F2) - sttOptionsStartEdit(hwndDlg, hwndHotkey); - } - break; - - case LVN_ITEMACTIVATE: - { - LVITEM lvi = {}; - lvi.mask = LVIF_PARAM; - lvi.iItem = ListView_GetNextItem(lpnmhdr->hwndFrom, -1, LVNI_SELECTED); - if (lvi.iItem < 0) break; - ListView_GetItem(lpnmhdr->hwndFrom, &lvi); - - if (lvi.lParam) - sttOptionsStartEdit(hwndDlg, hwndHotkey); - else - ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, !ListView_GetCheckState(lpnmhdr->hwndFrom, lvi.iItem)); - } - break; - - case LVN_ITEMCHANGED: - if (initialized) { - LPNMLISTVIEW param = (LPNMLISTVIEW)lParam; - THotkeyItem *item = (THotkeyItem *)param->lParam; - if (param->uNewState >> 12 == param->uOldState >> 12) - break; - - if (item && !item->rootHotkey) { - item->OptEnabled = ListView_GetCheckState(lpnmhdr->hwndFrom, param->iItem) ? 1 : 0; - sttOptionsSetChanged(item); - SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); - } - else if (!item) { - wchar_t buf[256]; - LVITEM lvi = {}; - lvi.mask = LVIF_TEXT; - lvi.iItem = param->iItem; - lvi.pszText = buf; - lvi.cchTextMax = _countof(buf); - ListView_GetItem(lpnmhdr->hwndFrom, &lvi); - - if (param->uNewState >> 12 == 1) { - int count = ListView_GetItemCount(lpnmhdr->hwndFrom); - LVITEM lvi2 = {}; - lvi2.mask = LVIF_PARAM; - for (lvi2.iItem = 0; lvi2.iItem < count; ++lvi2.iItem) { - ListView_GetItem(lpnmhdr->hwndFrom, &lvi2); - item = (THotkeyItem *)lvi2.lParam; - if (!item) continue; - if (!mir_wstrcmp(item->getSection(), buf)) { - ListView_DeleteItem(lpnmhdr->hwndFrom, lvi2.iItem); - --lvi2.iItem; - --count; - } - } - } - else if (param->uNewState >> 12 == 2) { - int nItems = ListView_GetItemCount(lpnmhdr->hwndFrom); - initialized = false; - for (auto &it : hotkeys) { - LVITEM lvi2 = {}; - if (it->OptDeleted || mir_wstrcmp(buf, it->getSection())) - continue; - - lvi2.mask = LVIF_PARAM | LVIF_INDENT; - lvi2.iIndent = 1; - lvi2.iItem = nItems++; - lvi2.lParam = (LPARAM)it; - ListView_InsertItem(lpnmhdr->hwndFrom, &lvi2); - sttOptionsSetupItem(lpnmhdr->hwndFrom, nItems - 1, it); - } - ListView_SortItemsEx(lpnmhdr->hwndFrom, sttOptionsSortList, (LPARAM)lpnmhdr->hwndFrom); - initialized = TRUE; - } - } - } - break; - - case NM_CUSTOMDRAW: - { - NMLVCUSTOMDRAW *param = (NMLVCUSTOMDRAW *)lParam; - switch (param->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - case CDDS_ITEMPREPAINT: - SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_NOTIFYSUBITEMDRAW); - return TRUE; - - case CDDS_SUBITEM | CDDS_ITEMPREPAINT: - { - THotkeyItem *item; - wchar_t buf[256]; - LVITEM lvi = {}; - lvi.mask = LVIF_TEXT | LVIF_PARAM; - lvi.iItem = param->nmcd.dwItemSpec; - lvi.pszText = buf; - lvi.cchTextMax = _countof(buf); - ListView_GetItem(lpnmhdr->hwndFrom, &lvi); - - item = (THotkeyItem *)lvi.lParam; - if (!item) { - RECT rc; - HFONT hfnt; - - ListView_GetSubItemRect(lpnmhdr->hwndFrom, param->nmcd.dwItemSpec, param->iSubItem, LVIR_BOUNDS, &rc); - FillRect(param->nmcd.hdc, &rc, GetSysColorBrush(param->nmcd.uItemState & CDIS_SELECTED ? COLOR_HIGHLIGHT : COLOR_WINDOW)); - SetTextColor(param->nmcd.hdc, GetSysColor(param->nmcd.uItemState & CDIS_SELECTED ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); - - if (param->iSubItem == 0) { - rc.left += 3; - HIMAGELIST hIml = ListView_GetImageList(hwndHotkey, LVSIL_SMALL); - ImageList_Draw(hIml, - ListView_GetCheckState(hwndHotkey, lvi.iItem) ? 6 : 7, - param->nmcd.hdc, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT); - rc.left += 18; - hfnt = (HFONT)SelectObject(param->nmcd.hdc, (HFONT)SendMessage(GetParent(hwndDlg), PSM_GETBOLDFONT, 0, 0)); - DrawText(param->nmcd.hdc, buf, -1, &rc, DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER); - SelectObject(param->nmcd.hdc, hfnt); - } - - SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT); - return TRUE; - } - - if (item->rootHotkey && (param->iSubItem == 0)) { - RECT rc; - ListView_GetSubItemRect(lpnmhdr->hwndFrom, param->nmcd.dwItemSpec, param->iSubItem, LVIR_BOUNDS, &rc); - FillRect(param->nmcd.hdc, &rc, GetSysColorBrush(COLOR_WINDOW)); - SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT); - return TRUE; - } - break; - } - } - break; - } - break; - } - } - } - break; - - case WM_DESTROY: - { - int count = ListView_GetItemCount(hwndHotkey); - - g_hwndHkOptions = nullptr; - - KillTimer(hwndDlg, 1024); - - wchar_t buf[128]; - LVITEM lvi = {}; - lvi.pszText = buf; - lvi.cchTextMax = _countof(buf); - for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) { - lvi.mask = LVIF_PARAM; - lvi.iSubItem = 0; - ListView_GetItem(hwndHotkey, &lvi); - if (lvi.lParam) continue; - - lvi.mask = LVIF_TEXT; - lvi.iSubItem = 1; - ListView_GetItem(hwndHotkey, &lvi); - - db_set_b(0, DBMODULENAME "UI", _T2A(lvi.pszText), ListView_GetCheckState(hwndHotkey, lvi.iItem)); - } - } - } - - return FALSE; -} - -int HotkeyOptionsInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.flags = ODPF_BOLDGROUPS; - odp.position = -180000000; - odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_HOTKEYS); - odp.szTitle.a = LPGEN("Hotkeys"); - odp.szGroup.a = LPGEN("Customize"); - odp.pfnDlgProc = sttOptionsDlgProc; - g_plugin.addOptions(wParam, &odp); - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "stdafx.h"
+
+#include <m_hotkeys.h>
+#include "skin.h"
+
+static wchar_t *sttHokeyVkToName(uint16_t vkKey)
+{
+ static wchar_t buf[256] = {};
+ uint32_t code = MapVirtualKey(vkKey, 0) << 16;
+
+ switch (vkKey) {
+ case 0:
+ case VK_CONTROL:
+ case VK_SHIFT:
+ case VK_MENU:
+ case VK_LWIN:
+ case VK_RWIN:
+ case VK_PAUSE:
+ case VK_CANCEL:
+ case VK_NUMLOCK:
+ case VK_CAPITAL:
+ case VK_SCROLL:
+ return L"";
+ case VK_BROWSER_BACK:
+ return TranslateT("Browser: Back");
+ case VK_BROWSER_FORWARD:
+ return TranslateT("Browser: Forward");
+ case VK_BROWSER_REFRESH:
+ return TranslateT("Browser: Refresh");
+ case VK_BROWSER_STOP:
+ return TranslateT("Browser: Stop");
+ case VK_BROWSER_SEARCH:
+ return TranslateT("Browser: Search");
+ case VK_BROWSER_FAVORITES:
+ return TranslateT("Browser: Fav");
+ case VK_BROWSER_HOME:
+ return TranslateT("Browser: Home");
+ case VK_VOLUME_MUTE:
+ return TranslateT("Mute");
+ case VK_VOLUME_DOWN:
+ return TranslateT("Vol-");
+ case VK_VOLUME_UP:
+ return TranslateT("Vol+");
+ case VK_MEDIA_NEXT_TRACK:
+ return TranslateT("Media: Next Track");
+ case VK_MEDIA_PREV_TRACK:
+ return TranslateT("Media: Prev. Track");
+ case VK_MEDIA_STOP:
+ return TranslateT("Media: Stop");
+ case VK_MEDIA_PLAY_PAUSE:
+ return TranslateT("Media: Play/Pause");
+ case VK_LAUNCH_MAIL:
+ return TranslateT("Mail");
+ case VK_LAUNCH_MEDIA_SELECT:
+ return TranslateT("Media: Select");
+ case VK_LAUNCH_APP1:
+ return TranslateT("App 1");
+ case VK_LAUNCH_APP2:
+ return TranslateT("App 2");
+
+ case VK_DIVIDE:
+ case VK_INSERT:
+ case VK_HOME:
+ case VK_PRIOR:
+ case VK_DELETE:
+ case VK_END:
+ case VK_NEXT:
+ case VK_LEFT:
+ case VK_RIGHT:
+ case VK_UP:
+ case VK_DOWN:
+ code |= (1UL << 24);
+ }
+
+ GetKeyNameText(code, buf, 256);
+ return buf;
+}
+
+void HotkeyToName(wchar_t *buf, int size, uint8_t shift, uint8_t key)
+{
+ mir_snwprintf(buf, size, L"%s%s%s%s%s",
+ (shift & HOTKEYF_CONTROL) ? TranslateT("Ctrl + ") : L"",
+ (shift & HOTKEYF_ALT) ? TranslateT("Alt + ") : L"",
+ (shift & HOTKEYF_SHIFT) ? TranslateT("Shift + ") : L"",
+ (shift & HOTKEYF_EXT) ? TranslateT("Win + ") : L"",
+ sttHokeyVkToName(key));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Hotkey control
+
+static LRESULT CALLBACK sttHotkeyEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ THotkeyBoxData *data = (THotkeyBoxData *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (!data)
+ return 0;
+
+ bool bKeyDown = false;
+
+ switch (msg) {
+ case HKM_GETHOTKEY:
+ return data->key ? MAKEWORD(data->key, data->shift) : 0;
+
+ case HKM_SETHOTKEY:
+ {
+ wchar_t buf[256] = {};
+ data->key = (uint8_t)LOWORD(wParam);
+ data->shift = (uint8_t)HIWORD(wParam);
+ HotkeyToName(buf, _countof(buf), data->shift, data->key);
+ SetWindowText(hwnd, buf);
+ }
+ return 0;
+
+ case WM_GETDLGCODE:
+ return DLGC_WANTALLKEYS;
+
+ case WM_KILLFOCUS:
+ break;
+
+ case WM_CHAR:
+ case WM_SYSCHAR:
+ case WM_PASTE:
+ case WM_CONTEXTMENU:
+ return TRUE;
+
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ bKeyDown = true;
+
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ {
+ wchar_t buf[256] = {};
+
+ uint8_t shift = 0;
+ uint8_t key = wParam;
+ wchar_t *name = sttHokeyVkToName(key);
+ if (!*name || !bKeyDown)
+ key = 0;
+
+ if (GetAsyncKeyState(VK_CONTROL)) shift |= HOTKEYF_CONTROL;
+ if (GetAsyncKeyState(VK_MENU)) shift |= HOTKEYF_ALT;
+ if (GetAsyncKeyState(VK_SHIFT)) shift |= HOTKEYF_SHIFT;
+ if (GetAsyncKeyState(VK_LWIN) || GetAsyncKeyState(VK_RWIN)) shift |= HOTKEYF_EXT;
+
+ if (bKeyDown || !data->key) {
+ data->shift = shift;
+ data->key = key;
+ }
+
+ HotkeyToName(buf, _countof(buf), data->shift, data->key);
+ SetWindowText(hwnd, buf);
+
+ if (bKeyDown && data->key)
+ SendMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(GetWindowLongPtr(hwnd, GWL_ID), 0), (LPARAM)hwnd);
+ }
+ return TRUE;
+
+ case WM_DESTROY:
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
+ mir_free(data);
+ }
+
+ return mir_callNextSubclass(hwnd, sttHotkeyEditProc, msg, wParam, lParam);
+}
+
+MIR_APP_DLL(void) Hotkey_Subclass(HWND hwnd)
+{
+ THotkeyBoxData *data = (THotkeyBoxData *)mir_alloc(sizeof(THotkeyBoxData));
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (ULONG_PTR)data);
+ mir_subclassWindow(hwnd, sttHotkeyEditProc);
+}
+
+MIR_APP_DLL(void) Hotkey_Unsubclass(HWND hwnd)
+{
+ THotkeyBoxData *data = (THotkeyBoxData *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
+ mir_free(data);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Options
+
+enum { COL_NAME, COL_TYPE, COL_KEY, COL_RESET, COL_ADDREMOVE };
+
+static void sttOptionsSetupItem(HWND hwndList, int idx, THotkeyItem *item)
+{
+ wchar_t buf[256];
+ LVITEM lvi = {};
+ lvi.iItem = idx;
+
+ if (!item->rootHotkey) {
+ lvi.mask = LVIF_TEXT | LVIF_IMAGE;
+ lvi.iSubItem = COL_NAME;
+ lvi.pszText = item->getDescr();
+ lvi.iImage = item->OptType;
+ ListView_SetItem(hwndList, &lvi);
+
+ ListView_SetCheckState(hwndList, lvi.iItem, item->Enabled);
+ }
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iSubItem = COL_KEY;
+ HotkeyToName(buf, _countof(buf), HIBYTE(item->OptHotkey), LOBYTE(item->OptHotkey));
+ lvi.pszText = buf;
+ ListView_SetItem(hwndList, &lvi);
+
+ if (item->rootHotkey) {
+ lvi.mask = LVIF_IMAGE;
+ lvi.iSubItem = COL_TYPE;
+ lvi.iImage = item->OptType;
+ ListView_SetItem(hwndList, &lvi);
+ }
+
+ lvi.mask = LVIF_IMAGE;
+ lvi.iSubItem = COL_RESET;
+ lvi.iImage = (item->Hotkey != item->OptHotkey) ? 5 : -1;
+ ListView_SetItem(hwndList, &lvi);
+
+ lvi.mask = LVIF_IMAGE | LVIF_TEXT;
+ lvi.iSubItem = COL_ADDREMOVE;
+ if (item->rootHotkey) {
+ lvi.iImage = 4;
+ lvi.pszText = TranslateT("Remove shortcut");
+ }
+ else {
+ lvi.iImage = 3;
+ lvi.pszText = TranslateT("Add another shortcut");
+ }
+ ListView_SetItem(hwndList, &lvi);
+}
+
+static void sttOptionsDeleteHotkey(HWND hwndList, int idx, THotkeyItem *item)
+{
+ item->OptDeleted = true;
+ ListView_DeleteItem(hwndList, idx);
+ if (item->rootHotkey)
+ item->rootHotkey->OptChanged = true;
+}
+
+static int CALLBACK sttOptionsSortList(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
+{
+ wchar_t title1[256] = {}, title2[256] = {};
+ THotkeyItem *item1 = nullptr, *item2 = nullptr;
+ LVITEM lvi = {};
+ int res;
+
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iItem = lParam1;
+ lvi.pszText = title1;
+ lvi.cchTextMax = _countof(title1);
+ if (ListView_GetItem((HWND)lParamSort, &lvi))
+ item1 = (THotkeyItem *)lvi.lParam;
+
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iItem = lParam2;
+ lvi.pszText = title2;
+ lvi.cchTextMax = _countof(title2);
+ if (ListView_GetItem((HWND)lParamSort, &lvi))
+ item2 = (THotkeyItem *)lvi.lParam;
+
+ if (!item1 && !item2)
+ return mir_wstrcmp(title1, title2);
+
+ if (!item1 && item2) {
+ if (res = mir_wstrcmp(title1, item2->getSection()))
+ return res;
+ return -1;
+ }
+
+ if (!item2 && item1) {
+ if (res = mir_wstrcmp(item1->getSection(), title2))
+ return res;
+ return 1;
+ }
+ /* item1 != nullptr && item2 != nullptr */
+
+ if (res = mir_wstrcmp(item1->getSection(), item2->getSection())) return res;
+ if (res = mir_wstrcmp(item1->getDescr(), item2->getDescr())) return res;
+ if (!item1->rootHotkey && item2->rootHotkey) return -1;
+ if (item1->rootHotkey && !item2->rootHotkey) return 1;
+ return 0;
+}
+
+static void sttOptionsAddHotkey(HWND hwndList, THotkeyItem *item)
+{
+ char buf[256];
+ mir_snprintf(buf, "mir_hotkey_%d_%d", g_pid, g_hkid++);
+
+ THotkeyItem *newItem = (THotkeyItem *)mir_calloc(sizeof(THotkeyItem));
+ newItem->pPlugin = item->pPlugin;
+ newItem->pszService = mir_strdup(item->pszService);
+ newItem->pwszSection = mir_wstrdup(item->pwszSection);
+ newItem->pwszDescription = mir_wstrdup(item->pwszDescription);
+ newItem->lParam = item->lParam;
+ newItem->idHotkey = GlobalAddAtomA(buf);
+ newItem->rootHotkey = item;
+ newItem->type = newItem->OptType = item->OptType;
+ newItem->Enabled = newItem->OptEnabled = newItem->OptNew = true;
+ hotkeys.insert(newItem);
+
+ SendMessage(hwndList, WM_SETREDRAW, FALSE, 0);
+
+ LVITEM lvi = {};
+ lvi.mask |= LVIF_PARAM;
+ lvi.lParam = (LPARAM)newItem;
+ sttOptionsSetupItem(hwndList, ListView_InsertItem(hwndList, &lvi), newItem);
+ ListView_SortItemsEx(hwndList, sttOptionsSortList, (LPARAM)hwndList);
+
+ SendMessage(hwndList, WM_SETREDRAW, TRUE, 0);
+ RedrawWindow(hwndList, nullptr, nullptr, RDW_INVALIDATE);
+
+ item->OptChanged = true;
+}
+
+static void sttOptionsSetChanged(THotkeyItem *item)
+{
+ item->OptChanged = true;
+ if (item->rootHotkey)
+ item->rootHotkey->OptChanged = true;
+}
+
+static void sttOptionsSaveItem(THotkeyItem *item)
+{
+ char buf[MAXMODULELABELLENGTH];
+
+ if (item->rootHotkey) return;
+ if (!item->OptChanged) return;
+
+ item->Hotkey = item->OptHotkey;
+ item->type = item->OptType;
+ item->Enabled = item->OptEnabled;
+
+ db_set_w(0, DBMODULENAME, item->pszName, item->Hotkey);
+ db_set_b(0, DBMODULENAME "Off", item->pszName, (uint8_t)!item->Enabled);
+ if (item->type != HKT_MANUAL)
+ db_set_b(0, DBMODULENAME "Types", item->pszName, (uint8_t)item->type);
+
+ item->nSubHotkeys = 0;
+ for (auto &it : hotkeys) {
+ if (it->rootHotkey == item) {
+ it->Hotkey = it->OptHotkey;
+ it->type = it->OptType;
+
+ mir_snprintf(buf, "%s$%d", item->pszName, item->nSubHotkeys);
+ db_set_w(0, DBMODULENAME, buf, it->Hotkey);
+ if (it->type != HKT_MANUAL)
+ db_set_b(0, DBMODULENAME "Types", buf, (uint8_t)it->type);
+
+ ++item->nSubHotkeys;
+ }
+ }
+
+ mir_snprintf(buf, "%s$count", item->pszName);
+ db_set_dw(0, DBMODULENAME, buf, item->nSubHotkeys);
+}
+
+static void sttBuildHotkeyList(HWND hwndList)
+{
+ ListView_DeleteAllItems(hwndList);
+
+ int nItems = 0;
+ THotkeyItem *prevItem = nullptr;
+ for (auto &item : hotkeys) {
+ LVITEM lvi = {};
+
+ if (!item->OptDeleted) {
+ if (!prevItem || mir_wstrcmp(item->pwszSection, prevItem->pwszSection)) {
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iItem = nItems++;
+ lvi.iSubItem = 0;
+ lvi.lParam = 0;
+ lvi.pszText = item->getSection();
+ ListView_InsertItem(hwndList, &lvi);
+ ListView_SetCheckState(hwndList, lvi.iItem, TRUE);
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iSubItem = 1;
+ lvi.pszText = item->pwszSection;
+ ListView_SetItem(hwndList, &lvi);
+
+ lvi.iSubItem = 0;
+ }
+
+ lvi.mask = LVIF_PARAM | LVIF_INDENT;
+ lvi.iIndent = 1;
+ lvi.iItem = nItems++;
+ lvi.lParam = (LPARAM)item;
+ ListView_InsertItem(hwndList, &lvi);
+ sttOptionsSetupItem(hwndList, nItems - 1, item);
+ }
+
+ prevItem = item;
+ }
+
+ ListView_SortItemsEx(hwndList, sttOptionsSortList, (LPARAM)hwndList);
+}
+
+static void sttOptionsStartEdit(HWND hwndDlg, HWND hwndHotkey)
+{
+ LVITEM lvi;
+ THotkeyItem *item;
+ int iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED);
+ if (iItem < 0)
+ return;
+
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = iItem;
+ ListView_GetItem(hwndHotkey, &lvi);
+
+ if (item = (THotkeyItem *)lvi.lParam) {
+ RECT rc;
+ ListView_GetSubItemRect(hwndHotkey, iItem, COL_KEY, LVIR_BOUNDS, &rc);
+ MapWindowPoints(hwndHotkey, hwndDlg, (LPPOINT)&rc, 2);
+ SendDlgItemMessage(hwndDlg, IDC_HOTKEY, HKM_SETHOTKEY, MAKELONG(LOBYTE(item->OptHotkey), HIBYTE(item->OptHotkey)), 0);
+
+ SetWindowPos(hwndHotkey, HWND_BOTTOM, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
+ SetWindowPos(GetDlgItem(hwndDlg, IDC_HOTKEY), HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW);
+ RedrawWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), nullptr, nullptr, RDW_INVALIDATE);
+
+ SetFocus(GetDlgItem(hwndDlg, IDC_HOTKEY));
+ RedrawWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), nullptr, nullptr, RDW_INVALIDATE);
+ }
+}
+
+static void sttOptionsDrawTextChunk(HDC hdc, wchar_t *text, RECT *rc)
+{
+ DrawText(hdc, text, -1, rc, DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER | DT_WORD_ELLIPSIS);
+
+ SIZE sz;
+ GetTextExtentPoint32(hdc, text, (int)mir_wstrlen(text), &sz);
+ rc->left += sz.cx;
+}
+
+static INT_PTR CALLBACK sttOptionsDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ static bool initialized = false;
+ static int colWidth = 0;
+ static uint16_t currentLanguage = 0;
+
+ HWND hwndHotkey = GetDlgItem(hwndDlg, IDC_LV_HOTKEYS);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ initialized = false;
+
+ TranslateDialogDefault(hwndDlg);
+
+ Hotkey_Subclass(GetDlgItem(hwndDlg, IDC_HOTKEY));
+ {
+ HIMAGELIST hIml = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 3, 1);
+ ImageList_AddSkinIcon(hIml, SKINICON_OTHER_WINDOWS);
+ ImageList_AddSkinIcon(hIml, SKINICON_OTHER_MIRANDA);
+ ImageList_AddSkinIcon(hIml, SKINICON_OTHER_WINDOW);
+ ImageList_AddSkinIcon(hIml, SKINICON_OTHER_ADDCONTACT);
+ ImageList_AddSkinIcon(hIml, SKINICON_OTHER_DELETE);
+ ImageList_AddSkinIcon(hIml, SKINICON_OTHER_UNDO);
+ ImageList_AddSkinIcon(hIml, SKINICON_OTHER_GROUPOPEN);
+ ImageList_AddSkinIcon(hIml, SKINICON_OTHER_GROUPSHUT);
+ ListView_SetImageList(hwndHotkey, hIml, LVSIL_SMALL);
+ }
+ ListView_SetExtendedListViewStyle(hwndHotkey, LVS_EX_CHECKBOXES | LVS_EX_SUBITEMIMAGES | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP);
+ {
+ RECT rc;
+ GetClientRect(hwndHotkey, &rc);
+ colWidth = rc.right - GetSystemMetrics(SM_CXHTHUMB) - 3 * g_iIconSX - 5;
+
+ LVCOLUMN lvc;
+ lvc.mask = LVCF_WIDTH;
+ lvc.cx = colWidth * 2 / 3;
+ ListView_InsertColumn(hwndHotkey, COL_NAME, &lvc);
+ lvc.cx = g_iIconSX;
+ ListView_InsertColumn(hwndHotkey, COL_TYPE, &lvc);
+ lvc.cx = colWidth / 3;
+ ListView_InsertColumn(hwndHotkey, COL_KEY, &lvc);
+ lvc.cx = g_iIconSX;
+ ListView_InsertColumn(hwndHotkey, COL_RESET, &lvc);
+ lvc.cx = g_iIconSX;
+ ListView_InsertColumn(hwndHotkey, COL_ADDREMOVE, &lvc);
+
+ for (auto &it : hotkeys) {
+ it->OptChanged = false;
+ it->OptDeleted = it->OptNew = false;
+ it->OptEnabled = it->Enabled;
+ it->OptHotkey = it->Hotkey;
+ it->OptType = it->type;
+ }
+
+ currentLanguage = LOWORD(GetKeyboardLayout(0));
+ sttBuildHotkeyList(hwndHotkey);
+ }
+ SetTimer(hwndDlg, 1024, 1000, nullptr);
+ initialized = TRUE;
+ {
+ /* load group states */
+ int count = ListView_GetItemCount(hwndHotkey);
+ wchar_t buf[128];
+ LVITEM lvi = {};
+ lvi.pszText = buf;
+ lvi.cchTextMax = _countof(buf);
+ for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) {
+ lvi.mask = LVIF_PARAM;
+ lvi.iSubItem = 0;
+ ListView_GetItem(hwndHotkey, &lvi);
+ if (lvi.lParam)
+ continue;
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iSubItem = 1;
+ ListView_GetItem(hwndHotkey, &lvi);
+
+ ListView_SetCheckState(hwndHotkey, lvi.iItem, db_get_b(0, DBMODULENAME "UI", _T2A(lvi.pszText), TRUE));
+ }
+ }
+
+ g_hwndHkOptions = hwndDlg;
+ break;
+
+ case WM_TIMER:
+ if (initialized) {
+ uint16_t newLanguage = LOWORD(GetKeyboardLayout(0));
+ if (newLanguage == currentLanguage)
+ break;
+
+ int count = ListView_GetItemCount(hwndHotkey);
+
+ LVITEM lvi = {};
+ lvi.mask = LVIF_PARAM;
+ for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) {
+ ListView_GetItem(hwndHotkey, &lvi);
+ if (lvi.lParam)
+ sttOptionsSetupItem(hwndHotkey, lvi.iItem, (THotkeyItem *)lvi.lParam);
+ }
+ currentLanguage = newLanguage;
+ }
+ break;
+
+ case WM_HOTKEYUNREGISTERED:
+ {
+ int count = ListView_GetItemCount(hwndHotkey);
+
+ LVITEM lvi = {};
+ lvi.mask = LVIF_PARAM;
+ for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) {
+ ListView_GetItem(hwndHotkey, &lvi);
+ if (!lvi.lParam) continue;
+
+ if (((THotkeyItem *)lvi.lParam)->UnregisterHotkey) {
+ ListView_DeleteItem(hwndHotkey, lvi.iItem);
+ --lvi.iItem;
+ --count;
+ }
+ }
+ }
+ break;
+
+ case WM_DRAWITEM:
+ {
+ LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
+ RECT rc = lpdis->rcItem;
+ int prefix = 65;
+ int width = (lpdis->rcItem.right - lpdis->rcItem.left - prefix) / 3;
+ rc.left += 5;
+
+ HIMAGELIST hIml = ListView_GetImageList(hwndHotkey, LVSIL_SMALL);
+ if (lpdis->CtlID == IDC_CANVAS2) {
+ sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Scope:"), &rc);
+
+ rc.left = prefix;
+ ImageList_Draw(hIml, 0, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT);
+ rc.left += 20;
+ sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("System"), &rc);
+
+ rc.left = prefix + width;
+ ImageList_Draw(hIml, 1, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT);
+ rc.left += 20;
+ sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Miranda"), &rc);
+
+ rc.left = prefix + width * 2;
+ ImageList_Draw(hIml, 2, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT);
+ rc.left += 20;
+ sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Window"), &rc);
+ return TRUE;
+ }
+
+ if (lpdis->CtlID == IDC_CANVAS) {
+ sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Actions:"), &rc);
+
+ rc.left = prefix;
+ ImageList_Draw(hIml, 5, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT);
+ rc.left += 20;
+ sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Undo"), &rc);
+
+ rc.left = prefix + width * 1;
+ ImageList_Draw(hIml, 3, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT);
+ rc.left += 20;
+ sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Add binding"), &rc);
+
+ rc.left = prefix + width * 2;
+ ImageList_Draw(hIml, 4, lpdis->hDC, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT);
+ rc.left += 20;
+ sttOptionsDrawTextChunk(lpdis->hDC, TranslateT("Remove"), &rc);
+ return TRUE;
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ if ((LOWORD(wParam) == IDC_HOTKEY) && ((HIWORD(wParam) == EN_KILLFOCUS) || (HIWORD(wParam) == 0))) {
+ LVITEM lvi;
+ THotkeyItem *item;
+ uint16_t wHotkey = (uint16_t)SendDlgItemMessage(hwndDlg, IDC_HOTKEY, HKM_GETHOTKEY, 0, 0);
+
+ ShowWindow(GetDlgItem(hwndDlg, IDC_HOTKEY), SW_HIDE);
+ SetFocus(hwndHotkey);
+ if (!wHotkey || (wHotkey == VK_ESCAPE) || (HIWORD(wParam) != 0))
+ break;
+
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED);
+ if (lvi.iItem >= 0) {
+ ListView_GetItem(hwndHotkey, &lvi);
+ if (item = (THotkeyItem *)lvi.lParam) {
+ item->OptHotkey = wHotkey;
+
+ sttOptionsSetupItem(hwndHotkey, lvi.iItem, item);
+ sttOptionsSetChanged(item);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ }
+ }
+ break;
+
+ case WM_CONTEXTMENU:
+ if (GetWindowLongPtr((HWND)wParam, GWL_ID) == IDC_LV_HOTKEYS) {
+ HWND hwndList = (HWND)wParam;
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ LVITEM lvi = {};
+ THotkeyItem *item = nullptr;
+
+ lvi.iItem = ListView_GetNextItem(hwndHotkey, -1, LVNI_SELECTED);
+ if (lvi.iItem < 0)
+ return FALSE;
+
+ lvi.mask = LVIF_PARAM;
+ ListView_GetItem(hwndList, &lvi);
+ if (!(item = (THotkeyItem *)lvi.lParam))
+ return FALSE;
+
+ if (pt.x == -1 && pt.y == -1) {
+ RECT rc;
+ ListView_GetItemRect(hwndList, lvi.iItem, &rc, LVIR_LABEL);
+ pt.x = rc.left;
+ pt.y = rc.bottom;
+ ClientToScreen(hwndList, &pt);
+ }
+
+ enum { MI_CANCEL, MI_CHANGE, MI_SYSTEM, MI_LOCAL, MI_ADD, MI_REMOVE, MI_REVERT };
+
+ MENUITEMINFO mii = {};
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_STATE;
+ mii.fState = MFS_DEFAULT;
+
+ HMENU hMenu = CreatePopupMenu();
+ AppendMenu(hMenu, MF_STRING, MI_CHANGE, TranslateT("Modify"));
+ SetMenuItemInfo(hMenu, MI_CHANGE, FALSE, &mii);
+ if (item->type != HKT_MANUAL) {
+ AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
+ AppendMenu(hMenu, MF_STRING |
+ ((item->OptType == HKT_GLOBAL) ? MF_CHECKED : 0),
+ (UINT_PTR)MI_SYSTEM, TranslateT("System scope"));
+ AppendMenu(hMenu, MF_STRING |
+ ((item->OptType == HKT_LOCAL) ? MF_CHECKED : 0),
+ (UINT_PTR)MI_LOCAL, TranslateT("Miranda scope"));
+ }
+ AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
+ if (!item->rootHotkey)
+ AppendMenu(hMenu, MF_STRING, MI_ADD, TranslateT("Add binding"));
+ else
+ AppendMenu(hMenu, MF_STRING, MI_REMOVE, TranslateT("Remove"));
+ if (item->Hotkey != item->OptHotkey) {
+ AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
+ AppendMenu(hMenu, MF_STRING, MI_REVERT, TranslateT("Undo"));
+ }
+
+ switch (TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, nullptr)) {
+ case MI_CHANGE:
+ sttOptionsStartEdit(hwndDlg, hwndHotkey);
+ break;
+ case MI_SYSTEM:
+ item->OptType = HKT_GLOBAL;
+ sttOptionsSetupItem(hwndList, lvi.iItem, item);
+ sttOptionsSetChanged(item);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ case MI_LOCAL:
+ item->OptType = HKT_LOCAL;
+ sttOptionsSetupItem(hwndList, lvi.iItem, item);
+ sttOptionsSetChanged(item);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ case MI_ADD:
+ initialized = false;
+ sttOptionsAddHotkey(hwndList, item);
+ initialized = true;
+ break;
+ case MI_REMOVE:
+ sttOptionsDeleteHotkey(hwndList, lvi.iItem, item);
+ break;
+ case MI_REVERT:
+ item->OptHotkey = item->Hotkey;
+ sttOptionsSetupItem(hwndList, lvi.iItem, item);
+ break;
+ }
+ DestroyMenu(hMenu);
+ break;
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR lpnmhdr = (LPNMHDR)lParam;
+ switch (lpnmhdr->idFrom) {
+ case 0:
+ if ((lpnmhdr->code != PSN_APPLY) && (lpnmhdr->code != PSN_RESET))
+ break;
+
+ UnregisterHotkeys();
+
+ for (auto &p : hotkeys.rev_iter())
+ if (p->OptNew && p->OptDeleted || p->rootHotkey && !p->OptHotkey || (lpnmhdr->code == PSN_APPLY) && p->OptDeleted || (lpnmhdr->code == PSN_RESET) && p->OptNew)
+ FreeHotkey(hotkeys.removeItem(&p));
+
+ if (lpnmhdr->code == PSN_APPLY) {
+ LVITEM lvi = {};
+ int count = ListView_GetItemCount(hwndHotkey);
+
+ for (auto &it : hotkeys)
+ sttOptionsSaveItem(it);
+
+ lvi.mask = LVIF_IMAGE;
+ lvi.iSubItem = COL_RESET;
+ lvi.iImage = -1;
+ for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem)
+ ListView_SetItem(hwndHotkey, &lvi);
+ }
+
+ RegisterHotkeys();
+
+ NotifyEventHooks(hEvChanged, 0, 0);
+ break;
+
+ case IDC_LV_HOTKEYS:
+ switch (lpnmhdr->code) {
+ case NM_CLICK:
+ {
+ LPNMITEMACTIVATE lpnmia = (LPNMITEMACTIVATE)lParam;
+
+ LVITEM lvi = {};
+ lvi.mask = LVIF_PARAM | LVIF_IMAGE;
+ lvi.iItem = lpnmia->iItem;
+ ListView_GetItem(lpnmia->hdr.hwndFrom, &lvi);
+
+ auto *item = (THotkeyItem *)lvi.lParam;
+ if (item == nullptr)
+ break;
+
+ LVHITTESTINFO lvhti = {};
+ lvhti.pt = lpnmia->ptAction;
+ lvhti.iItem = lpnmia->iItem;
+ lvhti.iSubItem = lpnmia->iSubItem;
+ ListView_HitTest(lpnmia->hdr.hwndFrom, &lvhti);
+
+ if ((!item->rootHotkey && (lpnmia->iSubItem == COL_NAME) && ((lvhti.flags & LVHT_ONITEM) == LVHT_ONITEMICON) ||
+ item->rootHotkey && (lpnmia->iSubItem == COL_TYPE)) &&
+ ((item->OptType == HKT_GLOBAL) || (item->OptType == HKT_LOCAL))) {
+ item->OptType = (item->OptType == HKT_GLOBAL) ? HKT_LOCAL : HKT_GLOBAL;
+ sttOptionsSetupItem(lpnmia->hdr.hwndFrom, lpnmia->iItem, item);
+ sttOptionsSetChanged(item);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ else if (lpnmia->iSubItem == COL_RESET) {
+ item->OptHotkey = item->Hotkey;
+ sttOptionsSetupItem(lpnmia->hdr.hwndFrom, lpnmia->iItem, item);
+ }
+ else if (lpnmia->iSubItem == COL_ADDREMOVE) {
+ if (item->rootHotkey)
+ sttOptionsDeleteHotkey(lpnmia->hdr.hwndFrom, lpnmia->iItem, item);
+ else {
+ initialized = false;
+ sttOptionsAddHotkey(lpnmia->hdr.hwndFrom, item);
+ initialized = true;
+ }
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ }
+ break;
+
+ case LVN_KEYDOWN:
+ {
+ LPNMLVKEYDOWN param = (LPNMLVKEYDOWN)lParam;
+ if (param->wVKey == VK_SUBTRACT || param->wVKey == VK_LEFT || param->wVKey == VK_ADD || param->wVKey == VK_RIGHT) {
+ LVITEM lvi = {};
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = ListView_GetNextItem(lpnmhdr->hwndFrom, -1, LVNI_SELECTED);
+ if (lvi.iItem < 0)
+ break;
+
+ ListView_GetItem(lpnmhdr->hwndFrom, &lvi);
+ if (lvi.lParam)
+ break;
+
+ if (param->wVKey == VK_ADD || param->wVKey == VK_RIGHT) {
+ ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, TRUE);
+ }
+ else {
+ ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, FALSE);
+ }
+ }
+ else if (param->wVKey == VK_F2)
+ sttOptionsStartEdit(hwndDlg, hwndHotkey);
+ }
+ break;
+
+ case LVN_ITEMACTIVATE:
+ {
+ LVITEM lvi = {};
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = ListView_GetNextItem(lpnmhdr->hwndFrom, -1, LVNI_SELECTED);
+ if (lvi.iItem < 0) break;
+ ListView_GetItem(lpnmhdr->hwndFrom, &lvi);
+
+ if (lvi.lParam)
+ sttOptionsStartEdit(hwndDlg, hwndHotkey);
+ else
+ ListView_SetCheckState(lpnmhdr->hwndFrom, lvi.iItem, !ListView_GetCheckState(lpnmhdr->hwndFrom, lvi.iItem));
+ }
+ break;
+
+ case LVN_ITEMCHANGED:
+ if (initialized) {
+ LPNMLISTVIEW param = (LPNMLISTVIEW)lParam;
+ THotkeyItem *item = (THotkeyItem *)param->lParam;
+ if (param->uNewState >> 12 == param->uOldState >> 12)
+ break;
+
+ if (item && !item->rootHotkey) {
+ item->OptEnabled = ListView_GetCheckState(lpnmhdr->hwndFrom, param->iItem) ? 1 : 0;
+ sttOptionsSetChanged(item);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ else if (!item) {
+ wchar_t buf[256];
+ LVITEM lvi = {};
+ lvi.mask = LVIF_TEXT;
+ lvi.iItem = param->iItem;
+ lvi.pszText = buf;
+ lvi.cchTextMax = _countof(buf);
+ ListView_GetItem(lpnmhdr->hwndFrom, &lvi);
+
+ if (param->uNewState >> 12 == 1) {
+ int count = ListView_GetItemCount(lpnmhdr->hwndFrom);
+ LVITEM lvi2 = {};
+ lvi2.mask = LVIF_PARAM;
+ for (lvi2.iItem = 0; lvi2.iItem < count; ++lvi2.iItem) {
+ ListView_GetItem(lpnmhdr->hwndFrom, &lvi2);
+ item = (THotkeyItem *)lvi2.lParam;
+ if (!item) continue;
+ if (!mir_wstrcmp(item->getSection(), buf)) {
+ ListView_DeleteItem(lpnmhdr->hwndFrom, lvi2.iItem);
+ --lvi2.iItem;
+ --count;
+ }
+ }
+ }
+ else if (param->uNewState >> 12 == 2) {
+ int nItems = ListView_GetItemCount(lpnmhdr->hwndFrom);
+ initialized = false;
+ for (auto &it : hotkeys) {
+ LVITEM lvi2 = {};
+ if (it->OptDeleted || mir_wstrcmp(buf, it->getSection()))
+ continue;
+
+ lvi2.mask = LVIF_PARAM | LVIF_INDENT;
+ lvi2.iIndent = 1;
+ lvi2.iItem = nItems++;
+ lvi2.lParam = (LPARAM)it;
+ ListView_InsertItem(lpnmhdr->hwndFrom, &lvi2);
+ sttOptionsSetupItem(lpnmhdr->hwndFrom, nItems - 1, it);
+ }
+ ListView_SortItemsEx(lpnmhdr->hwndFrom, sttOptionsSortList, (LPARAM)lpnmhdr->hwndFrom);
+ initialized = TRUE;
+ }
+ }
+ }
+ break;
+
+ case NM_CUSTOMDRAW:
+ {
+ NMLVCUSTOMDRAW *param = (NMLVCUSTOMDRAW *)lParam;
+ switch (param->nmcd.dwDrawStage) {
+ case CDDS_PREPAINT:
+ case CDDS_ITEMPREPAINT:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_NOTIFYSUBITEMDRAW);
+ return TRUE;
+
+ case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
+ {
+ THotkeyItem *item;
+ wchar_t buf[256];
+ LVITEM lvi = {};
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iItem = param->nmcd.dwItemSpec;
+ lvi.pszText = buf;
+ lvi.cchTextMax = _countof(buf);
+ ListView_GetItem(lpnmhdr->hwndFrom, &lvi);
+
+ item = (THotkeyItem *)lvi.lParam;
+ if (!item) {
+ RECT rc;
+ HFONT hfnt;
+
+ ListView_GetSubItemRect(lpnmhdr->hwndFrom, param->nmcd.dwItemSpec, param->iSubItem, LVIR_BOUNDS, &rc);
+ FillRect(param->nmcd.hdc, &rc, GetSysColorBrush(param->nmcd.uItemState & CDIS_SELECTED ? COLOR_HIGHLIGHT : COLOR_WINDOW));
+ SetTextColor(param->nmcd.hdc, GetSysColor(param->nmcd.uItemState & CDIS_SELECTED ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
+
+ if (param->iSubItem == 0) {
+ rc.left += 3;
+ HIMAGELIST hIml = ListView_GetImageList(hwndHotkey, LVSIL_SMALL);
+ ImageList_Draw(hIml,
+ ListView_GetCheckState(hwndHotkey, lvi.iItem) ? 6 : 7,
+ param->nmcd.hdc, rc.left, (rc.top + rc.bottom - 16) / 2, ILD_TRANSPARENT);
+ rc.left += 18;
+ hfnt = (HFONT)SelectObject(param->nmcd.hdc, (HFONT)SendMessage(GetParent(hwndDlg), PSM_GETBOLDFONT, 0, 0));
+ DrawText(param->nmcd.hdc, buf, -1, &rc, DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
+ SelectObject(param->nmcd.hdc, hfnt);
+ }
+
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT);
+ return TRUE;
+ }
+
+ if (item->rootHotkey && (param->iSubItem == 0)) {
+ RECT rc;
+ ListView_GetSubItemRect(lpnmhdr->hwndFrom, param->nmcd.dwItemSpec, param->iSubItem, LVIR_BOUNDS, &rc);
+ FillRect(param->nmcd.hdc, &rc, GetSysColorBrush(COLOR_WINDOW));
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT);
+ return TRUE;
+ }
+ break;
+ }
+ }
+ break;
+ }
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ {
+ int count = ListView_GetItemCount(hwndHotkey);
+
+ g_hwndHkOptions = nullptr;
+
+ KillTimer(hwndDlg, 1024);
+
+ wchar_t buf[128];
+ LVITEM lvi = {};
+ lvi.pszText = buf;
+ lvi.cchTextMax = _countof(buf);
+ for (lvi.iItem = 0; lvi.iItem < count; ++lvi.iItem) {
+ lvi.mask = LVIF_PARAM;
+ lvi.iSubItem = 0;
+ ListView_GetItem(hwndHotkey, &lvi);
+ if (lvi.lParam) continue;
+
+ lvi.mask = LVIF_TEXT;
+ lvi.iSubItem = 1;
+ ListView_GetItem(hwndHotkey, &lvi);
+
+ db_set_b(0, DBMODULENAME "UI", _T2A(lvi.pszText), ListView_GetCheckState(hwndHotkey, lvi.iItem));
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+int HotkeyOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.position = -180000000;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_HOTKEYS);
+ odp.szTitle.a = LPGEN("Hotkeys");
+ odp.szGroup.a = LPGEN("Customize");
+ odp.pfnDlgProc = sttOptionsDlgProc;
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/hotkeys.cpp b/src/mir_app/src/hotkeys.cpp index 336bf3602e..a60cf84fe8 100644 --- a/src/mir_app/src/hotkeys.cpp +++ b/src/mir_app/src/hotkeys.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/icolib.cpp b/src/mir_app/src/icolib.cpp index aaccc00fd1..3327151b41 100644 --- a/src/mir_app/src/icolib.cpp +++ b/src/mir_app/src/icolib.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/idle.cpp b/src/mir_app/src/idle.cpp index cb1c771f11..fdf0f7b49b 100644 --- a/src/mir_app/src/idle.cpp +++ b/src/mir_app/src/idle.cpp @@ -1,87 +1,87 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#define IDLE_MODULE "Idle" - -static bool bModuleInitialized = false; - -static int g_idleType; -static int g_bIsIdle; - -static HANDLE hIdleEvent; - -MIR_APP_DLL(void) Idle_Enter(int type) -{ - int flags = 0; - - if (db_get_b(0, IDLE_MODULE, "IdlePrivate")) - flags |= IDF_PRIVACY; - - if (!g_bIsIdle && type != -1) { - g_bIsIdle = true; - g_idleType = type; - NotifyEventHooks(hIdleEvent, 0, IDF_ISIDLE | flags); - } - - if (g_bIsIdle && type == -1) { - g_bIsIdle = false; - g_idleType = 0; - NotifyEventHooks(hIdleEvent, 0, flags); - } -} - -MIR_APP_DLL(void) Idle_GetInfo(MIRANDA_IDLE_INFO &pInfo) -{ - pInfo.idleTime = db_get_dw(0, IDLE_MODULE, "IdleTime1st"); - pInfo.privacy = db_get_b(0, IDLE_MODULE, "IdlePrivate"); - pInfo.aaStatus = db_get_b(0, IDLE_MODULE, "AAEnable", 1) ? db_get_dw(0, IDLE_MODULE, "AAStatus") : 0; - pInfo.aaLock = db_get_b(0, IDLE_MODULE, "IdleStatusLock"); - pInfo.idlesoundsoff = db_get_b(0, IDLE_MODULE, "IdleSoundsOff"); - pInfo.idleType = g_idleType; -} - -int LoadIdleModule(void) -{ - bModuleInitialized = true; - - hIdleEvent = CreateHookableEvent(ME_IDLE_CHANGED); - - g_idleType = g_bIsIdle = 0; - return 0; -} - -void UnloadIdleModule() -{ - if (!bModuleInitialized) return; - - if (g_bIsIdle) { - NotifyEventHooks(hIdleEvent, 0, 0); - g_bIsIdle = false; - } - - DestroyHookableEvent(hIdleEvent); - hIdleEvent = nullptr; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#define IDLE_MODULE "Idle"
+
+static bool bModuleInitialized = false;
+
+static int g_idleType;
+static int g_bIsIdle;
+
+static HANDLE hIdleEvent;
+
+MIR_APP_DLL(void) Idle_Enter(int type)
+{
+ int flags = 0;
+
+ if (db_get_b(0, IDLE_MODULE, "IdlePrivate"))
+ flags |= IDF_PRIVACY;
+
+ if (!g_bIsIdle && type != -1) {
+ g_bIsIdle = true;
+ g_idleType = type;
+ NotifyEventHooks(hIdleEvent, 0, IDF_ISIDLE | flags);
+ }
+
+ if (g_bIsIdle && type == -1) {
+ g_bIsIdle = false;
+ g_idleType = 0;
+ NotifyEventHooks(hIdleEvent, 0, flags);
+ }
+}
+
+MIR_APP_DLL(void) Idle_GetInfo(MIRANDA_IDLE_INFO &pInfo)
+{
+ pInfo.idleTime = db_get_dw(0, IDLE_MODULE, "IdleTime1st");
+ pInfo.privacy = db_get_b(0, IDLE_MODULE, "IdlePrivate");
+ pInfo.aaStatus = db_get_b(0, IDLE_MODULE, "AAEnable", 1) ? db_get_dw(0, IDLE_MODULE, "AAStatus") : 0;
+ pInfo.aaLock = db_get_b(0, IDLE_MODULE, "IdleStatusLock");
+ pInfo.idlesoundsoff = db_get_b(0, IDLE_MODULE, "IdleSoundsOff");
+ pInfo.idleType = g_idleType;
+}
+
+int LoadIdleModule(void)
+{
+ bModuleInitialized = true;
+
+ hIdleEvent = CreateHookableEvent(ME_IDLE_CHANGED);
+
+ g_idleType = g_bIsIdle = 0;
+ return 0;
+}
+
+void UnloadIdleModule()
+{
+ if (!bModuleInitialized) return;
+
+ if (g_bIsIdle) {
+ NotifyEventHooks(hIdleEvent, 0, 0);
+ g_bIsIdle = false;
+ }
+
+ DestroyHookableEvent(hIdleEvent);
+ hIdleEvent = nullptr;
+}
diff --git a/src/mir_app/src/ignore.cpp b/src/mir_app/src/ignore.cpp index 4e5eb804cf..df84141dfc 100644 --- a/src/mir_app/src/ignore.cpp +++ b/src/mir_app/src/ignore.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/keyboard.cpp b/src/mir_app/src/keyboard.cpp index dd5643ae7e..87929e756c 100644 --- a/src/mir_app/src/keyboard.cpp +++ b/src/mir_app/src/keyboard.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/langpack.h b/src/mir_app/src/langpack.h index 04a5abd7d1..1683e3e0ac 100644 --- a/src/mir_app/src/langpack.h +++ b/src/mir_app/src/langpack.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/lpopts.cpp b/src/mir_app/src/lpopts.cpp index 3675694ff9..7444c8e1dc 100644 --- a/src/mir_app/src/lpopts.cpp +++ b/src/mir_app/src/lpopts.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/mdatabasecache.cpp b/src/mir_app/src/mdatabasecache.cpp index 29cc99cfb5..01ef939bd0 100644 --- a/src/mir_app/src/mdatabasecache.cpp +++ b/src/mir_app/src/mdatabasecache.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team,
+Copyright (C) 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/menu_clist.cpp b/src/mir_app/src/menu_clist.cpp index 0a545db4a0..bf94b21017 100644 --- a/src/mir_app/src/menu_clist.cpp +++ b/src/mir_app/src/menu_clist.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/menu_frames.cpp b/src/mir_app/src/menu_frames.cpp index 323cd0c5b0..1b64b9bd0b 100644 --- a/src/mir_app/src/menu_frames.cpp +++ b/src/mir_app/src/menu_frames.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/menu_groups.cpp b/src/mir_app/src/menu_groups.cpp index 4b12e7a0f8..3460d68b1d 100644 --- a/src/mir_app/src/menu_groups.cpp +++ b/src/mir_app/src/menu_groups.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-08 Miranda ICQ/IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/menu_options.cpp b/src/mir_app/src/menu_options.cpp index 648e3bbc4e..2f5456918b 100644 --- a/src/mir_app/src/menu_options.cpp +++ b/src/mir_app/src/menu_options.cpp @@ -1,588 +1,588 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "genmenu.h" -#include "plugins.h" - -#define STR_SEPARATOR L"-----------------------------------" - -extern bool bIconsDisabled; -extern int DefaultImageListColorDepth; -void RebuildProtoMenus(); - -MIR_APP_DLL(void) Menu_SetVisible(TMO_IntMenuItem *pimi, bool bVisible) -{ - if ((pimi = MO_GetIntMenuItem(pimi)) == nullptr) - return; - - char szModule[256], menuItemName[256]; - mir_snprintf(szModule, "%s_Items", pimi->parent->pszName); - bin2hex(&pimi->mi.uid, sizeof(pimi->mi.uid), menuItemName); - - ptrW wszValue(db_get_wsa(0, szModule, menuItemName, L"1;;;")); - wszValue[0] = bVisible ? '1' : '0'; - db_set_ws(0, szModule, menuItemName, wszValue); - - Menu_ShowItem(pimi, bVisible); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -struct MenuItemOptData : public MZeroedObject -{ - ~MenuItemOptData() {} - - int pos; - - ptrW name; - ptrW defname; - - bool bShow; - int id; - - TMO_IntMenuItem *pimi; -}; - -static int SortMenuItems(const MenuItemOptData *p1, const MenuItemOptData *p2) -{ - if (p1->pos < p2->pos) return -1; - if (p1->pos > p2->pos) return 1; - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -class CGenMenuOptionsPage : public CDlgBase -{ - int iInitMenuValue; - LIST<TMO_IntMenuItem> m_arDeleted; - - wchar_t idstr[100]; - - void SaveTreeInternal(MenuItemOptData *pParent, HTREEITEM hRootItem, const char *szModule) - { - TVITEMEX tvi; - tvi.hItem = hRootItem; - tvi.cchTextMax = _countof(idstr); - tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_HANDLE | TVIF_IMAGE; - tvi.pszText = idstr; - - int count = 0, customOrder = 0; - int runtimepos = 100; - - char pszParent[33]; - if (pParent == nullptr) - pszParent[0] = 0; - else - bin2hex(&pParent->pimi->mi.uid, sizeof(MUUID), pszParent); - - while (tvi.hItem != nullptr) { - m_menuItems.GetItem(&tvi); - auto *iod = (MenuItemOptData*)tvi.lParam; - if (TMO_IntMenuItem *pimi = iod->pimi) { - if (pimi->mi.uid != miid_last) { - char menuItemName[256]; - bin2hex(&pimi->mi.uid, sizeof(pimi->mi.uid), menuItemName); - - int visible = tvi.iImage != 0; - wchar_t *ptszCustomName; - if (iod->name != nullptr && iod->defname != nullptr && mir_wstrcmp(iod->name, iod->defname) != 0) - ptszCustomName = iod->name; - else - ptszCustomName = L""; - - CMStringW tszValue(FORMAT, L"%d;%d;%S;%s", visible, runtimepos, pszParent, ptszCustomName); - db_set_ws(0, szModule, menuItemName, tszValue); - - if (pimi->mi.flags & CMIF_CUSTOM) - db_set_s(0, szModule, CMStringA(FORMAT, "Custom%d", customOrder++), menuItemName); - } - - HTREEITEM hChild = m_menuItems.GetChild(tvi.hItem); - if (hChild != nullptr) - SaveTreeInternal(iod, hChild, szModule); - - runtimepos += 100; - } - - if (iod->name && !mir_wstrcmp(iod->name, STR_SEPARATOR) && tvi.iImage) - runtimepos += SEPARATORPOSITIONINTERVAL; - - tvi.hItem = m_menuItems.GetNextSibling(tvi.hItem); - count++; - } - } - - void SaveTree() - { - int MenuObjectId; - if (!GetCurrentMenuObjectID(MenuObjectId)) - return; - - TIntMenuObject *pmo = GetMenuObjbyId(MenuObjectId); - if (pmo == nullptr) - return; - - char szModule[256]; - mir_snprintf(szModule, "%s_Items", pmo->pszName); - db_delete_module(0, szModule); - SaveTreeInternal(nullptr, m_menuItems.GetRoot(), szModule); - db_set_b(0, szModule, "MenuFormat", 1); - } - - void FreeTreeData() - { - HTREEITEM hItem = m_menuItems.GetRoot(); - while (hItem != nullptr) { - TVITEMEX tvi; - tvi.mask = TVIF_HANDLE | TVIF_PARAM; - tvi.hItem = hItem; - m_menuItems.GetItem(&tvi); - delete (MenuItemOptData *)tvi.lParam; - - tvi.lParam = 0; - m_menuItems.SetItem(&tvi); - - hItem = m_menuItems.GetNextSibling(hItem); - } - } - - void RebuildCurrent() - { - int MenuObjectID; - if (GetCurrentMenuObjectID(MenuObjectID)) - BuildTree(MenuObjectID, true); - } - - void BuildTreeInternal(const char *pszModule, bool bReread, TMO_IntMenuItem *pFirst, HTREEITEM hRoot) - { - LIST<MenuItemOptData> arItems(10, SortMenuItems); - - for (TMO_IntMenuItem *p = pFirst; p != nullptr; p = p->next) { - // filter out items whose presence & position might not be changed - if (p->mi.flags & CMIF_SYSTEM) - continue; - - MenuItemOptData *PD = new MenuItemOptData(); - PD->pimi = p; - PD->defname = mir_wstrdup(GetMenuItemText(p)); - PD->name = mir_wstrdup((bReread && p->ptszCustomName != nullptr) ? p->ptszCustomName : PD->defname); - PD->bShow = (p->mi.flags & CMIF_HIDDEN) == 0; - PD->pos = (bReread) ? p->mi.position : p->originalPosition; - PD->id = p->iCommand; - arItems.insert(PD); - } - - int lastpos = 0; - bool bIsFirst = TRUE; - - TVINSERTSTRUCT tvis; - tvis.hParent = hRoot; - tvis.hInsertAfter = TVI_LAST; - tvis.item.mask = TVIF_PARAM | TVIF_CHILDREN | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; - - for (auto &it : arItems) { - if (it != arItems[0] && it->pos - lastpos >= SEPARATORPOSITIONINTERVAL) { - MenuItemOptData *sep = new MenuItemOptData(); - sep->id = -1; - sep->name = mir_wstrdup(STR_SEPARATOR); - sep->pos = it->pos - 1; - - tvis.item.lParam = (LPARAM)sep; - tvis.item.pszText = sep->name; - tvis.item.iImage = tvis.item.iSelectedImage = 1; - tvis.item.cChildren = 0; - m_menuItems.InsertItem(&tvis); - } - - tvis.item.lParam = (LPARAM)it; - tvis.item.pszText = it->name; - tvis.item.iImage = tvis.item.iSelectedImage = it->bShow; - tvis.item.cChildren = it->pimi->submenu.first != nullptr; - - HTREEITEM hti = m_menuItems.InsertItem(&tvis); - if (bIsFirst) { - if (hRoot == nullptr) - m_menuItems.SelectItem(hti); - bIsFirst = false; - } - - if (it->pimi->submenu.first != nullptr) { - BuildTreeInternal(pszModule, bReread, it->pimi->submenu.first, hti); - m_menuItems.Expand(hti, TVE_EXPAND); - } - - lastpos = it->pos; - } - } - - bool BuildTree(int MenuObjectId, bool bReread) - { - FreeTreeData(); - - TIntMenuObject *pmo = GetMenuObjbyId(MenuObjectId); - if (pmo == nullptr || pmo->m_items.first == nullptr) - return false; - - char szModule[256]; - mir_snprintf(szModule, "%s_Items", pmo->pszName); - - if (bReread) // no need to reread database on reset - MO_RecursiveWalkMenu(pmo->m_items.first, Menu_LoadFromDatabase, szModule); - - m_menuItems.SetDraw(false); - m_menuItems.DeleteAllItems(); - - BuildTreeInternal(szModule, bReread, pmo->m_items.first, nullptr); - - m_menuItems.SetDraw(true); - - m_warning.Show(!pmo->m_bUseUserDefinedItems); - m_menuItems.Enable(pmo->m_bUseUserDefinedItems); - m_btnInsSeparator.Enable(pmo->m_bUseUserDefinedItems); - m_btnInsMenu.Enable(pmo->m_bUseUserDefinedItems); - return 1; - } - - bool GetCurrentMenuObjectID(int &result) - { - int iItem = m_menuObjects.GetCurSel(); - if (iItem == -1) - return false; - - result = (int)m_menuObjects.GetItemData(iItem); - return true; - } - - CCtrlListBox m_menuObjects; - CCtrlTreeView m_menuItems; - CCtrlCheck m_radio1, m_radio2, m_enableIcons; - CCtrlEdit m_customName, m_service, m_module; - CCtrlButton m_btnInsSeparator, m_btnInsMenu, m_btnReset, m_btnSet, m_btnDefault, m_btnDelete; - CCtrlBase m_warning; - -public: - CGenMenuOptionsPage() : - CDlgBase(g_plugin, IDD_OPT_GENMENU), - m_arDeleted(1), - m_menuItems(this, IDC_MENUITEMS), - m_menuObjects(this, IDC_MENUOBJECTS), - m_radio1(this, IDC_RADIO1), - m_radio2(this, IDC_RADIO2), - m_enableIcons(this, IDC_DISABLEMENUICONS), - m_btnInsSeparator(this, IDC_INSERTSEPARATOR), - m_btnInsMenu(this, IDC_INSERTSUBMENU), - m_btnReset(this, IDC_RESETMENU), - m_btnSet(this, IDC_GENMENU_SET), - m_btnDelete(this, IDC_GENMENU_DELETE), - m_btnDefault(this, IDC_GENMENU_DEFAULT), - m_customName(this, IDC_GENMENU_CUSTOMNAME), - m_service(this, IDC_GENMENU_SERVICE), - m_module(this, IDC_GENMENU_MODULE), - m_warning(this, IDC_NOTSUPPORTWARNING) - { - m_btnSet.OnClick = Callback(this, &CGenMenuOptionsPage::btnSet_Clicked); - m_btnReset.OnClick = Callback(this, &CGenMenuOptionsPage::btnReset_Clicked); - m_btnInsSeparator.OnClick = Callback(this, &CGenMenuOptionsPage::btnInsSep_Clicked); - m_btnInsMenu.OnClick = Callback(this, &CGenMenuOptionsPage::btnInsMenu_Clicked); - m_btnDefault.OnClick = Callback(this, &CGenMenuOptionsPage::btnDefault_Clicked); - m_btnDelete.OnClick = Callback(this, &CGenMenuOptionsPage::btnDelete_Clicked); - - m_menuObjects.OnSelChange = Callback(this, &CGenMenuOptionsPage::onMenuObjectChanged); - - m_menuItems.SetFlags(MTREE_CHECKBOX | MTREE_DND); - m_menuItems.OnSelChanged = Callback(this, &CGenMenuOptionsPage::onMenuItemChanged); - m_menuItems.OnBeginDrag = Callback(this, &CGenMenuOptionsPage::onMenuItemBeginDrag); - - m_customName.SetSilent(); - m_service.SetSilent(); - m_module.SetSilent(); - } - - //---- init dialog ------------------------------------------- - bool OnInitDialog() override - { - iInitMenuValue = db_get_b(0, "CList", "MoveProtoMenus", TRUE); - - if (iInitMenuValue) - m_radio2.SetState(true); - else - m_radio1.SetState(true); - - m_enableIcons.SetState(!bIconsDisabled); - - //---- init menu object list -------------------------------------- - for (auto &p : g_menus) - if (p->id != (int)hStatusMenuObject && p->m_bUseUserDefinedItems) - m_menuObjects.AddString(TranslateW(p->ptszDisplayName), p->id); - - m_menuObjects.SetCurSel(0); - RebuildCurrent(); - return true; - } - - bool OnApply() override - { - bIconsDisabled = m_enableIcons.GetState() == 0; - db_set_b(0, "CList", "DisableMenuIcons", bIconsDisabled); - SaveTree(); - - for (auto &pimi : m_arDeleted) - Menu_RemoveItem(pimi); - - int iNewMenuValue = !m_radio1.GetState(); - if (iNewMenuValue != iInitMenuValue) { - db_set_b(0, "CList", "MoveProtoMenus", iNewMenuValue); - - RebuildProtoMenus(); - iInitMenuValue = iNewMenuValue; - } - RebuildCurrent(); - return true; - } - - void OnDestroy() override - { - FreeTreeData(); - } - - void btnInsSep_Clicked(CCtrlButton*) - { - HTREEITEM hti = m_menuItems.GetSelection(); - if (hti == nullptr) - return; - - TVITEMEX tvi = { 0 }; - tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT; - tvi.hItem = hti; - if (!m_menuItems.GetItem(&tvi)) - return; - - MenuItemOptData *PD = new MenuItemOptData(); - PD->id = -1; - PD->name = mir_wstrdup(STR_SEPARATOR); - PD->pos = ((MenuItemOptData *)tvi.lParam)->pos - 1; - - TVINSERTSTRUCT tvis = {}; - tvis.item.lParam = (LPARAM)PD; - tvis.item.pszText = PD->name; - tvis.item.iImage = tvis.item.iSelectedImage = 1; - tvis.hInsertAfter = hti; - tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; - m_menuItems.InsertItem(&tvis); - - NotifyChange(); - } - - void btnInsMenu_Clicked(CCtrlButton*) - { - HTREEITEM hti = m_menuItems.GetSelection(); - if (hti == nullptr) - return; - - TVITEMEX tvi = { 0 }; - tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT; - tvi.hItem = hti; - if (!m_menuItems.GetItem(&tvi)) - return; - - MenuItemOptData *curData = (MenuItemOptData*)tvi.lParam; - - TMO_MenuItem mi = {}; - UuidCreate((UUID*)&mi.uid); - mi.flags = CMIF_CUSTOM; - mi.name.a = LPGEN("New submenu"); - mi.position = curData->pos - 1; - TMO_IntMenuItem *pimi = Menu_AddItem(curData->pimi->parent->id, &mi, nullptr); - - MenuItemOptData *PD = new MenuItemOptData(); - PD->id = -1; - PD->name = mir_wstrdup(pimi->mi.name.w); - PD->pos = pimi->mi.position; - PD->pimi = pimi; - - TVINSERTSTRUCT tvis = {}; - tvis.item.lParam = (LPARAM)PD; - tvis.item.pszText = PD->name; - tvis.item.iImage = tvis.item.iSelectedImage = 1; - tvis.hInsertAfter = hti; - tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; - m_menuItems.InsertItem(&tvis); - - NotifyChange(); - } - - void btnReset_Clicked(CCtrlButton*) - { - int MenuObjectID; - if (GetCurrentMenuObjectID(MenuObjectID)) { - BuildTree(MenuObjectID, false); - NotifyChange(); - } - } - - void btnDefault_Clicked(CCtrlButton*) - { - HTREEITEM hti = m_menuItems.GetSelection(); - if (hti == nullptr) - return; - - TVITEMEX tvi; - tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; - tvi.hItem = hti; - m_menuItems.GetItem(&tvi); - - MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam; - if (iod->name && wcsstr(iod->name, STR_SEPARATOR)) - return; - - iod->name = mir_wstrdup(iod->defname); - m_customName.SetText(iod->defname); - - tvi.mask = TVIF_TEXT; - tvi.pszText = iod->name; - m_menuItems.SetItem(&tvi); - NotifyChange(); - } - - void btnSet_Clicked(CCtrlButton*) - { - HTREEITEM hti = m_menuItems.GetSelection(); - if (hti == nullptr) - return; - - TVITEMEX tvi; - tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; - tvi.hItem = hti; - m_menuItems.GetItem(&tvi); - - MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam; - if (iod->name && wcsstr(iod->name, STR_SEPARATOR)) - return; - - iod->name = m_customName.GetText(); - - tvi.mask = TVIF_TEXT; - tvi.pszText = iod->name; - m_menuItems.SetItem(&tvi); - NotifyChange(); - } - - void btnDelete_Clicked(CCtrlButton *) - { - HTREEITEM hti = m_menuItems.GetSelection(); - if (hti == nullptr) - return; - - TVITEMEX tvi; - tvi.mask = TVIF_PARAM; - tvi.hItem = hti; - m_menuItems.GetItem(&tvi); - - MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam; - if (!(iod->pimi->mi.flags & CMIF_CUSTOM)) - return; - - if (IDYES == MessageBoxW(m_hwnd, TranslateT("Do you really want to delete this menu item?"), TranslateT("Miranda"), MB_YESNO | MB_ICONQUESTION)) { - m_arDeleted.insert(iod->pimi); - m_menuItems.DeleteItem(hti); - delete iod; - NotifyChange(); - } - } - - void onMenuObjectChanged(void*) - { - m_bInitialized = false; - RebuildCurrent(); - m_bInitialized = true; - } - - void onMenuItemChanged(void*) - { - m_customName.SetTextA(""); - m_service.SetTextA(""); - m_module.SetTextA(""); - - m_btnInsMenu.Disable(); - m_btnDefault.Disable(); - m_btnSet.Disable(); - m_btnDelete.Disable(); - m_customName.Disable(); - - HTREEITEM hti = m_menuItems.GetSelection(); - if (hti == nullptr) - return; - - TVITEMEX tvi; - tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; - tvi.hItem = hti; - m_menuItems.GetItem(&tvi); - if (tvi.lParam == 0) - return; - - MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam; - if (iod->name && wcsstr(iod->name, STR_SEPARATOR)) - return; - - m_customName.SetText(iod->name); - - if (iod->pimi->mi.uid != miid_last) { - char szText[100]; - bin2hex(&iod->pimi->mi.uid, sizeof(iod->pimi->mi.uid), szText); - m_service.SetTextA(szText); - } - - const CMPluginBase *pPlugin = iod->pimi->mi.pPlugin; - m_module.SetTextA(pPlugin == nullptr ? "" : pPlugin->getInfo().shortName); - - m_btnInsMenu.Enable(iod->pimi->mi.root == nullptr); - m_btnDefault.Enable(mir_wstrcmp(iod->name, iod->defname) != 0); - m_btnDelete.Enable(iod->pimi->mi.flags & CMIF_CUSTOM); - m_btnSet.Enable(true); - m_customName.Enable(true); - } - - void onMenuItemBeginDrag(CCtrlTreeView::TEventInfo *evt) - { - MenuItemOptData *p = (MenuItemOptData*)evt->nmtv->itemNew.lParam; - if (p->pimi != nullptr) - if (p->pimi->mi.flags & CMIF_UNMOVABLE) - evt->nmhdr->code = 0; // reject an attempt to change item's position - } -}; - -int GenMenuOptInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.position = -1000000000; - odp.szTitle.a = LPGEN("Menus"); - odp.szGroup.a = LPGEN("Customize"); - odp.flags = ODPF_BOLDGROUPS; - odp.pDialog = new CGenMenuOptionsPage(); - g_plugin.addOptions(wParam, &odp); - - return ProtocolOrderOptInit(wParam, 0); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "genmenu.h"
+#include "plugins.h"
+
+#define STR_SEPARATOR L"-----------------------------------"
+
+extern bool bIconsDisabled;
+extern int DefaultImageListColorDepth;
+void RebuildProtoMenus();
+
+MIR_APP_DLL(void) Menu_SetVisible(TMO_IntMenuItem *pimi, bool bVisible)
+{
+ if ((pimi = MO_GetIntMenuItem(pimi)) == nullptr)
+ return;
+
+ char szModule[256], menuItemName[256];
+ mir_snprintf(szModule, "%s_Items", pimi->parent->pszName);
+ bin2hex(&pimi->mi.uid, sizeof(pimi->mi.uid), menuItemName);
+
+ ptrW wszValue(db_get_wsa(0, szModule, menuItemName, L"1;;;"));
+ wszValue[0] = bVisible ? '1' : '0';
+ db_set_ws(0, szModule, menuItemName, wszValue);
+
+ Menu_ShowItem(pimi, bVisible);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct MenuItemOptData : public MZeroedObject
+{
+ ~MenuItemOptData() {}
+
+ int pos;
+
+ ptrW name;
+ ptrW defname;
+
+ bool bShow;
+ int id;
+
+ TMO_IntMenuItem *pimi;
+};
+
+static int SortMenuItems(const MenuItemOptData *p1, const MenuItemOptData *p2)
+{
+ if (p1->pos < p2->pos) return -1;
+ if (p1->pos > p2->pos) return 1;
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class CGenMenuOptionsPage : public CDlgBase
+{
+ int iInitMenuValue;
+ LIST<TMO_IntMenuItem> m_arDeleted;
+
+ wchar_t idstr[100];
+
+ void SaveTreeInternal(MenuItemOptData *pParent, HTREEITEM hRootItem, const char *szModule)
+ {
+ TVITEMEX tvi;
+ tvi.hItem = hRootItem;
+ tvi.cchTextMax = _countof(idstr);
+ tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_HANDLE | TVIF_IMAGE;
+ tvi.pszText = idstr;
+
+ int count = 0, customOrder = 0;
+ int runtimepos = 100;
+
+ char pszParent[33];
+ if (pParent == nullptr)
+ pszParent[0] = 0;
+ else
+ bin2hex(&pParent->pimi->mi.uid, sizeof(MUUID), pszParent);
+
+ while (tvi.hItem != nullptr) {
+ m_menuItems.GetItem(&tvi);
+ auto *iod = (MenuItemOptData*)tvi.lParam;
+ if (TMO_IntMenuItem *pimi = iod->pimi) {
+ if (pimi->mi.uid != miid_last) {
+ char menuItemName[256];
+ bin2hex(&pimi->mi.uid, sizeof(pimi->mi.uid), menuItemName);
+
+ int visible = tvi.iImage != 0;
+ wchar_t *ptszCustomName;
+ if (iod->name != nullptr && iod->defname != nullptr && mir_wstrcmp(iod->name, iod->defname) != 0)
+ ptszCustomName = iod->name;
+ else
+ ptszCustomName = L"";
+
+ CMStringW tszValue(FORMAT, L"%d;%d;%S;%s", visible, runtimepos, pszParent, ptszCustomName);
+ db_set_ws(0, szModule, menuItemName, tszValue);
+
+ if (pimi->mi.flags & CMIF_CUSTOM)
+ db_set_s(0, szModule, CMStringA(FORMAT, "Custom%d", customOrder++), menuItemName);
+ }
+
+ HTREEITEM hChild = m_menuItems.GetChild(tvi.hItem);
+ if (hChild != nullptr)
+ SaveTreeInternal(iod, hChild, szModule);
+
+ runtimepos += 100;
+ }
+
+ if (iod->name && !mir_wstrcmp(iod->name, STR_SEPARATOR) && tvi.iImage)
+ runtimepos += SEPARATORPOSITIONINTERVAL;
+
+ tvi.hItem = m_menuItems.GetNextSibling(tvi.hItem);
+ count++;
+ }
+ }
+
+ void SaveTree()
+ {
+ int MenuObjectId;
+ if (!GetCurrentMenuObjectID(MenuObjectId))
+ return;
+
+ TIntMenuObject *pmo = GetMenuObjbyId(MenuObjectId);
+ if (pmo == nullptr)
+ return;
+
+ char szModule[256];
+ mir_snprintf(szModule, "%s_Items", pmo->pszName);
+ db_delete_module(0, szModule);
+ SaveTreeInternal(nullptr, m_menuItems.GetRoot(), szModule);
+ db_set_b(0, szModule, "MenuFormat", 1);
+ }
+
+ void FreeTreeData()
+ {
+ HTREEITEM hItem = m_menuItems.GetRoot();
+ while (hItem != nullptr) {
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ m_menuItems.GetItem(&tvi);
+ delete (MenuItemOptData *)tvi.lParam;
+
+ tvi.lParam = 0;
+ m_menuItems.SetItem(&tvi);
+
+ hItem = m_menuItems.GetNextSibling(hItem);
+ }
+ }
+
+ void RebuildCurrent()
+ {
+ int MenuObjectID;
+ if (GetCurrentMenuObjectID(MenuObjectID))
+ BuildTree(MenuObjectID, true);
+ }
+
+ void BuildTreeInternal(const char *pszModule, bool bReread, TMO_IntMenuItem *pFirst, HTREEITEM hRoot)
+ {
+ LIST<MenuItemOptData> arItems(10, SortMenuItems);
+
+ for (TMO_IntMenuItem *p = pFirst; p != nullptr; p = p->next) {
+ // filter out items whose presence & position might not be changed
+ if (p->mi.flags & CMIF_SYSTEM)
+ continue;
+
+ MenuItemOptData *PD = new MenuItemOptData();
+ PD->pimi = p;
+ PD->defname = mir_wstrdup(GetMenuItemText(p));
+ PD->name = mir_wstrdup((bReread && p->ptszCustomName != nullptr) ? p->ptszCustomName : PD->defname);
+ PD->bShow = (p->mi.flags & CMIF_HIDDEN) == 0;
+ PD->pos = (bReread) ? p->mi.position : p->originalPosition;
+ PD->id = p->iCommand;
+ arItems.insert(PD);
+ }
+
+ int lastpos = 0;
+ bool bIsFirst = TRUE;
+
+ TVINSERTSTRUCT tvis;
+ tvis.hParent = hRoot;
+ tvis.hInsertAfter = TVI_LAST;
+ tvis.item.mask = TVIF_PARAM | TVIF_CHILDREN | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+
+ for (auto &it : arItems) {
+ if (it != arItems[0] && it->pos - lastpos >= SEPARATORPOSITIONINTERVAL) {
+ MenuItemOptData *sep = new MenuItemOptData();
+ sep->id = -1;
+ sep->name = mir_wstrdup(STR_SEPARATOR);
+ sep->pos = it->pos - 1;
+
+ tvis.item.lParam = (LPARAM)sep;
+ tvis.item.pszText = sep->name;
+ tvis.item.iImage = tvis.item.iSelectedImage = 1;
+ tvis.item.cChildren = 0;
+ m_menuItems.InsertItem(&tvis);
+ }
+
+ tvis.item.lParam = (LPARAM)it;
+ tvis.item.pszText = it->name;
+ tvis.item.iImage = tvis.item.iSelectedImage = it->bShow;
+ tvis.item.cChildren = it->pimi->submenu.first != nullptr;
+
+ HTREEITEM hti = m_menuItems.InsertItem(&tvis);
+ if (bIsFirst) {
+ if (hRoot == nullptr)
+ m_menuItems.SelectItem(hti);
+ bIsFirst = false;
+ }
+
+ if (it->pimi->submenu.first != nullptr) {
+ BuildTreeInternal(pszModule, bReread, it->pimi->submenu.first, hti);
+ m_menuItems.Expand(hti, TVE_EXPAND);
+ }
+
+ lastpos = it->pos;
+ }
+ }
+
+ bool BuildTree(int MenuObjectId, bool bReread)
+ {
+ FreeTreeData();
+
+ TIntMenuObject *pmo = GetMenuObjbyId(MenuObjectId);
+ if (pmo == nullptr || pmo->m_items.first == nullptr)
+ return false;
+
+ char szModule[256];
+ mir_snprintf(szModule, "%s_Items", pmo->pszName);
+
+ if (bReread) // no need to reread database on reset
+ MO_RecursiveWalkMenu(pmo->m_items.first, Menu_LoadFromDatabase, szModule);
+
+ m_menuItems.SetDraw(false);
+ m_menuItems.DeleteAllItems();
+
+ BuildTreeInternal(szModule, bReread, pmo->m_items.first, nullptr);
+
+ m_menuItems.SetDraw(true);
+
+ m_warning.Show(!pmo->m_bUseUserDefinedItems);
+ m_menuItems.Enable(pmo->m_bUseUserDefinedItems);
+ m_btnInsSeparator.Enable(pmo->m_bUseUserDefinedItems);
+ m_btnInsMenu.Enable(pmo->m_bUseUserDefinedItems);
+ return 1;
+ }
+
+ bool GetCurrentMenuObjectID(int &result)
+ {
+ int iItem = m_menuObjects.GetCurSel();
+ if (iItem == -1)
+ return false;
+
+ result = (int)m_menuObjects.GetItemData(iItem);
+ return true;
+ }
+
+ CCtrlListBox m_menuObjects;
+ CCtrlTreeView m_menuItems;
+ CCtrlCheck m_radio1, m_radio2, m_enableIcons;
+ CCtrlEdit m_customName, m_service, m_module;
+ CCtrlButton m_btnInsSeparator, m_btnInsMenu, m_btnReset, m_btnSet, m_btnDefault, m_btnDelete;
+ CCtrlBase m_warning;
+
+public:
+ CGenMenuOptionsPage() :
+ CDlgBase(g_plugin, IDD_OPT_GENMENU),
+ m_arDeleted(1),
+ m_menuItems(this, IDC_MENUITEMS),
+ m_menuObjects(this, IDC_MENUOBJECTS),
+ m_radio1(this, IDC_RADIO1),
+ m_radio2(this, IDC_RADIO2),
+ m_enableIcons(this, IDC_DISABLEMENUICONS),
+ m_btnInsSeparator(this, IDC_INSERTSEPARATOR),
+ m_btnInsMenu(this, IDC_INSERTSUBMENU),
+ m_btnReset(this, IDC_RESETMENU),
+ m_btnSet(this, IDC_GENMENU_SET),
+ m_btnDelete(this, IDC_GENMENU_DELETE),
+ m_btnDefault(this, IDC_GENMENU_DEFAULT),
+ m_customName(this, IDC_GENMENU_CUSTOMNAME),
+ m_service(this, IDC_GENMENU_SERVICE),
+ m_module(this, IDC_GENMENU_MODULE),
+ m_warning(this, IDC_NOTSUPPORTWARNING)
+ {
+ m_btnSet.OnClick = Callback(this, &CGenMenuOptionsPage::btnSet_Clicked);
+ m_btnReset.OnClick = Callback(this, &CGenMenuOptionsPage::btnReset_Clicked);
+ m_btnInsSeparator.OnClick = Callback(this, &CGenMenuOptionsPage::btnInsSep_Clicked);
+ m_btnInsMenu.OnClick = Callback(this, &CGenMenuOptionsPage::btnInsMenu_Clicked);
+ m_btnDefault.OnClick = Callback(this, &CGenMenuOptionsPage::btnDefault_Clicked);
+ m_btnDelete.OnClick = Callback(this, &CGenMenuOptionsPage::btnDelete_Clicked);
+
+ m_menuObjects.OnSelChange = Callback(this, &CGenMenuOptionsPage::onMenuObjectChanged);
+
+ m_menuItems.SetFlags(MTREE_CHECKBOX | MTREE_DND);
+ m_menuItems.OnSelChanged = Callback(this, &CGenMenuOptionsPage::onMenuItemChanged);
+ m_menuItems.OnBeginDrag = Callback(this, &CGenMenuOptionsPage::onMenuItemBeginDrag);
+
+ m_customName.SetSilent();
+ m_service.SetSilent();
+ m_module.SetSilent();
+ }
+
+ //---- init dialog -------------------------------------------
+ bool OnInitDialog() override
+ {
+ iInitMenuValue = db_get_b(0, "CList", "MoveProtoMenus", TRUE);
+
+ if (iInitMenuValue)
+ m_radio2.SetState(true);
+ else
+ m_radio1.SetState(true);
+
+ m_enableIcons.SetState(!bIconsDisabled);
+
+ //---- init menu object list --------------------------------------
+ for (auto &p : g_menus)
+ if (p->id != (int)hStatusMenuObject && p->m_bUseUserDefinedItems)
+ m_menuObjects.AddString(TranslateW(p->ptszDisplayName), p->id);
+
+ m_menuObjects.SetCurSel(0);
+ RebuildCurrent();
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ bIconsDisabled = m_enableIcons.GetState() == 0;
+ db_set_b(0, "CList", "DisableMenuIcons", bIconsDisabled);
+ SaveTree();
+
+ for (auto &pimi : m_arDeleted)
+ Menu_RemoveItem(pimi);
+
+ int iNewMenuValue = !m_radio1.GetState();
+ if (iNewMenuValue != iInitMenuValue) {
+ db_set_b(0, "CList", "MoveProtoMenus", iNewMenuValue);
+
+ RebuildProtoMenus();
+ iInitMenuValue = iNewMenuValue;
+ }
+ RebuildCurrent();
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ FreeTreeData();
+ }
+
+ void btnInsSep_Clicked(CCtrlButton*)
+ {
+ HTREEITEM hti = m_menuItems.GetSelection();
+ if (hti == nullptr)
+ return;
+
+ TVITEMEX tvi = { 0 };
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT;
+ tvi.hItem = hti;
+ if (!m_menuItems.GetItem(&tvi))
+ return;
+
+ MenuItemOptData *PD = new MenuItemOptData();
+ PD->id = -1;
+ PD->name = mir_wstrdup(STR_SEPARATOR);
+ PD->pos = ((MenuItemOptData *)tvi.lParam)->pos - 1;
+
+ TVINSERTSTRUCT tvis = {};
+ tvis.item.lParam = (LPARAM)PD;
+ tvis.item.pszText = PD->name;
+ tvis.item.iImage = tvis.item.iSelectedImage = 1;
+ tvis.hInsertAfter = hti;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ m_menuItems.InsertItem(&tvis);
+
+ NotifyChange();
+ }
+
+ void btnInsMenu_Clicked(CCtrlButton*)
+ {
+ HTREEITEM hti = m_menuItems.GetSelection();
+ if (hti == nullptr)
+ return;
+
+ TVITEMEX tvi = { 0 };
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT;
+ tvi.hItem = hti;
+ if (!m_menuItems.GetItem(&tvi))
+ return;
+
+ MenuItemOptData *curData = (MenuItemOptData*)tvi.lParam;
+
+ TMO_MenuItem mi = {};
+ UuidCreate((UUID*)&mi.uid);
+ mi.flags = CMIF_CUSTOM;
+ mi.name.a = LPGEN("New submenu");
+ mi.position = curData->pos - 1;
+ TMO_IntMenuItem *pimi = Menu_AddItem(curData->pimi->parent->id, &mi, nullptr);
+
+ MenuItemOptData *PD = new MenuItemOptData();
+ PD->id = -1;
+ PD->name = mir_wstrdup(pimi->mi.name.w);
+ PD->pos = pimi->mi.position;
+ PD->pimi = pimi;
+
+ TVINSERTSTRUCT tvis = {};
+ tvis.item.lParam = (LPARAM)PD;
+ tvis.item.pszText = PD->name;
+ tvis.item.iImage = tvis.item.iSelectedImage = 1;
+ tvis.hInsertAfter = hti;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ m_menuItems.InsertItem(&tvis);
+
+ NotifyChange();
+ }
+
+ void btnReset_Clicked(CCtrlButton*)
+ {
+ int MenuObjectID;
+ if (GetCurrentMenuObjectID(MenuObjectID)) {
+ BuildTree(MenuObjectID, false);
+ NotifyChange();
+ }
+ }
+
+ void btnDefault_Clicked(CCtrlButton*)
+ {
+ HTREEITEM hti = m_menuItems.GetSelection();
+ if (hti == nullptr)
+ return;
+
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
+ tvi.hItem = hti;
+ m_menuItems.GetItem(&tvi);
+
+ MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam;
+ if (iod->name && wcsstr(iod->name, STR_SEPARATOR))
+ return;
+
+ iod->name = mir_wstrdup(iod->defname);
+ m_customName.SetText(iod->defname);
+
+ tvi.mask = TVIF_TEXT;
+ tvi.pszText = iod->name;
+ m_menuItems.SetItem(&tvi);
+ NotifyChange();
+ }
+
+ void btnSet_Clicked(CCtrlButton*)
+ {
+ HTREEITEM hti = m_menuItems.GetSelection();
+ if (hti == nullptr)
+ return;
+
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
+ tvi.hItem = hti;
+ m_menuItems.GetItem(&tvi);
+
+ MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam;
+ if (iod->name && wcsstr(iod->name, STR_SEPARATOR))
+ return;
+
+ iod->name = m_customName.GetText();
+
+ tvi.mask = TVIF_TEXT;
+ tvi.pszText = iod->name;
+ m_menuItems.SetItem(&tvi);
+ NotifyChange();
+ }
+
+ void btnDelete_Clicked(CCtrlButton *)
+ {
+ HTREEITEM hti = m_menuItems.GetSelection();
+ if (hti == nullptr)
+ return;
+
+ TVITEMEX tvi;
+ tvi.mask = TVIF_PARAM;
+ tvi.hItem = hti;
+ m_menuItems.GetItem(&tvi);
+
+ MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam;
+ if (!(iod->pimi->mi.flags & CMIF_CUSTOM))
+ return;
+
+ if (IDYES == MessageBoxW(m_hwnd, TranslateT("Do you really want to delete this menu item?"), TranslateT("Miranda"), MB_YESNO | MB_ICONQUESTION)) {
+ m_arDeleted.insert(iod->pimi);
+ m_menuItems.DeleteItem(hti);
+ delete iod;
+ NotifyChange();
+ }
+ }
+
+ void onMenuObjectChanged(void*)
+ {
+ m_bInitialized = false;
+ RebuildCurrent();
+ m_bInitialized = true;
+ }
+
+ void onMenuItemChanged(void*)
+ {
+ m_customName.SetTextA("");
+ m_service.SetTextA("");
+ m_module.SetTextA("");
+
+ m_btnInsMenu.Disable();
+ m_btnDefault.Disable();
+ m_btnSet.Disable();
+ m_btnDelete.Disable();
+ m_customName.Disable();
+
+ HTREEITEM hti = m_menuItems.GetSelection();
+ if (hti == nullptr)
+ return;
+
+ TVITEMEX tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
+ tvi.hItem = hti;
+ m_menuItems.GetItem(&tvi);
+ if (tvi.lParam == 0)
+ return;
+
+ MenuItemOptData *iod = (MenuItemOptData *)tvi.lParam;
+ if (iod->name && wcsstr(iod->name, STR_SEPARATOR))
+ return;
+
+ m_customName.SetText(iod->name);
+
+ if (iod->pimi->mi.uid != miid_last) {
+ char szText[100];
+ bin2hex(&iod->pimi->mi.uid, sizeof(iod->pimi->mi.uid), szText);
+ m_service.SetTextA(szText);
+ }
+
+ const CMPluginBase *pPlugin = iod->pimi->mi.pPlugin;
+ m_module.SetTextA(pPlugin == nullptr ? "" : pPlugin->getInfo().shortName);
+
+ m_btnInsMenu.Enable(iod->pimi->mi.root == nullptr);
+ m_btnDefault.Enable(mir_wstrcmp(iod->name, iod->defname) != 0);
+ m_btnDelete.Enable(iod->pimi->mi.flags & CMIF_CUSTOM);
+ m_btnSet.Enable(true);
+ m_customName.Enable(true);
+ }
+
+ void onMenuItemBeginDrag(CCtrlTreeView::TEventInfo *evt)
+ {
+ MenuItemOptData *p = (MenuItemOptData*)evt->nmtv->itemNew.lParam;
+ if (p->pimi != nullptr)
+ if (p->pimi->mi.flags & CMIF_UNMOVABLE)
+ evt->nmhdr->code = 0; // reject an attempt to change item's position
+ }
+};
+
+int GenMenuOptInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.position = -1000000000;
+ odp.szTitle.a = LPGEN("Menus");
+ odp.szGroup.a = LPGEN("Customize");
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pDialog = new CGenMenuOptionsPage();
+ g_plugin.addOptions(wParam, &odp);
+
+ return ProtocolOrderOptInit(wParam, 0);
+}
diff --git a/src/mir_app/src/menu_tray.cpp b/src/mir_app/src/menu_tray.cpp index 69446e1590..77b4a482b9 100644 --- a/src/mir_app/src/menu_tray.cpp +++ b/src/mir_app/src/menu_tray.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/menu_utils.cpp b/src/mir_app/src/menu_utils.cpp index 12df821386..ff048552de 100644 --- a/src/mir_app/src/menu_utils.cpp +++ b/src/mir_app/src/menu_utils.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/meta_addto.cpp b/src/mir_app/src/meta_addto.cpp index 7cf6b8d7ce..345598d429 100644 --- a/src/mir_app/src/meta_addto.cpp +++ b/src/mir_app/src/meta_addto.cpp @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/meta_api.cpp b/src/mir_app/src/meta_api.cpp index 97da5e0acd..886e0be1c7 100644 --- a/src/mir_app/src/meta_api.cpp +++ b/src/mir_app/src/meta_api.cpp @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/meta_edit.cpp b/src/mir_app/src/meta_edit.cpp index 9f57a4131f..ac7917ac77 100644 --- a/src/mir_app/src/meta_edit.cpp +++ b/src/mir_app/src/meta_edit.cpp @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/meta_main.cpp b/src/mir_app/src/meta_main.cpp index 7ac06606b7..f59794693b 100644 --- a/src/mir_app/src/meta_main.cpp +++ b/src/mir_app/src/meta_main.cpp @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/meta_menu.cpp b/src/mir_app/src/meta_menu.cpp index dc4ad1252f..1ae7ec2247 100644 --- a/src/mir_app/src/meta_menu.cpp +++ b/src/mir_app/src/meta_menu.cpp @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/meta_options.cpp b/src/mir_app/src/meta_options.cpp index b6e3f81b7f..71f7c6a997 100644 --- a/src/mir_app/src/meta_options.cpp +++ b/src/mir_app/src/meta_options.cpp @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/meta_services.cpp b/src/mir_app/src/meta_services.cpp index e8d5b042d6..74f218e3d1 100644 --- a/src/mir_app/src/meta_services.cpp +++ b/src/mir_app/src/meta_services.cpp @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/meta_utils.cpp b/src/mir_app/src/meta_utils.cpp index ad75ed4910..3db142ba98 100644 --- a/src/mir_app/src/meta_utils.cpp +++ b/src/mir_app/src/meta_utils.cpp @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/metacontacts.h b/src/mir_app/src/metacontacts.h index 49e86ef32c..47f8b5bab2 100644 --- a/src/mir_app/src/metacontacts.h +++ b/src/mir_app/src/metacontacts.h @@ -1,7 +1,7 @@ /*
former MetaContacts Plugin for Miranda IM.
-Copyright © 2014-22 Miranda NG team
+Copyright © 2014-23 Miranda NG team
Copyright © 2004-07 Scott Ellis
Copyright © 2004 Universite Louis PASTEUR, STRASBOURG.
diff --git a/src/mir_app/src/miranda.cpp b/src/mir_app/src/miranda.cpp index 7628da2e66..850e769535 100644 --- a/src/mir_app/src/miranda.cpp +++ b/src/mir_app/src/miranda.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/miranda.h b/src/mir_app/src/miranda.h index b3dd931124..b0508c18f3 100644 --- a/src/mir_app/src/miranda.h +++ b/src/mir_app/src/miranda.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/modules.cpp b/src/mir_app/src/modules.cpp index 2274247cc2..ff0b6682f2 100644 --- a/src/mir_app/src/modules.cpp +++ b/src/mir_app/src/modules.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/movetogroup.cpp b/src/mir_app/src/movetogroup.cpp index 4f810c4002..3eb84c9e87 100644 --- a/src/mir_app/src/movetogroup.cpp +++ b/src/mir_app/src/movetogroup.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/netlib.cpp b/src/mir_app/src/netlib.cpp index 68c2d6e3d0..2914620911 100644 --- a/src/mir_app/src/netlib.cpp +++ b/src/mir_app/src/netlib.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/netlib.h b/src/mir_app/src/netlib.h index 4b85f6c365..09480637bf 100644 --- a/src/mir_app/src/netlib.h +++ b/src/mir_app/src/netlib.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/netlib_autoproxy.cpp b/src/mir_app/src/netlib_autoproxy.cpp index cc2ef2b9e1..f325f3a721 100644 --- a/src/mir_app/src/netlib_autoproxy.cpp +++ b/src/mir_app/src/netlib_autoproxy.cpp @@ -1,365 +1,365 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -#include <wininet.h> - -///////////////////////////////////////////////////////////////////////////////////////// -// local module data - -static char *szProxyHost[3]; -static LIST<char> proxyBypass(5); - -static HMODULE hModJS; - -static pfnInternetInitializeAutoProxyDll pInternetInitializeAutoProxyDll; -static pfnInternetDeInitializeAutoProxyDll pInternetDeInitializeAutoProxyDll; -static pfnInternetGetProxyInfo pInternetGetProxyInfo; - -static bool bEnabled, bOneProxy; - -///////////////////////////////////////////////////////////////////////////////////////// - -static void GetFile(char *szUrl, AUTO_PROXY_SCRIPT_BUFFER &buf) -{ - NetlibUser nlu = {}; - nlu.handleType = NLH_USER; - nlu.user.flags = NUF_OUTGOING | NUF_HTTPCONNS; - nlu.user.szSettingsModule = "(NULL)"; - nlu.toLog = 1; - - // initialize the netlib request - NETLIBHTTPREQUEST nlhr = {}; - nlhr.cbSize = sizeof(nlhr); - nlhr.requestType = REQUEST_GET; - nlhr.flags = NLHRF_HTTP11 | NLHRF_DUMPASTEXT | NLHRF_REDIRECT; - nlhr.szUrl = szUrl; - - // download the page - NLHR_PTR nlhrReply(Netlib_HttpTransaction(&nlu, &nlhr)); - if (nlhrReply) { - if (nlhrReply->resultCode == 200) { - buf.lpszScriptBuffer = nlhrReply->pData; - buf.dwScriptBufferSize = nlhrReply->dataLength + 1; - - nlhrReply->dataLength = 0; - nlhrReply->pData = nullptr; - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -bool NetlibGetIeProxyConn(NetlibConnection *nlc, bool forceHttps) -{ - bool noHttp = false; - bool usingSsl = false; - char szUrl[1024]; - - if ((nlc->url.flags & NLOCF_HTTP) && (nlc->url.flags & NLOCF_SSL) || nlc->url.port == 443 || forceHttps) { - mir_snprintf(szUrl, "https://%s", nlc->url.szHost.c_str()); - usingSsl = true; - } - else if ((nlc->url.flags & NLOCF_HTTP)) - mir_snprintf(szUrl, "http://%s", nlc->url.szHost.c_str()); - else { - strncpy_s(szUrl, nlc->url.szHost, _TRUNCATE); - noHttp = true; - } - - mir_free(nlc->szProxyServer); nlc->szProxyServer = nullptr; - nlc->wProxyPort = 0; - nlc->proxyType = 0; - - char *mt = NetlibGetIeProxy(szUrl); - char *m = NEWSTR_ALLOCA(mt); - mir_free(mt); - - if (m == nullptr) - return false; - - // if multiple servers, use the first one - char *c = strchr(m, ';'); if (c) *c = 0; - - // if 'direct' no proxy - if (_stricmp(lrtrim(m), "direct") == 0) - return false; - - // find proxy address - char *h = strchr(m, ' '); - if (h == nullptr) - return false; - - // find proxy port - *h = 0; ++h; - char *p = strchr(h, ':'); - if (p) { *p = 0; ++p; } - - lrtrim(h); ltrim(p); - if (_stricmp(m, "proxy") == 0 && h[0]) { - nlc->proxyType = (usingSsl || noHttp) ? PROXYTYPE_HTTPS : PROXYTYPE_HTTP; - nlc->wProxyPort = p ? atol(p) : 8080; - nlc->szProxyServer = mir_strdup(h); - } - else if (_stricmp(m, "socks") == 0 && h[0]) { - nlc->proxyType = PROXYTYPE_SOCKS4; - nlc->wProxyPort = p ? atol(p) : 1080; - nlc->szProxyServer = mir_strdup(h); - } - else if (_stricmp(m, "socks5") == 0 && h[0]) { - nlc->proxyType = PROXYTYPE_SOCKS5; - nlc->wProxyPort = p ? atol(p) : 1080; - nlc->szProxyServer = mir_strdup(h); - } - else return false; - - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static char szAutoUrlStr[MAX_PATH] = ""; -static AUTO_PROXY_SCRIPT_BUFFER abuf = { 0 }; -static HANDLE hIeProxyMutex; -static bool bAutoProxyInit; - -static void NetlibInitAutoProxy(void) -{ - if (bAutoProxyInit) return; - - if (!hModJS) { - if (!(hModJS = LoadLibraryA("jsproxy.dll"))) - return; - - pInternetInitializeAutoProxyDll = (pfnInternetInitializeAutoProxyDll)GetProcAddress(hModJS, "InternetInitializeAutoProxyDll"); - pInternetDeInitializeAutoProxyDll = (pfnInternetDeInitializeAutoProxyDll)GetProcAddress(hModJS, "InternetDeInitializeAutoProxyDll"); - pInternetGetProxyInfo = (pfnInternetGetProxyInfo)GetProcAddress(hModJS, "InternetGetProxyInfo"); - } - - if (strstr(szAutoUrlStr, "file://") == nullptr && strstr(szAutoUrlStr, "://") != nullptr) { - abuf.dwStructSize = sizeof(abuf); - GetFile(szAutoUrlStr, abuf); - } - bAutoProxyInit = true; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -struct IeProxyParam -{ - char *szUrl; - char *szHost; - char *szProxy; -}; - -static void __cdecl NetlibIeProxyThread(IeProxyParam *param) -{ - param->szProxy = nullptr; - - if (!bAutoProxyInit) { - WaitForSingleObject(hIeProxyMutex, INFINITE); - NetlibInitAutoProxy(); - ReleaseMutex(hIeProxyMutex); - } - - BOOL res; - char *loc = strstr(szAutoUrlStr, "file://"); - if (loc || strstr(szAutoUrlStr, "://") == nullptr) { - Netlib_Logf(nullptr, "Autoproxy Init file: %s", loc); - loc = loc ? loc + 7 : szAutoUrlStr; - res = pInternetInitializeAutoProxyDll(0, loc, nullptr, nullptr /*&HelperFunctions*/, nullptr); - } - else { - Netlib_Logf(nullptr, "Autoproxy Init %d", abuf.dwScriptBufferSize); - if (abuf.dwScriptBufferSize) - res = pInternetInitializeAutoProxyDll(0, nullptr, nullptr, nullptr /*&HelperFunctions*/, &abuf); - else - res = false; - } - - if (res) { - char proxyBuffer[1024]; - char *proxy = proxyBuffer; - DWORD dwProxyLen = sizeof(proxyBuffer); - - if (pInternetGetProxyInfo(param->szUrl, (uint32_t)mir_strlen(param->szUrl), - param->szHost, (uint32_t)mir_strlen(param->szHost), &proxy, &dwProxyLen)) - param->szProxy = mir_strdup(lrtrim(proxy)); - - Netlib_Logf(nullptr, "Autoproxy got response %s, Param: %s %s", param->szProxy, param->szUrl, param->szHost); - pInternetDeInitializeAutoProxyDll(nullptr, 0); - } - else Netlib_Logf(nullptr, "Autoproxy init failed"); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -char* NetlibGetIeProxy(char *szUrl) -{ - char *res = nullptr, *szHost; - { - char* p = strstr(szUrl, "://"); - if (p) p += 3; else p = szUrl; - - szHost = NEWSTR_ALLOCA(p); - p = strchr(szHost, '/'); if (p) *p = 0; - p = strchr(szHost, ':'); if (p) *p = 0; - _strlwr(szHost); - } - - if (bEnabled) { - for (auto &p : proxyBypass) { - if (mir_strcmp(p, "<local>") == 0) { - if (strchr(szHost, '.') == nullptr) - return nullptr; - } - else if (wildcmp(szHost, p)) - return nullptr; - } - - int ind = -1; - if (strstr(szUrl, "http://")) - ind = szProxyHost[0] ? 0 : 2; - else if (strstr(szUrl, "https://")) - ind = bOneProxy ? 0 : (szProxyHost[1] ? 1 : 2); - else - ind = szProxyHost[2] ? 2 : (bOneProxy ? 0 : (szProxyHost[1] ? 1 : 2)); - - if (ind < 0 || !szProxyHost[ind]) - return nullptr; - - size_t len = mir_strlen(szHost) + 20; - res = (char*)mir_alloc(len); - mir_snprintf(res, len, "%s %s", ind == 2 ? "SOCKS" : "PROXY", szProxyHost[ind]); - return res; - } - - if (szAutoUrlStr[0]) { - IeProxyParam param = { szUrl, szHost, nullptr }; - HANDLE hThread = mir_forkThread<IeProxyParam>(NetlibIeProxyThread, ¶m); - WaitForSingleObject(hThread, INFINITE); - res = param.szProxy; - } - return res; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void NetlibLoadIeProxy(void) -{ - HKEY hSettings; - if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_QUERY_VALUE, &hSettings)) - return; - - DWORD tValueLen, enabled = 0; - char szHostStr[256] = "", szProxyBypassStr[4096] = ""; - - tValueLen = sizeof(enabled); - int tResult = RegQueryValueExA(hSettings, "ProxyEnable", nullptr, nullptr, (uint8_t*)&enabled, &tValueLen); - bEnabled = enabled && tResult == ERROR_SUCCESS; - - tValueLen = _countof(szHostStr); - tResult = RegQueryValueExA(hSettings, "ProxyServer", nullptr, nullptr, (uint8_t*)szHostStr, &tValueLen); - bEnabled = bEnabled && tResult == ERROR_SUCCESS; - - tValueLen = _countof(szAutoUrlStr); - RegQueryValueExA(hSettings, "AutoConfigUrl", nullptr, nullptr, (uint8_t*)szAutoUrlStr, &tValueLen); - - tValueLen = _countof(szProxyBypassStr); - RegQueryValueExA(hSettings, "ProxyOverride", nullptr, nullptr, (uint8_t*)szProxyBypassStr, &tValueLen); - - RegCloseKey(hSettings); - - if (bEnabled) { - char *szProxy = ltrim(szHostStr); - if (szProxy[0] == 0) { - enabled = false; - return; - } - - while (true) { - char *szProxyEnd = strchr(szProxy, ';'); - if (szProxyEnd) - *szProxyEnd = 0; - - int ind = -1; - if (strncmp(szProxy, "http=", 5) == 0) { ind = 0; szProxy += 5; } - else if (strncmp(szProxy, "https=", 6) == 0) { ind = 1; szProxy += 6; } - else if (strncmp(szProxy, "socks=", 6) == 0) { ind = 2; szProxy += 6; } - else if (strchr(szProxy, '=')) ind = -2; - - if (ind != -2) { - bOneProxy = ind < 0; if (ind < 0) ind = 0; - - lrtrim(szProxy); - - if (strchr(szProxy, ':')) - szProxyHost[ind] = mir_strdup(szProxy); - else { - size_t len = mir_strlen(szProxy) + 10; - szProxyHost[ind] = (char*)mir_alloc(len); - mir_snprintf(szProxyHost[ind], len, "%s:%u", szProxy, ind == 2 ? 1080 : 8080); - } - if (bOneProxy) - break; - } - if (szProxyEnd == nullptr) - break; - szProxy = szProxyEnd + 1; - } - - char *szProxyBypass = szProxyBypassStr; - while (true) { - char *szProxyBypassEnd = strchr(szProxyBypass, ';'); - if (szProxyBypassEnd) - *szProxyBypassEnd = 0; - - lrtrim(szProxyBypass); - - proxyBypass.insert(_strlwr(mir_strdup(szProxyBypass))); - if (szProxyBypassEnd == nullptr) - break; - - szProxyBypass = szProxyBypassEnd + 1; - } - } - - if (bEnabled || szAutoUrlStr[0]) - hIeProxyMutex = CreateMutex(nullptr, FALSE, nullptr); -} - -void NetlibUnloadIeProxy(void) -{ - for (int i = 0; i < 3; i++) - mir_free(szProxyHost[i]); - - for (auto &p : proxyBypass) - mir_free(p); - - mir_free(abuf.lpszScriptBuffer); - - CloseHandle(hIeProxyMutex); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+#include <wininet.h>
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// local module data
+
+static char *szProxyHost[3];
+static LIST<char> proxyBypass(5);
+
+static HMODULE hModJS;
+
+static pfnInternetInitializeAutoProxyDll pInternetInitializeAutoProxyDll;
+static pfnInternetDeInitializeAutoProxyDll pInternetDeInitializeAutoProxyDll;
+static pfnInternetGetProxyInfo pInternetGetProxyInfo;
+
+static bool bEnabled, bOneProxy;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void GetFile(char *szUrl, AUTO_PROXY_SCRIPT_BUFFER &buf)
+{
+ NetlibUser nlu = {};
+ nlu.handleType = NLH_USER;
+ nlu.user.flags = NUF_OUTGOING | NUF_HTTPCONNS;
+ nlu.user.szSettingsModule = "(NULL)";
+ nlu.toLog = 1;
+
+ // initialize the netlib request
+ NETLIBHTTPREQUEST nlhr = {};
+ nlhr.cbSize = sizeof(nlhr);
+ nlhr.requestType = REQUEST_GET;
+ nlhr.flags = NLHRF_HTTP11 | NLHRF_DUMPASTEXT | NLHRF_REDIRECT;
+ nlhr.szUrl = szUrl;
+
+ // download the page
+ NLHR_PTR nlhrReply(Netlib_HttpTransaction(&nlu, &nlhr));
+ if (nlhrReply) {
+ if (nlhrReply->resultCode == 200) {
+ buf.lpszScriptBuffer = nlhrReply->pData;
+ buf.dwScriptBufferSize = nlhrReply->dataLength + 1;
+
+ nlhrReply->dataLength = 0;
+ nlhrReply->pData = nullptr;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool NetlibGetIeProxyConn(NetlibConnection *nlc, bool forceHttps)
+{
+ bool noHttp = false;
+ bool usingSsl = false;
+ char szUrl[1024];
+
+ if ((nlc->url.flags & NLOCF_HTTP) && (nlc->url.flags & NLOCF_SSL) || nlc->url.port == 443 || forceHttps) {
+ mir_snprintf(szUrl, "https://%s", nlc->url.szHost.c_str());
+ usingSsl = true;
+ }
+ else if ((nlc->url.flags & NLOCF_HTTP))
+ mir_snprintf(szUrl, "http://%s", nlc->url.szHost.c_str());
+ else {
+ strncpy_s(szUrl, nlc->url.szHost, _TRUNCATE);
+ noHttp = true;
+ }
+
+ mir_free(nlc->szProxyServer); nlc->szProxyServer = nullptr;
+ nlc->wProxyPort = 0;
+ nlc->proxyType = 0;
+
+ char *mt = NetlibGetIeProxy(szUrl);
+ char *m = NEWSTR_ALLOCA(mt);
+ mir_free(mt);
+
+ if (m == nullptr)
+ return false;
+
+ // if multiple servers, use the first one
+ char *c = strchr(m, ';'); if (c) *c = 0;
+
+ // if 'direct' no proxy
+ if (_stricmp(lrtrim(m), "direct") == 0)
+ return false;
+
+ // find proxy address
+ char *h = strchr(m, ' ');
+ if (h == nullptr)
+ return false;
+
+ // find proxy port
+ *h = 0; ++h;
+ char *p = strchr(h, ':');
+ if (p) { *p = 0; ++p; }
+
+ lrtrim(h); ltrim(p);
+ if (_stricmp(m, "proxy") == 0 && h[0]) {
+ nlc->proxyType = (usingSsl || noHttp) ? PROXYTYPE_HTTPS : PROXYTYPE_HTTP;
+ nlc->wProxyPort = p ? atol(p) : 8080;
+ nlc->szProxyServer = mir_strdup(h);
+ }
+ else if (_stricmp(m, "socks") == 0 && h[0]) {
+ nlc->proxyType = PROXYTYPE_SOCKS4;
+ nlc->wProxyPort = p ? atol(p) : 1080;
+ nlc->szProxyServer = mir_strdup(h);
+ }
+ else if (_stricmp(m, "socks5") == 0 && h[0]) {
+ nlc->proxyType = PROXYTYPE_SOCKS5;
+ nlc->wProxyPort = p ? atol(p) : 1080;
+ nlc->szProxyServer = mir_strdup(h);
+ }
+ else return false;
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static char szAutoUrlStr[MAX_PATH] = "";
+static AUTO_PROXY_SCRIPT_BUFFER abuf = { 0 };
+static HANDLE hIeProxyMutex;
+static bool bAutoProxyInit;
+
+static void NetlibInitAutoProxy(void)
+{
+ if (bAutoProxyInit) return;
+
+ if (!hModJS) {
+ if (!(hModJS = LoadLibraryA("jsproxy.dll")))
+ return;
+
+ pInternetInitializeAutoProxyDll = (pfnInternetInitializeAutoProxyDll)GetProcAddress(hModJS, "InternetInitializeAutoProxyDll");
+ pInternetDeInitializeAutoProxyDll = (pfnInternetDeInitializeAutoProxyDll)GetProcAddress(hModJS, "InternetDeInitializeAutoProxyDll");
+ pInternetGetProxyInfo = (pfnInternetGetProxyInfo)GetProcAddress(hModJS, "InternetGetProxyInfo");
+ }
+
+ if (strstr(szAutoUrlStr, "file://") == nullptr && strstr(szAutoUrlStr, "://") != nullptr) {
+ abuf.dwStructSize = sizeof(abuf);
+ GetFile(szAutoUrlStr, abuf);
+ }
+ bAutoProxyInit = true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct IeProxyParam
+{
+ char *szUrl;
+ char *szHost;
+ char *szProxy;
+};
+
+static void __cdecl NetlibIeProxyThread(IeProxyParam *param)
+{
+ param->szProxy = nullptr;
+
+ if (!bAutoProxyInit) {
+ WaitForSingleObject(hIeProxyMutex, INFINITE);
+ NetlibInitAutoProxy();
+ ReleaseMutex(hIeProxyMutex);
+ }
+
+ BOOL res;
+ char *loc = strstr(szAutoUrlStr, "file://");
+ if (loc || strstr(szAutoUrlStr, "://") == nullptr) {
+ Netlib_Logf(nullptr, "Autoproxy Init file: %s", loc);
+ loc = loc ? loc + 7 : szAutoUrlStr;
+ res = pInternetInitializeAutoProxyDll(0, loc, nullptr, nullptr /*&HelperFunctions*/, nullptr);
+ }
+ else {
+ Netlib_Logf(nullptr, "Autoproxy Init %d", abuf.dwScriptBufferSize);
+ if (abuf.dwScriptBufferSize)
+ res = pInternetInitializeAutoProxyDll(0, nullptr, nullptr, nullptr /*&HelperFunctions*/, &abuf);
+ else
+ res = false;
+ }
+
+ if (res) {
+ char proxyBuffer[1024];
+ char *proxy = proxyBuffer;
+ DWORD dwProxyLen = sizeof(proxyBuffer);
+
+ if (pInternetGetProxyInfo(param->szUrl, (uint32_t)mir_strlen(param->szUrl),
+ param->szHost, (uint32_t)mir_strlen(param->szHost), &proxy, &dwProxyLen))
+ param->szProxy = mir_strdup(lrtrim(proxy));
+
+ Netlib_Logf(nullptr, "Autoproxy got response %s, Param: %s %s", param->szProxy, param->szUrl, param->szHost);
+ pInternetDeInitializeAutoProxyDll(nullptr, 0);
+ }
+ else Netlib_Logf(nullptr, "Autoproxy init failed");
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+char* NetlibGetIeProxy(char *szUrl)
+{
+ char *res = nullptr, *szHost;
+ {
+ char* p = strstr(szUrl, "://");
+ if (p) p += 3; else p = szUrl;
+
+ szHost = NEWSTR_ALLOCA(p);
+ p = strchr(szHost, '/'); if (p) *p = 0;
+ p = strchr(szHost, ':'); if (p) *p = 0;
+ _strlwr(szHost);
+ }
+
+ if (bEnabled) {
+ for (auto &p : proxyBypass) {
+ if (mir_strcmp(p, "<local>") == 0) {
+ if (strchr(szHost, '.') == nullptr)
+ return nullptr;
+ }
+ else if (wildcmp(szHost, p))
+ return nullptr;
+ }
+
+ int ind = -1;
+ if (strstr(szUrl, "http://"))
+ ind = szProxyHost[0] ? 0 : 2;
+ else if (strstr(szUrl, "https://"))
+ ind = bOneProxy ? 0 : (szProxyHost[1] ? 1 : 2);
+ else
+ ind = szProxyHost[2] ? 2 : (bOneProxy ? 0 : (szProxyHost[1] ? 1 : 2));
+
+ if (ind < 0 || !szProxyHost[ind])
+ return nullptr;
+
+ size_t len = mir_strlen(szHost) + 20;
+ res = (char*)mir_alloc(len);
+ mir_snprintf(res, len, "%s %s", ind == 2 ? "SOCKS" : "PROXY", szProxyHost[ind]);
+ return res;
+ }
+
+ if (szAutoUrlStr[0]) {
+ IeProxyParam param = { szUrl, szHost, nullptr };
+ HANDLE hThread = mir_forkThread<IeProxyParam>(NetlibIeProxyThread, ¶m);
+ WaitForSingleObject(hThread, INFINITE);
+ res = param.szProxy;
+ }
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void NetlibLoadIeProxy(void)
+{
+ HKEY hSettings;
+ if (RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_QUERY_VALUE, &hSettings))
+ return;
+
+ DWORD tValueLen, enabled = 0;
+ char szHostStr[256] = "", szProxyBypassStr[4096] = "";
+
+ tValueLen = sizeof(enabled);
+ int tResult = RegQueryValueExA(hSettings, "ProxyEnable", nullptr, nullptr, (uint8_t*)&enabled, &tValueLen);
+ bEnabled = enabled && tResult == ERROR_SUCCESS;
+
+ tValueLen = _countof(szHostStr);
+ tResult = RegQueryValueExA(hSettings, "ProxyServer", nullptr, nullptr, (uint8_t*)szHostStr, &tValueLen);
+ bEnabled = bEnabled && tResult == ERROR_SUCCESS;
+
+ tValueLen = _countof(szAutoUrlStr);
+ RegQueryValueExA(hSettings, "AutoConfigUrl", nullptr, nullptr, (uint8_t*)szAutoUrlStr, &tValueLen);
+
+ tValueLen = _countof(szProxyBypassStr);
+ RegQueryValueExA(hSettings, "ProxyOverride", nullptr, nullptr, (uint8_t*)szProxyBypassStr, &tValueLen);
+
+ RegCloseKey(hSettings);
+
+ if (bEnabled) {
+ char *szProxy = ltrim(szHostStr);
+ if (szProxy[0] == 0) {
+ enabled = false;
+ return;
+ }
+
+ while (true) {
+ char *szProxyEnd = strchr(szProxy, ';');
+ if (szProxyEnd)
+ *szProxyEnd = 0;
+
+ int ind = -1;
+ if (strncmp(szProxy, "http=", 5) == 0) { ind = 0; szProxy += 5; }
+ else if (strncmp(szProxy, "https=", 6) == 0) { ind = 1; szProxy += 6; }
+ else if (strncmp(szProxy, "socks=", 6) == 0) { ind = 2; szProxy += 6; }
+ else if (strchr(szProxy, '=')) ind = -2;
+
+ if (ind != -2) {
+ bOneProxy = ind < 0; if (ind < 0) ind = 0;
+
+ lrtrim(szProxy);
+
+ if (strchr(szProxy, ':'))
+ szProxyHost[ind] = mir_strdup(szProxy);
+ else {
+ size_t len = mir_strlen(szProxy) + 10;
+ szProxyHost[ind] = (char*)mir_alloc(len);
+ mir_snprintf(szProxyHost[ind], len, "%s:%u", szProxy, ind == 2 ? 1080 : 8080);
+ }
+ if (bOneProxy)
+ break;
+ }
+ if (szProxyEnd == nullptr)
+ break;
+ szProxy = szProxyEnd + 1;
+ }
+
+ char *szProxyBypass = szProxyBypassStr;
+ while (true) {
+ char *szProxyBypassEnd = strchr(szProxyBypass, ';');
+ if (szProxyBypassEnd)
+ *szProxyBypassEnd = 0;
+
+ lrtrim(szProxyBypass);
+
+ proxyBypass.insert(_strlwr(mir_strdup(szProxyBypass)));
+ if (szProxyBypassEnd == nullptr)
+ break;
+
+ szProxyBypass = szProxyBypassEnd + 1;
+ }
+ }
+
+ if (bEnabled || szAutoUrlStr[0])
+ hIeProxyMutex = CreateMutex(nullptr, FALSE, nullptr);
+}
+
+void NetlibUnloadIeProxy(void)
+{
+ for (int i = 0; i < 3; i++)
+ mir_free(szProxyHost[i]);
+
+ for (auto &p : proxyBypass)
+ mir_free(p);
+
+ mir_free(abuf.lpszScriptBuffer);
+
+ CloseHandle(hIeProxyMutex);
+}
diff --git a/src/mir_app/src/netlib_bind.cpp b/src/mir_app/src/netlib_bind.cpp index 53aad20fa3..cd9c955a75 100644 --- a/src/mir_app/src/netlib_bind.cpp +++ b/src/mir_app/src/netlib_bind.cpp @@ -1,330 +1,330 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -bool BindSocketToPort(const char *szPorts, SOCKET s, SOCKET s6, int* portn) -{ - SOCKADDR_IN sin = {0}; - sin.sin_family = AF_INET; - - SOCKADDR_IN6 sin6 = {0}; - sin6.sin6_family = AF_INET6; - - mir_cslock lck(csNetlibUser); - - if (--*portn < 0 && (s != INVALID_SOCKET || s6 != INVALID_SOCKET)) { - BindSocketToPort(szPorts, INVALID_SOCKET, INVALID_SOCKET, portn); - if (*portn == 0) - return false; - - uint16_t num; - Utils_GetRandom(&num, sizeof(uint16_t)); - *portn = num % *portn; - } - - bool before = false; - while (true) { - const char *psz; - char *pszEnd; - int portMin, portMax, port, portnum = 0; - - for (psz = szPorts;*psz;) { - while (*psz == ' ' || *psz == ',') psz++; - portMin = strtol(psz, &pszEnd, 0); - if (pszEnd == psz) - break; - while (*pszEnd == ' ') - pszEnd++; - if (*pszEnd == '-') { - psz = pszEnd + 1; - portMax = strtol(psz, &pszEnd, 0); - if (pszEnd == psz) portMax = 65535; - if (portMin > portMax) { - port = portMin; - portMin = portMax; - portMax = port; - } - } - else portMax = portMin; - if (portMax >= 1) { - if (portMin <= 0) portMin = 1; - for (port = portMin; port <= portMax; port++) { - if (port > 65535) - break; - - ++portnum; - - if (s == INVALID_SOCKET) continue; - if (!before && portnum <= *portn) continue; - if (before && portnum >= *portn) - return false; - - sin.sin_port = htons((uint16_t)port); - bool bV4Mapped = s == INVALID_SOCKET || bind(s, (SOCKADDR*)&sin, sizeof(sin)) == 0; - - sin6.sin6_port = htons((uint16_t)port); - bool bV6Mapped = s6 == INVALID_SOCKET || bind(s6, (PSOCKADDR)&sin6, sizeof(sin6)) == 0; - - if (bV4Mapped && bV6Mapped) { - *portn = portnum + 1; - return true; - } - } - } - psz = pszEnd; - } - - if (*portn < 0) { - *portn = portnum; - return true; - } - - if (*portn >= portnum) - *portn = 0; - else - before = true; - } -} - -int NetlibFreeBoundPort(NetlibBoundPort *nlbp) -{ - NETLIBCONNECTIONEVENTINFO ncei; - - ZeroMemory(&ncei, sizeof(ncei)); - ncei.connected = 0; - ncei.listening = 1; - ncei.szSettingsModule = nlbp->nlu->user.szSettingsModule; - int size = sizeof(SOCKADDR_IN); - getsockname(nlbp->s, (SOCKADDR *)&ncei.local, &size); - NotifyFastHook(hEventDisconnected, (WPARAM)&ncei, 0); - - nlbp->close(); - if (nlbp->hThread) - WaitForSingleObject(nlbp->hThread, INFINITE); - Netlib_Logf(nlbp->nlu, "(%u) Port %u closed for incoming connections", nlbp->s, nlbp->wPort); - delete nlbp; - return 1; -} - -static void __cdecl NetlibBindAcceptThread(NetlibBoundPort *nlbp) -{ - Netlib_Logf(nlbp->nlu, "(%u) Port %u opened for incoming connections", nlbp->s, nlbp->wPort); - - while (true) { - fd_set r; - FD_ZERO(&r); - if (nlbp->s != INVALID_SOCKET) - FD_SET(nlbp->s, &r); - if (nlbp->s6 != INVALID_SOCKET) - FD_SET(nlbp->s6, &r); - if (select(0, &r, nullptr, nullptr, nullptr) == SOCKET_ERROR) { - Netlib_Logf(nlbp->nlu, "NetlibBindAcceptThread (%p): select failed (%d)", (void*)nlbp->s, GetLastError()); - break; - } - - sockaddr_in sin; - int sinLen = sizeof(sin); - memset(&sin, 0, sizeof(sin)); - - SOCKET s; - if (FD_ISSET(nlbp->s, &r)) { - s = accept(nlbp->s, (sockaddr*)&sin, &sinLen); - if (s == INVALID_SOCKET) { - Netlib_Logf(nlbp->nlu, "NetlibBindAcceptThread (%p): accept V4 failed (%d)", (void*)nlbp->s, GetLastError()); - break; - } - } - else if (FD_ISSET(nlbp->s6, &r)) { - s = accept(nlbp->s6, (sockaddr*)&sin, &sinLen); - if (s == INVALID_SOCKET) { - Netlib_Logf(nlbp->nlu, "NetlibBindAcceptThread (%p): accept V6 failed (%d)", (void*)nlbp->s, GetLastError()); - break; - } - } - else s = 0; - - Netlib_Logf(nlbp->nlu, "New incoming connection on port %u from %s (%p)", nlbp->wPort, ptrA(Netlib_AddressToString(&sin)).get(), (void*)s); - - NetlibConnection *nlc = new NetlibConnection(); - nlc->nlu = nlbp->nlu; - nlc->s = s; - - if (nlbp->pfnNewConnection) - nlbp->pfnNewConnection(nlc, ntohl(sin.sin_addr.S_un.S_addr), nlbp->pExtra); - } - - NetlibUPnPDeletePortMapping(nlbp->wExPort, "TCP"); - nlbp->hThread = nullptr; - - Netlib_Logf(nlbp->nlu, "NetlibBindAcceptThread: (%p) thread for port %u closed", (void*)nlbp->s, nlbp->wPort); -} - -MIR_APP_DLL(HNETLIBBIND) Netlib_BindPort(HNETLIBUSER nlu, NETLIBBIND *nlb) -{ - if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_INCOMING) || nlb == nullptr || nlb->pfnNewConnection == nullptr) { - SetLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - - NetlibBoundPort *nlbp = new NetlibBoundPort(nlu, nlb); - if (nlbp->s == INVALID_SOCKET && nlbp->s6 == INVALID_SOCKET) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "socket", WSAGetLastError()); -LBL_Error: - delete nlbp; - return nullptr; - } - - SOCKADDR_IN sin = { 0 }; - sin.sin_family = AF_INET; - - SOCKADDR_IN6 sin6 = { 0 }; - sin6.sin6_family = AF_INET6; - - /* if the netlib user wanted a free port given in the range, then - they better have given wPort == 0, let's hope so */ - int foundPort = 0; - if (nlu->settings.specifyIncomingPorts && nlu->settings.szIncomingPorts && nlb->wPort == 0) { - if (!BindSocketToPort(nlu->settings.szIncomingPorts, nlbp->s, nlbp->s6, &nlu->outportnum)) { - Netlib_Logf(nlu, "Netlib bind: Not enough ports for incoming connections specified"); - SetLastError(WSAEADDRINUSE); - } - else foundPort = 1; - } - else { - /* if ->wPort == 0 then they'll get any free port, otherwise they'll - be asking for whatever was in nlb->wPort*/ - if (nlb->wPort != 0) { - Netlib_Logf(nlu, "%s %d: trying to bind port %d, this 'feature' can be abused, please be sure you want to allow it.", __FILE__, __LINE__, nlb->wPort); - sin.sin_port = htons(nlb->wPort); - sin6.sin6_port = htons(nlb->wPort); - } - - if (nlbp->s != INVALID_SOCKET) - if (bind(nlbp->s, (PSOCKADDR)&sin, sizeof(sin)) == 0) { - SOCKADDR_IN sin2 = { 0 }; - int len = sizeof(sin2); - if (!getsockname(nlbp->s, (PSOCKADDR)&sin2, &len)) - sin6.sin6_port = sin2.sin_port; - foundPort = 1; - } - - if (nlbp->s6 != INVALID_SOCKET) - if (bind(nlbp->s6, (PSOCKADDR)&sin6, sizeof(sin6)) == 0) - foundPort = 1; - } - if (!foundPort) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "bind", WSAGetLastError()); - goto LBL_Error; - } - - if (nlbp->s != INVALID_SOCKET && listen(nlbp->s, 5)) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "listen", WSAGetLastError()); - goto LBL_Error; - } - - if (nlbp->s6 != INVALID_SOCKET && listen(nlbp->s6, 5)) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "listen", WSAGetLastError()); - goto LBL_Error; - } - - SOCKADDR_INET_M sinm = { 0 }; - int len = sizeof(sinm); - if (!getsockname(nlbp->s, (PSOCKADDR)&sinm, &len)) { - nlb->wPort = ntohs(sinm.Ipv4.sin_port); - nlb->dwInternalIP = ntohl(sinm.Ipv4.sin_addr.S_un.S_addr); - } - else if (!getsockname(nlbp->s6, (PSOCKADDR)&sinm, &len)) - nlb->wPort = ntohs(sinm.Ipv6.sin6_port); - else { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "getsockname", WSAGetLastError()); - goto LBL_Error; - } - nlbp->wPort = nlb->wPort; - - if (nlb->dwInternalIP == 0) { - char hostname[64] = ""; - gethostname(hostname, _countof(hostname)); - - PHOSTENT he = gethostbyname(hostname); - if (he && he->h_addr) - nlb->dwInternalIP = ntohl(*(PDWORD)he->h_addr); - } - - uint32_t extIP; - if (nlu->settings.enableUPnP && NetlibUPnPAddPortMapping(nlb->wPort, "TCP", &nlbp->wExPort, &extIP, true)) { - Netlib_Logf(nullptr, "UPnP port mapping succeeded. Internal Port: %u External Port: %u\n", nlb->wPort, nlbp->wExPort); - nlb->wExPort = nlbp->wExPort; - nlb->dwExternalIP = extIP; - } - else { - if (nlu->settings.enableUPnP) - Netlib_Logf(nullptr, "UPnP port mapping failed. Internal Port: %u\n", nlb->wPort); - else - Netlib_Logf(nullptr, "UPnP disabled. Internal Port: %u\n", nlb->wPort); - - nlbp->wExPort = 0; - nlb->wExPort = nlb->wPort; - nlb->dwExternalIP = nlb->dwInternalIP; - } - - nlbp->hThread = mir_forkThread<NetlibBoundPort>(NetlibBindAcceptThread, nlbp); - - if (GetSubscribersCount((THook*)hEventConnected)) { - NETLIBCONNECTIONEVENTINFO ncei = {}; - ncei.connected = 1; - ncei.listening = 1; - ncei.szSettingsModule = nlu->user.szSettingsModule; - memcpy(&ncei.local, &sin, sizeof(sin)); - NotifyFastHook(hEventConnected, (WPARAM)&ncei, 0); - } - - return nlbp; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -NetlibBoundPort::NetlibBoundPort(HNETLIBUSER _nlu, NETLIBBIND *nlb) - : handleType(NLH_BOUNDPORT), - nlu(_nlu) -{ - pfnNewConnection = nlb->pfnNewConnection; - pExtra = nlb->pExtra; - - s = socket(PF_INET, SOCK_STREAM, 0); - s6 = socket(PF_INET6, SOCK_STREAM, 0); -} - -void NetlibBoundPort::close() -{ - if (s != INVALID_SOCKET) { - closesocket(s); - s = INVALID_SOCKET; - } - if (s6 != INVALID_SOCKET) { - closesocket(s6); - s6 = INVALID_SOCKET; - } -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+bool BindSocketToPort(const char *szPorts, SOCKET s, SOCKET s6, int* portn)
+{
+ SOCKADDR_IN sin = {0};
+ sin.sin_family = AF_INET;
+
+ SOCKADDR_IN6 sin6 = {0};
+ sin6.sin6_family = AF_INET6;
+
+ mir_cslock lck(csNetlibUser);
+
+ if (--*portn < 0 && (s != INVALID_SOCKET || s6 != INVALID_SOCKET)) {
+ BindSocketToPort(szPorts, INVALID_SOCKET, INVALID_SOCKET, portn);
+ if (*portn == 0)
+ return false;
+
+ uint16_t num;
+ Utils_GetRandom(&num, sizeof(uint16_t));
+ *portn = num % *portn;
+ }
+
+ bool before = false;
+ while (true) {
+ const char *psz;
+ char *pszEnd;
+ int portMin, portMax, port, portnum = 0;
+
+ for (psz = szPorts;*psz;) {
+ while (*psz == ' ' || *psz == ',') psz++;
+ portMin = strtol(psz, &pszEnd, 0);
+ if (pszEnd == psz)
+ break;
+ while (*pszEnd == ' ')
+ pszEnd++;
+ if (*pszEnd == '-') {
+ psz = pszEnd + 1;
+ portMax = strtol(psz, &pszEnd, 0);
+ if (pszEnd == psz) portMax = 65535;
+ if (portMin > portMax) {
+ port = portMin;
+ portMin = portMax;
+ portMax = port;
+ }
+ }
+ else portMax = portMin;
+ if (portMax >= 1) {
+ if (portMin <= 0) portMin = 1;
+ for (port = portMin; port <= portMax; port++) {
+ if (port > 65535)
+ break;
+
+ ++portnum;
+
+ if (s == INVALID_SOCKET) continue;
+ if (!before && portnum <= *portn) continue;
+ if (before && portnum >= *portn)
+ return false;
+
+ sin.sin_port = htons((uint16_t)port);
+ bool bV4Mapped = s == INVALID_SOCKET || bind(s, (SOCKADDR*)&sin, sizeof(sin)) == 0;
+
+ sin6.sin6_port = htons((uint16_t)port);
+ bool bV6Mapped = s6 == INVALID_SOCKET || bind(s6, (PSOCKADDR)&sin6, sizeof(sin6)) == 0;
+
+ if (bV4Mapped && bV6Mapped) {
+ *portn = portnum + 1;
+ return true;
+ }
+ }
+ }
+ psz = pszEnd;
+ }
+
+ if (*portn < 0) {
+ *portn = portnum;
+ return true;
+ }
+
+ if (*portn >= portnum)
+ *portn = 0;
+ else
+ before = true;
+ }
+}
+
+int NetlibFreeBoundPort(NetlibBoundPort *nlbp)
+{
+ NETLIBCONNECTIONEVENTINFO ncei;
+
+ ZeroMemory(&ncei, sizeof(ncei));
+ ncei.connected = 0;
+ ncei.listening = 1;
+ ncei.szSettingsModule = nlbp->nlu->user.szSettingsModule;
+ int size = sizeof(SOCKADDR_IN);
+ getsockname(nlbp->s, (SOCKADDR *)&ncei.local, &size);
+ NotifyFastHook(hEventDisconnected, (WPARAM)&ncei, 0);
+
+ nlbp->close();
+ if (nlbp->hThread)
+ WaitForSingleObject(nlbp->hThread, INFINITE);
+ Netlib_Logf(nlbp->nlu, "(%u) Port %u closed for incoming connections", nlbp->s, nlbp->wPort);
+ delete nlbp;
+ return 1;
+}
+
+static void __cdecl NetlibBindAcceptThread(NetlibBoundPort *nlbp)
+{
+ Netlib_Logf(nlbp->nlu, "(%u) Port %u opened for incoming connections", nlbp->s, nlbp->wPort);
+
+ while (true) {
+ fd_set r;
+ FD_ZERO(&r);
+ if (nlbp->s != INVALID_SOCKET)
+ FD_SET(nlbp->s, &r);
+ if (nlbp->s6 != INVALID_SOCKET)
+ FD_SET(nlbp->s6, &r);
+ if (select(0, &r, nullptr, nullptr, nullptr) == SOCKET_ERROR) {
+ Netlib_Logf(nlbp->nlu, "NetlibBindAcceptThread (%p): select failed (%d)", (void*)nlbp->s, GetLastError());
+ break;
+ }
+
+ sockaddr_in sin;
+ int sinLen = sizeof(sin);
+ memset(&sin, 0, sizeof(sin));
+
+ SOCKET s;
+ if (FD_ISSET(nlbp->s, &r)) {
+ s = accept(nlbp->s, (sockaddr*)&sin, &sinLen);
+ if (s == INVALID_SOCKET) {
+ Netlib_Logf(nlbp->nlu, "NetlibBindAcceptThread (%p): accept V4 failed (%d)", (void*)nlbp->s, GetLastError());
+ break;
+ }
+ }
+ else if (FD_ISSET(nlbp->s6, &r)) {
+ s = accept(nlbp->s6, (sockaddr*)&sin, &sinLen);
+ if (s == INVALID_SOCKET) {
+ Netlib_Logf(nlbp->nlu, "NetlibBindAcceptThread (%p): accept V6 failed (%d)", (void*)nlbp->s, GetLastError());
+ break;
+ }
+ }
+ else s = 0;
+
+ Netlib_Logf(nlbp->nlu, "New incoming connection on port %u from %s (%p)", nlbp->wPort, ptrA(Netlib_AddressToString(&sin)).get(), (void*)s);
+
+ NetlibConnection *nlc = new NetlibConnection();
+ nlc->nlu = nlbp->nlu;
+ nlc->s = s;
+
+ if (nlbp->pfnNewConnection)
+ nlbp->pfnNewConnection(nlc, ntohl(sin.sin_addr.S_un.S_addr), nlbp->pExtra);
+ }
+
+ NetlibUPnPDeletePortMapping(nlbp->wExPort, "TCP");
+ nlbp->hThread = nullptr;
+
+ Netlib_Logf(nlbp->nlu, "NetlibBindAcceptThread: (%p) thread for port %u closed", (void*)nlbp->s, nlbp->wPort);
+}
+
+MIR_APP_DLL(HNETLIBBIND) Netlib_BindPort(HNETLIBUSER nlu, NETLIBBIND *nlb)
+{
+ if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_INCOMING) || nlb == nullptr || nlb->pfnNewConnection == nullptr) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return nullptr;
+ }
+
+ NetlibBoundPort *nlbp = new NetlibBoundPort(nlu, nlb);
+ if (nlbp->s == INVALID_SOCKET && nlbp->s6 == INVALID_SOCKET) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "socket", WSAGetLastError());
+LBL_Error:
+ delete nlbp;
+ return nullptr;
+ }
+
+ SOCKADDR_IN sin = { 0 };
+ sin.sin_family = AF_INET;
+
+ SOCKADDR_IN6 sin6 = { 0 };
+ sin6.sin6_family = AF_INET6;
+
+ /* if the netlib user wanted a free port given in the range, then
+ they better have given wPort == 0, let's hope so */
+ int foundPort = 0;
+ if (nlu->settings.specifyIncomingPorts && nlu->settings.szIncomingPorts && nlb->wPort == 0) {
+ if (!BindSocketToPort(nlu->settings.szIncomingPorts, nlbp->s, nlbp->s6, &nlu->outportnum)) {
+ Netlib_Logf(nlu, "Netlib bind: Not enough ports for incoming connections specified");
+ SetLastError(WSAEADDRINUSE);
+ }
+ else foundPort = 1;
+ }
+ else {
+ /* if ->wPort == 0 then they'll get any free port, otherwise they'll
+ be asking for whatever was in nlb->wPort*/
+ if (nlb->wPort != 0) {
+ Netlib_Logf(nlu, "%s %d: trying to bind port %d, this 'feature' can be abused, please be sure you want to allow it.", __FILE__, __LINE__, nlb->wPort);
+ sin.sin_port = htons(nlb->wPort);
+ sin6.sin6_port = htons(nlb->wPort);
+ }
+
+ if (nlbp->s != INVALID_SOCKET)
+ if (bind(nlbp->s, (PSOCKADDR)&sin, sizeof(sin)) == 0) {
+ SOCKADDR_IN sin2 = { 0 };
+ int len = sizeof(sin2);
+ if (!getsockname(nlbp->s, (PSOCKADDR)&sin2, &len))
+ sin6.sin6_port = sin2.sin_port;
+ foundPort = 1;
+ }
+
+ if (nlbp->s6 != INVALID_SOCKET)
+ if (bind(nlbp->s6, (PSOCKADDR)&sin6, sizeof(sin6)) == 0)
+ foundPort = 1;
+ }
+ if (!foundPort) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "bind", WSAGetLastError());
+ goto LBL_Error;
+ }
+
+ if (nlbp->s != INVALID_SOCKET && listen(nlbp->s, 5)) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "listen", WSAGetLastError());
+ goto LBL_Error;
+ }
+
+ if (nlbp->s6 != INVALID_SOCKET && listen(nlbp->s6, 5)) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "listen", WSAGetLastError());
+ goto LBL_Error;
+ }
+
+ SOCKADDR_INET_M sinm = { 0 };
+ int len = sizeof(sinm);
+ if (!getsockname(nlbp->s, (PSOCKADDR)&sinm, &len)) {
+ nlb->wPort = ntohs(sinm.Ipv4.sin_port);
+ nlb->dwInternalIP = ntohl(sinm.Ipv4.sin_addr.S_un.S_addr);
+ }
+ else if (!getsockname(nlbp->s6, (PSOCKADDR)&sinm, &len))
+ nlb->wPort = ntohs(sinm.Ipv6.sin6_port);
+ else {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "getsockname", WSAGetLastError());
+ goto LBL_Error;
+ }
+ nlbp->wPort = nlb->wPort;
+
+ if (nlb->dwInternalIP == 0) {
+ char hostname[64] = "";
+ gethostname(hostname, _countof(hostname));
+
+ PHOSTENT he = gethostbyname(hostname);
+ if (he && he->h_addr)
+ nlb->dwInternalIP = ntohl(*(PDWORD)he->h_addr);
+ }
+
+ uint32_t extIP;
+ if (nlu->settings.enableUPnP && NetlibUPnPAddPortMapping(nlb->wPort, "TCP", &nlbp->wExPort, &extIP, true)) {
+ Netlib_Logf(nullptr, "UPnP port mapping succeeded. Internal Port: %u External Port: %u\n", nlb->wPort, nlbp->wExPort);
+ nlb->wExPort = nlbp->wExPort;
+ nlb->dwExternalIP = extIP;
+ }
+ else {
+ if (nlu->settings.enableUPnP)
+ Netlib_Logf(nullptr, "UPnP port mapping failed. Internal Port: %u\n", nlb->wPort);
+ else
+ Netlib_Logf(nullptr, "UPnP disabled. Internal Port: %u\n", nlb->wPort);
+
+ nlbp->wExPort = 0;
+ nlb->wExPort = nlb->wPort;
+ nlb->dwExternalIP = nlb->dwInternalIP;
+ }
+
+ nlbp->hThread = mir_forkThread<NetlibBoundPort>(NetlibBindAcceptThread, nlbp);
+
+ if (GetSubscribersCount((THook*)hEventConnected)) {
+ NETLIBCONNECTIONEVENTINFO ncei = {};
+ ncei.connected = 1;
+ ncei.listening = 1;
+ ncei.szSettingsModule = nlu->user.szSettingsModule;
+ memcpy(&ncei.local, &sin, sizeof(sin));
+ NotifyFastHook(hEventConnected, (WPARAM)&ncei, 0);
+ }
+
+ return nlbp;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+NetlibBoundPort::NetlibBoundPort(HNETLIBUSER _nlu, NETLIBBIND *nlb)
+ : handleType(NLH_BOUNDPORT),
+ nlu(_nlu)
+{
+ pfnNewConnection = nlb->pfnNewConnection;
+ pExtra = nlb->pExtra;
+
+ s = socket(PF_INET, SOCK_STREAM, 0);
+ s6 = socket(PF_INET6, SOCK_STREAM, 0);
+}
+
+void NetlibBoundPort::close()
+{
+ if (s != INVALID_SOCKET) {
+ closesocket(s);
+ s = INVALID_SOCKET;
+ }
+ if (s6 != INVALID_SOCKET) {
+ closesocket(s6);
+ s6 = INVALID_SOCKET;
+ }
+}
diff --git a/src/mir_app/src/netlib_http.cpp b/src/mir_app/src/netlib_http.cpp index 7c94db57ca..260d3a6199 100644 --- a/src/mir_app/src/netlib_http.cpp +++ b/src/mir_app/src/netlib_http.cpp @@ -1,1150 +1,1150 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "../libs/zlib/src/zlib.h" -#include "netlib.h" - -#define HTTPRECVHEADERSTIMEOUT 30000 //in ms -#define HTTPRECVDATATIMEOUT 20000 - -struct ProxyAuth -{ - char *szServer; - char *szMethod; - - ProxyAuth(const char *pszServer, const char *pszMethod) - { - szServer = mir_strdup(pszServer); - szMethod = mir_strdup(pszMethod); - } - ~ProxyAuth() - { - mir_free(szServer); - mir_free(szMethod); - } - static int Compare(const ProxyAuth *p1, const ProxyAuth *p2) - { - return mir_strcmpi(p1->szServer, p2->szServer); - } -}; - -struct ProxyAuthList : OBJLIST<ProxyAuth> -{ - ProxyAuthList() : OBJLIST<ProxyAuth>(2, ProxyAuth::Compare) {} - - void add(const char *szServer, const char *szMethod) - { - if (szServer == nullptr) return; - int i = getIndex((ProxyAuth*)&szServer); - if (i >= 0) { - ProxyAuth &rec = (*this)[i]; - if (szMethod == nullptr) - remove(i); - else if (_stricmp(rec.szMethod, szMethod)) { - mir_free(rec.szMethod); - rec.szMethod = mir_strdup(szMethod); - } - } - else insert(new ProxyAuth(szServer, szMethod)); - } - - const char* find(const char *szServer) - { - ProxyAuth *rec = szServer ? OBJLIST<ProxyAuth>::find((ProxyAuth*)&szServer) : nullptr; - return rec ? rec->szMethod : nullptr; - } -}; - -ProxyAuthList proxyAuthList; - -static int RecvWithTimeoutTime(NetlibConnection *nlc, int dwTimeoutTime, char *buf, int len, int flags) -{ - int dwTimeNow; - - if (nlc->foreBuf.isEmpty() && !Netlib_SslPending(nlc->hSsl)) { - while ((dwTimeNow = GetTickCount()) < dwTimeoutTime) { - int dwDeltaTime = min(dwTimeoutTime - dwTimeNow, 1000); - int res = WaitUntilReadable(nlc->s, dwDeltaTime); - - switch (res) { - case SOCKET_ERROR: - return SOCKET_ERROR; - - case 1: - return Netlib_Recv(nlc, buf, len, flags); - } - - if (nlc->termRequested || Miranda_IsTerminated()) - return 0; - } - SetLastError(ERROR_TIMEOUT); - return SOCKET_ERROR; - } - return Netlib_Recv(nlc, buf, len, flags); -} - -MIR_APP_DLL(char *) Netlib_GetHeader(const NETLIBHTTPREQUEST *nlhr, const char *hdr) -{ - if (nlhr == nullptr || hdr == nullptr) - return nullptr; - - for (int i=0; i < nlhr->headersCount; i++) { - NETLIBHTTPHEADER &p = nlhr->headers[i]; - if (_stricmp(p.szName, hdr) == 0) - return p.szValue; - } - - return nullptr; -} - -static char* NetlibHttpFindAuthHeader(NETLIBHTTPREQUEST *nlhrReply, const char *hdr, const char *szProvider) -{ - char *szBasicHdr = nullptr; - char *szNegoHdr = nullptr; - char *szNtlmHdr = nullptr; - - for (int i=0; i < nlhrReply->headersCount; i++) { - NETLIBHTTPHEADER &p = nlhrReply->headers[i]; - if (_stricmp(p.szName, hdr) == 0) { - if (_strnicmp(p.szValue, "Negotiate", 9) == 0) - szNegoHdr = p.szValue; - else if (_strnicmp(p.szValue, "NTLM", 4) == 0) - szNtlmHdr = p.szValue; - else if (_strnicmp(p.szValue, "Basic", 5) == 0) - szBasicHdr = p.szValue; - } - } - - if (szNegoHdr && (!szProvider || !_stricmp(szProvider, "Negotiate"))) return szNegoHdr; - if (szNtlmHdr && (!szProvider || !_stricmp(szProvider, "NTLM"))) return szNtlmHdr; - if (!szProvider || !_stricmp(szProvider, "Basic")) return szBasicHdr; - return nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void NetlibConnFromUrl(const char *szUrl, bool secur, NetlibUrl &url) -{ - secur = secur || _strnicmp(szUrl, "https", 5) == 0; - - const char* phost = strstr(szUrl, "://"); - url.szHost = phost ? phost + 3 : szUrl; - - int idx = url.szHost.Find('/'); - if (idx != -1) - url.szHost.Truncate(idx); - - if ((idx = url.szHost.Find(':')) != -1) { - url.port = strtol(url.szHost.c_str() + idx + 1, nullptr, 10); - url.szHost.Truncate(idx); - } - else url.port = secur ? 443 : 80; - url.flags = (secur ? NLOCF_SSL : 0); -} - -static NetlibConnection* NetlibHttpProcessUrl(NETLIBHTTPREQUEST *nlhr, NetlibUser *nlu, NetlibConnection *nlc, const char *szUrl = nullptr) -{ - NetlibUrl url; - - if (szUrl == nullptr) - NetlibConnFromUrl(nlhr->szUrl, (nlhr->flags & NLHRF_SSL) != 0, url); - else - NetlibConnFromUrl(szUrl, false, url); - - url.flags |= NLOCF_HTTP; - if (url.flags & NLOCF_SSL) - nlhr->flags |= NLHRF_SSL; - else - nlhr->flags &= ~NLHRF_SSL; - - if (nlc != nullptr) { - bool httpProxy = !(url.flags & NLOCF_SSL) && nlc->proxyType == PROXYTYPE_HTTP; - bool sameHost = mir_strcmp(nlc->url.szHost, url.szHost) == 0 && nlc->url.port == url.port; - - if (!httpProxy && !sameHost) { - NetlibDoCloseSocket(nlc); - - nlc->url = url; - return NetlibDoConnect(nlc) ? nlc : nullptr; - } - } - else nlc = (NetlibConnection*)Netlib_OpenConnection(nlu, url.szHost, url.port, 0, url.flags); - - return nlc; -} - -struct HttpSecurityContext -{ - HANDLE m_hNtlmSecurity; - char *m_szHost; - char *m_szProvider; - - HttpSecurityContext() - { - m_hNtlmSecurity = nullptr; m_szHost = nullptr; m_szProvider = nullptr; - } - - ~HttpSecurityContext() { Destroy(); } - - void Destroy(void) - { - if (!m_hNtlmSecurity) return; - - Netlib_DestroySecurityProvider(m_hNtlmSecurity); - m_hNtlmSecurity = nullptr; - mir_free(m_szHost); m_szHost = nullptr; - mir_free(m_szProvider); m_szProvider = nullptr; - } - - bool TryBasic(void) - { - return m_hNtlmSecurity && m_szProvider && _stricmp(m_szProvider, "Basic"); - } - - char* Execute(NetlibConnection *nlc, char *szHost, const char *szProvider, const char *szChallenge, unsigned &complete) - { - char *szAuthHdr = nullptr; - bool justCreated = false; - NetlibUser *nlu = nlc->nlu; - - if (m_hNtlmSecurity) { - bool newAuth = !m_szProvider || !szProvider || _stricmp(m_szProvider, szProvider); - newAuth = newAuth || (m_szHost != szHost && (!m_szHost || !szHost || _stricmp(m_szHost, szHost))); - if (newAuth) - Destroy(); - } - - if (m_hNtlmSecurity == nullptr) { - CMStringA szSpnStr; - if (szHost && _stricmp(szProvider, "Basic")) { - unsigned long ip = inet_addr(szHost); - PHOSTENT host = (ip == INADDR_NONE) ? gethostbyname(szHost) : gethostbyaddr((char*)&ip, 4, AF_INET); - szSpnStr.Format("HTTP/%s", host && host->h_name ? host->h_name : szHost); - _strlwr(szSpnStr.GetBuffer() + 5); - Netlib_Logf(nlu, "Host SPN: %s", szSpnStr.c_str()); - } - m_hNtlmSecurity = Netlib_InitSecurityProvider(_A2T(szProvider), szSpnStr.IsEmpty() ? nullptr : _A2T(szSpnStr.c_str())); - if (m_hNtlmSecurity) { - m_szProvider = mir_strdup(szProvider); - m_szHost = mir_strdup(szHost); - justCreated = true; - } - } - - if (m_hNtlmSecurity) { - ptrW szLogin, szPassw; - - if (nlu->settings.useProxyAuth) { - mir_cslock lck(csNetlibUser); - szLogin = mir_a2u(nlu->settings.szProxyAuthUser); - szPassw = mir_a2u(nlu->settings.szProxyAuthPassword); - } - - szAuthHdr = NtlmCreateResponseFromChallenge(m_hNtlmSecurity, szChallenge, szLogin, szPassw, true, complete); - if (!szAuthHdr) - Netlib_Logf(nullptr, "Security login %s failed, user: %S pssw: %S", szProvider, szLogin ? szLogin.get() : L"(no user)", szPassw ? L"(exist)" : L"(no psw)"); - else if (justCreated) - proxyAuthList.add(m_szHost, m_szProvider); - } - else complete = 1; - - return szAuthHdr; - } -}; - -static int HttpPeekFirstResponseLine(NetlibConnection *nlc, uint32_t dwTimeoutTime, uint32_t recvFlags, int *resultCode, char **ppszResultDescr, int *length) -{ - int bytesPeeked; - char buffer[2048], *peol; - - while (true) { - bytesPeeked = RecvWithTimeoutTime(nlc, dwTimeoutTime, buffer, _countof(buffer) - 1, MSG_PEEK | recvFlags); - if (bytesPeeked == 0) { - SetLastError(ERROR_HANDLE_EOF); - return 0; - } - if (bytesPeeked == SOCKET_ERROR) - return 0; - - buffer[bytesPeeked] = '\0'; - if ((peol = strchr(buffer, '\n')) != nullptr) - break; - - if ((int)mir_strlen(buffer) < bytesPeeked) { - SetLastError(ERROR_BAD_FORMAT); - return 0; - } - if (bytesPeeked == _countof(buffer) - 1) { - SetLastError(ERROR_BUFFER_OVERFLOW); - return 0; - } - if (Miranda_IsTerminated()) - return 0; - Sleep(10); - } - - if (peol == buffer) { - SetLastError(ERROR_BAD_FORMAT); - return 0; - } - - *peol = '\0'; - - if (_strnicmp(buffer, "HTTP/", 5)) { - SetLastError(ERROR_BAD_FORMAT); - return 0; - } - - size_t off = strcspn(buffer, " \t"); - if (off >= (unsigned)bytesPeeked) - return 0; - - char *pResultCode = buffer + off; - *(pResultCode++) = 0; - - char *pResultDescr; - *resultCode = strtol(pResultCode, &pResultDescr, 10); - - if (ppszResultDescr) - *ppszResultDescr = mir_strdup(lrtrimp(pResultDescr)); - - if (length) - *length = peol - buffer + 1; - return 1; -} - -static int SendHttpRequestAndData(NetlibConnection *nlc, CMStringA &httpRequest, NETLIBHTTPREQUEST *nlhr, int sendContentLengthHeader) -{ - bool sendData = (nlhr->requestType == REQUEST_POST || nlhr->requestType == REQUEST_PUT || nlhr->requestType == REQUEST_PATCH); - - if (sendContentLengthHeader && sendData) - httpRequest.AppendFormat("Content-Length: %d\r\n\r\n", nlhr->dataLength); - else - httpRequest.AppendFormat("\r\n"); - - uint32_t hflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) | - (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND | NLHRF_NODUMPHEADERS) ? - MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | - (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); - - int bytesSent = Netlib_Send(nlc, httpRequest, httpRequest.GetLength(), hflags); - if (bytesSent != SOCKET_ERROR && sendData && nlhr->dataLength) { - uint32_t sflags = MSG_NOTITLE | (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) | - (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND) ? - MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | - (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); - - int sendResult = Netlib_Send(nlc, nlhr->pData, nlhr->dataLength, sflags); - - bytesSent = sendResult != SOCKET_ERROR ? bytesSent + sendResult : SOCKET_ERROR; - } - - return bytesSent; -} - -MIR_APP_DLL(int) Netlib_SendHttpRequest(HNETLIBCONN nlc, NETLIBHTTPREQUEST *nlhr) -{ - NETLIBHTTPREQUEST *nlhrReply = nullptr; - HttpSecurityContext httpSecurity; - - char *szHost = nullptr, *szNewUrl = nullptr; - char *pszProxyAuthHdr = nullptr, *pszAuthHdr = nullptr; - int i, doneHostHeader, doneContentLengthHeader, doneProxyAuthHeader, doneAuthHeader; - int bytesSent = 0; - bool lastFirstLineFail = false; - - if (nlhr == nullptr || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) || nlhr->szUrl == nullptr || nlhr->szUrl[0] == '\0') { - SetLastError(ERROR_INVALID_PARAMETER); - return SOCKET_ERROR; - } - - NetlibUser *nlu = nlc->nlu; - if (GetNetlibHandleType(nlu) != NLH_USER) { - SetLastError(ERROR_INVALID_PARAMETER); - return SOCKET_ERROR; - } - - int hdrTimeout = (nlhr->timeout) ? nlhr->timeout : HTTPRECVHEADERSTIMEOUT; - - const char *pszRequest; - switch (nlhr->requestType) { - case REQUEST_GET: pszRequest = "GET"; break; - case REQUEST_POST: pszRequest = "POST"; break; - case REQUEST_CONNECT: pszRequest = "CONNECT"; break; - case REQUEST_HEAD: pszRequest = "HEAD"; break; - case REQUEST_PUT: pszRequest = "PUT"; break; - case REQUEST_DELETE: pszRequest = "DELETE"; break; - case REQUEST_PATCH: pszRequest = "PATCH"; break; - default: - SetLastError(ERROR_INVALID_PARAMETER); - return SOCKET_ERROR; - } - - if (!NetlibEnterNestedCS(nlc, NLNCS_SEND)) - return SOCKET_ERROR; - - const char *pszFullUrl = nlhr->szUrl; - const char *pszUrl = nullptr; - - unsigned complete = false; - int count = 11; - while (--count) { - if (GetNetlibHandleType(nlc) != NLH_CONNECTION) { - nlc = nullptr; - bytesSent = SOCKET_ERROR; - break; - } - - if (!NetlibReconnect(nlc)) { - bytesSent = SOCKET_ERROR; - break; - } - - if (!pszUrl) { - pszUrl = pszFullUrl; - if (!(nlhr->flags & NLHRF_MANUALHOST)) { - bool usingProxy = nlc->proxyType == PROXYTYPE_HTTP && !(nlhr->flags & NLHRF_SSL); - - const char *ppath, *phost; - phost = strstr(pszUrl, "://"); - if (phost == nullptr) phost = pszUrl; - else phost += 3; - ppath = strchr(phost, '/'); - if (ppath == phost) - phost = nullptr; - - replaceStr(szHost, phost); - if (ppath && phost) - szHost[ppath - phost] = 0; - - if ((nlhr->flags & NLHRF_SMARTREMOVEHOST) && !usingProxy) - pszUrl = ppath ? ppath : "/"; - - if (usingProxy && phost && !nlc->dnsThroughProxy) { - char *tszHost = mir_strdup(phost); - if (ppath) - tszHost[ppath - phost] = 0; - char *cln = strchr(tszHost, ':'); if (cln) *cln = 0; - - if (inet_addr(tszHost) == INADDR_NONE) { - in_addr ip; - if (ip.S_un.S_addr = DnsLookup(nlu, tszHost)) { - mir_free(szHost); - if (cln) *cln = ':'; - szHost = CMStringA(FORMAT, "%s%s", inet_ntoa(ip), cln ? cln : "").Detach(); - } - } - mir_free(tszHost); - } - } - } - - if (nlc->proxyAuthNeeded && proxyAuthList.getCount()) { - if (httpSecurity.m_szProvider == nullptr && nlc->szProxyServer) { - const char *szAuthMethodNlu = proxyAuthList.find(nlc->szProxyServer); - if (szAuthMethodNlu) { - mir_free(pszProxyAuthHdr); - pszProxyAuthHdr = httpSecurity.Execute(nlc, nlc->szProxyServer, szAuthMethodNlu, "", complete); - } - } - } - nlc->proxyAuthNeeded = false; - - CMStringA httpRequest(FORMAT, "%s %s HTTP/1.%d\r\n", pszRequest, pszUrl, (nlhr->flags & NLHRF_HTTP11) != 0); - - // HTTP headers - doneHostHeader = doneContentLengthHeader = doneProxyAuthHeader = doneAuthHeader = 0; - for (i = 0; i < nlhr->headersCount; i++) { - NETLIBHTTPHEADER &p = nlhr->headers[i]; - if (!mir_strcmpi(p.szName, "Host")) doneHostHeader = 1; - else if (!mir_strcmpi(p.szName, "Content-Length")) doneContentLengthHeader = 1; - else if (!mir_strcmpi(p.szName, "Proxy-Authorization")) doneProxyAuthHeader = 1; - else if (!mir_strcmpi(p.szName, "Authorization")) doneAuthHeader = 1; - else if (!mir_strcmpi(p.szName, "Connection")) continue; - if (p.szValue == nullptr) continue; - httpRequest.AppendFormat("%s: %s\r\n", p.szName, p.szValue); - } - if (szHost && !doneHostHeader) - httpRequest.AppendFormat("%s: %s\r\n", "Host", szHost); - if (pszProxyAuthHdr && !doneProxyAuthHeader) - httpRequest.AppendFormat("%s: %s\r\n", "Proxy-Authorization", pszProxyAuthHdr); - if (pszAuthHdr && !doneAuthHeader) - httpRequest.AppendFormat("%s: %s\r\n", "Authorization", pszAuthHdr); - httpRequest.AppendFormat("%s: %s\r\n", "Connection", "Keep-Alive"); - httpRequest.AppendFormat("%s: %s\r\n", "Proxy-Connection", "Keep-Alive"); - - // Add Sticky Headers - if (nlu->szStickyHeaders != nullptr) - httpRequest.AppendFormat("%s\r\n", nlu->szStickyHeaders); - - // send it - bytesSent = SendHttpRequestAndData(nlc, httpRequest, nlhr, !doneContentLengthHeader); - if (bytesSent == SOCKET_ERROR) - break; - - // ntlm reply - if (doneContentLengthHeader && nlhr->requestType != REQUEST_HEAD) - break; - - uint32_t fflags = MSG_PEEK | MSG_NODUMP | ((nlhr->flags & NLHRF_NOPROXY) ? MSG_RAW : 0); - uint32_t dwTimeOutTime = hdrTimeout < 0 ? -1 : GetTickCount() + hdrTimeout; - if (!HttpPeekFirstResponseLine(nlc, dwTimeOutTime, fflags, &nlhr->resultCode, nullptr, nullptr)) { - uint32_t err = GetLastError(); - Netlib_Logf(nlu, "%s %d: %s Failed (%u %u)", __FILE__, __LINE__, "HttpPeekFirstResponseLine", err, count); - - // connection died while we were waiting - if (GetNetlibHandleType(nlc) != NLH_CONNECTION) { - nlc = nullptr; - break; - } - - if (err == ERROR_TIMEOUT || err == ERROR_BAD_FORMAT || err == ERROR_BUFFER_OVERFLOW || lastFirstLineFail || nlc->termRequested || nlhr->requestType == REQUEST_CONNECT) { - bytesSent = SOCKET_ERROR; - break; - } - - lastFirstLineFail = true; - continue; - } - - int resultCode = nlhr->resultCode; - lastFirstLineFail = false; - - uint32_t hflags = (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPHEADERS | NLHRF_NODUMPSEND) ? - MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | - (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); - - uint32_t dflags = (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND) ? MSG_NODUMP : MSG_DUMPASTEXT | MSG_DUMPPROXY) | - (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0) | MSG_NODUMP; - - if (resultCode == 100) - nlhrReply = (NETLIBHTTPREQUEST*)Netlib_RecvHttpHeaders(nlc, hflags); - - else if (resultCode == 307 || ((resultCode == 301 || resultCode == 302) && (nlhr->flags & NLHRF_REDIRECT))) { // redirect - pszUrl = nullptr; - - if (nlhr->requestType == REQUEST_HEAD) - nlhrReply = (NETLIBHTTPREQUEST*)Netlib_RecvHttpHeaders(nlc, hflags); - else - nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); - - if (nlhrReply) { - auto *tmpUrl = Netlib_GetHeader(nlhrReply, "Location"); - if (tmpUrl) { - size_t rlen = 0; - if (tmpUrl[0] == '/') { - const char *ppath, *phost; - phost = strstr(pszFullUrl, "://"); - phost = phost ? phost + 3 : pszFullUrl; - ppath = strchr(phost, '/'); - rlen = ppath ? ppath - pszFullUrl : mir_strlen(pszFullUrl); - } - - nlc->szNewUrl = (char*)mir_realloc(nlc->szNewUrl, rlen + mir_strlen(tmpUrl) * 3 + 1); - - strncpy(nlc->szNewUrl, pszFullUrl, rlen); - mir_strcpy(nlc->szNewUrl + rlen, tmpUrl); - pszFullUrl = nlc->szNewUrl; - pszUrl = nullptr; - - if (NetlibHttpProcessUrl(nlhr, nlu, nlc, pszFullUrl) == nullptr) { - bytesSent = SOCKET_ERROR; - break; - } - } - else { - NetlibHttpSetLastErrorUsingHttpResult(resultCode); - bytesSent = SOCKET_ERROR; - break; - } - } - else { - NetlibHttpSetLastErrorUsingHttpResult(resultCode); - bytesSent = SOCKET_ERROR; - break; - } - } - else if (resultCode == 401 && !doneAuthHeader) { //auth required - if (nlhr->requestType == REQUEST_HEAD) - nlhrReply = (NETLIBHTTPREQUEST*)Netlib_RecvHttpHeaders(nlc, hflags); - else - nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); - - replaceStr(pszAuthHdr, nullptr); - if (nlhrReply) { - char *szAuthStr = nullptr; - if (!complete) { - szAuthStr = NetlibHttpFindAuthHeader(nlhrReply, "WWW-Authenticate", httpSecurity.m_szProvider); - if (szAuthStr) { - char *szChallenge = strchr(szAuthStr, ' '); - if (!szChallenge || !*lrtrimp(szChallenge)) - complete = true; - } - } - if (complete && httpSecurity.m_hNtlmSecurity) - szAuthStr = httpSecurity.TryBasic() ? NetlibHttpFindAuthHeader(nlhrReply, "WWW-Authenticate", "Basic") : nullptr; - - if (szAuthStr) { - char *szChallenge = strchr(szAuthStr, ' '); - if (szChallenge) { *szChallenge = 0; szChallenge = lrtrimp(szChallenge + 1); } - - pszAuthHdr = httpSecurity.Execute(nlc, szHost, szAuthStr, szChallenge, complete); - } - } - if (pszAuthHdr == nullptr) { - proxyAuthList.add(szHost, nullptr); - NetlibHttpSetLastErrorUsingHttpResult(resultCode); - bytesSent = SOCKET_ERROR; - break; - } - } - else if (resultCode == 407 && !doneProxyAuthHeader) { //proxy auth required - if (nlhr->requestType == REQUEST_HEAD) - nlhrReply = Netlib_RecvHttpHeaders(nlc, hflags); - else - nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); - - mir_free(pszProxyAuthHdr); pszProxyAuthHdr = nullptr; - if (nlhrReply) { - char *szAuthStr = nullptr; - if (!complete) { - szAuthStr = NetlibHttpFindAuthHeader(nlhrReply, "Proxy-Authenticate", httpSecurity.m_szProvider); - if (szAuthStr) { - char *szChallenge = strchr(szAuthStr, ' '); - if (!szChallenge || !*lrtrimp(szChallenge + 1)) - complete = true; - } - } - if (complete && httpSecurity.m_hNtlmSecurity) - szAuthStr = httpSecurity.TryBasic() ? NetlibHttpFindAuthHeader(nlhrReply, "Proxy-Authenticate", "Basic") : nullptr; - - if (szAuthStr) { - char *szChallenge = strchr(szAuthStr, ' '); - if (szChallenge) { *szChallenge = 0; szChallenge = lrtrimp(szChallenge + 1); } - - pszProxyAuthHdr = httpSecurity.Execute(nlc, nlc->szProxyServer, szAuthStr, szChallenge, complete); - } - } - if (pszProxyAuthHdr == nullptr) { - proxyAuthList.add(nlc->szProxyServer, nullptr); - NetlibHttpSetLastErrorUsingHttpResult(resultCode); - bytesSent = SOCKET_ERROR; - break; - } - } - else break; - - if (pszProxyAuthHdr && resultCode != 407 && !doneProxyAuthHeader) - replaceStr(pszProxyAuthHdr, nullptr); - - if (pszAuthHdr && resultCode != 401 && !doneAuthHeader) - replaceStr(pszAuthHdr, nullptr); - - if (nlhrReply) { - Netlib_FreeHttpRequest(nlhrReply); - nlhrReply = nullptr; - } - } - - if (count == 0) bytesSent = SOCKET_ERROR; - if (nlhrReply) - Netlib_FreeHttpRequest(nlhrReply); - - //clean up - mir_free(pszProxyAuthHdr); - mir_free(pszAuthHdr); - mir_free(szHost); - mir_free(szNewUrl); - - if (nlc) - NetlibLeaveNestedCS(&nlc->ncsSend); - - return bytesSent; -} - -MIR_APP_DLL(bool) Netlib_FreeHttpRequest(NETLIBHTTPREQUEST *nlhr) -{ - if (nlhr == nullptr || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) || nlhr->requestType != REQUEST_RESPONSE) { - SetLastError(ERROR_INVALID_PARAMETER); - return false; - } - - if (nlhr->headers) { - for (int i = 0; i < nlhr->headersCount; i++) { - NETLIBHTTPHEADER &p = nlhr->headers[i]; - mir_free(p.szName); - mir_free(p.szValue); - } - mir_free(nlhr->headers); - } - mir_free(nlhr->pData); - mir_free(nlhr->szResultDescr); - mir_free(nlhr->szUrl); - mir_free(nlhr); - return true; -} - -#define NHRV_BUF_SIZE 8192 - -MIR_APP_DLL(NETLIBHTTPREQUEST*) Netlib_RecvHttpHeaders(HNETLIBCONN hConnection, int flags) -{ - NetlibConnection *nlc = (NetlibConnection*)hConnection; - if (!NetlibEnterNestedCS(nlc, NLNCS_RECV)) - return nullptr; - - uint32_t dwRequestTimeoutTime = GetTickCount() + HTTPRECVDATATIMEOUT; - NETLIBHTTPREQUEST *nlhr = (NETLIBHTTPREQUEST*)mir_calloc(sizeof(NETLIBHTTPREQUEST)); - nlhr->cbSize = sizeof(NETLIBHTTPREQUEST); - nlhr->nlc = nlc; // Needed to id connection in the protocol HTTP gateway wrapper functions - nlhr->requestType = REQUEST_RESPONSE; - - int firstLineLength = 0; - if (!HttpPeekFirstResponseLine(nlc, dwRequestTimeoutTime, flags | MSG_PEEK, &nlhr->resultCode, &nlhr->szResultDescr, &firstLineLength)) { - NetlibLeaveNestedCS(&nlc->ncsRecv); - Netlib_FreeHttpRequest(nlhr); - return nullptr; - } - - char *buffer = (char*)_alloca(NHRV_BUF_SIZE + 1); - int bytesPeeked = Netlib_Recv(nlc, buffer, min(firstLineLength, NHRV_BUF_SIZE), flags | MSG_DUMPASTEXT); - if (bytesPeeked != firstLineLength) { - NetlibLeaveNestedCS(&nlc->ncsRecv); - Netlib_FreeHttpRequest(nlhr); - if (bytesPeeked != SOCKET_ERROR) - SetLastError(ERROR_HANDLE_EOF); - return nullptr; - } - - // Make sure all headers arrived - MBinBuffer buf; - int headersCount = 0; - bytesPeeked = 0; - for (bool headersCompleted = false; !headersCompleted;) { - bytesPeeked = RecvWithTimeoutTime(nlc, dwRequestTimeoutTime, buffer, NHRV_BUF_SIZE, flags | MSG_DUMPASTEXT | MSG_NOTITLE); - if (bytesPeeked == 0) - break; - - if (bytesPeeked == SOCKET_ERROR) { - bytesPeeked = 0; - break; - } - - buf.append(buffer, bytesPeeked); - - headersCount = 0; - for (char *pbuffer = (char*)buf.data();; headersCount++) { - char *peol = strchr(pbuffer, '\n'); - if (peol == nullptr) break; - if (peol == pbuffer || (peol == (pbuffer + 1) && *pbuffer == '\r')) { - bytesPeeked = peol - (char*)buf.data() + 1; - headersCompleted = true; - break; - } - pbuffer = peol + 1; - } - } - - if (bytesPeeked <= 0) { - NetlibLeaveNestedCS(&nlc->ncsRecv); - Netlib_FreeHttpRequest(nlhr); - return nullptr; - } - - // Receive headers - nlhr->headersCount = headersCount; - nlhr->headers = (NETLIBHTTPHEADER*)mir_calloc(sizeof(NETLIBHTTPHEADER) * headersCount); - - headersCount = 0; - for (char *pbuffer = (char*)buf.data();; headersCount++) { - char *peol = strchr(pbuffer, '\n'); - if (peol == nullptr || peol == pbuffer || (peol == (pbuffer+1) && *pbuffer == '\r')) - break; - *peol = 0; - - char *pColon = strchr(pbuffer, ':'); - if (pColon == nullptr) { - Netlib_FreeHttpRequest(nlhr); nlhr = nullptr; - SetLastError(ERROR_INVALID_DATA); - break; - } - - *pColon = 0; - nlhr->headers[headersCount].szName = mir_strdup(rtrim(pbuffer)); - nlhr->headers[headersCount].szValue = mir_strdup(lrtrimp(pColon+1)); - pbuffer = peol + 1; - } - - // remove processed data - buf.remove(bytesPeeked); - nlc->foreBuf.appendBefore(buf.data(), buf.length()); - - NetlibLeaveNestedCS(&nlc->ncsRecv); - return nlhr; -} - -MIR_APP_DLL(NETLIBHTTPREQUEST*) Netlib_HttpTransaction(HNETLIBUSER nlu, NETLIBHTTPREQUEST *nlhr) -{ - if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING) || - nlhr == nullptr || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) || - nlhr->szUrl == nullptr || nlhr->szUrl[0] == 0) - { - SetLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - - if (nlhr->nlc != nullptr && GetNetlibHandleType(nlhr->nlc) != NLH_CONNECTION) - nlhr->nlc = nullptr; - - NetlibConnection *nlc = NetlibHttpProcessUrl(nlhr, nlu, (NetlibConnection*)nlhr->nlc); - if (nlc == nullptr) - return nullptr; - - NETLIBHTTPREQUEST nlhrSend = *nlhr; - nlhrSend.flags |= NLHRF_SMARTREMOVEHOST; - - bool doneUserAgentHeader = Netlib_GetHeader(nlhr, "User-Agent") != nullptr; - bool doneAcceptEncoding = Netlib_GetHeader(nlhr, "Accept-Encoding") != nullptr; - if (!doneUserAgentHeader || !doneAcceptEncoding) { - nlhrSend.headers = (NETLIBHTTPHEADER*)mir_alloc(sizeof(NETLIBHTTPHEADER) * (nlhrSend.headersCount + 2)); - memcpy(nlhrSend.headers, nlhr->headers, sizeof(NETLIBHTTPHEADER) * nlhr->headersCount); - } - - char szUserAgent[64]; - if (!doneUserAgentHeader) { - nlhrSend.headers[nlhrSend.headersCount].szName = "User-Agent"; - nlhrSend.headers[nlhrSend.headersCount].szValue = szUserAgent; - ++nlhrSend.headersCount; - - char szMirandaVer[64]; - strncpy_s(szMirandaVer, MIRANDA_VERSION_STRING, _TRUNCATE); - #if defined(_WIN64) - strncat_s(szMirandaVer, " x64", _TRUNCATE); - #endif - - char *pspace = strchr(szMirandaVer, ' '); - if (pspace) { - *pspace++ = '\0'; - mir_snprintf(szUserAgent, "Miranda/%s (%s)", szMirandaVer, pspace); - } - else mir_snprintf(szUserAgent, "Miranda/%s", szMirandaVer); - } - if (!doneAcceptEncoding) { - nlhrSend.headers[nlhrSend.headersCount].szName = "Accept-Encoding"; - nlhrSend.headers[nlhrSend.headersCount].szValue = "deflate, gzip"; - ++nlhrSend.headersCount; - } - if (Netlib_SendHttpRequest(nlc, &nlhrSend) == SOCKET_ERROR) { - if (!doneUserAgentHeader || !doneAcceptEncoding) mir_free(nlhrSend.headers); - nlhr->resultCode = nlhrSend.resultCode; - Netlib_CloseHandle(nlc); - return nullptr; - } - if (!doneUserAgentHeader || !doneAcceptEncoding) - mir_free(nlhrSend.headers); - - uint32_t dflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) | - (nlhr->flags & NLHRF_NODUMP ? MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | - (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); - - uint32_t hflags = - (nlhr->flags & NLHRF_NODUMP ? MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) | - (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0); - - NETLIBHTTPREQUEST *nlhrReply; - if (nlhr->requestType == REQUEST_HEAD) - nlhrReply = Netlib_RecvHttpHeaders(nlc); - else - nlhrReply = NetlibHttpRecv(nlc, hflags, dflags); - - if (nlhrReply) { - nlhrReply->szUrl = nlc->szNewUrl; - nlc->szNewUrl = nullptr; - } - - if ((nlhr->flags & NLHRF_PERSISTENT) == 0 || nlhrReply == nullptr) { - Netlib_CloseHandle(nlc); - if (nlhrReply) - nlhrReply->nlc = nullptr; - } - else nlhrReply->nlc = nlc; - - return nlhrReply; -} - -void NetlibHttpSetLastErrorUsingHttpResult(int result) -{ - if (result >= 200 && result < 300) { - SetLastError(ERROR_SUCCESS); - return; - } - switch (result) { - case 400: SetLastError(ERROR_BAD_FORMAT); break; - case 401: - case 402: - case 403: - case 407: SetLastError(ERROR_ACCESS_DENIED); break; - case 404: SetLastError(ERROR_FILE_NOT_FOUND); break; - case 405: - case 406: SetLastError(ERROR_INVALID_FUNCTION); break; - case 408: SetLastError(ERROR_TIMEOUT); break; - default: SetLastError(ERROR_GEN_FAILURE); break; - } -} - -char* gzip_decode(char *gzip_data, int *len_ptr, int window) -{ - if (*len_ptr == 0) return nullptr; - - int gzip_len = *len_ptr * 5; - char* output_data = nullptr; - - int gzip_err; - z_stream zstr; - - do { - output_data = (char*)mir_realloc(output_data, gzip_len+1); - if (output_data == nullptr) - break; - - zstr.next_in = (Bytef*)gzip_data; - zstr.avail_in = *len_ptr; - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - zstr.opaque = Z_NULL; - inflateInit2_(&zstr, window, ZLIB_VERSION, sizeof(z_stream)); - - zstr.next_out = (Bytef*)output_data; - zstr.avail_out = gzip_len; - - gzip_err = inflate(&zstr, Z_FINISH); - - inflateEnd(&zstr); - gzip_len *= 2; - if (gzip_len > 10000000) - break; - } while (gzip_err == Z_BUF_ERROR); - - gzip_len = gzip_err == Z_STREAM_END ? zstr.total_out : -1; - - if (gzip_len <= 0) { - mir_free(output_data); - output_data = nullptr; - } - else output_data[gzip_len] = 0; - - *len_ptr = gzip_len; - return output_data; -} - -static int NetlibHttpRecvChunkHeader(NetlibConnection *nlc, bool first, uint32_t flags) -{ - MBinBuffer buf; - - while (true) { - char data[1000]; - int recvResult = Netlib_Recv(nlc, data, _countof(data) - 1, MSG_RAW | flags); - if (recvResult <= 0 || recvResult >= _countof(data)) - return SOCKET_ERROR; - - buf.append(data, recvResult); // add chunk - - auto *peol1 = (const char*)memchr(buf.data(), '\n', buf.length()); - if (peol1 == nullptr) - continue; - - auto *pStart = (const char *)buf.data(); - int cbRest = int(peol1 - pStart) + 1; - const char *peol2 = first ? peol1 : (const char*)memchr(peol1 + 1, '\n', buf.length() - cbRest); - if (peol2 == nullptr) - continue; - - int sz = peol2 - pStart + 1; - int r = strtol(first ? pStart : peol1 + 1, nullptr, 16); - if (r == 0) { - const char *peol3 = strchr(peol2 + 1, '\n'); - if (peol3 == nullptr) - continue; - sz = peol3 - pStart + 1; - } - buf.remove(sz); // remove all our data from buffer - nlc->foreBuf.appendBefore(buf.data(), buf.length()); - return r; - } -} - -NETLIBHTTPREQUEST* NetlibHttpRecv(NetlibConnection *nlc, uint32_t hflags, uint32_t dflags, bool isConnect) -{ - int dataLen = -1, i, chunkhdr = 0; - bool chunked = false; - int cenc = 0, cenctype = 0, close = 0; - -next: - NETLIBHTTPREQUEST *nlhrReply = Netlib_RecvHttpHeaders(nlc, hflags); - if (nlhrReply == nullptr) - return nullptr; - - if (nlhrReply->resultCode == 100) { - Netlib_FreeHttpRequest(nlhrReply); - goto next; - } - - if (nlhrReply->resultCode == 204) - dataLen = 0; - - for (i = 0; i < nlhrReply->headersCount; i++) { - NETLIBHTTPHEADER &p = nlhrReply->headers[i]; - if (!mir_strcmpi(p.szName, "Content-Length")) - dataLen = atoi(p.szValue); - - if (!mir_strcmpi(p.szName, "Content-Encoding")) { - cenc = i; - if (strstr(p.szValue, "gzip")) - cenctype = 1; - else if (strstr(p.szValue, "deflate")) - cenctype = 2; - } - - if (!mir_strcmpi(p.szName, "Connection")) - close = !mir_strcmpi(p.szValue, "close"); - - if (!mir_strcmpi(p.szName, "Transfer-Encoding") && !mir_strcmpi(p.szValue, "chunked")) { - chunked = true; - chunkhdr = i; - dataLen = -1; - } - } - - if (nlhrReply->resultCode >= 200 && (dataLen > 0 || (!isConnect && dataLen < 0))) { - int recvResult, chunksz = -1; - int dataBufferAlloced; - - if (chunked) { - chunksz = NetlibHttpRecvChunkHeader(nlc, true, dflags | (cenctype ? MSG_NODUMP : 0)); - if (chunksz == SOCKET_ERROR) { - Netlib_FreeHttpRequest(nlhrReply); - return nullptr; - } - dataLen = chunksz; - } - dataBufferAlloced = dataLen < 0 ? 2048 : dataLen + 1; - nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); - - while (chunksz != 0) { - while (true) { - recvResult = RecvWithTimeoutTime(nlc, GetTickCount() + HTTPRECVDATATIMEOUT, - nlhrReply->pData + nlhrReply->dataLength, - dataBufferAlloced - nlhrReply->dataLength - 1, - dflags | (cenctype ? MSG_NODUMP : 0)); - - if (recvResult == 0) break; - if (recvResult == SOCKET_ERROR) { - Netlib_FreeHttpRequest(nlhrReply); - return nullptr; - } - nlhrReply->dataLength += recvResult; - - if (dataLen >= 0) { - if (nlhrReply->dataLength >= dataLen) - break; - } - else if ((dataBufferAlloced - nlhrReply->dataLength) < 256) { - dataBufferAlloced += 2048; - nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); - if (nlhrReply->pData == nullptr) { - SetLastError(ERROR_OUTOFMEMORY); - Netlib_FreeHttpRequest(nlhrReply); - return nullptr; - } - } - Sleep(10); - } - - if (!chunked) - break; - - chunksz = NetlibHttpRecvChunkHeader(nlc, false, dflags | MSG_NODUMP); - if (chunksz == SOCKET_ERROR) { - Netlib_FreeHttpRequest(nlhrReply); - return nullptr; - } - dataLen += chunksz; - dataBufferAlloced += chunksz; - - nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced); - } - - nlhrReply->pData[nlhrReply->dataLength] = '\0'; - } - - if (chunked) { - nlhrReply->headers[chunkhdr].szName = (char*)mir_realloc(nlhrReply->headers[chunkhdr].szName, 16); - mir_strcpy(nlhrReply->headers[chunkhdr].szName, "Content-Length"); - - nlhrReply->headers[chunkhdr].szValue = (char*)mir_realloc(nlhrReply->headers[chunkhdr].szValue, 16); - mir_snprintf(nlhrReply->headers[chunkhdr].szValue, 16, "%u", nlhrReply->dataLength); - } - - if (cenctype) { - int bufsz = nlhrReply->dataLength; - char* szData = nullptr; - - switch (cenctype) { - case 1: - szData = gzip_decode(nlhrReply->pData, &bufsz, 0x10 | MAX_WBITS); - break; - - case 2: - szData = gzip_decode(nlhrReply->pData, &bufsz, -MAX_WBITS); - if (bufsz < 0) { - bufsz = nlhrReply->dataLength; - szData = gzip_decode(nlhrReply->pData, &bufsz, MAX_WBITS); - } - break; - } - - if (bufsz > 0) { - Netlib_Dump(nlc, (uint8_t*)szData, bufsz, false, dflags | MSG_NOTITLE); - mir_free(nlhrReply->pData); - nlhrReply->pData = szData; - nlhrReply->dataLength = bufsz; - - mir_free(nlhrReply->headers[cenc].szName); - mir_free(nlhrReply->headers[cenc].szValue); - memmove(&nlhrReply->headers[cenc], &nlhrReply->headers[cenc+1], (--nlhrReply->headersCount-cenc)*sizeof(nlhrReply->headers[0])); - } - else if (bufsz == 0) { - mir_free(nlhrReply->pData); - nlhrReply->pData = nullptr; - nlhrReply->dataLength = 0; - } - } - - if (close && - (nlc->proxyType != PROXYTYPE_HTTP || nlc->url.flags & NLOCF_SSL) && - (!isConnect || nlhrReply->resultCode != 200)) - NetlibDoCloseSocket(nlc); - - return nlhrReply; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "../libs/zlib/src/zlib.h"
+#include "netlib.h"
+
+#define HTTPRECVHEADERSTIMEOUT 30000 //in ms
+#define HTTPRECVDATATIMEOUT 20000
+
+struct ProxyAuth
+{
+ char *szServer;
+ char *szMethod;
+
+ ProxyAuth(const char *pszServer, const char *pszMethod)
+ {
+ szServer = mir_strdup(pszServer);
+ szMethod = mir_strdup(pszMethod);
+ }
+ ~ProxyAuth()
+ {
+ mir_free(szServer);
+ mir_free(szMethod);
+ }
+ static int Compare(const ProxyAuth *p1, const ProxyAuth *p2)
+ {
+ return mir_strcmpi(p1->szServer, p2->szServer);
+ }
+};
+
+struct ProxyAuthList : OBJLIST<ProxyAuth>
+{
+ ProxyAuthList() : OBJLIST<ProxyAuth>(2, ProxyAuth::Compare) {}
+
+ void add(const char *szServer, const char *szMethod)
+ {
+ if (szServer == nullptr) return;
+ int i = getIndex((ProxyAuth*)&szServer);
+ if (i >= 0) {
+ ProxyAuth &rec = (*this)[i];
+ if (szMethod == nullptr)
+ remove(i);
+ else if (_stricmp(rec.szMethod, szMethod)) {
+ mir_free(rec.szMethod);
+ rec.szMethod = mir_strdup(szMethod);
+ }
+ }
+ else insert(new ProxyAuth(szServer, szMethod));
+ }
+
+ const char* find(const char *szServer)
+ {
+ ProxyAuth *rec = szServer ? OBJLIST<ProxyAuth>::find((ProxyAuth*)&szServer) : nullptr;
+ return rec ? rec->szMethod : nullptr;
+ }
+};
+
+ProxyAuthList proxyAuthList;
+
+static int RecvWithTimeoutTime(NetlibConnection *nlc, int dwTimeoutTime, char *buf, int len, int flags)
+{
+ int dwTimeNow;
+
+ if (nlc->foreBuf.isEmpty() && !Netlib_SslPending(nlc->hSsl)) {
+ while ((dwTimeNow = GetTickCount()) < dwTimeoutTime) {
+ int dwDeltaTime = min(dwTimeoutTime - dwTimeNow, 1000);
+ int res = WaitUntilReadable(nlc->s, dwDeltaTime);
+
+ switch (res) {
+ case SOCKET_ERROR:
+ return SOCKET_ERROR;
+
+ case 1:
+ return Netlib_Recv(nlc, buf, len, flags);
+ }
+
+ if (nlc->termRequested || Miranda_IsTerminated())
+ return 0;
+ }
+ SetLastError(ERROR_TIMEOUT);
+ return SOCKET_ERROR;
+ }
+ return Netlib_Recv(nlc, buf, len, flags);
+}
+
+MIR_APP_DLL(char *) Netlib_GetHeader(const NETLIBHTTPREQUEST *nlhr, const char *hdr)
+{
+ if (nlhr == nullptr || hdr == nullptr)
+ return nullptr;
+
+ for (int i=0; i < nlhr->headersCount; i++) {
+ NETLIBHTTPHEADER &p = nlhr->headers[i];
+ if (_stricmp(p.szName, hdr) == 0)
+ return p.szValue;
+ }
+
+ return nullptr;
+}
+
+static char* NetlibHttpFindAuthHeader(NETLIBHTTPREQUEST *nlhrReply, const char *hdr, const char *szProvider)
+{
+ char *szBasicHdr = nullptr;
+ char *szNegoHdr = nullptr;
+ char *szNtlmHdr = nullptr;
+
+ for (int i=0; i < nlhrReply->headersCount; i++) {
+ NETLIBHTTPHEADER &p = nlhrReply->headers[i];
+ if (_stricmp(p.szName, hdr) == 0) {
+ if (_strnicmp(p.szValue, "Negotiate", 9) == 0)
+ szNegoHdr = p.szValue;
+ else if (_strnicmp(p.szValue, "NTLM", 4) == 0)
+ szNtlmHdr = p.szValue;
+ else if (_strnicmp(p.szValue, "Basic", 5) == 0)
+ szBasicHdr = p.szValue;
+ }
+ }
+
+ if (szNegoHdr && (!szProvider || !_stricmp(szProvider, "Negotiate"))) return szNegoHdr;
+ if (szNtlmHdr && (!szProvider || !_stricmp(szProvider, "NTLM"))) return szNtlmHdr;
+ if (!szProvider || !_stricmp(szProvider, "Basic")) return szBasicHdr;
+ return nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void NetlibConnFromUrl(const char *szUrl, bool secur, NetlibUrl &url)
+{
+ secur = secur || _strnicmp(szUrl, "https", 5) == 0;
+
+ const char* phost = strstr(szUrl, "://");
+ url.szHost = phost ? phost + 3 : szUrl;
+
+ int idx = url.szHost.Find('/');
+ if (idx != -1)
+ url.szHost.Truncate(idx);
+
+ if ((idx = url.szHost.Find(':')) != -1) {
+ url.port = strtol(url.szHost.c_str() + idx + 1, nullptr, 10);
+ url.szHost.Truncate(idx);
+ }
+ else url.port = secur ? 443 : 80;
+ url.flags = (secur ? NLOCF_SSL : 0);
+}
+
+static NetlibConnection* NetlibHttpProcessUrl(NETLIBHTTPREQUEST *nlhr, NetlibUser *nlu, NetlibConnection *nlc, const char *szUrl = nullptr)
+{
+ NetlibUrl url;
+
+ if (szUrl == nullptr)
+ NetlibConnFromUrl(nlhr->szUrl, (nlhr->flags & NLHRF_SSL) != 0, url);
+ else
+ NetlibConnFromUrl(szUrl, false, url);
+
+ url.flags |= NLOCF_HTTP;
+ if (url.flags & NLOCF_SSL)
+ nlhr->flags |= NLHRF_SSL;
+ else
+ nlhr->flags &= ~NLHRF_SSL;
+
+ if (nlc != nullptr) {
+ bool httpProxy = !(url.flags & NLOCF_SSL) && nlc->proxyType == PROXYTYPE_HTTP;
+ bool sameHost = mir_strcmp(nlc->url.szHost, url.szHost) == 0 && nlc->url.port == url.port;
+
+ if (!httpProxy && !sameHost) {
+ NetlibDoCloseSocket(nlc);
+
+ nlc->url = url;
+ return NetlibDoConnect(nlc) ? nlc : nullptr;
+ }
+ }
+ else nlc = (NetlibConnection*)Netlib_OpenConnection(nlu, url.szHost, url.port, 0, url.flags);
+
+ return nlc;
+}
+
+struct HttpSecurityContext
+{
+ HANDLE m_hNtlmSecurity;
+ char *m_szHost;
+ char *m_szProvider;
+
+ HttpSecurityContext()
+ {
+ m_hNtlmSecurity = nullptr; m_szHost = nullptr; m_szProvider = nullptr;
+ }
+
+ ~HttpSecurityContext() { Destroy(); }
+
+ void Destroy(void)
+ {
+ if (!m_hNtlmSecurity) return;
+
+ Netlib_DestroySecurityProvider(m_hNtlmSecurity);
+ m_hNtlmSecurity = nullptr;
+ mir_free(m_szHost); m_szHost = nullptr;
+ mir_free(m_szProvider); m_szProvider = nullptr;
+ }
+
+ bool TryBasic(void)
+ {
+ return m_hNtlmSecurity && m_szProvider && _stricmp(m_szProvider, "Basic");
+ }
+
+ char* Execute(NetlibConnection *nlc, char *szHost, const char *szProvider, const char *szChallenge, unsigned &complete)
+ {
+ char *szAuthHdr = nullptr;
+ bool justCreated = false;
+ NetlibUser *nlu = nlc->nlu;
+
+ if (m_hNtlmSecurity) {
+ bool newAuth = !m_szProvider || !szProvider || _stricmp(m_szProvider, szProvider);
+ newAuth = newAuth || (m_szHost != szHost && (!m_szHost || !szHost || _stricmp(m_szHost, szHost)));
+ if (newAuth)
+ Destroy();
+ }
+
+ if (m_hNtlmSecurity == nullptr) {
+ CMStringA szSpnStr;
+ if (szHost && _stricmp(szProvider, "Basic")) {
+ unsigned long ip = inet_addr(szHost);
+ PHOSTENT host = (ip == INADDR_NONE) ? gethostbyname(szHost) : gethostbyaddr((char*)&ip, 4, AF_INET);
+ szSpnStr.Format("HTTP/%s", host && host->h_name ? host->h_name : szHost);
+ _strlwr(szSpnStr.GetBuffer() + 5);
+ Netlib_Logf(nlu, "Host SPN: %s", szSpnStr.c_str());
+ }
+ m_hNtlmSecurity = Netlib_InitSecurityProvider(_A2T(szProvider), szSpnStr.IsEmpty() ? nullptr : _A2T(szSpnStr.c_str()));
+ if (m_hNtlmSecurity) {
+ m_szProvider = mir_strdup(szProvider);
+ m_szHost = mir_strdup(szHost);
+ justCreated = true;
+ }
+ }
+
+ if (m_hNtlmSecurity) {
+ ptrW szLogin, szPassw;
+
+ if (nlu->settings.useProxyAuth) {
+ mir_cslock lck(csNetlibUser);
+ szLogin = mir_a2u(nlu->settings.szProxyAuthUser);
+ szPassw = mir_a2u(nlu->settings.szProxyAuthPassword);
+ }
+
+ szAuthHdr = NtlmCreateResponseFromChallenge(m_hNtlmSecurity, szChallenge, szLogin, szPassw, true, complete);
+ if (!szAuthHdr)
+ Netlib_Logf(nullptr, "Security login %s failed, user: %S pssw: %S", szProvider, szLogin ? szLogin.get() : L"(no user)", szPassw ? L"(exist)" : L"(no psw)");
+ else if (justCreated)
+ proxyAuthList.add(m_szHost, m_szProvider);
+ }
+ else complete = 1;
+
+ return szAuthHdr;
+ }
+};
+
+static int HttpPeekFirstResponseLine(NetlibConnection *nlc, uint32_t dwTimeoutTime, uint32_t recvFlags, int *resultCode, char **ppszResultDescr, int *length)
+{
+ int bytesPeeked;
+ char buffer[2048], *peol;
+
+ while (true) {
+ bytesPeeked = RecvWithTimeoutTime(nlc, dwTimeoutTime, buffer, _countof(buffer) - 1, MSG_PEEK | recvFlags);
+ if (bytesPeeked == 0) {
+ SetLastError(ERROR_HANDLE_EOF);
+ return 0;
+ }
+ if (bytesPeeked == SOCKET_ERROR)
+ return 0;
+
+ buffer[bytesPeeked] = '\0';
+ if ((peol = strchr(buffer, '\n')) != nullptr)
+ break;
+
+ if ((int)mir_strlen(buffer) < bytesPeeked) {
+ SetLastError(ERROR_BAD_FORMAT);
+ return 0;
+ }
+ if (bytesPeeked == _countof(buffer) - 1) {
+ SetLastError(ERROR_BUFFER_OVERFLOW);
+ return 0;
+ }
+ if (Miranda_IsTerminated())
+ return 0;
+ Sleep(10);
+ }
+
+ if (peol == buffer) {
+ SetLastError(ERROR_BAD_FORMAT);
+ return 0;
+ }
+
+ *peol = '\0';
+
+ if (_strnicmp(buffer, "HTTP/", 5)) {
+ SetLastError(ERROR_BAD_FORMAT);
+ return 0;
+ }
+
+ size_t off = strcspn(buffer, " \t");
+ if (off >= (unsigned)bytesPeeked)
+ return 0;
+
+ char *pResultCode = buffer + off;
+ *(pResultCode++) = 0;
+
+ char *pResultDescr;
+ *resultCode = strtol(pResultCode, &pResultDescr, 10);
+
+ if (ppszResultDescr)
+ *ppszResultDescr = mir_strdup(lrtrimp(pResultDescr));
+
+ if (length)
+ *length = peol - buffer + 1;
+ return 1;
+}
+
+static int SendHttpRequestAndData(NetlibConnection *nlc, CMStringA &httpRequest, NETLIBHTTPREQUEST *nlhr, int sendContentLengthHeader)
+{
+ bool sendData = (nlhr->requestType == REQUEST_POST || nlhr->requestType == REQUEST_PUT || nlhr->requestType == REQUEST_PATCH);
+
+ if (sendContentLengthHeader && sendData)
+ httpRequest.AppendFormat("Content-Length: %d\r\n\r\n", nlhr->dataLength);
+ else
+ httpRequest.AppendFormat("\r\n");
+
+ uint32_t hflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) |
+ (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND | NLHRF_NODUMPHEADERS) ?
+ MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) |
+ (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0);
+
+ int bytesSent = Netlib_Send(nlc, httpRequest, httpRequest.GetLength(), hflags);
+ if (bytesSent != SOCKET_ERROR && sendData && nlhr->dataLength) {
+ uint32_t sflags = MSG_NOTITLE | (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) |
+ (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND) ?
+ MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) |
+ (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0);
+
+ int sendResult = Netlib_Send(nlc, nlhr->pData, nlhr->dataLength, sflags);
+
+ bytesSent = sendResult != SOCKET_ERROR ? bytesSent + sendResult : SOCKET_ERROR;
+ }
+
+ return bytesSent;
+}
+
+MIR_APP_DLL(int) Netlib_SendHttpRequest(HNETLIBCONN nlc, NETLIBHTTPREQUEST *nlhr)
+{
+ NETLIBHTTPREQUEST *nlhrReply = nullptr;
+ HttpSecurityContext httpSecurity;
+
+ char *szHost = nullptr, *szNewUrl = nullptr;
+ char *pszProxyAuthHdr = nullptr, *pszAuthHdr = nullptr;
+ int i, doneHostHeader, doneContentLengthHeader, doneProxyAuthHeader, doneAuthHeader;
+ int bytesSent = 0;
+ bool lastFirstLineFail = false;
+
+ if (nlhr == nullptr || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) || nlhr->szUrl == nullptr || nlhr->szUrl[0] == '\0') {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SOCKET_ERROR;
+ }
+
+ NetlibUser *nlu = nlc->nlu;
+ if (GetNetlibHandleType(nlu) != NLH_USER) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SOCKET_ERROR;
+ }
+
+ int hdrTimeout = (nlhr->timeout) ? nlhr->timeout : HTTPRECVHEADERSTIMEOUT;
+
+ const char *pszRequest;
+ switch (nlhr->requestType) {
+ case REQUEST_GET: pszRequest = "GET"; break;
+ case REQUEST_POST: pszRequest = "POST"; break;
+ case REQUEST_CONNECT: pszRequest = "CONNECT"; break;
+ case REQUEST_HEAD: pszRequest = "HEAD"; break;
+ case REQUEST_PUT: pszRequest = "PUT"; break;
+ case REQUEST_DELETE: pszRequest = "DELETE"; break;
+ case REQUEST_PATCH: pszRequest = "PATCH"; break;
+ default:
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SOCKET_ERROR;
+ }
+
+ if (!NetlibEnterNestedCS(nlc, NLNCS_SEND))
+ return SOCKET_ERROR;
+
+ const char *pszFullUrl = nlhr->szUrl;
+ const char *pszUrl = nullptr;
+
+ unsigned complete = false;
+ int count = 11;
+ while (--count) {
+ if (GetNetlibHandleType(nlc) != NLH_CONNECTION) {
+ nlc = nullptr;
+ bytesSent = SOCKET_ERROR;
+ break;
+ }
+
+ if (!NetlibReconnect(nlc)) {
+ bytesSent = SOCKET_ERROR;
+ break;
+ }
+
+ if (!pszUrl) {
+ pszUrl = pszFullUrl;
+ if (!(nlhr->flags & NLHRF_MANUALHOST)) {
+ bool usingProxy = nlc->proxyType == PROXYTYPE_HTTP && !(nlhr->flags & NLHRF_SSL);
+
+ const char *ppath, *phost;
+ phost = strstr(pszUrl, "://");
+ if (phost == nullptr) phost = pszUrl;
+ else phost += 3;
+ ppath = strchr(phost, '/');
+ if (ppath == phost)
+ phost = nullptr;
+
+ replaceStr(szHost, phost);
+ if (ppath && phost)
+ szHost[ppath - phost] = 0;
+
+ if ((nlhr->flags & NLHRF_SMARTREMOVEHOST) && !usingProxy)
+ pszUrl = ppath ? ppath : "/";
+
+ if (usingProxy && phost && !nlc->dnsThroughProxy) {
+ char *tszHost = mir_strdup(phost);
+ if (ppath)
+ tszHost[ppath - phost] = 0;
+ char *cln = strchr(tszHost, ':'); if (cln) *cln = 0;
+
+ if (inet_addr(tszHost) == INADDR_NONE) {
+ in_addr ip;
+ if (ip.S_un.S_addr = DnsLookup(nlu, tszHost)) {
+ mir_free(szHost);
+ if (cln) *cln = ':';
+ szHost = CMStringA(FORMAT, "%s%s", inet_ntoa(ip), cln ? cln : "").Detach();
+ }
+ }
+ mir_free(tszHost);
+ }
+ }
+ }
+
+ if (nlc->proxyAuthNeeded && proxyAuthList.getCount()) {
+ if (httpSecurity.m_szProvider == nullptr && nlc->szProxyServer) {
+ const char *szAuthMethodNlu = proxyAuthList.find(nlc->szProxyServer);
+ if (szAuthMethodNlu) {
+ mir_free(pszProxyAuthHdr);
+ pszProxyAuthHdr = httpSecurity.Execute(nlc, nlc->szProxyServer, szAuthMethodNlu, "", complete);
+ }
+ }
+ }
+ nlc->proxyAuthNeeded = false;
+
+ CMStringA httpRequest(FORMAT, "%s %s HTTP/1.%d\r\n", pszRequest, pszUrl, (nlhr->flags & NLHRF_HTTP11) != 0);
+
+ // HTTP headers
+ doneHostHeader = doneContentLengthHeader = doneProxyAuthHeader = doneAuthHeader = 0;
+ for (i = 0; i < nlhr->headersCount; i++) {
+ NETLIBHTTPHEADER &p = nlhr->headers[i];
+ if (!mir_strcmpi(p.szName, "Host")) doneHostHeader = 1;
+ else if (!mir_strcmpi(p.szName, "Content-Length")) doneContentLengthHeader = 1;
+ else if (!mir_strcmpi(p.szName, "Proxy-Authorization")) doneProxyAuthHeader = 1;
+ else if (!mir_strcmpi(p.szName, "Authorization")) doneAuthHeader = 1;
+ else if (!mir_strcmpi(p.szName, "Connection")) continue;
+ if (p.szValue == nullptr) continue;
+ httpRequest.AppendFormat("%s: %s\r\n", p.szName, p.szValue);
+ }
+ if (szHost && !doneHostHeader)
+ httpRequest.AppendFormat("%s: %s\r\n", "Host", szHost);
+ if (pszProxyAuthHdr && !doneProxyAuthHeader)
+ httpRequest.AppendFormat("%s: %s\r\n", "Proxy-Authorization", pszProxyAuthHdr);
+ if (pszAuthHdr && !doneAuthHeader)
+ httpRequest.AppendFormat("%s: %s\r\n", "Authorization", pszAuthHdr);
+ httpRequest.AppendFormat("%s: %s\r\n", "Connection", "Keep-Alive");
+ httpRequest.AppendFormat("%s: %s\r\n", "Proxy-Connection", "Keep-Alive");
+
+ // Add Sticky Headers
+ if (nlu->szStickyHeaders != nullptr)
+ httpRequest.AppendFormat("%s\r\n", nlu->szStickyHeaders);
+
+ // send it
+ bytesSent = SendHttpRequestAndData(nlc, httpRequest, nlhr, !doneContentLengthHeader);
+ if (bytesSent == SOCKET_ERROR)
+ break;
+
+ // ntlm reply
+ if (doneContentLengthHeader && nlhr->requestType != REQUEST_HEAD)
+ break;
+
+ uint32_t fflags = MSG_PEEK | MSG_NODUMP | ((nlhr->flags & NLHRF_NOPROXY) ? MSG_RAW : 0);
+ uint32_t dwTimeOutTime = hdrTimeout < 0 ? -1 : GetTickCount() + hdrTimeout;
+ if (!HttpPeekFirstResponseLine(nlc, dwTimeOutTime, fflags, &nlhr->resultCode, nullptr, nullptr)) {
+ uint32_t err = GetLastError();
+ Netlib_Logf(nlu, "%s %d: %s Failed (%u %u)", __FILE__, __LINE__, "HttpPeekFirstResponseLine", err, count);
+
+ // connection died while we were waiting
+ if (GetNetlibHandleType(nlc) != NLH_CONNECTION) {
+ nlc = nullptr;
+ break;
+ }
+
+ if (err == ERROR_TIMEOUT || err == ERROR_BAD_FORMAT || err == ERROR_BUFFER_OVERFLOW || lastFirstLineFail || nlc->termRequested || nlhr->requestType == REQUEST_CONNECT) {
+ bytesSent = SOCKET_ERROR;
+ break;
+ }
+
+ lastFirstLineFail = true;
+ continue;
+ }
+
+ int resultCode = nlhr->resultCode;
+ lastFirstLineFail = false;
+
+ uint32_t hflags = (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPHEADERS | NLHRF_NODUMPSEND) ?
+ MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) |
+ (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0);
+
+ uint32_t dflags = (nlhr->flags & (NLHRF_NODUMP | NLHRF_NODUMPSEND) ? MSG_NODUMP : MSG_DUMPASTEXT | MSG_DUMPPROXY) |
+ (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0) | MSG_NODUMP;
+
+ if (resultCode == 100)
+ nlhrReply = (NETLIBHTTPREQUEST*)Netlib_RecvHttpHeaders(nlc, hflags);
+
+ else if (resultCode == 307 || ((resultCode == 301 || resultCode == 302) && (nlhr->flags & NLHRF_REDIRECT))) { // redirect
+ pszUrl = nullptr;
+
+ if (nlhr->requestType == REQUEST_HEAD)
+ nlhrReply = (NETLIBHTTPREQUEST*)Netlib_RecvHttpHeaders(nlc, hflags);
+ else
+ nlhrReply = NetlibHttpRecv(nlc, hflags, dflags);
+
+ if (nlhrReply) {
+ auto *tmpUrl = Netlib_GetHeader(nlhrReply, "Location");
+ if (tmpUrl) {
+ size_t rlen = 0;
+ if (tmpUrl[0] == '/') {
+ const char *ppath, *phost;
+ phost = strstr(pszFullUrl, "://");
+ phost = phost ? phost + 3 : pszFullUrl;
+ ppath = strchr(phost, '/');
+ rlen = ppath ? ppath - pszFullUrl : mir_strlen(pszFullUrl);
+ }
+
+ nlc->szNewUrl = (char*)mir_realloc(nlc->szNewUrl, rlen + mir_strlen(tmpUrl) * 3 + 1);
+
+ strncpy(nlc->szNewUrl, pszFullUrl, rlen);
+ mir_strcpy(nlc->szNewUrl + rlen, tmpUrl);
+ pszFullUrl = nlc->szNewUrl;
+ pszUrl = nullptr;
+
+ if (NetlibHttpProcessUrl(nlhr, nlu, nlc, pszFullUrl) == nullptr) {
+ bytesSent = SOCKET_ERROR;
+ break;
+ }
+ }
+ else {
+ NetlibHttpSetLastErrorUsingHttpResult(resultCode);
+ bytesSent = SOCKET_ERROR;
+ break;
+ }
+ }
+ else {
+ NetlibHttpSetLastErrorUsingHttpResult(resultCode);
+ bytesSent = SOCKET_ERROR;
+ break;
+ }
+ }
+ else if (resultCode == 401 && !doneAuthHeader) { //auth required
+ if (nlhr->requestType == REQUEST_HEAD)
+ nlhrReply = (NETLIBHTTPREQUEST*)Netlib_RecvHttpHeaders(nlc, hflags);
+ else
+ nlhrReply = NetlibHttpRecv(nlc, hflags, dflags);
+
+ replaceStr(pszAuthHdr, nullptr);
+ if (nlhrReply) {
+ char *szAuthStr = nullptr;
+ if (!complete) {
+ szAuthStr = NetlibHttpFindAuthHeader(nlhrReply, "WWW-Authenticate", httpSecurity.m_szProvider);
+ if (szAuthStr) {
+ char *szChallenge = strchr(szAuthStr, ' ');
+ if (!szChallenge || !*lrtrimp(szChallenge))
+ complete = true;
+ }
+ }
+ if (complete && httpSecurity.m_hNtlmSecurity)
+ szAuthStr = httpSecurity.TryBasic() ? NetlibHttpFindAuthHeader(nlhrReply, "WWW-Authenticate", "Basic") : nullptr;
+
+ if (szAuthStr) {
+ char *szChallenge = strchr(szAuthStr, ' ');
+ if (szChallenge) { *szChallenge = 0; szChallenge = lrtrimp(szChallenge + 1); }
+
+ pszAuthHdr = httpSecurity.Execute(nlc, szHost, szAuthStr, szChallenge, complete);
+ }
+ }
+ if (pszAuthHdr == nullptr) {
+ proxyAuthList.add(szHost, nullptr);
+ NetlibHttpSetLastErrorUsingHttpResult(resultCode);
+ bytesSent = SOCKET_ERROR;
+ break;
+ }
+ }
+ else if (resultCode == 407 && !doneProxyAuthHeader) { //proxy auth required
+ if (nlhr->requestType == REQUEST_HEAD)
+ nlhrReply = Netlib_RecvHttpHeaders(nlc, hflags);
+ else
+ nlhrReply = NetlibHttpRecv(nlc, hflags, dflags);
+
+ mir_free(pszProxyAuthHdr); pszProxyAuthHdr = nullptr;
+ if (nlhrReply) {
+ char *szAuthStr = nullptr;
+ if (!complete) {
+ szAuthStr = NetlibHttpFindAuthHeader(nlhrReply, "Proxy-Authenticate", httpSecurity.m_szProvider);
+ if (szAuthStr) {
+ char *szChallenge = strchr(szAuthStr, ' ');
+ if (!szChallenge || !*lrtrimp(szChallenge + 1))
+ complete = true;
+ }
+ }
+ if (complete && httpSecurity.m_hNtlmSecurity)
+ szAuthStr = httpSecurity.TryBasic() ? NetlibHttpFindAuthHeader(nlhrReply, "Proxy-Authenticate", "Basic") : nullptr;
+
+ if (szAuthStr) {
+ char *szChallenge = strchr(szAuthStr, ' ');
+ if (szChallenge) { *szChallenge = 0; szChallenge = lrtrimp(szChallenge + 1); }
+
+ pszProxyAuthHdr = httpSecurity.Execute(nlc, nlc->szProxyServer, szAuthStr, szChallenge, complete);
+ }
+ }
+ if (pszProxyAuthHdr == nullptr) {
+ proxyAuthList.add(nlc->szProxyServer, nullptr);
+ NetlibHttpSetLastErrorUsingHttpResult(resultCode);
+ bytesSent = SOCKET_ERROR;
+ break;
+ }
+ }
+ else break;
+
+ if (pszProxyAuthHdr && resultCode != 407 && !doneProxyAuthHeader)
+ replaceStr(pszProxyAuthHdr, nullptr);
+
+ if (pszAuthHdr && resultCode != 401 && !doneAuthHeader)
+ replaceStr(pszAuthHdr, nullptr);
+
+ if (nlhrReply) {
+ Netlib_FreeHttpRequest(nlhrReply);
+ nlhrReply = nullptr;
+ }
+ }
+
+ if (count == 0) bytesSent = SOCKET_ERROR;
+ if (nlhrReply)
+ Netlib_FreeHttpRequest(nlhrReply);
+
+ //clean up
+ mir_free(pszProxyAuthHdr);
+ mir_free(pszAuthHdr);
+ mir_free(szHost);
+ mir_free(szNewUrl);
+
+ if (nlc)
+ NetlibLeaveNestedCS(&nlc->ncsSend);
+
+ return bytesSent;
+}
+
+MIR_APP_DLL(bool) Netlib_FreeHttpRequest(NETLIBHTTPREQUEST *nlhr)
+{
+ if (nlhr == nullptr || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) || nlhr->requestType != REQUEST_RESPONSE) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return false;
+ }
+
+ if (nlhr->headers) {
+ for (int i = 0; i < nlhr->headersCount; i++) {
+ NETLIBHTTPHEADER &p = nlhr->headers[i];
+ mir_free(p.szName);
+ mir_free(p.szValue);
+ }
+ mir_free(nlhr->headers);
+ }
+ mir_free(nlhr->pData);
+ mir_free(nlhr->szResultDescr);
+ mir_free(nlhr->szUrl);
+ mir_free(nlhr);
+ return true;
+}
+
+#define NHRV_BUF_SIZE 8192
+
+MIR_APP_DLL(NETLIBHTTPREQUEST*) Netlib_RecvHttpHeaders(HNETLIBCONN hConnection, int flags)
+{
+ NetlibConnection *nlc = (NetlibConnection*)hConnection;
+ if (!NetlibEnterNestedCS(nlc, NLNCS_RECV))
+ return nullptr;
+
+ uint32_t dwRequestTimeoutTime = GetTickCount() + HTTPRECVDATATIMEOUT;
+ NETLIBHTTPREQUEST *nlhr = (NETLIBHTTPREQUEST*)mir_calloc(sizeof(NETLIBHTTPREQUEST));
+ nlhr->cbSize = sizeof(NETLIBHTTPREQUEST);
+ nlhr->nlc = nlc; // Needed to id connection in the protocol HTTP gateway wrapper functions
+ nlhr->requestType = REQUEST_RESPONSE;
+
+ int firstLineLength = 0;
+ if (!HttpPeekFirstResponseLine(nlc, dwRequestTimeoutTime, flags | MSG_PEEK, &nlhr->resultCode, &nlhr->szResultDescr, &firstLineLength)) {
+ NetlibLeaveNestedCS(&nlc->ncsRecv);
+ Netlib_FreeHttpRequest(nlhr);
+ return nullptr;
+ }
+
+ char *buffer = (char*)_alloca(NHRV_BUF_SIZE + 1);
+ int bytesPeeked = Netlib_Recv(nlc, buffer, min(firstLineLength, NHRV_BUF_SIZE), flags | MSG_DUMPASTEXT);
+ if (bytesPeeked != firstLineLength) {
+ NetlibLeaveNestedCS(&nlc->ncsRecv);
+ Netlib_FreeHttpRequest(nlhr);
+ if (bytesPeeked != SOCKET_ERROR)
+ SetLastError(ERROR_HANDLE_EOF);
+ return nullptr;
+ }
+
+ // Make sure all headers arrived
+ MBinBuffer buf;
+ int headersCount = 0;
+ bytesPeeked = 0;
+ for (bool headersCompleted = false; !headersCompleted;) {
+ bytesPeeked = RecvWithTimeoutTime(nlc, dwRequestTimeoutTime, buffer, NHRV_BUF_SIZE, flags | MSG_DUMPASTEXT | MSG_NOTITLE);
+ if (bytesPeeked == 0)
+ break;
+
+ if (bytesPeeked == SOCKET_ERROR) {
+ bytesPeeked = 0;
+ break;
+ }
+
+ buf.append(buffer, bytesPeeked);
+
+ headersCount = 0;
+ for (char *pbuffer = (char*)buf.data();; headersCount++) {
+ char *peol = strchr(pbuffer, '\n');
+ if (peol == nullptr) break;
+ if (peol == pbuffer || (peol == (pbuffer + 1) && *pbuffer == '\r')) {
+ bytesPeeked = peol - (char*)buf.data() + 1;
+ headersCompleted = true;
+ break;
+ }
+ pbuffer = peol + 1;
+ }
+ }
+
+ if (bytesPeeked <= 0) {
+ NetlibLeaveNestedCS(&nlc->ncsRecv);
+ Netlib_FreeHttpRequest(nlhr);
+ return nullptr;
+ }
+
+ // Receive headers
+ nlhr->headersCount = headersCount;
+ nlhr->headers = (NETLIBHTTPHEADER*)mir_calloc(sizeof(NETLIBHTTPHEADER) * headersCount);
+
+ headersCount = 0;
+ for (char *pbuffer = (char*)buf.data();; headersCount++) {
+ char *peol = strchr(pbuffer, '\n');
+ if (peol == nullptr || peol == pbuffer || (peol == (pbuffer+1) && *pbuffer == '\r'))
+ break;
+ *peol = 0;
+
+ char *pColon = strchr(pbuffer, ':');
+ if (pColon == nullptr) {
+ Netlib_FreeHttpRequest(nlhr); nlhr = nullptr;
+ SetLastError(ERROR_INVALID_DATA);
+ break;
+ }
+
+ *pColon = 0;
+ nlhr->headers[headersCount].szName = mir_strdup(rtrim(pbuffer));
+ nlhr->headers[headersCount].szValue = mir_strdup(lrtrimp(pColon+1));
+ pbuffer = peol + 1;
+ }
+
+ // remove processed data
+ buf.remove(bytesPeeked);
+ nlc->foreBuf.appendBefore(buf.data(), buf.length());
+
+ NetlibLeaveNestedCS(&nlc->ncsRecv);
+ return nlhr;
+}
+
+MIR_APP_DLL(NETLIBHTTPREQUEST*) Netlib_HttpTransaction(HNETLIBUSER nlu, NETLIBHTTPREQUEST *nlhr)
+{
+ if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING) ||
+ nlhr == nullptr || nlhr->cbSize != sizeof(NETLIBHTTPREQUEST) ||
+ nlhr->szUrl == nullptr || nlhr->szUrl[0] == 0)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return nullptr;
+ }
+
+ if (nlhr->nlc != nullptr && GetNetlibHandleType(nlhr->nlc) != NLH_CONNECTION)
+ nlhr->nlc = nullptr;
+
+ NetlibConnection *nlc = NetlibHttpProcessUrl(nlhr, nlu, (NetlibConnection*)nlhr->nlc);
+ if (nlc == nullptr)
+ return nullptr;
+
+ NETLIBHTTPREQUEST nlhrSend = *nlhr;
+ nlhrSend.flags |= NLHRF_SMARTREMOVEHOST;
+
+ bool doneUserAgentHeader = Netlib_GetHeader(nlhr, "User-Agent") != nullptr;
+ bool doneAcceptEncoding = Netlib_GetHeader(nlhr, "Accept-Encoding") != nullptr;
+ if (!doneUserAgentHeader || !doneAcceptEncoding) {
+ nlhrSend.headers = (NETLIBHTTPHEADER*)mir_alloc(sizeof(NETLIBHTTPHEADER) * (nlhrSend.headersCount + 2));
+ memcpy(nlhrSend.headers, nlhr->headers, sizeof(NETLIBHTTPHEADER) * nlhr->headersCount);
+ }
+
+ char szUserAgent[64];
+ if (!doneUserAgentHeader) {
+ nlhrSend.headers[nlhrSend.headersCount].szName = "User-Agent";
+ nlhrSend.headers[nlhrSend.headersCount].szValue = szUserAgent;
+ ++nlhrSend.headersCount;
+
+ char szMirandaVer[64];
+ strncpy_s(szMirandaVer, MIRANDA_VERSION_STRING, _TRUNCATE);
+ #if defined(_WIN64)
+ strncat_s(szMirandaVer, " x64", _TRUNCATE);
+ #endif
+
+ char *pspace = strchr(szMirandaVer, ' ');
+ if (pspace) {
+ *pspace++ = '\0';
+ mir_snprintf(szUserAgent, "Miranda/%s (%s)", szMirandaVer, pspace);
+ }
+ else mir_snprintf(szUserAgent, "Miranda/%s", szMirandaVer);
+ }
+ if (!doneAcceptEncoding) {
+ nlhrSend.headers[nlhrSend.headersCount].szName = "Accept-Encoding";
+ nlhrSend.headers[nlhrSend.headersCount].szValue = "deflate, gzip";
+ ++nlhrSend.headersCount;
+ }
+ if (Netlib_SendHttpRequest(nlc, &nlhrSend) == SOCKET_ERROR) {
+ if (!doneUserAgentHeader || !doneAcceptEncoding) mir_free(nlhrSend.headers);
+ nlhr->resultCode = nlhrSend.resultCode;
+ Netlib_CloseHandle(nlc);
+ return nullptr;
+ }
+ if (!doneUserAgentHeader || !doneAcceptEncoding)
+ mir_free(nlhrSend.headers);
+
+ uint32_t dflags = (nlhr->flags & NLHRF_DUMPASTEXT ? MSG_DUMPASTEXT : 0) |
+ (nlhr->flags & NLHRF_NODUMP ? MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) |
+ (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0);
+
+ uint32_t hflags =
+ (nlhr->flags & NLHRF_NODUMP ? MSG_NODUMP : (nlhr->flags & NLHRF_DUMPPROXY ? MSG_DUMPPROXY : 0)) |
+ (nlhr->flags & NLHRF_NOPROXY ? MSG_RAW : 0);
+
+ NETLIBHTTPREQUEST *nlhrReply;
+ if (nlhr->requestType == REQUEST_HEAD)
+ nlhrReply = Netlib_RecvHttpHeaders(nlc);
+ else
+ nlhrReply = NetlibHttpRecv(nlc, hflags, dflags);
+
+ if (nlhrReply) {
+ nlhrReply->szUrl = nlc->szNewUrl;
+ nlc->szNewUrl = nullptr;
+ }
+
+ if ((nlhr->flags & NLHRF_PERSISTENT) == 0 || nlhrReply == nullptr) {
+ Netlib_CloseHandle(nlc);
+ if (nlhrReply)
+ nlhrReply->nlc = nullptr;
+ }
+ else nlhrReply->nlc = nlc;
+
+ return nlhrReply;
+}
+
+void NetlibHttpSetLastErrorUsingHttpResult(int result)
+{
+ if (result >= 200 && result < 300) {
+ SetLastError(ERROR_SUCCESS);
+ return;
+ }
+ switch (result) {
+ case 400: SetLastError(ERROR_BAD_FORMAT); break;
+ case 401:
+ case 402:
+ case 403:
+ case 407: SetLastError(ERROR_ACCESS_DENIED); break;
+ case 404: SetLastError(ERROR_FILE_NOT_FOUND); break;
+ case 405:
+ case 406: SetLastError(ERROR_INVALID_FUNCTION); break;
+ case 408: SetLastError(ERROR_TIMEOUT); break;
+ default: SetLastError(ERROR_GEN_FAILURE); break;
+ }
+}
+
+char* gzip_decode(char *gzip_data, int *len_ptr, int window)
+{
+ if (*len_ptr == 0) return nullptr;
+
+ int gzip_len = *len_ptr * 5;
+ char* output_data = nullptr;
+
+ int gzip_err;
+ z_stream zstr;
+
+ do {
+ output_data = (char*)mir_realloc(output_data, gzip_len+1);
+ if (output_data == nullptr)
+ break;
+
+ zstr.next_in = (Bytef*)gzip_data;
+ zstr.avail_in = *len_ptr;
+ zstr.zalloc = Z_NULL;
+ zstr.zfree = Z_NULL;
+ zstr.opaque = Z_NULL;
+ inflateInit2_(&zstr, window, ZLIB_VERSION, sizeof(z_stream));
+
+ zstr.next_out = (Bytef*)output_data;
+ zstr.avail_out = gzip_len;
+
+ gzip_err = inflate(&zstr, Z_FINISH);
+
+ inflateEnd(&zstr);
+ gzip_len *= 2;
+ if (gzip_len > 10000000)
+ break;
+ } while (gzip_err == Z_BUF_ERROR);
+
+ gzip_len = gzip_err == Z_STREAM_END ? zstr.total_out : -1;
+
+ if (gzip_len <= 0) {
+ mir_free(output_data);
+ output_data = nullptr;
+ }
+ else output_data[gzip_len] = 0;
+
+ *len_ptr = gzip_len;
+ return output_data;
+}
+
+static int NetlibHttpRecvChunkHeader(NetlibConnection *nlc, bool first, uint32_t flags)
+{
+ MBinBuffer buf;
+
+ while (true) {
+ char data[1000];
+ int recvResult = Netlib_Recv(nlc, data, _countof(data) - 1, MSG_RAW | flags);
+ if (recvResult <= 0 || recvResult >= _countof(data))
+ return SOCKET_ERROR;
+
+ buf.append(data, recvResult); // add chunk
+
+ auto *peol1 = (const char*)memchr(buf.data(), '\n', buf.length());
+ if (peol1 == nullptr)
+ continue;
+
+ auto *pStart = (const char *)buf.data();
+ int cbRest = int(peol1 - pStart) + 1;
+ const char *peol2 = first ? peol1 : (const char*)memchr(peol1 + 1, '\n', buf.length() - cbRest);
+ if (peol2 == nullptr)
+ continue;
+
+ int sz = peol2 - pStart + 1;
+ int r = strtol(first ? pStart : peol1 + 1, nullptr, 16);
+ if (r == 0) {
+ const char *peol3 = strchr(peol2 + 1, '\n');
+ if (peol3 == nullptr)
+ continue;
+ sz = peol3 - pStart + 1;
+ }
+ buf.remove(sz); // remove all our data from buffer
+ nlc->foreBuf.appendBefore(buf.data(), buf.length());
+ return r;
+ }
+}
+
+NETLIBHTTPREQUEST* NetlibHttpRecv(NetlibConnection *nlc, uint32_t hflags, uint32_t dflags, bool isConnect)
+{
+ int dataLen = -1, i, chunkhdr = 0;
+ bool chunked = false;
+ int cenc = 0, cenctype = 0, close = 0;
+
+next:
+ NETLIBHTTPREQUEST *nlhrReply = Netlib_RecvHttpHeaders(nlc, hflags);
+ if (nlhrReply == nullptr)
+ return nullptr;
+
+ if (nlhrReply->resultCode == 100) {
+ Netlib_FreeHttpRequest(nlhrReply);
+ goto next;
+ }
+
+ if (nlhrReply->resultCode == 204)
+ dataLen = 0;
+
+ for (i = 0; i < nlhrReply->headersCount; i++) {
+ NETLIBHTTPHEADER &p = nlhrReply->headers[i];
+ if (!mir_strcmpi(p.szName, "Content-Length"))
+ dataLen = atoi(p.szValue);
+
+ if (!mir_strcmpi(p.szName, "Content-Encoding")) {
+ cenc = i;
+ if (strstr(p.szValue, "gzip"))
+ cenctype = 1;
+ else if (strstr(p.szValue, "deflate"))
+ cenctype = 2;
+ }
+
+ if (!mir_strcmpi(p.szName, "Connection"))
+ close = !mir_strcmpi(p.szValue, "close");
+
+ if (!mir_strcmpi(p.szName, "Transfer-Encoding") && !mir_strcmpi(p.szValue, "chunked")) {
+ chunked = true;
+ chunkhdr = i;
+ dataLen = -1;
+ }
+ }
+
+ if (nlhrReply->resultCode >= 200 && (dataLen > 0 || (!isConnect && dataLen < 0))) {
+ int recvResult, chunksz = -1;
+ int dataBufferAlloced;
+
+ if (chunked) {
+ chunksz = NetlibHttpRecvChunkHeader(nlc, true, dflags | (cenctype ? MSG_NODUMP : 0));
+ if (chunksz == SOCKET_ERROR) {
+ Netlib_FreeHttpRequest(nlhrReply);
+ return nullptr;
+ }
+ dataLen = chunksz;
+ }
+ dataBufferAlloced = dataLen < 0 ? 2048 : dataLen + 1;
+ nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced);
+
+ while (chunksz != 0) {
+ while (true) {
+ recvResult = RecvWithTimeoutTime(nlc, GetTickCount() + HTTPRECVDATATIMEOUT,
+ nlhrReply->pData + nlhrReply->dataLength,
+ dataBufferAlloced - nlhrReply->dataLength - 1,
+ dflags | (cenctype ? MSG_NODUMP : 0));
+
+ if (recvResult == 0) break;
+ if (recvResult == SOCKET_ERROR) {
+ Netlib_FreeHttpRequest(nlhrReply);
+ return nullptr;
+ }
+ nlhrReply->dataLength += recvResult;
+
+ if (dataLen >= 0) {
+ if (nlhrReply->dataLength >= dataLen)
+ break;
+ }
+ else if ((dataBufferAlloced - nlhrReply->dataLength) < 256) {
+ dataBufferAlloced += 2048;
+ nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced);
+ if (nlhrReply->pData == nullptr) {
+ SetLastError(ERROR_OUTOFMEMORY);
+ Netlib_FreeHttpRequest(nlhrReply);
+ return nullptr;
+ }
+ }
+ Sleep(10);
+ }
+
+ if (!chunked)
+ break;
+
+ chunksz = NetlibHttpRecvChunkHeader(nlc, false, dflags | MSG_NODUMP);
+ if (chunksz == SOCKET_ERROR) {
+ Netlib_FreeHttpRequest(nlhrReply);
+ return nullptr;
+ }
+ dataLen += chunksz;
+ dataBufferAlloced += chunksz;
+
+ nlhrReply->pData = (char*)mir_realloc(nlhrReply->pData, dataBufferAlloced);
+ }
+
+ nlhrReply->pData[nlhrReply->dataLength] = '\0';
+ }
+
+ if (chunked) {
+ nlhrReply->headers[chunkhdr].szName = (char*)mir_realloc(nlhrReply->headers[chunkhdr].szName, 16);
+ mir_strcpy(nlhrReply->headers[chunkhdr].szName, "Content-Length");
+
+ nlhrReply->headers[chunkhdr].szValue = (char*)mir_realloc(nlhrReply->headers[chunkhdr].szValue, 16);
+ mir_snprintf(nlhrReply->headers[chunkhdr].szValue, 16, "%u", nlhrReply->dataLength);
+ }
+
+ if (cenctype) {
+ int bufsz = nlhrReply->dataLength;
+ char* szData = nullptr;
+
+ switch (cenctype) {
+ case 1:
+ szData = gzip_decode(nlhrReply->pData, &bufsz, 0x10 | MAX_WBITS);
+ break;
+
+ case 2:
+ szData = gzip_decode(nlhrReply->pData, &bufsz, -MAX_WBITS);
+ if (bufsz < 0) {
+ bufsz = nlhrReply->dataLength;
+ szData = gzip_decode(nlhrReply->pData, &bufsz, MAX_WBITS);
+ }
+ break;
+ }
+
+ if (bufsz > 0) {
+ Netlib_Dump(nlc, (uint8_t*)szData, bufsz, false, dflags | MSG_NOTITLE);
+ mir_free(nlhrReply->pData);
+ nlhrReply->pData = szData;
+ nlhrReply->dataLength = bufsz;
+
+ mir_free(nlhrReply->headers[cenc].szName);
+ mir_free(nlhrReply->headers[cenc].szValue);
+ memmove(&nlhrReply->headers[cenc], &nlhrReply->headers[cenc+1], (--nlhrReply->headersCount-cenc)*sizeof(nlhrReply->headers[0]));
+ }
+ else if (bufsz == 0) {
+ mir_free(nlhrReply->pData);
+ nlhrReply->pData = nullptr;
+ nlhrReply->dataLength = 0;
+ }
+ }
+
+ if (close &&
+ (nlc->proxyType != PROXYTYPE_HTTP || nlc->url.flags & NLOCF_SSL) &&
+ (!isConnect || nlhrReply->resultCode != 200))
+ NetlibDoCloseSocket(nlc);
+
+ return nlhrReply;
+}
diff --git a/src/mir_app/src/netlib_httpproxy.cpp b/src/mir_app/src/netlib_httpproxy.cpp index 4ea58fcd57..66adc7fe9a 100644 --- a/src/mir_app/src/netlib_httpproxy.cpp +++ b/src/mir_app/src/netlib_httpproxy.cpp @@ -1,85 +1,85 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -typedef enum -{ - reqHelloGet, - reqOldGet, - reqOldPost, - reqNewPost, -} -RequestType; - -///////////////////////////////////////////////////////////////////////////////////////// - -#define NETLIBHTTP_RETRYCOUNT 3 -#define NETLIBHTTP_RETRYTIMEOUT 2000 - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Netlib_SetHttpProxyInfo(HNETLIBCONN nlc, const NETLIBHTTPPROXYINFO *nlhpi) -{ - if (GetNetlibHandleType(nlc) != NLH_CONNECTION || nlhpi == nullptr || nlhpi->szHttpPostUrl == nullptr) { - SetLastError(ERROR_INVALID_PARAMETER); - return 0; - } - - mir_free(nlc->nlhpi.szHttpGetUrl); - mir_free(nlc->nlhpi.szHttpPostUrl); - - nlc->nlhpi.combinePackets = 1; - memcpy(&nlc->nlhpi, nlhpi, sizeof(*nlhpi)); - if (nlc->nlhpi.combinePackets == 0) - nlc->nlhpi.combinePackets = 1; - - nlc->nlhpi.szHttpGetUrl = mir_strdup(nlc->nlhpi.szHttpGetUrl); - nlc->nlhpi.szHttpPostUrl = mir_strdup(nlc->nlhpi.szHttpPostUrl); - return 1; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Netlib_SetStickyHeaders(HNETLIBUSER nlu, const char *szHeaders) -{ - if (GetNetlibHandleType(nlu) != NLH_USER) - return ERROR_INVALID_PARAMETER; - - replaceStr(nlu->szStickyHeaders, szHeaders); // pointer is ours - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Netlib_SetPollingTimeout(HNETLIBCONN nlc, int iTimeout) -{ - if (GetNetlibHandleType(nlc) != NLH_CONNECTION) - return -1; - - int oldTimeout = nlc->pollingTimeout; - nlc->pollingTimeout = iTimeout; - return oldTimeout; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+typedef enum
+{
+ reqHelloGet,
+ reqOldGet,
+ reqOldPost,
+ reqNewPost,
+}
+RequestType;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#define NETLIBHTTP_RETRYCOUNT 3
+#define NETLIBHTTP_RETRYTIMEOUT 2000
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Netlib_SetHttpProxyInfo(HNETLIBCONN nlc, const NETLIBHTTPPROXYINFO *nlhpi)
+{
+ if (GetNetlibHandleType(nlc) != NLH_CONNECTION || nlhpi == nullptr || nlhpi->szHttpPostUrl == nullptr) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ mir_free(nlc->nlhpi.szHttpGetUrl);
+ mir_free(nlc->nlhpi.szHttpPostUrl);
+
+ nlc->nlhpi.combinePackets = 1;
+ memcpy(&nlc->nlhpi, nlhpi, sizeof(*nlhpi));
+ if (nlc->nlhpi.combinePackets == 0)
+ nlc->nlhpi.combinePackets = 1;
+
+ nlc->nlhpi.szHttpGetUrl = mir_strdup(nlc->nlhpi.szHttpGetUrl);
+ nlc->nlhpi.szHttpPostUrl = mir_strdup(nlc->nlhpi.szHttpPostUrl);
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Netlib_SetStickyHeaders(HNETLIBUSER nlu, const char *szHeaders)
+{
+ if (GetNetlibHandleType(nlu) != NLH_USER)
+ return ERROR_INVALID_PARAMETER;
+
+ replaceStr(nlu->szStickyHeaders, szHeaders); // pointer is ours
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Netlib_SetPollingTimeout(HNETLIBCONN nlc, int iTimeout)
+{
+ if (GetNetlibHandleType(nlc) != NLH_CONNECTION)
+ return -1;
+
+ int oldTimeout = nlc->pollingTimeout;
+ nlc->pollingTimeout = iTimeout;
+ return oldTimeout;
+}
diff --git a/src/mir_app/src/netlib_log.cpp b/src/mir_app/src/netlib_log.cpp index 4a1cea21df..1ddbb63f6f 100644 --- a/src/mir_app/src/netlib_log.cpp +++ b/src/mir_app/src/netlib_log.cpp @@ -1,578 +1,578 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -#define MS_NETLIB_LOGWIN "Netlib/Log/Win" - -#define TIMEFORMAT_NONE 0 -#define TIMEFORMAT_HHMMSS 1 -#define TIMEFORMAT_MILLISECONDS 2 -#define TIMEFORMAT_MICROSECONDS 3 -struct { - HWND hwndOpts; - bool toOutputDebugString, toFile, toLog; - bool showUser, rotateLogs, bPrintDate; - bool dumpSent, dumpRecv, dumpProxy, dumpSsl; - bool textDumps, autoDetectText; - int timeFormat; - CMStringW tszFile, tszUserFile; -} -static logOptions = {}; - -struct LOGMSG -{ - const char* pszHead; - const char* pszMsg; -}; - -static __int64 mirandaStartTime, perfCounterFreq; -static int bIsActive = TRUE; -static HANDLE hLogEvent = nullptr; -static HANDLE hLogger = nullptr; - -static void InitLog() -{ - logOptions.dumpRecv = db_get_b(0, "Netlib", "DumpRecv", true) != 0; - logOptions.dumpSent = db_get_b(0, "Netlib", "DumpSent", true) != 0; - logOptions.dumpProxy = db_get_b(0, "Netlib", "DumpProxy", true) != 0; - logOptions.dumpSsl = db_get_b(0, "Netlib", "DumpSsl", false) != 0; - logOptions.textDumps = db_get_b(0, "Netlib", "TextDumps", true) != 0; - logOptions.autoDetectText = db_get_b(0, "Netlib", "AutoDetectText", true) != 0; - logOptions.bPrintDate = db_get_b(0, "Netlib", "PrintDate", false) != 0; - logOptions.timeFormat = db_get_b(0, "Netlib", "TimeFormat", TIMEFORMAT_HHMMSS); - logOptions.rotateLogs = db_get_b(0, "Netlib", "RotateLogs", false); - logOptions.showUser = db_get_b(0, "Netlib", "ShowUser", true) != 0; - logOptions.toOutputDebugString = db_get_b(0, "Netlib", "ToOutputDebugString", false) != 0; - logOptions.toFile = db_get_b(0, "Netlib", "ToFile", false) != 0; - logOptions.toLog = db_get_dw(0, "Netlib", "NLlog", true) != 0; - - if (hLogger) { - mir_closeLog(hLogger); - hLogger = nullptr; - } - - ptrW szBuf(db_get_wsa(0, "Netlib", "File")); - if (mir_wstrlen(szBuf)) { - logOptions.tszUserFile = szBuf.get(); - - wchar_t path[MAX_PATH]; - PathToAbsoluteW(VARSW(szBuf), path); - logOptions.tszFile = path; - } - else { - db_set_ws(0, "Netlib", "File", logOptions.tszUserFile = L"%miranda_logpath%\\netlog.txt"); - logOptions.tszFile = VARSW(logOptions.tszUserFile); - } - - if (logOptions.toFile) { - CMStringW wszFileName = logOptions.tszFile; - if (logOptions.rotateLogs) { - int iLogNumber = db_get_dw(0, "Netlib", "RotateId"); - wszFileName.AppendFormat(L".%d", iLogNumber); - db_set_dw(0, "Netlib", "RotateId", (iLogNumber + 1) % 10); - } - - hLogger = mir_createLog("Netlib", LPGENW("Standard Netlib log"), wszFileName, 0); - } -} - -static const wchar_t *szTimeFormats[] = -{ - LPGENW("No times"), - LPGENW("Standard hh:mm:ss times"), - LPGENW("Times in milliseconds"), - LPGENW("Times in microseconds") -}; - -class CLogOptionsDlg : public CDlgBase -{ - CCtrlEdit edtFileName; - CCtrlCombo cmbTimeFormat; - CCtrlButton btnRunNow, btnFileName, btnRunAtStart; - CCtrlTreeView treeFilter; - -public: - CLogOptionsDlg() : - CDlgBase(g_plugin, IDD_NETLIBLOGOPTS), - treeFilter(this, IDC_FILTER), - edtFileName(this, IDC_FILENAME), - cmbTimeFormat(this, IDC_TIMEFORMAT), - btnRunNow(this, IDC_RUNNOW), - btnFileName(this, IDC_FILENAMEBROWSE), - btnRunAtStart(this, IDC_RUNATSTARTBROWSE) - { - btnFileName.OnClick = btnRunAtStart.OnClick = Callback(this, &CLogOptionsDlg::onClick_Browse); - - edtFileName.OnChange = Callback(this, &CLogOptionsDlg::onChange_FileName); - } - - bool OnInitDialog() override - { - logOptions.hwndOpts = m_hwnd; - - CheckDlgButton(m_hwnd, IDC_TOFILE, logOptions.toFile ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_DUMPSSL, logOptions.dumpSsl ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_DUMPRECV, logOptions.dumpRecv ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_DUMPSENT, logOptions.dumpSent ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_SHOWDATE, logOptions.bPrintDate ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_SHOWNAMES, logOptions.showUser ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_DUMPPROXY, logOptions.dumpProxy ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_TEXTDUMPS, logOptions.textDumps ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_LOGROTATE, logOptions.rotateLogs ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_AUTODETECTTEXT, logOptions.autoDetectText ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_SHOWTHISDLGATSTART, db_get_b(0, "Netlib", "ShowLogOptsAtStart", 0) ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(m_hwnd, IDC_TOOUTPUTDEBUGSTRING, logOptions.toOutputDebugString ? BST_CHECKED : BST_UNCHECKED); - - for (auto &it : szTimeFormats) - cmbTimeFormat.AddString(TranslateW(it)); - cmbTimeFormat.SetCurSel(logOptions.timeFormat); - - edtFileName.SetText(logOptions.tszUserFile); - SetDlgItemText(m_hwnd, IDC_PATH, logOptions.tszFile); - - ptrA szRun(db_get_sa(0, "Netlib", "RunAtStart")); - if (szRun) - SetDlgItemTextA(m_hwnd, IDC_RUNATSTART, szRun); - - SetWindowLongPtr(treeFilter.GetHwnd(), GWL_STYLE, GetWindowLongPtr(treeFilter.GetHwnd(), GWL_STYLE) | (TVS_NOHSCROLL | TVS_CHECKBOXES)); - - TVINSERTSTRUCT tvis = {}; - tvis.hInsertAfter = TVI_SORT; - tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_STATE; - tvis.item.stateMask = TVIS_STATEIMAGEMASK; - - for (auto &it : netlibUser) { - tvis.item.pszText = it->user.szDescriptiveName.w; - tvis.item.lParam = netlibUser.indexOf(&it); - tvis.item.state = INDEXTOSTATEIMAGEMASK(it->toLog ? 2 : 1); - treeFilter.InsertItem(&tvis); - } - - tvis.item.lParam = -1; - tvis.item.pszText = TranslateT("(Miranda core logging)"); - tvis.item.state = INDEXTOSTATEIMAGEMASK((logOptions.toLog) ? 2 : 1); - treeFilter.InsertItem(&tvis); - return true; - } - - bool OnApply() override - { - wchar_t str[MAX_PATH]; - GetDlgItemText(m_hwnd, IDC_RUNATSTART, str, _countof(str)); - db_set_ws(0, "Netlib", "RunAtStart", str); - - edtFileName.GetText(str, _countof(str)); - logOptions.tszUserFile = rtrimw(str); - db_set_ws(0, "Netlib", "File", str); - - GetDlgItemText(m_hwnd, IDC_PATH, str, _countof(str)); - logOptions.tszFile = rtrimw(str); - - db_set_b(0, "Netlib", "ToFile", IsDlgButtonChecked(m_hwnd, IDC_TOFILE)); - db_set_b(0, "Netlib", "DumpSsl", IsDlgButtonChecked(m_hwnd, IDC_DUMPSSL)); - db_set_b(0, "Netlib", "DumpRecv", IsDlgButtonChecked(m_hwnd, IDC_DUMPRECV)); - db_set_b(0, "Netlib", "DumpSent", IsDlgButtonChecked(m_hwnd, IDC_DUMPSENT)); - db_set_b(0, "Netlib", "ShowUser", IsDlgButtonChecked(m_hwnd, IDC_SHOWNAMES)); - db_set_b(0, "Netlib", "DumpProxy", IsDlgButtonChecked(m_hwnd, IDC_DUMPPROXY)); - db_set_b(0, "Netlib", "PrintDate", IsDlgButtonChecked(m_hwnd, IDC_SHOWDATE)); - db_set_b(0, "Netlib", "TextDumps", IsDlgButtonChecked(m_hwnd, IDC_TEXTDUMPS)); - db_set_b(0, "Netlib", "RotateLogs", IsDlgButtonChecked(m_hwnd, IDC_LOGROTATE)); - db_set_b(0, "Netlib", "AutoDetectText", IsDlgButtonChecked(m_hwnd, IDC_AUTODETECTTEXT)); - db_set_b(0, "Netlib", "ShowLogOptsAtStart", IsDlgButtonChecked(m_hwnd, IDC_SHOWTHISDLGATSTART)); - db_set_b(0, "Netlib", "ToOutputDebugString", IsDlgButtonChecked(m_hwnd, IDC_TOOUTPUTDEBUGSTRING)); - - db_set_b(0, "Netlib", "TimeFormat", cmbTimeFormat.GetCurSel()); - - TVITEMEX tvi = {}; - tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE | TVIF_TEXT; - tvi.hItem = treeFilter.GetRoot(); - - while (tvi.hItem) { - treeFilter.GetItem(&tvi); - bool checked = ((tvi.state & TVIS_STATEIMAGEMASK) >> 12 == 2); - - if (tvi.lParam == -1) { - logOptions.toLog = checked; - db_set_dw(0, "Netlib", "NLlog", checked); - } - else if (tvi.lParam < netlibUser.getCount()) { - netlibUser[tvi.lParam]->toLog = checked; - db_set_dw(0, netlibUser[tvi.lParam]->user.szSettingsModule, "NLlog", checked); - } - - tvi.hItem = treeFilter.GetNextSibling(tvi.hItem); - } - - InitLog(); - return true; - } - - void OnDestroy() override - { - ImageList_Destroy(TreeView_GetImageList(GetDlgItem(m_hwnd, IDC_FILTER), TVSIL_STATE)); - logOptions.hwndOpts = nullptr; - } - - void onChange_FileName(CCtrlEdit *pEdit) - { - if (pEdit->GetHwnd() == GetFocus()) - CheckDlgButton(m_hwnd, IDC_TOFILE, BST_CHECKED); - - wchar_t path[MAX_PATH]; - pEdit->GetText(path, _countof(path)); - PathToAbsoluteW(VARSW(path), path); - SetDlgItemText(m_hwnd, IDC_PATH, path); - } - - void onClick_Browse(CCtrlButton *pButton) - { - wchar_t str[MAX_PATH]; - GetWindowText(GetWindow(pButton->GetHwnd(), GW_HWNDPREV), str, _countof(str)); - - wchar_t filter[200]; - mir_snwprintf(filter, L"%s (*)%c*%c", TranslateT("All files"), 0, 0); - - OPENFILENAME ofn = { 0 }; - ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; - ofn.hwndOwner = m_hwnd; - ofn.Flags = OFN_HIDEREADONLY | OFN_DONTADDTORECENT; - if (pButton->GetCtrlId() == IDC_FILENAMEBROWSE) - ofn.lpstrTitle = TranslateT("Select where log file will be created"); - else { - ofn.Flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; - ofn.lpstrTitle = TranslateT("Select program to be run"); - } - ofn.lpstrFilter = filter; - ofn.lpstrFile = str; - ofn.nMaxFile = _countof(str) - 2; - ofn.nMaxFileTitle = MAX_PATH; - if (pButton->GetCtrlId() == IDC_FILENAMEBROWSE) { - if (!GetSaveFileName(&ofn)) - return; - } - else if (!GetOpenFileName(&ofn)) - return; - - if (pButton->GetCtrlId() == IDC_RUNATSTARTBROWSE && wcschr(str, ' ') != nullptr) { - memmove(str + 1, str, ((_countof(str) - 2) * sizeof(wchar_t))); - str[0] = '"'; - mir_wstrcat(str, L"\""); - } - SetWindowText(GetWindow(pButton->GetHwnd(), GW_HWNDPREV), str); - } - - void onClick_RunNow(CCtrlButton *) - { - wchar_t str[MAX_PATH]; - GetDlgItemText(m_hwnd, IDC_RUNATSTART, str, _countof(str)); - if (!str[0]) - return; - - STARTUPINFO si = { sizeof(si) }; - PROCESS_INFORMATION pi; - CreateProcessW(nullptr, str, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - } -}; - -void NetlibLogShowOptions(void) -{ - if (logOptions.hwndOpts == nullptr) - (new CLogOptionsDlg())->Create(); - SetForegroundWindow(logOptions.hwndOpts); -} - -static INT_PTR ShowOptions(WPARAM, LPARAM) -{ - NetlibLogShowOptions(); - return 0; -} - -int NetlibLog_Worker(NetlibUser *nlu, const char *pszMsg, int flags) -{ - if (!bIsActive) - return 0; - - uint32_t dwOriginalLastError = GetLastError(); - - if ((nlu != nullptr && GetNetlibHandleType(nlu) != NLH_USER) || pszMsg == nullptr) { - SetLastError(ERROR_INVALID_PARAMETER); - return 0; - } - - /* if the Netlib user handle is nullptr, just pretend its not */ - if (!(nlu != nullptr ? nlu->toLog : logOptions.toLog)) - return 1; - - LARGE_INTEGER liTimeNow; - char szDate[32], szTime[32], szHead[128]; - switch (logOptions.timeFormat) { - case TIMEFORMAT_HHMMSS: - GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, nullptr, nullptr, szTime, _countof(szTime)); - mir_strcat(szTime, " "); - break; - - case TIMEFORMAT_MILLISECONDS: - QueryPerformanceCounter(&liTimeNow); - liTimeNow.QuadPart -= mirandaStartTime; - mir_snprintf(szTime, "%I64u.%03I64u ", liTimeNow.QuadPart / perfCounterFreq, - 1000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq); - break; - - case TIMEFORMAT_MICROSECONDS: - QueryPerformanceCounter(&liTimeNow); - liTimeNow.QuadPart -= mirandaStartTime; - mir_snprintf(szTime, "%I64u.%06I64u ", liTimeNow.QuadPart / perfCounterFreq, - 1000000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq); - break; - - default: - szTime[0] = 0; - break; - } - - if (logOptions.bPrintDate) { - GetDateFormatA(LOCALE_USER_DEFAULT, 0, nullptr, "yyyy-MM-dd", szDate, _countof(szDate)); - mir_strcat(szDate, " "); - } - else szDate[0] = 0; - - if (flags & MSG_NOTITLE) - szHead[0] = 0; - else { - char *szUser = (logOptions.showUser) ? (nlu == nullptr ? nullptr : nlu->user.szSettingsModule) : nullptr; - if (szUser) - mir_snprintf(szHead, "[%s%s%04X] [%s] ", szDate, szTime, GetCurrentThreadId(), szUser); - else - mir_snprintf(szHead, "[%s%s%04X] ", szDate, szTime, GetCurrentThreadId()); - } - - if (logOptions.toOutputDebugString) { - if (szHead[0]) - OutputDebugStringA(szHead); - OutputDebugStringA(pszMsg); - OutputDebugStringA("\n"); - } - - if (logOptions.toFile && !logOptions.tszFile.IsEmpty()) { - size_t len = mir_strlen(pszMsg); - mir_writeLogA(hLogger, "%s%s%s", szHead, pszMsg, pszMsg[len-1] == '\n' ? "" : "\r\n"); - } - - LOGMSG logMsg = { szHead, pszMsg }; - NotifyFastHook(hLogEvent, (WPARAM)nlu, (LPARAM)&logMsg); - - SetLastError(dwOriginalLastError); - return 1; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void PROTO_INTERFACE::debugLogA(const char *szFormat, ...) -{ - char buf[4096]; - va_list args; - va_start(args, szFormat); - int res = _vsnprintf(buf, _countof(buf), szFormat, args); - NetlibLog_Worker(m_hNetlibUser, (res != -1) ? buf : CMStringA().FormatV(szFormat, args), 0); - va_end(args); -} - -void PROTO_INTERFACE::debugLogW(const wchar_t *wszFormat, ...) -{ - wchar_t buf[4096]; - va_list args; - va_start(args, wszFormat); - int res = _vsnwprintf(buf, _countof(buf), wszFormat, args); - NetlibLog_Worker(m_hNetlibUser, ptrA(mir_utf8encodeW((res != -1) ? buf : CMStringW().FormatV(wszFormat, args))), 0); - va_end(args); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Netlib_Logf(HNETLIBUSER hUser, _Printf_format_string_ const char *fmt, ...) -{ - va_list va; - va_start(va, fmt); - char szText[8000]; - mir_vsnprintf(szText, _countof(szText), fmt, va); - va_end(va); - return NetlibLog_Worker(hUser, szText, 0); -} - -MIR_APP_DLL(int) Netlib_LogfW(HNETLIBUSER hUser, _Printf_format_string_ const wchar_t *fmt, ...) -{ - va_list va; - va_start(va, fmt); - wchar_t szText[8000]; - mir_vsnwprintf(szText, _countof(szText), fmt, va); - va_end(va); - return NetlibLog_Worker(hUser, ptrA(mir_utf8encodeW(szText)), 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Netlib_Log(HNETLIBUSER hUser, const char *pszStr) -{ - return NetlibLog_Worker(hUser, pszStr, 0); -} - -MIR_APP_DLL(int) Netlib_LogW(HNETLIBUSER hUser, const wchar_t *pwszStr) -{ - return NetlibLog_Worker(hUser, ptrA(mir_utf8encodeW(pwszStr)), 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(void) Netlib_Dump(HNETLIBCONN nlc, const void *pBuf, size_t len, bool bIsSent, int flags) -{ - // This section checks a number of conditions and aborts - // the dump if the data should not be written to the log - - // Check packet flags - if (flags & (MSG_PEEK | MSG_NODUMP)) - return; - - // Check user's log settings - if (!(logOptions.toOutputDebugString || GetSubscribersCount((THook*)hLogEvent) != 0 || (logOptions.toFile && !logOptions.tszFile.IsEmpty()))) - return; - if ((bIsSent && !logOptions.dumpSent) || (!bIsSent && !logOptions.dumpRecv)) - return; - if ((flags & MSG_DUMPPROXY) && !logOptions.dumpProxy) - return; - if ((flags & MSG_DUMPSSL) && !logOptions.dumpSsl) - return; - - NetlibUser *nlu; - CMStringA str; - { - mir_cslock lock(csConnectionHeader); - - nlu = nlc ? nlc->nlu : nullptr; - if (!(flags & MSG_NOTITLE)) - str.Format("(%p:%u) Data %s%s\r\n", nlc, nlc ? (int)nlc->s : 0, bIsSent ? "sent" : "received", flags & MSG_DUMPPROXY ? " (proxy)" : ""); - } - - // check filter settings - if (nlu == nullptr) { - if (!logOptions.toLog) - return; - } - else if (!nlu->toLog) - return; - - const uint8_t *buf = (const uint8_t *)pBuf; - - bool isText = true; - if (!logOptions.textDumps) - isText = false; - else if (!(flags & MSG_DUMPASTEXT)) { - if (logOptions.autoDetectText) { - for (size_t i = 0; i < len; i++) { - if ((buf[i] < ' ' && buf[i] != '\t' && buf[i] != '\r' && buf[i] != '\n') || buf[i] >= 0x80) { - isText = false; - break; - } - } - } - else isText = false; - } - - // Text data - if (isText) { - str.Append((const char*)buf, (int)len); - } - // Binary data - else { - for (int line = 0;; line += 16) { - auto *p = buf + line; - int colsInLine = min(16, (int)len - line); - if (colsInLine == 16) - str.AppendFormat("%08X: %02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X ", - line, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); - else { - str.AppendFormat("%08X: ", line); - - // Dump data as hex - int col; - for (col = 0; col < colsInLine; col++) - str.AppendFormat("%02X%c", p[col], ((col & 3) == 3) ? '-' : ' '); - - // Fill out last line with blanks - for (; col < 16; col++) - str.Append(" "); - - str.AppendChar(' '); - } - - for (int col = 0; col < colsInLine; col++) - str.AppendChar((p[col] < ' ') ? '.' : p[col]); - - if (len - line <= 16) - break; - - str.AppendChar('\r'); // End each line with a break - str.AppendChar('\n'); // End each line with a break - } - } - - NetlibLog_Worker(nlu, str, flags); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void NetlibLogInit(void) -{ - LARGE_INTEGER li; - QueryPerformanceFrequency(&li); - perfCounterFreq = li.QuadPart; - QueryPerformanceCounter(&li); - mirandaStartTime = li.QuadPart; - - CreateServiceFunction(MS_NETLIB_LOGWIN, ShowOptions); - hLogEvent = CreateHookableEvent(ME_NETLIB_FASTDUMP); - - InitLog(); - - if (db_get_b(0, "Netlib", "ShowLogOptsAtStart", 0)) - NetlibLogShowOptions(); - - ptrW szBuf(db_get_wsa(0, "Netlib", "RunAtStart")); - if (szBuf) { - STARTUPINFO si = { sizeof(si) }; - PROCESS_INFORMATION pi; - CreateProcess(nullptr, szBuf, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); - } -} - -void NetlibLogShutdown(void) -{ - bIsActive = FALSE; - DestroyHookableEvent(hLogEvent); hLogEvent = nullptr; - if (IsWindow(logOptions.hwndOpts)) - DestroyWindow(logOptions.hwndOpts); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+#define MS_NETLIB_LOGWIN "Netlib/Log/Win"
+
+#define TIMEFORMAT_NONE 0
+#define TIMEFORMAT_HHMMSS 1
+#define TIMEFORMAT_MILLISECONDS 2
+#define TIMEFORMAT_MICROSECONDS 3
+struct {
+ HWND hwndOpts;
+ bool toOutputDebugString, toFile, toLog;
+ bool showUser, rotateLogs, bPrintDate;
+ bool dumpSent, dumpRecv, dumpProxy, dumpSsl;
+ bool textDumps, autoDetectText;
+ int timeFormat;
+ CMStringW tszFile, tszUserFile;
+}
+static logOptions = {};
+
+struct LOGMSG
+{
+ const char* pszHead;
+ const char* pszMsg;
+};
+
+static __int64 mirandaStartTime, perfCounterFreq;
+static int bIsActive = TRUE;
+static HANDLE hLogEvent = nullptr;
+static HANDLE hLogger = nullptr;
+
+static void InitLog()
+{
+ logOptions.dumpRecv = db_get_b(0, "Netlib", "DumpRecv", true) != 0;
+ logOptions.dumpSent = db_get_b(0, "Netlib", "DumpSent", true) != 0;
+ logOptions.dumpProxy = db_get_b(0, "Netlib", "DumpProxy", true) != 0;
+ logOptions.dumpSsl = db_get_b(0, "Netlib", "DumpSsl", false) != 0;
+ logOptions.textDumps = db_get_b(0, "Netlib", "TextDumps", true) != 0;
+ logOptions.autoDetectText = db_get_b(0, "Netlib", "AutoDetectText", true) != 0;
+ logOptions.bPrintDate = db_get_b(0, "Netlib", "PrintDate", false) != 0;
+ logOptions.timeFormat = db_get_b(0, "Netlib", "TimeFormat", TIMEFORMAT_HHMMSS);
+ logOptions.rotateLogs = db_get_b(0, "Netlib", "RotateLogs", false);
+ logOptions.showUser = db_get_b(0, "Netlib", "ShowUser", true) != 0;
+ logOptions.toOutputDebugString = db_get_b(0, "Netlib", "ToOutputDebugString", false) != 0;
+ logOptions.toFile = db_get_b(0, "Netlib", "ToFile", false) != 0;
+ logOptions.toLog = db_get_dw(0, "Netlib", "NLlog", true) != 0;
+
+ if (hLogger) {
+ mir_closeLog(hLogger);
+ hLogger = nullptr;
+ }
+
+ ptrW szBuf(db_get_wsa(0, "Netlib", "File"));
+ if (mir_wstrlen(szBuf)) {
+ logOptions.tszUserFile = szBuf.get();
+
+ wchar_t path[MAX_PATH];
+ PathToAbsoluteW(VARSW(szBuf), path);
+ logOptions.tszFile = path;
+ }
+ else {
+ db_set_ws(0, "Netlib", "File", logOptions.tszUserFile = L"%miranda_logpath%\\netlog.txt");
+ logOptions.tszFile = VARSW(logOptions.tszUserFile);
+ }
+
+ if (logOptions.toFile) {
+ CMStringW wszFileName = logOptions.tszFile;
+ if (logOptions.rotateLogs) {
+ int iLogNumber = db_get_dw(0, "Netlib", "RotateId");
+ wszFileName.AppendFormat(L".%d", iLogNumber);
+ db_set_dw(0, "Netlib", "RotateId", (iLogNumber + 1) % 10);
+ }
+
+ hLogger = mir_createLog("Netlib", LPGENW("Standard Netlib log"), wszFileName, 0);
+ }
+}
+
+static const wchar_t *szTimeFormats[] =
+{
+ LPGENW("No times"),
+ LPGENW("Standard hh:mm:ss times"),
+ LPGENW("Times in milliseconds"),
+ LPGENW("Times in microseconds")
+};
+
+class CLogOptionsDlg : public CDlgBase
+{
+ CCtrlEdit edtFileName;
+ CCtrlCombo cmbTimeFormat;
+ CCtrlButton btnRunNow, btnFileName, btnRunAtStart;
+ CCtrlTreeView treeFilter;
+
+public:
+ CLogOptionsDlg() :
+ CDlgBase(g_plugin, IDD_NETLIBLOGOPTS),
+ treeFilter(this, IDC_FILTER),
+ edtFileName(this, IDC_FILENAME),
+ cmbTimeFormat(this, IDC_TIMEFORMAT),
+ btnRunNow(this, IDC_RUNNOW),
+ btnFileName(this, IDC_FILENAMEBROWSE),
+ btnRunAtStart(this, IDC_RUNATSTARTBROWSE)
+ {
+ btnFileName.OnClick = btnRunAtStart.OnClick = Callback(this, &CLogOptionsDlg::onClick_Browse);
+
+ edtFileName.OnChange = Callback(this, &CLogOptionsDlg::onChange_FileName);
+ }
+
+ bool OnInitDialog() override
+ {
+ logOptions.hwndOpts = m_hwnd;
+
+ CheckDlgButton(m_hwnd, IDC_TOFILE, logOptions.toFile ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_DUMPSSL, logOptions.dumpSsl ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_DUMPRECV, logOptions.dumpRecv ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_DUMPSENT, logOptions.dumpSent ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_SHOWDATE, logOptions.bPrintDate ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_SHOWNAMES, logOptions.showUser ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_DUMPPROXY, logOptions.dumpProxy ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_TEXTDUMPS, logOptions.textDumps ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_LOGROTATE, logOptions.rotateLogs ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_AUTODETECTTEXT, logOptions.autoDetectText ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_SHOWTHISDLGATSTART, db_get_b(0, "Netlib", "ShowLogOptsAtStart", 0) ? BST_CHECKED : BST_UNCHECKED);
+ CheckDlgButton(m_hwnd, IDC_TOOUTPUTDEBUGSTRING, logOptions.toOutputDebugString ? BST_CHECKED : BST_UNCHECKED);
+
+ for (auto &it : szTimeFormats)
+ cmbTimeFormat.AddString(TranslateW(it));
+ cmbTimeFormat.SetCurSel(logOptions.timeFormat);
+
+ edtFileName.SetText(logOptions.tszUserFile);
+ SetDlgItemText(m_hwnd, IDC_PATH, logOptions.tszFile);
+
+ ptrA szRun(db_get_sa(0, "Netlib", "RunAtStart"));
+ if (szRun)
+ SetDlgItemTextA(m_hwnd, IDC_RUNATSTART, szRun);
+
+ SetWindowLongPtr(treeFilter.GetHwnd(), GWL_STYLE, GetWindowLongPtr(treeFilter.GetHwnd(), GWL_STYLE) | (TVS_NOHSCROLL | TVS_CHECKBOXES));
+
+ TVINSERTSTRUCT tvis = {};
+ tvis.hInsertAfter = TVI_SORT;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_STATE;
+ tvis.item.stateMask = TVIS_STATEIMAGEMASK;
+
+ for (auto &it : netlibUser) {
+ tvis.item.pszText = it->user.szDescriptiveName.w;
+ tvis.item.lParam = netlibUser.indexOf(&it);
+ tvis.item.state = INDEXTOSTATEIMAGEMASK(it->toLog ? 2 : 1);
+ treeFilter.InsertItem(&tvis);
+ }
+
+ tvis.item.lParam = -1;
+ tvis.item.pszText = TranslateT("(Miranda core logging)");
+ tvis.item.state = INDEXTOSTATEIMAGEMASK((logOptions.toLog) ? 2 : 1);
+ treeFilter.InsertItem(&tvis);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ wchar_t str[MAX_PATH];
+ GetDlgItemText(m_hwnd, IDC_RUNATSTART, str, _countof(str));
+ db_set_ws(0, "Netlib", "RunAtStart", str);
+
+ edtFileName.GetText(str, _countof(str));
+ logOptions.tszUserFile = rtrimw(str);
+ db_set_ws(0, "Netlib", "File", str);
+
+ GetDlgItemText(m_hwnd, IDC_PATH, str, _countof(str));
+ logOptions.tszFile = rtrimw(str);
+
+ db_set_b(0, "Netlib", "ToFile", IsDlgButtonChecked(m_hwnd, IDC_TOFILE));
+ db_set_b(0, "Netlib", "DumpSsl", IsDlgButtonChecked(m_hwnd, IDC_DUMPSSL));
+ db_set_b(0, "Netlib", "DumpRecv", IsDlgButtonChecked(m_hwnd, IDC_DUMPRECV));
+ db_set_b(0, "Netlib", "DumpSent", IsDlgButtonChecked(m_hwnd, IDC_DUMPSENT));
+ db_set_b(0, "Netlib", "ShowUser", IsDlgButtonChecked(m_hwnd, IDC_SHOWNAMES));
+ db_set_b(0, "Netlib", "DumpProxy", IsDlgButtonChecked(m_hwnd, IDC_DUMPPROXY));
+ db_set_b(0, "Netlib", "PrintDate", IsDlgButtonChecked(m_hwnd, IDC_SHOWDATE));
+ db_set_b(0, "Netlib", "TextDumps", IsDlgButtonChecked(m_hwnd, IDC_TEXTDUMPS));
+ db_set_b(0, "Netlib", "RotateLogs", IsDlgButtonChecked(m_hwnd, IDC_LOGROTATE));
+ db_set_b(0, "Netlib", "AutoDetectText", IsDlgButtonChecked(m_hwnd, IDC_AUTODETECTTEXT));
+ db_set_b(0, "Netlib", "ShowLogOptsAtStart", IsDlgButtonChecked(m_hwnd, IDC_SHOWTHISDLGATSTART));
+ db_set_b(0, "Netlib", "ToOutputDebugString", IsDlgButtonChecked(m_hwnd, IDC_TOOUTPUTDEBUGSTRING));
+
+ db_set_b(0, "Netlib", "TimeFormat", cmbTimeFormat.GetCurSel());
+
+ TVITEMEX tvi = {};
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_STATE | TVIF_TEXT;
+ tvi.hItem = treeFilter.GetRoot();
+
+ while (tvi.hItem) {
+ treeFilter.GetItem(&tvi);
+ bool checked = ((tvi.state & TVIS_STATEIMAGEMASK) >> 12 == 2);
+
+ if (tvi.lParam == -1) {
+ logOptions.toLog = checked;
+ db_set_dw(0, "Netlib", "NLlog", checked);
+ }
+ else if (tvi.lParam < netlibUser.getCount()) {
+ netlibUser[tvi.lParam]->toLog = checked;
+ db_set_dw(0, netlibUser[tvi.lParam]->user.szSettingsModule, "NLlog", checked);
+ }
+
+ tvi.hItem = treeFilter.GetNextSibling(tvi.hItem);
+ }
+
+ InitLog();
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ ImageList_Destroy(TreeView_GetImageList(GetDlgItem(m_hwnd, IDC_FILTER), TVSIL_STATE));
+ logOptions.hwndOpts = nullptr;
+ }
+
+ void onChange_FileName(CCtrlEdit *pEdit)
+ {
+ if (pEdit->GetHwnd() == GetFocus())
+ CheckDlgButton(m_hwnd, IDC_TOFILE, BST_CHECKED);
+
+ wchar_t path[MAX_PATH];
+ pEdit->GetText(path, _countof(path));
+ PathToAbsoluteW(VARSW(path), path);
+ SetDlgItemText(m_hwnd, IDC_PATH, path);
+ }
+
+ void onClick_Browse(CCtrlButton *pButton)
+ {
+ wchar_t str[MAX_PATH];
+ GetWindowText(GetWindow(pButton->GetHwnd(), GW_HWNDPREV), str, _countof(str));
+
+ wchar_t filter[200];
+ mir_snwprintf(filter, L"%s (*)%c*%c", TranslateT("All files"), 0, 0);
+
+ OPENFILENAME ofn = { 0 };
+ ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400;
+ ofn.hwndOwner = m_hwnd;
+ ofn.Flags = OFN_HIDEREADONLY | OFN_DONTADDTORECENT;
+ if (pButton->GetCtrlId() == IDC_FILENAMEBROWSE)
+ ofn.lpstrTitle = TranslateT("Select where log file will be created");
+ else {
+ ofn.Flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
+ ofn.lpstrTitle = TranslateT("Select program to be run");
+ }
+ ofn.lpstrFilter = filter;
+ ofn.lpstrFile = str;
+ ofn.nMaxFile = _countof(str) - 2;
+ ofn.nMaxFileTitle = MAX_PATH;
+ if (pButton->GetCtrlId() == IDC_FILENAMEBROWSE) {
+ if (!GetSaveFileName(&ofn))
+ return;
+ }
+ else if (!GetOpenFileName(&ofn))
+ return;
+
+ if (pButton->GetCtrlId() == IDC_RUNATSTARTBROWSE && wcschr(str, ' ') != nullptr) {
+ memmove(str + 1, str, ((_countof(str) - 2) * sizeof(wchar_t)));
+ str[0] = '"';
+ mir_wstrcat(str, L"\"");
+ }
+ SetWindowText(GetWindow(pButton->GetHwnd(), GW_HWNDPREV), str);
+ }
+
+ void onClick_RunNow(CCtrlButton *)
+ {
+ wchar_t str[MAX_PATH];
+ GetDlgItemText(m_hwnd, IDC_RUNATSTART, str, _countof(str));
+ if (!str[0])
+ return;
+
+ STARTUPINFO si = { sizeof(si) };
+ PROCESS_INFORMATION pi;
+ CreateProcessW(nullptr, str, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+};
+
+void NetlibLogShowOptions(void)
+{
+ if (logOptions.hwndOpts == nullptr)
+ (new CLogOptionsDlg())->Create();
+ SetForegroundWindow(logOptions.hwndOpts);
+}
+
+static INT_PTR ShowOptions(WPARAM, LPARAM)
+{
+ NetlibLogShowOptions();
+ return 0;
+}
+
+int NetlibLog_Worker(NetlibUser *nlu, const char *pszMsg, int flags)
+{
+ if (!bIsActive)
+ return 0;
+
+ uint32_t dwOriginalLastError = GetLastError();
+
+ if ((nlu != nullptr && GetNetlibHandleType(nlu) != NLH_USER) || pszMsg == nullptr) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+ /* if the Netlib user handle is nullptr, just pretend its not */
+ if (!(nlu != nullptr ? nlu->toLog : logOptions.toLog))
+ return 1;
+
+ LARGE_INTEGER liTimeNow;
+ char szDate[32], szTime[32], szHead[128];
+ switch (logOptions.timeFormat) {
+ case TIMEFORMAT_HHMMSS:
+ GetTimeFormatA(LOCALE_USER_DEFAULT, TIME_FORCE24HOURFORMAT | TIME_NOTIMEMARKER, nullptr, nullptr, szTime, _countof(szTime));
+ mir_strcat(szTime, " ");
+ break;
+
+ case TIMEFORMAT_MILLISECONDS:
+ QueryPerformanceCounter(&liTimeNow);
+ liTimeNow.QuadPart -= mirandaStartTime;
+ mir_snprintf(szTime, "%I64u.%03I64u ", liTimeNow.QuadPart / perfCounterFreq,
+ 1000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq);
+ break;
+
+ case TIMEFORMAT_MICROSECONDS:
+ QueryPerformanceCounter(&liTimeNow);
+ liTimeNow.QuadPart -= mirandaStartTime;
+ mir_snprintf(szTime, "%I64u.%06I64u ", liTimeNow.QuadPart / perfCounterFreq,
+ 1000000 * (liTimeNow.QuadPart % perfCounterFreq) / perfCounterFreq);
+ break;
+
+ default:
+ szTime[0] = 0;
+ break;
+ }
+
+ if (logOptions.bPrintDate) {
+ GetDateFormatA(LOCALE_USER_DEFAULT, 0, nullptr, "yyyy-MM-dd", szDate, _countof(szDate));
+ mir_strcat(szDate, " ");
+ }
+ else szDate[0] = 0;
+
+ if (flags & MSG_NOTITLE)
+ szHead[0] = 0;
+ else {
+ char *szUser = (logOptions.showUser) ? (nlu == nullptr ? nullptr : nlu->user.szSettingsModule) : nullptr;
+ if (szUser)
+ mir_snprintf(szHead, "[%s%s%04X] [%s] ", szDate, szTime, GetCurrentThreadId(), szUser);
+ else
+ mir_snprintf(szHead, "[%s%s%04X] ", szDate, szTime, GetCurrentThreadId());
+ }
+
+ if (logOptions.toOutputDebugString) {
+ if (szHead[0])
+ OutputDebugStringA(szHead);
+ OutputDebugStringA(pszMsg);
+ OutputDebugStringA("\n");
+ }
+
+ if (logOptions.toFile && !logOptions.tszFile.IsEmpty()) {
+ size_t len = mir_strlen(pszMsg);
+ mir_writeLogA(hLogger, "%s%s%s", szHead, pszMsg, pszMsg[len-1] == '\n' ? "" : "\r\n");
+ }
+
+ LOGMSG logMsg = { szHead, pszMsg };
+ NotifyFastHook(hLogEvent, (WPARAM)nlu, (LPARAM)&logMsg);
+
+ SetLastError(dwOriginalLastError);
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void PROTO_INTERFACE::debugLogA(const char *szFormat, ...)
+{
+ char buf[4096];
+ va_list args;
+ va_start(args, szFormat);
+ int res = _vsnprintf(buf, _countof(buf), szFormat, args);
+ NetlibLog_Worker(m_hNetlibUser, (res != -1) ? buf : CMStringA().FormatV(szFormat, args), 0);
+ va_end(args);
+}
+
+void PROTO_INTERFACE::debugLogW(const wchar_t *wszFormat, ...)
+{
+ wchar_t buf[4096];
+ va_list args;
+ va_start(args, wszFormat);
+ int res = _vsnwprintf(buf, _countof(buf), wszFormat, args);
+ NetlibLog_Worker(m_hNetlibUser, ptrA(mir_utf8encodeW((res != -1) ? buf : CMStringW().FormatV(wszFormat, args))), 0);
+ va_end(args);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Netlib_Logf(HNETLIBUSER hUser, _Printf_format_string_ const char *fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ char szText[8000];
+ mir_vsnprintf(szText, _countof(szText), fmt, va);
+ va_end(va);
+ return NetlibLog_Worker(hUser, szText, 0);
+}
+
+MIR_APP_DLL(int) Netlib_LogfW(HNETLIBUSER hUser, _Printf_format_string_ const wchar_t *fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ wchar_t szText[8000];
+ mir_vsnwprintf(szText, _countof(szText), fmt, va);
+ va_end(va);
+ return NetlibLog_Worker(hUser, ptrA(mir_utf8encodeW(szText)), 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Netlib_Log(HNETLIBUSER hUser, const char *pszStr)
+{
+ return NetlibLog_Worker(hUser, pszStr, 0);
+}
+
+MIR_APP_DLL(int) Netlib_LogW(HNETLIBUSER hUser, const wchar_t *pwszStr)
+{
+ return NetlibLog_Worker(hUser, ptrA(mir_utf8encodeW(pwszStr)), 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(void) Netlib_Dump(HNETLIBCONN nlc, const void *pBuf, size_t len, bool bIsSent, int flags)
+{
+ // This section checks a number of conditions and aborts
+ // the dump if the data should not be written to the log
+
+ // Check packet flags
+ if (flags & (MSG_PEEK | MSG_NODUMP))
+ return;
+
+ // Check user's log settings
+ if (!(logOptions.toOutputDebugString || GetSubscribersCount((THook*)hLogEvent) != 0 || (logOptions.toFile && !logOptions.tszFile.IsEmpty())))
+ return;
+ if ((bIsSent && !logOptions.dumpSent) || (!bIsSent && !logOptions.dumpRecv))
+ return;
+ if ((flags & MSG_DUMPPROXY) && !logOptions.dumpProxy)
+ return;
+ if ((flags & MSG_DUMPSSL) && !logOptions.dumpSsl)
+ return;
+
+ NetlibUser *nlu;
+ CMStringA str;
+ {
+ mir_cslock lock(csConnectionHeader);
+
+ nlu = nlc ? nlc->nlu : nullptr;
+ if (!(flags & MSG_NOTITLE))
+ str.Format("(%p:%u) Data %s%s\r\n", nlc, nlc ? (int)nlc->s : 0, bIsSent ? "sent" : "received", flags & MSG_DUMPPROXY ? " (proxy)" : "");
+ }
+
+ // check filter settings
+ if (nlu == nullptr) {
+ if (!logOptions.toLog)
+ return;
+ }
+ else if (!nlu->toLog)
+ return;
+
+ const uint8_t *buf = (const uint8_t *)pBuf;
+
+ bool isText = true;
+ if (!logOptions.textDumps)
+ isText = false;
+ else if (!(flags & MSG_DUMPASTEXT)) {
+ if (logOptions.autoDetectText) {
+ for (size_t i = 0; i < len; i++) {
+ if ((buf[i] < ' ' && buf[i] != '\t' && buf[i] != '\r' && buf[i] != '\n') || buf[i] >= 0x80) {
+ isText = false;
+ break;
+ }
+ }
+ }
+ else isText = false;
+ }
+
+ // Text data
+ if (isText) {
+ str.Append((const char*)buf, (int)len);
+ }
+ // Binary data
+ else {
+ for (int line = 0;; line += 16) {
+ auto *p = buf + line;
+ int colsInLine = min(16, (int)len - line);
+ if (colsInLine == 16)
+ str.AppendFormat("%08X: %02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X-%02X %02X %02X %02X ",
+ line, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
+ else {
+ str.AppendFormat("%08X: ", line);
+
+ // Dump data as hex
+ int col;
+ for (col = 0; col < colsInLine; col++)
+ str.AppendFormat("%02X%c", p[col], ((col & 3) == 3) ? '-' : ' ');
+
+ // Fill out last line with blanks
+ for (; col < 16; col++)
+ str.Append(" ");
+
+ str.AppendChar(' ');
+ }
+
+ for (int col = 0; col < colsInLine; col++)
+ str.AppendChar((p[col] < ' ') ? '.' : p[col]);
+
+ if (len - line <= 16)
+ break;
+
+ str.AppendChar('\r'); // End each line with a break
+ str.AppendChar('\n'); // End each line with a break
+ }
+ }
+
+ NetlibLog_Worker(nlu, str, flags);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void NetlibLogInit(void)
+{
+ LARGE_INTEGER li;
+ QueryPerformanceFrequency(&li);
+ perfCounterFreq = li.QuadPart;
+ QueryPerformanceCounter(&li);
+ mirandaStartTime = li.QuadPart;
+
+ CreateServiceFunction(MS_NETLIB_LOGWIN, ShowOptions);
+ hLogEvent = CreateHookableEvent(ME_NETLIB_FASTDUMP);
+
+ InitLog();
+
+ if (db_get_b(0, "Netlib", "ShowLogOptsAtStart", 0))
+ NetlibLogShowOptions();
+
+ ptrW szBuf(db_get_wsa(0, "Netlib", "RunAtStart"));
+ if (szBuf) {
+ STARTUPINFO si = { sizeof(si) };
+ PROCESS_INFORMATION pi;
+ CreateProcess(nullptr, szBuf, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
+ }
+}
+
+void NetlibLogShutdown(void)
+{
+ bIsActive = FALSE;
+ DestroyHookableEvent(hLogEvent); hLogEvent = nullptr;
+ if (IsWindow(logOptions.hwndOpts))
+ DestroyWindow(logOptions.hwndOpts);
+}
diff --git a/src/mir_app/src/netlib_openconn.cpp b/src/mir_app/src/netlib_openconn.cpp index 49e584e4d3..7d9b0b9114 100644 --- a/src/mir_app/src/netlib_openconn.cpp +++ b/src/mir_app/src/netlib_openconn.cpp @@ -1,726 +1,726 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -extern mir_cs csNetlibUser; -extern uint32_t g_LastConnectionTick; -extern int connectionTimeout; -static int iUPnPCleanup = 0; - -#define RECV_DEFAULT_TIMEOUT 60000 - -//returns in network byte order -uint32_t DnsLookup(NetlibUser *nlu, const char *szHost) -{ - HOSTENT* host; - uint32_t ip = inet_addr(szHost); - if (ip != INADDR_NONE) - return ip; - - __try { - host = gethostbyname(szHost); - if (host) - return *(u_long*)host->h_addr_list[0]; - - Netlib_Logf(nlu, "%s %d: %s() for host %s failed (%u)", __FILE__, __LINE__, "gethostbyname", szHost, WSAGetLastError()); - } - __except (EXCEPTION_EXECUTE_HANDLER) {} - - return 0; -} - -int WaitUntilReadable(SOCKET s, uint32_t dwTimeout, bool check) -{ - fd_set readfd; - TIMEVAL tv; - - if (s == INVALID_SOCKET) return SOCKET_ERROR; - - tv.tv_sec = dwTimeout / 1000; - tv.tv_usec = (dwTimeout % 1000) * 1000; - - FD_ZERO(&readfd); - FD_SET(s, &readfd); - - int result = select(0, &readfd, nullptr, nullptr, &tv); - if (result == 0 && !check) SetLastError(ERROR_TIMEOUT); - return result; -} - -int WaitUntilWritable(SOCKET s, uint32_t dwTimeout) -{ - fd_set writefd; - TIMEVAL tv; - - tv.tv_sec = dwTimeout / 1000; - tv.tv_usec = (dwTimeout % 1000) * 1000; - - FD_ZERO(&writefd); - FD_SET(s, &writefd); - - switch (select(0, nullptr, &writefd, nullptr, &tv)) { - case 0: - SetLastError(ERROR_TIMEOUT); - case SOCKET_ERROR: - return 0; - } - return 1; -} - -bool RecvUntilTimeout(NetlibConnection *nlc, char *buf, int len, int flags, uint32_t dwTimeout) -{ - int nReceived = 0; - uint32_t dwTimeNow, dwCompleteTime = GetTickCount() + dwTimeout; - - while ((dwTimeNow = GetTickCount()) < dwCompleteTime) { - if (WaitUntilReadable(nlc->s, dwCompleteTime - dwTimeNow) <= 0) return false; - nReceived = Netlib_Recv(nlc, buf, len, flags); - if (nReceived <= 0) return false; - - buf += nReceived; - len -= nReceived; - if (len <= 0) return true; - } - SetLastError(ERROR_TIMEOUT); - return false; -} - -static int NetlibInitSocks4Connection(NetlibConnection *nlc) -{ - // http://www.socks.nec.com/protocol/socks4.protocol and http://www.socks.nec.com/protocol/socks4a.protocol - NetlibUrl &url = nlc->url; - if (url.szHost.IsEmpty()) - return 0; - - NetlibUser *nlu = nlc->nlu; - size_t nHostLen = mir_strlen(url.szHost) + 1; - size_t nUserLen = nlu->settings.szProxyAuthUser ? mir_strlen(nlu->settings.szProxyAuthUser) + 1 : 1; - size_t len = 8 + nUserLen; - - char* pInit = (char*)alloca(len + nHostLen); - pInit[0] = 4; // SOCKS4 - pInit[1] = 1; //connect - *(PWORD)&pInit[2] = htons(url.port); - - if (nUserLen <= 1) pInit[8] = 0; - else memcpy(&pInit[8], nlu->settings.szProxyAuthUser, nUserLen); - - //if cannot resolve host, try resolving through proxy (requires SOCKS4a) - uint32_t ip = DnsLookup(nlu, url.szHost); - *(PDWORD)&pInit[4] = ip ? ip : 0x01000000; - if (!ip) { - memcpy(&pInit[len], url.szHost, nHostLen); - len += nHostLen; - } - - if (Netlib_Send(nlc, pInit, (int)len, MSG_DUMPPROXY) == SOCKET_ERROR) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Send", GetLastError()); - return 0; - } - - char reply[8]; - if (!RecvUntilTimeout(nlc, reply, sizeof(reply), MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError()); - return 0; - } - - switch ((uint8_t)reply[1]) { - case 90: return 1; - case 91: SetLastError(ERROR_ACCESS_DENIED); break; - case 92: SetLastError(ERROR_CONNECTION_UNAVAIL); break; - case 93: SetLastError(ERROR_INVALID_ACCESS); break; - default: SetLastError(ERROR_INVALID_DATA); break; - } - Netlib_Logf(nlu, "%s %d: Proxy connection failed (%x %u)", __FILE__, __LINE__, (uint8_t)reply[1], GetLastError()); - return 0; -} - -static int NetlibInitSocks5Connection(NetlibConnection *nlc) -{ - //rfc1928 - uint8_t buf[258]; - NetlibUser *nlu = nlc->nlu; - - buf[0] = 5; //yep, socks5 - buf[1] = 1; //one auth method - buf[2] = nlu->settings.useProxyAuth ? 2 : 0; - if (Netlib_Send(nlc, (char*)buf, 3, MSG_DUMPPROXY) == SOCKET_ERROR) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Send", GetLastError()); - return 0; - } - - //confirmation of auth method - if (!RecvUntilTimeout(nlc, (char*)buf, 2, MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError()); - return 0; - } - if ((buf[1] != 0 && buf[1] != 2)) { - SetLastError(ERROR_INVALID_ID_AUTHORITY); - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Recv", GetLastError()); - return 0; - } - - if (buf[1] == 2) { //rfc1929 - size_t nUserLen = mir_strlen(nlu->settings.szProxyAuthUser); - size_t nPassLen = mir_strlen(nlu->settings.szProxyAuthPassword); - uint8_t *pAuthBuf = (uint8_t*)mir_alloc(3 + nUserLen + nPassLen); - pAuthBuf[0] = 1; //auth version - pAuthBuf[1] = (uint8_t)nUserLen; - memcpy(pAuthBuf + 2, nlu->settings.szProxyAuthUser, nUserLen); - pAuthBuf[2 + nUserLen] = (uint8_t)nPassLen; - memcpy(pAuthBuf + 3 + nUserLen, nlu->settings.szProxyAuthPassword, nPassLen); - if (Netlib_Send(nlc, (char*)pAuthBuf, int(3 + nUserLen + nPassLen), MSG_DUMPPROXY) == SOCKET_ERROR) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Send", GetLastError()); - mir_free(pAuthBuf); - return 0; - } - mir_free(pAuthBuf); - - if (!RecvUntilTimeout(nlc, (char*)buf, 2, MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError()); - return 0; - } - if (buf[1]) { - SetLastError(ERROR_ACCESS_DENIED); - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError()); - return 0; - } - } - - size_t nHostLen; - uint32_t hostIP; - - NetlibUrl &url = nlc->url; - if (nlc->dnsThroughProxy) { - hostIP = inet_addr(url.szHost); - nHostLen = (hostIP == INADDR_NONE) ? mir_strlen(url.szHost) + 1 : 4; - } - else { - hostIP = DnsLookup(nlu, url.szHost); - if (hostIP == 0) - return 0; - nHostLen = 4; - } - uint8_t *pInit = (uint8_t*)mir_alloc(6 + nHostLen); - pInit[0] = 5; //SOCKS5 - pInit[1] = url.flags & NLOCF_UDP ? 3 : 1; //connect or UDP - pInit[2] = 0; //reserved - if (hostIP == INADDR_NONE) { //DNS lookup through proxy - pInit[3] = 3; - pInit[4] = uint8_t(nHostLen - 1); - memcpy(pInit + 5, url.szHost, nHostLen - 1); - } - else { - pInit[3] = 1; - *(PDWORD)(pInit + 4) = hostIP; - } - *(PWORD)(pInit + 4 + nHostLen) = htons(url.port); - if (Netlib_Send(nlc, (char*)pInit, int(6 + nHostLen), MSG_DUMPPROXY) == SOCKET_ERROR) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Send", GetLastError()); - mir_free(pInit); - return 0; - } - mir_free(pInit); - - if (!RecvUntilTimeout(nlc, (char*)buf, 5, MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError()); - return 0; - } - - if (buf[0] != 5 || buf[1]) { - const char* err = "Unknown response"; - if (buf[0] != 5) - SetLastError(ERROR_BAD_FORMAT); - else { - switch (buf[1]) { - case 1: SetLastError(ERROR_GEN_FAILURE); err = "General failure"; break; - case 2: SetLastError(ERROR_ACCESS_DENIED); err = "Connection not allowed by ruleset"; break; - case 3: SetLastError(WSAENETUNREACH); err = "Network unreachable"; break; - case 4: SetLastError(WSAEHOSTUNREACH); err = "Host unreachable"; break; - case 5: SetLastError(WSAECONNREFUSED); err = "Connection refused by destination host"; break; - case 6: SetLastError(WSAETIMEDOUT); err = "TTL expired"; break; - case 7: SetLastError(ERROR_CALL_NOT_IMPLEMENTED); err = "Command not supported / protocol error"; break; - case 8: SetLastError(ERROR_INVALID_ADDRESS); err = "Address type not supported"; break; - default: SetLastError(ERROR_INVALID_DATA); break; - } - } - Netlib_Logf(nlu, "%s %d: Proxy conection failed. %s.", __FILE__, __LINE__, err); - return 0; - } - - int nRecvSize = 0; - switch (buf[3]) { - case 1:// ipv4 addr - nRecvSize = 5; - break; - case 3:// dns name addr - nRecvSize = buf[4] + 2; - break; - case 4:// ipv6 addr - nRecvSize = 17; - break; - default: - Netlib_Logf(nlu, "%s %d: %s() unknown address type (%u)", __FILE__, __LINE__, "NetlibInitSocks5Connection", (int)buf[3]); - return 0; - } - if (!RecvUntilTimeout(nlc, (char*)buf, nRecvSize, MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) { - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError()); - return 0; - } - - //connected - return 1; -} - -static bool NetlibInitHttpsConnection(NetlibConnection *nlc) -{ - // rfc2817 - NetlibUrl &url = nlc->url; - CMStringA szUrl; - if (nlc->dnsThroughProxy) - szUrl.Format("%s:%u", url.szHost.c_str(), url.port); - else { - uint32_t ip = DnsLookup(nlc->nlu, url.szHost); - if (ip == 0) return false; - szUrl.Format("%s:%u", inet_ntoa(*(PIN_ADDR)&ip), url.port); - } - - NETLIBHTTPREQUEST nlhrSend = { 0 }; - nlhrSend.cbSize = sizeof(nlhrSend); - nlhrSend.requestType = REQUEST_CONNECT; - nlhrSend.flags = NLHRF_DUMPPROXY | NLHRF_HTTP11 | NLHRF_NOPROXY | NLHRF_REDIRECT; - nlhrSend.szUrl = szUrl.GetBuffer(); - - if (Netlib_SendHttpRequest(nlc, &nlhrSend) == SOCKET_ERROR) - return false; - - NETLIBHTTPREQUEST *nlhrReply = NetlibHttpRecv(nlc, MSG_DUMPPROXY | MSG_RAW, MSG_DUMPPROXY | MSG_RAW, true); - if (nlhrReply == nullptr) - return false; - - if (nlhrReply->resultCode < 200 || nlhrReply->resultCode >= 300) { - if (nlhrReply->resultCode == 403 && nlc->dnsThroughProxy) { - Netlib_FreeHttpRequest(nlhrReply); - nlc->dnsThroughProxy = 0; - return NetlibInitHttpsConnection(nlc); - } - - NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode); - Netlib_Logf(nlc->nlu, "%s %d: %s request failed (%u %s)", __FILE__, __LINE__, - nlc->nlu->settings.proxyType == PROXYTYPE_HTTP ? "HTTP" : "HTTPS", nlhrReply->resultCode, nlhrReply->szResultDescr); - Netlib_FreeHttpRequest(nlhrReply); - return 0; - } - Netlib_FreeHttpRequest(nlhrReply); - return true; // connected -} - -static void FreePartiallyInitedConnection(NetlibConnection *nlc) -{ - uint32_t dwOriginalLastError = GetLastError(); - - if (GetNetlibHandleType(nlc) == NLH_CONNECTION) - delete nlc; - - SetLastError(dwOriginalLastError); -} - -static bool my_connectIP(NetlibConnection *nlc) -{ - NetlibUser *nlu = nlc->nlu; - int rc = SOCKET_ERROR, retrycnt = 0; - u_long notblocking = 1; - uint32_t lasterr = 0; - static const TIMEVAL tv = { 1, 0 }; - - // if timeout is zero then its an old style connection or new with a 0 timeout, select() will error quicker anyway - int timeout = (nlc->timeout <= 0) ? 30 : nlc->timeout; - - // this is for XP SP2 where there is a default connection attempt limit of 10/second - if (connectionTimeout) { - WaitForSingleObject(hConnectionOpenMutex, 10000); - int waitdiff = GetTickCount() - g_LastConnectionTick; - if (waitdiff < connectionTimeout) SleepEx(connectionTimeout, TRUE); - g_LastConnectionTick = GetTickCount(); - ReleaseMutex(hConnectionOpenMutex); - - // might have died in between the wait - if (Miranda_IsTerminated()) - return false; - } - - char szPort[6]; - addrinfo *air = nullptr, *ai, hints = { 0 }; - - hints.ai_family = AF_UNSPEC; - - NetlibUrl &url = nlc->url; - if (url.flags & NLOCF_UDP) { - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - } - else { - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - } - - if (nlc->proxyType) { - if (!nlc->szProxyServer) - return false; - - Netlib_Logf(nlu, "(%p) Connecting to proxy %s:%d for %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort, url.szHost.c_str(), url.port); - - _itoa(nlc->wProxyPort, szPort, 10); - if (GetAddrInfoA(nlc->szProxyServer, szPort, &hints, &air)) { - Netlib_Logf(nlu, "%s %d: %s() for host %s failed (%u)", __FILE__, __LINE__, "getaddrinfo", nlc->szProxyServer, WSAGetLastError()); - return false; - } - } - else { - if (url.szHost.IsEmpty()) - return false; - - Netlib_Logf(nlu, "(%p) Connecting to server %s:%d....", nlc, url.szHost.c_str(), url.port); - - _itoa(url.port, szPort, 10); - - if (GetAddrInfoA(url.szHost, szPort, &hints, &air)) { - Netlib_Logf(nlu, "%s %d: %s() for host %s failed (%u)", __FILE__, __LINE__, "getaddrinfo", url.szHost.c_str(), WSAGetLastError()); - return false; - } - } - - for (ai = air; ai && !Miranda_IsTerminated(); ai = ai->ai_next) { - Netlib_Logf(nlu, "(%p) Connecting to ip %s ....", nlc, ptrA(Netlib_AddressToString((sockaddr_in*)ai->ai_addr)).get()); -retry: - nlc->s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (nlc->s == INVALID_SOCKET) { - FreeAddrInfoA(air); - return false; - } - - // return the socket to non blocking - if (ioctlsocket(nlc->s, FIONBIO, ¬blocking) != 0) { - FreeAddrInfoA(air); - return false; - } - - if (nlu->settings.specifyOutgoingPorts && nlu->settings.szOutgoingPorts && nlu->settings.szOutgoingPorts[0]) { - SOCKET s = ai->ai_family == AF_INET ? nlc->s : INVALID_SOCKET; - SOCKET s6 = ai->ai_family == AF_INET6 ? nlc->s : INVALID_SOCKET; - if (!BindSocketToPort(nlu->settings.szOutgoingPorts, s, s6, &nlu->inportnum)) - Netlib_Logf(nlu, "Netlib connect: Not enough ports for outgoing connections specified"); - } - - // try a connect - if (connect(nlc->s, ai->ai_addr, (int)ai->ai_addrlen) == 0) { - rc = 0; - break; - } - - // didn't work, was it cos of nonblocking? - if (WSAGetLastError() != WSAEWOULDBLOCK) { - rc = SOCKET_ERROR; - closesocket(nlc->s); - nlc->s = INVALID_SOCKET; - continue; - } - - while (true) { // timeout loop - fd_set r, w, e; - FD_ZERO(&r); FD_ZERO(&w); FD_ZERO(&e); - FD_SET(nlc->s, &r); - FD_SET(nlc->s, &w); - FD_SET(nlc->s, &e); - if ((rc = select(0, &r, &w, &e, &tv)) == SOCKET_ERROR) - break; - - if (rc > 0) { - if (FD_ISSET(nlc->s, &w)) { - // connection was successful - rc = 0; - lasterr = 0; - } - if (FD_ISSET(nlc->s, &r)) { - // connection was closed - rc = SOCKET_ERROR; - lasterr = WSAECONNRESET; - } - if (FD_ISSET(nlc->s, &e)) { - // connection failed. - int len = sizeof(lasterr); - rc = SOCKET_ERROR; - getsockopt(nlc->s, SOL_SOCKET, SO_ERROR, (char*)&lasterr, &len); - if (lasterr == WSAEADDRINUSE && ++retrycnt <= 2) { - closesocket(nlc->s); - nlc->s = INVALID_SOCKET; - goto retry; - } - } - break; - } - else if (Miranda_IsTerminated()) { - rc = SOCKET_ERROR; - lasterr = ERROR_TIMEOUT; - break; - } - - if (--timeout == 0) { - rc = SOCKET_ERROR; - lasterr = ERROR_TIMEOUT; - break; - } - } - - if (rc == 0) break; - - closesocket(nlc->s); - nlc->s = INVALID_SOCKET; - } - - FreeAddrInfoA(air); - - notblocking = 0; - if (nlc->s != INVALID_SOCKET) ioctlsocket(nlc->s, FIONBIO, ¬blocking); - if (rc && lasterr) SetLastError(lasterr); - return rc == 0; -} - -static int NetlibHttpFallbackToDirect(NetlibConnection *nlc) -{ - NetlibDoCloseSocket(nlc, true); - - Netlib_Logf(nlc->nlu, "Fallback to direct connection"); - - nlc->proxyAuthNeeded = false; - nlc->proxyType = 0; - replaceStr(nlc->szProxyServer, nullptr); - if (!my_connectIP(nlc)) { - Netlib_Logf(nlc->nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError()); - return false; - } - return true; -} - -bool NetlibDoConnect(NetlibConnection *nlc) -{ - NetlibUser *nlu = nlc->nlu; - - replaceStr(nlc->szProxyServer, nullptr); - - bool usingProxy = false, forceHttps = false; - if (nlu->settings.useProxy) { - if (nlu->settings.proxyType == PROXYTYPE_IE) - usingProxy = NetlibGetIeProxyConn(nlc, false); - else { - if (nlu->settings.szProxyServer && nlu->settings.szProxyServer[0]) { - nlc->szProxyServer = mir_strdup(nlu->settings.szProxyServer); - nlc->wProxyPort = nlu->settings.wProxyPort; - nlc->proxyType = nlu->settings.proxyType; - usingProxy = true; - } - } - } - - while (!my_connectIP(nlc)) { - // if connection failed, the state of nlc might be unpredictable - if (GetNetlibHandleType(nlc) == NLH_CONNECTION) { - // Fallback to direct only when using HTTP proxy, as this is what used by companies - // If other type of proxy used it's an indication of security nutcase, leave him alone - if (usingProxy && (nlc->proxyType == PROXYTYPE_HTTPS || nlc->proxyType == PROXYTYPE_HTTP)) { - usingProxy = false; - nlc->proxyType = 0; - Netlib_Logf(nlu, "Fallback to direct connection"); - continue; - } - if (nlu->settings.useProxy && !usingProxy && nlu->settings.proxyType == PROXYTYPE_IE && !forceHttps) { - forceHttps = true; - usingProxy = NetlibGetIeProxyConn(nlc, true); - if (usingProxy) - continue; - } - } - Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError()); - return false; - } - - if (usingProxy && !((nlc->url.flags & (NLOCF_HTTP | NLOCF_SSL)) == NLOCF_HTTP && (nlc->proxyType == PROXYTYPE_HTTP || nlc->proxyType == PROXYTYPE_HTTPS))) { - if (!WaitUntilWritable(nlc->s, 30000)) - return false; - - switch (nlc->proxyType) { - case PROXYTYPE_SOCKS4: - if (!NetlibInitSocks4Connection(nlc)) - return false; - break; - - case PROXYTYPE_SOCKS5: - if (!NetlibInitSocks5Connection(nlc)) - return false; - break; - - case PROXYTYPE_HTTPS: - case PROXYTYPE_HTTP: - nlc->proxyAuthNeeded = true; - if (!NetlibInitHttpsConnection(nlc)) { - usingProxy = false; - if (!NetlibHttpFallbackToDirect(nlc)) - return false; - } - break; - - default: - SetLastError(ERROR_INVALID_PARAMETER); - FreePartiallyInitedConnection(nlc); - return false; - } - } - - Netlib_Logf(nlu, "(%d) Connected to %s:%d", nlc->s, nlc->url.szHost.c_str(), nlc->url.port); - - if (GetSubscribersCount((THook*)hEventConnected)) { - NETLIBCONNECTIONEVENTINFO ncei = {}; - ncei.connected = 1; - ncei.szSettingsModule = nlu->user.szSettingsModule; - int size = sizeof(SOCKADDR_IN); - getsockname(nlc->s, (SOCKADDR *)&ncei.local, &size); - if (nlu->settings.useProxy) { - size = sizeof(SOCKADDR_IN); - getpeername(nlc->s, (SOCKADDR *)&ncei.proxy, &size); - ncei.remote.sin_family = AF_INET; - ncei.remote.sin_port = htons(nlc->url.port); - ncei.remote.sin_addr.S_un.S_addr = DnsLookup(nlu, nlc->url.szHost); - } - else { - size = sizeof(SOCKADDR_IN); - getpeername(nlc->s, (SOCKADDR *)&ncei.remote, &size); - } - NotifyFastHook(hEventConnected, (WPARAM)&ncei, 0); - } - - if (NLOCF_SSL & nlc->url.flags) - return Netlib_StartSsl(nlc, nullptr) != 0; - - return true; -} - -bool NetlibReconnect(NetlibConnection *nlc) -{ - // a connection might be freed already - if (GetNetlibHandleType(nlc) != NLH_CONNECTION) - return false; - - char buf[4]; - bool opened = nlc->s != INVALID_SOCKET; - if (opened) { - switch (WaitUntilReadable(nlc->s, 0, true)) { - case SOCKET_ERROR: - opened = false; - break; - - case 0: - opened = true; - break; - - case 1: - opened = recv(nlc->s, buf, 1, MSG_PEEK) > 0; - break; - } - - if (!opened) - NetlibDoCloseSocket(nlc, true); - } - - if (!opened) { - if (Miranda_IsTerminated()) - return false; - - return NetlibDoConnect(nlc); - } - return true; -} - -MIR_APP_DLL(HNETLIBCONN) Netlib_OpenConnection(NetlibUser *nlu, const char *szHost, int port, int timeout, int flags) -{ - if (szHost == nullptr || port == 0) { - SetLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - - if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING)) - return nullptr; - - Netlib_Logf(nlu, "Connection request to %s:%d (Flags %x)....", szHost, port, flags); - - NetlibConnection *nlc = new NetlibConnection(); - nlc->nlu = nlu; - nlc->timeout = timeout; - nlc->url.szHost = szHost; - nlc->url.port = port; - nlc->url.flags = flags; - nlc->dnsThroughProxy = nlu->settings.dnsThroughProxy != 0; - - if (!NetlibDoConnect(nlc)) { - FreePartiallyInitedConnection(nlc); - return nullptr; - } - - if (iUPnPCleanup == 0) { - mir_cslock lck(csNetlibUser); - iUPnPCleanup = 1; - mir_forkthread(NetlibUPnPCleanup); - } - - return nlc; -} - -NetlibConnection::NetlibConnection() -{ - handleType = NLH_CONNECTION; - s = s2 = INVALID_SOCKET; - hOkToCloseEvent = CreateEvent(nullptr, TRUE, TRUE, nullptr); - NetlibInitializeNestedCS(&ncsSend); - NetlibInitializeNestedCS(&ncsRecv); -} - -NetlibConnection::~NetlibConnection() -{ - handleType = 0; - - if (s != INVALID_SOCKET) - closesocket(s); - - mir_free(szNewUrl); - mir_free(szProxyServer); - - mir_free(nlhpi.szHttpPostUrl); - mir_free(nlhpi.szHttpGetUrl); - - NetlibDeleteNestedCS(&ncsSend); - NetlibDeleteNestedCS(&ncsRecv); - - CloseHandle(hOkToCloseEvent); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+extern mir_cs csNetlibUser;
+extern uint32_t g_LastConnectionTick;
+extern int connectionTimeout;
+static int iUPnPCleanup = 0;
+
+#define RECV_DEFAULT_TIMEOUT 60000
+
+//returns in network byte order
+uint32_t DnsLookup(NetlibUser *nlu, const char *szHost)
+{
+ HOSTENT* host;
+ uint32_t ip = inet_addr(szHost);
+ if (ip != INADDR_NONE)
+ return ip;
+
+ __try {
+ host = gethostbyname(szHost);
+ if (host)
+ return *(u_long*)host->h_addr_list[0];
+
+ Netlib_Logf(nlu, "%s %d: %s() for host %s failed (%u)", __FILE__, __LINE__, "gethostbyname", szHost, WSAGetLastError());
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER) {}
+
+ return 0;
+}
+
+int WaitUntilReadable(SOCKET s, uint32_t dwTimeout, bool check)
+{
+ fd_set readfd;
+ TIMEVAL tv;
+
+ if (s == INVALID_SOCKET) return SOCKET_ERROR;
+
+ tv.tv_sec = dwTimeout / 1000;
+ tv.tv_usec = (dwTimeout % 1000) * 1000;
+
+ FD_ZERO(&readfd);
+ FD_SET(s, &readfd);
+
+ int result = select(0, &readfd, nullptr, nullptr, &tv);
+ if (result == 0 && !check) SetLastError(ERROR_TIMEOUT);
+ return result;
+}
+
+int WaitUntilWritable(SOCKET s, uint32_t dwTimeout)
+{
+ fd_set writefd;
+ TIMEVAL tv;
+
+ tv.tv_sec = dwTimeout / 1000;
+ tv.tv_usec = (dwTimeout % 1000) * 1000;
+
+ FD_ZERO(&writefd);
+ FD_SET(s, &writefd);
+
+ switch (select(0, nullptr, &writefd, nullptr, &tv)) {
+ case 0:
+ SetLastError(ERROR_TIMEOUT);
+ case SOCKET_ERROR:
+ return 0;
+ }
+ return 1;
+}
+
+bool RecvUntilTimeout(NetlibConnection *nlc, char *buf, int len, int flags, uint32_t dwTimeout)
+{
+ int nReceived = 0;
+ uint32_t dwTimeNow, dwCompleteTime = GetTickCount() + dwTimeout;
+
+ while ((dwTimeNow = GetTickCount()) < dwCompleteTime) {
+ if (WaitUntilReadable(nlc->s, dwCompleteTime - dwTimeNow) <= 0) return false;
+ nReceived = Netlib_Recv(nlc, buf, len, flags);
+ if (nReceived <= 0) return false;
+
+ buf += nReceived;
+ len -= nReceived;
+ if (len <= 0) return true;
+ }
+ SetLastError(ERROR_TIMEOUT);
+ return false;
+}
+
+static int NetlibInitSocks4Connection(NetlibConnection *nlc)
+{
+ // http://www.socks.nec.com/protocol/socks4.protocol and http://www.socks.nec.com/protocol/socks4a.protocol
+ NetlibUrl &url = nlc->url;
+ if (url.szHost.IsEmpty())
+ return 0;
+
+ NetlibUser *nlu = nlc->nlu;
+ size_t nHostLen = mir_strlen(url.szHost) + 1;
+ size_t nUserLen = nlu->settings.szProxyAuthUser ? mir_strlen(nlu->settings.szProxyAuthUser) + 1 : 1;
+ size_t len = 8 + nUserLen;
+
+ char* pInit = (char*)alloca(len + nHostLen);
+ pInit[0] = 4; // SOCKS4
+ pInit[1] = 1; //connect
+ *(PWORD)&pInit[2] = htons(url.port);
+
+ if (nUserLen <= 1) pInit[8] = 0;
+ else memcpy(&pInit[8], nlu->settings.szProxyAuthUser, nUserLen);
+
+ //if cannot resolve host, try resolving through proxy (requires SOCKS4a)
+ uint32_t ip = DnsLookup(nlu, url.szHost);
+ *(PDWORD)&pInit[4] = ip ? ip : 0x01000000;
+ if (!ip) {
+ memcpy(&pInit[len], url.szHost, nHostLen);
+ len += nHostLen;
+ }
+
+ if (Netlib_Send(nlc, pInit, (int)len, MSG_DUMPPROXY) == SOCKET_ERROR) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Send", GetLastError());
+ return 0;
+ }
+
+ char reply[8];
+ if (!RecvUntilTimeout(nlc, reply, sizeof(reply), MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError());
+ return 0;
+ }
+
+ switch ((uint8_t)reply[1]) {
+ case 90: return 1;
+ case 91: SetLastError(ERROR_ACCESS_DENIED); break;
+ case 92: SetLastError(ERROR_CONNECTION_UNAVAIL); break;
+ case 93: SetLastError(ERROR_INVALID_ACCESS); break;
+ default: SetLastError(ERROR_INVALID_DATA); break;
+ }
+ Netlib_Logf(nlu, "%s %d: Proxy connection failed (%x %u)", __FILE__, __LINE__, (uint8_t)reply[1], GetLastError());
+ return 0;
+}
+
+static int NetlibInitSocks5Connection(NetlibConnection *nlc)
+{
+ //rfc1928
+ uint8_t buf[258];
+ NetlibUser *nlu = nlc->nlu;
+
+ buf[0] = 5; //yep, socks5
+ buf[1] = 1; //one auth method
+ buf[2] = nlu->settings.useProxyAuth ? 2 : 0;
+ if (Netlib_Send(nlc, (char*)buf, 3, MSG_DUMPPROXY) == SOCKET_ERROR) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Send", GetLastError());
+ return 0;
+ }
+
+ //confirmation of auth method
+ if (!RecvUntilTimeout(nlc, (char*)buf, 2, MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError());
+ return 0;
+ }
+ if ((buf[1] != 0 && buf[1] != 2)) {
+ SetLastError(ERROR_INVALID_ID_AUTHORITY);
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Recv", GetLastError());
+ return 0;
+ }
+
+ if (buf[1] == 2) { //rfc1929
+ size_t nUserLen = mir_strlen(nlu->settings.szProxyAuthUser);
+ size_t nPassLen = mir_strlen(nlu->settings.szProxyAuthPassword);
+ uint8_t *pAuthBuf = (uint8_t*)mir_alloc(3 + nUserLen + nPassLen);
+ pAuthBuf[0] = 1; //auth version
+ pAuthBuf[1] = (uint8_t)nUserLen;
+ memcpy(pAuthBuf + 2, nlu->settings.szProxyAuthUser, nUserLen);
+ pAuthBuf[2 + nUserLen] = (uint8_t)nPassLen;
+ memcpy(pAuthBuf + 3 + nUserLen, nlu->settings.szProxyAuthPassword, nPassLen);
+ if (Netlib_Send(nlc, (char*)pAuthBuf, int(3 + nUserLen + nPassLen), MSG_DUMPPROXY) == SOCKET_ERROR) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Send", GetLastError());
+ mir_free(pAuthBuf);
+ return 0;
+ }
+ mir_free(pAuthBuf);
+
+ if (!RecvUntilTimeout(nlc, (char*)buf, 2, MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError());
+ return 0;
+ }
+ if (buf[1]) {
+ SetLastError(ERROR_ACCESS_DENIED);
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError());
+ return 0;
+ }
+ }
+
+ size_t nHostLen;
+ uint32_t hostIP;
+
+ NetlibUrl &url = nlc->url;
+ if (nlc->dnsThroughProxy) {
+ hostIP = inet_addr(url.szHost);
+ nHostLen = (hostIP == INADDR_NONE) ? mir_strlen(url.szHost) + 1 : 4;
+ }
+ else {
+ hostIP = DnsLookup(nlu, url.szHost);
+ if (hostIP == 0)
+ return 0;
+ nHostLen = 4;
+ }
+ uint8_t *pInit = (uint8_t*)mir_alloc(6 + nHostLen);
+ pInit[0] = 5; //SOCKS5
+ pInit[1] = url.flags & NLOCF_UDP ? 3 : 1; //connect or UDP
+ pInit[2] = 0; //reserved
+ if (hostIP == INADDR_NONE) { //DNS lookup through proxy
+ pInit[3] = 3;
+ pInit[4] = uint8_t(nHostLen - 1);
+ memcpy(pInit + 5, url.szHost, nHostLen - 1);
+ }
+ else {
+ pInit[3] = 1;
+ *(PDWORD)(pInit + 4) = hostIP;
+ }
+ *(PWORD)(pInit + 4 + nHostLen) = htons(url.port);
+ if (Netlib_Send(nlc, (char*)pInit, int(6 + nHostLen), MSG_DUMPPROXY) == SOCKET_ERROR) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "Netlib_Send", GetLastError());
+ mir_free(pInit);
+ return 0;
+ }
+ mir_free(pInit);
+
+ if (!RecvUntilTimeout(nlc, (char*)buf, 5, MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError());
+ return 0;
+ }
+
+ if (buf[0] != 5 || buf[1]) {
+ const char* err = "Unknown response";
+ if (buf[0] != 5)
+ SetLastError(ERROR_BAD_FORMAT);
+ else {
+ switch (buf[1]) {
+ case 1: SetLastError(ERROR_GEN_FAILURE); err = "General failure"; break;
+ case 2: SetLastError(ERROR_ACCESS_DENIED); err = "Connection not allowed by ruleset"; break;
+ case 3: SetLastError(WSAENETUNREACH); err = "Network unreachable"; break;
+ case 4: SetLastError(WSAEHOSTUNREACH); err = "Host unreachable"; break;
+ case 5: SetLastError(WSAECONNREFUSED); err = "Connection refused by destination host"; break;
+ case 6: SetLastError(WSAETIMEDOUT); err = "TTL expired"; break;
+ case 7: SetLastError(ERROR_CALL_NOT_IMPLEMENTED); err = "Command not supported / protocol error"; break;
+ case 8: SetLastError(ERROR_INVALID_ADDRESS); err = "Address type not supported"; break;
+ default: SetLastError(ERROR_INVALID_DATA); break;
+ }
+ }
+ Netlib_Logf(nlu, "%s %d: Proxy conection failed. %s.", __FILE__, __LINE__, err);
+ return 0;
+ }
+
+ int nRecvSize = 0;
+ switch (buf[3]) {
+ case 1:// ipv4 addr
+ nRecvSize = 5;
+ break;
+ case 3:// dns name addr
+ nRecvSize = buf[4] + 2;
+ break;
+ case 4:// ipv6 addr
+ nRecvSize = 17;
+ break;
+ default:
+ Netlib_Logf(nlu, "%s %d: %s() unknown address type (%u)", __FILE__, __LINE__, "NetlibInitSocks5Connection", (int)buf[3]);
+ return 0;
+ }
+ if (!RecvUntilTimeout(nlc, (char*)buf, nRecvSize, MSG_DUMPPROXY, RECV_DEFAULT_TIMEOUT)) {
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "RecvUntilTimeout", GetLastError());
+ return 0;
+ }
+
+ //connected
+ return 1;
+}
+
+static bool NetlibInitHttpsConnection(NetlibConnection *nlc)
+{
+ // rfc2817
+ NetlibUrl &url = nlc->url;
+ CMStringA szUrl;
+ if (nlc->dnsThroughProxy)
+ szUrl.Format("%s:%u", url.szHost.c_str(), url.port);
+ else {
+ uint32_t ip = DnsLookup(nlc->nlu, url.szHost);
+ if (ip == 0) return false;
+ szUrl.Format("%s:%u", inet_ntoa(*(PIN_ADDR)&ip), url.port);
+ }
+
+ NETLIBHTTPREQUEST nlhrSend = { 0 };
+ nlhrSend.cbSize = sizeof(nlhrSend);
+ nlhrSend.requestType = REQUEST_CONNECT;
+ nlhrSend.flags = NLHRF_DUMPPROXY | NLHRF_HTTP11 | NLHRF_NOPROXY | NLHRF_REDIRECT;
+ nlhrSend.szUrl = szUrl.GetBuffer();
+
+ if (Netlib_SendHttpRequest(nlc, &nlhrSend) == SOCKET_ERROR)
+ return false;
+
+ NETLIBHTTPREQUEST *nlhrReply = NetlibHttpRecv(nlc, MSG_DUMPPROXY | MSG_RAW, MSG_DUMPPROXY | MSG_RAW, true);
+ if (nlhrReply == nullptr)
+ return false;
+
+ if (nlhrReply->resultCode < 200 || nlhrReply->resultCode >= 300) {
+ if (nlhrReply->resultCode == 403 && nlc->dnsThroughProxy) {
+ Netlib_FreeHttpRequest(nlhrReply);
+ nlc->dnsThroughProxy = 0;
+ return NetlibInitHttpsConnection(nlc);
+ }
+
+ NetlibHttpSetLastErrorUsingHttpResult(nlhrReply->resultCode);
+ Netlib_Logf(nlc->nlu, "%s %d: %s request failed (%u %s)", __FILE__, __LINE__,
+ nlc->nlu->settings.proxyType == PROXYTYPE_HTTP ? "HTTP" : "HTTPS", nlhrReply->resultCode, nlhrReply->szResultDescr);
+ Netlib_FreeHttpRequest(nlhrReply);
+ return 0;
+ }
+ Netlib_FreeHttpRequest(nlhrReply);
+ return true; // connected
+}
+
+static void FreePartiallyInitedConnection(NetlibConnection *nlc)
+{
+ uint32_t dwOriginalLastError = GetLastError();
+
+ if (GetNetlibHandleType(nlc) == NLH_CONNECTION)
+ delete nlc;
+
+ SetLastError(dwOriginalLastError);
+}
+
+static bool my_connectIP(NetlibConnection *nlc)
+{
+ NetlibUser *nlu = nlc->nlu;
+ int rc = SOCKET_ERROR, retrycnt = 0;
+ u_long notblocking = 1;
+ uint32_t lasterr = 0;
+ static const TIMEVAL tv = { 1, 0 };
+
+ // if timeout is zero then its an old style connection or new with a 0 timeout, select() will error quicker anyway
+ int timeout = (nlc->timeout <= 0) ? 30 : nlc->timeout;
+
+ // this is for XP SP2 where there is a default connection attempt limit of 10/second
+ if (connectionTimeout) {
+ WaitForSingleObject(hConnectionOpenMutex, 10000);
+ int waitdiff = GetTickCount() - g_LastConnectionTick;
+ if (waitdiff < connectionTimeout) SleepEx(connectionTimeout, TRUE);
+ g_LastConnectionTick = GetTickCount();
+ ReleaseMutex(hConnectionOpenMutex);
+
+ // might have died in between the wait
+ if (Miranda_IsTerminated())
+ return false;
+ }
+
+ char szPort[6];
+ addrinfo *air = nullptr, *ai, hints = { 0 };
+
+ hints.ai_family = AF_UNSPEC;
+
+ NetlibUrl &url = nlc->url;
+ if (url.flags & NLOCF_UDP) {
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ }
+ else {
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ }
+
+ if (nlc->proxyType) {
+ if (!nlc->szProxyServer)
+ return false;
+
+ Netlib_Logf(nlu, "(%p) Connecting to proxy %s:%d for %s:%d ....", nlc, nlc->szProxyServer, nlc->wProxyPort, url.szHost.c_str(), url.port);
+
+ _itoa(nlc->wProxyPort, szPort, 10);
+ if (GetAddrInfoA(nlc->szProxyServer, szPort, &hints, &air)) {
+ Netlib_Logf(nlu, "%s %d: %s() for host %s failed (%u)", __FILE__, __LINE__, "getaddrinfo", nlc->szProxyServer, WSAGetLastError());
+ return false;
+ }
+ }
+ else {
+ if (url.szHost.IsEmpty())
+ return false;
+
+ Netlib_Logf(nlu, "(%p) Connecting to server %s:%d....", nlc, url.szHost.c_str(), url.port);
+
+ _itoa(url.port, szPort, 10);
+
+ if (GetAddrInfoA(url.szHost, szPort, &hints, &air)) {
+ Netlib_Logf(nlu, "%s %d: %s() for host %s failed (%u)", __FILE__, __LINE__, "getaddrinfo", url.szHost.c_str(), WSAGetLastError());
+ return false;
+ }
+ }
+
+ for (ai = air; ai && !Miranda_IsTerminated(); ai = ai->ai_next) {
+ Netlib_Logf(nlu, "(%p) Connecting to ip %s ....", nlc, ptrA(Netlib_AddressToString((sockaddr_in*)ai->ai_addr)).get());
+retry:
+ nlc->s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (nlc->s == INVALID_SOCKET) {
+ FreeAddrInfoA(air);
+ return false;
+ }
+
+ // return the socket to non blocking
+ if (ioctlsocket(nlc->s, FIONBIO, ¬blocking) != 0) {
+ FreeAddrInfoA(air);
+ return false;
+ }
+
+ if (nlu->settings.specifyOutgoingPorts && nlu->settings.szOutgoingPorts && nlu->settings.szOutgoingPorts[0]) {
+ SOCKET s = ai->ai_family == AF_INET ? nlc->s : INVALID_SOCKET;
+ SOCKET s6 = ai->ai_family == AF_INET6 ? nlc->s : INVALID_SOCKET;
+ if (!BindSocketToPort(nlu->settings.szOutgoingPorts, s, s6, &nlu->inportnum))
+ Netlib_Logf(nlu, "Netlib connect: Not enough ports for outgoing connections specified");
+ }
+
+ // try a connect
+ if (connect(nlc->s, ai->ai_addr, (int)ai->ai_addrlen) == 0) {
+ rc = 0;
+ break;
+ }
+
+ // didn't work, was it cos of nonblocking?
+ if (WSAGetLastError() != WSAEWOULDBLOCK) {
+ rc = SOCKET_ERROR;
+ closesocket(nlc->s);
+ nlc->s = INVALID_SOCKET;
+ continue;
+ }
+
+ while (true) { // timeout loop
+ fd_set r, w, e;
+ FD_ZERO(&r); FD_ZERO(&w); FD_ZERO(&e);
+ FD_SET(nlc->s, &r);
+ FD_SET(nlc->s, &w);
+ FD_SET(nlc->s, &e);
+ if ((rc = select(0, &r, &w, &e, &tv)) == SOCKET_ERROR)
+ break;
+
+ if (rc > 0) {
+ if (FD_ISSET(nlc->s, &w)) {
+ // connection was successful
+ rc = 0;
+ lasterr = 0;
+ }
+ if (FD_ISSET(nlc->s, &r)) {
+ // connection was closed
+ rc = SOCKET_ERROR;
+ lasterr = WSAECONNRESET;
+ }
+ if (FD_ISSET(nlc->s, &e)) {
+ // connection failed.
+ int len = sizeof(lasterr);
+ rc = SOCKET_ERROR;
+ getsockopt(nlc->s, SOL_SOCKET, SO_ERROR, (char*)&lasterr, &len);
+ if (lasterr == WSAEADDRINUSE && ++retrycnt <= 2) {
+ closesocket(nlc->s);
+ nlc->s = INVALID_SOCKET;
+ goto retry;
+ }
+ }
+ break;
+ }
+ else if (Miranda_IsTerminated()) {
+ rc = SOCKET_ERROR;
+ lasterr = ERROR_TIMEOUT;
+ break;
+ }
+
+ if (--timeout == 0) {
+ rc = SOCKET_ERROR;
+ lasterr = ERROR_TIMEOUT;
+ break;
+ }
+ }
+
+ if (rc == 0) break;
+
+ closesocket(nlc->s);
+ nlc->s = INVALID_SOCKET;
+ }
+
+ FreeAddrInfoA(air);
+
+ notblocking = 0;
+ if (nlc->s != INVALID_SOCKET) ioctlsocket(nlc->s, FIONBIO, ¬blocking);
+ if (rc && lasterr) SetLastError(lasterr);
+ return rc == 0;
+}
+
+static int NetlibHttpFallbackToDirect(NetlibConnection *nlc)
+{
+ NetlibDoCloseSocket(nlc, true);
+
+ Netlib_Logf(nlc->nlu, "Fallback to direct connection");
+
+ nlc->proxyAuthNeeded = false;
+ nlc->proxyType = 0;
+ replaceStr(nlc->szProxyServer, nullptr);
+ if (!my_connectIP(nlc)) {
+ Netlib_Logf(nlc->nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError());
+ return false;
+ }
+ return true;
+}
+
+bool NetlibDoConnect(NetlibConnection *nlc)
+{
+ NetlibUser *nlu = nlc->nlu;
+
+ replaceStr(nlc->szProxyServer, nullptr);
+
+ bool usingProxy = false, forceHttps = false;
+ if (nlu->settings.useProxy) {
+ if (nlu->settings.proxyType == PROXYTYPE_IE)
+ usingProxy = NetlibGetIeProxyConn(nlc, false);
+ else {
+ if (nlu->settings.szProxyServer && nlu->settings.szProxyServer[0]) {
+ nlc->szProxyServer = mir_strdup(nlu->settings.szProxyServer);
+ nlc->wProxyPort = nlu->settings.wProxyPort;
+ nlc->proxyType = nlu->settings.proxyType;
+ usingProxy = true;
+ }
+ }
+ }
+
+ while (!my_connectIP(nlc)) {
+ // if connection failed, the state of nlc might be unpredictable
+ if (GetNetlibHandleType(nlc) == NLH_CONNECTION) {
+ // Fallback to direct only when using HTTP proxy, as this is what used by companies
+ // If other type of proxy used it's an indication of security nutcase, leave him alone
+ if (usingProxy && (nlc->proxyType == PROXYTYPE_HTTPS || nlc->proxyType == PROXYTYPE_HTTP)) {
+ usingProxy = false;
+ nlc->proxyType = 0;
+ Netlib_Logf(nlu, "Fallback to direct connection");
+ continue;
+ }
+ if (nlu->settings.useProxy && !usingProxy && nlu->settings.proxyType == PROXYTYPE_IE && !forceHttps) {
+ forceHttps = true;
+ usingProxy = NetlibGetIeProxyConn(nlc, true);
+ if (usingProxy)
+ continue;
+ }
+ }
+ Netlib_Logf(nlu, "%s %d: %s() failed (%u)", __FILE__, __LINE__, "connect", WSAGetLastError());
+ return false;
+ }
+
+ if (usingProxy && !((nlc->url.flags & (NLOCF_HTTP | NLOCF_SSL)) == NLOCF_HTTP && (nlc->proxyType == PROXYTYPE_HTTP || nlc->proxyType == PROXYTYPE_HTTPS))) {
+ if (!WaitUntilWritable(nlc->s, 30000))
+ return false;
+
+ switch (nlc->proxyType) {
+ case PROXYTYPE_SOCKS4:
+ if (!NetlibInitSocks4Connection(nlc))
+ return false;
+ break;
+
+ case PROXYTYPE_SOCKS5:
+ if (!NetlibInitSocks5Connection(nlc))
+ return false;
+ break;
+
+ case PROXYTYPE_HTTPS:
+ case PROXYTYPE_HTTP:
+ nlc->proxyAuthNeeded = true;
+ if (!NetlibInitHttpsConnection(nlc)) {
+ usingProxy = false;
+ if (!NetlibHttpFallbackToDirect(nlc))
+ return false;
+ }
+ break;
+
+ default:
+ SetLastError(ERROR_INVALID_PARAMETER);
+ FreePartiallyInitedConnection(nlc);
+ return false;
+ }
+ }
+
+ Netlib_Logf(nlu, "(%d) Connected to %s:%d", nlc->s, nlc->url.szHost.c_str(), nlc->url.port);
+
+ if (GetSubscribersCount((THook*)hEventConnected)) {
+ NETLIBCONNECTIONEVENTINFO ncei = {};
+ ncei.connected = 1;
+ ncei.szSettingsModule = nlu->user.szSettingsModule;
+ int size = sizeof(SOCKADDR_IN);
+ getsockname(nlc->s, (SOCKADDR *)&ncei.local, &size);
+ if (nlu->settings.useProxy) {
+ size = sizeof(SOCKADDR_IN);
+ getpeername(nlc->s, (SOCKADDR *)&ncei.proxy, &size);
+ ncei.remote.sin_family = AF_INET;
+ ncei.remote.sin_port = htons(nlc->url.port);
+ ncei.remote.sin_addr.S_un.S_addr = DnsLookup(nlu, nlc->url.szHost);
+ }
+ else {
+ size = sizeof(SOCKADDR_IN);
+ getpeername(nlc->s, (SOCKADDR *)&ncei.remote, &size);
+ }
+ NotifyFastHook(hEventConnected, (WPARAM)&ncei, 0);
+ }
+
+ if (NLOCF_SSL & nlc->url.flags)
+ return Netlib_StartSsl(nlc, nullptr) != 0;
+
+ return true;
+}
+
+bool NetlibReconnect(NetlibConnection *nlc)
+{
+ // a connection might be freed already
+ if (GetNetlibHandleType(nlc) != NLH_CONNECTION)
+ return false;
+
+ char buf[4];
+ bool opened = nlc->s != INVALID_SOCKET;
+ if (opened) {
+ switch (WaitUntilReadable(nlc->s, 0, true)) {
+ case SOCKET_ERROR:
+ opened = false;
+ break;
+
+ case 0:
+ opened = true;
+ break;
+
+ case 1:
+ opened = recv(nlc->s, buf, 1, MSG_PEEK) > 0;
+ break;
+ }
+
+ if (!opened)
+ NetlibDoCloseSocket(nlc, true);
+ }
+
+ if (!opened) {
+ if (Miranda_IsTerminated())
+ return false;
+
+ return NetlibDoConnect(nlc);
+ }
+ return true;
+}
+
+MIR_APP_DLL(HNETLIBCONN) Netlib_OpenConnection(NetlibUser *nlu, const char *szHost, int port, int timeout, int flags)
+{
+ if (szHost == nullptr || port == 0) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return nullptr;
+ }
+
+ if (GetNetlibHandleType(nlu) != NLH_USER || !(nlu->user.flags & NUF_OUTGOING))
+ return nullptr;
+
+ Netlib_Logf(nlu, "Connection request to %s:%d (Flags %x)....", szHost, port, flags);
+
+ NetlibConnection *nlc = new NetlibConnection();
+ nlc->nlu = nlu;
+ nlc->timeout = timeout;
+ nlc->url.szHost = szHost;
+ nlc->url.port = port;
+ nlc->url.flags = flags;
+ nlc->dnsThroughProxy = nlu->settings.dnsThroughProxy != 0;
+
+ if (!NetlibDoConnect(nlc)) {
+ FreePartiallyInitedConnection(nlc);
+ return nullptr;
+ }
+
+ if (iUPnPCleanup == 0) {
+ mir_cslock lck(csNetlibUser);
+ iUPnPCleanup = 1;
+ mir_forkthread(NetlibUPnPCleanup);
+ }
+
+ return nlc;
+}
+
+NetlibConnection::NetlibConnection()
+{
+ handleType = NLH_CONNECTION;
+ s = s2 = INVALID_SOCKET;
+ hOkToCloseEvent = CreateEvent(nullptr, TRUE, TRUE, nullptr);
+ NetlibInitializeNestedCS(&ncsSend);
+ NetlibInitializeNestedCS(&ncsRecv);
+}
+
+NetlibConnection::~NetlibConnection()
+{
+ handleType = 0;
+
+ if (s != INVALID_SOCKET)
+ closesocket(s);
+
+ mir_free(szNewUrl);
+ mir_free(szProxyServer);
+
+ mir_free(nlhpi.szHttpPostUrl);
+ mir_free(nlhpi.szHttpGetUrl);
+
+ NetlibDeleteNestedCS(&ncsSend);
+ NetlibDeleteNestedCS(&ncsRecv);
+
+ CloseHandle(hOkToCloseEvent);
+}
diff --git a/src/mir_app/src/netlib_opts.cpp b/src/mir_app/src/netlib_opts.cpp index a7e0419287..be75114fac 100644 --- a/src/mir_app/src/netlib_opts.cpp +++ b/src/mir_app/src/netlib_opts.cpp @@ -1,518 +1,518 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -struct NetlibTempSettings -{ - uint32_t flags; - char *szSettingsModule; - NETLIBUSERSETTINGS settings; -}; - -static LIST <NetlibTempSettings> tempSettings(5); - -static const UINT outgoingConnectionsControls[] = -{ - IDC_STATIC12, - IDC_USEPROXY, - IDC_STATIC21, IDC_PROXYTYPE, - IDC_STATIC22, IDC_PROXYHOST, IDC_STATIC23, IDC_PROXYPORT, IDC_STOFTENPORT, - IDC_PROXYAUTH, - IDC_STATIC31, IDC_PROXYUSER, IDC_STATIC32, IDC_PROXYPASS, - IDC_PROXYDNS, - IDC_SPECIFYPORTSO, - IDC_PORTSRANGEO, - IDC_STATIC54, - IDC_VALIDATESSL}; -static const UINT useProxyControls[] = { - IDC_STATIC21, IDC_PROXYTYPE, - IDC_STATIC22, IDC_PROXYHOST, IDC_STATIC23, IDC_PROXYPORT, IDC_STOFTENPORT, - IDC_PROXYAUTH, - IDC_STATIC31, IDC_PROXYUSER, IDC_STATIC32, IDC_PROXYPASS, - IDC_PROXYDNS}; -static const UINT specifyOPortsControls[] = { - IDC_PORTSRANGEO, - IDC_STATIC54 -}; -static const UINT incomingConnectionsControls[] = { - IDC_STATIC43, - IDC_SPECIFYPORTS, - IDC_PORTSRANGE, - IDC_STATIC52, - IDC_ENABLEUPNP}; -static const UINT specifyPortsControls[] = { - IDC_PORTSRANGE, - IDC_STATIC52}; - -static const wchar_t* szProxyTypes[] = {LPGENW("<mixed>"), L"SOCKS4", L"SOCKS5", L"HTTP", L"HTTPS", L"Internet Explorer"}; -static const uint16_t oftenProxyPorts[] = {1080, 1080, 1080, 8080, 8080, 8080}; - -#define M_REFRESHALL (WM_USER+100) -#define M_REFRESHENABLING (WM_USER+101) - -static void ShowMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state) -{ - for (int i = 0; i < cControls; i++) - ShowWindow(GetDlgItem(hwndDlg, controls[i]), state); -} - -static void EnableMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state) -{ - for (int i = 0; i < cControls; i++) - EnableWindow(GetDlgItem(hwndDlg, controls[i]), state); -} - -static void AddProxyTypeItem(HWND hwndDlg, int type, int selectType) -{ - int i = SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_ADDSTRING, 0, (LPARAM)(type == 0 ? TranslateW(szProxyTypes[type]) : szProxyTypes[type])); - SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_SETITEMDATA, i, type); - if (type == selectType) - SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_SETCURSEL, i, 0); -} - -static void CopySettingsStruct(NETLIBUSERSETTINGS *dest, const NETLIBUSERSETTINGS *source) -{ - *dest = *source; - if (dest->szIncomingPorts) dest->szIncomingPorts = mir_strdup(dest->szIncomingPorts); - if (dest->szOutgoingPorts) dest->szOutgoingPorts = mir_strdup(dest->szOutgoingPorts); - if (dest->szProxyAuthPassword) dest->szProxyAuthPassword = mir_strdup(dest->szProxyAuthPassword); - if (dest->szProxyAuthUser) dest->szProxyAuthUser = mir_strdup(dest->szProxyAuthUser); - if (dest->szProxyServer) dest->szProxyServer = mir_strdup(dest->szProxyServer); -} - -static void CombineSettingsStrings(char **dest, char **source) -{ - if (*dest != nullptr && (*source == nullptr || mir_strcmpi(*dest, *source))) { mir_free(*dest); *dest = nullptr; } -} - -static void CombineSettingsStructs(NETLIBUSERSETTINGS *dest, uint32_t *destFlags, NETLIBUSERSETTINGS *source, uint32_t sourceFlags) -{ - if (sourceFlags & NUF_OUTGOING) { - if (*destFlags & NUF_OUTGOING) { - if (dest->validateSSL != source->validateSSL) dest->validateSSL = 2; - if (dest->useProxy != source->useProxy) dest->useProxy = 2; - if (dest->proxyType != source->proxyType) dest->proxyType = 0; - CombineSettingsStrings(&dest->szProxyServer, &source->szProxyServer); - if (dest->wProxyPort != source->wProxyPort) dest->wProxyPort = 0; - if (dest->useProxyAuth != source->useProxyAuth) dest->useProxyAuth = 2; - CombineSettingsStrings(&dest->szProxyAuthUser, &source->szProxyAuthUser); - CombineSettingsStrings(&dest->szProxyAuthPassword, &source->szProxyAuthPassword); - if (dest->dnsThroughProxy != source->dnsThroughProxy) dest->dnsThroughProxy = 2; - if (dest->specifyOutgoingPorts != source->specifyOutgoingPorts) dest->specifyOutgoingPorts = 2; - CombineSettingsStrings(&dest->szOutgoingPorts, &source->szOutgoingPorts); - } - else { - dest->validateSSL = source->validateSSL; - dest->useProxy = source->useProxy; - dest->proxyType = source->proxyType; - dest->szProxyServer = source->szProxyServer; - if (dest->szProxyServer) dest->szProxyServer = mir_strdup(dest->szProxyServer); - dest->wProxyPort = source->wProxyPort; - dest->useProxyAuth = source->useProxyAuth; - dest->szProxyAuthUser = source->szProxyAuthUser; - if (dest->szProxyAuthUser) dest->szProxyAuthUser = mir_strdup(dest->szProxyAuthUser); - dest->szProxyAuthPassword = source->szProxyAuthPassword; - if (dest->szProxyAuthPassword) dest->szProxyAuthPassword = mir_strdup(dest->szProxyAuthPassword); - dest->dnsThroughProxy = source->dnsThroughProxy; - dest->specifyOutgoingPorts = source->specifyOutgoingPorts; - dest->szOutgoingPorts = source->szOutgoingPorts; - if (dest->szOutgoingPorts) dest->szOutgoingPorts = mir_strdup(dest->szOutgoingPorts); - } - } - if (sourceFlags & NUF_INCOMING) { - if (*destFlags & NUF_INCOMING) { - if (dest->enableUPnP != source->enableUPnP) dest->enableUPnP = 2; - if (dest->specifyIncomingPorts != source->specifyIncomingPorts) dest->specifyIncomingPorts = 2; - CombineSettingsStrings(&dest->szIncomingPorts, &source->szIncomingPorts); - } - else { - dest->enableUPnP = source->enableUPnP; - dest->specifyIncomingPorts = source->specifyIncomingPorts; - dest->szIncomingPorts = source->szIncomingPorts; - if (dest->szIncomingPorts) dest->szIncomingPorts = mir_strdup(dest->szIncomingPorts); - } - } - if ((*destFlags & NUF_NOHTTPSOPTION) != (sourceFlags & NUF_NOHTTPSOPTION)) - *destFlags = (*destFlags | sourceFlags) & ~NUF_NOHTTPSOPTION; - else *destFlags |= sourceFlags; -} - -static void ChangeSettingIntByCheckbox(HWND hwndDlg, UINT ctrlId, int iUser, int memberOffset) -{ - int newValue = IsDlgButtonChecked(hwndDlg, ctrlId) != BST_CHECKED; - CheckDlgButton(hwndDlg, ctrlId, newValue ? BST_CHECKED : BST_UNCHECKED); - if (iUser == -1) { - for (auto &p : tempSettings) - if (!(p->flags & NUF_NOOPTIONS)) - *(int*)(((uint8_t*)&p->settings) + memberOffset) = newValue; - } - else *(int*)(((uint8_t*)&tempSettings[iUser]->settings) + memberOffset) = newValue; - SendMessage(hwndDlg, M_REFRESHENABLING, 0, 0); -} - -static void ChangeSettingStringByEdit(HWND hwndDlg, UINT ctrlId, int iUser, int memberOffset) -{ - int newValueLen = GetWindowTextLength(GetDlgItem(hwndDlg, ctrlId)); - char *szNewValue = (char*)mir_alloc(newValueLen+1); - GetDlgItemTextA(hwndDlg, ctrlId, szNewValue, newValueLen+1); - if (iUser == -1) { - for (auto &p : tempSettings) { - if (!(p->flags & NUF_NOOPTIONS)) { - char **ppszNew = (char**)(((uint8_t*)&p->settings) + memberOffset); - mir_free(*ppszNew); - *ppszNew = mir_strdup(szNewValue); - } - } - mir_free(szNewValue); - } - else { - char **ppszNew = (char**)(((uint8_t*)&tempSettings[iUser]->settings) + memberOffset); - mir_free(*ppszNew); - *ppszNew = szNewValue; - } -} - -static void WriteSettingsStructToDb(const char *szSettingsModule, NETLIBUSERSETTINGS *settings, uint32_t flags) -{ - if (flags & NUF_OUTGOING) { - db_set_b(0, szSettingsModule, "NLValidateSSL", (uint8_t)settings->validateSSL); - db_set_b(0, szSettingsModule, "NLUseProxy", (uint8_t)settings->useProxy); - db_set_b(0, szSettingsModule, "NLProxyType", (uint8_t)settings->proxyType); - db_set_s(0, szSettingsModule, "NLProxyServer", settings->szProxyServer ? settings->szProxyServer : ""); - db_set_w(0, szSettingsModule, "NLProxyPort", (uint16_t)settings->wProxyPort); - db_set_b(0, szSettingsModule, "NLUseProxyAuth", (uint8_t)settings->useProxyAuth); - db_set_s(0, szSettingsModule, "NLProxyAuthUser", settings->szProxyAuthUser ? settings->szProxyAuthUser : ""); - db_set_s(0, szSettingsModule, "NLProxyAuthPassword", settings->szProxyAuthPassword ? settings->szProxyAuthPassword : ""); - db_set_b(0, szSettingsModule, "NLDnsThroughProxy", (uint8_t)settings->dnsThroughProxy); - db_set_b(0, szSettingsModule, "NLSpecifyOutgoingPorts", (uint8_t)settings->specifyOutgoingPorts); - db_set_s(0, szSettingsModule, "NLOutgoingPorts", settings->szOutgoingPorts ? settings->szOutgoingPorts : ""); - } - if (flags & NUF_INCOMING) { - db_set_b(0, szSettingsModule, "NLEnableUPnP", (uint8_t)settings->enableUPnP); - db_set_b(0, szSettingsModule, "NLSpecifyIncomingPorts", (uint8_t)settings->specifyIncomingPorts); - db_set_s(0, szSettingsModule, "NLIncomingPorts", settings->szIncomingPorts ? settings->szIncomingPorts : ""); - } -} - -void NetlibSaveUserSettingsStruct(const char *szSettingsModule, const NETLIBUSERSETTINGS *settings) -{ - mir_cslock lck(csNetlibUser); - - NetlibUser tUser; - tUser.user.szSettingsModule = (char*)szSettingsModule; - NetlibUser *thisUser = netlibUser.find(&tUser); - if (thisUser == nullptr) - return; - - NetlibFreeUserSettingsStruct(&thisUser->settings); - CopySettingsStruct(&thisUser->settings, settings); - WriteSettingsStructToDb(thisUser->user.szSettingsModule, &thisUser->settings, thisUser->user.flags); - - NETLIBUSERSETTINGS combinedSettings = { 0 }; - combinedSettings.cbSize = sizeof(combinedSettings); - - uint32_t flags = 0; - for (auto &p : netlibUser) { - if (p->user.flags & NUF_NOOPTIONS) - continue; - CombineSettingsStructs(&combinedSettings, &flags, &p->settings, p->user.flags); - } - if (combinedSettings.validateSSL == 2) combinedSettings.validateSSL = 0; - if (combinedSettings.useProxy == 2) combinedSettings.useProxy = 0; - if (combinedSettings.proxyType == 0) combinedSettings.proxyType = PROXYTYPE_SOCKS5; - if (combinedSettings.useProxyAuth == 2) combinedSettings.useProxyAuth = 0; - if (combinedSettings.dnsThroughProxy == 2) combinedSettings.dnsThroughProxy = 1; - if (combinedSettings.enableUPnP == 2) combinedSettings.enableUPnP = 1; - if (combinedSettings.specifyIncomingPorts == 2) combinedSettings.specifyIncomingPorts = 0; - WriteSettingsStructToDb("Netlib", &combinedSettings, flags); - NetlibFreeUserSettingsStruct(&combinedSettings); -} - -static INT_PTR CALLBACK DlgProcNetlibOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) -{ - int iUser; - - switch (msg) { - case WM_INITDIALOG: - TranslateDialogDefault(hwndDlg); - { - int iItem = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_ADDSTRING, 0, (LPARAM)TranslateT("<All connections>")); - SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETITEMDATA, iItem, (LPARAM)-1); - SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETCURSEL, iItem, 0); - - mir_cslock lck(csNetlibUser); - for (auto &it : netlibUser) { - NetlibTempSettings *thisSettings = (NetlibTempSettings*)mir_calloc(sizeof(NetlibTempSettings)); - thisSettings->flags = it->user.flags; - thisSettings->szSettingsModule = mir_strdup(it->user.szSettingsModule); - CopySettingsStruct(&thisSettings->settings, &it->settings); - tempSettings.insert(thisSettings); - - if (it->user.flags & NUF_NOOPTIONS) - continue; - iItem = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_ADDSTRING, 0, (LPARAM)it->user.szDescriptiveName.w); - SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETITEMDATA, iItem, netlibUser.indexOf(&it)); - } - } - - SendMessage(hwndDlg, M_REFRESHALL, 0, 0); - return TRUE; - - case M_REFRESHALL: - iUser = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETCURSEL, 0, 0), 0); - { - NETLIBUSERSETTINGS settings = { 0 }; - uint32_t flags = 0; - - if (iUser == -1) { - settings.cbSize = sizeof(settings); - for (auto &p : tempSettings) - if (!(p->flags & NUF_NOOPTIONS)) - CombineSettingsStructs(&settings, &flags, &p->settings, p->flags); - } - else { - NetlibFreeUserSettingsStruct(&settings); - CopySettingsStruct(&settings, &tempSettings[iUser]->settings); - flags = tempSettings[iUser]->flags; - } - ShowMultipleControls(hwndDlg, outgoingConnectionsControls, _countof(outgoingConnectionsControls), flags & NUF_OUTGOING ? SW_SHOW : SW_HIDE); - CheckDlgButton(hwndDlg, IDC_USEPROXY, settings.useProxy); - SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_RESETCONTENT, 0, 0); - if (settings.proxyType == 0) AddProxyTypeItem(hwndDlg, 0, settings.proxyType); - AddProxyTypeItem(hwndDlg, PROXYTYPE_SOCKS4, settings.proxyType); - AddProxyTypeItem(hwndDlg, PROXYTYPE_SOCKS5, settings.proxyType); - if (flags & NUF_HTTPCONNS) AddProxyTypeItem(hwndDlg, PROXYTYPE_HTTP, settings.proxyType); - if (!(flags & NUF_NOHTTPSOPTION)) AddProxyTypeItem(hwndDlg, PROXYTYPE_HTTPS, settings.proxyType); - if ((flags & NUF_HTTPCONNS) || !(flags & NUF_NOHTTPSOPTION)) - AddProxyTypeItem(hwndDlg, PROXYTYPE_IE, settings.proxyType); - SetDlgItemTextA(hwndDlg, IDC_PROXYHOST, settings.szProxyServer ? settings.szProxyServer : ""); - if (settings.wProxyPort) SetDlgItemInt(hwndDlg, IDC_PROXYPORT, settings.wProxyPort, FALSE); - else SetDlgItemTextA(hwndDlg, IDC_PROXYPORT, ""); - CheckDlgButton(hwndDlg, IDC_PROXYAUTH, settings.useProxyAuth); - SetDlgItemTextA(hwndDlg, IDC_PROXYUSER, settings.szProxyAuthUser ? settings.szProxyAuthUser : ""); - SetDlgItemTextA(hwndDlg, IDC_PROXYPASS, settings.szProxyAuthPassword ? settings.szProxyAuthPassword : ""); - CheckDlgButton(hwndDlg, IDC_PROXYDNS, settings.dnsThroughProxy); - CheckDlgButton(hwndDlg, IDC_VALIDATESSL, settings.validateSSL); - - ShowMultipleControls(hwndDlg, incomingConnectionsControls, _countof(incomingConnectionsControls), flags & NUF_INCOMING ? SW_SHOW : SW_HIDE); - CheckDlgButton(hwndDlg, IDC_SPECIFYPORTS, settings.specifyIncomingPorts); - SetDlgItemTextA(hwndDlg, IDC_PORTSRANGE, settings.szIncomingPorts ? settings.szIncomingPorts : ""); - - CheckDlgButton(hwndDlg, IDC_SPECIFYPORTSO, settings.specifyOutgoingPorts); - SetDlgItemTextA(hwndDlg, IDC_PORTSRANGEO, settings.szOutgoingPorts ? settings.szOutgoingPorts : ""); - - CheckDlgButton(hwndDlg, IDC_ENABLEUPNP, settings.enableUPnP); - - NetlibFreeUserSettingsStruct(&settings); - SendMessage(hwndDlg, M_REFRESHENABLING, 0, 0); - } - break; - - case M_REFRESHENABLING: - wchar_t str[80]; - { - int selectedProxyType = SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETCURSEL, 0, 0), 0); - mir_snwprintf(str, TranslateT("(often %d)"), oftenProxyPorts[selectedProxyType]); - SetDlgItemText(hwndDlg, IDC_STOFTENPORT, str); - if (IsDlgButtonChecked(hwndDlg, IDC_USEPROXY) != BST_UNCHECKED) { - int enableAuth = 0, enableUser = 0, enablePass = 0, enableServer = 1; - EnableMultipleControls(hwndDlg, useProxyControls, _countof(useProxyControls), TRUE); - if (selectedProxyType == 0) { - for (auto &p : tempSettings) { - if (!p->settings.useProxy || p->flags & NUF_NOOPTIONS || !(p->flags & NUF_OUTGOING)) - continue; - - if (p->settings.proxyType == PROXYTYPE_SOCKS4) enableUser = 1; - else { - enableAuth = 1; - if (p->settings.useProxyAuth) - enableUser = enablePass = 1; - } - } - } - else { - if (selectedProxyType == PROXYTYPE_SOCKS4) enableUser = 1; - else { - if (selectedProxyType == PROXYTYPE_IE) enableServer = 0; - enableAuth = 1; - if (IsDlgButtonChecked(hwndDlg, IDC_PROXYAUTH) != BST_UNCHECKED) - enableUser = enablePass = 1; - } - } - EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYAUTH), enableAuth); - EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC31), enableUser); - EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYUSER), enableUser); - EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC32), enablePass); - EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYPASS), enablePass); - EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYHOST), enableServer); - EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYPORT), enableServer); - } - else EnableMultipleControls(hwndDlg, useProxyControls, _countof(useProxyControls), FALSE); - EnableMultipleControls(hwndDlg, specifyPortsControls, _countof(specifyPortsControls), IsDlgButtonChecked(hwndDlg, IDC_SPECIFYPORTS) != BST_UNCHECKED); - EnableMultipleControls(hwndDlg, specifyOPortsControls, _countof(specifyOPortsControls), IsDlgButtonChecked(hwndDlg, IDC_SPECIFYPORTSO) != BST_UNCHECKED); - } - break; - - case WM_COMMAND: - iUser = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETCURSEL, 0, 0), 0); - switch (LOWORD(wParam)) { - case IDC_NETLIBUSERS: - if (HIWORD(wParam) == CBN_SELCHANGE) SendMessage(hwndDlg, M_REFRESHALL, 0, 0); - return 0; - - case IDC_LOGOPTIONS: - NetlibLogShowOptions(); - return 0; - - case IDC_PROXYTYPE: - if (HIWORD(wParam) == CBN_SELCHANGE) { - int newValue = SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETCURSEL, 0, 0), 0); - if (iUser == -1) { - if (newValue == 0) - return 0; - - for (auto &p : tempSettings) { - if (p->flags & NUF_NOOPTIONS) - continue; - if (newValue == PROXYTYPE_HTTP && !(p->flags & NUF_HTTPCONNS)) - p->settings.proxyType = PROXYTYPE_HTTPS; - else if (newValue == PROXYTYPE_HTTPS && p->flags & NUF_NOHTTPSOPTION) - p->settings.proxyType = PROXYTYPE_HTTP; - else p->settings.proxyType = newValue; - } - SendMessage(hwndDlg, M_REFRESHALL, 0, 0); - } - else { - tempSettings[iUser]->settings.proxyType = newValue; - SendMessage(hwndDlg, M_REFRESHENABLING, 0, 0); - } - } - break; - case IDC_USEPROXY: - ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, useProxy)); - break; - case IDC_PROXYAUTH: - ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, useProxyAuth)); - break; - case IDC_PROXYDNS: - ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, dnsThroughProxy)); - break; - case IDC_SPECIFYPORTS: - ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, specifyIncomingPorts)); - break; - case IDC_SPECIFYPORTSO: - ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, specifyOutgoingPorts)); - break; - case IDC_ENABLEUPNP: - ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, enableUPnP)); - break; - case IDC_VALIDATESSL: - ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, validateSSL)); - break; - case IDC_PROXYHOST: - if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0; - ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szProxyServer)); - break; - case IDC_PROXYPORT: - if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0; - { - int newValue = GetDlgItemInt(hwndDlg, LOWORD(wParam), nullptr, FALSE); - if (iUser == -1) { - for (auto &p : tempSettings) - if (!(p->flags & NUF_NOOPTIONS)) - p->settings.wProxyPort = newValue; - } - else tempSettings[iUser]->settings.wProxyPort = newValue; - } - break; - case IDC_PROXYUSER: - if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0; - ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szProxyAuthUser)); - break; - case IDC_PROXYPASS: - if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0; - ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szProxyAuthPassword)); - break; - case IDC_PORTSRANGE: - if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0; - ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szIncomingPorts)); - break; - case IDC_PORTSRANGEO: - if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0; - ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szOutgoingPorts)); - break; - } - ShowWindow(GetDlgItem(hwndDlg, IDC_RECONNECTREQD), SW_SHOW); - SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); - break; - - case WM_NOTIFY: - switch (((LPNMHDR)lParam)->idFrom) { - case 0: - switch (((LPNMHDR)lParam)->code) { - case PSN_APPLY: - for (auto &p : tempSettings) - NetlibSaveUserSettingsStruct(p->szSettingsModule, &p->settings); - return TRUE; - } - break; - } - break; - - case WM_DESTROY: - for (auto &p : tempSettings) { - mir_free(p->szSettingsModule); - NetlibFreeUserSettingsStruct(&p->settings); - mir_free(p); - } - tempSettings.destroy(); - break; - } - return FALSE; -} - -int NetlibOptInitialise(WPARAM wParam, LPARAM) -{ - int optionsCount = 0; - { - mir_cslock lck(csNetlibUser); - for (auto &p : netlibUser) - if (!(p->user.flags & NUF_NOOPTIONS)) - ++optionsCount; - } - - if (optionsCount == 0) - return 0; - - OPTIONSDIALOGPAGE odp = {}; - odp.position = 900000000; - odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_NETLIB); - odp.szTitle.a = LPGEN("Network"); - odp.pfnDlgProc = DlgProcNetlibOpts; - odp.flags = ODPF_BOLDGROUPS; - g_plugin.addOptions(wParam, &odp); - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+struct NetlibTempSettings
+{
+ uint32_t flags;
+ char *szSettingsModule;
+ NETLIBUSERSETTINGS settings;
+};
+
+static LIST <NetlibTempSettings> tempSettings(5);
+
+static const UINT outgoingConnectionsControls[] =
+{
+ IDC_STATIC12,
+ IDC_USEPROXY,
+ IDC_STATIC21, IDC_PROXYTYPE,
+ IDC_STATIC22, IDC_PROXYHOST, IDC_STATIC23, IDC_PROXYPORT, IDC_STOFTENPORT,
+ IDC_PROXYAUTH,
+ IDC_STATIC31, IDC_PROXYUSER, IDC_STATIC32, IDC_PROXYPASS,
+ IDC_PROXYDNS,
+ IDC_SPECIFYPORTSO,
+ IDC_PORTSRANGEO,
+ IDC_STATIC54,
+ IDC_VALIDATESSL};
+static const UINT useProxyControls[] = {
+ IDC_STATIC21, IDC_PROXYTYPE,
+ IDC_STATIC22, IDC_PROXYHOST, IDC_STATIC23, IDC_PROXYPORT, IDC_STOFTENPORT,
+ IDC_PROXYAUTH,
+ IDC_STATIC31, IDC_PROXYUSER, IDC_STATIC32, IDC_PROXYPASS,
+ IDC_PROXYDNS};
+static const UINT specifyOPortsControls[] = {
+ IDC_PORTSRANGEO,
+ IDC_STATIC54
+};
+static const UINT incomingConnectionsControls[] = {
+ IDC_STATIC43,
+ IDC_SPECIFYPORTS,
+ IDC_PORTSRANGE,
+ IDC_STATIC52,
+ IDC_ENABLEUPNP};
+static const UINT specifyPortsControls[] = {
+ IDC_PORTSRANGE,
+ IDC_STATIC52};
+
+static const wchar_t* szProxyTypes[] = {LPGENW("<mixed>"), L"SOCKS4", L"SOCKS5", L"HTTP", L"HTTPS", L"Internet Explorer"};
+static const uint16_t oftenProxyPorts[] = {1080, 1080, 1080, 8080, 8080, 8080};
+
+#define M_REFRESHALL (WM_USER+100)
+#define M_REFRESHENABLING (WM_USER+101)
+
+static void ShowMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state)
+{
+ for (int i = 0; i < cControls; i++)
+ ShowWindow(GetDlgItem(hwndDlg, controls[i]), state);
+}
+
+static void EnableMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state)
+{
+ for (int i = 0; i < cControls; i++)
+ EnableWindow(GetDlgItem(hwndDlg, controls[i]), state);
+}
+
+static void AddProxyTypeItem(HWND hwndDlg, int type, int selectType)
+{
+ int i = SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_ADDSTRING, 0, (LPARAM)(type == 0 ? TranslateW(szProxyTypes[type]) : szProxyTypes[type]));
+ SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_SETITEMDATA, i, type);
+ if (type == selectType)
+ SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_SETCURSEL, i, 0);
+}
+
+static void CopySettingsStruct(NETLIBUSERSETTINGS *dest, const NETLIBUSERSETTINGS *source)
+{
+ *dest = *source;
+ if (dest->szIncomingPorts) dest->szIncomingPorts = mir_strdup(dest->szIncomingPorts);
+ if (dest->szOutgoingPorts) dest->szOutgoingPorts = mir_strdup(dest->szOutgoingPorts);
+ if (dest->szProxyAuthPassword) dest->szProxyAuthPassword = mir_strdup(dest->szProxyAuthPassword);
+ if (dest->szProxyAuthUser) dest->szProxyAuthUser = mir_strdup(dest->szProxyAuthUser);
+ if (dest->szProxyServer) dest->szProxyServer = mir_strdup(dest->szProxyServer);
+}
+
+static void CombineSettingsStrings(char **dest, char **source)
+{
+ if (*dest != nullptr && (*source == nullptr || mir_strcmpi(*dest, *source))) { mir_free(*dest); *dest = nullptr; }
+}
+
+static void CombineSettingsStructs(NETLIBUSERSETTINGS *dest, uint32_t *destFlags, NETLIBUSERSETTINGS *source, uint32_t sourceFlags)
+{
+ if (sourceFlags & NUF_OUTGOING) {
+ if (*destFlags & NUF_OUTGOING) {
+ if (dest->validateSSL != source->validateSSL) dest->validateSSL = 2;
+ if (dest->useProxy != source->useProxy) dest->useProxy = 2;
+ if (dest->proxyType != source->proxyType) dest->proxyType = 0;
+ CombineSettingsStrings(&dest->szProxyServer, &source->szProxyServer);
+ if (dest->wProxyPort != source->wProxyPort) dest->wProxyPort = 0;
+ if (dest->useProxyAuth != source->useProxyAuth) dest->useProxyAuth = 2;
+ CombineSettingsStrings(&dest->szProxyAuthUser, &source->szProxyAuthUser);
+ CombineSettingsStrings(&dest->szProxyAuthPassword, &source->szProxyAuthPassword);
+ if (dest->dnsThroughProxy != source->dnsThroughProxy) dest->dnsThroughProxy = 2;
+ if (dest->specifyOutgoingPorts != source->specifyOutgoingPorts) dest->specifyOutgoingPorts = 2;
+ CombineSettingsStrings(&dest->szOutgoingPorts, &source->szOutgoingPorts);
+ }
+ else {
+ dest->validateSSL = source->validateSSL;
+ dest->useProxy = source->useProxy;
+ dest->proxyType = source->proxyType;
+ dest->szProxyServer = source->szProxyServer;
+ if (dest->szProxyServer) dest->szProxyServer = mir_strdup(dest->szProxyServer);
+ dest->wProxyPort = source->wProxyPort;
+ dest->useProxyAuth = source->useProxyAuth;
+ dest->szProxyAuthUser = source->szProxyAuthUser;
+ if (dest->szProxyAuthUser) dest->szProxyAuthUser = mir_strdup(dest->szProxyAuthUser);
+ dest->szProxyAuthPassword = source->szProxyAuthPassword;
+ if (dest->szProxyAuthPassword) dest->szProxyAuthPassword = mir_strdup(dest->szProxyAuthPassword);
+ dest->dnsThroughProxy = source->dnsThroughProxy;
+ dest->specifyOutgoingPorts = source->specifyOutgoingPorts;
+ dest->szOutgoingPorts = source->szOutgoingPorts;
+ if (dest->szOutgoingPorts) dest->szOutgoingPorts = mir_strdup(dest->szOutgoingPorts);
+ }
+ }
+ if (sourceFlags & NUF_INCOMING) {
+ if (*destFlags & NUF_INCOMING) {
+ if (dest->enableUPnP != source->enableUPnP) dest->enableUPnP = 2;
+ if (dest->specifyIncomingPorts != source->specifyIncomingPorts) dest->specifyIncomingPorts = 2;
+ CombineSettingsStrings(&dest->szIncomingPorts, &source->szIncomingPorts);
+ }
+ else {
+ dest->enableUPnP = source->enableUPnP;
+ dest->specifyIncomingPorts = source->specifyIncomingPorts;
+ dest->szIncomingPorts = source->szIncomingPorts;
+ if (dest->szIncomingPorts) dest->szIncomingPorts = mir_strdup(dest->szIncomingPorts);
+ }
+ }
+ if ((*destFlags & NUF_NOHTTPSOPTION) != (sourceFlags & NUF_NOHTTPSOPTION))
+ *destFlags = (*destFlags | sourceFlags) & ~NUF_NOHTTPSOPTION;
+ else *destFlags |= sourceFlags;
+}
+
+static void ChangeSettingIntByCheckbox(HWND hwndDlg, UINT ctrlId, int iUser, int memberOffset)
+{
+ int newValue = IsDlgButtonChecked(hwndDlg, ctrlId) != BST_CHECKED;
+ CheckDlgButton(hwndDlg, ctrlId, newValue ? BST_CHECKED : BST_UNCHECKED);
+ if (iUser == -1) {
+ for (auto &p : tempSettings)
+ if (!(p->flags & NUF_NOOPTIONS))
+ *(int*)(((uint8_t*)&p->settings) + memberOffset) = newValue;
+ }
+ else *(int*)(((uint8_t*)&tempSettings[iUser]->settings) + memberOffset) = newValue;
+ SendMessage(hwndDlg, M_REFRESHENABLING, 0, 0);
+}
+
+static void ChangeSettingStringByEdit(HWND hwndDlg, UINT ctrlId, int iUser, int memberOffset)
+{
+ int newValueLen = GetWindowTextLength(GetDlgItem(hwndDlg, ctrlId));
+ char *szNewValue = (char*)mir_alloc(newValueLen+1);
+ GetDlgItemTextA(hwndDlg, ctrlId, szNewValue, newValueLen+1);
+ if (iUser == -1) {
+ for (auto &p : tempSettings) {
+ if (!(p->flags & NUF_NOOPTIONS)) {
+ char **ppszNew = (char**)(((uint8_t*)&p->settings) + memberOffset);
+ mir_free(*ppszNew);
+ *ppszNew = mir_strdup(szNewValue);
+ }
+ }
+ mir_free(szNewValue);
+ }
+ else {
+ char **ppszNew = (char**)(((uint8_t*)&tempSettings[iUser]->settings) + memberOffset);
+ mir_free(*ppszNew);
+ *ppszNew = szNewValue;
+ }
+}
+
+static void WriteSettingsStructToDb(const char *szSettingsModule, NETLIBUSERSETTINGS *settings, uint32_t flags)
+{
+ if (flags & NUF_OUTGOING) {
+ db_set_b(0, szSettingsModule, "NLValidateSSL", (uint8_t)settings->validateSSL);
+ db_set_b(0, szSettingsModule, "NLUseProxy", (uint8_t)settings->useProxy);
+ db_set_b(0, szSettingsModule, "NLProxyType", (uint8_t)settings->proxyType);
+ db_set_s(0, szSettingsModule, "NLProxyServer", settings->szProxyServer ? settings->szProxyServer : "");
+ db_set_w(0, szSettingsModule, "NLProxyPort", (uint16_t)settings->wProxyPort);
+ db_set_b(0, szSettingsModule, "NLUseProxyAuth", (uint8_t)settings->useProxyAuth);
+ db_set_s(0, szSettingsModule, "NLProxyAuthUser", settings->szProxyAuthUser ? settings->szProxyAuthUser : "");
+ db_set_s(0, szSettingsModule, "NLProxyAuthPassword", settings->szProxyAuthPassword ? settings->szProxyAuthPassword : "");
+ db_set_b(0, szSettingsModule, "NLDnsThroughProxy", (uint8_t)settings->dnsThroughProxy);
+ db_set_b(0, szSettingsModule, "NLSpecifyOutgoingPorts", (uint8_t)settings->specifyOutgoingPorts);
+ db_set_s(0, szSettingsModule, "NLOutgoingPorts", settings->szOutgoingPorts ? settings->szOutgoingPorts : "");
+ }
+ if (flags & NUF_INCOMING) {
+ db_set_b(0, szSettingsModule, "NLEnableUPnP", (uint8_t)settings->enableUPnP);
+ db_set_b(0, szSettingsModule, "NLSpecifyIncomingPorts", (uint8_t)settings->specifyIncomingPorts);
+ db_set_s(0, szSettingsModule, "NLIncomingPorts", settings->szIncomingPorts ? settings->szIncomingPorts : "");
+ }
+}
+
+void NetlibSaveUserSettingsStruct(const char *szSettingsModule, const NETLIBUSERSETTINGS *settings)
+{
+ mir_cslock lck(csNetlibUser);
+
+ NetlibUser tUser;
+ tUser.user.szSettingsModule = (char*)szSettingsModule;
+ NetlibUser *thisUser = netlibUser.find(&tUser);
+ if (thisUser == nullptr)
+ return;
+
+ NetlibFreeUserSettingsStruct(&thisUser->settings);
+ CopySettingsStruct(&thisUser->settings, settings);
+ WriteSettingsStructToDb(thisUser->user.szSettingsModule, &thisUser->settings, thisUser->user.flags);
+
+ NETLIBUSERSETTINGS combinedSettings = { 0 };
+ combinedSettings.cbSize = sizeof(combinedSettings);
+
+ uint32_t flags = 0;
+ for (auto &p : netlibUser) {
+ if (p->user.flags & NUF_NOOPTIONS)
+ continue;
+ CombineSettingsStructs(&combinedSettings, &flags, &p->settings, p->user.flags);
+ }
+ if (combinedSettings.validateSSL == 2) combinedSettings.validateSSL = 0;
+ if (combinedSettings.useProxy == 2) combinedSettings.useProxy = 0;
+ if (combinedSettings.proxyType == 0) combinedSettings.proxyType = PROXYTYPE_SOCKS5;
+ if (combinedSettings.useProxyAuth == 2) combinedSettings.useProxyAuth = 0;
+ if (combinedSettings.dnsThroughProxy == 2) combinedSettings.dnsThroughProxy = 1;
+ if (combinedSettings.enableUPnP == 2) combinedSettings.enableUPnP = 1;
+ if (combinedSettings.specifyIncomingPorts == 2) combinedSettings.specifyIncomingPorts = 0;
+ WriteSettingsStructToDb("Netlib", &combinedSettings, flags);
+ NetlibFreeUserSettingsStruct(&combinedSettings);
+}
+
+static INT_PTR CALLBACK DlgProcNetlibOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ int iUser;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ {
+ int iItem = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_ADDSTRING, 0, (LPARAM)TranslateT("<All connections>"));
+ SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETITEMDATA, iItem, (LPARAM)-1);
+ SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETCURSEL, iItem, 0);
+
+ mir_cslock lck(csNetlibUser);
+ for (auto &it : netlibUser) {
+ NetlibTempSettings *thisSettings = (NetlibTempSettings*)mir_calloc(sizeof(NetlibTempSettings));
+ thisSettings->flags = it->user.flags;
+ thisSettings->szSettingsModule = mir_strdup(it->user.szSettingsModule);
+ CopySettingsStruct(&thisSettings->settings, &it->settings);
+ tempSettings.insert(thisSettings);
+
+ if (it->user.flags & NUF_NOOPTIONS)
+ continue;
+ iItem = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_ADDSTRING, 0, (LPARAM)it->user.szDescriptiveName.w);
+ SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_SETITEMDATA, iItem, netlibUser.indexOf(&it));
+ }
+ }
+
+ SendMessage(hwndDlg, M_REFRESHALL, 0, 0);
+ return TRUE;
+
+ case M_REFRESHALL:
+ iUser = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETCURSEL, 0, 0), 0);
+ {
+ NETLIBUSERSETTINGS settings = { 0 };
+ uint32_t flags = 0;
+
+ if (iUser == -1) {
+ settings.cbSize = sizeof(settings);
+ for (auto &p : tempSettings)
+ if (!(p->flags & NUF_NOOPTIONS))
+ CombineSettingsStructs(&settings, &flags, &p->settings, p->flags);
+ }
+ else {
+ NetlibFreeUserSettingsStruct(&settings);
+ CopySettingsStruct(&settings, &tempSettings[iUser]->settings);
+ flags = tempSettings[iUser]->flags;
+ }
+ ShowMultipleControls(hwndDlg, outgoingConnectionsControls, _countof(outgoingConnectionsControls), flags & NUF_OUTGOING ? SW_SHOW : SW_HIDE);
+ CheckDlgButton(hwndDlg, IDC_USEPROXY, settings.useProxy);
+ SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_RESETCONTENT, 0, 0);
+ if (settings.proxyType == 0) AddProxyTypeItem(hwndDlg, 0, settings.proxyType);
+ AddProxyTypeItem(hwndDlg, PROXYTYPE_SOCKS4, settings.proxyType);
+ AddProxyTypeItem(hwndDlg, PROXYTYPE_SOCKS5, settings.proxyType);
+ if (flags & NUF_HTTPCONNS) AddProxyTypeItem(hwndDlg, PROXYTYPE_HTTP, settings.proxyType);
+ if (!(flags & NUF_NOHTTPSOPTION)) AddProxyTypeItem(hwndDlg, PROXYTYPE_HTTPS, settings.proxyType);
+ if ((flags & NUF_HTTPCONNS) || !(flags & NUF_NOHTTPSOPTION))
+ AddProxyTypeItem(hwndDlg, PROXYTYPE_IE, settings.proxyType);
+ SetDlgItemTextA(hwndDlg, IDC_PROXYHOST, settings.szProxyServer ? settings.szProxyServer : "");
+ if (settings.wProxyPort) SetDlgItemInt(hwndDlg, IDC_PROXYPORT, settings.wProxyPort, FALSE);
+ else SetDlgItemTextA(hwndDlg, IDC_PROXYPORT, "");
+ CheckDlgButton(hwndDlg, IDC_PROXYAUTH, settings.useProxyAuth);
+ SetDlgItemTextA(hwndDlg, IDC_PROXYUSER, settings.szProxyAuthUser ? settings.szProxyAuthUser : "");
+ SetDlgItemTextA(hwndDlg, IDC_PROXYPASS, settings.szProxyAuthPassword ? settings.szProxyAuthPassword : "");
+ CheckDlgButton(hwndDlg, IDC_PROXYDNS, settings.dnsThroughProxy);
+ CheckDlgButton(hwndDlg, IDC_VALIDATESSL, settings.validateSSL);
+
+ ShowMultipleControls(hwndDlg, incomingConnectionsControls, _countof(incomingConnectionsControls), flags & NUF_INCOMING ? SW_SHOW : SW_HIDE);
+ CheckDlgButton(hwndDlg, IDC_SPECIFYPORTS, settings.specifyIncomingPorts);
+ SetDlgItemTextA(hwndDlg, IDC_PORTSRANGE, settings.szIncomingPorts ? settings.szIncomingPorts : "");
+
+ CheckDlgButton(hwndDlg, IDC_SPECIFYPORTSO, settings.specifyOutgoingPorts);
+ SetDlgItemTextA(hwndDlg, IDC_PORTSRANGEO, settings.szOutgoingPorts ? settings.szOutgoingPorts : "");
+
+ CheckDlgButton(hwndDlg, IDC_ENABLEUPNP, settings.enableUPnP);
+
+ NetlibFreeUserSettingsStruct(&settings);
+ SendMessage(hwndDlg, M_REFRESHENABLING, 0, 0);
+ }
+ break;
+
+ case M_REFRESHENABLING:
+ wchar_t str[80];
+ {
+ int selectedProxyType = SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETCURSEL, 0, 0), 0);
+ mir_snwprintf(str, TranslateT("(often %d)"), oftenProxyPorts[selectedProxyType]);
+ SetDlgItemText(hwndDlg, IDC_STOFTENPORT, str);
+ if (IsDlgButtonChecked(hwndDlg, IDC_USEPROXY) != BST_UNCHECKED) {
+ int enableAuth = 0, enableUser = 0, enablePass = 0, enableServer = 1;
+ EnableMultipleControls(hwndDlg, useProxyControls, _countof(useProxyControls), TRUE);
+ if (selectedProxyType == 0) {
+ for (auto &p : tempSettings) {
+ if (!p->settings.useProxy || p->flags & NUF_NOOPTIONS || !(p->flags & NUF_OUTGOING))
+ continue;
+
+ if (p->settings.proxyType == PROXYTYPE_SOCKS4) enableUser = 1;
+ else {
+ enableAuth = 1;
+ if (p->settings.useProxyAuth)
+ enableUser = enablePass = 1;
+ }
+ }
+ }
+ else {
+ if (selectedProxyType == PROXYTYPE_SOCKS4) enableUser = 1;
+ else {
+ if (selectedProxyType == PROXYTYPE_IE) enableServer = 0;
+ enableAuth = 1;
+ if (IsDlgButtonChecked(hwndDlg, IDC_PROXYAUTH) != BST_UNCHECKED)
+ enableUser = enablePass = 1;
+ }
+ }
+ EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYAUTH), enableAuth);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC31), enableUser);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYUSER), enableUser);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_STATIC32), enablePass);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYPASS), enablePass);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYHOST), enableServer);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_PROXYPORT), enableServer);
+ }
+ else EnableMultipleControls(hwndDlg, useProxyControls, _countof(useProxyControls), FALSE);
+ EnableMultipleControls(hwndDlg, specifyPortsControls, _countof(specifyPortsControls), IsDlgButtonChecked(hwndDlg, IDC_SPECIFYPORTS) != BST_UNCHECKED);
+ EnableMultipleControls(hwndDlg, specifyOPortsControls, _countof(specifyOPortsControls), IsDlgButtonChecked(hwndDlg, IDC_SPECIFYPORTSO) != BST_UNCHECKED);
+ }
+ break;
+
+ case WM_COMMAND:
+ iUser = SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_NETLIBUSERS, CB_GETCURSEL, 0, 0), 0);
+ switch (LOWORD(wParam)) {
+ case IDC_NETLIBUSERS:
+ if (HIWORD(wParam) == CBN_SELCHANGE) SendMessage(hwndDlg, M_REFRESHALL, 0, 0);
+ return 0;
+
+ case IDC_LOGOPTIONS:
+ NetlibLogShowOptions();
+ return 0;
+
+ case IDC_PROXYTYPE:
+ if (HIWORD(wParam) == CBN_SELCHANGE) {
+ int newValue = SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, IDC_PROXYTYPE, CB_GETCURSEL, 0, 0), 0);
+ if (iUser == -1) {
+ if (newValue == 0)
+ return 0;
+
+ for (auto &p : tempSettings) {
+ if (p->flags & NUF_NOOPTIONS)
+ continue;
+ if (newValue == PROXYTYPE_HTTP && !(p->flags & NUF_HTTPCONNS))
+ p->settings.proxyType = PROXYTYPE_HTTPS;
+ else if (newValue == PROXYTYPE_HTTPS && p->flags & NUF_NOHTTPSOPTION)
+ p->settings.proxyType = PROXYTYPE_HTTP;
+ else p->settings.proxyType = newValue;
+ }
+ SendMessage(hwndDlg, M_REFRESHALL, 0, 0);
+ }
+ else {
+ tempSettings[iUser]->settings.proxyType = newValue;
+ SendMessage(hwndDlg, M_REFRESHENABLING, 0, 0);
+ }
+ }
+ break;
+ case IDC_USEPROXY:
+ ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, useProxy));
+ break;
+ case IDC_PROXYAUTH:
+ ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, useProxyAuth));
+ break;
+ case IDC_PROXYDNS:
+ ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, dnsThroughProxy));
+ break;
+ case IDC_SPECIFYPORTS:
+ ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, specifyIncomingPorts));
+ break;
+ case IDC_SPECIFYPORTSO:
+ ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, specifyOutgoingPorts));
+ break;
+ case IDC_ENABLEUPNP:
+ ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, enableUPnP));
+ break;
+ case IDC_VALIDATESSL:
+ ChangeSettingIntByCheckbox(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, validateSSL));
+ break;
+ case IDC_PROXYHOST:
+ if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0;
+ ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szProxyServer));
+ break;
+ case IDC_PROXYPORT:
+ if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0;
+ {
+ int newValue = GetDlgItemInt(hwndDlg, LOWORD(wParam), nullptr, FALSE);
+ if (iUser == -1) {
+ for (auto &p : tempSettings)
+ if (!(p->flags & NUF_NOOPTIONS))
+ p->settings.wProxyPort = newValue;
+ }
+ else tempSettings[iUser]->settings.wProxyPort = newValue;
+ }
+ break;
+ case IDC_PROXYUSER:
+ if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0;
+ ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szProxyAuthUser));
+ break;
+ case IDC_PROXYPASS:
+ if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0;
+ ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szProxyAuthPassword));
+ break;
+ case IDC_PORTSRANGE:
+ if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0;
+ ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szIncomingPorts));
+ break;
+ case IDC_PORTSRANGEO:
+ if (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()) return 0;
+ ChangeSettingStringByEdit(hwndDlg, LOWORD(wParam), iUser, offsetof(NETLIBUSERSETTINGS, szOutgoingPorts));
+ break;
+ }
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RECONNECTREQD), SW_SHOW);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->idFrom) {
+ case 0:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_APPLY:
+ for (auto &p : tempSettings)
+ NetlibSaveUserSettingsStruct(p->szSettingsModule, &p->settings);
+ return TRUE;
+ }
+ break;
+ }
+ break;
+
+ case WM_DESTROY:
+ for (auto &p : tempSettings) {
+ mir_free(p->szSettingsModule);
+ NetlibFreeUserSettingsStruct(&p->settings);
+ mir_free(p);
+ }
+ tempSettings.destroy();
+ break;
+ }
+ return FALSE;
+}
+
+int NetlibOptInitialise(WPARAM wParam, LPARAM)
+{
+ int optionsCount = 0;
+ {
+ mir_cslock lck(csNetlibUser);
+ for (auto &p : netlibUser)
+ if (!(p->user.flags & NUF_NOOPTIONS))
+ ++optionsCount;
+ }
+
+ if (optionsCount == 0)
+ return 0;
+
+ OPTIONSDIALOGPAGE odp = {};
+ odp.position = 900000000;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_NETLIB);
+ odp.szTitle.a = LPGEN("Network");
+ odp.pfnDlgProc = DlgProcNetlibOpts;
+ odp.flags = ODPF_BOLDGROUPS;
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/netlib_pktrecver.cpp b/src/mir_app/src/netlib_pktrecver.cpp index 66621d43a3..3b86fed51d 100644 --- a/src/mir_app/src/netlib_pktrecver.cpp +++ b/src/mir_app/src/netlib_pktrecver.cpp @@ -1,80 +1,80 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -MIR_APP_DLL(HANDLE) Netlib_CreatePacketReceiver(HNETLIBCONN nlc, int iMaxSize) -{ - if (GetNetlibHandleType(nlc) != NLH_CONNECTION || iMaxSize == 0) { - SetLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - - NetlibPacketRecver *nlpr = (struct NetlibPacketRecver*)mir_calloc(sizeof(struct NetlibPacketRecver)); - nlpr->handleType = NLH_PACKETRECVER; - nlpr->nlc = nlc; - nlpr->packetRecver.bufferSize = iMaxSize; - nlpr->packetRecver.buffer = (uint8_t*)mir_alloc(nlpr->packetRecver.bufferSize); - nlpr->packetRecver.bytesUsed = 0; - nlpr->packetRecver.bytesAvailable = 0; - return nlpr; -} - -MIR_APP_DLL(int) Netlib_GetMorePackets(HANDLE hReceiver, NETLIBPACKETRECVER *nlprParam) -{ - NetlibPacketRecver *nlpr = (NetlibPacketRecver*)hReceiver; - if (GetNetlibHandleType(nlpr) != NLH_PACKETRECVER || nlprParam == nullptr || nlprParam->bytesUsed > nlpr->packetRecver.bytesAvailable) { - SetLastError(ERROR_INVALID_PARAMETER); - return SOCKET_ERROR; - } - if (Miranda_IsTerminated()) { /* HACK: Lame, break while loops of protocols that can't kill their while loops, (cough, ICQ, cough) */ - SetLastError(ERROR_TIMEOUT); - return SOCKET_ERROR; - } - nlpr->packetRecver.dwTimeout = nlprParam->dwTimeout; - if (nlprParam->bytesUsed == 0) { - if (nlpr->packetRecver.bytesAvailable == nlpr->packetRecver.bufferSize) { - nlpr->packetRecver.bytesAvailable = 0; - Netlib_Logf(nlpr->nlc->nlu, "Packet recver: packet overflowed buffer, ditching"); - } - } - else { - memmove(nlpr->packetRecver.buffer, nlpr->packetRecver.buffer + nlprParam->bytesUsed, nlpr->packetRecver.bytesAvailable - nlprParam->bytesUsed); - nlpr->packetRecver.bytesAvailable -= nlprParam->bytesUsed; - } - - if (nlprParam->dwTimeout != INFINITE) { - if (!Netlib_SslPending(nlpr->nlc->hSsl) && WaitUntilReadable(nlpr->nlc->s, nlprParam->dwTimeout) <= 0) { - *nlprParam = nlpr->packetRecver; - return SOCKET_ERROR; - } - } - - INT_PTR recvResult = Netlib_Recv(nlpr->nlc, (char*)nlpr->packetRecver.buffer + nlpr->packetRecver.bytesAvailable, nlpr->packetRecver.bufferSize - nlpr->packetRecver.bytesAvailable, 0); - if (recvResult > 0) - nlpr->packetRecver.bytesAvailable += recvResult; - *nlprParam = nlpr->packetRecver; - return recvResult; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+MIR_APP_DLL(HANDLE) Netlib_CreatePacketReceiver(HNETLIBCONN nlc, int iMaxSize)
+{
+ if (GetNetlibHandleType(nlc) != NLH_CONNECTION || iMaxSize == 0) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return nullptr;
+ }
+
+ NetlibPacketRecver *nlpr = (struct NetlibPacketRecver*)mir_calloc(sizeof(struct NetlibPacketRecver));
+ nlpr->handleType = NLH_PACKETRECVER;
+ nlpr->nlc = nlc;
+ nlpr->packetRecver.bufferSize = iMaxSize;
+ nlpr->packetRecver.buffer = (uint8_t*)mir_alloc(nlpr->packetRecver.bufferSize);
+ nlpr->packetRecver.bytesUsed = 0;
+ nlpr->packetRecver.bytesAvailable = 0;
+ return nlpr;
+}
+
+MIR_APP_DLL(int) Netlib_GetMorePackets(HANDLE hReceiver, NETLIBPACKETRECVER *nlprParam)
+{
+ NetlibPacketRecver *nlpr = (NetlibPacketRecver*)hReceiver;
+ if (GetNetlibHandleType(nlpr) != NLH_PACKETRECVER || nlprParam == nullptr || nlprParam->bytesUsed > nlpr->packetRecver.bytesAvailable) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SOCKET_ERROR;
+ }
+ if (Miranda_IsTerminated()) { /* HACK: Lame, break while loops of protocols that can't kill their while loops, (cough, ICQ, cough) */
+ SetLastError(ERROR_TIMEOUT);
+ return SOCKET_ERROR;
+ }
+ nlpr->packetRecver.dwTimeout = nlprParam->dwTimeout;
+ if (nlprParam->bytesUsed == 0) {
+ if (nlpr->packetRecver.bytesAvailable == nlpr->packetRecver.bufferSize) {
+ nlpr->packetRecver.bytesAvailable = 0;
+ Netlib_Logf(nlpr->nlc->nlu, "Packet recver: packet overflowed buffer, ditching");
+ }
+ }
+ else {
+ memmove(nlpr->packetRecver.buffer, nlpr->packetRecver.buffer + nlprParam->bytesUsed, nlpr->packetRecver.bytesAvailable - nlprParam->bytesUsed);
+ nlpr->packetRecver.bytesAvailable -= nlprParam->bytesUsed;
+ }
+
+ if (nlprParam->dwTimeout != INFINITE) {
+ if (!Netlib_SslPending(nlpr->nlc->hSsl) && WaitUntilReadable(nlpr->nlc->s, nlprParam->dwTimeout) <= 0) {
+ *nlprParam = nlpr->packetRecver;
+ return SOCKET_ERROR;
+ }
+ }
+
+ INT_PTR recvResult = Netlib_Recv(nlpr->nlc, (char*)nlpr->packetRecver.buffer + nlpr->packetRecver.bytesAvailable, nlpr->packetRecver.bufferSize - nlpr->packetRecver.bytesAvailable, 0);
+ if (recvResult > 0)
+ nlpr->packetRecver.bytesAvailable += recvResult;
+ *nlprParam = nlpr->packetRecver;
+ return recvResult;
+}
diff --git a/src/mir_app/src/netlib_security.cpp b/src/mir_app/src/netlib_security.cpp index 278991b7e4..90aa9c68ff 100644 --- a/src/mir_app/src/netlib_security.cpp +++ b/src/mir_app/src/netlib_security.cpp @@ -1,363 +1,363 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -#define SECURITY_WIN32 -#include <security.h> -#include <rpcdce.h> - -#pragma comment(lib, "secur32.lib") - -struct NtlmHandleType -{ - CtxtHandle hClientContext; - CredHandle hClientCredential; - wchar_t* szProvider; - wchar_t* szPrincipal; - unsigned cbMaxToken; - bool hasDomain; -}; - -struct NTLM_String -{ - uint16_t len; - uint16_t allocedSpace; - uint32_t offset; -}; - -struct NtlmType2packet -{ - char sign[8]; - uint32_t type; // == 2 - NTLM_String targetName; - uint32_t flags; - uint8_t challenge[8]; - uint8_t context[8]; - NTLM_String targetInfo; -}; - -static unsigned ntlmCnt = 0; -static mir_cs csSec; - -static void ReportSecError(SECURITY_STATUS scRet, int line) -{ - wchar_t szMsgBuf[256]; - FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, scRet, LANG_USER_DEFAULT, szMsgBuf, _countof(szMsgBuf), nullptr); - - wchar_t *p = wcschr(szMsgBuf, 13); if (p) *p = 0; - - Netlib_LogfW(nullptr, L"Security error 0x%x on line %u (%s)", scRet, line, szMsgBuf); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(HANDLE) Netlib_InitSecurityProvider(const wchar_t *szProvider, const wchar_t *szPrincipal) -{ - HANDLE hSecurity = nullptr; - - if (mir_wstrcmpi(szProvider, L"Basic") == 0) { - NtlmHandleType* hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType)); - hNtlm->szProvider = mir_wstrdup(szProvider); - SecInvalidateHandle(&hNtlm->hClientContext); - SecInvalidateHandle(&hNtlm->hClientCredential); - ntlmCnt++; - - return hNtlm; - } - - mir_cslock lck(csSec); - - PSecPkgInfo ntlmSecurityPackageInfo; - SECURITY_STATUS sc = QuerySecurityPackageInfo((LPTSTR)szProvider, &ntlmSecurityPackageInfo); - if (sc == SEC_E_OK) { - NtlmHandleType* hNtlm; - - hSecurity = hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType)); - hNtlm->cbMaxToken = ntlmSecurityPackageInfo->cbMaxToken; - FreeContextBuffer(ntlmSecurityPackageInfo); - - hNtlm->szProvider = mir_wstrdup(szProvider); - hNtlm->szPrincipal = mir_wstrdup(szPrincipal ? szPrincipal : L""); - SecInvalidateHandle(&hNtlm->hClientContext); - SecInvalidateHandle(&hNtlm->hClientCredential); - ntlmCnt++; - } - return hSecurity; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(void) Netlib_DestroySecurityProvider(HANDLE hSecurity) -{ - if (hSecurity == nullptr) - return; - - mir_cslock lck(csSec); - - if (ntlmCnt != 0) { - NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity; - if (hNtlm != nullptr) { - if (SecIsValidHandle(&hNtlm->hClientContext)) - DeleteSecurityContext(&hNtlm->hClientContext); - if (SecIsValidHandle(&hNtlm->hClientCredential)) - FreeCredentialsHandle(&hNtlm->hClientCredential); - mir_free(hNtlm->szProvider); - mir_free(hNtlm->szPrincipal); - mir_free(hNtlm); - } - - --ntlmCnt; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -char* CompleteGssapi(HANDLE hSecurity, unsigned char *szChallenge, unsigned chlsz) -{ - if (!szChallenge || !szChallenge[0]) return nullptr; - - NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity; - unsigned char inDataBuffer[1024]; - - SecBuffer inBuffers[2] = - { - { sizeof(inDataBuffer), SECBUFFER_DATA, inDataBuffer }, - { chlsz, SECBUFFER_STREAM, szChallenge } - }; - - SecBufferDesc inBuffersDesc = { SECBUFFER_VERSION, 2, inBuffers }; - - unsigned long qop = 0; - SECURITY_STATUS sc = DecryptMessage(&hNtlm->hClientContext, &inBuffersDesc, 0, &qop); - if (sc != SEC_E_OK) { - ReportSecError(sc, __LINE__); - return nullptr; - } - - // unsigned char LayerMask = inDataBuffer[0]; - // unsigned int MaxMessageSize = htonl(*(unsigned*)&inDataBuffer[1]); - - SecPkgContext_Sizes sizes; - sc = QueryContextAttributes(&hNtlm->hClientContext, SECPKG_ATTR_SIZES, &sizes); - if (sc != SEC_E_OK) { - ReportSecError(sc, __LINE__); - return nullptr; - } - - unsigned char *tokenBuffer = (unsigned char*)alloca(sizes.cbSecurityTrailer); - unsigned char *paddingBuffer = (unsigned char*)alloca(sizes.cbBlockSize); - - unsigned char outDataBuffer[4] = { 1, 0, 16, 0 }; - - SecBuffer outBuffers[3] = - { - { sizes.cbSecurityTrailer, SECBUFFER_TOKEN, tokenBuffer }, - { sizeof(outDataBuffer), SECBUFFER_DATA, outDataBuffer }, - { sizes.cbBlockSize, SECBUFFER_PADDING, paddingBuffer } - }; - SecBufferDesc outBuffersDesc = { SECBUFFER_VERSION, 3, outBuffers }; - - sc = EncryptMessage(&hNtlm->hClientContext, SECQOP_WRAP_NO_ENCRYPT, &outBuffersDesc, 0); - if (sc != SEC_E_OK) { - ReportSecError(sc, __LINE__); - return nullptr; - } - - unsigned i, ressz = 0; - for (i = 0; i < outBuffersDesc.cBuffers; i++) - ressz += outBuffersDesc.pBuffers[i].cbBuffer; - - unsigned char *response = (unsigned char*)alloca(ressz), *p = response; - for (i = 0; i < outBuffersDesc.cBuffers; i++) { - memcpy(p, outBuffersDesc.pBuffers[i].pvBuffer, outBuffersDesc.pBuffers[i].cbBuffer); - p += outBuffersDesc.pBuffers[i].cbBuffer; - } - - return mir_base64_encode(response, ressz); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -char* NtlmCreateResponseFromChallenge(HANDLE hSecurity, const char *szChallenge, const wchar_t *login, const wchar_t *psw, bool http, unsigned &complete) -{ - if (hSecurity == nullptr || ntlmCnt == 0) - return nullptr; - - SecBufferDesc outputBufferDescriptor, inputBufferDescriptor; - SecBuffer outputSecurityToken, inputSecurityToken; - char *szOutputToken; - - NtlmHandleType *hNtlm = (NtlmHandleType*)hSecurity; - - Netlib_Logf(nullptr, "NtlmCreateResponseFromChallenge (%s): chl=%s {%S:%S} => %d", hNtlm->szProvider, szChallenge, login, psw, complete); - - if (mir_wstrcmpi(hNtlm->szProvider, L"Basic")) { - bool isGSSAPI = mir_wstrcmpi(hNtlm->szProvider, L"Kerberos") == 0; - bool hasChallenge = szChallenge != nullptr && szChallenge[0] != '\0'; - if (hasChallenge) { - size_t tokenLen; - uint8_t *token = (uint8_t*)mir_base64_decode(szChallenge, &tokenLen); - if (token == nullptr) - return nullptr; - - if (isGSSAPI && complete) - return CompleteGssapi(hSecurity, token, (unsigned)tokenLen); - - inputBufferDescriptor.cBuffers = 1; - inputBufferDescriptor.pBuffers = &inputSecurityToken; - inputBufferDescriptor.ulVersion = SECBUFFER_VERSION; - inputSecurityToken.BufferType = SECBUFFER_TOKEN; - inputSecurityToken.cbBuffer = (unsigned)tokenLen; - inputSecurityToken.pvBuffer = token; - - // try to decode the domain name from the NTLM challenge - if (login != nullptr && login[0] != '\0' && !hNtlm->hasDomain) { - NtlmType2packet* pkt = (NtlmType2packet*)token; - if (!strncmp(pkt->sign, "NTLMSSP", 8) && pkt->type == 2) { - - wchar_t* domainName = (wchar_t*)&token[pkt->targetName.offset]; - int domainLen = pkt->targetName.len; - - // Negotiate ANSI? if yes, convert the ANSI name to unicode - if ((pkt->flags & 1) == 0) { - int bufsz = MultiByteToWideChar(CP_ACP, 0, (char*)domainName, domainLen, nullptr, 0); - wchar_t* buf = (wchar_t*)alloca((bufsz+1) * sizeof(wchar_t)); - domainLen = MultiByteToWideChar(CP_ACP, 0, (char*)domainName, domainLen, buf, bufsz) - 1; - buf[domainLen] = 0; - domainName = buf; - } - else domainLen /= sizeof(wchar_t); - - if (domainLen) { - CMStringW wszNewLogin(FORMAT, L"%s\\%s", domainName, login); - char* szChl = NtlmCreateResponseFromChallenge(hSecurity, nullptr, wszNewLogin, psw, http, complete); - mir_free(szChl); - } - } - } - } - else { - if (SecIsValidHandle(&hNtlm->hClientContext)) - DeleteSecurityContext(&hNtlm->hClientContext); - if (SecIsValidHandle(&hNtlm->hClientCredential)) - FreeCredentialsHandle(&hNtlm->hClientCredential); - - SEC_WINNT_AUTH_IDENTITY auth; - - if (login != nullptr && login[0] != '\0') { - memset(&auth, 0, sizeof(auth)); - - Netlib_Logf(nullptr, "Security login requested, user: %S pssw: %s", login, psw ? "(exist)" : "(no psw)"); - - const wchar_t* loginName = login; - const wchar_t* domainName = wcschr(login, '\\'); - size_t domainLen = 0; - size_t loginLen = mir_wstrlen(loginName); - if (domainName != nullptr) { - loginName = domainName + 1; - loginLen = mir_wstrlen(loginName); - domainLen = domainName - login; - domainName = login; - } - else if ((domainName = wcschr(login, '@')) != nullptr) { - loginName = login; - loginLen = domainName - login; - domainLen = mir_wstrlen(++domainName); - } - - auth.User = (PWORD)loginName; - auth.UserLength = (ULONG)loginLen; - auth.Password = (PWORD)psw; - auth.PasswordLength = (ULONG)mir_wstrlen(psw); - auth.Domain = (PWORD)domainName; - auth.DomainLength = (ULONG)domainLen; - auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; - - hNtlm->hasDomain = domainLen != 0; - } - - TimeStamp tokenExpiration; - SECURITY_STATUS sc = AcquireCredentialsHandle(nullptr, hNtlm->szProvider, SECPKG_CRED_OUTBOUND, nullptr, hNtlm->hasDomain ? &auth : nullptr, nullptr, nullptr, &hNtlm->hClientCredential, &tokenExpiration); - if (sc != SEC_E_OK) { - ReportSecError(sc, __LINE__); - return nullptr; - } - } - - outputBufferDescriptor.cBuffers = 1; - outputBufferDescriptor.pBuffers = &outputSecurityToken; - outputBufferDescriptor.ulVersion = SECBUFFER_VERSION; - outputSecurityToken.BufferType = SECBUFFER_TOKEN; - outputSecurityToken.cbBuffer = hNtlm->cbMaxToken; - outputSecurityToken.pvBuffer = alloca(outputSecurityToken.cbBuffer); - - ULONG contextAttributes; - TimeStamp tokenExpiration; - SECURITY_STATUS sc = InitializeSecurityContext(&hNtlm->hClientCredential, - hasChallenge ? &hNtlm->hClientContext : nullptr, - hNtlm->szPrincipal, isGSSAPI ? ISC_REQ_MUTUAL_AUTH | ISC_REQ_STREAM : 0, 0, SECURITY_NATIVE_DREP, - hasChallenge ? &inputBufferDescriptor : nullptr, 0, &hNtlm->hClientContext, - &outputBufferDescriptor, &contextAttributes, &tokenExpiration); - Netlib_Logf(nullptr, "InitializeSecurityContext(%S): 0x%x", hNtlm->szProvider, sc); - - complete = (sc != SEC_I_COMPLETE_AND_CONTINUE && sc != SEC_I_CONTINUE_NEEDED); - if (sc == SEC_I_COMPLETE_NEEDED || sc == SEC_I_COMPLETE_AND_CONTINUE) { - sc = CompleteAuthToken(&hNtlm->hClientContext, &outputBufferDescriptor); - Netlib_Logf(nullptr, "CompleteAuthToken: 0x%x", sc); - } - - if (sc != SEC_E_OK && sc != SEC_I_CONTINUE_NEEDED) { - ReportSecError(sc, __LINE__); - return nullptr; - } - - szOutputToken = mir_base64_encode(outputSecurityToken.pvBuffer, outputSecurityToken.cbBuffer); - } - else { - if (!login || !psw) - return nullptr; - - T2Utf szAuth(CMStringW(FORMAT, L"%s:%s", login, psw)); - szOutputToken = mir_base64_encode(szAuth.get(), mir_strlen(szAuth)); - complete = true; - } - - if (szOutputToken == nullptr) - return nullptr; - - if (!http) - return szOutputToken; - - CMStringA szResult(FORMAT, "%S %s", hNtlm->szProvider, szOutputToken); - mir_free(szOutputToken); - return szResult.Detach(); -} - -MIR_APP_DLL(char*) Netlib_NtlmCreateResponse(HANDLE hProvider, const char *szChallenge, wchar_t *pwszLogin, wchar_t *pwszPassword, unsigned &complete) -{ - return NtlmCreateResponseFromChallenge(hProvider, szChallenge, pwszLogin, pwszPassword, false, complete); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+#define SECURITY_WIN32
+#include <security.h>
+#include <rpcdce.h>
+
+#pragma comment(lib, "secur32.lib")
+
+struct NtlmHandleType
+{
+ CtxtHandle hClientContext;
+ CredHandle hClientCredential;
+ wchar_t* szProvider;
+ wchar_t* szPrincipal;
+ unsigned cbMaxToken;
+ bool hasDomain;
+};
+
+struct NTLM_String
+{
+ uint16_t len;
+ uint16_t allocedSpace;
+ uint32_t offset;
+};
+
+struct NtlmType2packet
+{
+ char sign[8];
+ uint32_t type; // == 2
+ NTLM_String targetName;
+ uint32_t flags;
+ uint8_t challenge[8];
+ uint8_t context[8];
+ NTLM_String targetInfo;
+};
+
+static unsigned ntlmCnt = 0;
+static mir_cs csSec;
+
+static void ReportSecError(SECURITY_STATUS scRet, int line)
+{
+ wchar_t szMsgBuf[256];
+ FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, scRet, LANG_USER_DEFAULT, szMsgBuf, _countof(szMsgBuf), nullptr);
+
+ wchar_t *p = wcschr(szMsgBuf, 13); if (p) *p = 0;
+
+ Netlib_LogfW(nullptr, L"Security error 0x%x on line %u (%s)", scRet, line, szMsgBuf);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(HANDLE) Netlib_InitSecurityProvider(const wchar_t *szProvider, const wchar_t *szPrincipal)
+{
+ HANDLE hSecurity = nullptr;
+
+ if (mir_wstrcmpi(szProvider, L"Basic") == 0) {
+ NtlmHandleType* hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType));
+ hNtlm->szProvider = mir_wstrdup(szProvider);
+ SecInvalidateHandle(&hNtlm->hClientContext);
+ SecInvalidateHandle(&hNtlm->hClientCredential);
+ ntlmCnt++;
+
+ return hNtlm;
+ }
+
+ mir_cslock lck(csSec);
+
+ PSecPkgInfo ntlmSecurityPackageInfo;
+ SECURITY_STATUS sc = QuerySecurityPackageInfo((LPTSTR)szProvider, &ntlmSecurityPackageInfo);
+ if (sc == SEC_E_OK) {
+ NtlmHandleType* hNtlm;
+
+ hSecurity = hNtlm = (NtlmHandleType*)mir_calloc(sizeof(NtlmHandleType));
+ hNtlm->cbMaxToken = ntlmSecurityPackageInfo->cbMaxToken;
+ FreeContextBuffer(ntlmSecurityPackageInfo);
+
+ hNtlm->szProvider = mir_wstrdup(szProvider);
+ hNtlm->szPrincipal = mir_wstrdup(szPrincipal ? szPrincipal : L"");
+ SecInvalidateHandle(&hNtlm->hClientContext);
+ SecInvalidateHandle(&hNtlm->hClientCredential);
+ ntlmCnt++;
+ }
+ return hSecurity;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(void) Netlib_DestroySecurityProvider(HANDLE hSecurity)
+{
+ if (hSecurity == nullptr)
+ return;
+
+ mir_cslock lck(csSec);
+
+ if (ntlmCnt != 0) {
+ NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity;
+ if (hNtlm != nullptr) {
+ if (SecIsValidHandle(&hNtlm->hClientContext))
+ DeleteSecurityContext(&hNtlm->hClientContext);
+ if (SecIsValidHandle(&hNtlm->hClientCredential))
+ FreeCredentialsHandle(&hNtlm->hClientCredential);
+ mir_free(hNtlm->szProvider);
+ mir_free(hNtlm->szPrincipal);
+ mir_free(hNtlm);
+ }
+
+ --ntlmCnt;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+char* CompleteGssapi(HANDLE hSecurity, unsigned char *szChallenge, unsigned chlsz)
+{
+ if (!szChallenge || !szChallenge[0]) return nullptr;
+
+ NtlmHandleType* hNtlm = (NtlmHandleType*)hSecurity;
+ unsigned char inDataBuffer[1024];
+
+ SecBuffer inBuffers[2] =
+ {
+ { sizeof(inDataBuffer), SECBUFFER_DATA, inDataBuffer },
+ { chlsz, SECBUFFER_STREAM, szChallenge }
+ };
+
+ SecBufferDesc inBuffersDesc = { SECBUFFER_VERSION, 2, inBuffers };
+
+ unsigned long qop = 0;
+ SECURITY_STATUS sc = DecryptMessage(&hNtlm->hClientContext, &inBuffersDesc, 0, &qop);
+ if (sc != SEC_E_OK) {
+ ReportSecError(sc, __LINE__);
+ return nullptr;
+ }
+
+ // unsigned char LayerMask = inDataBuffer[0];
+ // unsigned int MaxMessageSize = htonl(*(unsigned*)&inDataBuffer[1]);
+
+ SecPkgContext_Sizes sizes;
+ sc = QueryContextAttributes(&hNtlm->hClientContext, SECPKG_ATTR_SIZES, &sizes);
+ if (sc != SEC_E_OK) {
+ ReportSecError(sc, __LINE__);
+ return nullptr;
+ }
+
+ unsigned char *tokenBuffer = (unsigned char*)alloca(sizes.cbSecurityTrailer);
+ unsigned char *paddingBuffer = (unsigned char*)alloca(sizes.cbBlockSize);
+
+ unsigned char outDataBuffer[4] = { 1, 0, 16, 0 };
+
+ SecBuffer outBuffers[3] =
+ {
+ { sizes.cbSecurityTrailer, SECBUFFER_TOKEN, tokenBuffer },
+ { sizeof(outDataBuffer), SECBUFFER_DATA, outDataBuffer },
+ { sizes.cbBlockSize, SECBUFFER_PADDING, paddingBuffer }
+ };
+ SecBufferDesc outBuffersDesc = { SECBUFFER_VERSION, 3, outBuffers };
+
+ sc = EncryptMessage(&hNtlm->hClientContext, SECQOP_WRAP_NO_ENCRYPT, &outBuffersDesc, 0);
+ if (sc != SEC_E_OK) {
+ ReportSecError(sc, __LINE__);
+ return nullptr;
+ }
+
+ unsigned i, ressz = 0;
+ for (i = 0; i < outBuffersDesc.cBuffers; i++)
+ ressz += outBuffersDesc.pBuffers[i].cbBuffer;
+
+ unsigned char *response = (unsigned char*)alloca(ressz), *p = response;
+ for (i = 0; i < outBuffersDesc.cBuffers; i++) {
+ memcpy(p, outBuffersDesc.pBuffers[i].pvBuffer, outBuffersDesc.pBuffers[i].cbBuffer);
+ p += outBuffersDesc.pBuffers[i].cbBuffer;
+ }
+
+ return mir_base64_encode(response, ressz);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+char* NtlmCreateResponseFromChallenge(HANDLE hSecurity, const char *szChallenge, const wchar_t *login, const wchar_t *psw, bool http, unsigned &complete)
+{
+ if (hSecurity == nullptr || ntlmCnt == 0)
+ return nullptr;
+
+ SecBufferDesc outputBufferDescriptor, inputBufferDescriptor;
+ SecBuffer outputSecurityToken, inputSecurityToken;
+ char *szOutputToken;
+
+ NtlmHandleType *hNtlm = (NtlmHandleType*)hSecurity;
+
+ Netlib_Logf(nullptr, "NtlmCreateResponseFromChallenge (%s): chl=%s {%S:%S} => %d", hNtlm->szProvider, szChallenge, login, psw, complete);
+
+ if (mir_wstrcmpi(hNtlm->szProvider, L"Basic")) {
+ bool isGSSAPI = mir_wstrcmpi(hNtlm->szProvider, L"Kerberos") == 0;
+ bool hasChallenge = szChallenge != nullptr && szChallenge[0] != '\0';
+ if (hasChallenge) {
+ size_t tokenLen;
+ uint8_t *token = (uint8_t*)mir_base64_decode(szChallenge, &tokenLen);
+ if (token == nullptr)
+ return nullptr;
+
+ if (isGSSAPI && complete)
+ return CompleteGssapi(hSecurity, token, (unsigned)tokenLen);
+
+ inputBufferDescriptor.cBuffers = 1;
+ inputBufferDescriptor.pBuffers = &inputSecurityToken;
+ inputBufferDescriptor.ulVersion = SECBUFFER_VERSION;
+ inputSecurityToken.BufferType = SECBUFFER_TOKEN;
+ inputSecurityToken.cbBuffer = (unsigned)tokenLen;
+ inputSecurityToken.pvBuffer = token;
+
+ // try to decode the domain name from the NTLM challenge
+ if (login != nullptr && login[0] != '\0' && !hNtlm->hasDomain) {
+ NtlmType2packet* pkt = (NtlmType2packet*)token;
+ if (!strncmp(pkt->sign, "NTLMSSP", 8) && pkt->type == 2) {
+
+ wchar_t* domainName = (wchar_t*)&token[pkt->targetName.offset];
+ int domainLen = pkt->targetName.len;
+
+ // Negotiate ANSI? if yes, convert the ANSI name to unicode
+ if ((pkt->flags & 1) == 0) {
+ int bufsz = MultiByteToWideChar(CP_ACP, 0, (char*)domainName, domainLen, nullptr, 0);
+ wchar_t* buf = (wchar_t*)alloca((bufsz+1) * sizeof(wchar_t));
+ domainLen = MultiByteToWideChar(CP_ACP, 0, (char*)domainName, domainLen, buf, bufsz) - 1;
+ buf[domainLen] = 0;
+ domainName = buf;
+ }
+ else domainLen /= sizeof(wchar_t);
+
+ if (domainLen) {
+ CMStringW wszNewLogin(FORMAT, L"%s\\%s", domainName, login);
+ char* szChl = NtlmCreateResponseFromChallenge(hSecurity, nullptr, wszNewLogin, psw, http, complete);
+ mir_free(szChl);
+ }
+ }
+ }
+ }
+ else {
+ if (SecIsValidHandle(&hNtlm->hClientContext))
+ DeleteSecurityContext(&hNtlm->hClientContext);
+ if (SecIsValidHandle(&hNtlm->hClientCredential))
+ FreeCredentialsHandle(&hNtlm->hClientCredential);
+
+ SEC_WINNT_AUTH_IDENTITY auth;
+
+ if (login != nullptr && login[0] != '\0') {
+ memset(&auth, 0, sizeof(auth));
+
+ Netlib_Logf(nullptr, "Security login requested, user: %S pssw: %s", login, psw ? "(exist)" : "(no psw)");
+
+ const wchar_t* loginName = login;
+ const wchar_t* domainName = wcschr(login, '\\');
+ size_t domainLen = 0;
+ size_t loginLen = mir_wstrlen(loginName);
+ if (domainName != nullptr) {
+ loginName = domainName + 1;
+ loginLen = mir_wstrlen(loginName);
+ domainLen = domainName - login;
+ domainName = login;
+ }
+ else if ((domainName = wcschr(login, '@')) != nullptr) {
+ loginName = login;
+ loginLen = domainName - login;
+ domainLen = mir_wstrlen(++domainName);
+ }
+
+ auth.User = (PWORD)loginName;
+ auth.UserLength = (ULONG)loginLen;
+ auth.Password = (PWORD)psw;
+ auth.PasswordLength = (ULONG)mir_wstrlen(psw);
+ auth.Domain = (PWORD)domainName;
+ auth.DomainLength = (ULONG)domainLen;
+ auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
+
+ hNtlm->hasDomain = domainLen != 0;
+ }
+
+ TimeStamp tokenExpiration;
+ SECURITY_STATUS sc = AcquireCredentialsHandle(nullptr, hNtlm->szProvider, SECPKG_CRED_OUTBOUND, nullptr, hNtlm->hasDomain ? &auth : nullptr, nullptr, nullptr, &hNtlm->hClientCredential, &tokenExpiration);
+ if (sc != SEC_E_OK) {
+ ReportSecError(sc, __LINE__);
+ return nullptr;
+ }
+ }
+
+ outputBufferDescriptor.cBuffers = 1;
+ outputBufferDescriptor.pBuffers = &outputSecurityToken;
+ outputBufferDescriptor.ulVersion = SECBUFFER_VERSION;
+ outputSecurityToken.BufferType = SECBUFFER_TOKEN;
+ outputSecurityToken.cbBuffer = hNtlm->cbMaxToken;
+ outputSecurityToken.pvBuffer = alloca(outputSecurityToken.cbBuffer);
+
+ ULONG contextAttributes;
+ TimeStamp tokenExpiration;
+ SECURITY_STATUS sc = InitializeSecurityContext(&hNtlm->hClientCredential,
+ hasChallenge ? &hNtlm->hClientContext : nullptr,
+ hNtlm->szPrincipal, isGSSAPI ? ISC_REQ_MUTUAL_AUTH | ISC_REQ_STREAM : 0, 0, SECURITY_NATIVE_DREP,
+ hasChallenge ? &inputBufferDescriptor : nullptr, 0, &hNtlm->hClientContext,
+ &outputBufferDescriptor, &contextAttributes, &tokenExpiration);
+ Netlib_Logf(nullptr, "InitializeSecurityContext(%S): 0x%x", hNtlm->szProvider, sc);
+
+ complete = (sc != SEC_I_COMPLETE_AND_CONTINUE && sc != SEC_I_CONTINUE_NEEDED);
+ if (sc == SEC_I_COMPLETE_NEEDED || sc == SEC_I_COMPLETE_AND_CONTINUE) {
+ sc = CompleteAuthToken(&hNtlm->hClientContext, &outputBufferDescriptor);
+ Netlib_Logf(nullptr, "CompleteAuthToken: 0x%x", sc);
+ }
+
+ if (sc != SEC_E_OK && sc != SEC_I_CONTINUE_NEEDED) {
+ ReportSecError(sc, __LINE__);
+ return nullptr;
+ }
+
+ szOutputToken = mir_base64_encode(outputSecurityToken.pvBuffer, outputSecurityToken.cbBuffer);
+ }
+ else {
+ if (!login || !psw)
+ return nullptr;
+
+ T2Utf szAuth(CMStringW(FORMAT, L"%s:%s", login, psw));
+ szOutputToken = mir_base64_encode(szAuth.get(), mir_strlen(szAuth));
+ complete = true;
+ }
+
+ if (szOutputToken == nullptr)
+ return nullptr;
+
+ if (!http)
+ return szOutputToken;
+
+ CMStringA szResult(FORMAT, "%S %s", hNtlm->szProvider, szOutputToken);
+ mir_free(szOutputToken);
+ return szResult.Detach();
+}
+
+MIR_APP_DLL(char*) Netlib_NtlmCreateResponse(HANDLE hProvider, const char *szChallenge, wchar_t *pwszLogin, wchar_t *pwszPassword, unsigned &complete)
+{
+ return NtlmCreateResponseFromChallenge(hProvider, szChallenge, pwszLogin, pwszPassword, false, complete);
+}
diff --git a/src/mir_app/src/netlib_sock.cpp b/src/mir_app/src/netlib_sock.cpp index ebd7f3a7ea..032bfed993 100644 --- a/src/mir_app/src/netlib_sock.cpp +++ b/src/mir_app/src/netlib_sock.cpp @@ -1,287 +1,287 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -extern HANDLE hSendEvent, hRecvEvent; - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Netlib_Send(HNETLIBCONN nlc, const char *buf, int len, int flags) -{ - if (!NetlibEnterNestedCS(nlc, NLNCS_SEND)) - return SOCKET_ERROR; - - int result; - Netlib_Dump(nlc, (uint8_t*)buf, len, true, flags); - if (nlc->hSsl) - result = Netlib_SslWrite(nlc->hSsl, buf, len); - else - result = send(nlc->s, buf, len, flags & 0xFFFF); - - NetlibLeaveNestedCS(&nlc->ncsSend); - - NETLIBNOTIFY nln = { buf, len, flags, result }; - NotifyFastHook(hSendEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user); - - return result; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Netlib_Recv(HNETLIBCONN nlc, char *buf, int len, int flags) -{ - if (!NetlibEnterNestedCS(nlc, NLNCS_RECV)) - return SOCKET_ERROR; - - int recvResult; - if (!nlc->foreBuf.isEmpty()) { - recvResult = min(len, (int)nlc->foreBuf.length()); - memcpy(buf, nlc->foreBuf.data(), recvResult); - nlc->foreBuf.remove(recvResult); - } - else if (nlc->hSsl) - recvResult = Netlib_SslRead(nlc->hSsl, buf, len, (flags & MSG_PEEK) != 0); - else - recvResult = recv(nlc->s, buf, len, flags & 0xFFFF); - - NetlibLeaveNestedCS(&nlc->ncsRecv); - if (recvResult <= 0) - return recvResult; - - Netlib_Dump(nlc, (uint8_t*)buf, recvResult, false, flags); - - if ((flags & MSG_PEEK) == 0) { - NETLIBNOTIFY nln = { buf, len, flags, recvResult }; - NotifyFastHook(hRecvEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user); - } - return recvResult; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static int ConnectionListToSocketList(const HNETLIBCONN *hConns, fd_set *fd, int& pending) -{ - FD_ZERO(fd); - for (int i = 0; hConns[i] && hConns[i] != INVALID_HANDLE_VALUE && i < FD_SETSIZE; i++) { - NetlibConnection *nlcCheck = hConns[i]; - if (nlcCheck->handleType != NLH_CONNECTION && nlcCheck->handleType != NLH_BOUNDPORT) { - SetLastError(ERROR_INVALID_DATA); - return 0; - } - FD_SET(nlcCheck->s, fd); - if (!nlcCheck->foreBuf.isEmpty() || Netlib_SslPending(nlcCheck->hSsl)) - pending++; - } - return 1; -} - -MIR_APP_DLL(int) Netlib_Select(NETLIBSELECT *nls) -{ - if (nls == nullptr) { - SetLastError(ERROR_INVALID_PARAMETER); - return SOCKET_ERROR; - } - - int pending = 0; - fd_set readfd, writefd, exceptfd; - { - mir_cslock lock(csConnectionHeader); - if (!ConnectionListToSocketList(nls->hReadConns, &readfd, pending) - || !ConnectionListToSocketList(nls->hWriteConns, &writefd, pending) - || !ConnectionListToSocketList(nls->hExceptConns, &exceptfd, pending)) { - return SOCKET_ERROR; - } - } - if (pending) - return 1; - - TIMEVAL tv; - tv.tv_sec = nls->dwTimeout / 1000; - tv.tv_usec = (nls->dwTimeout % 1000) * 1000; - return select(0, &readfd, &writefd, &exceptfd, nls->dwTimeout == INFINITE ? nullptr : &tv); -} - -MIR_APP_DLL(int) Netlib_SelectEx(NETLIBSELECTEX *nls) -{ - if (nls == nullptr) { - SetLastError(ERROR_INVALID_PARAMETER); - return SOCKET_ERROR; - } - - int pending = 0; - fd_set readfd, writefd, exceptfd; - - TIMEVAL tv; - tv.tv_sec = nls->dwTimeout / 1000; - tv.tv_usec = (nls->dwTimeout % 1000) * 1000; - { - mir_cslock lock(csConnectionHeader); - - if (!ConnectionListToSocketList(nls->hReadConns, &readfd, pending) - || !ConnectionListToSocketList(nls->hWriteConns, &writefd, pending) - || !ConnectionListToSocketList(nls->hExceptConns, &exceptfd, pending)) { - return SOCKET_ERROR; - } - } - - int rc = (pending) ? pending : select(0, &readfd, &writefd, &exceptfd, nls->dwTimeout == INFINITE ? nullptr : &tv); - - mir_cslock lock(csConnectionHeader); - /* go thru each passed HCONN array and grab its socket handle, then give it to FD_ISSET() - to see if an event happened for that socket, if it has it will be returned as TRUE (otherwise not) - This happens for read/write/except */ - NetlibConnection *conn = nullptr; - int j; - for (j = 0; j < FD_SETSIZE; j++) { - conn = (NetlibConnection*)nls->hReadConns[j]; - if (conn == nullptr || conn == INVALID_HANDLE_VALUE) break; - - if (Netlib_SslPending(conn->hSsl)) - nls->hReadStatus[j] = TRUE; - nls->hReadStatus[j] = FD_ISSET(conn->s, &readfd); - } - - for (j = 0; j < FD_SETSIZE; j++) { - conn = (NetlibConnection*)nls->hWriteConns[j]; - if (conn == nullptr || conn == INVALID_HANDLE_VALUE) break; - nls->hWriteStatus[j] = FD_ISSET(conn->s, &writefd); - } - - for (j = 0; j < FD_SETSIZE; j++) { - conn = (NetlibConnection*)nls->hExceptConns[j]; - if (conn == nullptr || conn == INVALID_HANDLE_VALUE) break; - nls->hExceptStatus[j] = FD_ISSET(conn->s, &exceptfd); - } - return rc; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(bool) Netlib_StringToAddress(const char *str, SOCKADDR_INET_M *addr) -{ - if (!str) return false; - - int len = sizeof(SOCKADDR_INET_M); - return !WSAStringToAddressA((char*)str, AF_INET6, nullptr, (PSOCKADDR)addr, &len); -} - -MIR_APP_DLL(char*) Netlib_AddressToString(sockaddr_in *addr) -{ - char saddr[128]; - DWORD len = sizeof(saddr); - if (!WSAAddressToStringA((PSOCKADDR)addr, sizeof(*addr), nullptr, saddr, &len)) - return mir_strdup(saddr); - - if (addr->sin_family == AF_INET) { - char *szIp = inet_ntoa(addr->sin_addr); - if (addr->sin_port != 0) { - mir_snprintf(saddr, "%s:%d", szIp, addr->sin_port); - return mir_strdup(saddr); - } - return mir_strdup(szIp); - } - return nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Netlib_GetConnectionInfo(HNETLIBCONN nlc, NETLIBCONNINFO *connInfo) -{ - if (!nlc || !connInfo) - return 1; - - sockaddr_in sin = { 0 }; - int len = sizeof(sin); - if (!getsockname(nlc->s, (PSOCKADDR)&sin, &len)) { - connInfo->wPort = ntohs(sin.sin_port); - connInfo->dwIpv4 = sin.sin_family == AF_INET ? htonl(sin.sin_addr.s_addr) : 0; - strncpy_s(connInfo->szIpPort, ptrA(Netlib_AddressToString(&sin)), _TRUNCATE); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -inline bool IsAddrGlobal(const IN6_ADDR *a) -{ - unsigned char High = a->s6_bytes[0] & 0xf0; - return High != 0 && High != 0xf0; -} - -MIR_APP_DLL(NETLIBIPLIST*) Netlib_GetMyIp(bool bGlobalOnly) -{ - addrinfo *air = nullptr, *ai, hints = { 0 }; - const char *szMyHost = ""; - - hints.ai_family = AF_UNSPEC; - hints.ai_flags = AI_PASSIVE; - - if (GetAddrInfoA(szMyHost, nullptr, &hints, &air)) - return nullptr; - - unsigned n = 0; - for (ai = air; ai; ai = ai->ai_next) { - SOCKADDR_INET_M *iaddr = (SOCKADDR_INET_M*)ai->ai_addr; - if (ai->ai_family == AF_INET || (ai->ai_family == AF_INET6 && (!bGlobalOnly || IsAddrGlobal(&iaddr->Ipv6.sin6_addr)))) - ++n; - } - - NETLIBIPLIST *addr = (NETLIBIPLIST*)mir_calloc(n * 64 + 4); - addr->cbNum = n; - - unsigned i = 0; - for (ai = air; ai; ai = ai->ai_next) { - sockaddr_in6 *iaddr = (sockaddr_in6*)ai->ai_addr; - if (ai->ai_family == AF_INET || (ai->ai_family == AF_INET6 && (!bGlobalOnly || IsAddrGlobal(&iaddr->sin6_addr)))) { - char *szIp = Netlib_AddressToString((sockaddr_in*)iaddr); - if (szIp) - strncpy_s(addr->szIp[i++], szIp, _TRUNCATE); - mir_free(szIp); - } - } - FreeAddrInfoA(air); - return addr; -} - -static NETLIBIPLIST* GetMyIpv4(void) -{ - char hostname[256] = ""; - - gethostname(hostname, sizeof(hostname)); - PHOSTENT he = gethostbyname(hostname); - - unsigned n; - for (n = 0; he->h_addr_list[n]; ++n) - ; - - NETLIBIPLIST *addr = (NETLIBIPLIST*)mir_calloc(n * 64 + 4); - addr->cbNum = n; - - for (unsigned i = 0; i < n; i++) - strncpy_s(addr->szIp[i], inet_ntoa(*(PIN_ADDR)he->h_addr_list[i]), _TRUNCATE); - - return addr; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+extern HANDLE hSendEvent, hRecvEvent;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Netlib_Send(HNETLIBCONN nlc, const char *buf, int len, int flags)
+{
+ if (!NetlibEnterNestedCS(nlc, NLNCS_SEND))
+ return SOCKET_ERROR;
+
+ int result;
+ Netlib_Dump(nlc, (uint8_t*)buf, len, true, flags);
+ if (nlc->hSsl)
+ result = Netlib_SslWrite(nlc->hSsl, buf, len);
+ else
+ result = send(nlc->s, buf, len, flags & 0xFFFF);
+
+ NetlibLeaveNestedCS(&nlc->ncsSend);
+
+ NETLIBNOTIFY nln = { buf, len, flags, result };
+ NotifyFastHook(hSendEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user);
+
+ return result;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Netlib_Recv(HNETLIBCONN nlc, char *buf, int len, int flags)
+{
+ if (!NetlibEnterNestedCS(nlc, NLNCS_RECV))
+ return SOCKET_ERROR;
+
+ int recvResult;
+ if (!nlc->foreBuf.isEmpty()) {
+ recvResult = min(len, (int)nlc->foreBuf.length());
+ memcpy(buf, nlc->foreBuf.data(), recvResult);
+ nlc->foreBuf.remove(recvResult);
+ }
+ else if (nlc->hSsl)
+ recvResult = Netlib_SslRead(nlc->hSsl, buf, len, (flags & MSG_PEEK) != 0);
+ else
+ recvResult = recv(nlc->s, buf, len, flags & 0xFFFF);
+
+ NetlibLeaveNestedCS(&nlc->ncsRecv);
+ if (recvResult <= 0)
+ return recvResult;
+
+ Netlib_Dump(nlc, (uint8_t*)buf, recvResult, false, flags);
+
+ if ((flags & MSG_PEEK) == 0) {
+ NETLIBNOTIFY nln = { buf, len, flags, recvResult };
+ NotifyFastHook(hRecvEvent, (WPARAM)&nln, (LPARAM)&nlc->nlu->user);
+ }
+ return recvResult;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int ConnectionListToSocketList(const HNETLIBCONN *hConns, fd_set *fd, int& pending)
+{
+ FD_ZERO(fd);
+ for (int i = 0; hConns[i] && hConns[i] != INVALID_HANDLE_VALUE && i < FD_SETSIZE; i++) {
+ NetlibConnection *nlcCheck = hConns[i];
+ if (nlcCheck->handleType != NLH_CONNECTION && nlcCheck->handleType != NLH_BOUNDPORT) {
+ SetLastError(ERROR_INVALID_DATA);
+ return 0;
+ }
+ FD_SET(nlcCheck->s, fd);
+ if (!nlcCheck->foreBuf.isEmpty() || Netlib_SslPending(nlcCheck->hSsl))
+ pending++;
+ }
+ return 1;
+}
+
+MIR_APP_DLL(int) Netlib_Select(NETLIBSELECT *nls)
+{
+ if (nls == nullptr) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SOCKET_ERROR;
+ }
+
+ int pending = 0;
+ fd_set readfd, writefd, exceptfd;
+ {
+ mir_cslock lock(csConnectionHeader);
+ if (!ConnectionListToSocketList(nls->hReadConns, &readfd, pending)
+ || !ConnectionListToSocketList(nls->hWriteConns, &writefd, pending)
+ || !ConnectionListToSocketList(nls->hExceptConns, &exceptfd, pending)) {
+ return SOCKET_ERROR;
+ }
+ }
+ if (pending)
+ return 1;
+
+ TIMEVAL tv;
+ tv.tv_sec = nls->dwTimeout / 1000;
+ tv.tv_usec = (nls->dwTimeout % 1000) * 1000;
+ return select(0, &readfd, &writefd, &exceptfd, nls->dwTimeout == INFINITE ? nullptr : &tv);
+}
+
+MIR_APP_DLL(int) Netlib_SelectEx(NETLIBSELECTEX *nls)
+{
+ if (nls == nullptr) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return SOCKET_ERROR;
+ }
+
+ int pending = 0;
+ fd_set readfd, writefd, exceptfd;
+
+ TIMEVAL tv;
+ tv.tv_sec = nls->dwTimeout / 1000;
+ tv.tv_usec = (nls->dwTimeout % 1000) * 1000;
+ {
+ mir_cslock lock(csConnectionHeader);
+
+ if (!ConnectionListToSocketList(nls->hReadConns, &readfd, pending)
+ || !ConnectionListToSocketList(nls->hWriteConns, &writefd, pending)
+ || !ConnectionListToSocketList(nls->hExceptConns, &exceptfd, pending)) {
+ return SOCKET_ERROR;
+ }
+ }
+
+ int rc = (pending) ? pending : select(0, &readfd, &writefd, &exceptfd, nls->dwTimeout == INFINITE ? nullptr : &tv);
+
+ mir_cslock lock(csConnectionHeader);
+ /* go thru each passed HCONN array and grab its socket handle, then give it to FD_ISSET()
+ to see if an event happened for that socket, if it has it will be returned as TRUE (otherwise not)
+ This happens for read/write/except */
+ NetlibConnection *conn = nullptr;
+ int j;
+ for (j = 0; j < FD_SETSIZE; j++) {
+ conn = (NetlibConnection*)nls->hReadConns[j];
+ if (conn == nullptr || conn == INVALID_HANDLE_VALUE) break;
+
+ if (Netlib_SslPending(conn->hSsl))
+ nls->hReadStatus[j] = TRUE;
+ nls->hReadStatus[j] = FD_ISSET(conn->s, &readfd);
+ }
+
+ for (j = 0; j < FD_SETSIZE; j++) {
+ conn = (NetlibConnection*)nls->hWriteConns[j];
+ if (conn == nullptr || conn == INVALID_HANDLE_VALUE) break;
+ nls->hWriteStatus[j] = FD_ISSET(conn->s, &writefd);
+ }
+
+ for (j = 0; j < FD_SETSIZE; j++) {
+ conn = (NetlibConnection*)nls->hExceptConns[j];
+ if (conn == nullptr || conn == INVALID_HANDLE_VALUE) break;
+ nls->hExceptStatus[j] = FD_ISSET(conn->s, &exceptfd);
+ }
+ return rc;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(bool) Netlib_StringToAddress(const char *str, SOCKADDR_INET_M *addr)
+{
+ if (!str) return false;
+
+ int len = sizeof(SOCKADDR_INET_M);
+ return !WSAStringToAddressA((char*)str, AF_INET6, nullptr, (PSOCKADDR)addr, &len);
+}
+
+MIR_APP_DLL(char*) Netlib_AddressToString(sockaddr_in *addr)
+{
+ char saddr[128];
+ DWORD len = sizeof(saddr);
+ if (!WSAAddressToStringA((PSOCKADDR)addr, sizeof(*addr), nullptr, saddr, &len))
+ return mir_strdup(saddr);
+
+ if (addr->sin_family == AF_INET) {
+ char *szIp = inet_ntoa(addr->sin_addr);
+ if (addr->sin_port != 0) {
+ mir_snprintf(saddr, "%s:%d", szIp, addr->sin_port);
+ return mir_strdup(saddr);
+ }
+ return mir_strdup(szIp);
+ }
+ return nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Netlib_GetConnectionInfo(HNETLIBCONN nlc, NETLIBCONNINFO *connInfo)
+{
+ if (!nlc || !connInfo)
+ return 1;
+
+ sockaddr_in sin = { 0 };
+ int len = sizeof(sin);
+ if (!getsockname(nlc->s, (PSOCKADDR)&sin, &len)) {
+ connInfo->wPort = ntohs(sin.sin_port);
+ connInfo->dwIpv4 = sin.sin_family == AF_INET ? htonl(sin.sin_addr.s_addr) : 0;
+ strncpy_s(connInfo->szIpPort, ptrA(Netlib_AddressToString(&sin)), _TRUNCATE);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+inline bool IsAddrGlobal(const IN6_ADDR *a)
+{
+ unsigned char High = a->s6_bytes[0] & 0xf0;
+ return High != 0 && High != 0xf0;
+}
+
+MIR_APP_DLL(NETLIBIPLIST*) Netlib_GetMyIp(bool bGlobalOnly)
+{
+ addrinfo *air = nullptr, *ai, hints = { 0 };
+ const char *szMyHost = "";
+
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE;
+
+ if (GetAddrInfoA(szMyHost, nullptr, &hints, &air))
+ return nullptr;
+
+ unsigned n = 0;
+ for (ai = air; ai; ai = ai->ai_next) {
+ SOCKADDR_INET_M *iaddr = (SOCKADDR_INET_M*)ai->ai_addr;
+ if (ai->ai_family == AF_INET || (ai->ai_family == AF_INET6 && (!bGlobalOnly || IsAddrGlobal(&iaddr->Ipv6.sin6_addr))))
+ ++n;
+ }
+
+ NETLIBIPLIST *addr = (NETLIBIPLIST*)mir_calloc(n * 64 + 4);
+ addr->cbNum = n;
+
+ unsigned i = 0;
+ for (ai = air; ai; ai = ai->ai_next) {
+ sockaddr_in6 *iaddr = (sockaddr_in6*)ai->ai_addr;
+ if (ai->ai_family == AF_INET || (ai->ai_family == AF_INET6 && (!bGlobalOnly || IsAddrGlobal(&iaddr->sin6_addr)))) {
+ char *szIp = Netlib_AddressToString((sockaddr_in*)iaddr);
+ if (szIp)
+ strncpy_s(addr->szIp[i++], szIp, _TRUNCATE);
+ mir_free(szIp);
+ }
+ }
+ FreeAddrInfoA(air);
+ return addr;
+}
+
+static NETLIBIPLIST* GetMyIpv4(void)
+{
+ char hostname[256] = "";
+
+ gethostname(hostname, sizeof(hostname));
+ PHOSTENT he = gethostbyname(hostname);
+
+ unsigned n;
+ for (n = 0; he->h_addr_list[n]; ++n)
+ ;
+
+ NETLIBIPLIST *addr = (NETLIBIPLIST*)mir_calloc(n * 64 + 4);
+ addr->cbNum = n;
+
+ for (unsigned i = 0; i < n; i++)
+ strncpy_s(addr->szIp[i], inet_ntoa(*(PIN_ADDR)he->h_addr_list[i]), _TRUNCATE);
+
+ return addr;
+}
diff --git a/src/mir_app/src/netlib_ssl.cpp b/src/mir_app/src/netlib_ssl.cpp index d2ab355ec6..14d2ca5d13 100644 --- a/src/mir_app/src/netlib_ssl.cpp +++ b/src/mir_app/src/netlib_ssl.cpp @@ -1,465 +1,465 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -#include <openssl/ssl.h> -#include <openssl/err.h> -#include <openssl/rand.h> - -static bool bSslInitDone; - -enum SocketState -{ - sockOpen, - sockClosed, - sockError -}; - -struct SslHandle : public MZeroedObject -{ - ~SslHandle() - { - if (session) - SSL_free(session); - } - - SOCKET s; - SSL *session; - SocketState state; -}; - -static SSL_CTX *g_ctx; -static mir_cs csSsl; - -static void dump_error(SSL *session, int err) -{ - err = SSL_get_error(session, err); - - char buf[100]; - ERR_error_string_n(err, buf, sizeof(buf)); - Netlib_Logf(nullptr, "SSL negotiation failure: %s (%d)", buf, err); -} - -const char* SSL_GetCipherName(SslHandle *ssl) -{ - if (!ssl || !ssl->session) - return nullptr; - - return SSL_CIPHER_get_name(SSL_get_current_cipher(ssl->session)); -} - -static void ReportSslError(SECURITY_STATUS scRet, int line, bool = false) -{ - CMStringW tszMsg(FORMAT, L"SSL connection failure(%x %u) :", scRet, line); - - switch (scRet) { - case 0: - case ERROR_NOT_READY: - return; - - case SEC_E_INVALID_TOKEN: - tszMsg += TranslateW_LP(L"Client cannot decode host message. Possible causes: host does not support SSL or requires not existing security package"); - break; - - case CERT_E_CN_NO_MATCH: - case SEC_E_WRONG_PRINCIPAL: - tszMsg += TranslateW_LP(L"Host we are connecting to is not the one certificate was issued for"); - break; - - default: - wchar_t szMsgBuf[256]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, scRet, LANG_USER_DEFAULT, szMsgBuf, _countof(szMsgBuf), nullptr); - tszMsg += szMsgBuf; - } - - Netlib_LogfW(nullptr, tszMsg); - - SetLastError(scRet); - PUShowMessageW(tszMsg.GetBuffer(), SM_WARNING); -} - -static PCCERT_CONTEXT SSL_X509ToCryptCert(X509 * x509) -{ - unsigned char *buf = nullptr; - PCCERT_CONTEXT pCertContext = nullptr; - - int len = i2d_X509(x509, &buf); - if ((len >= 0) && buf) { - pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, buf, len); - - CRYPTO_free(buf, __FILE__, __LINE__); - } - return pCertContext; -} - -static PCCERT_CONTEXT SSL_CertChainToCryptAnchor(SSL* session) -{ - /* convert the active certificate chain provided in the handshake of 'session' into - the format used by CryptAPI. - */ - PCCERT_CONTEXT anchor = nullptr; - // create cert store - HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, nullptr); - - if (store) { - X509 *server_cert = SSL_get_peer_certificate(session); - if (server_cert) { - // add the server's cert first, to make sure CryptAPI builds the correct chain - PCCERT_CONTEXT primary_cert; - BOOL ok = CertAddCertificateContextToStore(store, SSL_X509ToCryptCert(server_cert), CERT_STORE_ADD_ALWAYS, &primary_cert); - if (ok && primary_cert) { - // add all remaining certs to store (note: stack needs not be freed, it is not a copy) - STACK_OF(X509) *server_chain = SSL_get_peer_cert_chain(session); - if (server_chain) { - for (int i = 0; i < OPENSSL_sk_num((OPENSSL_STACK *)server_chain); i++) { - X509 *next_cert = (X509 *)OPENSSL_sk_value((OPENSSL_STACK *)server_chain, i); - CertAddCertificateContextToStore(store, SSL_X509ToCryptCert(next_cert), CERT_STORE_ADD_USE_EXISTING, nullptr); - } - } - - // return primary cert; MUST be freed by caller which will free the associated store - anchor = primary_cert; - } - else { - if (primary_cert) - CertFreeCertificateContext(primary_cert); - } - - X509_free(server_cert); - } - - CertCloseStore(store, 0); - } - - return anchor; -} - -static LPSTR rgszUsages[] = -{ - szOID_PKIX_KP_SERVER_AUTH, - szOID_SERVER_GATED_CRYPTO, - szOID_SGC_NETSCAPE -}; - -static bool VerifyCertificate(SslHandle *ssl, PCSTR pszServerName, uint32_t dwCertFlags) -{ - uint32_t scRet; - - ptrW pwszServerName(mir_a2u(pszServerName)); - - HTTPSPolicyCallbackData polHttps = {}; - CERT_CHAIN_POLICY_PARA PolicyPara = {}; - CERT_CHAIN_POLICY_STATUS PolicyStatus = {}; - CERT_CHAIN_PARA ChainPara = {}; - - PCCERT_CHAIN_CONTEXT pChainContext = nullptr; - PCCERT_CONTEXT pServerCert = SSL_CertChainToCryptAnchor(ssl->session); - if (pServerCert == nullptr) { - scRet = SEC_E_WRONG_PRINCIPAL; - goto cleanup; - } - - ChainPara.cbSize = sizeof(ChainPara); - ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; - ChainPara.RequestedUsage.Usage.cUsageIdentifier = _countof(rgszUsages); - ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; - - if (!CertGetCertificateChain(nullptr, pServerCert, nullptr, pServerCert->hCertStore, &ChainPara, 0, nullptr, &pChainContext)) { - scRet = GetLastError(); - goto cleanup; - } - - polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); - polHttps.dwAuthType = AUTHTYPE_SERVER; - polHttps.fdwChecks = dwCertFlags; - polHttps.pwszServerName = pwszServerName; - - PolicyPara.cbSize = sizeof(PolicyPara); - PolicyPara.pvExtraPolicyPara = &polHttps; - - PolicyStatus.cbSize = sizeof(PolicyStatus); - - if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus)) { - scRet = GetLastError(); - goto cleanup; - } - - if (PolicyStatus.dwError) { - scRet = PolicyStatus.dwError; - goto cleanup; - } - - scRet = SEC_E_OK; - -cleanup: - if (pChainContext) - CertFreeCertificateChain(pChainContext); - if (pServerCert) - CertFreeCertificateContext(pServerCert); - - ReportSslError(scRet, __LINE__, true); - return scRet == SEC_E_OK; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// negotiate SSL session, verify cert, return NULL if failed - -MIR_APP_DLL(HSSL) Netlib_SslConnect(SOCKET s, const char* host, int verify) -{ - std::unique_ptr<SslHandle> ssl(new SslHandle()); - ssl->s = s; - { - mir_cslock lck(csSsl); - ssl->session = SSL_new(g_ctx); - } - - if (!ssl->session) { - Netlib_Logf(nullptr, "SSL setup failure: session"); - return false; - } - SSL_set_fd(ssl->session, ssl->s); - - SSL_set_tlsext_host_name(ssl->session, host); - - int err = SSL_connect(ssl->session); - if (err != 1) { - dump_error(ssl->session, err); - return nullptr; - } - - if (verify) { - uint32_t dwFlags = 0; - if (!host || inet_addr(host) != INADDR_NONE) - dwFlags |= 0x00001000; - if (!VerifyCertificate(ssl.get(), host, dwFlags)) - return nullptr; - } - - return ssl.release(); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// return true if there is either unsend or buffered received data (ie. after peek) - -MIR_APP_DLL(BOOL) Netlib_SslPending(HSSL ssl) -{ - return ssl && ssl->session && (SSL_pending(ssl->session) > 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// reads number of bytes, keeps in buffer if peek != 0 - -MIR_APP_DLL(int) Netlib_SslRead(HSSL ssl, char *buf, int num, int peek) -{ - if (!ssl || !ssl->session) return SOCKET_ERROR; - if (num <= 0) return 0; - - int err = 0; - if (peek) - err = SSL_peek(ssl->session, buf, num); - else - err = SSL_read(ssl->session, buf, num); - - if (err <= 0) { - int err2 = SSL_get_error(ssl->session, err); - if (err2 == SSL_ERROR_ZERO_RETURN) { - Netlib_Logf(nullptr, "SSL connection gracefully closed"); - ssl->state = sockClosed; - return 0; - } - - int err3 = ERR_get_error(); - if (err3) { - Netlib_Logf(nullptr, "SSL failure recieving data (%d, %d, %d, %d)", err, err2, err3, WSAGetLastError()); - ssl->state = sockError; - return SOCKET_ERROR; - } - } - - return err; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// writes data to the SSL socket - -MIR_APP_DLL(int) Netlib_SslWrite(HSSL ssl, const char *buf, int num) -{ - if (!ssl || !ssl->session) - return SOCKET_ERROR; - if (num <= 0) - return 0; - - int err = SSL_write(ssl->session, buf, num); - if (err > 0) - return err; - - int err2 = SSL_get_error(ssl->session, err); - switch (err2) { - case SSL_ERROR_ZERO_RETURN: - Netlib_Logf(nullptr, "SSL connection gracefully closed"); - ssl->state = sockClosed; - break; - - default: - Netlib_Logf(nullptr, "SSL failure sending data (%d, %d, %d)", err, err2, WSAGetLastError()); - ssl->state = sockError; - return SOCKET_ERROR; - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// closes SSL session, but keeps socket open - -MIR_APP_DLL(void) Netlib_SslShutdown(HSSL ssl) -{ - if (ssl && ssl->session) { - SOCKET s = SSL_get_fd(ssl->session); - if (s != -1) - shutdown(s, SD_BOTH); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// frees all data associated with the SSL socket - -MIR_APP_DLL(void) Netlib_SslFree(HSSL ssl) -{ - delete ssl; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// makes connection SSL -// returns 0 on failure / 1 on success - -MIR_APP_DLL(int) Netlib_StartSsl(HNETLIBCONN hConnection, const char *szHost) -{ - NetlibConnection *nlc = (NetlibConnection*)hConnection; - if (nlc == nullptr) - return 0; - - NetlibUser *nlu = nlc->nlu; - if (szHost == nullptr) - szHost = nlc->url.szHost; - szHost = NEWSTR_ALLOCA(szHost); - - Netlib_Logf(nlu, "(%d %s) Starting SSL/TLS negotiation", int(nlc->s), szHost); - - nlc->hSsl = Netlib_SslConnect(nlc->s, szHost, nlu->settings.validateSSL); - if (nlc->hSsl == nullptr) - Netlib_Logf(nlu, "(%d %s) Failure to negotiate SSL/TLS connection", int(nlc->s), szHost); - else - Netlib_Logf(nlu, "(%d %s) SSL/TLS negotiation successful", int(nlc->s), szHost); - - return nlc->hSsl != nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// gets TLS channel binging data for a socket - -static char TLS13_Label[] = "EXPORTER-Channel-Binding"; - -MIR_APP_DLL(void*) Netlib_GetTlsUnique(HNETLIBCONN nlc, int &cbLen, int &tlsVer) -{ - if (nlc == nullptr || nlc->hSsl == nullptr) - return nullptr; - - char buf[1000]; - auto *pszVersion = SSL_get_version(nlc->hSsl->session); - if (tlsVer && !mir_strcmp(pszVersion, "TLSv1.3")) { - int res = SSL_export_keying_material(nlc->hSsl->session, - (uint8_t *)buf, 32, TLS13_Label, sizeof(TLS13_Label) - 1, 0, 0, 0); - if (res == 1) { - tlsVer = 13; - void *pBuf = mir_alloc(cbLen = 32); - memcpy(pBuf, buf, cbLen); - return pBuf; - } - } - - size_t len = SSL_get_finished(nlc->hSsl->session, buf, sizeof(buf)); - if (len == 0) - return nullptr; - - tlsVer = 12; - cbLen = (int)len; - void *pBuf = mir_alloc(len); - memcpy(pBuf, buf, len); - return pBuf; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// module entry point - -bool OpenSsl_Init(void) -{ - /* Load Library Pointers */ - if (bSslInitDone) - return true; - - if (!bSslInitDone) { // init OpenSSL - SSL_library_init(); - SSL_load_error_strings(); - // FIXME check errors - - const SSL_METHOD *meth = TLS_client_method(); - if (!meth) { - Netlib_Logf(nullptr, "SSL setup failure: client method"); - return false; - } - - g_ctx = SSL_CTX_new(meth); - if (!g_ctx) { - Netlib_Logf(nullptr, "SSL setup failure: context"); - return false; - } - - VARSW wszPemFile(L"%miranda_path%\\libs\\microsoft.pem"); - SSL_CTX_load_verify_locations(g_ctx, _T2A(wszPemFile), NULL); - - // SSL_read/write should transparently handle renegotiations - SSL_CTX_ctrl(g_ctx, SSL_CTRL_MODE, SSL_MODE_AUTO_RETRY, nullptr); - // SSL_CTX_set_quiet_shutdown(g_ctx, TRUE); - - RAND_screen(); - - bSslInitDone = true; - } - - return bSslInitDone; -} - -void OpenSsl_Unload(void) -{ - /* Load Library Pointers */ - if (!bSslInitDone) - return; - - if (g_ctx) - SSL_CTX_free(g_ctx); - - bSslInitDone = false; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+static bool bSslInitDone;
+
+enum SocketState
+{
+ sockOpen,
+ sockClosed,
+ sockError
+};
+
+struct SslHandle : public MZeroedObject
+{
+ ~SslHandle()
+ {
+ if (session)
+ SSL_free(session);
+ }
+
+ SOCKET s;
+ SSL *session;
+ SocketState state;
+};
+
+static SSL_CTX *g_ctx;
+static mir_cs csSsl;
+
+static void dump_error(SSL *session, int err)
+{
+ err = SSL_get_error(session, err);
+
+ char buf[100];
+ ERR_error_string_n(err, buf, sizeof(buf));
+ Netlib_Logf(nullptr, "SSL negotiation failure: %s (%d)", buf, err);
+}
+
+const char* SSL_GetCipherName(SslHandle *ssl)
+{
+ if (!ssl || !ssl->session)
+ return nullptr;
+
+ return SSL_CIPHER_get_name(SSL_get_current_cipher(ssl->session));
+}
+
+static void ReportSslError(SECURITY_STATUS scRet, int line, bool = false)
+{
+ CMStringW tszMsg(FORMAT, L"SSL connection failure(%x %u) :", scRet, line);
+
+ switch (scRet) {
+ case 0:
+ case ERROR_NOT_READY:
+ return;
+
+ case SEC_E_INVALID_TOKEN:
+ tszMsg += TranslateW_LP(L"Client cannot decode host message. Possible causes: host does not support SSL or requires not existing security package");
+ break;
+
+ case CERT_E_CN_NO_MATCH:
+ case SEC_E_WRONG_PRINCIPAL:
+ tszMsg += TranslateW_LP(L"Host we are connecting to is not the one certificate was issued for");
+ break;
+
+ default:
+ wchar_t szMsgBuf[256];
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, scRet, LANG_USER_DEFAULT, szMsgBuf, _countof(szMsgBuf), nullptr);
+ tszMsg += szMsgBuf;
+ }
+
+ Netlib_LogfW(nullptr, tszMsg);
+
+ SetLastError(scRet);
+ PUShowMessageW(tszMsg.GetBuffer(), SM_WARNING);
+}
+
+static PCCERT_CONTEXT SSL_X509ToCryptCert(X509 * x509)
+{
+ unsigned char *buf = nullptr;
+ PCCERT_CONTEXT pCertContext = nullptr;
+
+ int len = i2d_X509(x509, &buf);
+ if ((len >= 0) && buf) {
+ pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, buf, len);
+
+ CRYPTO_free(buf, __FILE__, __LINE__);
+ }
+ return pCertContext;
+}
+
+static PCCERT_CONTEXT SSL_CertChainToCryptAnchor(SSL* session)
+{
+ /* convert the active certificate chain provided in the handshake of 'session' into
+ the format used by CryptAPI.
+ */
+ PCCERT_CONTEXT anchor = nullptr;
+ // create cert store
+ HCERTSTORE store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, nullptr);
+
+ if (store) {
+ X509 *server_cert = SSL_get_peer_certificate(session);
+ if (server_cert) {
+ // add the server's cert first, to make sure CryptAPI builds the correct chain
+ PCCERT_CONTEXT primary_cert;
+ BOOL ok = CertAddCertificateContextToStore(store, SSL_X509ToCryptCert(server_cert), CERT_STORE_ADD_ALWAYS, &primary_cert);
+ if (ok && primary_cert) {
+ // add all remaining certs to store (note: stack needs not be freed, it is not a copy)
+ STACK_OF(X509) *server_chain = SSL_get_peer_cert_chain(session);
+ if (server_chain) {
+ for (int i = 0; i < OPENSSL_sk_num((OPENSSL_STACK *)server_chain); i++) {
+ X509 *next_cert = (X509 *)OPENSSL_sk_value((OPENSSL_STACK *)server_chain, i);
+ CertAddCertificateContextToStore(store, SSL_X509ToCryptCert(next_cert), CERT_STORE_ADD_USE_EXISTING, nullptr);
+ }
+ }
+
+ // return primary cert; MUST be freed by caller which will free the associated store
+ anchor = primary_cert;
+ }
+ else {
+ if (primary_cert)
+ CertFreeCertificateContext(primary_cert);
+ }
+
+ X509_free(server_cert);
+ }
+
+ CertCloseStore(store, 0);
+ }
+
+ return anchor;
+}
+
+static LPSTR rgszUsages[] =
+{
+ szOID_PKIX_KP_SERVER_AUTH,
+ szOID_SERVER_GATED_CRYPTO,
+ szOID_SGC_NETSCAPE
+};
+
+static bool VerifyCertificate(SslHandle *ssl, PCSTR pszServerName, uint32_t dwCertFlags)
+{
+ uint32_t scRet;
+
+ ptrW pwszServerName(mir_a2u(pszServerName));
+
+ HTTPSPolicyCallbackData polHttps = {};
+ CERT_CHAIN_POLICY_PARA PolicyPara = {};
+ CERT_CHAIN_POLICY_STATUS PolicyStatus = {};
+ CERT_CHAIN_PARA ChainPara = {};
+
+ PCCERT_CHAIN_CONTEXT pChainContext = nullptr;
+ PCCERT_CONTEXT pServerCert = SSL_CertChainToCryptAnchor(ssl->session);
+ if (pServerCert == nullptr) {
+ scRet = SEC_E_WRONG_PRINCIPAL;
+ goto cleanup;
+ }
+
+ ChainPara.cbSize = sizeof(ChainPara);
+ ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ ChainPara.RequestedUsage.Usage.cUsageIdentifier = _countof(rgszUsages);
+ ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages;
+
+ if (!CertGetCertificateChain(nullptr, pServerCert, nullptr, pServerCert->hCertStore, &ChainPara, 0, nullptr, &pChainContext)) {
+ scRet = GetLastError();
+ goto cleanup;
+ }
+
+ polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData);
+ polHttps.dwAuthType = AUTHTYPE_SERVER;
+ polHttps.fdwChecks = dwCertFlags;
+ polHttps.pwszServerName = pwszServerName;
+
+ PolicyPara.cbSize = sizeof(PolicyPara);
+ PolicyPara.pvExtraPolicyPara = &polHttps;
+
+ PolicyStatus.cbSize = sizeof(PolicyStatus);
+
+ if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, pChainContext, &PolicyPara, &PolicyStatus)) {
+ scRet = GetLastError();
+ goto cleanup;
+ }
+
+ if (PolicyStatus.dwError) {
+ scRet = PolicyStatus.dwError;
+ goto cleanup;
+ }
+
+ scRet = SEC_E_OK;
+
+cleanup:
+ if (pChainContext)
+ CertFreeCertificateChain(pChainContext);
+ if (pServerCert)
+ CertFreeCertificateContext(pServerCert);
+
+ ReportSslError(scRet, __LINE__, true);
+ return scRet == SEC_E_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// negotiate SSL session, verify cert, return NULL if failed
+
+MIR_APP_DLL(HSSL) Netlib_SslConnect(SOCKET s, const char* host, int verify)
+{
+ std::unique_ptr<SslHandle> ssl(new SslHandle());
+ ssl->s = s;
+ {
+ mir_cslock lck(csSsl);
+ ssl->session = SSL_new(g_ctx);
+ }
+
+ if (!ssl->session) {
+ Netlib_Logf(nullptr, "SSL setup failure: session");
+ return false;
+ }
+ SSL_set_fd(ssl->session, ssl->s);
+
+ SSL_set_tlsext_host_name(ssl->session, host);
+
+ int err = SSL_connect(ssl->session);
+ if (err != 1) {
+ dump_error(ssl->session, err);
+ return nullptr;
+ }
+
+ if (verify) {
+ uint32_t dwFlags = 0;
+ if (!host || inet_addr(host) != INADDR_NONE)
+ dwFlags |= 0x00001000;
+ if (!VerifyCertificate(ssl.get(), host, dwFlags))
+ return nullptr;
+ }
+
+ return ssl.release();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// return true if there is either unsend or buffered received data (ie. after peek)
+
+MIR_APP_DLL(BOOL) Netlib_SslPending(HSSL ssl)
+{
+ return ssl && ssl->session && (SSL_pending(ssl->session) > 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// reads number of bytes, keeps in buffer if peek != 0
+
+MIR_APP_DLL(int) Netlib_SslRead(HSSL ssl, char *buf, int num, int peek)
+{
+ if (!ssl || !ssl->session) return SOCKET_ERROR;
+ if (num <= 0) return 0;
+
+ int err = 0;
+ if (peek)
+ err = SSL_peek(ssl->session, buf, num);
+ else
+ err = SSL_read(ssl->session, buf, num);
+
+ if (err <= 0) {
+ int err2 = SSL_get_error(ssl->session, err);
+ if (err2 == SSL_ERROR_ZERO_RETURN) {
+ Netlib_Logf(nullptr, "SSL connection gracefully closed");
+ ssl->state = sockClosed;
+ return 0;
+ }
+
+ int err3 = ERR_get_error();
+ if (err3) {
+ Netlib_Logf(nullptr, "SSL failure recieving data (%d, %d, %d, %d)", err, err2, err3, WSAGetLastError());
+ ssl->state = sockError;
+ return SOCKET_ERROR;
+ }
+ }
+
+ return err;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// writes data to the SSL socket
+
+MIR_APP_DLL(int) Netlib_SslWrite(HSSL ssl, const char *buf, int num)
+{
+ if (!ssl || !ssl->session)
+ return SOCKET_ERROR;
+ if (num <= 0)
+ return 0;
+
+ int err = SSL_write(ssl->session, buf, num);
+ if (err > 0)
+ return err;
+
+ int err2 = SSL_get_error(ssl->session, err);
+ switch (err2) {
+ case SSL_ERROR_ZERO_RETURN:
+ Netlib_Logf(nullptr, "SSL connection gracefully closed");
+ ssl->state = sockClosed;
+ break;
+
+ default:
+ Netlib_Logf(nullptr, "SSL failure sending data (%d, %d, %d)", err, err2, WSAGetLastError());
+ ssl->state = sockError;
+ return SOCKET_ERROR;
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// closes SSL session, but keeps socket open
+
+MIR_APP_DLL(void) Netlib_SslShutdown(HSSL ssl)
+{
+ if (ssl && ssl->session) {
+ SOCKET s = SSL_get_fd(ssl->session);
+ if (s != -1)
+ shutdown(s, SD_BOTH);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// frees all data associated with the SSL socket
+
+MIR_APP_DLL(void) Netlib_SslFree(HSSL ssl)
+{
+ delete ssl;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// makes connection SSL
+// returns 0 on failure / 1 on success
+
+MIR_APP_DLL(int) Netlib_StartSsl(HNETLIBCONN hConnection, const char *szHost)
+{
+ NetlibConnection *nlc = (NetlibConnection*)hConnection;
+ if (nlc == nullptr)
+ return 0;
+
+ NetlibUser *nlu = nlc->nlu;
+ if (szHost == nullptr)
+ szHost = nlc->url.szHost;
+ szHost = NEWSTR_ALLOCA(szHost);
+
+ Netlib_Logf(nlu, "(%d %s) Starting SSL/TLS negotiation", int(nlc->s), szHost);
+
+ nlc->hSsl = Netlib_SslConnect(nlc->s, szHost, nlu->settings.validateSSL);
+ if (nlc->hSsl == nullptr)
+ Netlib_Logf(nlu, "(%d %s) Failure to negotiate SSL/TLS connection", int(nlc->s), szHost);
+ else
+ Netlib_Logf(nlu, "(%d %s) SSL/TLS negotiation successful", int(nlc->s), szHost);
+
+ return nlc->hSsl != nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// gets TLS channel binging data for a socket
+
+static char TLS13_Label[] = "EXPORTER-Channel-Binding";
+
+MIR_APP_DLL(void*) Netlib_GetTlsUnique(HNETLIBCONN nlc, int &cbLen, int &tlsVer)
+{
+ if (nlc == nullptr || nlc->hSsl == nullptr)
+ return nullptr;
+
+ char buf[1000];
+ auto *pszVersion = SSL_get_version(nlc->hSsl->session);
+ if (tlsVer && !mir_strcmp(pszVersion, "TLSv1.3")) {
+ int res = SSL_export_keying_material(nlc->hSsl->session,
+ (uint8_t *)buf, 32, TLS13_Label, sizeof(TLS13_Label) - 1, 0, 0, 0);
+ if (res == 1) {
+ tlsVer = 13;
+ void *pBuf = mir_alloc(cbLen = 32);
+ memcpy(pBuf, buf, cbLen);
+ return pBuf;
+ }
+ }
+
+ size_t len = SSL_get_finished(nlc->hSsl->session, buf, sizeof(buf));
+ if (len == 0)
+ return nullptr;
+
+ tlsVer = 12;
+ cbLen = (int)len;
+ void *pBuf = mir_alloc(len);
+ memcpy(pBuf, buf, len);
+ return pBuf;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// module entry point
+
+bool OpenSsl_Init(void)
+{
+ /* Load Library Pointers */
+ if (bSslInitDone)
+ return true;
+
+ if (!bSslInitDone) { // init OpenSSL
+ SSL_library_init();
+ SSL_load_error_strings();
+ // FIXME check errors
+
+ const SSL_METHOD *meth = TLS_client_method();
+ if (!meth) {
+ Netlib_Logf(nullptr, "SSL setup failure: client method");
+ return false;
+ }
+
+ g_ctx = SSL_CTX_new(meth);
+ if (!g_ctx) {
+ Netlib_Logf(nullptr, "SSL setup failure: context");
+ return false;
+ }
+
+ VARSW wszPemFile(L"%miranda_path%\\libs\\microsoft.pem");
+ SSL_CTX_load_verify_locations(g_ctx, _T2A(wszPemFile), NULL);
+
+ // SSL_read/write should transparently handle renegotiations
+ SSL_CTX_ctrl(g_ctx, SSL_CTRL_MODE, SSL_MODE_AUTO_RETRY, nullptr);
+ // SSL_CTX_set_quiet_shutdown(g_ctx, TRUE);
+
+ RAND_screen();
+
+ bSslInitDone = true;
+ }
+
+ return bSslInitDone;
+}
+
+void OpenSsl_Unload(void)
+{
+ /* Load Library Pointers */
+ if (!bSslInitDone)
+ return;
+
+ if (g_ctx)
+ SSL_CTX_free(g_ctx);
+
+ bSslInitDone = false;
+}
diff --git a/src/mir_app/src/netlib_upnp.cpp b/src/mir_app/src/netlib_upnp.cpp index d9570b23af..17b7f5f463 100644 --- a/src/mir_app/src/netlib_upnp.cpp +++ b/src/mir_app/src/netlib_upnp.cpp @@ -1,813 +1,813 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -static const char search_request_msg[] = - "M-SEARCH * HTTP/1.1\r\n" - "HOST: 239.255.255.250:1900\r\n" - "MAN: \"ssdp:discover\"\r\n" - "MX: 1\r\n" - "ST: urn:schemas-upnp-org:service:%s\r\n" - "\r\n"; - -static const char xml_get_hdr[] = - "GET %s HTTP/1.1\r\n" - "HOST: %s:%u\r\n" - "ACCEPT-LANGUAGE: *\r\n\r\n"; - -static const char soap_post_hdr[] = - "POST %s HTTP/1.1\r\n" - "HOST: %s:%u\r\n" - "CONTENT-LENGTH: %u\r\n" - "CONTENT-TYPE: text/xml; charset = \"utf-8\"\r\n" - "SOAPACTION: \"%s#%s\"\r\n\r\n" - "%s"; - -static const char soap_post_hdr_m[] = - "M-POST %s URL HTTP/1.1\r\n" - "HOST: %s:%u\r\n" - "CONTENT-LENGTH: %u\r\n" - "CONTENT-TYPE: text/xml; charset = \"utf-8\"\r\n" - "MAN: \"http://schemas.xmlsoap.org/soap/envelope/\"; ns = 01\r\n" - "01-SOAPACTION: \"%s#%s\"\r\n\r\n" - "%s"; - -static const char search_device[] = - "<serviceType>%s</serviceType>"; - -static const char soap_action[] = - "<?xml version = \"1.0\"?>\r\n" - "<s:Envelope\r\n" - " xmlns:s = \"http://schemas.xmlsoap.org/soap/envelope/\"\r\n" - " s:encodingStyle = \"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" - " <s:Body>\r\n" - " <u:%s xmlns:u = \"%s\">\r\n" - "%s" - " </u:%s>\r\n" - " </s:Body>\r\n" - "</s:Envelope>\r\n"; - -static const char soap_query[] = - "<s:Envelope\r\n" - " xmlns:s = \"http://schemas.xmlsoap.org/soap/envelope/\"\r\n" - " s:encodingStyle = \"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" - " <s:Body>\r\n" - " <u:QueryStateVariable xmlns:u = \"urn:schemas-upnp-org:control-1-0\">\r\n" - " <u:varName>%s</u:varName>\r\n" - " </u:QueryStateVariable>\r\n" - " </s:Body>\r\n" - "</s:Envelope>\r\n"; - -static const char add_port_mapping[] = - " <NewRemoteHost></NewRemoteHost>\r\n" - " <NewExternalPort>%i</NewExternalPort>\r\n" - " <NewProtocol>%s</NewProtocol>\r\n" - " <NewInternalPort>%i</NewInternalPort>\r\n" - " <NewInternalClient>%s</NewInternalClient>\r\n" - " <NewEnabled>1</NewEnabled>\r\n" - " <NewPortMappingDescription>Miranda</NewPortMappingDescription>\r\n" - " <NewLeaseDuration>0</NewLeaseDuration>\r\n"; - -static const char delete_port_mapping[] = - " <NewRemoteHost></NewRemoteHost>\r\n" - " <NewExternalPort>%i</NewExternalPort>\r\n" - " <NewProtocol>%s</NewProtocol>\r\n"; - -static const char get_port_mapping[] = - " <NewPortMappingIndex>%i</NewPortMappingIndex>\r\n"; - -static bool gatewayFound; -static SOCKADDR_IN locIP; -static time_t lastDiscTime; -static int expireTime = 120; - -static int retryCount; -static SOCKET sock = INVALID_SOCKET; -static char szConnHost[256]; -static unsigned short sConnPort; - -static uint16_t *portList; -static unsigned numports, numportsAlloc; -static HANDLE portListMutex; - -static char szCtlUrl[256], szDev[256]; - -typedef enum -{ - DeviceGetReq, - ControlAction, - ControlQuery -} ReqType; - -static bool txtParseParam(char* szData, char* presearch, - char* start, char* finish, char* param, size_t size) -{ - char *cp, *cp1; - size_t len; - - *param = 0; - - if (presearch != nullptr) { - cp1 = strstr(szData, presearch); - if (cp1 == nullptr) return false; - } - else - cp1 = szData; - - cp = strstr(cp1, start); - if (cp == nullptr) return false; - cp += mir_strlen(start); - while (*cp == ' ') ++cp; - - cp1 = strstr(cp, finish); - if (cp1 == nullptr) return false; - while (*(cp1-1) == ' ' && cp1 > cp) --cp1; - - len = min((size_t)(cp1 - cp), size-1); - strncpy(param, cp, len); - param[len] = 0; - - return true; -} - -void parseURL(char* szUrl, char* szHost, unsigned short* sPort, char* szPath) -{ - char *ppath, *phost, *pport; - int sz; - - phost = strstr(szUrl, "://"); - if (phost == nullptr) phost = szUrl; - else phost += 3; - - ppath = strchr(phost, '/'); - if (ppath == nullptr) ppath = phost + mir_strlen(phost); - - pport = strchr(phost, ':'); - if (pport == nullptr) pport = ppath; - - if (szHost != nullptr) { - sz = pport - phost + 1; - if (sz > 256) sz = 256; - strncpy(szHost, phost, sz); - szHost[sz - 1] = 0; - } - - if (sPort != nullptr) { - if (pport < ppath) { - long prt = atol(pport + 1); - *sPort = prt != 0 ? (unsigned short)prt : 80; - } - else - *sPort = 80; - } - - if (szPath != nullptr) { - strncpy(szPath, ppath, 256); - szPath[255] = 0; - } -} - -static void LongLog(char* szData) -{ - Netlib_Logf(nullptr, szData); -} - -static void closeRouterConnection(void) -{ - if (sock != INVALID_SOCKET) { - closesocket(sock); - sock = INVALID_SOCKET; - } -} - -static void validateSocket(void) -{ - static const TIMEVAL tv = { 0, 0 }; - fd_set rfd; - char buf[4]; - - if (sock == INVALID_SOCKET) - return; - - FD_ZERO(&rfd); - FD_SET(sock, &rfd); - - bool opened = false; - switch (select(1, &rfd, nullptr, nullptr, &tv)) { - case 0: - opened = true; - break; - - case 1: - opened = recv(sock, buf, 1, MSG_PEEK) > 0; - break; - } - - if (!opened) - closeRouterConnection(); -} - -static int httpTransact(char* szUrl, char* szResult, int resSize, char* szActionName, ReqType reqtype) -{ - // Parse URL - char szHost[256], szPath[256], szRes[16]; - int sz = 0, res = 0; - unsigned short sPort; - bool needClose = false; - - const char* szPostHdr = soap_post_hdr; - char* szData = (char*)mir_alloc(4096); - char* szReq = nullptr; - - parseURL(szUrl, szHost, &sPort, szPath); - - if (sPort != sConnPort || _stricmp(szHost, szConnHost)) - closeRouterConnection(); - else - validateSocket(); - - while (true) { - retryCount = 0; - switch (reqtype) { - case DeviceGetReq: - sz = mir_snprintf(szData, 4096, xml_get_hdr, szPath, szHost, sPort); - break; - - case ControlAction: - { - char szData1[1024]; - - szReq = mir_strdup(szResult); - sz = mir_snprintf(szData1, soap_action, szActionName, szDev, szReq, szActionName); - sz = mir_snprintf(szData, 4096, szPostHdr, szPath, szHost, sPort, sz, szDev, szActionName, szData1); - } - break; - - case ControlQuery: - { - char szData1[1024]; - sz = mir_snprintf(szData1, soap_query, szActionName); - sz = mir_snprintf(szData, 4096, szPostHdr, szPath, szHost, sPort, sz, "urn:schemas-upnp-org:control-1-0", "QueryStateVariable", szData1); - } - break; - } - szResult[0] = 0; - { - static const TIMEVAL tv = { 6, 0 }; - static unsigned ttl = 4; - static u_long mode = 1; - fd_set rfd, wfd, efd; - SOCKADDR_IN enetaddr; - -retrycon: - if (sock == INVALID_SOCKET) { - sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - - enetaddr.sin_family = AF_INET; - enetaddr.sin_port = htons(sPort); - enetaddr.sin_addr.s_addr = inet_addr(szHost); - - // Resolve host name if needed - if (enetaddr.sin_addr.s_addr == INADDR_NONE) { - PHOSTENT he = gethostbyname(szHost); - if (he) - enetaddr.sin_addr.s_addr = *(unsigned*)he->h_addr_list[0]; - } - - Netlib_Logf(nullptr, "UPnP HTTP connection Host: %s Port: %u", szHost, sPort); - - FD_ZERO(&rfd); FD_ZERO(&wfd); FD_ZERO(&efd); - FD_SET(sock, &rfd); FD_SET(sock, &wfd); FD_SET(sock, &efd); - - // Limit the scope of the connection (does not work for - setsockopt(sock, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(unsigned)); - - // Put socket into non-blocking mode for timeout on connect - ioctlsocket(sock, FIONBIO, &mode); - - // Connect to the remote host - if (connect(sock, (SOCKADDR*)&enetaddr, sizeof(enetaddr)) == SOCKET_ERROR) { - int err = WSAGetLastError(); - - // Socket connection failed - if (err != WSAEWOULDBLOCK) { - closeRouterConnection(); - Netlib_Logf(nullptr, "UPnP connect failed %d", err); - break; - } - // Wait for socket to connect - else if (select(1, &rfd, &wfd, &efd, &tv) != 1) { - closeRouterConnection(); - Netlib_Logf(nullptr, "UPnP connect timeout"); - break; - } - else if (!FD_ISSET(sock, &wfd)) { - closeRouterConnection(); - Netlib_Logf(nullptr, "UPnP connect failed"); - break; - } - } - strncpy_s(szConnHost, szHost, _TRUNCATE); - sConnPort = sPort; - } - - if (send(sock, szData, sz, 0) != SOCKET_ERROR) { - char *hdrend = nullptr; - int acksz = 0, pktsz = 0; - - if (szActionName == nullptr) { - int len = sizeof(locIP); - getsockname(sock, (SOCKADDR*)&locIP, &len); - if (locIP.sin_addr.S_un.S_addr == 0x0100007f) { - struct hostent *he; - - gethostname(szPath, sizeof(szPath)); - he = gethostbyname(szPath); - if (he != nullptr) - locIP.sin_addr.S_un.S_addr = *(PDWORD)he->h_addr_list[0]; - } - } - - LongLog(szData); - sz = 0; - while (true) { - int bytesRecv; - - FD_ZERO(&rfd); - FD_SET(sock, &rfd); - - // Wait for the next packet - if (select(1, &rfd, nullptr, nullptr, &tv) != 1) { - closeRouterConnection(); - Netlib_Logf(nullptr, "UPnP recieve timeout"); - break; - } - - // - bytesRecv = recv(sock, &szResult[sz], resSize - sz, 0); - - // Connection closed or aborted, all data received - if (bytesRecv == 0 || bytesRecv == SOCKET_ERROR) { - closeRouterConnection(); - if ((bytesRecv == SOCKET_ERROR || sz == 0) && retryCount < 2) { - ++retryCount; - goto retrycon; - } - break; - } - - sz += bytesRecv; - - // Insert null terminator to use string functions - if (sz >= (resSize - 1)) { - szResult[resSize - 1] = 0; - break; - } - else - szResult[sz] = 0; - - // HTTP header found? - if (hdrend == nullptr) { - // Find HTTP header end - hdrend = strstr(szResult, "\r\n\r\n"); - if (hdrend == nullptr) { - hdrend = strstr(szResult, "\n\n"); - if (hdrend) hdrend += 2; - } - - else - hdrend += 4; - - if (hdrend != nullptr) { - // Get packet size if provided - if (txtParseParam(szResult, nullptr, "Content-Length:", "\n", szRes, sizeof(szRes)) || - txtParseParam(szResult, nullptr, "CONTENT-LENGTH:", "\n", szRes, sizeof(szRes))) { - // Add size of HTTP header to the packet size to compute full transmission size - pktsz = atol(ltrimp(szRes)) + (hdrend - szResult); - } - // Get encoding type if provided - else if (txtParseParam(szResult, nullptr, "Transfer-Encoding:", "\n", szRes, sizeof(szRes))) { - if (_stricmp(lrtrimp(szRes), "Chunked") == 0) - acksz = hdrend - szResult; - } - if (txtParseParam(szResult, nullptr, "Connection:", "\n", szRes, sizeof(szRes))) { - needClose = (_stricmp(lrtrimp(szRes), "close") == 0); - } - } - } - - // Content-Length bytes reached, all data received - if (sz >= pktsz && pktsz != 0) { - szResult[pktsz] = 0; - break; - } - - // Chunked encoding processing - if (sz > acksz && acksz != 0) { -retry: - // Parse out chunk size - char* data = szResult + acksz; - char* peol1 = data == hdrend ? data - 1 : strchr(data, '\n'); - if (peol1 != nullptr) { - char *peol2 = strchr(++peol1, '\n'); - if (peol2 != nullptr) { - // Get chunk size - int chunkBytes = strtol(peol1, nullptr, 16); - acksz += chunkBytes; - peol2++; - - memmove(data, peol2, mir_strlen(peol2) + 1); - sz -= peol2 - data; - - // Last chunk, all data received - if (chunkBytes == 0) break; - if (sz > acksz) goto retry; - } - } - } - } - LongLog(szResult); - } - else { - if (retryCount < 2) { - closeRouterConnection(); - ++retryCount; - goto retrycon; - } - else - Netlib_Logf(nullptr, "UPnP send failed %d", WSAGetLastError()); - } - } - txtParseParam(szResult, "HTTP", " ", " ", szRes, sizeof(szRes)); - res = atol(szRes); - if (szActionName != nullptr && res == 405 && szPostHdr == soap_post_hdr) - szPostHdr = soap_post_hdr_m; - else - break; - } - - if (needClose) - closeRouterConnection(); - - mir_free(szData); - mir_free(szReq); - return res; -} - -static unsigned getExtIP(void) -{ - char szExtIP[30]; - char* szData = (char*)mir_alloc(4096); szData[0] = 0; - - unsigned extip = 0; - int res = httpTransact(szCtlUrl, szData, 4096, "GetExternalIPAddress", ControlAction); - if (res == 200 && txtParseParam(szData, "<NewExternalIPAddress", ">", "<", szExtIP, sizeof(szExtIP))) - extip = ntohl(inet_addr(szExtIP)); - - mir_free(szData); - return extip; -} - -static bool getUPnPURLs(char* szUrl, size_t sizeUrl) -{ - char* szData = (char*)mir_alloc(8192); - - gatewayFound = httpTransact(szUrl, szData, 8192, nullptr, DeviceGetReq) == 200; - if (gatewayFound) { - char szTemp[256], *rpth; - size_t ctlLen; - - txtParseParam(szData, nullptr, "<URLBase>", "</URLBase>", szTemp, sizeof(szTemp)); - strncpy(szCtlUrl, szTemp[0] ? szTemp : szUrl, sizeof(szCtlUrl)); - szCtlUrl[sizeof(szCtlUrl) - 1] = 0; - - mir_snprintf(szTemp, search_device, szDev); - txtParseParam(szData, szTemp, "<controlURL>", "</controlURL>", szUrl, sizeUrl); - - // URL combining per RFC 2396 - if (szUrl[0] != 0) { - if (strstr(szUrl, "://") != nullptr) // absolute URI - rpth = szCtlUrl; - else if (strncmp(szUrl, "//", 2) == 0) // relative URI net_path - { - rpth = strstr(szCtlUrl, "//"); - if (rpth == nullptr) rpth = szCtlUrl; - } - else if (szUrl[0] == '/') // relative URI abs_path - { - rpth = strstr(szCtlUrl, "//"); - rpth = rpth ? rpth + 2 : szCtlUrl; - - rpth = strchr(rpth, '/'); - if (rpth == nullptr) rpth = szCtlUrl + mir_strlen(szCtlUrl); - } - else { // relative URI rel_path - size_t ctlCLen = mir_strlen(szCtlUrl); - rpth = szCtlUrl + ctlCLen; - if (ctlCLen != 0 && *(rpth - 1) != '/') - strncpy(rpth++, "/", sizeof(szCtlUrl) - ctlCLen); - } - - ctlLen = sizeof(szCtlUrl) - (rpth - szCtlUrl); - strncpy(rpth, szUrl, ctlLen); - szCtlUrl[sizeof(szCtlUrl) - 1] = 0; - } - else { - szCtlUrl[0] = 0; - gatewayFound = false; - } - } - mir_free(szData); - - return gatewayFound; -} - -static void discoverUPnP(void) -{ - char* buf; - int buflen; - unsigned i, j, nip = 0; - unsigned* ips = nullptr; - - static const unsigned any = INADDR_ANY; - static const TIMEVAL tv = { 1, 600000 }; - - char szUrl[256] = ""; - char hostname[256]; - PHOSTENT he; - fd_set readfd; - - SOCKET s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - - SOCKADDR_IN enetaddr; - enetaddr.sin_family = AF_INET; - enetaddr.sin_port = htons(1900); - enetaddr.sin_addr.s_addr = inet_addr("239.255.255.250"); - - gethostname(hostname, sizeof(hostname)); - he = gethostbyname(hostname); - - if (he) { - while (he->h_addr_list[nip]) ++nip; - - ips = (unsigned*)mir_alloc(nip * sizeof(unsigned)); - - for (j = 0; j < nip; j++) - ips[j] = *(unsigned*)he->h_addr_list[j]; - } - - buf = (char*)mir_alloc(1500); - - for (i = 3; --i && szUrl[0] == 0;) { - for (j = 0; j < nip; j++) { - if (ips) - setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&ips[j], sizeof(unsigned)); - - buflen = mir_snprintf(buf, 1500, search_request_msg, "WANIPConnection:1"); - sendto(s, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr)); - LongLog(buf); - - buflen = mir_snprintf(buf, 1500, search_request_msg, "WANPPPConnection:1"); - sendto(s, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr)); - LongLog(buf); - } - - if (Miranda_IsTerminated()) break; - - FD_ZERO(&readfd); - FD_SET(s, &readfd); - - while (select(1, &readfd, nullptr, nullptr, &tv) >= 1) { - buflen = recv(s, buf, 1500, 0); - if (buflen != SOCKET_ERROR) { - buf[buflen] = 0; - LongLog(buf); - - if (txtParseParam(buf, nullptr, "LOCATION:", "\n", szUrl, sizeof(szUrl)) || - txtParseParam(buf, nullptr, "Location:", "\n", szUrl, sizeof(szUrl))) { - char age[30]; - char szHostNew[256], szHostExist[256]; - - lrtrim(szUrl); - - parseURL(szUrl, szHostNew, nullptr, nullptr); - parseURL(szCtlUrl, szHostExist, nullptr, nullptr); - if (mir_strcmp(szHostNew, szHostExist) == 0) { - gatewayFound = true; - break; - } - - txtParseParam(buf, nullptr, "ST:", "\n", szDev, sizeof(szDev)); - txtParseParam(buf, "max-age", " = ", "\n", age, sizeof(age)); - expireTime = atoi(lrtrimp(age)); - lrtrim(szDev); - - if (getUPnPURLs(szUrl, sizeof(szUrl))) { - gatewayFound = getExtIP() != 0; - if (gatewayFound) break; - } - } - } - FD_ZERO(&readfd); - FD_SET(s, &readfd); - } - } - - mir_free(buf); - mir_free(ips); - setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&any, sizeof(unsigned)); - closesocket(s); -} - -static bool findUPnPGateway(void) -{ - if ((time(0) - lastDiscTime) >= expireTime) { - WaitForSingleObject(portListMutex, INFINITE); - - time_t curTime = time(0); - - if ((curTime - lastDiscTime) >= expireTime) { - gatewayFound = false; - - discoverUPnP(); - lastDiscTime = curTime; - - Netlib_Logf(nullptr, "UPnP Gateway detected %d, Control URL: %s", gatewayFound, szCtlUrl); - } - - ReleaseMutex(portListMutex); - } - - return gatewayFound; -} - -bool NetlibUPnPAddPortMapping(uint16_t intport, char *proto, uint16_t *extport, uint32_t *extip, bool search) -{ - int res = 0, i = 5; - - if (findUPnPGateway()) { - char* szData = (char*)mir_alloc(4096); - char szExtIP[30]; - - *extport = intport - 1; - *extip = ntohl(locIP.sin_addr.S_un.S_addr); - - WaitForSingleObject(portListMutex, INFINITE); - - do { - ++*extport; - mir_snprintf(szData, 4096, add_port_mapping, - *extport, proto, intport, inet_ntoa(locIP.sin_addr)); - res = httpTransact(szCtlUrl, szData, 4096, "AddPortMapping", ControlAction); - txtParseParam(szData, nullptr, "<errorCode>", "</errorCode>", szExtIP, sizeof(szExtIP)); - - } while (search && res == 500 && atol(szExtIP) == 718 && --i); - - mir_free(szData); - - if (res == 200) { - unsigned ip = getExtIP(); - if (ip) *extip = ip; - - if (numports >= numportsAlloc) - mir_realloc(portList, sizeof(uint16_t)*(numportsAlloc += 10)); - portList[numports++] = *extport; - } - - ReleaseMutex(portListMutex); - } - - return res == 200; -} - -void NetlibUPnPDeletePortMapping(uint16_t extport, char* proto) -{ - if (extport == 0) - return; - - // findUPnPGateway(); - - if (gatewayFound) { - unsigned i; - char* szData = (char*)mir_alloc(4096); - - WaitForSingleObject(portListMutex, INFINITE); - mir_snprintf(szData, 4096, delete_port_mapping, extport, proto); - httpTransact(szCtlUrl, szData, 4096, "DeletePortMapping", ControlAction); - - for (i = 0; i < numports; i++) - if (portList[i] == extport && --numports > 0) - memmove(&portList[i], &portList[i + 1], (numports - i) * sizeof(uint16_t)); - - mir_free(szData); - ReleaseMutex(portListMutex); - } -} - -void NetlibUPnPCleanup(void*) -{ - // upnp is disabled globally, no need for a cleanup - if (db_get_b(0, "Netlib", "NLEnableUPnP", 1) == 0) - return; - - { - int incoming = 0; - mir_cslock lck(csNetlibUser); - for (auto &p : netlibUser) - if (p->user.flags & NUF_INCOMING) { - incoming = 1; - break; - } - - if (!incoming) - return; - } - - if (findUPnPGateway()) { - char *szData = (char*)alloca(4096); - char buf[50], lip[50]; - unsigned j = 0, k, num = 100; - - strncpy_s(lip, inet_ntoa(locIP.sin_addr), _TRUNCATE); - - WaitForSingleObject(portListMutex, INFINITE); - - if (httpTransact(szCtlUrl, szData, 4096, "PortMappingNumberOfEntries", ControlQuery) == 200 && - txtParseParam(szData, "QueryStateVariableResponse", "<return>", "<", buf, sizeof(buf))) - num = atol(buf); - - uint16_t ports[30]; - for (unsigned i = 0; i < num && !Miranda_IsTerminated(); i++) { - mir_snprintf(szData, 4096, get_port_mapping, i); - - ReleaseMutex(portListMutex); - WaitForSingleObject(portListMutex, INFINITE); - - if (httpTransact(szCtlUrl, szData, 4096, "GetGenericPortMappingEntry", ControlAction) != 200) - break; - - if (!txtParseParam(szData, "<NewPortMappingDescription", ">", "<", buf, sizeof(buf)) || mir_strcmp(buf, "Miranda") != 0) - continue; - - if (!txtParseParam(szData, "<NewInternalClient", ">", "<", buf, sizeof(buf)) || mir_strcmp(buf, lip) != 0) - continue; - - if (txtParseParam(szData, "<NewExternalPort", ">", "<", buf, sizeof(buf))) { - uint16_t mport = (uint16_t)atol(buf); - - if (j >= _countof(ports)) - break; - - for (k = 0; k < numports; ++k) - if (portList[k] == mport) - break; - - if (k >= numports) - ports[j++] = mport; - } - } - - ReleaseMutex(portListMutex); - - for (unsigned i = 0; i < j && !Miranda_IsTerminated(); i++) - NetlibUPnPDeletePortMapping(ports[i], "TCP"); - } -} - -void NetlibUPnPInit(void) -{ - numports = 0; - numportsAlloc = 10; - portList = (uint16_t*)mir_alloc(sizeof(uint16_t)*numportsAlloc); - - portListMutex = CreateMutex(nullptr, FALSE, nullptr); -} - -void NetlibUPnPDestroy(void) -{ - mir_free(portList); - CloseHandle(portListMutex); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+static const char search_request_msg[] =
+ "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1900\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "MX: 1\r\n"
+ "ST: urn:schemas-upnp-org:service:%s\r\n"
+ "\r\n";
+
+static const char xml_get_hdr[] =
+ "GET %s HTTP/1.1\r\n"
+ "HOST: %s:%u\r\n"
+ "ACCEPT-LANGUAGE: *\r\n\r\n";
+
+static const char soap_post_hdr[] =
+ "POST %s HTTP/1.1\r\n"
+ "HOST: %s:%u\r\n"
+ "CONTENT-LENGTH: %u\r\n"
+ "CONTENT-TYPE: text/xml; charset = \"utf-8\"\r\n"
+ "SOAPACTION: \"%s#%s\"\r\n\r\n"
+ "%s";
+
+static const char soap_post_hdr_m[] =
+ "M-POST %s URL HTTP/1.1\r\n"
+ "HOST: %s:%u\r\n"
+ "CONTENT-LENGTH: %u\r\n"
+ "CONTENT-TYPE: text/xml; charset = \"utf-8\"\r\n"
+ "MAN: \"http://schemas.xmlsoap.org/soap/envelope/\"; ns = 01\r\n"
+ "01-SOAPACTION: \"%s#%s\"\r\n\r\n"
+ "%s";
+
+static const char search_device[] =
+ "<serviceType>%s</serviceType>";
+
+static const char soap_action[] =
+ "<?xml version = \"1.0\"?>\r\n"
+ "<s:Envelope\r\n"
+ " xmlns:s = \"http://schemas.xmlsoap.org/soap/envelope/\"\r\n"
+ " s:encodingStyle = \"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"
+ " <s:Body>\r\n"
+ " <u:%s xmlns:u = \"%s\">\r\n"
+ "%s"
+ " </u:%s>\r\n"
+ " </s:Body>\r\n"
+ "</s:Envelope>\r\n";
+
+static const char soap_query[] =
+ "<s:Envelope\r\n"
+ " xmlns:s = \"http://schemas.xmlsoap.org/soap/envelope/\"\r\n"
+ " s:encodingStyle = \"http://schemas.xmlsoap.org/soap/encoding/\">\r\n"
+ " <s:Body>\r\n"
+ " <u:QueryStateVariable xmlns:u = \"urn:schemas-upnp-org:control-1-0\">\r\n"
+ " <u:varName>%s</u:varName>\r\n"
+ " </u:QueryStateVariable>\r\n"
+ " </s:Body>\r\n"
+ "</s:Envelope>\r\n";
+
+static const char add_port_mapping[] =
+ " <NewRemoteHost></NewRemoteHost>\r\n"
+ " <NewExternalPort>%i</NewExternalPort>\r\n"
+ " <NewProtocol>%s</NewProtocol>\r\n"
+ " <NewInternalPort>%i</NewInternalPort>\r\n"
+ " <NewInternalClient>%s</NewInternalClient>\r\n"
+ " <NewEnabled>1</NewEnabled>\r\n"
+ " <NewPortMappingDescription>Miranda</NewPortMappingDescription>\r\n"
+ " <NewLeaseDuration>0</NewLeaseDuration>\r\n";
+
+static const char delete_port_mapping[] =
+ " <NewRemoteHost></NewRemoteHost>\r\n"
+ " <NewExternalPort>%i</NewExternalPort>\r\n"
+ " <NewProtocol>%s</NewProtocol>\r\n";
+
+static const char get_port_mapping[] =
+ " <NewPortMappingIndex>%i</NewPortMappingIndex>\r\n";
+
+static bool gatewayFound;
+static SOCKADDR_IN locIP;
+static time_t lastDiscTime;
+static int expireTime = 120;
+
+static int retryCount;
+static SOCKET sock = INVALID_SOCKET;
+static char szConnHost[256];
+static unsigned short sConnPort;
+
+static uint16_t *portList;
+static unsigned numports, numportsAlloc;
+static HANDLE portListMutex;
+
+static char szCtlUrl[256], szDev[256];
+
+typedef enum
+{
+ DeviceGetReq,
+ ControlAction,
+ ControlQuery
+} ReqType;
+
+static bool txtParseParam(char* szData, char* presearch,
+ char* start, char* finish, char* param, size_t size)
+{
+ char *cp, *cp1;
+ size_t len;
+
+ *param = 0;
+
+ if (presearch != nullptr) {
+ cp1 = strstr(szData, presearch);
+ if (cp1 == nullptr) return false;
+ }
+ else
+ cp1 = szData;
+
+ cp = strstr(cp1, start);
+ if (cp == nullptr) return false;
+ cp += mir_strlen(start);
+ while (*cp == ' ') ++cp;
+
+ cp1 = strstr(cp, finish);
+ if (cp1 == nullptr) return false;
+ while (*(cp1-1) == ' ' && cp1 > cp) --cp1;
+
+ len = min((size_t)(cp1 - cp), size-1);
+ strncpy(param, cp, len);
+ param[len] = 0;
+
+ return true;
+}
+
+void parseURL(char* szUrl, char* szHost, unsigned short* sPort, char* szPath)
+{
+ char *ppath, *phost, *pport;
+ int sz;
+
+ phost = strstr(szUrl, "://");
+ if (phost == nullptr) phost = szUrl;
+ else phost += 3;
+
+ ppath = strchr(phost, '/');
+ if (ppath == nullptr) ppath = phost + mir_strlen(phost);
+
+ pport = strchr(phost, ':');
+ if (pport == nullptr) pport = ppath;
+
+ if (szHost != nullptr) {
+ sz = pport - phost + 1;
+ if (sz > 256) sz = 256;
+ strncpy(szHost, phost, sz);
+ szHost[sz - 1] = 0;
+ }
+
+ if (sPort != nullptr) {
+ if (pport < ppath) {
+ long prt = atol(pport + 1);
+ *sPort = prt != 0 ? (unsigned short)prt : 80;
+ }
+ else
+ *sPort = 80;
+ }
+
+ if (szPath != nullptr) {
+ strncpy(szPath, ppath, 256);
+ szPath[255] = 0;
+ }
+}
+
+static void LongLog(char* szData)
+{
+ Netlib_Logf(nullptr, szData);
+}
+
+static void closeRouterConnection(void)
+{
+ if (sock != INVALID_SOCKET) {
+ closesocket(sock);
+ sock = INVALID_SOCKET;
+ }
+}
+
+static void validateSocket(void)
+{
+ static const TIMEVAL tv = { 0, 0 };
+ fd_set rfd;
+ char buf[4];
+
+ if (sock == INVALID_SOCKET)
+ return;
+
+ FD_ZERO(&rfd);
+ FD_SET(sock, &rfd);
+
+ bool opened = false;
+ switch (select(1, &rfd, nullptr, nullptr, &tv)) {
+ case 0:
+ opened = true;
+ break;
+
+ case 1:
+ opened = recv(sock, buf, 1, MSG_PEEK) > 0;
+ break;
+ }
+
+ if (!opened)
+ closeRouterConnection();
+}
+
+static int httpTransact(char* szUrl, char* szResult, int resSize, char* szActionName, ReqType reqtype)
+{
+ // Parse URL
+ char szHost[256], szPath[256], szRes[16];
+ int sz = 0, res = 0;
+ unsigned short sPort;
+ bool needClose = false;
+
+ const char* szPostHdr = soap_post_hdr;
+ char* szData = (char*)mir_alloc(4096);
+ char* szReq = nullptr;
+
+ parseURL(szUrl, szHost, &sPort, szPath);
+
+ if (sPort != sConnPort || _stricmp(szHost, szConnHost))
+ closeRouterConnection();
+ else
+ validateSocket();
+
+ while (true) {
+ retryCount = 0;
+ switch (reqtype) {
+ case DeviceGetReq:
+ sz = mir_snprintf(szData, 4096, xml_get_hdr, szPath, szHost, sPort);
+ break;
+
+ case ControlAction:
+ {
+ char szData1[1024];
+
+ szReq = mir_strdup(szResult);
+ sz = mir_snprintf(szData1, soap_action, szActionName, szDev, szReq, szActionName);
+ sz = mir_snprintf(szData, 4096, szPostHdr, szPath, szHost, sPort, sz, szDev, szActionName, szData1);
+ }
+ break;
+
+ case ControlQuery:
+ {
+ char szData1[1024];
+ sz = mir_snprintf(szData1, soap_query, szActionName);
+ sz = mir_snprintf(szData, 4096, szPostHdr, szPath, szHost, sPort, sz, "urn:schemas-upnp-org:control-1-0", "QueryStateVariable", szData1);
+ }
+ break;
+ }
+ szResult[0] = 0;
+ {
+ static const TIMEVAL tv = { 6, 0 };
+ static unsigned ttl = 4;
+ static u_long mode = 1;
+ fd_set rfd, wfd, efd;
+ SOCKADDR_IN enetaddr;
+
+retrycon:
+ if (sock == INVALID_SOCKET) {
+ sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+ enetaddr.sin_family = AF_INET;
+ enetaddr.sin_port = htons(sPort);
+ enetaddr.sin_addr.s_addr = inet_addr(szHost);
+
+ // Resolve host name if needed
+ if (enetaddr.sin_addr.s_addr == INADDR_NONE) {
+ PHOSTENT he = gethostbyname(szHost);
+ if (he)
+ enetaddr.sin_addr.s_addr = *(unsigned*)he->h_addr_list[0];
+ }
+
+ Netlib_Logf(nullptr, "UPnP HTTP connection Host: %s Port: %u", szHost, sPort);
+
+ FD_ZERO(&rfd); FD_ZERO(&wfd); FD_ZERO(&efd);
+ FD_SET(sock, &rfd); FD_SET(sock, &wfd); FD_SET(sock, &efd);
+
+ // Limit the scope of the connection (does not work for
+ setsockopt(sock, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(unsigned));
+
+ // Put socket into non-blocking mode for timeout on connect
+ ioctlsocket(sock, FIONBIO, &mode);
+
+ // Connect to the remote host
+ if (connect(sock, (SOCKADDR*)&enetaddr, sizeof(enetaddr)) == SOCKET_ERROR) {
+ int err = WSAGetLastError();
+
+ // Socket connection failed
+ if (err != WSAEWOULDBLOCK) {
+ closeRouterConnection();
+ Netlib_Logf(nullptr, "UPnP connect failed %d", err);
+ break;
+ }
+ // Wait for socket to connect
+ else if (select(1, &rfd, &wfd, &efd, &tv) != 1) {
+ closeRouterConnection();
+ Netlib_Logf(nullptr, "UPnP connect timeout");
+ break;
+ }
+ else if (!FD_ISSET(sock, &wfd)) {
+ closeRouterConnection();
+ Netlib_Logf(nullptr, "UPnP connect failed");
+ break;
+ }
+ }
+ strncpy_s(szConnHost, szHost, _TRUNCATE);
+ sConnPort = sPort;
+ }
+
+ if (send(sock, szData, sz, 0) != SOCKET_ERROR) {
+ char *hdrend = nullptr;
+ int acksz = 0, pktsz = 0;
+
+ if (szActionName == nullptr) {
+ int len = sizeof(locIP);
+ getsockname(sock, (SOCKADDR*)&locIP, &len);
+ if (locIP.sin_addr.S_un.S_addr == 0x0100007f) {
+ struct hostent *he;
+
+ gethostname(szPath, sizeof(szPath));
+ he = gethostbyname(szPath);
+ if (he != nullptr)
+ locIP.sin_addr.S_un.S_addr = *(PDWORD)he->h_addr_list[0];
+ }
+ }
+
+ LongLog(szData);
+ sz = 0;
+ while (true) {
+ int bytesRecv;
+
+ FD_ZERO(&rfd);
+ FD_SET(sock, &rfd);
+
+ // Wait for the next packet
+ if (select(1, &rfd, nullptr, nullptr, &tv) != 1) {
+ closeRouterConnection();
+ Netlib_Logf(nullptr, "UPnP recieve timeout");
+ break;
+ }
+
+ //
+ bytesRecv = recv(sock, &szResult[sz], resSize - sz, 0);
+
+ // Connection closed or aborted, all data received
+ if (bytesRecv == 0 || bytesRecv == SOCKET_ERROR) {
+ closeRouterConnection();
+ if ((bytesRecv == SOCKET_ERROR || sz == 0) && retryCount < 2) {
+ ++retryCount;
+ goto retrycon;
+ }
+ break;
+ }
+
+ sz += bytesRecv;
+
+ // Insert null terminator to use string functions
+ if (sz >= (resSize - 1)) {
+ szResult[resSize - 1] = 0;
+ break;
+ }
+ else
+ szResult[sz] = 0;
+
+ // HTTP header found?
+ if (hdrend == nullptr) {
+ // Find HTTP header end
+ hdrend = strstr(szResult, "\r\n\r\n");
+ if (hdrend == nullptr) {
+ hdrend = strstr(szResult, "\n\n");
+ if (hdrend) hdrend += 2;
+ }
+
+ else
+ hdrend += 4;
+
+ if (hdrend != nullptr) {
+ // Get packet size if provided
+ if (txtParseParam(szResult, nullptr, "Content-Length:", "\n", szRes, sizeof(szRes)) ||
+ txtParseParam(szResult, nullptr, "CONTENT-LENGTH:", "\n", szRes, sizeof(szRes))) {
+ // Add size of HTTP header to the packet size to compute full transmission size
+ pktsz = atol(ltrimp(szRes)) + (hdrend - szResult);
+ }
+ // Get encoding type if provided
+ else if (txtParseParam(szResult, nullptr, "Transfer-Encoding:", "\n", szRes, sizeof(szRes))) {
+ if (_stricmp(lrtrimp(szRes), "Chunked") == 0)
+ acksz = hdrend - szResult;
+ }
+ if (txtParseParam(szResult, nullptr, "Connection:", "\n", szRes, sizeof(szRes))) {
+ needClose = (_stricmp(lrtrimp(szRes), "close") == 0);
+ }
+ }
+ }
+
+ // Content-Length bytes reached, all data received
+ if (sz >= pktsz && pktsz != 0) {
+ szResult[pktsz] = 0;
+ break;
+ }
+
+ // Chunked encoding processing
+ if (sz > acksz && acksz != 0) {
+retry:
+ // Parse out chunk size
+ char* data = szResult + acksz;
+ char* peol1 = data == hdrend ? data - 1 : strchr(data, '\n');
+ if (peol1 != nullptr) {
+ char *peol2 = strchr(++peol1, '\n');
+ if (peol2 != nullptr) {
+ // Get chunk size
+ int chunkBytes = strtol(peol1, nullptr, 16);
+ acksz += chunkBytes;
+ peol2++;
+
+ memmove(data, peol2, mir_strlen(peol2) + 1);
+ sz -= peol2 - data;
+
+ // Last chunk, all data received
+ if (chunkBytes == 0) break;
+ if (sz > acksz) goto retry;
+ }
+ }
+ }
+ }
+ LongLog(szResult);
+ }
+ else {
+ if (retryCount < 2) {
+ closeRouterConnection();
+ ++retryCount;
+ goto retrycon;
+ }
+ else
+ Netlib_Logf(nullptr, "UPnP send failed %d", WSAGetLastError());
+ }
+ }
+ txtParseParam(szResult, "HTTP", " ", " ", szRes, sizeof(szRes));
+ res = atol(szRes);
+ if (szActionName != nullptr && res == 405 && szPostHdr == soap_post_hdr)
+ szPostHdr = soap_post_hdr_m;
+ else
+ break;
+ }
+
+ if (needClose)
+ closeRouterConnection();
+
+ mir_free(szData);
+ mir_free(szReq);
+ return res;
+}
+
+static unsigned getExtIP(void)
+{
+ char szExtIP[30];
+ char* szData = (char*)mir_alloc(4096); szData[0] = 0;
+
+ unsigned extip = 0;
+ int res = httpTransact(szCtlUrl, szData, 4096, "GetExternalIPAddress", ControlAction);
+ if (res == 200 && txtParseParam(szData, "<NewExternalIPAddress", ">", "<", szExtIP, sizeof(szExtIP)))
+ extip = ntohl(inet_addr(szExtIP));
+
+ mir_free(szData);
+ return extip;
+}
+
+static bool getUPnPURLs(char* szUrl, size_t sizeUrl)
+{
+ char* szData = (char*)mir_alloc(8192);
+
+ gatewayFound = httpTransact(szUrl, szData, 8192, nullptr, DeviceGetReq) == 200;
+ if (gatewayFound) {
+ char szTemp[256], *rpth;
+ size_t ctlLen;
+
+ txtParseParam(szData, nullptr, "<URLBase>", "</URLBase>", szTemp, sizeof(szTemp));
+ strncpy(szCtlUrl, szTemp[0] ? szTemp : szUrl, sizeof(szCtlUrl));
+ szCtlUrl[sizeof(szCtlUrl) - 1] = 0;
+
+ mir_snprintf(szTemp, search_device, szDev);
+ txtParseParam(szData, szTemp, "<controlURL>", "</controlURL>", szUrl, sizeUrl);
+
+ // URL combining per RFC 2396
+ if (szUrl[0] != 0) {
+ if (strstr(szUrl, "://") != nullptr) // absolute URI
+ rpth = szCtlUrl;
+ else if (strncmp(szUrl, "//", 2) == 0) // relative URI net_path
+ {
+ rpth = strstr(szCtlUrl, "//");
+ if (rpth == nullptr) rpth = szCtlUrl;
+ }
+ else if (szUrl[0] == '/') // relative URI abs_path
+ {
+ rpth = strstr(szCtlUrl, "//");
+ rpth = rpth ? rpth + 2 : szCtlUrl;
+
+ rpth = strchr(rpth, '/');
+ if (rpth == nullptr) rpth = szCtlUrl + mir_strlen(szCtlUrl);
+ }
+ else { // relative URI rel_path
+ size_t ctlCLen = mir_strlen(szCtlUrl);
+ rpth = szCtlUrl + ctlCLen;
+ if (ctlCLen != 0 && *(rpth - 1) != '/')
+ strncpy(rpth++, "/", sizeof(szCtlUrl) - ctlCLen);
+ }
+
+ ctlLen = sizeof(szCtlUrl) - (rpth - szCtlUrl);
+ strncpy(rpth, szUrl, ctlLen);
+ szCtlUrl[sizeof(szCtlUrl) - 1] = 0;
+ }
+ else {
+ szCtlUrl[0] = 0;
+ gatewayFound = false;
+ }
+ }
+ mir_free(szData);
+
+ return gatewayFound;
+}
+
+static void discoverUPnP(void)
+{
+ char* buf;
+ int buflen;
+ unsigned i, j, nip = 0;
+ unsigned* ips = nullptr;
+
+ static const unsigned any = INADDR_ANY;
+ static const TIMEVAL tv = { 1, 600000 };
+
+ char szUrl[256] = "";
+ char hostname[256];
+ PHOSTENT he;
+ fd_set readfd;
+
+ SOCKET s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ SOCKADDR_IN enetaddr;
+ enetaddr.sin_family = AF_INET;
+ enetaddr.sin_port = htons(1900);
+ enetaddr.sin_addr.s_addr = inet_addr("239.255.255.250");
+
+ gethostname(hostname, sizeof(hostname));
+ he = gethostbyname(hostname);
+
+ if (he) {
+ while (he->h_addr_list[nip]) ++nip;
+
+ ips = (unsigned*)mir_alloc(nip * sizeof(unsigned));
+
+ for (j = 0; j < nip; j++)
+ ips[j] = *(unsigned*)he->h_addr_list[j];
+ }
+
+ buf = (char*)mir_alloc(1500);
+
+ for (i = 3; --i && szUrl[0] == 0;) {
+ for (j = 0; j < nip; j++) {
+ if (ips)
+ setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&ips[j], sizeof(unsigned));
+
+ buflen = mir_snprintf(buf, 1500, search_request_msg, "WANIPConnection:1");
+ sendto(s, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr));
+ LongLog(buf);
+
+ buflen = mir_snprintf(buf, 1500, search_request_msg, "WANPPPConnection:1");
+ sendto(s, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr));
+ LongLog(buf);
+ }
+
+ if (Miranda_IsTerminated()) break;
+
+ FD_ZERO(&readfd);
+ FD_SET(s, &readfd);
+
+ while (select(1, &readfd, nullptr, nullptr, &tv) >= 1) {
+ buflen = recv(s, buf, 1500, 0);
+ if (buflen != SOCKET_ERROR) {
+ buf[buflen] = 0;
+ LongLog(buf);
+
+ if (txtParseParam(buf, nullptr, "LOCATION:", "\n", szUrl, sizeof(szUrl)) ||
+ txtParseParam(buf, nullptr, "Location:", "\n", szUrl, sizeof(szUrl))) {
+ char age[30];
+ char szHostNew[256], szHostExist[256];
+
+ lrtrim(szUrl);
+
+ parseURL(szUrl, szHostNew, nullptr, nullptr);
+ parseURL(szCtlUrl, szHostExist, nullptr, nullptr);
+ if (mir_strcmp(szHostNew, szHostExist) == 0) {
+ gatewayFound = true;
+ break;
+ }
+
+ txtParseParam(buf, nullptr, "ST:", "\n", szDev, sizeof(szDev));
+ txtParseParam(buf, "max-age", " = ", "\n", age, sizeof(age));
+ expireTime = atoi(lrtrimp(age));
+ lrtrim(szDev);
+
+ if (getUPnPURLs(szUrl, sizeof(szUrl))) {
+ gatewayFound = getExtIP() != 0;
+ if (gatewayFound) break;
+ }
+ }
+ }
+ FD_ZERO(&readfd);
+ FD_SET(s, &readfd);
+ }
+ }
+
+ mir_free(buf);
+ mir_free(ips);
+ setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF, (char *)&any, sizeof(unsigned));
+ closesocket(s);
+}
+
+static bool findUPnPGateway(void)
+{
+ if ((time(0) - lastDiscTime) >= expireTime) {
+ WaitForSingleObject(portListMutex, INFINITE);
+
+ time_t curTime = time(0);
+
+ if ((curTime - lastDiscTime) >= expireTime) {
+ gatewayFound = false;
+
+ discoverUPnP();
+ lastDiscTime = curTime;
+
+ Netlib_Logf(nullptr, "UPnP Gateway detected %d, Control URL: %s", gatewayFound, szCtlUrl);
+ }
+
+ ReleaseMutex(portListMutex);
+ }
+
+ return gatewayFound;
+}
+
+bool NetlibUPnPAddPortMapping(uint16_t intport, char *proto, uint16_t *extport, uint32_t *extip, bool search)
+{
+ int res = 0, i = 5;
+
+ if (findUPnPGateway()) {
+ char* szData = (char*)mir_alloc(4096);
+ char szExtIP[30];
+
+ *extport = intport - 1;
+ *extip = ntohl(locIP.sin_addr.S_un.S_addr);
+
+ WaitForSingleObject(portListMutex, INFINITE);
+
+ do {
+ ++*extport;
+ mir_snprintf(szData, 4096, add_port_mapping,
+ *extport, proto, intport, inet_ntoa(locIP.sin_addr));
+ res = httpTransact(szCtlUrl, szData, 4096, "AddPortMapping", ControlAction);
+ txtParseParam(szData, nullptr, "<errorCode>", "</errorCode>", szExtIP, sizeof(szExtIP));
+
+ } while (search && res == 500 && atol(szExtIP) == 718 && --i);
+
+ mir_free(szData);
+
+ if (res == 200) {
+ unsigned ip = getExtIP();
+ if (ip) *extip = ip;
+
+ if (numports >= numportsAlloc)
+ mir_realloc(portList, sizeof(uint16_t)*(numportsAlloc += 10));
+ portList[numports++] = *extport;
+ }
+
+ ReleaseMutex(portListMutex);
+ }
+
+ return res == 200;
+}
+
+void NetlibUPnPDeletePortMapping(uint16_t extport, char* proto)
+{
+ if (extport == 0)
+ return;
+
+ // findUPnPGateway();
+
+ if (gatewayFound) {
+ unsigned i;
+ char* szData = (char*)mir_alloc(4096);
+
+ WaitForSingleObject(portListMutex, INFINITE);
+ mir_snprintf(szData, 4096, delete_port_mapping, extport, proto);
+ httpTransact(szCtlUrl, szData, 4096, "DeletePortMapping", ControlAction);
+
+ for (i = 0; i < numports; i++)
+ if (portList[i] == extport && --numports > 0)
+ memmove(&portList[i], &portList[i + 1], (numports - i) * sizeof(uint16_t));
+
+ mir_free(szData);
+ ReleaseMutex(portListMutex);
+ }
+}
+
+void NetlibUPnPCleanup(void*)
+{
+ // upnp is disabled globally, no need for a cleanup
+ if (db_get_b(0, "Netlib", "NLEnableUPnP", 1) == 0)
+ return;
+
+ {
+ int incoming = 0;
+ mir_cslock lck(csNetlibUser);
+ for (auto &p : netlibUser)
+ if (p->user.flags & NUF_INCOMING) {
+ incoming = 1;
+ break;
+ }
+
+ if (!incoming)
+ return;
+ }
+
+ if (findUPnPGateway()) {
+ char *szData = (char*)alloca(4096);
+ char buf[50], lip[50];
+ unsigned j = 0, k, num = 100;
+
+ strncpy_s(lip, inet_ntoa(locIP.sin_addr), _TRUNCATE);
+
+ WaitForSingleObject(portListMutex, INFINITE);
+
+ if (httpTransact(szCtlUrl, szData, 4096, "PortMappingNumberOfEntries", ControlQuery) == 200 &&
+ txtParseParam(szData, "QueryStateVariableResponse", "<return>", "<", buf, sizeof(buf)))
+ num = atol(buf);
+
+ uint16_t ports[30];
+ for (unsigned i = 0; i < num && !Miranda_IsTerminated(); i++) {
+ mir_snprintf(szData, 4096, get_port_mapping, i);
+
+ ReleaseMutex(portListMutex);
+ WaitForSingleObject(portListMutex, INFINITE);
+
+ if (httpTransact(szCtlUrl, szData, 4096, "GetGenericPortMappingEntry", ControlAction) != 200)
+ break;
+
+ if (!txtParseParam(szData, "<NewPortMappingDescription", ">", "<", buf, sizeof(buf)) || mir_strcmp(buf, "Miranda") != 0)
+ continue;
+
+ if (!txtParseParam(szData, "<NewInternalClient", ">", "<", buf, sizeof(buf)) || mir_strcmp(buf, lip) != 0)
+ continue;
+
+ if (txtParseParam(szData, "<NewExternalPort", ">", "<", buf, sizeof(buf))) {
+ uint16_t mport = (uint16_t)atol(buf);
+
+ if (j >= _countof(ports))
+ break;
+
+ for (k = 0; k < numports; ++k)
+ if (portList[k] == mport)
+ break;
+
+ if (k >= numports)
+ ports[j++] = mport;
+ }
+ }
+
+ ReleaseMutex(portListMutex);
+
+ for (unsigned i = 0; i < j && !Miranda_IsTerminated(); i++)
+ NetlibUPnPDeletePortMapping(ports[i], "TCP");
+ }
+}
+
+void NetlibUPnPInit(void)
+{
+ numports = 0;
+ numportsAlloc = 10;
+ portList = (uint16_t*)mir_alloc(sizeof(uint16_t)*numportsAlloc);
+
+ portListMutex = CreateMutex(nullptr, FALSE, nullptr);
+}
+
+void NetlibUPnPDestroy(void)
+{
+ mir_free(portList);
+ CloseHandle(portListMutex);
+}
diff --git a/src/mir_app/src/netlib_websocket.cpp b/src/mir_app/src/netlib_websocket.cpp index 4b860cc0db..cd3aaaef4e 100644 --- a/src/mir_app/src/netlib_websocket.cpp +++ b/src/mir_app/src/netlib_websocket.cpp @@ -1,174 +1,174 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "netlib.h" - -#include "../../libs/zlib/src/zlib.h" - -MIR_APP_DLL(NETLIBHTTPREQUEST*) WebSocket_Connect(HNETLIBUSER nlu, const char *szHost, NETLIBHTTPHEADER *pHeaders) -{ - CMStringA tmpHost(szHost); - - // connect to the gateway server - if (!mir_strncmp(tmpHost, "wss://", 6)) - tmpHost.Delete(0, 6); - - auto *nlr = new MHttpRequest; - nlr->flags = NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_SSL; - nlr->szUrl = tmpHost.GetBuffer(); - nlr->AddHeader("Accept", "*/*"); - nlr->AddHeader("Upgrade", "websocket"); - nlr->AddHeader("Pragma", "no-cache"); - nlr->AddHeader("Cache-Control", "no-cache"); - nlr->AddHeader("Connection", "keep-alive, Upgrade"); - - uint8_t binNonce[16]; - Utils_GetRandom(binNonce, sizeof(binNonce)); - nlr->AddHeader("Sec-WebSocket-Key", ptrA(mir_base64_encode(binNonce, sizeof(binNonce)))); - nlr->AddHeader("Sec-WebSocket-Version", "13"); - nlr->AddHeader("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits"); - - if (pHeaders) { - while (pHeaders->szName != nullptr) { - nlr->AddHeader(pHeaders->szName, pHeaders->szValue); - pHeaders++; - } - } - - auto *pReply = Netlib_HttpTransaction(nlu, nlr); - delete nlr; - - if (pReply == nullptr) { - Netlib_Logf(nlu, "Error establishing WebSocket connection to %s, send failed", tmpHost.c_str()); - return nullptr; - } - - if (pReply->resultCode != 101) - Netlib_Logf(nlu, "Error establishing WebSocket connection to %s, status %d", tmpHost.c_str(), pReply->resultCode); - - return pReply; -} - -MIR_APP_DLL(bool) WebSocket_InitHeader(WSHeader &hdr, const void *pData, size_t bufSize) -{ - if (bufSize < 2) - return false; - - auto *buf = (const uint8_t *)pData; - hdr.bIsFinal = (buf[0] & 0x80) != 0; - hdr.bIsMasked = (buf[1] & 0x80) != 0; - hdr.opCode = buf[0] & 0x0F; - hdr.firstByte = buf[1] & 0x7F; - hdr.headerSize = 2 + (hdr.firstByte == 0x7E ? 2 : 0) + (hdr.firstByte == 0x7F ? 8 : 0) + (hdr.bIsMasked ? 4 : 0); - if (bufSize < hdr.headerSize) - return false; - - uint64_t tmpSize = 0; - switch (hdr.firstByte) { - case 0x7F: - tmpSize += ((uint64_t)buf[2]) << 56; - tmpSize += ((uint64_t)buf[3]) << 48; - tmpSize += ((uint64_t)buf[4]) << 40; - tmpSize += ((uint64_t)buf[5]) << 32; - tmpSize += ((uint64_t)buf[6]) << 24; - tmpSize += ((uint64_t)buf[7]) << 16; - tmpSize += ((uint64_t)buf[8]) << 8; - tmpSize += ((uint64_t)buf[9]); - break; - - case 0x7E: - tmpSize += ((uint64_t)buf[2]) << 8; - tmpSize += ((uint64_t)buf[3]); - break; - - default: - tmpSize = hdr.firstByte; - } - hdr.payloadSize = tmpSize; - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static void WebSocket_Send(HNETLIBCONN nlc, const void *pData, size_t dataLen, uint8_t opCode) -{ - uint8_t header[20]; - size_t datalen; - - header[0] = 0x80 + (opCode & 0x7F); - if (dataLen < 126) { - header[1] = (dataLen & 0xFF); - datalen = 2; - } - else if (dataLen < 65536) { - header[1] = 0x7E; - header[2] = (dataLen >> 8) & 0xFF; - header[3] = dataLen & 0xFF; - datalen = 4; - } - else { - header[1] = 0x7F; - header[2] = (dataLen >> 56) & 0xff; - header[3] = (dataLen >> 48) & 0xff; - header[4] = (dataLen >> 40) & 0xff; - header[5] = (dataLen >> 32) & 0xff; - header[6] = (dataLen >> 24) & 0xff; - header[7] = (dataLen >> 16) & 0xff; - header[8] = (dataLen >> 8) & 0xff; - header[9] = dataLen & 0xff; - datalen = 10; - } - - union { - uint32_t dwMask; - uint8_t arMask[4]; - }; - - dwMask = crc32(rand(), (uint8_t*)pData, (unsigned)dataLen); - memcpy(header + datalen, arMask, _countof(arMask)); - datalen += _countof(arMask); - header[1] |= 0x80; - - ptrA sendBuf((char*)mir_alloc(dataLen + datalen)); - memcpy(sendBuf, header, datalen); - if (dataLen) { - memcpy(sendBuf.get() + datalen, pData, dataLen); - for (size_t i = 0; i < dataLen; i++) - sendBuf[i + datalen] ^= arMask[i & 3]; - } - Netlib_Send(nlc, sendBuf, int(dataLen + datalen), MSG_NODUMP); -} - -MIR_APP_DLL(void) WebSocket_SendText(HNETLIBCONN nlc, const char *pData) -{ - if (nlc && pData) - WebSocket_Send(nlc, pData, strlen(pData), 1); -} - -MIR_APP_DLL(void) WebSocket_SendBinary(HNETLIBCONN nlc, const void *pData, size_t dataLen) -{ - if (nlc && pData) - WebSocket_Send(nlc, pData, dataLen, 2); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "netlib.h"
+
+#include "../../libs/zlib/src/zlib.h"
+
+MIR_APP_DLL(NETLIBHTTPREQUEST*) WebSocket_Connect(HNETLIBUSER nlu, const char *szHost, NETLIBHTTPHEADER *pHeaders)
+{
+ CMStringA tmpHost(szHost);
+
+ // connect to the gateway server
+ if (!mir_strncmp(tmpHost, "wss://", 6))
+ tmpHost.Delete(0, 6);
+
+ auto *nlr = new MHttpRequest;
+ nlr->flags = NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_SSL;
+ nlr->szUrl = tmpHost.GetBuffer();
+ nlr->AddHeader("Accept", "*/*");
+ nlr->AddHeader("Upgrade", "websocket");
+ nlr->AddHeader("Pragma", "no-cache");
+ nlr->AddHeader("Cache-Control", "no-cache");
+ nlr->AddHeader("Connection", "keep-alive, Upgrade");
+
+ uint8_t binNonce[16];
+ Utils_GetRandom(binNonce, sizeof(binNonce));
+ nlr->AddHeader("Sec-WebSocket-Key", ptrA(mir_base64_encode(binNonce, sizeof(binNonce))));
+ nlr->AddHeader("Sec-WebSocket-Version", "13");
+ nlr->AddHeader("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits");
+
+ if (pHeaders) {
+ while (pHeaders->szName != nullptr) {
+ nlr->AddHeader(pHeaders->szName, pHeaders->szValue);
+ pHeaders++;
+ }
+ }
+
+ auto *pReply = Netlib_HttpTransaction(nlu, nlr);
+ delete nlr;
+
+ if (pReply == nullptr) {
+ Netlib_Logf(nlu, "Error establishing WebSocket connection to %s, send failed", tmpHost.c_str());
+ return nullptr;
+ }
+
+ if (pReply->resultCode != 101)
+ Netlib_Logf(nlu, "Error establishing WebSocket connection to %s, status %d", tmpHost.c_str(), pReply->resultCode);
+
+ return pReply;
+}
+
+MIR_APP_DLL(bool) WebSocket_InitHeader(WSHeader &hdr, const void *pData, size_t bufSize)
+{
+ if (bufSize < 2)
+ return false;
+
+ auto *buf = (const uint8_t *)pData;
+ hdr.bIsFinal = (buf[0] & 0x80) != 0;
+ hdr.bIsMasked = (buf[1] & 0x80) != 0;
+ hdr.opCode = buf[0] & 0x0F;
+ hdr.firstByte = buf[1] & 0x7F;
+ hdr.headerSize = 2 + (hdr.firstByte == 0x7E ? 2 : 0) + (hdr.firstByte == 0x7F ? 8 : 0) + (hdr.bIsMasked ? 4 : 0);
+ if (bufSize < hdr.headerSize)
+ return false;
+
+ uint64_t tmpSize = 0;
+ switch (hdr.firstByte) {
+ case 0x7F:
+ tmpSize += ((uint64_t)buf[2]) << 56;
+ tmpSize += ((uint64_t)buf[3]) << 48;
+ tmpSize += ((uint64_t)buf[4]) << 40;
+ tmpSize += ((uint64_t)buf[5]) << 32;
+ tmpSize += ((uint64_t)buf[6]) << 24;
+ tmpSize += ((uint64_t)buf[7]) << 16;
+ tmpSize += ((uint64_t)buf[8]) << 8;
+ tmpSize += ((uint64_t)buf[9]);
+ break;
+
+ case 0x7E:
+ tmpSize += ((uint64_t)buf[2]) << 8;
+ tmpSize += ((uint64_t)buf[3]);
+ break;
+
+ default:
+ tmpSize = hdr.firstByte;
+ }
+ hdr.payloadSize = tmpSize;
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void WebSocket_Send(HNETLIBCONN nlc, const void *pData, size_t dataLen, uint8_t opCode)
+{
+ uint8_t header[20];
+ size_t datalen;
+
+ header[0] = 0x80 + (opCode & 0x7F);
+ if (dataLen < 126) {
+ header[1] = (dataLen & 0xFF);
+ datalen = 2;
+ }
+ else if (dataLen < 65536) {
+ header[1] = 0x7E;
+ header[2] = (dataLen >> 8) & 0xFF;
+ header[3] = dataLen & 0xFF;
+ datalen = 4;
+ }
+ else {
+ header[1] = 0x7F;
+ header[2] = (dataLen >> 56) & 0xff;
+ header[3] = (dataLen >> 48) & 0xff;
+ header[4] = (dataLen >> 40) & 0xff;
+ header[5] = (dataLen >> 32) & 0xff;
+ header[6] = (dataLen >> 24) & 0xff;
+ header[7] = (dataLen >> 16) & 0xff;
+ header[8] = (dataLen >> 8) & 0xff;
+ header[9] = dataLen & 0xff;
+ datalen = 10;
+ }
+
+ union {
+ uint32_t dwMask;
+ uint8_t arMask[4];
+ };
+
+ dwMask = crc32(rand(), (uint8_t*)pData, (unsigned)dataLen);
+ memcpy(header + datalen, arMask, _countof(arMask));
+ datalen += _countof(arMask);
+ header[1] |= 0x80;
+
+ ptrA sendBuf((char*)mir_alloc(dataLen + datalen));
+ memcpy(sendBuf, header, datalen);
+ if (dataLen) {
+ memcpy(sendBuf.get() + datalen, pData, dataLen);
+ for (size_t i = 0; i < dataLen; i++)
+ sendBuf[i + datalen] ^= arMask[i & 3];
+ }
+ Netlib_Send(nlc, sendBuf, int(dataLen + datalen), MSG_NODUMP);
+}
+
+MIR_APP_DLL(void) WebSocket_SendText(HNETLIBCONN nlc, const char *pData)
+{
+ if (nlc && pData)
+ WebSocket_Send(nlc, pData, strlen(pData), 1);
+}
+
+MIR_APP_DLL(void) WebSocket_SendBinary(HNETLIBCONN nlc, const void *pData, size_t dataLen)
+{
+ if (nlc && pData)
+ WebSocket_Send(nlc, pData, dataLen, 2);
+}
diff --git a/src/mir_app/src/newplugins.cpp b/src/mir_app/src/newplugins.cpp index 6a79d72330..367afde7b0 100644 --- a/src/mir_app/src/newplugins.cpp +++ b/src/mir_app/src/newplugins.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/options.cpp b/src/mir_app/src/options.cpp index ea7f32ac99..81c136a0ab 100644 --- a/src/mir_app/src/options.cpp +++ b/src/mir_app/src/options.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
Copyright (c) 2007 Artem Shpynov
all portions of this codebase are copyrighted to the people
diff --git a/src/mir_app/src/path.cpp b/src/mir_app/src/path.cpp index 616100510f..49e5cd7487 100644 --- a/src/mir_app/src/path.cpp +++ b/src/mir_app/src/path.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/pluginopts.cpp b/src/mir_app/src/pluginopts.cpp index c601a83072..7e58c5fca3 100644 --- a/src/mir_app/src/pluginopts.cpp +++ b/src/mir_app/src/pluginopts.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/popupOption.cpp b/src/mir_app/src/popupOption.cpp index 6749ba4e9c..4ac5290c6d 100644 --- a/src/mir_app/src/popupOption.cpp +++ b/src/mir_app/src/popupOption.cpp @@ -1,123 +1,123 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "plugins.h" - -struct MPopupOption -{ - MPopupOption(CMPluginBase *pPlugin, const char *pszDescr, CMOption<bool> &pVal) : - m_plugin(pPlugin), - m_val(pVal), - m_descr(pszDescr) - {} - - MPopupOption(CMPluginBase *pPlugin, const wchar_t *pwszDescr, CMOption<bool> &pVal) : - m_plugin(pPlugin), - m_val(pVal), - m_descr(pwszDescr) - {} - - CMPluginBase *m_plugin; - CMOption<bool> &m_val; - CMStringW m_descr; -}; - -static OBJLIST<MPopupOption> g_arOptions(1); - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMPluginBase::addPopupOption(const char *pszDescr, CMOption<bool> &pVal) -{ - g_arOptions.insert(new MPopupOption(this, pszDescr, pVal)); - return 0; -} - -int CMPluginBase::addPopupOption(const wchar_t *pwszDescr, CMOption<bool> &pVal) -{ - g_arOptions.insert(new MPopupOption(this, pwszDescr, pVal)); - return 0; -} - -void KillModulePopups(CMPluginBase *pPlugin) -{ - for (auto &it : g_arOptions.rev_iter()) - if (it->m_plugin == pPlugin) - g_arOptions.remove(g_arOptions.indexOf(&it)); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -class CPopupOptionsDlg : public CDlgBase -{ - CCtrlListView m_list; - -public: - CPopupOptionsDlg() : - CDlgBase(g_plugin, IDD_OPT_POPUPOPTION), - m_list(this, IDC_TREE) - {} - - bool OnInitDialog() override - { - m_list.SetExtendedListViewStyleEx(0, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT); - - LVITEM lvi; - lvi.mask = LVIF_TEXT | LVIF_PARAM; - lvi.iSubItem = 0; - - for (auto &it : g_arOptions) { - lvi.pszText = TranslateW_LP(it->m_descr, it->m_plugin); - lvi.lParam = LPARAM(it); - - int iRow = m_list.InsertItem(&lvi); - m_list.SetItemState(iRow, it->m_val ? 0x2000 : 0x1000, LVIS_STATEIMAGEMASK); - } - - return true; - } - - bool OnApply() override - { - int iRows = m_list.GetItemCount(); - - for (int i = 0; i < iRows; i++) { - auto *p = (MPopupOption *)m_list.GetItemData(i); - p->m_val = m_list.GetItemState(i, LVIS_STATEIMAGEMASK) == 0x2000; - } - return true; - } -}; - -int PopupOptionsInit(WPARAM wParam) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.position = -1000000000; - odp.szGroup.a = LPGEN("Popups"); - odp.szTitle.a = LPGEN("Events"); - odp.pDialog = new CPopupOptionsDlg(); - odp.flags = ODPF_BOLDGROUPS; - g_plugin.addOptions(wParam, &odp); - return 0; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "plugins.h"
+
+struct MPopupOption
+{
+ MPopupOption(CMPluginBase *pPlugin, const char *pszDescr, CMOption<bool> &pVal) :
+ m_plugin(pPlugin),
+ m_val(pVal),
+ m_descr(pszDescr)
+ {}
+
+ MPopupOption(CMPluginBase *pPlugin, const wchar_t *pwszDescr, CMOption<bool> &pVal) :
+ m_plugin(pPlugin),
+ m_val(pVal),
+ m_descr(pwszDescr)
+ {}
+
+ CMPluginBase *m_plugin;
+ CMOption<bool> &m_val;
+ CMStringW m_descr;
+};
+
+static OBJLIST<MPopupOption> g_arOptions(1);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPluginBase::addPopupOption(const char *pszDescr, CMOption<bool> &pVal)
+{
+ g_arOptions.insert(new MPopupOption(this, pszDescr, pVal));
+ return 0;
+}
+
+int CMPluginBase::addPopupOption(const wchar_t *pwszDescr, CMOption<bool> &pVal)
+{
+ g_arOptions.insert(new MPopupOption(this, pwszDescr, pVal));
+ return 0;
+}
+
+void KillModulePopups(CMPluginBase *pPlugin)
+{
+ for (auto &it : g_arOptions.rev_iter())
+ if (it->m_plugin == pPlugin)
+ g_arOptions.remove(g_arOptions.indexOf(&it));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+class CPopupOptionsDlg : public CDlgBase
+{
+ CCtrlListView m_list;
+
+public:
+ CPopupOptionsDlg() :
+ CDlgBase(g_plugin, IDD_OPT_POPUPOPTION),
+ m_list(this, IDC_TREE)
+ {}
+
+ bool OnInitDialog() override
+ {
+ m_list.SetExtendedListViewStyleEx(0, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT);
+
+ LVITEM lvi;
+ lvi.mask = LVIF_TEXT | LVIF_PARAM;
+ lvi.iSubItem = 0;
+
+ for (auto &it : g_arOptions) {
+ lvi.pszText = TranslateW_LP(it->m_descr, it->m_plugin);
+ lvi.lParam = LPARAM(it);
+
+ int iRow = m_list.InsertItem(&lvi);
+ m_list.SetItemState(iRow, it->m_val ? 0x2000 : 0x1000, LVIS_STATEIMAGEMASK);
+ }
+
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ int iRows = m_list.GetItemCount();
+
+ for (int i = 0; i < iRows; i++) {
+ auto *p = (MPopupOption *)m_list.GetItemData(i);
+ p->m_val = m_list.GetItemState(i, LVIS_STATEIMAGEMASK) == 0x2000;
+ }
+ return true;
+ }
+};
+
+int PopupOptionsInit(WPARAM wParam)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.position = -1000000000;
+ odp.szGroup.a = LPGEN("Popups");
+ odp.szTitle.a = LPGEN("Events");
+ odp.pDialog = new CPopupOptionsDlg();
+ odp.flags = ODPF_BOLDGROUPS;
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/src/mir_app/src/popups.cpp b/src/mir_app/src/popups.cpp index bc4d5f86d8..aa7af870f3 100644 --- a/src/mir_app/src/popups.cpp +++ b/src/mir_app/src/popups.cpp @@ -1,157 +1,157 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -static bool bModuleInit = false, bPopupsEnabled = true; - -MIR_APP_DLL(bool) Popup_Enabled() -{ - if (!bModuleInit) { - bModuleInit = true; - bPopupsEnabled = db_get_b(0, "Popup", "ModuleIsEnabled", 1) != 0; - } - - return bPopupsEnabled; -} - -MIR_APP_DLL(void) Popup_Enable(bool bEnable) -{ - bPopupsEnabled = bEnable; - db_set_b(0, "Popup", "ModuleIsEnabled", bEnable); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Popup v2.0 - -MIR_APP_DLL(HWND) Popup_Add(const POPUPDATA2 *ppdp, int flags) -{ - return (HWND)CallService(MS_POPUP_ADDPOPUP2, (WPARAM)ppdp, flags); -} - -MIR_APP_DLL(void) Popup_Change(HWND hwndPopup, const POPUPDATA2 *pData) -{ - CallService(MS_POPUP_CHANGEPOPUP2, (WPARAM)hwndPopup, (LPARAM)pData); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Popups v1.0 - -MIR_APP_DLL(HWND) PUAddPopup(POPUPDATA *ppdp, int flags) -{ - return (HWND)CallService(MS_POPUP_ADDPOPUP, (WPARAM)ppdp, flags); -} - -MIR_APP_DLL(HWND) PUAddPopupW(POPUPDATAW *ppdp, int flags) -{ - return (HWND)CallService(MS_POPUP_ADDPOPUPW, (WPARAM)ppdp, flags); -} - -MIR_APP_DLL(int) PUChangeW(HWND hWndPopup, POPUPDATAW *newData) -{ - return (int)CallService(MS_POPUP_CHANGEW, (WPARAM)hWndPopup, (LPARAM)newData); -} - -MIR_APP_DLL(int) PUChangeTextW(HWND hWndPopup, const wchar_t *lpwzNewText) -{ - return (int)CallService(MS_POPUP_CHANGETEXTW, (WPARAM)hWndPopup, (LPARAM)lpwzNewText); -} - -MIR_APP_DLL(int) PUDeletePopup(HWND hWndPopup) -{ - return (int)CallService(MS_POPUP_DESTROYPOPUP, 0, (LPARAM)hWndPopup); -} - -MIR_APP_DLL(MCONTACT) PUGetContact(HWND hPopupWindow) -{ - return (MCONTACT)CallService(MS_POPUP_GETCONTACT, (WPARAM)hPopupWindow, 0); -} - -MIR_APP_DLL(void*) PUGetPluginData(HWND hPopupWindow) -{ - return (void*)CallService(MS_POPUP_GETPLUGINDATA, (WPARAM)hPopupWindow, 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Messages - -MIR_APP_DLL(int) PUShowMessage(const char *lpzText, uint32_t kind) -{ - return (int)CallService(MS_POPUP_SHOWMESSAGE, (WPARAM)lpzText, (LPARAM)kind); -} - -MIR_APP_DLL(int) PUShowMessageW(const wchar_t *lpwzText, uint32_t kind) -{ - return (int)CallService(MS_POPUP_SHOWMESSAGEW, (WPARAM)lpwzText, (LPARAM)kind); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Actions - -MIR_APP_DLL(int) PURegisterActions(POPUPACTION *actions, int count) -{ - return (int)CallService(MS_POPUP_REGISTERACTIONS, (WPARAM)actions, (LPARAM)count); -} - -MIR_APP_DLL(HANDLE) PURegisterNotification(POPUPNOTIFICATION *notification) -{ - return (HANDLE)CallService(MS_POPUP_REGISTERNOTIFICATION, (WPARAM)notification, 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Popup classes - -MIR_APP_DLL(HANDLE) Popup_RegisterClass(POPUPCLASS *pc) -{ - return (HANDLE)CallService(MS_POPUP_REGISTERCLASS, 0, LPARAM(pc)); -} - -MIR_APP_DLL(void) Popup_UnregisterClass(HANDLE ppc) -{ - if (ppc) - CallService(MS_POPUP_UNREGISTERCLASS, 0, LPARAM(ppc)); -} - -MIR_APP_DLL(HWND) Popup_AddClass(POPUPDATACLASS *pData) -{ - return (HWND)CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)pData); -} - -MIR_APP_DLL(HWND) ShowClassPopup(const char *name, const char *title, const char *text) -{ - POPUPDATACLASS d = {}; - d.pszClassName = name; - d.szTitle.a = title; - d.szText.a = text; - return (HWND)CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&d); -} - -MIR_APP_DLL(HWND) ShowClassPopupW(const char *name, const wchar_t *title, const wchar_t *text) -{ - POPUPDATACLASS d = {}; - d.pszClassName = name; - d.szTitle.w = title; - d.szText.w = text; - return (HWND)CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&d); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+static bool bModuleInit = false, bPopupsEnabled = true;
+
+MIR_APP_DLL(bool) Popup_Enabled()
+{
+ if (!bModuleInit) {
+ bModuleInit = true;
+ bPopupsEnabled = db_get_b(0, "Popup", "ModuleIsEnabled", 1) != 0;
+ }
+
+ return bPopupsEnabled;
+}
+
+MIR_APP_DLL(void) Popup_Enable(bool bEnable)
+{
+ bPopupsEnabled = bEnable;
+ db_set_b(0, "Popup", "ModuleIsEnabled", bEnable);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popup v2.0
+
+MIR_APP_DLL(HWND) Popup_Add(const POPUPDATA2 *ppdp, int flags)
+{
+ return (HWND)CallService(MS_POPUP_ADDPOPUP2, (WPARAM)ppdp, flags);
+}
+
+MIR_APP_DLL(void) Popup_Change(HWND hwndPopup, const POPUPDATA2 *pData)
+{
+ CallService(MS_POPUP_CHANGEPOPUP2, (WPARAM)hwndPopup, (LPARAM)pData);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popups v1.0
+
+MIR_APP_DLL(HWND) PUAddPopup(POPUPDATA *ppdp, int flags)
+{
+ return (HWND)CallService(MS_POPUP_ADDPOPUP, (WPARAM)ppdp, flags);
+}
+
+MIR_APP_DLL(HWND) PUAddPopupW(POPUPDATAW *ppdp, int flags)
+{
+ return (HWND)CallService(MS_POPUP_ADDPOPUPW, (WPARAM)ppdp, flags);
+}
+
+MIR_APP_DLL(int) PUChangeW(HWND hWndPopup, POPUPDATAW *newData)
+{
+ return (int)CallService(MS_POPUP_CHANGEW, (WPARAM)hWndPopup, (LPARAM)newData);
+}
+
+MIR_APP_DLL(int) PUChangeTextW(HWND hWndPopup, const wchar_t *lpwzNewText)
+{
+ return (int)CallService(MS_POPUP_CHANGETEXTW, (WPARAM)hWndPopup, (LPARAM)lpwzNewText);
+}
+
+MIR_APP_DLL(int) PUDeletePopup(HWND hWndPopup)
+{
+ return (int)CallService(MS_POPUP_DESTROYPOPUP, 0, (LPARAM)hWndPopup);
+}
+
+MIR_APP_DLL(MCONTACT) PUGetContact(HWND hPopupWindow)
+{
+ return (MCONTACT)CallService(MS_POPUP_GETCONTACT, (WPARAM)hPopupWindow, 0);
+}
+
+MIR_APP_DLL(void*) PUGetPluginData(HWND hPopupWindow)
+{
+ return (void*)CallService(MS_POPUP_GETPLUGINDATA, (WPARAM)hPopupWindow, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Messages
+
+MIR_APP_DLL(int) PUShowMessage(const char *lpzText, uint32_t kind)
+{
+ return (int)CallService(MS_POPUP_SHOWMESSAGE, (WPARAM)lpzText, (LPARAM)kind);
+}
+
+MIR_APP_DLL(int) PUShowMessageW(const wchar_t *lpwzText, uint32_t kind)
+{
+ return (int)CallService(MS_POPUP_SHOWMESSAGEW, (WPARAM)lpwzText, (LPARAM)kind);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Actions
+
+MIR_APP_DLL(int) PURegisterActions(POPUPACTION *actions, int count)
+{
+ return (int)CallService(MS_POPUP_REGISTERACTIONS, (WPARAM)actions, (LPARAM)count);
+}
+
+MIR_APP_DLL(HANDLE) PURegisterNotification(POPUPNOTIFICATION *notification)
+{
+ return (HANDLE)CallService(MS_POPUP_REGISTERNOTIFICATION, (WPARAM)notification, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popup classes
+
+MIR_APP_DLL(HANDLE) Popup_RegisterClass(POPUPCLASS *pc)
+{
+ return (HANDLE)CallService(MS_POPUP_REGISTERCLASS, 0, LPARAM(pc));
+}
+
+MIR_APP_DLL(void) Popup_UnregisterClass(HANDLE ppc)
+{
+ if (ppc)
+ CallService(MS_POPUP_UNREGISTERCLASS, 0, LPARAM(ppc));
+}
+
+MIR_APP_DLL(HWND) Popup_AddClass(POPUPDATACLASS *pData)
+{
+ return (HWND)CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)pData);
+}
+
+MIR_APP_DLL(HWND) ShowClassPopup(const char *name, const char *title, const char *text)
+{
+ POPUPDATACLASS d = {};
+ d.pszClassName = name;
+ d.szTitle.a = title;
+ d.szText.a = text;
+ return (HWND)CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&d);
+}
+
+MIR_APP_DLL(HWND) ShowClassPopupW(const char *name, const wchar_t *title, const wchar_t *text)
+{
+ POPUPDATACLASS d = {};
+ d.pszClassName = name;
+ d.szTitle.w = title;
+ d.szText.w = text;
+ return (HWND)CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&d);
+}
diff --git a/src/mir_app/src/profilemanager.cpp b/src/mir_app/src/profilemanager.cpp index edbd7101cb..b2ea141b6c 100644 --- a/src/mir_app/src/profilemanager.cpp +++ b/src/mir_app/src/profilemanager.cpp @@ -1,641 +1,641 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "plugins.h" -#include "langpack.h" -#include "profilemanager.h" -#include <sys/stat.h> - -#pragma warning(disable : 4512) - -#define WM_INPUTCHANGED (WM_USER + 0x3000) -#define WM_FOCUSTEXTBOX (WM_USER + 0x3001) - -///////////////////////////////////////////////////////////////////////////////////////// -// Profile creator - -static BOOL EnumProfilesForList(const wchar_t *tszFullPath, wchar_t *profile, CCtrlListView &list, const wchar_t *szProfile) -{ - wchar_t sizeBuf[64]; - bool bFileLocked; - - wchar_t *p = wcsrchr(profile, '.'); - mir_wstrcpy(sizeBuf, L"0 KB"); - if (p != nullptr) *p = 0; - - LVITEM item = { 0 }; - item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM; - item.pszText = profile; - item.iItem = 0; - - struct _stat statbuf; - if (_wstat(tszFullPath, &statbuf) == 0) { - if (statbuf.st_size > 1000000) { - mir_snwprintf(sizeBuf, L"%.3lf", (double)statbuf.st_size / 1048576.0); - mir_wstrcpy(sizeBuf + 5, L" MB"); - } - else { - mir_snwprintf(sizeBuf, L"%.3lf", (double)statbuf.st_size / 1024.0); - mir_wstrcpy(sizeBuf + 5, L" KB"); - } - bFileLocked = Profile_CheckOpened(tszFullPath); - } - else bFileLocked = true; - - DATABASELINK *dblink; - switch (touchDatabase(tszFullPath, &dblink)) { - case ERROR_SUCCESS: - item.iImage = (bFileLocked) ? 1 : 0; - break; - - case EGROKPRF_OBSOLETE: - item.iImage = 2; - break; - - case EGROKPRF_CANTREAD: - item.iImage = (bFileLocked) ? 1 : 3; - break; - - default: - item.iImage = 3; - } - - item.lParam = (LPARAM)dblink; - - int iItem = list.InsertItem(&item); - if (mir_wstrcmpi(szProfile, tszFullPath) == 0) - list.SetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); - - list.SetItemText(iItem, 2, sizeBuf); - - if (dblink != nullptr) - list.SetItemText(iItem, 1, TranslateW(dblink->szFullName)); - else if (bFileLocked) // file locked - list.SetItemText(iItem, 1, TranslateT("<In use>")); - else - list.SetItemText(iItem, 1, TranslateT("<Unknown format>")); - - return TRUE; -} - -static int findProfiles(CCtrlListView &list, const wchar_t *szProfile) -{ - // find in Miranda NG profile subfolders - MFilePath searchspec; - searchspec.Format(L"%s\\*.*", g_profileDir); - - for (auto &it: searchspec.search()) { - // find all subfolders except "." and ".." - if (!it.isDir() || !wcscmp(it.getPath(), L".") || !wcscmp(it.getPath(), L"..")) - continue; - - MFilePath fullPath; - fullPath.Format(L"%s\\%s\\%s.dat", g_profileDir, it.getPath(), it.getPath()); - if (fullPath.isExist()) { - wchar_t profileName[MAX_PATH]; - mir_snwprintf(profileName, L"%s.dat", it.getPath()); - if (!EnumProfilesForList(fullPath, profileName, list, szProfile)) - break; - } - } - - return 1; -} - -static LRESULT CALLBACK ProfileNameValidate(HWND edit, UINT msg, WPARAM wParam, LPARAM lParam) -{ - if (msg == WM_CHAR) { - if (wcschr(L".?/\\#' ", (wchar_t)wParam) != nullptr) - return 0; - PostMessage(GetParent(edit), WM_INPUTCHANGED, 0, 0); - } - return mir_callNextSubclass(edit, ProfileNameValidate, msg, wParam, lParam); -} - -class CCreateProfileDlg : public CDlgBase -{ - CCtrlButton &m_btnOk; - PROFILEMANAGERDATA *m_pd; - - int CreateProfile(const wchar_t *profile, DATABASELINK *link) - { - // check if the file already exists - const wchar_t *file = wcsrchr(profile, '\\'); - if (file) - file++; - - int err = 0; - wchar_t buf[256]; - - if (_waccess(profile, 0) == 0) { - // file already exists! - mir_snwprintf(buf, - TranslateT("The profile '%s' already exists. Do you want to move it to the Recycle Bin?\n\nWARNING: The profile will be deleted if Recycle Bin is disabled.\nWARNING: A profile may contain confidential information and should be properly deleted."), - file); - if (MessageBoxW(m_hwnd, buf, TranslateT("The profile already exists"), MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) != IDYES) - return 0; - - // move the file - if (DeleteDirectoryTreeW(profile, true) != 0) { - mir_snwprintf(buf, TranslateT("Couldn't move '%s' to the Recycle Bin. Please select another profile name."), file); - MessageBoxW(m_hwnd, buf, TranslateT("Problem moving profile"), MB_ICONINFORMATION | MB_OK); - return 0; - } - // now the file should be gone! - } - // ask the database to create the profile - CreatePathToFileW(profile); - if ((err = link->makeDatabase(profile)) != ERROR_SUCCESS) { - mir_snwprintf(buf, TranslateT("Unable to create the profile '%s', the error was %x"), file, err); - MessageBoxW(m_hwnd, buf, TranslateT("Problem creating profile"), MB_ICONERROR | MB_OK); - return 0; - } - - // the profile has been created! - g_bDbCreated = true; - return 1; - } - - bool m_bFocused; - CCtrlCombo m_driverList; - CCtrlEdit m_profileName; - CCtrlBase m_warning; - -public: - CCreateProfileDlg(CCtrlButton &_btn, PROFILEMANAGERDATA *_pd) : - CDlgBase(g_plugin, IDD_PROFILE_NEW), - m_btnOk(_btn), - m_pd(_pd), - m_bFocused(false), - m_driverList(this, IDC_PROFILEDRIVERS), - m_profileName(this, IDC_PROFILENAME), - m_warning(this, IDC_NODBDRIVERS) - {} - - bool OnInitDialog() override - { - // what, no plugins?! - if (arDbPlugins.getCount() == 0) { - m_driverList.Enable(false); - m_profileName.Enable(false); - ShowWindow(m_warning.GetHwnd(), TRUE); - } - else { - for (auto &p : arDbPlugins) - if (p->capabilities & MDB_CAPS_CREATE) - m_driverList.AddString(TranslateW(p->szFullName), (LPARAM)p); - } - - // default item - m_driverList.SetCurSel(0); - - // subclass the profile name box - mir_subclassWindow(m_profileName.GetHwnd(), ProfileNameValidate); - - // decide if there is a default profile name given in the INI and if it should be used - if (m_pd->noProfiles || (shouldAutoCreate(m_pd->m_profile) && !m_pd->m_profile.isExist())) { - wchar_t *profile = wcsrchr(m_pd->m_profile.GetBuffer(), '\\'); - if (profile) ++profile; - else profile = m_pd->m_profile.GetBuffer(); - - wchar_t *p = wcsrchr(profile, '.'); - wchar_t c = 0; - if (p) { c = *p; *p = 0; } - - m_profileName.SetText(profile); - if (c) *p = c; - } - - // focus on the textbox - PostMessage(m_hwnd, WM_FOCUSTEXTBOX, 0, 0); - return true; - } - - INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override - { - switch (msg) { - case WM_FOCUSTEXTBOX: - SetFocus(m_profileName.GetHwnd()); - break; - - case WM_INPUTCHANGED: // when input in the edit box changes - NotifyChange(); - m_btnOk.Enable(GetWindowTextLength(m_profileName.GetHwnd()) > 0); - break; - - case WM_SHOWWINDOW: - if (wParam) { - m_btnOk.SetText(TranslateT("&Create")); - SendMessage(m_hwnd, WM_INPUTCHANGED, 0, 0); - m_bFocused = true; - } - else m_bFocused = false; - break; - } - return CDlgBase::DlgProc(msg, wParam, lParam); - } - - bool OnApply() override - { - LRESULT curSel = m_driverList.GetCurSel(); - if (curSel == -1 || !m_bFocused) - return false; // should never happen - - ptrW szName(m_profileName.GetText()); - if (mir_wstrlen(szName) == 0) - return false; - - // profile placed in "profile_name" subfolder - m_pd->m_profile.Format(L"%s\\%s\\%s.dat", g_profileDir, szName.get(), szName.get()); - m_pd->dblink = (DATABASELINK *)m_driverList.GetItemData(curSel); - - if (CreateProfile(m_pd->m_profile, m_pd->dblink) == 0) - SetWindowLongPtr(m_hwnd, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE); - return true; - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// Profile selector - -static int numMessages[5]; - -static void stubAddMessage(int iType, const wchar_t *, ...) -{ - if (iType < 5) - numMessages[iType]++; -} - -class CChooseProfileDlg : public CDlgBase -{ - CCtrlButton &m_btnOk; - PROFILEMANAGERDATA *m_pd; - HANDLE m_hFileNotify; - - void DeleteProfile(const LVITEM &item) - { - CMStringW wszMessage(FORMAT, TranslateT("Are you sure you want to remove profile \"%s\"?"), item.pszText); - if (IDYES != MessageBoxW(nullptr, wszMessage, L"Miranda NG", MB_YESNO | MB_TASKMODAL | MB_ICONWARNING)) - return; - - wszMessage.Format(L"%s\\%s", g_profileDir, item.pszText); - DeleteDirectoryTreeW(wszMessage, true); - - m_profileList.DeleteItem(item.iItem); - } - - void CheckProfile(const wchar_t *profile) - { - CMStringW wszFullName(FORMAT, L"%s\\%s\\%s.dat", g_profileDir, profile, profile); - - if (TryLoadPlugin(plugin_checker, false)) - CallService(MS_DB_CHECKPROFILE, (WPARAM)wszFullName.c_str(), 0); - else - Plugin_Uninit(plugin_checker); - } - - void CompactProfile(DATABASELINK *dblink, const wchar_t *profile) - { - CMStringW wszFullName(FORMAT, L"%s\\%s\\%s.dat", g_profileDir, profile, profile); - - if (auto *db = dblink->Load(wszFullName, false)) { - db->Compact(); - delete db; - - MessageBoxW(nullptr, TranslateT("Database was compacted successfully"), L"Miranda NG", MB_OK | MB_ICONINFORMATION); - } - } - - void CheckRun() - { - m_btnOk.Enable(m_profileList.GetSelectedCount() == 1); - - wchar_t profile[MAX_PATH]; - LVITEM item = { 0 }; - item.mask = LVIF_TEXT | LVIF_IMAGE; - item.iItem = m_profileList.GetNextItem(-1, LVNI_SELECTED | LVNI_ALL); - item.pszText = profile; - item.cchTextMax = _countof(profile); - if (!m_profileList.GetItem(&item)) - return; - - switch(item.iImage) { - case 3: - m_btnOk.Enable(false); - return; - - case 2: - m_btnOk.SetText(TranslateT("&Convert")); - break; - - default: - m_btnOk.SetText(TranslateT("&Run")); - } - - // profile is placed in "profile_name" subfolder - - wchar_t tmpPath[MAX_PATH]; - mir_snwprintf(tmpPath, L"%s\\%s.dat", g_profileDir, profile); - if (_waccess(tmpPath, 2)) - m_pd->m_profile.Format(L"%s\\%s\\%s.dat", g_profileDir, profile, profile); - else - m_pd->m_profile = tmpPath; - } - - void ExecuteMenu(LPARAM lParam) - { - LVHITTESTINFO lvht = { 0 }; - lvht.pt.x = GET_X_LPARAM(lParam); - lvht.pt.y = GET_Y_LPARAM(lParam); - ScreenToClient(m_profileList.GetHwnd(), &lvht.pt); - - if (m_profileList.HitTest(&lvht) == -1) - return; - - if (lvht.iItem == -1) - return; - - wchar_t profile[MAX_PATH]; - LVITEM item = { 0 }; - item.mask = LVIF_IMAGE | LVIF_PARAM | LVIF_TEXT; - item.iItem = lvht.iItem; - item.pszText = profile; - item.cchTextMax = _countof(profile); - if (!m_profileList.GetItem(&item)) - return; - - lvht.pt.x = GET_X_LPARAM(lParam); - lvht.pt.y = GET_Y_LPARAM(lParam); - - HMENU hMenu = CreatePopupMenu(); - if (item.iImage < 2) { - AppendMenu(hMenu, MF_STRING, 1, TranslateT("Run")); - AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); - } - - DATABASELINK *dblink = (DATABASELINK*)item.lParam; - if (dblink != nullptr) { - bool bAdded = false; - if (dblink->capabilities & MDB_CAPS_COMPACT) { - AppendMenu(hMenu, MF_STRING, 3, TranslateT("Compact database")); - bAdded = true; - } - - if (plugin_checker && (dblink->capabilities & MDB_CAPS_CHECK)) { - AppendMenu(hMenu, MF_STRING, 4, TranslateT("Check database")); - bAdded = true; - } - - if (bAdded) - AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr); - } - - AppendMenu(hMenu, MF_STRING, 2, TranslateT("Delete")); - int index = TrackPopupMenu(hMenu, TPM_RETURNCMD, lvht.pt.x, lvht.pt.y, 0, m_hwnd, nullptr); - switch (index) { - case 1: - SendMessage(GetParent(m_hwndParent), WM_COMMAND, IDOK, 0); - break; - - case 2: - DeleteProfile(item); - break; - - case 3: - CompactProfile(dblink, profile); - break; - - case 4: - CheckProfile(profile); - break; - } - DestroyMenu(hMenu); - } - - CCtrlListView m_profileList; - -public: - CChooseProfileDlg(CCtrlButton &_btn, PROFILEMANAGERDATA *_pd) : - CDlgBase(g_plugin, IDD_PROFILE_SELECTION), - m_btnOk(_btn), - m_pd(_pd), - m_profileList(this, IDC_PROFILELIST) - { - m_profileList.OnItemChanged = Callback(this, &CChooseProfileDlg::list_OnItemChanged); - m_profileList.OnKeyDown = Callback(this, &CChooseProfileDlg::list_OnKeyDown); - m_profileList.OnGetInfoTip = Callback(this, &CChooseProfileDlg::list_OnGetTip); - m_profileList.OnDoubleClick = Callback(this, &CChooseProfileDlg::list_OnDblClick); - } - - bool OnInitDialog() override - { - // set columns - LVCOLUMN col; - col.mask = LVCF_TEXT | LVCF_WIDTH; - col.pszText = TranslateT("Profile"); - col.cx = 100; - m_profileList.InsertColumn(0, &col); - - col.pszText = TranslateT("Driver"); - col.cx = 150 - GetSystemMetrics(SM_CXVSCROLL); - m_profileList.InsertColumn(1, &col); - - col.pszText = TranslateT("Size"); - col.cx = 60; - m_profileList.InsertColumn(2, &col); - - // icons - HIMAGELIST hImgList = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 2, 1); - ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_USERDETAILS)); - ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_DELETE)); - ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_MWARNING)); - ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_MFATAL)); - - // LV will destroy the image list - m_profileList.SetImageList(hImgList, LVSIL_SMALL); - m_profileList.SetExtendedListViewStyle(m_profileList.GetExtendedListViewStyle() | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT); - - // find all the profiles - findProfiles(m_profileList, m_pd->m_profile); - PostMessage(m_hwnd, WM_FOCUSTEXTBOX, 0, 0); - - m_hFileNotify = FindFirstChangeNotification(g_profileDir, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE); - if (m_hFileNotify != INVALID_HANDLE_VALUE) - SetTimer(m_hwnd, 0, 1200, nullptr); - return true; - } - - void OnDestroy() - { - KillTimer(m_hwnd, 0); - FindCloseChangeNotification(m_hFileNotify); - } - - void list_OnItemChanged(CCtrlListView::TEventInfo*) - { - CheckRun(); - } - - void list_OnKeyDown(CCtrlListView::TEventInfo *evt) - { - if (evt->nmlvkey->wVKey == VK_DELETE) { - wchar_t profile[MAX_PATH]; - LVITEM item = { 0 }; - item.mask = LVIF_TEXT; - item.iItem = m_profileList.GetNextItem(-1, LVNI_SELECTED | LVNI_ALL); - item.pszText = profile; - item.cchTextMax = _countof(profile); - if (m_profileList.GetItem(&item)) - DeleteProfile(item); - } - } - - void list_OnGetTip(CCtrlListView::TEventInfo *evt) - { - if (auto pTip = evt->nmlvit) { - wchar_t profilename[MAX_PATH], tszFullPath[MAX_PATH]; - struct _stat statbuf; - m_profileList.GetItemText(pTip->iItem, 0, profilename, _countof(profilename)); - mir_snwprintf(tszFullPath, L"%s\\%s\\%s.dat", g_profileDir, profilename, profilename); - _wstat(tszFullPath, &statbuf); - mir_snwprintf(pTip->pszText, pTip->cchTextMax, L"%s\n%s: %s\n%s: %s", tszFullPath, TranslateT("Created"), rtrimw(NEWWSTR_ALLOCA(_wctime(&statbuf.st_ctime))), TranslateT("Modified"), rtrimw(NEWWSTR_ALLOCA(_wctime(&statbuf.st_mtime)))); - } - } - - void list_OnDblClick(CCtrlListView::TEventInfo*) - { - CheckRun(); - EndDialog(GetParent(m_hwndParent), 1); - } - - INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override - { - switch (msg) { - case WM_TIMER: - if (WaitForSingleObject(m_hFileNotify, 0) == WAIT_OBJECT_0) { - m_profileList.DeleteAllItems(); - findProfiles(m_profileList, m_pd->m_profile); - FindNextChangeNotification(m_hFileNotify); - } - break; - - case WM_FOCUSTEXTBOX: - SetFocus(m_profileList.GetHwnd()); - if (m_pd->m_profile.IsEmpty() || m_profileList.GetSelectedCount() == 0) - m_profileList.SetItemState(0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); - break; - - case WM_SHOWWINDOW: - if (wParam) - CheckRun(); - break; - - case WM_CONTEXTMENU: - ExecuteMenu(lParam); - break; - } - - return CDlgBase::DlgProc(msg, wParam, lParam); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// Tab manager + its envelope - -class CProfileManager : public CDlgBase -{ - PROFILEMANAGERDATA *m_pd; - - CCtrlPages m_tab; - CCtrlButton m_btnOk; - CCtrlCheck m_chkSmEnabled; - CCtrlCombo m_servicePlugs; - CCtrlBase m_warning; - -public: - CProfileManager(PROFILEMANAGERDATA *_pd) : - CDlgBase(g_plugin, IDD_PROFILEMANAGER), - m_btnOk(this, IDOK), - m_pd(_pd), - m_tab(this, IDC_TABS), - m_warning(this, IDC_SM_LABEL), - m_servicePlugs(this, IDC_SM_COMBO), - m_chkSmEnabled(this, IDC_SM_ENABLED) - { - m_chkSmEnabled.OnChange = Callback(this, &CProfileManager::onChanged); - - m_tab.AddPage(LPGENW("My profiles"), nullptr, new CChooseProfileDlg(m_btnOk, m_pd)); - m_tab.AddPage(LPGENW("New profile"), nullptr, new CCreateProfileDlg(m_btnOk, m_pd)); - } - - bool OnInitDialog() override - { - // MUST NOT be replaced with Window_SetIcon_IcoLib!!! - SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(IDI_DETAILSLOGO), IMAGE_ICON, g_iIconSX, g_iIconSY, 0)); - SendMessage(m_hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(IDI_DETAILSLOGO), IMAGE_ICON, g_iIconX, g_iIconY, 0)); - - if (m_pd->noProfiles || shouldAutoCreate(m_pd->m_profile)) - m_tab.ActivatePage(1); - - // service mode combobox - if (servicePlugins.getCount() == 0) { - ShowWindow(m_warning.GetHwnd(), FALSE); - ShowWindow(m_chkSmEnabled.GetHwnd(), FALSE); - ShowWindow(m_servicePlugs.GetHwnd(), FALSE); - } - else { - for (int i = 0; i < servicePlugins.getCount(); i++) { - pluginEntry *p = servicePlugins[i]; - m_servicePlugs.AddStringA(p->pluginname, i); - } - - m_servicePlugs.Disable(); - m_servicePlugs.SetCurSel(0); - } - return true; - } - - void OnDestroy() - { - if (m_chkSmEnabled.GetState()) { - int idx = m_servicePlugs.GetCurData(); - if (idx != -1) - plugin_service = servicePlugins[idx]; - } - - DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, 0)); - DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_BIG, 0)); - } - - void onChanged(CCtrlCheck*) - { - m_servicePlugs.Enable(m_chkSmEnabled.GetState()); - } -}; - -int getProfileManager(PROFILEMANAGERDATA *pd) -{ - return CProfileManager(pd).DoModal(); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "plugins.h"
+#include "langpack.h"
+#include "profilemanager.h"
+#include <sys/stat.h>
+
+#pragma warning(disable : 4512)
+
+#define WM_INPUTCHANGED (WM_USER + 0x3000)
+#define WM_FOCUSTEXTBOX (WM_USER + 0x3001)
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Profile creator
+
+static BOOL EnumProfilesForList(const wchar_t *tszFullPath, wchar_t *profile, CCtrlListView &list, const wchar_t *szProfile)
+{
+ wchar_t sizeBuf[64];
+ bool bFileLocked;
+
+ wchar_t *p = wcsrchr(profile, '.');
+ mir_wstrcpy(sizeBuf, L"0 KB");
+ if (p != nullptr) *p = 0;
+
+ LVITEM item = { 0 };
+ item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
+ item.pszText = profile;
+ item.iItem = 0;
+
+ struct _stat statbuf;
+ if (_wstat(tszFullPath, &statbuf) == 0) {
+ if (statbuf.st_size > 1000000) {
+ mir_snwprintf(sizeBuf, L"%.3lf", (double)statbuf.st_size / 1048576.0);
+ mir_wstrcpy(sizeBuf + 5, L" MB");
+ }
+ else {
+ mir_snwprintf(sizeBuf, L"%.3lf", (double)statbuf.st_size / 1024.0);
+ mir_wstrcpy(sizeBuf + 5, L" KB");
+ }
+ bFileLocked = Profile_CheckOpened(tszFullPath);
+ }
+ else bFileLocked = true;
+
+ DATABASELINK *dblink;
+ switch (touchDatabase(tszFullPath, &dblink)) {
+ case ERROR_SUCCESS:
+ item.iImage = (bFileLocked) ? 1 : 0;
+ break;
+
+ case EGROKPRF_OBSOLETE:
+ item.iImage = 2;
+ break;
+
+ case EGROKPRF_CANTREAD:
+ item.iImage = (bFileLocked) ? 1 : 3;
+ break;
+
+ default:
+ item.iImage = 3;
+ }
+
+ item.lParam = (LPARAM)dblink;
+
+ int iItem = list.InsertItem(&item);
+ if (mir_wstrcmpi(szProfile, tszFullPath) == 0)
+ list.SetItemState(iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
+
+ list.SetItemText(iItem, 2, sizeBuf);
+
+ if (dblink != nullptr)
+ list.SetItemText(iItem, 1, TranslateW(dblink->szFullName));
+ else if (bFileLocked) // file locked
+ list.SetItemText(iItem, 1, TranslateT("<In use>"));
+ else
+ list.SetItemText(iItem, 1, TranslateT("<Unknown format>"));
+
+ return TRUE;
+}
+
+static int findProfiles(CCtrlListView &list, const wchar_t *szProfile)
+{
+ // find in Miranda NG profile subfolders
+ MFilePath searchspec;
+ searchspec.Format(L"%s\\*.*", g_profileDir);
+
+ for (auto &it: searchspec.search()) {
+ // find all subfolders except "." and ".."
+ if (!it.isDir() || !wcscmp(it.getPath(), L".") || !wcscmp(it.getPath(), L".."))
+ continue;
+
+ MFilePath fullPath;
+ fullPath.Format(L"%s\\%s\\%s.dat", g_profileDir, it.getPath(), it.getPath());
+ if (fullPath.isExist()) {
+ wchar_t profileName[MAX_PATH];
+ mir_snwprintf(profileName, L"%s.dat", it.getPath());
+ if (!EnumProfilesForList(fullPath, profileName, list, szProfile))
+ break;
+ }
+ }
+
+ return 1;
+}
+
+static LRESULT CALLBACK ProfileNameValidate(HWND edit, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (msg == WM_CHAR) {
+ if (wcschr(L".?/\\#' ", (wchar_t)wParam) != nullptr)
+ return 0;
+ PostMessage(GetParent(edit), WM_INPUTCHANGED, 0, 0);
+ }
+ return mir_callNextSubclass(edit, ProfileNameValidate, msg, wParam, lParam);
+}
+
+class CCreateProfileDlg : public CDlgBase
+{
+ CCtrlButton &m_btnOk;
+ PROFILEMANAGERDATA *m_pd;
+
+ int CreateProfile(const wchar_t *profile, DATABASELINK *link)
+ {
+ // check if the file already exists
+ const wchar_t *file = wcsrchr(profile, '\\');
+ if (file)
+ file++;
+
+ int err = 0;
+ wchar_t buf[256];
+
+ if (_waccess(profile, 0) == 0) {
+ // file already exists!
+ mir_snwprintf(buf,
+ TranslateT("The profile '%s' already exists. Do you want to move it to the Recycle Bin?\n\nWARNING: The profile will be deleted if Recycle Bin is disabled.\nWARNING: A profile may contain confidential information and should be properly deleted."),
+ file);
+ if (MessageBoxW(m_hwnd, buf, TranslateT("The profile already exists"), MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) != IDYES)
+ return 0;
+
+ // move the file
+ if (DeleteDirectoryTreeW(profile, true) != 0) {
+ mir_snwprintf(buf, TranslateT("Couldn't move '%s' to the Recycle Bin. Please select another profile name."), file);
+ MessageBoxW(m_hwnd, buf, TranslateT("Problem moving profile"), MB_ICONINFORMATION | MB_OK);
+ return 0;
+ }
+ // now the file should be gone!
+ }
+ // ask the database to create the profile
+ CreatePathToFileW(profile);
+ if ((err = link->makeDatabase(profile)) != ERROR_SUCCESS) {
+ mir_snwprintf(buf, TranslateT("Unable to create the profile '%s', the error was %x"), file, err);
+ MessageBoxW(m_hwnd, buf, TranslateT("Problem creating profile"), MB_ICONERROR | MB_OK);
+ return 0;
+ }
+
+ // the profile has been created!
+ g_bDbCreated = true;
+ return 1;
+ }
+
+ bool m_bFocused;
+ CCtrlCombo m_driverList;
+ CCtrlEdit m_profileName;
+ CCtrlBase m_warning;
+
+public:
+ CCreateProfileDlg(CCtrlButton &_btn, PROFILEMANAGERDATA *_pd) :
+ CDlgBase(g_plugin, IDD_PROFILE_NEW),
+ m_btnOk(_btn),
+ m_pd(_pd),
+ m_bFocused(false),
+ m_driverList(this, IDC_PROFILEDRIVERS),
+ m_profileName(this, IDC_PROFILENAME),
+ m_warning(this, IDC_NODBDRIVERS)
+ {}
+
+ bool OnInitDialog() override
+ {
+ // what, no plugins?!
+ if (arDbPlugins.getCount() == 0) {
+ m_driverList.Enable(false);
+ m_profileName.Enable(false);
+ ShowWindow(m_warning.GetHwnd(), TRUE);
+ }
+ else {
+ for (auto &p : arDbPlugins)
+ if (p->capabilities & MDB_CAPS_CREATE)
+ m_driverList.AddString(TranslateW(p->szFullName), (LPARAM)p);
+ }
+
+ // default item
+ m_driverList.SetCurSel(0);
+
+ // subclass the profile name box
+ mir_subclassWindow(m_profileName.GetHwnd(), ProfileNameValidate);
+
+ // decide if there is a default profile name given in the INI and if it should be used
+ if (m_pd->noProfiles || (shouldAutoCreate(m_pd->m_profile) && !m_pd->m_profile.isExist())) {
+ wchar_t *profile = wcsrchr(m_pd->m_profile.GetBuffer(), '\\');
+ if (profile) ++profile;
+ else profile = m_pd->m_profile.GetBuffer();
+
+ wchar_t *p = wcsrchr(profile, '.');
+ wchar_t c = 0;
+ if (p) { c = *p; *p = 0; }
+
+ m_profileName.SetText(profile);
+ if (c) *p = c;
+ }
+
+ // focus on the textbox
+ PostMessage(m_hwnd, WM_FOCUSTEXTBOX, 0, 0);
+ return true;
+ }
+
+ INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override
+ {
+ switch (msg) {
+ case WM_FOCUSTEXTBOX:
+ SetFocus(m_profileName.GetHwnd());
+ break;
+
+ case WM_INPUTCHANGED: // when input in the edit box changes
+ NotifyChange();
+ m_btnOk.Enable(GetWindowTextLength(m_profileName.GetHwnd()) > 0);
+ break;
+
+ case WM_SHOWWINDOW:
+ if (wParam) {
+ m_btnOk.SetText(TranslateT("&Create"));
+ SendMessage(m_hwnd, WM_INPUTCHANGED, 0, 0);
+ m_bFocused = true;
+ }
+ else m_bFocused = false;
+ break;
+ }
+ return CDlgBase::DlgProc(msg, wParam, lParam);
+ }
+
+ bool OnApply() override
+ {
+ LRESULT curSel = m_driverList.GetCurSel();
+ if (curSel == -1 || !m_bFocused)
+ return false; // should never happen
+
+ ptrW szName(m_profileName.GetText());
+ if (mir_wstrlen(szName) == 0)
+ return false;
+
+ // profile placed in "profile_name" subfolder
+ m_pd->m_profile.Format(L"%s\\%s\\%s.dat", g_profileDir, szName.get(), szName.get());
+ m_pd->dblink = (DATABASELINK *)m_driverList.GetItemData(curSel);
+
+ if (CreateProfile(m_pd->m_profile, m_pd->dblink) == 0)
+ SetWindowLongPtr(m_hwnd, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
+ return true;
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Profile selector
+
+static int numMessages[5];
+
+static void stubAddMessage(int iType, const wchar_t *, ...)
+{
+ if (iType < 5)
+ numMessages[iType]++;
+}
+
+class CChooseProfileDlg : public CDlgBase
+{
+ CCtrlButton &m_btnOk;
+ PROFILEMANAGERDATA *m_pd;
+ HANDLE m_hFileNotify;
+
+ void DeleteProfile(const LVITEM &item)
+ {
+ CMStringW wszMessage(FORMAT, TranslateT("Are you sure you want to remove profile \"%s\"?"), item.pszText);
+ if (IDYES != MessageBoxW(nullptr, wszMessage, L"Miranda NG", MB_YESNO | MB_TASKMODAL | MB_ICONWARNING))
+ return;
+
+ wszMessage.Format(L"%s\\%s", g_profileDir, item.pszText);
+ DeleteDirectoryTreeW(wszMessage, true);
+
+ m_profileList.DeleteItem(item.iItem);
+ }
+
+ void CheckProfile(const wchar_t *profile)
+ {
+ CMStringW wszFullName(FORMAT, L"%s\\%s\\%s.dat", g_profileDir, profile, profile);
+
+ if (TryLoadPlugin(plugin_checker, false))
+ CallService(MS_DB_CHECKPROFILE, (WPARAM)wszFullName.c_str(), 0);
+ else
+ Plugin_Uninit(plugin_checker);
+ }
+
+ void CompactProfile(DATABASELINK *dblink, const wchar_t *profile)
+ {
+ CMStringW wszFullName(FORMAT, L"%s\\%s\\%s.dat", g_profileDir, profile, profile);
+
+ if (auto *db = dblink->Load(wszFullName, false)) {
+ db->Compact();
+ delete db;
+
+ MessageBoxW(nullptr, TranslateT("Database was compacted successfully"), L"Miranda NG", MB_OK | MB_ICONINFORMATION);
+ }
+ }
+
+ void CheckRun()
+ {
+ m_btnOk.Enable(m_profileList.GetSelectedCount() == 1);
+
+ wchar_t profile[MAX_PATH];
+ LVITEM item = { 0 };
+ item.mask = LVIF_TEXT | LVIF_IMAGE;
+ item.iItem = m_profileList.GetNextItem(-1, LVNI_SELECTED | LVNI_ALL);
+ item.pszText = profile;
+ item.cchTextMax = _countof(profile);
+ if (!m_profileList.GetItem(&item))
+ return;
+
+ switch(item.iImage) {
+ case 3:
+ m_btnOk.Enable(false);
+ return;
+
+ case 2:
+ m_btnOk.SetText(TranslateT("&Convert"));
+ break;
+
+ default:
+ m_btnOk.SetText(TranslateT("&Run"));
+ }
+
+ // profile is placed in "profile_name" subfolder
+
+ wchar_t tmpPath[MAX_PATH];
+ mir_snwprintf(tmpPath, L"%s\\%s.dat", g_profileDir, profile);
+ if (_waccess(tmpPath, 2))
+ m_pd->m_profile.Format(L"%s\\%s\\%s.dat", g_profileDir, profile, profile);
+ else
+ m_pd->m_profile = tmpPath;
+ }
+
+ void ExecuteMenu(LPARAM lParam)
+ {
+ LVHITTESTINFO lvht = { 0 };
+ lvht.pt.x = GET_X_LPARAM(lParam);
+ lvht.pt.y = GET_Y_LPARAM(lParam);
+ ScreenToClient(m_profileList.GetHwnd(), &lvht.pt);
+
+ if (m_profileList.HitTest(&lvht) == -1)
+ return;
+
+ if (lvht.iItem == -1)
+ return;
+
+ wchar_t profile[MAX_PATH];
+ LVITEM item = { 0 };
+ item.mask = LVIF_IMAGE | LVIF_PARAM | LVIF_TEXT;
+ item.iItem = lvht.iItem;
+ item.pszText = profile;
+ item.cchTextMax = _countof(profile);
+ if (!m_profileList.GetItem(&item))
+ return;
+
+ lvht.pt.x = GET_X_LPARAM(lParam);
+ lvht.pt.y = GET_Y_LPARAM(lParam);
+
+ HMENU hMenu = CreatePopupMenu();
+ if (item.iImage < 2) {
+ AppendMenu(hMenu, MF_STRING, 1, TranslateT("Run"));
+ AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
+ }
+
+ DATABASELINK *dblink = (DATABASELINK*)item.lParam;
+ if (dblink != nullptr) {
+ bool bAdded = false;
+ if (dblink->capabilities & MDB_CAPS_COMPACT) {
+ AppendMenu(hMenu, MF_STRING, 3, TranslateT("Compact database"));
+ bAdded = true;
+ }
+
+ if (plugin_checker && (dblink->capabilities & MDB_CAPS_CHECK)) {
+ AppendMenu(hMenu, MF_STRING, 4, TranslateT("Check database"));
+ bAdded = true;
+ }
+
+ if (bAdded)
+ AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
+ }
+
+ AppendMenu(hMenu, MF_STRING, 2, TranslateT("Delete"));
+ int index = TrackPopupMenu(hMenu, TPM_RETURNCMD, lvht.pt.x, lvht.pt.y, 0, m_hwnd, nullptr);
+ switch (index) {
+ case 1:
+ SendMessage(GetParent(m_hwndParent), WM_COMMAND, IDOK, 0);
+ break;
+
+ case 2:
+ DeleteProfile(item);
+ break;
+
+ case 3:
+ CompactProfile(dblink, profile);
+ break;
+
+ case 4:
+ CheckProfile(profile);
+ break;
+ }
+ DestroyMenu(hMenu);
+ }
+
+ CCtrlListView m_profileList;
+
+public:
+ CChooseProfileDlg(CCtrlButton &_btn, PROFILEMANAGERDATA *_pd) :
+ CDlgBase(g_plugin, IDD_PROFILE_SELECTION),
+ m_btnOk(_btn),
+ m_pd(_pd),
+ m_profileList(this, IDC_PROFILELIST)
+ {
+ m_profileList.OnItemChanged = Callback(this, &CChooseProfileDlg::list_OnItemChanged);
+ m_profileList.OnKeyDown = Callback(this, &CChooseProfileDlg::list_OnKeyDown);
+ m_profileList.OnGetInfoTip = Callback(this, &CChooseProfileDlg::list_OnGetTip);
+ m_profileList.OnDoubleClick = Callback(this, &CChooseProfileDlg::list_OnDblClick);
+ }
+
+ bool OnInitDialog() override
+ {
+ // set columns
+ LVCOLUMN col;
+ col.mask = LVCF_TEXT | LVCF_WIDTH;
+ col.pszText = TranslateT("Profile");
+ col.cx = 100;
+ m_profileList.InsertColumn(0, &col);
+
+ col.pszText = TranslateT("Driver");
+ col.cx = 150 - GetSystemMetrics(SM_CXVSCROLL);
+ m_profileList.InsertColumn(1, &col);
+
+ col.pszText = TranslateT("Size");
+ col.cx = 60;
+ m_profileList.InsertColumn(2, &col);
+
+ // icons
+ HIMAGELIST hImgList = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 2, 1);
+ ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_USERDETAILS));
+ ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_DELETE));
+ ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_MWARNING));
+ ImageList_AddIcon_NotShared(hImgList, MAKEINTRESOURCE(IDI_MFATAL));
+
+ // LV will destroy the image list
+ m_profileList.SetImageList(hImgList, LVSIL_SMALL);
+ m_profileList.SetExtendedListViewStyle(m_profileList.GetExtendedListViewStyle() | LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_LABELTIP | LVS_EX_FULLROWSELECT);
+
+ // find all the profiles
+ findProfiles(m_profileList, m_pd->m_profile);
+ PostMessage(m_hwnd, WM_FOCUSTEXTBOX, 0, 0);
+
+ m_hFileNotify = FindFirstChangeNotification(g_profileDir, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE);
+ if (m_hFileNotify != INVALID_HANDLE_VALUE)
+ SetTimer(m_hwnd, 0, 1200, nullptr);
+ return true;
+ }
+
+ void OnDestroy()
+ {
+ KillTimer(m_hwnd, 0);
+ FindCloseChangeNotification(m_hFileNotify);
+ }
+
+ void list_OnItemChanged(CCtrlListView::TEventInfo*)
+ {
+ CheckRun();
+ }
+
+ void list_OnKeyDown(CCtrlListView::TEventInfo *evt)
+ {
+ if (evt->nmlvkey->wVKey == VK_DELETE) {
+ wchar_t profile[MAX_PATH];
+ LVITEM item = { 0 };
+ item.mask = LVIF_TEXT;
+ item.iItem = m_profileList.GetNextItem(-1, LVNI_SELECTED | LVNI_ALL);
+ item.pszText = profile;
+ item.cchTextMax = _countof(profile);
+ if (m_profileList.GetItem(&item))
+ DeleteProfile(item);
+ }
+ }
+
+ void list_OnGetTip(CCtrlListView::TEventInfo *evt)
+ {
+ if (auto pTip = evt->nmlvit) {
+ wchar_t profilename[MAX_PATH], tszFullPath[MAX_PATH];
+ struct _stat statbuf;
+ m_profileList.GetItemText(pTip->iItem, 0, profilename, _countof(profilename));
+ mir_snwprintf(tszFullPath, L"%s\\%s\\%s.dat", g_profileDir, profilename, profilename);
+ _wstat(tszFullPath, &statbuf);
+ mir_snwprintf(pTip->pszText, pTip->cchTextMax, L"%s\n%s: %s\n%s: %s", tszFullPath, TranslateT("Created"), rtrimw(NEWWSTR_ALLOCA(_wctime(&statbuf.st_ctime))), TranslateT("Modified"), rtrimw(NEWWSTR_ALLOCA(_wctime(&statbuf.st_mtime))));
+ }
+ }
+
+ void list_OnDblClick(CCtrlListView::TEventInfo*)
+ {
+ CheckRun();
+ EndDialog(GetParent(m_hwndParent), 1);
+ }
+
+ INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override
+ {
+ switch (msg) {
+ case WM_TIMER:
+ if (WaitForSingleObject(m_hFileNotify, 0) == WAIT_OBJECT_0) {
+ m_profileList.DeleteAllItems();
+ findProfiles(m_profileList, m_pd->m_profile);
+ FindNextChangeNotification(m_hFileNotify);
+ }
+ break;
+
+ case WM_FOCUSTEXTBOX:
+ SetFocus(m_profileList.GetHwnd());
+ if (m_pd->m_profile.IsEmpty() || m_profileList.GetSelectedCount() == 0)
+ m_profileList.SetItemState(0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
+ break;
+
+ case WM_SHOWWINDOW:
+ if (wParam)
+ CheckRun();
+ break;
+
+ case WM_CONTEXTMENU:
+ ExecuteMenu(lParam);
+ break;
+ }
+
+ return CDlgBase::DlgProc(msg, wParam, lParam);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Tab manager + its envelope
+
+class CProfileManager : public CDlgBase
+{
+ PROFILEMANAGERDATA *m_pd;
+
+ CCtrlPages m_tab;
+ CCtrlButton m_btnOk;
+ CCtrlCheck m_chkSmEnabled;
+ CCtrlCombo m_servicePlugs;
+ CCtrlBase m_warning;
+
+public:
+ CProfileManager(PROFILEMANAGERDATA *_pd) :
+ CDlgBase(g_plugin, IDD_PROFILEMANAGER),
+ m_btnOk(this, IDOK),
+ m_pd(_pd),
+ m_tab(this, IDC_TABS),
+ m_warning(this, IDC_SM_LABEL),
+ m_servicePlugs(this, IDC_SM_COMBO),
+ m_chkSmEnabled(this, IDC_SM_ENABLED)
+ {
+ m_chkSmEnabled.OnChange = Callback(this, &CProfileManager::onChanged);
+
+ m_tab.AddPage(LPGENW("My profiles"), nullptr, new CChooseProfileDlg(m_btnOk, m_pd));
+ m_tab.AddPage(LPGENW("New profile"), nullptr, new CCreateProfileDlg(m_btnOk, m_pd));
+ }
+
+ bool OnInitDialog() override
+ {
+ // MUST NOT be replaced with Window_SetIcon_IcoLib!!!
+ SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(IDI_DETAILSLOGO), IMAGE_ICON, g_iIconSX, g_iIconSY, 0));
+ SendMessage(m_hwnd, WM_SETICON, ICON_BIG, (LPARAM)LoadImage(g_plugin.getInst(), MAKEINTRESOURCE(IDI_DETAILSLOGO), IMAGE_ICON, g_iIconX, g_iIconY, 0));
+
+ if (m_pd->noProfiles || shouldAutoCreate(m_pd->m_profile))
+ m_tab.ActivatePage(1);
+
+ // service mode combobox
+ if (servicePlugins.getCount() == 0) {
+ ShowWindow(m_warning.GetHwnd(), FALSE);
+ ShowWindow(m_chkSmEnabled.GetHwnd(), FALSE);
+ ShowWindow(m_servicePlugs.GetHwnd(), FALSE);
+ }
+ else {
+ for (int i = 0; i < servicePlugins.getCount(); i++) {
+ pluginEntry *p = servicePlugins[i];
+ m_servicePlugs.AddStringA(p->pluginname, i);
+ }
+
+ m_servicePlugs.Disable();
+ m_servicePlugs.SetCurSel(0);
+ }
+ return true;
+ }
+
+ void OnDestroy()
+ {
+ if (m_chkSmEnabled.GetState()) {
+ int idx = m_servicePlugs.GetCurData();
+ if (idx != -1)
+ plugin_service = servicePlugins[idx];
+ }
+
+ DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, 0));
+ DestroyIcon((HICON)SendMessage(m_hwnd, WM_SETICON, ICON_BIG, 0));
+ }
+
+ void onChanged(CCtrlCheck*)
+ {
+ m_servicePlugs.Enable(m_chkSmEnabled.GetState());
+ }
+};
+
+int getProfileManager(PROFILEMANAGERDATA *pd)
+{
+ return CProfileManager(pd).DoModal();
+}
diff --git a/src/mir_app/src/profilemanager.h b/src/mir_app/src/profilemanager.h index 39685e82bc..ab7e9e8a14 100644 --- a/src/mir_app/src/profilemanager.h +++ b/src/mir_app/src/profilemanager.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/proto_accs.cpp b/src/mir_app/src/proto_accs.cpp index 8947568395..501497361c 100644 --- a/src/mir_app/src/proto_accs.cpp +++ b/src/mir_app/src/proto_accs.cpp @@ -1,435 +1,435 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "clc.h" - -bool CheckProtocolOrder(void); -void BuildProtoMenus(); - -HICON Proto_GetIcon(PROTO_INTERFACE *ppro, int iconIndex); - -static bool bModuleInitialized = false; -static HANDLE hHooks[4]; - -static int CompareAccounts(const PROTOACCOUNT* p1, const PROTOACCOUNT* p2) -{ - return mir_strcmp(p1->szModuleName, p2->szModuleName); -} - -LIST<PROTOACCOUNT> g_arAccounts(10, CompareAccounts); - -///////////////////////////////////////////////////////////////////////////////////////// - -static int EnumDbModules(const char *szModuleName, void*) -{ - ptrA szProtoName(db_get_sa(0, szModuleName, "AM_BaseProto")); - if (szProtoName) { - if (!Proto_GetAccount(szModuleName)) { - PROTOACCOUNT *pa = new PROTOACCOUNT(szModuleName); - pa->szProtoName = szProtoName.detach(); - pa->tszAccountName = mir_a2u(szModuleName); - pa->bIsVisible = true; - pa->bIsEnabled = false; - pa->iOrder = g_arAccounts.getCount(); - g_arAccounts.insert(pa); - } - } - return 0; -} - -void LoadDbAccounts(void) -{ - int ver = db_get_dw(0, "Protocols", "PrVer", -1); - int count = db_get_dw(0, "Protocols", "ProtoCount", 0); - - for (int i = 0; i < count; i++) { - char buf[10]; - _itoa(i, buf, 10); - ptrA szModuleName(db_get_sa(0, "Protocols", buf)); - if (szModuleName == nullptr) - continue; - - PROTOACCOUNT *pa = Proto_GetAccount(szModuleName); - if (pa == nullptr) { - pa = new PROTOACCOUNT(szModuleName); - g_arAccounts.insert(pa); - } - - _itoa(OFFSET_VISIBLE + i, buf, 10); - pa->bIsVisible = db_get_dw(0, "Protocols", buf, 1) != 0; - - _itoa(OFFSET_PROTOPOS + i, buf, 10); - pa->iOrder = db_get_dw(0, "Protocols", buf, 1); - - if (ver >= 4) { - _itoa(OFFSET_NAME + i, buf, 10); - pa->tszAccountName = db_get_wsa(0, "Protocols", buf); - - _itoa(OFFSET_ENABLED + i, buf, 10); - pa->bIsEnabled = db_get_dw(0, "Protocols", buf, 1) != 0; - if (!pa->bIsEnabled && !mir_strcmp(pa->szModuleName, META_PROTO)) { - pa->bIsEnabled = true; - db_set_dw(0, "Protocols", buf, 1); - } - pa->szProtoName = db_get_sa(0, szModuleName, "AM_BaseProto"); - } - else pa->bIsEnabled = true; - - if (!pa->szProtoName) { - pa->szProtoName = mir_strdup(szModuleName); - db_set_s(0, szModuleName, "AM_BaseProto", pa->szProtoName); - } - - if (!pa->tszAccountName) - pa->tszAccountName = mir_a2u(szModuleName); - } - - if (CheckProtocolOrder()) - WriteDbAccounts(); - - int anum = g_arAccounts.getCount(); - db_enum_modules(EnumDbModules); - if (anum != g_arAccounts.getCount()) - WriteDbAccounts(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void WriteDbAccounts() -{ - // enum all old settings to delete - db_delete_module(0, "Protocols"); - - // write new data - for (int i = 0; i < g_arAccounts.getCount(); i++) { - PROTOACCOUNT *pa = g_arAccounts[i]; - - char buf[20]; - _itoa(i, buf, 10); - db_set_s(0, "Protocols", buf, pa->szModuleName); - - _itoa(OFFSET_PROTOPOS + i, buf, 10); - db_set_dw(0, "Protocols", buf, pa->iOrder); - - _itoa(OFFSET_VISIBLE + i, buf, 10); - db_set_dw(0, "Protocols", buf, pa->bIsVisible); - - _itoa(OFFSET_ENABLED + i, buf, 10); - db_set_dw(0, "Protocols", buf, pa->bIsEnabled); - - _itoa(OFFSET_NAME + i, buf, 10); - db_set_ws(0, "Protocols", buf, pa->tszAccountName); - } - - db_set_dw(0, "Protocols", "ProtoCount", g_arAccounts.getCount()); - db_set_dw(0, "Protocols", "PrVer", 4); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static int OnContactDeleted(WPARAM hContact, LPARAM) -{ - if (auto *ppro = Proto_GetInstance(hContact)) - ppro->OnContactDeleted(hContact); - return 0; -} - -static int OnEventEdited(WPARAM hContact, LPARAM hDbEvent) -{ - if (auto *ppro = Proto_GetInstance(hContact)) - ppro->OnEventEdited(hContact, hDbEvent); - return 0; -} - -void InitStaticAccounts() -{ - int count = 0; - - for (auto &pa : g_arAccounts) { - if (!pa->ppro || !pa->IsEnabled()) - continue; - - pa->ppro->OnModulesLoaded(); - - if (!pa->bOldProto) - count++; - - if (pa->IsVisible()) - pa->ppro->OnBuildProtoMenu(); - } - - if (count == 0 && !db_get_b(0, "FirstRun", "AccManager", 0)) { - db_set_b(0, "FirstRun", "AccManager", 1); - CallService(MS_PROTO_SHOWACCMGR, 0, 0); - } - // This is for pack creators with a profile with predefined g_arAccounts - else if (db_get_b(0, "FirstRun", "ForceShowAccManager", 0)) { - CallService(MS_PROTO_SHOWACCMGR, 0, 0); - db_unset(0, "FirstRun", "ForceShowAccManager"); - } -} - -static int UninitializeStaticAccounts(WPARAM, LPARAM) -{ - // request permission to exit first - for (auto &pa : g_arAccounts) - if (pa->ppro && pa->IsEnabled()) - if (!pa->ppro->IsReadyToExit()) - return 1; - - // okay, all protocols are ready, exiting - for (auto &pa : g_arAccounts) - if (pa->ppro && pa->IsEnabled()) - pa->ppro->OnShutdown(); - - return 0; -} - -int LoadAccountsModule(void) -{ - bModuleInitialized = true; - - for (auto &pa : g_arAccounts) { - pa->bDynDisabled = !Proto_IsProtocolLoaded(pa->szProtoName); - if (pa->ppro) - continue; - - if (!pa->IsEnabled()) - continue; - - if (!ActivateAccount(pa, false)) - pa->bDynDisabled = true; - } - - hHooks[1] = HookEvent(ME_SYSTEM_PRESHUTDOWN, UninitializeStaticAccounts); - hHooks[2] = HookEvent(ME_DB_CONTACT_DELETED, OnContactDeleted); - hHooks[3] = HookEvent(ME_DB_EVENT_EDITED, OnEventEdited); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static HANDLE CreateProtoServiceEx(const char* szModule, const char* szService, MIRANDASERVICEOBJ pFunc, void* param) -{ - char tmp[100]; - mir_snprintf(tmp, "%s%s", szModule, szService); - return CreateServiceFunctionObj(tmp, pFunc, param); -} - -bool ActivateAccount(PROTOACCOUNT *pa, bool bIsDynamic) -{ - MBaseProto *ppd = Proto_GetProto(pa->szProtoName); - if (ppd == nullptr) - return false; - - if (ppd->fnInit == nullptr) - return false; - - PROTO_INTERFACE *ppi = pa->ppro; - if (ppi == nullptr) { - ppi = ppd->fnInit(pa->szModuleName, pa->tszAccountName); - if (ppi == nullptr) - return false; - - pa->ppro = ppi; - - if (bIsDynamic) { - if (g_bModulesLoadedFired) - pa->ppro->OnModulesLoaded(); - if (!db_get_b(0, "CList", "MoveProtoMenus", true)) - pa->ppro->OnBuildProtoMenu(); - pa->bDynDisabled = false; - } - } - - if (ppi->m_hProtoIcon == nullptr) - ppi->m_hProtoIcon = IcoLib_IsManaged(Skin_LoadProtoIcon(pa->szModuleName, ID_STATUS_ONLINE)); - ppi->m_iDesiredStatus = ppi->m_iStatus = ID_STATUS_OFFLINE; - return true; -} - -MIR_APP_DLL(int) Proto_GetAverageStatus(int *pAccountNumber) -{ - int netProtoCount = 0, averageMode = 0; - - for (auto &pa : g_arAccounts) { - if (!pa->IsVisible() || pa->IsLocked()) - continue; - - netProtoCount++; - if (averageMode == 0) - averageMode = pa->iRealStatus; - else if (averageMode > 0 && averageMode != pa->iRealStatus) { - averageMode = -1; - if (pAccountNumber == nullptr) - break; - } - } - - if (pAccountNumber) - *pAccountNumber = netProtoCount; - return averageMode; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -struct DeactivationThreadParam -{ - PROTO_INTERFACE *ppro; - pfnUninitProto fnUninit; - int flags; -}; - -pfnUninitProto GetProtocolDestructor(char *szProto); - -static void __cdecl DeactivationThread(DeactivationThreadParam *param) -{ - PROTO_INTERFACE *p = (PROTO_INTERFACE*)param->ppro; - p->SetStatus(ID_STATUS_OFFLINE); - - char *szModuleName = NEWSTR_ALLOCA(p->m_szModuleName); - - if (param->flags & DAF_DYNAMIC) { - while (!p->IsReadyToExit()) - SleepEx(100, TRUE); - - p->OnShutdown(); - } - - KillObjectThreads(p); // waits for them before terminating - KillObjectEventHooks(p); // untie an object from the outside world - - if (param->flags & DAF_ERASE) - p->OnErase(); - - if (param->fnUninit) - param->fnUninit(p); - - KillObjectServices(p); - - if (param->flags & DAF_ERASE) - EraseAccount(szModuleName); - - delete param; -} - -void DeactivateAccount(PROTOACCOUNT *pa, int flags) -{ - if (pa->hwndAccMgrUI) { - DestroyWindow(pa->hwndAccMgrUI); - pa->hwndAccMgrUI = nullptr; - pa->bAccMgrUIChanged = FALSE; - } - - if (flags & DAF_DYNAMIC) - NotifyEventHooks(hAccListChanged, PRAC_REMOVED, (LPARAM)pa); - else - pa->iIconBase = -1; - - if (pa->ppro == nullptr) { - if (flags & DAF_ERASE) - EraseAccount(pa->szModuleName); - return; - } - - DeactivationThreadParam *param = new DeactivationThreadParam; - param->ppro = pa->ppro; - param->fnUninit = GetProtocolDestructor(pa->szProtoName); - param->flags = flags; - pa->ppro = nullptr; - if (flags & DAF_FORK) - mir_forkThread<DeactivationThreadParam>(DeactivationThread, param); - else - DeactivationThread(param); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void KillModuleAccounts(HINSTANCE hInst) -{ - for (auto &pd : g_arProtos.rev_iter()) { - if (pd->hInst != hInst) - continue; - - for (auto &pa : g_arAccounts.rev_iter()) { - if (!mir_strcmp(pa->szProtoName, pd->szName)) { - pa->bDynDisabled = true; - DeactivateAccount(pa, DAF_DYNAMIC); - } - } - - g_arProtos.removeItem(&pd); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void EraseAccount(const char *pszModuleName) -{ - // remove protocol contacts first - for (MCONTACT hContact = db_find_first(pszModuleName); hContact != 0;) { - MCONTACT hNext = db_find_next(hContact, pszModuleName); - db_delete_contact(hContact); - hContact = hNext; - } - - // remove all protocol settings - db_delete_module(0, pszModuleName); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void UnloadAccount(PROTOACCOUNT *pa, int flags) -{ - DeactivateAccount(pa, flags); - - // szModuleName should be freed only on a program's exit. - // otherwise many plugins dependand on static protocol names will crash! - // do NOT fix this 'leak', please - if (!(flags & DAF_DYNAMIC)) - delete pa; - else { - replaceStrW(pa->tszAccountName, 0); - replaceStr(pa->szProtoName, 0); - replaceStr(pa->szUniqueId, 0); - } -} - -void UnloadAccountsModule() -{ - if (!bModuleInitialized) - return; - - auto T = g_arAccounts.rev_iter(); - for (auto &it : T) { - UnloadAccount(it, 0); - g_arAccounts.removeItem(&it); - } - g_arAccounts.destroy(); - - for (auto &it : hHooks) - UnhookEvent(it); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "clc.h"
+
+bool CheckProtocolOrder(void);
+void BuildProtoMenus();
+
+HICON Proto_GetIcon(PROTO_INTERFACE *ppro, int iconIndex);
+
+static bool bModuleInitialized = false;
+static HANDLE hHooks[4];
+
+static int CompareAccounts(const PROTOACCOUNT* p1, const PROTOACCOUNT* p2)
+{
+ return mir_strcmp(p1->szModuleName, p2->szModuleName);
+}
+
+LIST<PROTOACCOUNT> g_arAccounts(10, CompareAccounts);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int EnumDbModules(const char *szModuleName, void*)
+{
+ ptrA szProtoName(db_get_sa(0, szModuleName, "AM_BaseProto"));
+ if (szProtoName) {
+ if (!Proto_GetAccount(szModuleName)) {
+ PROTOACCOUNT *pa = new PROTOACCOUNT(szModuleName);
+ pa->szProtoName = szProtoName.detach();
+ pa->tszAccountName = mir_a2u(szModuleName);
+ pa->bIsVisible = true;
+ pa->bIsEnabled = false;
+ pa->iOrder = g_arAccounts.getCount();
+ g_arAccounts.insert(pa);
+ }
+ }
+ return 0;
+}
+
+void LoadDbAccounts(void)
+{
+ int ver = db_get_dw(0, "Protocols", "PrVer", -1);
+ int count = db_get_dw(0, "Protocols", "ProtoCount", 0);
+
+ for (int i = 0; i < count; i++) {
+ char buf[10];
+ _itoa(i, buf, 10);
+ ptrA szModuleName(db_get_sa(0, "Protocols", buf));
+ if (szModuleName == nullptr)
+ continue;
+
+ PROTOACCOUNT *pa = Proto_GetAccount(szModuleName);
+ if (pa == nullptr) {
+ pa = new PROTOACCOUNT(szModuleName);
+ g_arAccounts.insert(pa);
+ }
+
+ _itoa(OFFSET_VISIBLE + i, buf, 10);
+ pa->bIsVisible = db_get_dw(0, "Protocols", buf, 1) != 0;
+
+ _itoa(OFFSET_PROTOPOS + i, buf, 10);
+ pa->iOrder = db_get_dw(0, "Protocols", buf, 1);
+
+ if (ver >= 4) {
+ _itoa(OFFSET_NAME + i, buf, 10);
+ pa->tszAccountName = db_get_wsa(0, "Protocols", buf);
+
+ _itoa(OFFSET_ENABLED + i, buf, 10);
+ pa->bIsEnabled = db_get_dw(0, "Protocols", buf, 1) != 0;
+ if (!pa->bIsEnabled && !mir_strcmp(pa->szModuleName, META_PROTO)) {
+ pa->bIsEnabled = true;
+ db_set_dw(0, "Protocols", buf, 1);
+ }
+ pa->szProtoName = db_get_sa(0, szModuleName, "AM_BaseProto");
+ }
+ else pa->bIsEnabled = true;
+
+ if (!pa->szProtoName) {
+ pa->szProtoName = mir_strdup(szModuleName);
+ db_set_s(0, szModuleName, "AM_BaseProto", pa->szProtoName);
+ }
+
+ if (!pa->tszAccountName)
+ pa->tszAccountName = mir_a2u(szModuleName);
+ }
+
+ if (CheckProtocolOrder())
+ WriteDbAccounts();
+
+ int anum = g_arAccounts.getCount();
+ db_enum_modules(EnumDbModules);
+ if (anum != g_arAccounts.getCount())
+ WriteDbAccounts();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void WriteDbAccounts()
+{
+ // enum all old settings to delete
+ db_delete_module(0, "Protocols");
+
+ // write new data
+ for (int i = 0; i < g_arAccounts.getCount(); i++) {
+ PROTOACCOUNT *pa = g_arAccounts[i];
+
+ char buf[20];
+ _itoa(i, buf, 10);
+ db_set_s(0, "Protocols", buf, pa->szModuleName);
+
+ _itoa(OFFSET_PROTOPOS + i, buf, 10);
+ db_set_dw(0, "Protocols", buf, pa->iOrder);
+
+ _itoa(OFFSET_VISIBLE + i, buf, 10);
+ db_set_dw(0, "Protocols", buf, pa->bIsVisible);
+
+ _itoa(OFFSET_ENABLED + i, buf, 10);
+ db_set_dw(0, "Protocols", buf, pa->bIsEnabled);
+
+ _itoa(OFFSET_NAME + i, buf, 10);
+ db_set_ws(0, "Protocols", buf, pa->tszAccountName);
+ }
+
+ db_set_dw(0, "Protocols", "ProtoCount", g_arAccounts.getCount());
+ db_set_dw(0, "Protocols", "PrVer", 4);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int OnContactDeleted(WPARAM hContact, LPARAM)
+{
+ if (auto *ppro = Proto_GetInstance(hContact))
+ ppro->OnContactDeleted(hContact);
+ return 0;
+}
+
+static int OnEventEdited(WPARAM hContact, LPARAM hDbEvent)
+{
+ if (auto *ppro = Proto_GetInstance(hContact))
+ ppro->OnEventEdited(hContact, hDbEvent);
+ return 0;
+}
+
+void InitStaticAccounts()
+{
+ int count = 0;
+
+ for (auto &pa : g_arAccounts) {
+ if (!pa->ppro || !pa->IsEnabled())
+ continue;
+
+ pa->ppro->OnModulesLoaded();
+
+ if (!pa->bOldProto)
+ count++;
+
+ if (pa->IsVisible())
+ pa->ppro->OnBuildProtoMenu();
+ }
+
+ if (count == 0 && !db_get_b(0, "FirstRun", "AccManager", 0)) {
+ db_set_b(0, "FirstRun", "AccManager", 1);
+ CallService(MS_PROTO_SHOWACCMGR, 0, 0);
+ }
+ // This is for pack creators with a profile with predefined g_arAccounts
+ else if (db_get_b(0, "FirstRun", "ForceShowAccManager", 0)) {
+ CallService(MS_PROTO_SHOWACCMGR, 0, 0);
+ db_unset(0, "FirstRun", "ForceShowAccManager");
+ }
+}
+
+static int UninitializeStaticAccounts(WPARAM, LPARAM)
+{
+ // request permission to exit first
+ for (auto &pa : g_arAccounts)
+ if (pa->ppro && pa->IsEnabled())
+ if (!pa->ppro->IsReadyToExit())
+ return 1;
+
+ // okay, all protocols are ready, exiting
+ for (auto &pa : g_arAccounts)
+ if (pa->ppro && pa->IsEnabled())
+ pa->ppro->OnShutdown();
+
+ return 0;
+}
+
+int LoadAccountsModule(void)
+{
+ bModuleInitialized = true;
+
+ for (auto &pa : g_arAccounts) {
+ pa->bDynDisabled = !Proto_IsProtocolLoaded(pa->szProtoName);
+ if (pa->ppro)
+ continue;
+
+ if (!pa->IsEnabled())
+ continue;
+
+ if (!ActivateAccount(pa, false))
+ pa->bDynDisabled = true;
+ }
+
+ hHooks[1] = HookEvent(ME_SYSTEM_PRESHUTDOWN, UninitializeStaticAccounts);
+ hHooks[2] = HookEvent(ME_DB_CONTACT_DELETED, OnContactDeleted);
+ hHooks[3] = HookEvent(ME_DB_EVENT_EDITED, OnEventEdited);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static HANDLE CreateProtoServiceEx(const char* szModule, const char* szService, MIRANDASERVICEOBJ pFunc, void* param)
+{
+ char tmp[100];
+ mir_snprintf(tmp, "%s%s", szModule, szService);
+ return CreateServiceFunctionObj(tmp, pFunc, param);
+}
+
+bool ActivateAccount(PROTOACCOUNT *pa, bool bIsDynamic)
+{
+ MBaseProto *ppd = Proto_GetProto(pa->szProtoName);
+ if (ppd == nullptr)
+ return false;
+
+ if (ppd->fnInit == nullptr)
+ return false;
+
+ PROTO_INTERFACE *ppi = pa->ppro;
+ if (ppi == nullptr) {
+ ppi = ppd->fnInit(pa->szModuleName, pa->tszAccountName);
+ if (ppi == nullptr)
+ return false;
+
+ pa->ppro = ppi;
+
+ if (bIsDynamic) {
+ if (g_bModulesLoadedFired)
+ pa->ppro->OnModulesLoaded();
+ if (!db_get_b(0, "CList", "MoveProtoMenus", true))
+ pa->ppro->OnBuildProtoMenu();
+ pa->bDynDisabled = false;
+ }
+ }
+
+ if (ppi->m_hProtoIcon == nullptr)
+ ppi->m_hProtoIcon = IcoLib_IsManaged(Skin_LoadProtoIcon(pa->szModuleName, ID_STATUS_ONLINE));
+ ppi->m_iDesiredStatus = ppi->m_iStatus = ID_STATUS_OFFLINE;
+ return true;
+}
+
+MIR_APP_DLL(int) Proto_GetAverageStatus(int *pAccountNumber)
+{
+ int netProtoCount = 0, averageMode = 0;
+
+ for (auto &pa : g_arAccounts) {
+ if (!pa->IsVisible() || pa->IsLocked())
+ continue;
+
+ netProtoCount++;
+ if (averageMode == 0)
+ averageMode = pa->iRealStatus;
+ else if (averageMode > 0 && averageMode != pa->iRealStatus) {
+ averageMode = -1;
+ if (pAccountNumber == nullptr)
+ break;
+ }
+ }
+
+ if (pAccountNumber)
+ *pAccountNumber = netProtoCount;
+ return averageMode;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct DeactivationThreadParam
+{
+ PROTO_INTERFACE *ppro;
+ pfnUninitProto fnUninit;
+ int flags;
+};
+
+pfnUninitProto GetProtocolDestructor(char *szProto);
+
+static void __cdecl DeactivationThread(DeactivationThreadParam *param)
+{
+ PROTO_INTERFACE *p = (PROTO_INTERFACE*)param->ppro;
+ p->SetStatus(ID_STATUS_OFFLINE);
+
+ char *szModuleName = NEWSTR_ALLOCA(p->m_szModuleName);
+
+ if (param->flags & DAF_DYNAMIC) {
+ while (!p->IsReadyToExit())
+ SleepEx(100, TRUE);
+
+ p->OnShutdown();
+ }
+
+ KillObjectThreads(p); // waits for them before terminating
+ KillObjectEventHooks(p); // untie an object from the outside world
+
+ if (param->flags & DAF_ERASE)
+ p->OnErase();
+
+ if (param->fnUninit)
+ param->fnUninit(p);
+
+ KillObjectServices(p);
+
+ if (param->flags & DAF_ERASE)
+ EraseAccount(szModuleName);
+
+ delete param;
+}
+
+void DeactivateAccount(PROTOACCOUNT *pa, int flags)
+{
+ if (pa->hwndAccMgrUI) {
+ DestroyWindow(pa->hwndAccMgrUI);
+ pa->hwndAccMgrUI = nullptr;
+ pa->bAccMgrUIChanged = FALSE;
+ }
+
+ if (flags & DAF_DYNAMIC)
+ NotifyEventHooks(hAccListChanged, PRAC_REMOVED, (LPARAM)pa);
+ else
+ pa->iIconBase = -1;
+
+ if (pa->ppro == nullptr) {
+ if (flags & DAF_ERASE)
+ EraseAccount(pa->szModuleName);
+ return;
+ }
+
+ DeactivationThreadParam *param = new DeactivationThreadParam;
+ param->ppro = pa->ppro;
+ param->fnUninit = GetProtocolDestructor(pa->szProtoName);
+ param->flags = flags;
+ pa->ppro = nullptr;
+ if (flags & DAF_FORK)
+ mir_forkThread<DeactivationThreadParam>(DeactivationThread, param);
+ else
+ DeactivationThread(param);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void KillModuleAccounts(HINSTANCE hInst)
+{
+ for (auto &pd : g_arProtos.rev_iter()) {
+ if (pd->hInst != hInst)
+ continue;
+
+ for (auto &pa : g_arAccounts.rev_iter()) {
+ if (!mir_strcmp(pa->szProtoName, pd->szName)) {
+ pa->bDynDisabled = true;
+ DeactivateAccount(pa, DAF_DYNAMIC);
+ }
+ }
+
+ g_arProtos.removeItem(&pd);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void EraseAccount(const char *pszModuleName)
+{
+ // remove protocol contacts first
+ for (MCONTACT hContact = db_find_first(pszModuleName); hContact != 0;) {
+ MCONTACT hNext = db_find_next(hContact, pszModuleName);
+ db_delete_contact(hContact);
+ hContact = hNext;
+ }
+
+ // remove all protocol settings
+ db_delete_module(0, pszModuleName);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void UnloadAccount(PROTOACCOUNT *pa, int flags)
+{
+ DeactivateAccount(pa, flags);
+
+ // szModuleName should be freed only on a program's exit.
+ // otherwise many plugins dependand on static protocol names will crash!
+ // do NOT fix this 'leak', please
+ if (!(flags & DAF_DYNAMIC))
+ delete pa;
+ else {
+ replaceStrW(pa->tszAccountName, 0);
+ replaceStr(pa->szProtoName, 0);
+ replaceStr(pa->szUniqueId, 0);
+ }
+}
+
+void UnloadAccountsModule()
+{
+ if (!bModuleInitialized)
+ return;
+
+ auto T = g_arAccounts.rev_iter();
+ for (auto &it : T) {
+ UnloadAccount(it, 0);
+ g_arAccounts.removeItem(&it);
+ }
+ g_arAccounts.destroy();
+
+ for (auto &it : hHooks)
+ UnhookEvent(it);
+}
diff --git a/src/mir_app/src/proto_chains.cpp b/src/mir_app/src/proto_chains.cpp index 0764a01164..5417c07995 100644 --- a/src/mir_app/src/proto_chains.cpp +++ b/src/mir_app/src/proto_chains.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/proto_interface.cpp b/src/mir_app/src/proto_interface.cpp index ed500cf69e..4ccab724de 100644 --- a/src/mir_app/src/proto_interface.cpp +++ b/src/mir_app/src/proto_interface.cpp @@ -1,351 +1,351 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -static HGENMENU hReqAuth = nullptr, hGrantAuth = nullptr, hRevokeAuth = nullptr, hServerHist = nullptr; - -///////////////////////////////////////////////////////////////////////////////////////// -// protocol constructor & destructor - -PROTO_INTERFACE::PROTO_INTERFACE(const char *pszModuleName, const wchar_t *ptszUserName) -{ - m_iVersion = 2; - m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; - m_szModuleName = mir_strdup(pszModuleName); - m_tszUserName = mir_wstrdup(ptszUserName); - db_set_resident(m_szModuleName, "Status"); -} - -PROTO_INTERFACE::~PROTO_INTERFACE() -{ - if (m_hNetlibUser) - Netlib_CloseHandle(m_hNetlibUser); - - mir_free(m_szModuleName); - mir_free(m_tszUserName); - - WindowList_Destroy(m_hWindowList); -} - -HGENMENU PROTO_INTERFACE::GetMenuItem(ProtoMenuItemType aType) -{ - switch (aType) { - case PROTO_MENU_REQ_AUTH: return hReqAuth; - case PROTO_MENU_GRANT_AUTH: return hGrantAuth; - case PROTO_MENU_REVOKE_AUTH: return hRevokeAuth; - case PROTO_MENU_LOAD_HISTORY: return hServerHist; - } - - return nullptr; -} - -void PROTO_INTERFACE::OnBuildProtoMenu() -{} - -void PROTO_INTERFACE::OnContactAdded(MCONTACT) -{} - -void PROTO_INTERFACE::OnContactDeleted(MCONTACT) -{} - -void PROTO_INTERFACE::OnEventEdited(MCONTACT, MEVENT) -{} - -void PROTO_INTERFACE::OnErase() -{} - -void PROTO_INTERFACE::OnModulesLoaded() -{} - -bool PROTO_INTERFACE::IsReadyToExit() -{ - return true; -} - -void PROTO_INTERFACE::OnShutdown() -{} - -///////////////////////////////////////////////////////////////////////////////////////// -// default PROTO_INTERFACE method implementations - -MCONTACT PROTO_INTERFACE::AddToList(int, PROTOSEARCHRESULT*) -{ - return 0; // error -} - -MCONTACT PROTO_INTERFACE::AddToListByEvent(int, int, MEVENT) -{ - return 0; // error -} - -int PROTO_INTERFACE::Authorize(MEVENT) -{ - return 1; // error -} - -int PROTO_INTERFACE::AuthDeny(MEVENT, const wchar_t*) -{ - return 1; // error -} - -int PROTO_INTERFACE::AuthRecv(MCONTACT, PROTORECVEVENT*) -{ - return 1; // error -} - -int PROTO_INTERFACE::AuthRequest(MCONTACT, const wchar_t*) -{ - return 1; // error -} - -HANDLE PROTO_INTERFACE::FileAllow(MCONTACT, HANDLE, const wchar_t*) -{ - return nullptr; // error -} - -int PROTO_INTERFACE::FileCancel(MCONTACT, HANDLE) -{ - return 1; // error -} - -int PROTO_INTERFACE::FileDeny(MCONTACT, HANDLE, const wchar_t*) -{ - return 1; // error -} - -int PROTO_INTERFACE::FileResume(HANDLE, int, const wchar_t*) -{ - return 1; // error -} - -INT_PTR PROTO_INTERFACE::GetCaps(int, MCONTACT) -{ - return 0; // empty value -} - -int PROTO_INTERFACE::GetInfo(MCONTACT, int) -{ - return 1; // error -} - -HANDLE PROTO_INTERFACE::SearchBasic(const wchar_t*) -{ - return nullptr; // error -} - -HANDLE PROTO_INTERFACE::SearchByEmail(const wchar_t*) -{ - return nullptr; // error -} - -HANDLE PROTO_INTERFACE::SearchByName(const wchar_t*, const wchar_t*, const wchar_t*) -{ - return nullptr; // error -} - -HWND PROTO_INTERFACE::SearchAdvanced(HWND) -{ - return nullptr; // error -} - -HWND PROTO_INTERFACE::CreateExtendedSearchUI(HWND) -{ - return nullptr; // error -} - -int PROTO_INTERFACE::RecvContacts(MCONTACT, PROTORECVEVENT*) -{ - return 1; // error -} - -int PROTO_INTERFACE::RecvFile(MCONTACT hContact, PROTORECVFILE *pcre) -{ - CCSDATA ccs = { hContact, PSR_FILE, 0, (LPARAM)pcre }; - return CallService(MS_PROTO_RECVFILET, 0, (LPARAM)&ccs); -} - -MEVENT PROTO_INTERFACE::RecvMsg(MCONTACT hContact, PROTORECVEVENT *pre) -{ - if (pre->szMessage == nullptr) - return 0; - - ptrA pszTemp; - mir_ptr<uint8_t> pszBlob; - - DBEVENTINFO dbei = {}; - dbei.flags = DBEF_UTF; - dbei.szModule = Proto_GetBaseAccountName(hContact); - dbei.timestamp = pre->timestamp; - dbei.eventType = EVENTTYPE_MESSAGE; - dbei.cbBlob = (uint32_t)mir_strlen(pre->szMessage) + 1; - dbei.pBlob = (uint8_t*)pre->szMessage; - - if (pre->flags & PREF_CREATEREAD) - dbei.flags |= DBEF_READ; - if (pre->flags & PREF_SENT) - dbei.flags |= DBEF_SENT; - - // if it's possible to find an existing event by its id, do that - if ((GetCaps(PFLAGNUM_4) & PF4_SERVERMSGID) && pre->szMsgId != nullptr) { - MEVENT hDbEvent = db_event_getById(m_szModuleName, pre->szMsgId); - if (hDbEvent == 0 || db_event_edit(hContact, hDbEvent, &dbei)) { - dbei.szId = pre->szMsgId; - hDbEvent = db_event_add(hContact, &dbei); - } - return hDbEvent; - } - - // event is new? add it - return (INT_PTR)db_event_add(hContact, &dbei); -} - -int PROTO_INTERFACE::SendContacts(MCONTACT, int, int, MCONTACT*) -{ - return 1; // error -} - -HANDLE PROTO_INTERFACE::SendFile(MCONTACT, const wchar_t*, wchar_t**) -{ - return nullptr; // error -} - -int PROTO_INTERFACE::SendMsg(MCONTACT, int, const char*) -{ - return 0; // error -} - -int PROTO_INTERFACE::SetApparentMode(MCONTACT, int) -{ - return 1; // error -} - -int PROTO_INTERFACE::SetStatus(int) -{ - return 1; // you better declare it -} - -HANDLE PROTO_INTERFACE::GetAwayMsg(MCONTACT) -{ - return nullptr; // no away message -} - -int PROTO_INTERFACE::RecvAwayMsg(MCONTACT, int, PROTORECVEVENT*) -{ - return 1; // error -} - -int PROTO_INTERFACE::SetAwayMsg(int, const wchar_t*) -{ - return 1; // error -} - -int PROTO_INTERFACE::UserIsTyping(MCONTACT, int) -{ - return 1; // error -} - -///////////////////////////////////////////////////////////////////////////////////////// -// protocol menus - -static INT_PTR __cdecl stubRequestAuth(WPARAM hContact, LPARAM) -{ - const char *szProto = Proto_GetBaseAccountName(hContact); - if (szProto) - ProtoCallService(szProto, PS_MENU_REQAUTH, hContact, 0); - return 0; -} - -static INT_PTR __cdecl stubGrantAuth(WPARAM hContact, LPARAM) -{ - const char *szProto = Proto_GetBaseAccountName(hContact); - if (szProto) - ProtoCallService(szProto, PS_MENU_GRANTAUTH, hContact, 0); - return 0; -} - -static INT_PTR __cdecl stubRevokeAuth(WPARAM hContact, LPARAM) -{ - const char *szProto = Proto_GetBaseAccountName(hContact); - if (szProto) - ProtoCallService(szProto, PS_MENU_REVOKEAUTH, hContact, 0); - return 0; -} - -static INT_PTR __cdecl stubLoadHistory(WPARAM hContact, LPARAM) -{ - const char *szProto = Proto_GetBaseAccountName(hContact); - if (szProto) - ProtoCallService(szProto, PS_MENU_LOADHISTORY, hContact, 0); - return 0; -} - -static int __cdecl ProtoPrebuildContactMenu(WPARAM hContact, LPARAM) -{ - Menu_ShowItem(hReqAuth, false); - Menu_ShowItem(hGrantAuth, false); - Menu_ShowItem(hRevokeAuth, false); - - const char *szProto = Proto_GetBaseAccountName(hContact); - Menu_ShowItem(hServerHist, ProtoServiceExists(szProto, PS_MENU_LOADHISTORY)); - return 0; -} - -void InitProtoMenus(void) -{ - // "Request authorization" - CMenuItem mi(&g_plugin); - SET_UID(mi, 0x36375a1f, 0xc142, 0x4d6e, 0xa6, 0x57, 0xe4, 0x76, 0x5d, 0xbc, 0x59, 0x8e); - mi.pszService = "Proto/Menu/ReqAuth"; - mi.name.a = LPGEN("Request authorization"); - mi.position = -2000001002; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_AUTH_REQUEST); - hReqAuth = Menu_AddContactMenuItem(&mi); - CreateServiceFunction(mi.pszService, stubRequestAuth); - - // "Grant authorization" - SET_UID(mi, 0x4c90452a, 0x869a, 0x4a81, 0xaf, 0xa8, 0x28, 0x34, 0xaf, 0x2b, 0x6b, 0x30); - mi.pszService = "Proto/Menu/GrantAuth"; - mi.name.a = LPGEN("Grant authorization"); - mi.position = -2000001001; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_AUTH_GRANT); - hGrantAuth = Menu_AddContactMenuItem(&mi); - - // "Revoke authorization" - SET_UID(mi, 0x619efdcb, 0x99c0, 0x44a8, 0xbf, 0x28, 0xc3, 0xe0, 0x2f, 0xb3, 0x7e, 0x77); - mi.pszService = "Proto/Menu/RevokeAuth"; - mi.name.a = LPGEN("Revoke authorization"); - mi.position = -2000001000; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_AUTH_REVOKE); - hRevokeAuth = Menu_AddContactMenuItem(&mi); - - SET_UID(mi, 0xd15b841d, 0xb0fc, 0x4ab5, 0x96, 0x94, 0xcf, 0x6c, 0x6e, 0x99, 0x4b, 0x3c); // {D15B841D-B0FC-4AB5-9694-CF6C6E994B3C} - mi.pszService = "Proto/Menu/LoadHistory"; - mi.name.a = LPGEN("Load server history"); - mi.position = -200001004; - mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_HISTORY); - hServerHist = Menu_AddContactMenuItem(&mi); - CreateServiceFunction(mi.pszService, stubLoadHistory); - - HookEvent(ME_CLIST_PREBUILDCONTACTMENU, ProtoPrebuildContactMenu); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+static HGENMENU hReqAuth = nullptr, hGrantAuth = nullptr, hRevokeAuth = nullptr, hServerHist = nullptr;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// protocol constructor & destructor
+
+PROTO_INTERFACE::PROTO_INTERFACE(const char *pszModuleName, const wchar_t *ptszUserName)
+{
+ m_iVersion = 2;
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+ m_szModuleName = mir_strdup(pszModuleName);
+ m_tszUserName = mir_wstrdup(ptszUserName);
+ db_set_resident(m_szModuleName, "Status");
+}
+
+PROTO_INTERFACE::~PROTO_INTERFACE()
+{
+ if (m_hNetlibUser)
+ Netlib_CloseHandle(m_hNetlibUser);
+
+ mir_free(m_szModuleName);
+ mir_free(m_tszUserName);
+
+ WindowList_Destroy(m_hWindowList);
+}
+
+HGENMENU PROTO_INTERFACE::GetMenuItem(ProtoMenuItemType aType)
+{
+ switch (aType) {
+ case PROTO_MENU_REQ_AUTH: return hReqAuth;
+ case PROTO_MENU_GRANT_AUTH: return hGrantAuth;
+ case PROTO_MENU_REVOKE_AUTH: return hRevokeAuth;
+ case PROTO_MENU_LOAD_HISTORY: return hServerHist;
+ }
+
+ return nullptr;
+}
+
+void PROTO_INTERFACE::OnBuildProtoMenu()
+{}
+
+void PROTO_INTERFACE::OnContactAdded(MCONTACT)
+{}
+
+void PROTO_INTERFACE::OnContactDeleted(MCONTACT)
+{}
+
+void PROTO_INTERFACE::OnEventEdited(MCONTACT, MEVENT)
+{}
+
+void PROTO_INTERFACE::OnErase()
+{}
+
+void PROTO_INTERFACE::OnModulesLoaded()
+{}
+
+bool PROTO_INTERFACE::IsReadyToExit()
+{
+ return true;
+}
+
+void PROTO_INTERFACE::OnShutdown()
+{}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// default PROTO_INTERFACE method implementations
+
+MCONTACT PROTO_INTERFACE::AddToList(int, PROTOSEARCHRESULT*)
+{
+ return 0; // error
+}
+
+MCONTACT PROTO_INTERFACE::AddToListByEvent(int, int, MEVENT)
+{
+ return 0; // error
+}
+
+int PROTO_INTERFACE::Authorize(MEVENT)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::AuthDeny(MEVENT, const wchar_t*)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::AuthRecv(MCONTACT, PROTORECVEVENT*)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::AuthRequest(MCONTACT, const wchar_t*)
+{
+ return 1; // error
+}
+
+HANDLE PROTO_INTERFACE::FileAllow(MCONTACT, HANDLE, const wchar_t*)
+{
+ return nullptr; // error
+}
+
+int PROTO_INTERFACE::FileCancel(MCONTACT, HANDLE)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::FileDeny(MCONTACT, HANDLE, const wchar_t*)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::FileResume(HANDLE, int, const wchar_t*)
+{
+ return 1; // error
+}
+
+INT_PTR PROTO_INTERFACE::GetCaps(int, MCONTACT)
+{
+ return 0; // empty value
+}
+
+int PROTO_INTERFACE::GetInfo(MCONTACT, int)
+{
+ return 1; // error
+}
+
+HANDLE PROTO_INTERFACE::SearchBasic(const wchar_t*)
+{
+ return nullptr; // error
+}
+
+HANDLE PROTO_INTERFACE::SearchByEmail(const wchar_t*)
+{
+ return nullptr; // error
+}
+
+HANDLE PROTO_INTERFACE::SearchByName(const wchar_t*, const wchar_t*, const wchar_t*)
+{
+ return nullptr; // error
+}
+
+HWND PROTO_INTERFACE::SearchAdvanced(HWND)
+{
+ return nullptr; // error
+}
+
+HWND PROTO_INTERFACE::CreateExtendedSearchUI(HWND)
+{
+ return nullptr; // error
+}
+
+int PROTO_INTERFACE::RecvContacts(MCONTACT, PROTORECVEVENT*)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::RecvFile(MCONTACT hContact, PROTORECVFILE *pcre)
+{
+ CCSDATA ccs = { hContact, PSR_FILE, 0, (LPARAM)pcre };
+ return CallService(MS_PROTO_RECVFILET, 0, (LPARAM)&ccs);
+}
+
+MEVENT PROTO_INTERFACE::RecvMsg(MCONTACT hContact, PROTORECVEVENT *pre)
+{
+ if (pre->szMessage == nullptr)
+ return 0;
+
+ ptrA pszTemp;
+ mir_ptr<uint8_t> pszBlob;
+
+ DBEVENTINFO dbei = {};
+ dbei.flags = DBEF_UTF;
+ dbei.szModule = Proto_GetBaseAccountName(hContact);
+ dbei.timestamp = pre->timestamp;
+ dbei.eventType = EVENTTYPE_MESSAGE;
+ dbei.cbBlob = (uint32_t)mir_strlen(pre->szMessage) + 1;
+ dbei.pBlob = (uint8_t*)pre->szMessage;
+
+ if (pre->flags & PREF_CREATEREAD)
+ dbei.flags |= DBEF_READ;
+ if (pre->flags & PREF_SENT)
+ dbei.flags |= DBEF_SENT;
+
+ // if it's possible to find an existing event by its id, do that
+ if ((GetCaps(PFLAGNUM_4) & PF4_SERVERMSGID) && pre->szMsgId != nullptr) {
+ MEVENT hDbEvent = db_event_getById(m_szModuleName, pre->szMsgId);
+ if (hDbEvent == 0 || db_event_edit(hContact, hDbEvent, &dbei)) {
+ dbei.szId = pre->szMsgId;
+ hDbEvent = db_event_add(hContact, &dbei);
+ }
+ return hDbEvent;
+ }
+
+ // event is new? add it
+ return (INT_PTR)db_event_add(hContact, &dbei);
+}
+
+int PROTO_INTERFACE::SendContacts(MCONTACT, int, int, MCONTACT*)
+{
+ return 1; // error
+}
+
+HANDLE PROTO_INTERFACE::SendFile(MCONTACT, const wchar_t*, wchar_t**)
+{
+ return nullptr; // error
+}
+
+int PROTO_INTERFACE::SendMsg(MCONTACT, int, const char*)
+{
+ return 0; // error
+}
+
+int PROTO_INTERFACE::SetApparentMode(MCONTACT, int)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::SetStatus(int)
+{
+ return 1; // you better declare it
+}
+
+HANDLE PROTO_INTERFACE::GetAwayMsg(MCONTACT)
+{
+ return nullptr; // no away message
+}
+
+int PROTO_INTERFACE::RecvAwayMsg(MCONTACT, int, PROTORECVEVENT*)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::SetAwayMsg(int, const wchar_t*)
+{
+ return 1; // error
+}
+
+int PROTO_INTERFACE::UserIsTyping(MCONTACT, int)
+{
+ return 1; // error
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// protocol menus
+
+static INT_PTR __cdecl stubRequestAuth(WPARAM hContact, LPARAM)
+{
+ const char *szProto = Proto_GetBaseAccountName(hContact);
+ if (szProto)
+ ProtoCallService(szProto, PS_MENU_REQAUTH, hContact, 0);
+ return 0;
+}
+
+static INT_PTR __cdecl stubGrantAuth(WPARAM hContact, LPARAM)
+{
+ const char *szProto = Proto_GetBaseAccountName(hContact);
+ if (szProto)
+ ProtoCallService(szProto, PS_MENU_GRANTAUTH, hContact, 0);
+ return 0;
+}
+
+static INT_PTR __cdecl stubRevokeAuth(WPARAM hContact, LPARAM)
+{
+ const char *szProto = Proto_GetBaseAccountName(hContact);
+ if (szProto)
+ ProtoCallService(szProto, PS_MENU_REVOKEAUTH, hContact, 0);
+ return 0;
+}
+
+static INT_PTR __cdecl stubLoadHistory(WPARAM hContact, LPARAM)
+{
+ const char *szProto = Proto_GetBaseAccountName(hContact);
+ if (szProto)
+ ProtoCallService(szProto, PS_MENU_LOADHISTORY, hContact, 0);
+ return 0;
+}
+
+static int __cdecl ProtoPrebuildContactMenu(WPARAM hContact, LPARAM)
+{
+ Menu_ShowItem(hReqAuth, false);
+ Menu_ShowItem(hGrantAuth, false);
+ Menu_ShowItem(hRevokeAuth, false);
+
+ const char *szProto = Proto_GetBaseAccountName(hContact);
+ Menu_ShowItem(hServerHist, ProtoServiceExists(szProto, PS_MENU_LOADHISTORY));
+ return 0;
+}
+
+void InitProtoMenus(void)
+{
+ // "Request authorization"
+ CMenuItem mi(&g_plugin);
+ SET_UID(mi, 0x36375a1f, 0xc142, 0x4d6e, 0xa6, 0x57, 0xe4, 0x76, 0x5d, 0xbc, 0x59, 0x8e);
+ mi.pszService = "Proto/Menu/ReqAuth";
+ mi.name.a = LPGEN("Request authorization");
+ mi.position = -2000001002;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_AUTH_REQUEST);
+ hReqAuth = Menu_AddContactMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, stubRequestAuth);
+
+ // "Grant authorization"
+ SET_UID(mi, 0x4c90452a, 0x869a, 0x4a81, 0xaf, 0xa8, 0x28, 0x34, 0xaf, 0x2b, 0x6b, 0x30);
+ mi.pszService = "Proto/Menu/GrantAuth";
+ mi.name.a = LPGEN("Grant authorization");
+ mi.position = -2000001001;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_AUTH_GRANT);
+ hGrantAuth = Menu_AddContactMenuItem(&mi);
+
+ // "Revoke authorization"
+ SET_UID(mi, 0x619efdcb, 0x99c0, 0x44a8, 0xbf, 0x28, 0xc3, 0xe0, 0x2f, 0xb3, 0x7e, 0x77);
+ mi.pszService = "Proto/Menu/RevokeAuth";
+ mi.name.a = LPGEN("Revoke authorization");
+ mi.position = -2000001000;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_AUTH_REVOKE);
+ hRevokeAuth = Menu_AddContactMenuItem(&mi);
+
+ SET_UID(mi, 0xd15b841d, 0xb0fc, 0x4ab5, 0x96, 0x94, 0xcf, 0x6c, 0x6e, 0x99, 0x4b, 0x3c); // {D15B841D-B0FC-4AB5-9694-CF6C6E994B3C}
+ mi.pszService = "Proto/Menu/LoadHistory";
+ mi.name.a = LPGEN("Load server history");
+ mi.position = -200001004;
+ mi.hIcolibItem = Skin_GetIconHandle(SKINICON_OTHER_HISTORY);
+ hServerHist = Menu_AddContactMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, stubLoadHistory);
+
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, ProtoPrebuildContactMenu);
+}
diff --git a/src/mir_app/src/proto_internal.cpp b/src/mir_app/src/proto_internal.cpp index b7a74d8d4d..0b40f9cee1 100644 --- a/src/mir_app/src/proto_internal.cpp +++ b/src/mir_app/src/proto_internal.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/proto_opts.cpp b/src/mir_app/src/proto_opts.cpp index c25a6c937b..c2dfe73e72 100644 --- a/src/mir_app/src/proto_opts.cpp +++ b/src/mir_app/src/proto_opts.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/proto_order.cpp b/src/mir_app/src/proto_order.cpp index 9096b0cb61..3024e62b0e 100644 --- a/src/mir_app/src/proto_order.cpp +++ b/src/mir_app/src/proto_order.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/proto_ui.cpp b/src/mir_app/src/proto_ui.cpp index 3ef0a00233..13319b83a1 100644 --- a/src/mir_app/src/proto_ui.cpp +++ b/src/mir_app/src/proto_ui.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team,
+Copyright (C) 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/proto_utils.cpp b/src/mir_app/src/proto_utils.cpp index 79bd7012fa..281d76719e 100644 --- a/src/mir_app/src/proto_utils.cpp +++ b/src/mir_app/src/proto_utils.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team,
+Copyright (C) 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/protocols.cpp b/src/mir_app/src/protocols.cpp index 12fe246fb1..f3a1d60f5c 100644 --- a/src/mir_app/src/protocols.cpp +++ b/src/mir_app/src/protocols.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/pu_utils.cpp b/src/mir_app/src/pu_utils.cpp index 9495031fe7..1ed944ea3a 100644 --- a/src/mir_app/src/pu_utils.cpp +++ b/src/mir_app/src/pu_utils.cpp @@ -1,331 +1,331 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -///////////////////////////////////////////////////////////////////////////////////////// -// pu_stub.exe interface - -#include "stdafx.h" - -static HANDLE g_hPipe = nullptr; - -///////////////////////////////////////////////////////////////////////////////////////// -// are we running with admin priviledges? - -static bool IsRunAsAdmin() -{ - BOOL bIsRunAsAdmin = false; - uint32_t dwError = ERROR_SUCCESS; - PSID pAdministratorsGroup = nullptr; - - // Allocate and initialize a SID of the administrators group. - SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; - if (!AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdministratorsGroup)) { - dwError = GetLastError(); - goto Cleanup; - } - - // Determine whether the SID of administrators group is bEnabled in - // the primary access token of the process. - if (!CheckTokenMembership(nullptr, pAdministratorsGroup, &bIsRunAsAdmin)) { - dwError = GetLastError(); - goto Cleanup; - } - -Cleanup: - // Centralized cleanup for all allocated resources. - if (pAdministratorsGroup) { - FreeSid(pAdministratorsGroup); - pAdministratorsGroup = nullptr; - } - - // Throw the error if something failed in the function. - if (ERROR_SUCCESS != dwError) { - throw dwError; - } - - return bIsRunAsAdmin != 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Checks if we're working via pu_stub or not - -MIR_APP_DLL(bool) PU::IsDirect() -{ - return g_hPipe == nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Checks if Miranda's folder is writeable - -MIR_APP_DLL(bool) PU::IsMirandaFolderWritable() -{ - if (!IsWinVerVistaPlus()) - return true; - - wchar_t wszPath[MAX_PATH]; - GetModuleFileNameW(nullptr, wszPath, _countof(wszPath)); - wchar_t *ext = wcsrchr(wszPath, '.'); - if (ext != nullptr) - *ext = '\0'; - wcscat(wszPath, L".test"); - HANDLE hFile = CreateFileW(wszPath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile == INVALID_HANDLE_VALUE) - return false; - - CloseHandle(hFile); - DeleteFileW(wszPath); - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Checks if a process has enough rights to write into Miranda's folder - -MIR_APP_DLL(bool) PU::IsProcessElevated() -{ - bool bIsElevated = false; - HANDLE hToken = nullptr; - - // Open the primary access token of the process with TOKEN_QUERY. - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) - goto Cleanup; - - // Retrieve token elevation information. - TOKEN_ELEVATION elevation; - DWORD dwSize; - if (!GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize)) { - // When the process is run on operating systems prior to Windows - // Vista, GetTokenInformation returns FALSE with the - // ERROR_INVALID_PARAMETER error code because TokenElevation is - // not supported on those operating systems. - goto Cleanup; - } - - bIsElevated = elevation.TokenIsElevated != 0; - -Cleanup: - // Centralized cleanup for all allocated resources. - if (hToken) - CloseHandle(hToken); - - return bIsElevated; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Launches pu_stub.exe with elevated priviledges if needed - -MIR_APP_DLL(bool) PU::PrepareEscalation(const wchar_t *pwszFile) -{ - CMStringW wszFilePath; - // First try to create a file near Miranda32.exe - if (pwszFile == nullptr) { - wchar_t szPath[MAX_PATH]; - GetModuleFileName(nullptr, szPath, _countof(szPath)); - wchar_t *ext = wcsrchr(szPath, '.'); - if (ext != nullptr) - *ext = '\0'; - wszFilePath = szPath; - } - else wszFilePath = pwszFile; - - wszFilePath.Append(L".test"); - - HANDLE hFile = CreateFileW(wszFilePath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile != INVALID_HANDLE_VALUE) { - // we are admins or UAC is disable, cool - CloseHandle(hFile); - DeleteFileW(wszFilePath); - return true; - } - - // Check the current process's "run as administrator" status. - if (IsRunAsAdmin()) - return true; - - // if pipe already opened? - if (g_hPipe != nullptr) - return true; - - // Elevate the process. Create a pipe for a stub first - wchar_t wzPipeName[MAX_PATH]; - mir_snwprintf(wzPipeName, L"\\\\.\\pipe\\Miranda_Pu_%d", GetCurrentProcessId()); - g_hPipe = CreateNamedPipe(wzPipeName, PIPE_ACCESS_DUPLEX, PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, NMPWAIT_USE_DEFAULT_WAIT, nullptr); - if (g_hPipe == INVALID_HANDLE_VALUE) { - g_hPipe = nullptr; - } - else { - wchar_t cmdLine[100], *p; - wchar_t szPath[MAX_PATH]; - GetModuleFileName(nullptr, szPath, _countof(szPath)); - if ((p = wcsrchr(szPath, '\\')) != nullptr) - wcscpy(p + 1, L"pu_stub.exe"); - mir_snwprintf(cmdLine, L"%d", GetCurrentProcessId()); - - // Launch a stub - SHELLEXECUTEINFO sei = { sizeof(sei) }; - sei.lpVerb = L"runas"; - sei.lpFile = szPath; - sei.lpParameters = cmdLine; - sei.hwnd = nullptr; - sei.nShow = SW_NORMAL; - if (ShellExecuteEx(&sei)) { - if (g_hPipe != nullptr) - ConnectNamedPipe(g_hPipe, nullptr); - return true; - } - - uint32_t dwError = GetLastError(); - if (dwError == ERROR_CANCELLED) { - // The user refused to allow privileges elevation. - // Do nothing ... - } - } - return false; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static int TransactPipe(int opcode, const wchar_t *p1, const wchar_t *p2) -{ - uint8_t buf[1024]; - uint32_t l1 = lstrlen(p1), l2 = lstrlen(p2); - if (l1 > MAX_PATH || l2 > MAX_PATH) - return ERROR_BAD_ARGUMENTS; - - *(uint32_t *)buf = opcode; - wchar_t *dst = (wchar_t *)&buf[sizeof(uint32_t)]; - lstrcpy(dst, p1); - dst += l1 + 1; - if (p2) { - lstrcpy(dst, p2); - dst += l2 + 1; - } - else *dst++ = 0; - - DWORD dwBytes = 0, dwError; - if (!WriteFile(g_hPipe, buf, (uint32_t)((uint8_t *)dst - buf), &dwBytes, nullptr)) - return GetLastError(); - - dwError = 0; - if (!ReadFile(g_hPipe, &dwError, sizeof(uint32_t), &dwBytes, nullptr)) - return GetLastError(); - if (dwBytes != sizeof(uint32_t)) - return ERROR_BAD_ARGUMENTS; - - return dwError; -} - -MIR_APP_DLL(int) PU::SafeCopyFile(const wchar_t *pSrc, const wchar_t *pDst) -{ - if (g_hPipe == nullptr) - return CopyFileW(pSrc, pDst, FALSE); - - return TransactPipe(1, pSrc, pDst); -} - -MIR_APP_DLL(int) PU::SafeMoveFile(const wchar_t *pSrc, const wchar_t *pDst) -{ - if (g_hPipe == nullptr) { - if (!DeleteFileW(pDst)) { - uint32_t dwError = GetLastError(); - if (dwError != ERROR_ACCESS_DENIED && dwError != ERROR_FILE_NOT_FOUND) - return dwError; - } - - if (!MoveFileW(pSrc, pDst)) { // use copy on error - switch (uint32_t dwError = GetLastError()) { - case ERROR_ALREADY_EXISTS: - case ERROR_FILE_NOT_FOUND: - return 0; // this file was included into many archives, so Miranda tries to move it again & again - - case ERROR_ACCESS_DENIED: - case ERROR_SHARING_VIOLATION: - case ERROR_LOCK_VIOLATION: - // use copy routine if a move operation isn't available - // for example, when files are on different disks - if (!CopyFileW(pSrc, pDst, FALSE)) - return GetLastError(); - - if (!DeleteFileW(pSrc)) - return GetLastError(); - break; - - default: - return dwError; - } - } - - return ERROR_SUCCESS; - } - - return TransactPipe(2, pSrc, pDst); -} - -MIR_APP_DLL(int) PU::SafeDeleteFile(const wchar_t *pwszFile) -{ - if (g_hPipe == nullptr) - return DeleteFileW(pwszFile); - - return TransactPipe(3, pwszFile, nullptr); -} - -MIR_APP_DLL(int) PU::SafeRecycleBin(const wchar_t *pwszFile) -{ - if (g_hPipe == nullptr) { - CMStringW tmpPath(pwszFile); - tmpPath.AppendChar(0); - - SHFILEOPSTRUCT shfo = {}; - shfo.wFunc = FO_DELETE; - shfo.pFrom = tmpPath; - shfo.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_ALLOWUNDO; - return SHFileOperation(&shfo); - } - - return TransactPipe(7, pwszFile, nullptr); -} - -MIR_APP_DLL(int) PU::SafeCreateDirectory(const wchar_t *pwszFolder) -{ - if (g_hPipe == nullptr) - return CreateDirectoryTreeW(pwszFolder); - - return TransactPipe(4, pwszFolder, nullptr); -} - -MIR_APP_DLL(int) PU::SafeDeleteDirectory(const wchar_t *pwszDirName) -{ - if (g_hPipe == nullptr) - return DeleteDirectoryTreeW(pwszDirName); - - return TransactPipe(6, pwszDirName, nullptr); -} - -MIR_APP_DLL(int) PU::SafeCreateFilePath(const wchar_t *pwszFolder) -{ - if (g_hPipe == nullptr) { - CreatePathToFileW(pwszFolder); - return 0; - } - - return TransactPipe(5, pwszFolder, nullptr); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// pu_stub.exe interface
+
+#include "stdafx.h"
+
+static HANDLE g_hPipe = nullptr;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// are we running with admin priviledges?
+
+static bool IsRunAsAdmin()
+{
+ BOOL bIsRunAsAdmin = false;
+ uint32_t dwError = ERROR_SUCCESS;
+ PSID pAdministratorsGroup = nullptr;
+
+ // Allocate and initialize a SID of the administrators group.
+ SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
+ if (!AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdministratorsGroup)) {
+ dwError = GetLastError();
+ goto Cleanup;
+ }
+
+ // Determine whether the SID of administrators group is bEnabled in
+ // the primary access token of the process.
+ if (!CheckTokenMembership(nullptr, pAdministratorsGroup, &bIsRunAsAdmin)) {
+ dwError = GetLastError();
+ goto Cleanup;
+ }
+
+Cleanup:
+ // Centralized cleanup for all allocated resources.
+ if (pAdministratorsGroup) {
+ FreeSid(pAdministratorsGroup);
+ pAdministratorsGroup = nullptr;
+ }
+
+ // Throw the error if something failed in the function.
+ if (ERROR_SUCCESS != dwError) {
+ throw dwError;
+ }
+
+ return bIsRunAsAdmin != 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Checks if we're working via pu_stub or not
+
+MIR_APP_DLL(bool) PU::IsDirect()
+{
+ return g_hPipe == nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Checks if Miranda's folder is writeable
+
+MIR_APP_DLL(bool) PU::IsMirandaFolderWritable()
+{
+ if (!IsWinVerVistaPlus())
+ return true;
+
+ wchar_t wszPath[MAX_PATH];
+ GetModuleFileNameW(nullptr, wszPath, _countof(wszPath));
+ wchar_t *ext = wcsrchr(wszPath, '.');
+ if (ext != nullptr)
+ *ext = '\0';
+ wcscat(wszPath, L".test");
+ HANDLE hFile = CreateFileW(wszPath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (hFile == INVALID_HANDLE_VALUE)
+ return false;
+
+ CloseHandle(hFile);
+ DeleteFileW(wszPath);
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Checks if a process has enough rights to write into Miranda's folder
+
+MIR_APP_DLL(bool) PU::IsProcessElevated()
+{
+ bool bIsElevated = false;
+ HANDLE hToken = nullptr;
+
+ // Open the primary access token of the process with TOKEN_QUERY.
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
+ goto Cleanup;
+
+ // Retrieve token elevation information.
+ TOKEN_ELEVATION elevation;
+ DWORD dwSize;
+ if (!GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &dwSize)) {
+ // When the process is run on operating systems prior to Windows
+ // Vista, GetTokenInformation returns FALSE with the
+ // ERROR_INVALID_PARAMETER error code because TokenElevation is
+ // not supported on those operating systems.
+ goto Cleanup;
+ }
+
+ bIsElevated = elevation.TokenIsElevated != 0;
+
+Cleanup:
+ // Centralized cleanup for all allocated resources.
+ if (hToken)
+ CloseHandle(hToken);
+
+ return bIsElevated;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Launches pu_stub.exe with elevated priviledges if needed
+
+MIR_APP_DLL(bool) PU::PrepareEscalation(const wchar_t *pwszFile)
+{
+ CMStringW wszFilePath;
+ // First try to create a file near Miranda32.exe
+ if (pwszFile == nullptr) {
+ wchar_t szPath[MAX_PATH];
+ GetModuleFileName(nullptr, szPath, _countof(szPath));
+ wchar_t *ext = wcsrchr(szPath, '.');
+ if (ext != nullptr)
+ *ext = '\0';
+ wszFilePath = szPath;
+ }
+ else wszFilePath = pwszFile;
+
+ wszFilePath.Append(L".test");
+
+ HANDLE hFile = CreateFileW(wszFilePath, GENERIC_WRITE, FILE_SHARE_READ, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (hFile != INVALID_HANDLE_VALUE) {
+ // we are admins or UAC is disable, cool
+ CloseHandle(hFile);
+ DeleteFileW(wszFilePath);
+ return true;
+ }
+
+ // Check the current process's "run as administrator" status.
+ if (IsRunAsAdmin())
+ return true;
+
+ // if pipe already opened?
+ if (g_hPipe != nullptr)
+ return true;
+
+ // Elevate the process. Create a pipe for a stub first
+ wchar_t wzPipeName[MAX_PATH];
+ mir_snwprintf(wzPipeName, L"\\\\.\\pipe\\Miranda_Pu_%d", GetCurrentProcessId());
+ g_hPipe = CreateNamedPipe(wzPipeName, PIPE_ACCESS_DUPLEX, PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, NMPWAIT_USE_DEFAULT_WAIT, nullptr);
+ if (g_hPipe == INVALID_HANDLE_VALUE) {
+ g_hPipe = nullptr;
+ }
+ else {
+ wchar_t cmdLine[100], *p;
+ wchar_t szPath[MAX_PATH];
+ GetModuleFileName(nullptr, szPath, _countof(szPath));
+ if ((p = wcsrchr(szPath, '\\')) != nullptr)
+ wcscpy(p + 1, L"pu_stub.exe");
+ mir_snwprintf(cmdLine, L"%d", GetCurrentProcessId());
+
+ // Launch a stub
+ SHELLEXECUTEINFO sei = { sizeof(sei) };
+ sei.lpVerb = L"runas";
+ sei.lpFile = szPath;
+ sei.lpParameters = cmdLine;
+ sei.hwnd = nullptr;
+ sei.nShow = SW_NORMAL;
+ if (ShellExecuteEx(&sei)) {
+ if (g_hPipe != nullptr)
+ ConnectNamedPipe(g_hPipe, nullptr);
+ return true;
+ }
+
+ uint32_t dwError = GetLastError();
+ if (dwError == ERROR_CANCELLED) {
+ // The user refused to allow privileges elevation.
+ // Do nothing ...
+ }
+ }
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int TransactPipe(int opcode, const wchar_t *p1, const wchar_t *p2)
+{
+ uint8_t buf[1024];
+ uint32_t l1 = lstrlen(p1), l2 = lstrlen(p2);
+ if (l1 > MAX_PATH || l2 > MAX_PATH)
+ return ERROR_BAD_ARGUMENTS;
+
+ *(uint32_t *)buf = opcode;
+ wchar_t *dst = (wchar_t *)&buf[sizeof(uint32_t)];
+ lstrcpy(dst, p1);
+ dst += l1 + 1;
+ if (p2) {
+ lstrcpy(dst, p2);
+ dst += l2 + 1;
+ }
+ else *dst++ = 0;
+
+ DWORD dwBytes = 0, dwError;
+ if (!WriteFile(g_hPipe, buf, (uint32_t)((uint8_t *)dst - buf), &dwBytes, nullptr))
+ return GetLastError();
+
+ dwError = 0;
+ if (!ReadFile(g_hPipe, &dwError, sizeof(uint32_t), &dwBytes, nullptr))
+ return GetLastError();
+ if (dwBytes != sizeof(uint32_t))
+ return ERROR_BAD_ARGUMENTS;
+
+ return dwError;
+}
+
+MIR_APP_DLL(int) PU::SafeCopyFile(const wchar_t *pSrc, const wchar_t *pDst)
+{
+ if (g_hPipe == nullptr)
+ return CopyFileW(pSrc, pDst, FALSE);
+
+ return TransactPipe(1, pSrc, pDst);
+}
+
+MIR_APP_DLL(int) PU::SafeMoveFile(const wchar_t *pSrc, const wchar_t *pDst)
+{
+ if (g_hPipe == nullptr) {
+ if (!DeleteFileW(pDst)) {
+ uint32_t dwError = GetLastError();
+ if (dwError != ERROR_ACCESS_DENIED && dwError != ERROR_FILE_NOT_FOUND)
+ return dwError;
+ }
+
+ if (!MoveFileW(pSrc, pDst)) { // use copy on error
+ switch (uint32_t dwError = GetLastError()) {
+ case ERROR_ALREADY_EXISTS:
+ case ERROR_FILE_NOT_FOUND:
+ return 0; // this file was included into many archives, so Miranda tries to move it again & again
+
+ case ERROR_ACCESS_DENIED:
+ case ERROR_SHARING_VIOLATION:
+ case ERROR_LOCK_VIOLATION:
+ // use copy routine if a move operation isn't available
+ // for example, when files are on different disks
+ if (!CopyFileW(pSrc, pDst, FALSE))
+ return GetLastError();
+
+ if (!DeleteFileW(pSrc))
+ return GetLastError();
+ break;
+
+ default:
+ return dwError;
+ }
+ }
+
+ return ERROR_SUCCESS;
+ }
+
+ return TransactPipe(2, pSrc, pDst);
+}
+
+MIR_APP_DLL(int) PU::SafeDeleteFile(const wchar_t *pwszFile)
+{
+ if (g_hPipe == nullptr)
+ return DeleteFileW(pwszFile);
+
+ return TransactPipe(3, pwszFile, nullptr);
+}
+
+MIR_APP_DLL(int) PU::SafeRecycleBin(const wchar_t *pwszFile)
+{
+ if (g_hPipe == nullptr) {
+ CMStringW tmpPath(pwszFile);
+ tmpPath.AppendChar(0);
+
+ SHFILEOPSTRUCT shfo = {};
+ shfo.wFunc = FO_DELETE;
+ shfo.pFrom = tmpPath;
+ shfo.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOF_ALLOWUNDO;
+ return SHFileOperation(&shfo);
+ }
+
+ return TransactPipe(7, pwszFile, nullptr);
+}
+
+MIR_APP_DLL(int) PU::SafeCreateDirectory(const wchar_t *pwszFolder)
+{
+ if (g_hPipe == nullptr)
+ return CreateDirectoryTreeW(pwszFolder);
+
+ return TransactPipe(4, pwszFolder, nullptr);
+}
+
+MIR_APP_DLL(int) PU::SafeDeleteDirectory(const wchar_t *pwszDirName)
+{
+ if (g_hPipe == nullptr)
+ return DeleteDirectoryTreeW(pwszDirName);
+
+ return TransactPipe(6, pwszDirName, nullptr);
+}
+
+MIR_APP_DLL(int) PU::SafeCreateFilePath(const wchar_t *pwszFolder)
+{
+ if (g_hPipe == nullptr) {
+ CreatePathToFileW(pwszFolder);
+ return 0;
+ }
+
+ return TransactPipe(5, pwszFolder, nullptr);
+}
diff --git a/src/mir_app/src/searchresults.cpp b/src/mir_app/src/searchresults.cpp index 2a298d6598..892ec2c7dd 100644 --- a/src/mir_app/src/searchresults.cpp +++ b/src/mir_app/src/searchresults.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/skin.h b/src/mir_app/src/skin.h index d0a76adfc0..e6225825a8 100644 --- a/src/mir_app/src/skin.h +++ b/src/mir_app/src/skin.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/skin2opts.cpp b/src/mir_app/src/skin2opts.cpp index 5d1cd2e072..a42db0b245 100644 --- a/src/mir_app/src/skin2opts.cpp +++ b/src/mir_app/src/skin2opts.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/skinicons.cpp b/src/mir_app/src/skinicons.cpp index 927f5e798f..f6ce8e8b5d 100644 --- a/src/mir_app/src/skinicons.cpp +++ b/src/mir_app/src/skinicons.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/sounds.cpp b/src/mir_app/src/sounds.cpp index 8da80075df..8762adb26b 100644 --- a/src/mir_app/src/sounds.cpp +++ b/src/mir_app/src/sounds.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/srmm_base.cpp b/src/mir_app/src/srmm_base.cpp index 85a9308301..fd1d564a8a 100644 --- a/src/mir_app/src/srmm_base.cpp +++ b/src/mir_app/src/srmm_base.cpp @@ -1,923 +1,923 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -#include "chat.h" -#include "resource.h" -#include "skin.h" -#include <m_history.h> - -CSrmmBaseDialog::CSrmmBaseDialog(CMPluginBase &pPlugin, int idDialog, SESSION_INFO *si) : - CDlgBase(pPlugin, idDialog), - timerFlash(this, 1), - timerType(this, 2), - - m_message(this, IDC_SRMM_MESSAGE), - m_nickList(this, IDC_SRMM_NICKLIST), - - m_btnOk(this, IDOK), - m_btnFilter(this, IDC_SRMM_FILTER), - m_btnHistory(this, IDC_SRMM_HISTORY), - m_btnNickList(this, IDC_SRMM_SHOWNICKLIST), - m_btnChannelMgr(this, IDC_SRMM_CHANMGR), - - m_btnColor(this, IDC_SRMM_COLOR), - m_btnBkColor(this, IDC_SRMM_BKGCOLOR), - m_btnBold(this, IDC_SRMM_BOLD), - - m_btnItalic(this, IDC_SRMM_ITALICS), - m_btnUnderline(this, IDC_SRMM_UNDERLINE), - - m_si(si), - m_hContact(0), - m_clrInputBG(GetSysColor(COLOR_WINDOW)) -{ - m_bFilterEnabled = db_get_b(0, CHAT_MODULE, "FilterEnabled", 0) != 0; - m_bNicklistEnabled = db_get_b(0, CHAT_MODULE, "ShowNicklist", 1) != 0; - m_iLogFilterFlags = db_get_dw(0, CHAT_MODULE, "FilterFlags", 0x03E0); - - m_btnColor.OnClick = Callback(this, &CSrmmBaseDialog::onClick_Color); - m_btnBkColor.OnClick = Callback(this, &CSrmmBaseDialog::onClick_BkColor); - m_btnBold.OnClick = m_btnItalic.OnClick = m_btnUnderline.OnClick = Callback(this, &CSrmmBaseDialog::onClick_BIU); - - m_btnHistory.OnClick = Callback(this, &CSrmmBaseDialog::onClick_History); - m_btnChannelMgr.OnClick = Callback(this, &CSrmmBaseDialog::onClick_ChanMgr); - - m_nickList.OnDblClick = Callback(this, &CMsgDialog::onDblClick_List); - - if (si) { - m_hContact = si->hContact; - - if (si->pMI->bColor) { - m_iFG = 4; - m_bFGSet = true; - } - if (si->pMI->bBkgColor) { - m_iBG = 2; - m_bBGSet = true; - } - } -} - -void CSrmmBaseDialog::RunUserMenu(HWND hwndOwner, USERINFO *ui, const POINT &pt) -{ - HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_USERMENU)); - HMENU hSubMenu = GetSubMenu(hMenu, 0); - TranslateMenu(hSubMenu); - - USERINFO uinew; - memcpy(&uinew, ui, sizeof(USERINFO)); - - wchar_t szTemp[50]; - if (uinew.pszNick) - mir_snwprintf(szTemp, TranslateT("&Message %s"), uinew.pszNick); - else - mir_wstrncpy(szTemp, TranslateT("&Message"), _countof(szTemp) - 1); - - if (mir_wstrlen(szTemp) > 40) - mir_wstrncpy(szTemp + 40, L"...", 4); - ModifyMenu(hMenu, 0, MF_STRING | MF_BYPOSITION, IDM_SENDMESSAGE, szTemp); - - UINT uID = Chat_CreateMenu(hwndOwner, hSubMenu, pt, m_si, uinew.pszUID); - switch (uID) { - case 0: - break; - - case IDM_SENDMESSAGE: - Chat_DoEventHook(m_si, GC_USER_PRIVMESS, ui, nullptr, 0); - break; - - default: - Chat_DoEventHook(m_si, GC_USER_NICKLISTMENU, ui, nullptr, uID); - break; - } - DestroyMenu(hMenu); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static LRESULT CALLBACK Srmm_ButtonSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_RBUTTONUP: - if (g_chatApi.bRightClickFilter) { - CSrmmBaseDialog *pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); - if (pDlg == nullptr) - break; - - switch (GetDlgCtrlID(hwnd)) { - case IDC_SRMM_FILTER: - pDlg->ShowFilterMenu(); - break; - - case IDC_SRMM_COLOR: - pDlg->ShowColorChooser(IDC_SRMM_COLOR); - break; - - case IDC_SRMM_BKGCOLOR: - pDlg->ShowColorChooser(IDC_SRMM_BKGCOLOR); - break; - } - } - break; - } - - return mir_callNextSubclass(hwnd, Srmm_ButtonSubclassProc, msg, wParam, lParam); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -EXTERN_C MIR_APP_DLL(LRESULT) CALLBACK stubMessageProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - CSrmmBaseDialog *pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); - if (pDlg != nullptr) - return pDlg->WndProc_Message(msg, wParam, lParam); - - return mir_callNextSubclass(hwnd, stubMessageProc, msg, wParam, lParam); -} - -LRESULT CSrmmBaseDialog::WndProc_Message(UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_SETCURSOR: - if (m_bInMenu) { - SetCursor(LoadCursor(nullptr, IDC_ARROW)); - return TRUE; - } - break; - - case WM_CHAR: - switch (wParam) { - case 0x02: // ctrl+B - if (m_btnBold.Enabled()) - return 1; - break; - case 0x09: // ctrl+I - if (m_btnItalic.Enabled()) - return 1; - break; - case 0x15: // ctrl+U - if (m_btnUnderline.Enabled()) - return 1; - break; - } - break; - - case WM_SYSKEYDOWN: - case WM_KEYDOWN: - if (wParam == VK_BACK) - if (m_message.GetRichTextLength() == 0) - return 1; - - MSG tmp = { m_hwnd, msg, wParam, lParam }; - if (Hotkey_Check(&tmp, g_pszHotkeySection) == 100) { - if (!(GetWindowLongPtr(m_message.GetHwnd(), GWL_STYLE) & ES_READONLY)) { - PostMessage(m_hwnd, WM_COMMAND, IDOK, 0); - return true; - } - } - } - - LRESULT res = mir_callNextSubclass(m_message.GetHwnd(), stubMessageProc, msg, wParam, lParam); - switch (msg) { - case WM_GETDLGCODE: - return res & ~DLGC_HASSETSEL; - - case WM_KEYUP: - case WM_LBUTTONUP: - case WM_RBUTTONUP: - case WM_MBUTTONUP: - RefreshButtonStatus(); - break; - - case WM_KEYDOWN: - if ((GetKeyState(VK_CONTROL) & 0x8000) && wParam == 'V' || (GetKeyState(VK_SHIFT) & 0x8000) && wParam == VK_INSERT) { - if (IsClipboardFormatAvailable(CF_HDROP)) { - m_message.SendMsg(WM_PASTE, 0, 0); - return 0; - } - } - - __fallthrough; - - case WM_SYSKEYDOWN: - if (!(GetKeyState(VK_RMENU) & 0x8000)) { - MSG message = { m_hwnd, msg, wParam, lParam }; - LRESULT iButtonFrom = Hotkey_Check(&message, BB_HK_SECTION); - if (iButtonFrom) { - Srmm_ProcessToolbarHotkey(m_hContact, iButtonFrom, m_hwnd); - return TRUE; - } - } - break; - } - - return res; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// process mouse - hovering for the nickname list.fires events so the protocol can -// show the userinfo - tooltip. - -static void ProcessNickListHovering(HWND hwnd, int hoveredItem, SESSION_INFO *parentdat) -{ - static int currentHovered = -1; - static HWND hwndToolTip = nullptr; - static HWND oldParent = nullptr; - - if (hoveredItem == currentHovered) - return; - - currentHovered = hoveredItem; - - if (oldParent != hwnd && hwndToolTip) { - SendMessage(hwndToolTip, TTM_DELTOOL, 0, 0); - DestroyWindow(hwndToolTip); - hwndToolTip = nullptr; - } - - if (hoveredItem == -1) { - SendMessage(hwndToolTip, TTM_ACTIVATE, 0, 0); - return; - } - - bool bNewTip = false; - if (!hwndToolTip) { - bNewTip = true; - hwndToolTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, nullptr, - WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP, - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - hwnd, nullptr, g_plugin.getInst(), nullptr); - } - - RECT clientRect; - GetClientRect(hwnd, &clientRect); - - TOOLINFO ti = { sizeof(ti) }; - ti.uFlags = TTF_SUBCLASS; - ti.hinst = g_plugin.getInst(); - ti.hwnd = hwnd; - ti.uId = 1; - ti.rect = clientRect; - - CMStringW wszBuf; - - USERINFO *ui1 = g_chatApi.SM_GetUserFromIndex(parentdat->ptszID, parentdat->pszModule, currentHovered); - if (ui1) { - if (ProtoServiceExists(parentdat->pszModule, MS_GC_PROTO_GETTOOLTIPTEXT)) { - wchar_t *p = (wchar_t*)CallProtoService(parentdat->pszModule, MS_GC_PROTO_GETTOOLTIPTEXT, (WPARAM)parentdat->ptszID, (LPARAM)ui1->pszUID); - if (p != nullptr) { - wszBuf = p; - mir_free(p); - } - } - - if (wszBuf.IsEmpty()) - wszBuf.Format(L"%s: %s\r\n%s: %s\r\n%s: %s", - TranslateT("Nickname"), ui1->pszNick, - TranslateT("Unique ID"), ui1->pszUID, - TranslateT("Status"), g_chatApi.TM_WordToString(parentdat->pStatuses, ui1->Status)); - ti.lpszText = wszBuf.GetBuffer(); - } - - SendMessage(hwndToolTip, bNewTip ? TTM_ADDTOOL : TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); - SendMessage(hwndToolTip, TTM_ACTIVATE, (ti.lpszText != nullptr), 0); - SendMessage(hwndToolTip, TTM_SETMAXTIPWIDTH, 0, 400); -} - -static void CALLBACK ChatTimerProc(HWND hwnd, UINT, UINT_PTR idEvent, DWORD) -{ - SESSION_INFO *si = (SESSION_INFO*)idEvent; - - POINT pt; - GetCursorPos(&pt); - ScreenToClient(hwnd, &pt); - - uint32_t nItemUnderMouse = (uint32_t)SendMessage(hwnd, LB_ITEMFROMPOINT, 0, MAKELPARAM(pt.x, pt.y)); - if (HIWORD(nItemUnderMouse) == 1) - nItemUnderMouse = (uint32_t)(-1); - else - nItemUnderMouse &= 0xFFFF; - if (((int)nItemUnderMouse != si->currentHovered) || (nItemUnderMouse == -1)) { - KillTimer(hwnd, idEvent); - return; - } - - USERINFO *ui1 = g_chatApi.SM_GetUserFromIndex(si->ptszID, si->pszModule, si->currentHovered); - if (ui1) { - CMStringW wszBuf; - if (ProtoServiceExists(si->pszModule, MS_GC_PROTO_GETTOOLTIPTEXT)) { - wchar_t *p = (wchar_t*)CallProtoService(si->pszModule, MS_GC_PROTO_GETTOOLTIPTEXT, (WPARAM)si->ptszID, (LPARAM)ui1->pszUID); - if (p) { - wszBuf = p; - mir_free(p); - } - } - if (wszBuf.IsEmpty()) - wszBuf.Format(L"<b>%s:</b>\t%s\n<b>%s:</b>\t%s\n<b>%s:</b>\t%s", - TranslateT("Nick"), ui1->pszNick, - TranslateT("Unique ID"), ui1->pszUID, - TranslateT("Status"), g_chatApi.TM_WordToString(si->pStatuses, ui1->Status)); - - CLCINFOTIP ti = { sizeof(ti) }; - Tipper_ShowTip(wszBuf, &ti); - si->bHasToolTip = true; - } - KillTimer(hwnd, idEvent); -} - -EXTERN_C MIR_APP_DLL(LRESULT) CALLBACK stubNicklistProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - CSrmmBaseDialog *pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA); - if (pDlg != nullptr) - return pDlg->WndProc_Nicklist(msg, wParam, lParam); - - return mir_callNextSubclass(hwnd, stubNicklistProc, msg, wParam, lParam); -} - -LRESULT CSrmmBaseDialog::WndProc_Nicklist(UINT msg, WPARAM wParam, LPARAM lParam) -{ - RECT rc; - - switch (msg) { - case WM_MEASUREITEM: - { - MEASUREITEMSTRUCT *mis = (MEASUREITEMSTRUCT *)lParam; - if (mis->CtlType == ODT_MENU) - return Menu_MeasureItem(lParam); - } - return FALSE; - - case WM_DRAWITEM: - { - DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)lParam; - if (dis->CtlType == ODT_MENU) - return Menu_DrawItem(lParam); - } - return FALSE; - - case WM_MOUSEMOVE: - RECT clientRect; - { - bool bTooltipExists = ServiceExists(MS_TIPPER_HIDETIP); - - POINT pt = { LOWORD(lParam), HIWORD(lParam) }; - GetClientRect(m_nickList.GetHwnd(), &clientRect); - if (PtInRect(&clientRect, pt)) { - // hit test item under mouse - uint32_t nItemUnderMouse = m_nickList.SendMsg(LB_ITEMFROMPOINT, 0, lParam); - if (HIWORD(nItemUnderMouse) == 1) - nItemUnderMouse = (uint32_t)(-1); - else - nItemUnderMouse &= 0xFFFF; - - if (bTooltipExists) { - if ((int)nItemUnderMouse == m_si->currentHovered) - break; - m_si->currentHovered = (int)nItemUnderMouse; - - KillTimer(m_nickList.GetHwnd(), 1); - - if (m_si->bHasToolTip) { - Tipper_Hide(); - m_si->bHasToolTip = false; - } - - if (nItemUnderMouse != -1) - SetTimer(m_nickList.GetHwnd(), (UINT_PTR)m_si, 450, ChatTimerProc); - } - else ProcessNickListHovering(m_nickList.GetHwnd(), (int)nItemUnderMouse, m_si); - } - else { - if (bTooltipExists) { - KillTimer(m_nickList.GetHwnd(), 1); - if (m_si->bHasToolTip) { - Tipper_Hide(); - m_si->bHasToolTip = false; - } - } - else ProcessNickListHovering(m_nickList.GetHwnd(), -1, nullptr); - } - } - break; - - case WM_ERASEBKGND: - { - HDC dc = (HDC)wParam; - if (dc == nullptr) - break; - - int nUsers = m_si->getUserList().getCount(); - - int index = m_nickList.SendMsg(LB_GETTOPINDEX, 0, 0); - if (index == LB_ERR || nUsers <= 0) - break; - - int height = m_nickList.SendMsg(LB_GETITEMHEIGHT, 0, 0); - if (height == LB_ERR) - break; - - GetClientRect(m_nickList.GetHwnd(), &rc); - - int items = nUsers - index; - if (rc.bottom - rc.top > items * height) { - rc.top = items * height; - FillRect(dc, &rc, g_chatApi.hListBkgBrush); - } - } - return 1; - - case WM_CONTEXTMENU: - POINT pt; - { - int height = 0; - pt.x = GET_X_LPARAM(lParam); - pt.y = GET_Y_LPARAM(lParam); - if (pt.x == -1 && pt.y == -1) { - int index = m_nickList.GetCurSel(); - int top = m_nickList.SendMsg(LB_GETTOPINDEX, 0, 0); - height = m_nickList.SendMsg(LB_GETITEMHEIGHT, 0, 0); - pt.x = 4; - pt.y = (index - top)*height + 1; - } - else ScreenToClient(m_nickList.GetHwnd(), &pt); - - int item = LOWORD(m_nickList.SendMsg(LB_ITEMFROMPOINT, 0, MAKELPARAM(pt.x, pt.y))); - USERINFO *ui = g_chatApi.SM_GetUserFromIndex(m_si->ptszID, m_si->pszModule, item); - if (ui != nullptr) { - if (pt.x == -1 && pt.y == -1) - pt.y += height - 4; - ClientToScreen(m_nickList.GetHwnd(), &pt); - - RunUserMenu(m_nickList.GetHwnd(), ui, pt); - return TRUE; - } - } - break; - } - - return mir_callNextSubclass(m_nickList.GetHwnd(), stubNicklistProc, msg, wParam, lParam); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -bool CSrmmBaseDialog::OnInitDialog() -{ - WindowList_Add(g_hWindowList, m_hwnd, m_hContact); - SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)this); - - m_pLog = Srmm_GetLogWindow((CMsgDialog*)this); - if (m_pLog->GetType() != 0) { // custom log type - HWND hwndLog = GetDlgItem(m_hwnd, IDC_SRMM_LOG); - EnableWindow(hwndLog, FALSE); - ShowWindow(hwndLog, SW_HIDE); - } - m_pLog->Attach(); - - SetWindowLongPtr(m_message.GetHwnd(), GWLP_USERDATA, LPARAM(this)); - mir_subclassWindow(m_message.GetHwnd(), stubMessageProc); - m_message.SetReadOnly(false); - ::DragAcceptFiles(m_message.GetHwnd(), TRUE); - - if (isChat()) { - SetWindowLongPtr(m_nickList.GetHwnd(), GWLP_USERDATA, LPARAM(this)); - mir_subclassWindow(m_nickList.GetHwnd(), stubNicklistProc); - } - - // three buttons below are initiated inside this call, so button creation must precede subclassing - Srmm_CreateToolbarIcons(m_hwnd, isChat() ? BBBF_ISCHATBUTTON : BBBF_ISIMBUTTON); - - mir_subclassWindow(m_btnFilter.GetHwnd(), Srmm_ButtonSubclassProc); - mir_subclassWindow(m_btnColor.GetHwnd(), Srmm_ButtonSubclassProc); - mir_subclassWindow(m_btnBkColor.GetHwnd(), Srmm_ButtonSubclassProc); - - LoadSettings(); - return true; -} - -void CSrmmBaseDialog::OnDestroy() -{ - m_pLog->Detach(); - delete m_pLog; - - WindowList_Remove(g_hWindowList, m_hwnd); - - SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0); - mir_unsubclassWindow(m_message.GetHwnd(), stubMessageProc); - mir_unsubclassWindow(m_nickList.GetHwnd(), stubNicklistProc); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CSrmmBaseDialog::DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_COMMAND: - if (!lParam && Clist_MenuProcessCommand(LOWORD(wParam), MPCF_CONTACTMENU, m_hContact)) - return 0; - - if (wParam >= MIN_CBUTTONID && wParam <= MAX_CBUTTONID) { - Srmm_ClickToolbarIcon(m_hContact, wParam, m_hwnd, 0); - return 0; - } - break; - - case WM_ACTIVATE: - if (m_si && LOWORD(wParam) == WA_INACTIVE) { - m_si->wState &= ~GC_EVENT_HIGHLIGHT; - m_si->wState &= ~STATE_TALK; - } - break; - - case WM_CBD_RECREATE: - Srmm_CreateToolbarIcons(m_hwnd, isChat() ? BBBF_ISCHATBUTTON : BBBF_ISIMBUTTON); - break; - - case WM_NOTIFY: - LPNMHDR hdr = (LPNMHDR)lParam; - if (hdr->hwndFrom == m_pLog->GetHwnd()) - m_pLog->Notify(wParam, lParam); - break; - } - - return CDlgBase::DlgProc(msg, wParam, lParam); -} - -void CSrmmBaseDialog::AddLog() -{ - if (m_si->pLogEnd) - m_pLog->LogEvents(m_si->pLog, false); - else - m_pLog->Clear(); -} - -bool CSrmmBaseDialog::AllowTyping() const -{ - return isChat() ? m_si->iType != GCW_SERVER : true; -} - -void CSrmmBaseDialog::ClearLog() -{ - m_pLog->Clear(); -} - -void CSrmmBaseDialog::UpdateOptions() -{ - MODULEINFO *mi = m_si->pMI; - EnableWindow(m_btnBold.GetHwnd(), mi->bBold); - EnableWindow(m_btnItalic.GetHwnd(), mi->bItalics); - EnableWindow(m_btnUnderline.GetHwnd(), mi->bUnderline); - EnableWindow(m_btnColor.GetHwnd(), mi->bColor); - EnableWindow(m_btnBkColor.GetHwnd(), mi->bBkgColor); - if (m_si->iType == GCW_CHATROOM) - EnableWindow(m_btnChannelMgr.GetHwnd(), mi->bChanMgr); - - Resize(); - RedrawLog2(m_si); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void RedrawLog2(SESSION_INFO *si) -{ - si->LastTime = 0; - if (si->pLog) - si->pDlg->log()->LogEvents(si->pLogEnd, TRUE); -} - -static void __cdecl phase2(SESSION_INFO *si) -{ - Sleep(30); - if (si && si->pDlg) - RedrawLog2(si); -} - -void CSrmmBaseDialog::RedrawLog() -{ - m_si->LastTime = 0; - if (m_si->pLog) { - LOGINFO *pLog = m_si->pLog; - if (m_si->iEventCount > 60) { - int index = 0; - while (index < 59) { - if (pLog->next == nullptr) - break; - - pLog = pLog->next; - if (m_si->iType != GCW_CHATROOM || !m_bFilterEnabled || (m_iLogFilterFlags & pLog->iType) != 0) - index++; - } - m_pLog->LogEvents(pLog, true); - mir_forkThread<SESSION_INFO>(phase2, m_si); - } - else m_pLog->LogEvents(m_si->pLogEnd, true); - } - else ClearLog(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CSrmmBaseDialog::onClick_Color(CCtrlButton *pButton) -{ - if (!pButton->Enabled()) - return; - - CHARFORMAT2 cf; - cf.cbSize = sizeof(CHARFORMAT2); - cf.dwEffects = 0; - cf.dwMask = CFM_COLOR; - - if (IsDlgButtonChecked(m_hwnd, pButton->GetCtrlId())) { - if (!g_chatApi.bRightClickFilter) { - ShowColorChooser(pButton->GetCtrlId()); - return; - } - if (m_bFGSet) - cf.crTextColor = m_iFG; - } - else cf.crTextColor = m_clrInputFG; - - m_message.SendMsg(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); -} - -void CSrmmBaseDialog::onClick_BkColor(CCtrlButton *pButton) -{ - if (!pButton->Enabled()) - return; - - CHARFORMAT2 cf; - cf.cbSize = sizeof(CHARFORMAT2); - cf.dwEffects = 0; - cf.dwMask = CFM_BACKCOLOR; - - if (IsDlgButtonChecked(m_hwnd, pButton->GetCtrlId())) { - if (!g_chatApi.bRightClickFilter) { - ShowColorChooser(pButton->GetCtrlId()); - return; - } - if (m_bBGSet) - cf.crBackColor = m_iBG; - } - else cf.crBackColor = m_clrInputBG; - - m_message.SendMsg(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); -} - -void CSrmmBaseDialog::onClick_BIU(CCtrlButton *pButton) -{ - if (!pButton->Enabled()) - return; - - CHARFORMAT2 cf; - cf.cbSize = sizeof(CHARFORMAT2); - cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE; - cf.dwEffects = 0; - - if (IsDlgButtonChecked(m_hwnd, IDC_SRMM_BOLD)) - cf.dwEffects |= CFE_BOLD; - if (IsDlgButtonChecked(m_hwnd, IDC_SRMM_ITALICS)) - cf.dwEffects |= CFE_ITALIC; - if (IsDlgButtonChecked(m_hwnd, IDC_SRMM_UNDERLINE)) - cf.dwEffects |= CFE_UNDERLINE; - m_message.SendMsg(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); -} - -void CSrmmBaseDialog::onClick_History(CCtrlButton *pButton) -{ - if (!pButton->Enabled()) - return; - - if (m_si != nullptr) - ShellExecute(m_hwnd, nullptr, g_chatApi.GetChatLogsFilename(m_si, 0), nullptr, nullptr, SW_SHOW); - else - CallService(MS_HISTORY_SHOWCONTACTHISTORY, m_hContact, 0); -} - -void CSrmmBaseDialog::onClick_ChanMgr(CCtrlButton *pButton) -{ - if (pButton->Enabled()) - Chat_DoEventHook(m_si, GC_USER_CHANMGR, nullptr, nullptr, 0); -} - -void CSrmmBaseDialog::onDblClick_List(CCtrlListBox *pList) -{ - TVHITTESTINFO hti; - hti.pt.x = (short)LOWORD(GetMessagePos()); - hti.pt.y = (short)HIWORD(GetMessagePos()); - ScreenToClient(pList->GetHwnd(), &hti.pt); - - int item = LOWORD(pList->SendMsg(LB_ITEMFROMPOINT, 0, MAKELPARAM(hti.pt.x, hti.pt.y))); - USERINFO *ui = g_chatApi.UM_FindUserFromIndex(m_si, item); - if (ui == nullptr) - return; - - bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0; - if (Chat::bDoubleClick4Privat ? bShift : !bShift) { - int selStart = LOWORD(m_message.SendMsg(EM_GETSEL, 0, 0)); - CMStringW tszName(ui->pszNick); - if (selStart == 0) - tszName.AppendChar(':'); - tszName.AppendChar(' '); - - m_message.SendMsg(EM_REPLACESEL, FALSE, (LPARAM)tszName.GetString()); - PostMessage(m_hwnd, WM_MOUSEACTIVATE, 0, 0); - SetFocus(m_message.GetHwnd()); - } - else Chat_DoEventHook(m_si, GC_USER_PRIVMESS, ui, nullptr, 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -extern HANDLE hHookSrmmEvent; - -int CSrmmBaseDialog::NotifyEvent(int code) -{ - if (m_hContact == 0 && m_hwnd == nullptr) - return -1; - - MessageWindowEventData mwe = {}; - mwe.hContact = m_hContact; - mwe.hwndWindow = m_hwnd; - mwe.uType = code; - mwe.uFlags = MSG_WINDOW_UFLAG_MSG_BOTH; - mwe.hwndInput = m_message.GetHwnd(); - mwe.hwndLog = m_pLog->GetHwnd(); - return ::NotifyEventHooks(hHookSrmmEvent, 0, (LPARAM)&mwe); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -bool CSrmmBaseDialog::ProcessFileDrop(HDROP hDrop, MCONTACT hContact) -{ - if (PasteFilesAsURL(hDrop)) - return true; - - return ::ProcessFileDrop(hDrop, hContact); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// If enabled pastes droped files as list of URL of file:/// type -// Can be enabled/disabled by Chat/ShiftDropFilePasteURL database parameter -// @param hDrop - Drop handle -// @return Returns true if processed here, returns false if should be processed elsewhere - -bool CSrmmBaseDialog::PasteFilesAsURL(HDROP hDrop) -{ - bool isShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0; - if (db_get_b(0, CHAT_MODULE, "ShiftDropFilePasteURL", 1) == 0 || !isShift) // hidden setting: Chat/ShiftDropFilePasteURL - return false; - - int fileCount = DragQueryFileW(hDrop, -1, nullptr, 0); - if (fileCount == 0) - return true; - - CMStringW pasteString(L" "); - for (int i = 0; i < fileCount; i++) { - wchar_t szFilename[MAX_PATH]; - if (DragQueryFileW(hDrop, i, szFilename, _countof(szFilename))) { - CMStringW fileString(L"file:///"); - fileString.Append(szFilename); - fileString.Replace(L"%", L"%25"); - fileString.Replace(L" ", L"%20"); - fileString.Append((i != fileCount - 1) ? L"\r\n" : L" "); - pasteString += fileString; - } - } - - m_message.SendMsg(EM_REPLACESEL, TRUE, (LPARAM)pasteString.c_str()); - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -bool CSrmmBaseDialog::ProcessHotkeys(int key, bool isShift, bool isCtrl, bool isAlt) -{ - // Esc (close tab) - if (key == VK_ESCAPE && !isShift && !isCtrl && !isAlt) { - CloseTab(); - return true; - } - - if (isCtrl && !isAlt) { - switch (key) { - case VK_SPACE: // ctrl-space (paste clean text) - m_btnBold.Push(false); m_btnBold.Click(); - m_btnItalic.Push(false); m_btnItalic.Click(); - m_btnUnderline.Push(false); m_btnUnderline.Click(); - - m_btnColor.Push(false); m_btnColor.Click(); - m_btnBkColor.Push(false); m_btnBkColor.Click(); - return true; - - case 0x42: // ctrl-b (bold) - m_btnBold.Push(!m_btnBold.IsPushed()); - m_btnBold.Click(); - return true; - - case 0x48: // ctrl-h (history) - m_btnHistory.Click(); - return true; - - case 0x49: // ctrl-i (italics) - m_btnItalic.Push(!m_btnItalic.IsPushed()); - m_btnItalic.Click(); - return true; - - case 0x4b: // ctrl-k (text color) - m_btnColor.Push(!m_btnColor.IsPushed()); - m_btnColor.Click(); - return true; - - case 0x4c: // ctrl-l (back color) - m_btnBkColor.Push(!m_btnBkColor.IsPushed()); - m_btnBkColor.Click(); - return true; - - case 0x55: // ctrl-u (underlining) - m_btnUnderline.Push(!m_btnUnderline.IsPushed()); - m_btnUnderline.Click(); - return true; - - case VK_F4: // ctrl-F4 - CloseTab(); - return true; - } - } - - return false; -} - -void CSrmmBaseDialog::RefreshButtonStatus() -{ - if (m_si == nullptr) - return; - - CHARFORMAT2 cf; - cf.cbSize = sizeof(CHARFORMAT2); - cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_BACKCOLOR | CFM_COLOR; - m_message.SendMsg(EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); - - if (m_si->pMI->bColor) { - bool bState = m_btnColor.IsPushed(); - if (!bState && cf.crTextColor != m_clrInputFG) - m_btnColor.Push(true); - else if (bState && cf.crTextColor == m_clrInputFG) - m_btnColor.Push(false); - } - - if (m_si->pMI->bBkgColor) { - bool bState = m_btnBkColor.IsPushed(); - if (!bState && cf.crBackColor != m_clrInputBG) - m_btnBkColor.Push(true); - else if (bState && cf.crBackColor == m_clrInputBG) - m_btnBkColor.Push(false); - } - - if (m_si->pMI->bBold) { - bool bState = m_btnBold.IsPushed(); - UINT u2 = cf.dwEffects & CFE_BOLD; - if (!bState && u2 != 0) - m_btnBold.Push(true); - else if (bState && u2 == 0) - m_btnBold.Push(false); - } - - if (m_si->pMI->bItalics) { - bool bState = m_btnItalic.IsPushed(); - UINT u2 = cf.dwEffects & CFE_ITALIC; - if (!bState && u2 != 0) - m_btnItalic.Push(true); - else if (bState && u2 == 0) - m_btnItalic.Push(false); - } - - if (m_si->pMI->bUnderline) { - bool bState = m_btnUnderline.IsPushed(); - UINT u2 = cf.dwEffects & CFE_UNDERLINE; - if (!bState && u2 != 0) - m_btnUnderline.Push(true); - else if (bState && u2 == 0) - m_btnUnderline.Push(false); - } -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+#include "chat.h"
+#include "resource.h"
+#include "skin.h"
+#include <m_history.h>
+
+CSrmmBaseDialog::CSrmmBaseDialog(CMPluginBase &pPlugin, int idDialog, SESSION_INFO *si) :
+ CDlgBase(pPlugin, idDialog),
+ timerFlash(this, 1),
+ timerType(this, 2),
+
+ m_message(this, IDC_SRMM_MESSAGE),
+ m_nickList(this, IDC_SRMM_NICKLIST),
+
+ m_btnOk(this, IDOK),
+ m_btnFilter(this, IDC_SRMM_FILTER),
+ m_btnHistory(this, IDC_SRMM_HISTORY),
+ m_btnNickList(this, IDC_SRMM_SHOWNICKLIST),
+ m_btnChannelMgr(this, IDC_SRMM_CHANMGR),
+
+ m_btnColor(this, IDC_SRMM_COLOR),
+ m_btnBkColor(this, IDC_SRMM_BKGCOLOR),
+ m_btnBold(this, IDC_SRMM_BOLD),
+
+ m_btnItalic(this, IDC_SRMM_ITALICS),
+ m_btnUnderline(this, IDC_SRMM_UNDERLINE),
+
+ m_si(si),
+ m_hContact(0),
+ m_clrInputBG(GetSysColor(COLOR_WINDOW))
+{
+ m_bFilterEnabled = db_get_b(0, CHAT_MODULE, "FilterEnabled", 0) != 0;
+ m_bNicklistEnabled = db_get_b(0, CHAT_MODULE, "ShowNicklist", 1) != 0;
+ m_iLogFilterFlags = db_get_dw(0, CHAT_MODULE, "FilterFlags", 0x03E0);
+
+ m_btnColor.OnClick = Callback(this, &CSrmmBaseDialog::onClick_Color);
+ m_btnBkColor.OnClick = Callback(this, &CSrmmBaseDialog::onClick_BkColor);
+ m_btnBold.OnClick = m_btnItalic.OnClick = m_btnUnderline.OnClick = Callback(this, &CSrmmBaseDialog::onClick_BIU);
+
+ m_btnHistory.OnClick = Callback(this, &CSrmmBaseDialog::onClick_History);
+ m_btnChannelMgr.OnClick = Callback(this, &CSrmmBaseDialog::onClick_ChanMgr);
+
+ m_nickList.OnDblClick = Callback(this, &CMsgDialog::onDblClick_List);
+
+ if (si) {
+ m_hContact = si->hContact;
+
+ if (si->pMI->bColor) {
+ m_iFG = 4;
+ m_bFGSet = true;
+ }
+ if (si->pMI->bBkgColor) {
+ m_iBG = 2;
+ m_bBGSet = true;
+ }
+ }
+}
+
+void CSrmmBaseDialog::RunUserMenu(HWND hwndOwner, USERINFO *ui, const POINT &pt)
+{
+ HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_USERMENU));
+ HMENU hSubMenu = GetSubMenu(hMenu, 0);
+ TranslateMenu(hSubMenu);
+
+ USERINFO uinew;
+ memcpy(&uinew, ui, sizeof(USERINFO));
+
+ wchar_t szTemp[50];
+ if (uinew.pszNick)
+ mir_snwprintf(szTemp, TranslateT("&Message %s"), uinew.pszNick);
+ else
+ mir_wstrncpy(szTemp, TranslateT("&Message"), _countof(szTemp) - 1);
+
+ if (mir_wstrlen(szTemp) > 40)
+ mir_wstrncpy(szTemp + 40, L"...", 4);
+ ModifyMenu(hMenu, 0, MF_STRING | MF_BYPOSITION, IDM_SENDMESSAGE, szTemp);
+
+ UINT uID = Chat_CreateMenu(hwndOwner, hSubMenu, pt, m_si, uinew.pszUID);
+ switch (uID) {
+ case 0:
+ break;
+
+ case IDM_SENDMESSAGE:
+ Chat_DoEventHook(m_si, GC_USER_PRIVMESS, ui, nullptr, 0);
+ break;
+
+ default:
+ Chat_DoEventHook(m_si, GC_USER_NICKLISTMENU, ui, nullptr, uID);
+ break;
+ }
+ DestroyMenu(hMenu);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static LRESULT CALLBACK Srmm_ButtonSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_RBUTTONUP:
+ if (g_chatApi.bRightClickFilter) {
+ CSrmmBaseDialog *pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA);
+ if (pDlg == nullptr)
+ break;
+
+ switch (GetDlgCtrlID(hwnd)) {
+ case IDC_SRMM_FILTER:
+ pDlg->ShowFilterMenu();
+ break;
+
+ case IDC_SRMM_COLOR:
+ pDlg->ShowColorChooser(IDC_SRMM_COLOR);
+ break;
+
+ case IDC_SRMM_BKGCOLOR:
+ pDlg->ShowColorChooser(IDC_SRMM_BKGCOLOR);
+ break;
+ }
+ }
+ break;
+ }
+
+ return mir_callNextSubclass(hwnd, Srmm_ButtonSubclassProc, msg, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+EXTERN_C MIR_APP_DLL(LRESULT) CALLBACK stubMessageProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CSrmmBaseDialog *pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA);
+ if (pDlg != nullptr)
+ return pDlg->WndProc_Message(msg, wParam, lParam);
+
+ return mir_callNextSubclass(hwnd, stubMessageProc, msg, wParam, lParam);
+}
+
+LRESULT CSrmmBaseDialog::WndProc_Message(UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_SETCURSOR:
+ if (m_bInMenu) {
+ SetCursor(LoadCursor(nullptr, IDC_ARROW));
+ return TRUE;
+ }
+ break;
+
+ case WM_CHAR:
+ switch (wParam) {
+ case 0x02: // ctrl+B
+ if (m_btnBold.Enabled())
+ return 1;
+ break;
+ case 0x09: // ctrl+I
+ if (m_btnItalic.Enabled())
+ return 1;
+ break;
+ case 0x15: // ctrl+U
+ if (m_btnUnderline.Enabled())
+ return 1;
+ break;
+ }
+ break;
+
+ case WM_SYSKEYDOWN:
+ case WM_KEYDOWN:
+ if (wParam == VK_BACK)
+ if (m_message.GetRichTextLength() == 0)
+ return 1;
+
+ MSG tmp = { m_hwnd, msg, wParam, lParam };
+ if (Hotkey_Check(&tmp, g_pszHotkeySection) == 100) {
+ if (!(GetWindowLongPtr(m_message.GetHwnd(), GWL_STYLE) & ES_READONLY)) {
+ PostMessage(m_hwnd, WM_COMMAND, IDOK, 0);
+ return true;
+ }
+ }
+ }
+
+ LRESULT res = mir_callNextSubclass(m_message.GetHwnd(), stubMessageProc, msg, wParam, lParam);
+ switch (msg) {
+ case WM_GETDLGCODE:
+ return res & ~DLGC_HASSETSEL;
+
+ case WM_KEYUP:
+ case WM_LBUTTONUP:
+ case WM_RBUTTONUP:
+ case WM_MBUTTONUP:
+ RefreshButtonStatus();
+ break;
+
+ case WM_KEYDOWN:
+ if ((GetKeyState(VK_CONTROL) & 0x8000) && wParam == 'V' || (GetKeyState(VK_SHIFT) & 0x8000) && wParam == VK_INSERT) {
+ if (IsClipboardFormatAvailable(CF_HDROP)) {
+ m_message.SendMsg(WM_PASTE, 0, 0);
+ return 0;
+ }
+ }
+
+ __fallthrough;
+
+ case WM_SYSKEYDOWN:
+ if (!(GetKeyState(VK_RMENU) & 0x8000)) {
+ MSG message = { m_hwnd, msg, wParam, lParam };
+ LRESULT iButtonFrom = Hotkey_Check(&message, BB_HK_SECTION);
+ if (iButtonFrom) {
+ Srmm_ProcessToolbarHotkey(m_hContact, iButtonFrom, m_hwnd);
+ return TRUE;
+ }
+ }
+ break;
+ }
+
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// process mouse - hovering for the nickname list.fires events so the protocol can
+// show the userinfo - tooltip.
+
+static void ProcessNickListHovering(HWND hwnd, int hoveredItem, SESSION_INFO *parentdat)
+{
+ static int currentHovered = -1;
+ static HWND hwndToolTip = nullptr;
+ static HWND oldParent = nullptr;
+
+ if (hoveredItem == currentHovered)
+ return;
+
+ currentHovered = hoveredItem;
+
+ if (oldParent != hwnd && hwndToolTip) {
+ SendMessage(hwndToolTip, TTM_DELTOOL, 0, 0);
+ DestroyWindow(hwndToolTip);
+ hwndToolTip = nullptr;
+ }
+
+ if (hoveredItem == -1) {
+ SendMessage(hwndToolTip, TTM_ACTIVATE, 0, 0);
+ return;
+ }
+
+ bool bNewTip = false;
+ if (!hwndToolTip) {
+ bNewTip = true;
+ hwndToolTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, nullptr,
+ WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ hwnd, nullptr, g_plugin.getInst(), nullptr);
+ }
+
+ RECT clientRect;
+ GetClientRect(hwnd, &clientRect);
+
+ TOOLINFO ti = { sizeof(ti) };
+ ti.uFlags = TTF_SUBCLASS;
+ ti.hinst = g_plugin.getInst();
+ ti.hwnd = hwnd;
+ ti.uId = 1;
+ ti.rect = clientRect;
+
+ CMStringW wszBuf;
+
+ USERINFO *ui1 = g_chatApi.SM_GetUserFromIndex(parentdat->ptszID, parentdat->pszModule, currentHovered);
+ if (ui1) {
+ if (ProtoServiceExists(parentdat->pszModule, MS_GC_PROTO_GETTOOLTIPTEXT)) {
+ wchar_t *p = (wchar_t*)CallProtoService(parentdat->pszModule, MS_GC_PROTO_GETTOOLTIPTEXT, (WPARAM)parentdat->ptszID, (LPARAM)ui1->pszUID);
+ if (p != nullptr) {
+ wszBuf = p;
+ mir_free(p);
+ }
+ }
+
+ if (wszBuf.IsEmpty())
+ wszBuf.Format(L"%s: %s\r\n%s: %s\r\n%s: %s",
+ TranslateT("Nickname"), ui1->pszNick,
+ TranslateT("Unique ID"), ui1->pszUID,
+ TranslateT("Status"), g_chatApi.TM_WordToString(parentdat->pStatuses, ui1->Status));
+ ti.lpszText = wszBuf.GetBuffer();
+ }
+
+ SendMessage(hwndToolTip, bNewTip ? TTM_ADDTOOL : TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
+ SendMessage(hwndToolTip, TTM_ACTIVATE, (ti.lpszText != nullptr), 0);
+ SendMessage(hwndToolTip, TTM_SETMAXTIPWIDTH, 0, 400);
+}
+
+static void CALLBACK ChatTimerProc(HWND hwnd, UINT, UINT_PTR idEvent, DWORD)
+{
+ SESSION_INFO *si = (SESSION_INFO*)idEvent;
+
+ POINT pt;
+ GetCursorPos(&pt);
+ ScreenToClient(hwnd, &pt);
+
+ uint32_t nItemUnderMouse = (uint32_t)SendMessage(hwnd, LB_ITEMFROMPOINT, 0, MAKELPARAM(pt.x, pt.y));
+ if (HIWORD(nItemUnderMouse) == 1)
+ nItemUnderMouse = (uint32_t)(-1);
+ else
+ nItemUnderMouse &= 0xFFFF;
+ if (((int)nItemUnderMouse != si->currentHovered) || (nItemUnderMouse == -1)) {
+ KillTimer(hwnd, idEvent);
+ return;
+ }
+
+ USERINFO *ui1 = g_chatApi.SM_GetUserFromIndex(si->ptszID, si->pszModule, si->currentHovered);
+ if (ui1) {
+ CMStringW wszBuf;
+ if (ProtoServiceExists(si->pszModule, MS_GC_PROTO_GETTOOLTIPTEXT)) {
+ wchar_t *p = (wchar_t*)CallProtoService(si->pszModule, MS_GC_PROTO_GETTOOLTIPTEXT, (WPARAM)si->ptszID, (LPARAM)ui1->pszUID);
+ if (p) {
+ wszBuf = p;
+ mir_free(p);
+ }
+ }
+ if (wszBuf.IsEmpty())
+ wszBuf.Format(L"<b>%s:</b>\t%s\n<b>%s:</b>\t%s\n<b>%s:</b>\t%s",
+ TranslateT("Nick"), ui1->pszNick,
+ TranslateT("Unique ID"), ui1->pszUID,
+ TranslateT("Status"), g_chatApi.TM_WordToString(si->pStatuses, ui1->Status));
+
+ CLCINFOTIP ti = { sizeof(ti) };
+ Tipper_ShowTip(wszBuf, &ti);
+ si->bHasToolTip = true;
+ }
+ KillTimer(hwnd, idEvent);
+}
+
+EXTERN_C MIR_APP_DLL(LRESULT) CALLBACK stubNicklistProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CSrmmBaseDialog *pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(GetParent(hwnd), GWLP_USERDATA);
+ if (pDlg != nullptr)
+ return pDlg->WndProc_Nicklist(msg, wParam, lParam);
+
+ return mir_callNextSubclass(hwnd, stubNicklistProc, msg, wParam, lParam);
+}
+
+LRESULT CSrmmBaseDialog::WndProc_Nicklist(UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ RECT rc;
+
+ switch (msg) {
+ case WM_MEASUREITEM:
+ {
+ MEASUREITEMSTRUCT *mis = (MEASUREITEMSTRUCT *)lParam;
+ if (mis->CtlType == ODT_MENU)
+ return Menu_MeasureItem(lParam);
+ }
+ return FALSE;
+
+ case WM_DRAWITEM:
+ {
+ DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)lParam;
+ if (dis->CtlType == ODT_MENU)
+ return Menu_DrawItem(lParam);
+ }
+ return FALSE;
+
+ case WM_MOUSEMOVE:
+ RECT clientRect;
+ {
+ bool bTooltipExists = ServiceExists(MS_TIPPER_HIDETIP);
+
+ POINT pt = { LOWORD(lParam), HIWORD(lParam) };
+ GetClientRect(m_nickList.GetHwnd(), &clientRect);
+ if (PtInRect(&clientRect, pt)) {
+ // hit test item under mouse
+ uint32_t nItemUnderMouse = m_nickList.SendMsg(LB_ITEMFROMPOINT, 0, lParam);
+ if (HIWORD(nItemUnderMouse) == 1)
+ nItemUnderMouse = (uint32_t)(-1);
+ else
+ nItemUnderMouse &= 0xFFFF;
+
+ if (bTooltipExists) {
+ if ((int)nItemUnderMouse == m_si->currentHovered)
+ break;
+ m_si->currentHovered = (int)nItemUnderMouse;
+
+ KillTimer(m_nickList.GetHwnd(), 1);
+
+ if (m_si->bHasToolTip) {
+ Tipper_Hide();
+ m_si->bHasToolTip = false;
+ }
+
+ if (nItemUnderMouse != -1)
+ SetTimer(m_nickList.GetHwnd(), (UINT_PTR)m_si, 450, ChatTimerProc);
+ }
+ else ProcessNickListHovering(m_nickList.GetHwnd(), (int)nItemUnderMouse, m_si);
+ }
+ else {
+ if (bTooltipExists) {
+ KillTimer(m_nickList.GetHwnd(), 1);
+ if (m_si->bHasToolTip) {
+ Tipper_Hide();
+ m_si->bHasToolTip = false;
+ }
+ }
+ else ProcessNickListHovering(m_nickList.GetHwnd(), -1, nullptr);
+ }
+ }
+ break;
+
+ case WM_ERASEBKGND:
+ {
+ HDC dc = (HDC)wParam;
+ if (dc == nullptr)
+ break;
+
+ int nUsers = m_si->getUserList().getCount();
+
+ int index = m_nickList.SendMsg(LB_GETTOPINDEX, 0, 0);
+ if (index == LB_ERR || nUsers <= 0)
+ break;
+
+ int height = m_nickList.SendMsg(LB_GETITEMHEIGHT, 0, 0);
+ if (height == LB_ERR)
+ break;
+
+ GetClientRect(m_nickList.GetHwnd(), &rc);
+
+ int items = nUsers - index;
+ if (rc.bottom - rc.top > items * height) {
+ rc.top = items * height;
+ FillRect(dc, &rc, g_chatApi.hListBkgBrush);
+ }
+ }
+ return 1;
+
+ case WM_CONTEXTMENU:
+ POINT pt;
+ {
+ int height = 0;
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+ if (pt.x == -1 && pt.y == -1) {
+ int index = m_nickList.GetCurSel();
+ int top = m_nickList.SendMsg(LB_GETTOPINDEX, 0, 0);
+ height = m_nickList.SendMsg(LB_GETITEMHEIGHT, 0, 0);
+ pt.x = 4;
+ pt.y = (index - top)*height + 1;
+ }
+ else ScreenToClient(m_nickList.GetHwnd(), &pt);
+
+ int item = LOWORD(m_nickList.SendMsg(LB_ITEMFROMPOINT, 0, MAKELPARAM(pt.x, pt.y)));
+ USERINFO *ui = g_chatApi.SM_GetUserFromIndex(m_si->ptszID, m_si->pszModule, item);
+ if (ui != nullptr) {
+ if (pt.x == -1 && pt.y == -1)
+ pt.y += height - 4;
+ ClientToScreen(m_nickList.GetHwnd(), &pt);
+
+ RunUserMenu(m_nickList.GetHwnd(), ui, pt);
+ return TRUE;
+ }
+ }
+ break;
+ }
+
+ return mir_callNextSubclass(m_nickList.GetHwnd(), stubNicklistProc, msg, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool CSrmmBaseDialog::OnInitDialog()
+{
+ WindowList_Add(g_hWindowList, m_hwnd, m_hContact);
+ SetWindowLongPtr(m_hwnd, GWLP_USERDATA, (LONG_PTR)this);
+
+ m_pLog = Srmm_GetLogWindow((CMsgDialog*)this);
+ if (m_pLog->GetType() != 0) { // custom log type
+ HWND hwndLog = GetDlgItem(m_hwnd, IDC_SRMM_LOG);
+ EnableWindow(hwndLog, FALSE);
+ ShowWindow(hwndLog, SW_HIDE);
+ }
+ m_pLog->Attach();
+
+ SetWindowLongPtr(m_message.GetHwnd(), GWLP_USERDATA, LPARAM(this));
+ mir_subclassWindow(m_message.GetHwnd(), stubMessageProc);
+ m_message.SetReadOnly(false);
+ ::DragAcceptFiles(m_message.GetHwnd(), TRUE);
+
+ if (isChat()) {
+ SetWindowLongPtr(m_nickList.GetHwnd(), GWLP_USERDATA, LPARAM(this));
+ mir_subclassWindow(m_nickList.GetHwnd(), stubNicklistProc);
+ }
+
+ // three buttons below are initiated inside this call, so button creation must precede subclassing
+ Srmm_CreateToolbarIcons(m_hwnd, isChat() ? BBBF_ISCHATBUTTON : BBBF_ISIMBUTTON);
+
+ mir_subclassWindow(m_btnFilter.GetHwnd(), Srmm_ButtonSubclassProc);
+ mir_subclassWindow(m_btnColor.GetHwnd(), Srmm_ButtonSubclassProc);
+ mir_subclassWindow(m_btnBkColor.GetHwnd(), Srmm_ButtonSubclassProc);
+
+ LoadSettings();
+ return true;
+}
+
+void CSrmmBaseDialog::OnDestroy()
+{
+ m_pLog->Detach();
+ delete m_pLog;
+
+ WindowList_Remove(g_hWindowList, m_hwnd);
+
+ SetWindowLongPtr(m_hwnd, GWLP_USERDATA, 0);
+ mir_unsubclassWindow(m_message.GetHwnd(), stubMessageProc);
+ mir_unsubclassWindow(m_nickList.GetHwnd(), stubNicklistProc);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR CSrmmBaseDialog::DlgProc(UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_COMMAND:
+ if (!lParam && Clist_MenuProcessCommand(LOWORD(wParam), MPCF_CONTACTMENU, m_hContact))
+ return 0;
+
+ if (wParam >= MIN_CBUTTONID && wParam <= MAX_CBUTTONID) {
+ Srmm_ClickToolbarIcon(m_hContact, wParam, m_hwnd, 0);
+ return 0;
+ }
+ break;
+
+ case WM_ACTIVATE:
+ if (m_si && LOWORD(wParam) == WA_INACTIVE) {
+ m_si->wState &= ~GC_EVENT_HIGHLIGHT;
+ m_si->wState &= ~STATE_TALK;
+ }
+ break;
+
+ case WM_CBD_RECREATE:
+ Srmm_CreateToolbarIcons(m_hwnd, isChat() ? BBBF_ISCHATBUTTON : BBBF_ISIMBUTTON);
+ break;
+
+ case WM_NOTIFY:
+ LPNMHDR hdr = (LPNMHDR)lParam;
+ if (hdr->hwndFrom == m_pLog->GetHwnd())
+ m_pLog->Notify(wParam, lParam);
+ break;
+ }
+
+ return CDlgBase::DlgProc(msg, wParam, lParam);
+}
+
+void CSrmmBaseDialog::AddLog()
+{
+ if (m_si->pLogEnd)
+ m_pLog->LogEvents(m_si->pLog, false);
+ else
+ m_pLog->Clear();
+}
+
+bool CSrmmBaseDialog::AllowTyping() const
+{
+ return isChat() ? m_si->iType != GCW_SERVER : true;
+}
+
+void CSrmmBaseDialog::ClearLog()
+{
+ m_pLog->Clear();
+}
+
+void CSrmmBaseDialog::UpdateOptions()
+{
+ MODULEINFO *mi = m_si->pMI;
+ EnableWindow(m_btnBold.GetHwnd(), mi->bBold);
+ EnableWindow(m_btnItalic.GetHwnd(), mi->bItalics);
+ EnableWindow(m_btnUnderline.GetHwnd(), mi->bUnderline);
+ EnableWindow(m_btnColor.GetHwnd(), mi->bColor);
+ EnableWindow(m_btnBkColor.GetHwnd(), mi->bBkgColor);
+ if (m_si->iType == GCW_CHATROOM)
+ EnableWindow(m_btnChannelMgr.GetHwnd(), mi->bChanMgr);
+
+ Resize();
+ RedrawLog2(m_si);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void RedrawLog2(SESSION_INFO *si)
+{
+ si->LastTime = 0;
+ if (si->pLog)
+ si->pDlg->log()->LogEvents(si->pLogEnd, TRUE);
+}
+
+static void __cdecl phase2(SESSION_INFO *si)
+{
+ Sleep(30);
+ if (si && si->pDlg)
+ RedrawLog2(si);
+}
+
+void CSrmmBaseDialog::RedrawLog()
+{
+ m_si->LastTime = 0;
+ if (m_si->pLog) {
+ LOGINFO *pLog = m_si->pLog;
+ if (m_si->iEventCount > 60) {
+ int index = 0;
+ while (index < 59) {
+ if (pLog->next == nullptr)
+ break;
+
+ pLog = pLog->next;
+ if (m_si->iType != GCW_CHATROOM || !m_bFilterEnabled || (m_iLogFilterFlags & pLog->iType) != 0)
+ index++;
+ }
+ m_pLog->LogEvents(pLog, true);
+ mir_forkThread<SESSION_INFO>(phase2, m_si);
+ }
+ else m_pLog->LogEvents(m_si->pLogEnd, true);
+ }
+ else ClearLog();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CSrmmBaseDialog::onClick_Color(CCtrlButton *pButton)
+{
+ if (!pButton->Enabled())
+ return;
+
+ CHARFORMAT2 cf;
+ cf.cbSize = sizeof(CHARFORMAT2);
+ cf.dwEffects = 0;
+ cf.dwMask = CFM_COLOR;
+
+ if (IsDlgButtonChecked(m_hwnd, pButton->GetCtrlId())) {
+ if (!g_chatApi.bRightClickFilter) {
+ ShowColorChooser(pButton->GetCtrlId());
+ return;
+ }
+ if (m_bFGSet)
+ cf.crTextColor = m_iFG;
+ }
+ else cf.crTextColor = m_clrInputFG;
+
+ m_message.SendMsg(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
+}
+
+void CSrmmBaseDialog::onClick_BkColor(CCtrlButton *pButton)
+{
+ if (!pButton->Enabled())
+ return;
+
+ CHARFORMAT2 cf;
+ cf.cbSize = sizeof(CHARFORMAT2);
+ cf.dwEffects = 0;
+ cf.dwMask = CFM_BACKCOLOR;
+
+ if (IsDlgButtonChecked(m_hwnd, pButton->GetCtrlId())) {
+ if (!g_chatApi.bRightClickFilter) {
+ ShowColorChooser(pButton->GetCtrlId());
+ return;
+ }
+ if (m_bBGSet)
+ cf.crBackColor = m_iBG;
+ }
+ else cf.crBackColor = m_clrInputBG;
+
+ m_message.SendMsg(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
+}
+
+void CSrmmBaseDialog::onClick_BIU(CCtrlButton *pButton)
+{
+ if (!pButton->Enabled())
+ return;
+
+ CHARFORMAT2 cf;
+ cf.cbSize = sizeof(CHARFORMAT2);
+ cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE;
+ cf.dwEffects = 0;
+
+ if (IsDlgButtonChecked(m_hwnd, IDC_SRMM_BOLD))
+ cf.dwEffects |= CFE_BOLD;
+ if (IsDlgButtonChecked(m_hwnd, IDC_SRMM_ITALICS))
+ cf.dwEffects |= CFE_ITALIC;
+ if (IsDlgButtonChecked(m_hwnd, IDC_SRMM_UNDERLINE))
+ cf.dwEffects |= CFE_UNDERLINE;
+ m_message.SendMsg(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
+}
+
+void CSrmmBaseDialog::onClick_History(CCtrlButton *pButton)
+{
+ if (!pButton->Enabled())
+ return;
+
+ if (m_si != nullptr)
+ ShellExecute(m_hwnd, nullptr, g_chatApi.GetChatLogsFilename(m_si, 0), nullptr, nullptr, SW_SHOW);
+ else
+ CallService(MS_HISTORY_SHOWCONTACTHISTORY, m_hContact, 0);
+}
+
+void CSrmmBaseDialog::onClick_ChanMgr(CCtrlButton *pButton)
+{
+ if (pButton->Enabled())
+ Chat_DoEventHook(m_si, GC_USER_CHANMGR, nullptr, nullptr, 0);
+}
+
+void CSrmmBaseDialog::onDblClick_List(CCtrlListBox *pList)
+{
+ TVHITTESTINFO hti;
+ hti.pt.x = (short)LOWORD(GetMessagePos());
+ hti.pt.y = (short)HIWORD(GetMessagePos());
+ ScreenToClient(pList->GetHwnd(), &hti.pt);
+
+ int item = LOWORD(pList->SendMsg(LB_ITEMFROMPOINT, 0, MAKELPARAM(hti.pt.x, hti.pt.y)));
+ USERINFO *ui = g_chatApi.UM_FindUserFromIndex(m_si, item);
+ if (ui == nullptr)
+ return;
+
+ bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
+ if (Chat::bDoubleClick4Privat ? bShift : !bShift) {
+ int selStart = LOWORD(m_message.SendMsg(EM_GETSEL, 0, 0));
+ CMStringW tszName(ui->pszNick);
+ if (selStart == 0)
+ tszName.AppendChar(':');
+ tszName.AppendChar(' ');
+
+ m_message.SendMsg(EM_REPLACESEL, FALSE, (LPARAM)tszName.GetString());
+ PostMessage(m_hwnd, WM_MOUSEACTIVATE, 0, 0);
+ SetFocus(m_message.GetHwnd());
+ }
+ else Chat_DoEventHook(m_si, GC_USER_PRIVMESS, ui, nullptr, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+extern HANDLE hHookSrmmEvent;
+
+int CSrmmBaseDialog::NotifyEvent(int code)
+{
+ if (m_hContact == 0 && m_hwnd == nullptr)
+ return -1;
+
+ MessageWindowEventData mwe = {};
+ mwe.hContact = m_hContact;
+ mwe.hwndWindow = m_hwnd;
+ mwe.uType = code;
+ mwe.uFlags = MSG_WINDOW_UFLAG_MSG_BOTH;
+ mwe.hwndInput = m_message.GetHwnd();
+ mwe.hwndLog = m_pLog->GetHwnd();
+ return ::NotifyEventHooks(hHookSrmmEvent, 0, (LPARAM)&mwe);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool CSrmmBaseDialog::ProcessFileDrop(HDROP hDrop, MCONTACT hContact)
+{
+ if (PasteFilesAsURL(hDrop))
+ return true;
+
+ return ::ProcessFileDrop(hDrop, hContact);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// If enabled pastes droped files as list of URL of file:/// type
+// Can be enabled/disabled by Chat/ShiftDropFilePasteURL database parameter
+// @param hDrop - Drop handle
+// @return Returns true if processed here, returns false if should be processed elsewhere
+
+bool CSrmmBaseDialog::PasteFilesAsURL(HDROP hDrop)
+{
+ bool isShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
+ if (db_get_b(0, CHAT_MODULE, "ShiftDropFilePasteURL", 1) == 0 || !isShift) // hidden setting: Chat/ShiftDropFilePasteURL
+ return false;
+
+ int fileCount = DragQueryFileW(hDrop, -1, nullptr, 0);
+ if (fileCount == 0)
+ return true;
+
+ CMStringW pasteString(L" ");
+ for (int i = 0; i < fileCount; i++) {
+ wchar_t szFilename[MAX_PATH];
+ if (DragQueryFileW(hDrop, i, szFilename, _countof(szFilename))) {
+ CMStringW fileString(L"file:///");
+ fileString.Append(szFilename);
+ fileString.Replace(L"%", L"%25");
+ fileString.Replace(L" ", L"%20");
+ fileString.Append((i != fileCount - 1) ? L"\r\n" : L" ");
+ pasteString += fileString;
+ }
+ }
+
+ m_message.SendMsg(EM_REPLACESEL, TRUE, (LPARAM)pasteString.c_str());
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool CSrmmBaseDialog::ProcessHotkeys(int key, bool isShift, bool isCtrl, bool isAlt)
+{
+ // Esc (close tab)
+ if (key == VK_ESCAPE && !isShift && !isCtrl && !isAlt) {
+ CloseTab();
+ return true;
+ }
+
+ if (isCtrl && !isAlt) {
+ switch (key) {
+ case VK_SPACE: // ctrl-space (paste clean text)
+ m_btnBold.Push(false); m_btnBold.Click();
+ m_btnItalic.Push(false); m_btnItalic.Click();
+ m_btnUnderline.Push(false); m_btnUnderline.Click();
+
+ m_btnColor.Push(false); m_btnColor.Click();
+ m_btnBkColor.Push(false); m_btnBkColor.Click();
+ return true;
+
+ case 0x42: // ctrl-b (bold)
+ m_btnBold.Push(!m_btnBold.IsPushed());
+ m_btnBold.Click();
+ return true;
+
+ case 0x48: // ctrl-h (history)
+ m_btnHistory.Click();
+ return true;
+
+ case 0x49: // ctrl-i (italics)
+ m_btnItalic.Push(!m_btnItalic.IsPushed());
+ m_btnItalic.Click();
+ return true;
+
+ case 0x4b: // ctrl-k (text color)
+ m_btnColor.Push(!m_btnColor.IsPushed());
+ m_btnColor.Click();
+ return true;
+
+ case 0x4c: // ctrl-l (back color)
+ m_btnBkColor.Push(!m_btnBkColor.IsPushed());
+ m_btnBkColor.Click();
+ return true;
+
+ case 0x55: // ctrl-u (underlining)
+ m_btnUnderline.Push(!m_btnUnderline.IsPushed());
+ m_btnUnderline.Click();
+ return true;
+
+ case VK_F4: // ctrl-F4
+ CloseTab();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CSrmmBaseDialog::RefreshButtonStatus()
+{
+ if (m_si == nullptr)
+ return;
+
+ CHARFORMAT2 cf;
+ cf.cbSize = sizeof(CHARFORMAT2);
+ cf.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_BACKCOLOR | CFM_COLOR;
+ m_message.SendMsg(EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
+
+ if (m_si->pMI->bColor) {
+ bool bState = m_btnColor.IsPushed();
+ if (!bState && cf.crTextColor != m_clrInputFG)
+ m_btnColor.Push(true);
+ else if (bState && cf.crTextColor == m_clrInputFG)
+ m_btnColor.Push(false);
+ }
+
+ if (m_si->pMI->bBkgColor) {
+ bool bState = m_btnBkColor.IsPushed();
+ if (!bState && cf.crBackColor != m_clrInputBG)
+ m_btnBkColor.Push(true);
+ else if (bState && cf.crBackColor == m_clrInputBG)
+ m_btnBkColor.Push(false);
+ }
+
+ if (m_si->pMI->bBold) {
+ bool bState = m_btnBold.IsPushed();
+ UINT u2 = cf.dwEffects & CFE_BOLD;
+ if (!bState && u2 != 0)
+ m_btnBold.Push(true);
+ else if (bState && u2 == 0)
+ m_btnBold.Push(false);
+ }
+
+ if (m_si->pMI->bItalics) {
+ bool bState = m_btnItalic.IsPushed();
+ UINT u2 = cf.dwEffects & CFE_ITALIC;
+ if (!bState && u2 != 0)
+ m_btnItalic.Push(true);
+ else if (bState && u2 == 0)
+ m_btnItalic.Push(false);
+ }
+
+ if (m_si->pMI->bUnderline) {
+ bool bState = m_btnUnderline.IsPushed();
+ UINT u2 = cf.dwEffects & CFE_UNDERLINE;
+ if (!bState && u2 != 0)
+ m_btnUnderline.Push(true);
+ else if (bState && u2 == 0)
+ m_btnUnderline.Push(false);
+ }
+}
diff --git a/src/mir_app/src/srmm_log.cpp b/src/mir_app/src/srmm_log.cpp index bc97e37023..d60636339d 100644 --- a/src/mir_app/src/srmm_log.cpp +++ b/src/mir_app/src/srmm_log.cpp @@ -1,196 +1,196 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -///////////////////////////////////////////////////////////////////////////////////////// -// SRMM log container - -#include "stdafx.h" -#include "chat.h" - -struct LoggerClass -{ - LoggerClass(CMPlugin *p1, const char *p2, const wchar_t *p3, pfnSrmmLogCreator p4) : - pPlugin(p1), - szShortName(mir_strdup(p2)), - wszScreenName(mir_wstrdup(p3)), - pfnBuilder(p4) - {} - - CMPlugin *pPlugin; - ptrA szShortName; - ptrW wszScreenName; - pfnSrmmLogCreator pfnBuilder; -}; - -static OBJLIST<LoggerClass> g_arLogClasses(1, PtrKeySortT); - -static CMOption<uint8_t> g_bEnableCustomLogs("SRMM", "EnableCustomLogs", 0); - -///////////////////////////////////////////////////////////////////////////////////////// - -static bool sttEnableCustomLogs(CMsgDialog *pDlg) -{ - // always enable custom log viewers for private chats - if (!pDlg->isChat()) - return true; - - // if custom log viewers are disable, use build-in one - if (!g_bEnableCustomLogs) - return false; - - // check if custom viewers are forbidden for this particular account - auto *szProto = Proto_GetBaseAccountName(pDlg->m_hContact); - if (szProto) { - // hidden setting !!!!!!!! - CMStringA szProtoList(db_get_sm(0, "SRMM", "DisableCustomLogsForProto")); - - int iStart = 0; - while (true) { - auto forbiddenProto = szProtoList.Tokenize(",; ", iStart); - if (forbiddenProto.IsEmpty()) - break; - - if (forbiddenProto == szProto) - return false; - } - } - - // ok-ok, use that custom viewer - return true; -} - -CSrmmLogWindow* Srmm_GetLogWindow(CMsgDialog *pDlg) -{ - if (sttEnableCustomLogs(pDlg)) { - CMStringA szViewerName(db_get_sm(pDlg->m_hContact, SRMM_MODULE, "Logger")); - if (szViewerName.IsEmpty()) - szViewerName = db_get_sm(0, "SRMM", "Logger", "built-in"); - - for (auto &it : g_arLogClasses) - if (szViewerName == it->szShortName) - return it->pfnBuilder(*pDlg); - } - - for (auto &it : g_arLogClasses) - if (!mir_strcmp(it->szShortName, "built-in")) - return it->pfnBuilder(*pDlg); - - return nullptr; // shall never happen -} - -///////////////////////////////////////////////////////////////////////////////////////// -// options dialog - -static class CSrmmLogOptionsDlg *pDialog = nullptr; - -class CSrmmLogOptionsDlg : public CDlgBase -{ - CCtrlListBox m_list; - CCtrlCheck chkCustomLogs; - -public: - CSrmmLogOptionsDlg() : - CDlgBase(g_plugin, IDD_OPT_SRMMLOG), - m_list(this, IDC_LIST), - chkCustomLogs(this, IDC_ENABLE_CUSTOM) - { - CreateLink(chkCustomLogs, g_bEnableCustomLogs); - - m_list.OnSelChange = Callback(this, &CSrmmLogOptionsDlg::onChange_List); - } - - bool OnInitDialog() override - { - pDialog = this; - ptrA szCurr(db_get_sa(0, "SRMM", "Logger", "built-in")); - - for (auto &it : g_arLogClasses) { - int idx = m_list.AddString(TranslateW_LP(it->wszScreenName, it->pPlugin), LPARAM(it)); - if (!mir_strcmp(szCurr, it->szShortName)) - m_list.SetCurSel(idx); - } - - return true; - } - - bool OnApply() override - { - int idx = m_list.GetCurSel(); - if (idx == -1) - return false; - - auto *pLogger = (LoggerClass *)m_list.GetItemData(idx); - db_set_s(0, "SRMM", "Logger", pLogger->szShortName); - return true; - } - - void OnDestroy() override - { - pDialog = nullptr; - } - - void Rebuild() - { - m_list.ResetContent(); - OnInitDialog(); - } - - void onChange_List(CCtrlListBox *) - { - NotifyChange(); - } -}; - -void SrmmLogOptionsInit(WPARAM wParam) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.position = 910000000; - odp.szGroup.a = LPGEN("Message sessions"); - odp.szTitle.a = LPGEN("Log viewer"); - odp.flags = ODPF_BOLDGROUPS; - odp.pDialog = new CSrmmLogOptionsDlg(); - g_plugin.addOptions(wParam, &odp); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(HANDLE) RegisterSrmmLog(CMPlugin *pPlugin, const char *pszShortName, const wchar_t *pwszScreenName, pfnSrmmLogCreator fnBuilder) -{ - if (!pszShortName || !pwszScreenName || !fnBuilder) - return nullptr; - - auto *p = new LoggerClass(pPlugin, pszShortName, pwszScreenName, fnBuilder); - g_arLogClasses.insert(p); - - if (pDialog) - pDialog->Rebuild(); - return p; -} - -MIR_APP_DLL(void) UnregisterSrmmLog(HANDLE pLogger) -{ - g_arLogClasses.remove((LoggerClass *)pLogger); - - if (pDialog) - pDialog->Rebuild(); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// SRMM log container
+
+#include "stdafx.h"
+#include "chat.h"
+
+struct LoggerClass
+{
+ LoggerClass(CMPlugin *p1, const char *p2, const wchar_t *p3, pfnSrmmLogCreator p4) :
+ pPlugin(p1),
+ szShortName(mir_strdup(p2)),
+ wszScreenName(mir_wstrdup(p3)),
+ pfnBuilder(p4)
+ {}
+
+ CMPlugin *pPlugin;
+ ptrA szShortName;
+ ptrW wszScreenName;
+ pfnSrmmLogCreator pfnBuilder;
+};
+
+static OBJLIST<LoggerClass> g_arLogClasses(1, PtrKeySortT);
+
+static CMOption<uint8_t> g_bEnableCustomLogs("SRMM", "EnableCustomLogs", 0);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool sttEnableCustomLogs(CMsgDialog *pDlg)
+{
+ // always enable custom log viewers for private chats
+ if (!pDlg->isChat())
+ return true;
+
+ // if custom log viewers are disable, use build-in one
+ if (!g_bEnableCustomLogs)
+ return false;
+
+ // check if custom viewers are forbidden for this particular account
+ auto *szProto = Proto_GetBaseAccountName(pDlg->m_hContact);
+ if (szProto) {
+ // hidden setting !!!!!!!!
+ CMStringA szProtoList(db_get_sm(0, "SRMM", "DisableCustomLogsForProto"));
+
+ int iStart = 0;
+ while (true) {
+ auto forbiddenProto = szProtoList.Tokenize(",; ", iStart);
+ if (forbiddenProto.IsEmpty())
+ break;
+
+ if (forbiddenProto == szProto)
+ return false;
+ }
+ }
+
+ // ok-ok, use that custom viewer
+ return true;
+}
+
+CSrmmLogWindow* Srmm_GetLogWindow(CMsgDialog *pDlg)
+{
+ if (sttEnableCustomLogs(pDlg)) {
+ CMStringA szViewerName(db_get_sm(pDlg->m_hContact, SRMM_MODULE, "Logger"));
+ if (szViewerName.IsEmpty())
+ szViewerName = db_get_sm(0, "SRMM", "Logger", "built-in");
+
+ for (auto &it : g_arLogClasses)
+ if (szViewerName == it->szShortName)
+ return it->pfnBuilder(*pDlg);
+ }
+
+ for (auto &it : g_arLogClasses)
+ if (!mir_strcmp(it->szShortName, "built-in"))
+ return it->pfnBuilder(*pDlg);
+
+ return nullptr; // shall never happen
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// options dialog
+
+static class CSrmmLogOptionsDlg *pDialog = nullptr;
+
+class CSrmmLogOptionsDlg : public CDlgBase
+{
+ CCtrlListBox m_list;
+ CCtrlCheck chkCustomLogs;
+
+public:
+ CSrmmLogOptionsDlg() :
+ CDlgBase(g_plugin, IDD_OPT_SRMMLOG),
+ m_list(this, IDC_LIST),
+ chkCustomLogs(this, IDC_ENABLE_CUSTOM)
+ {
+ CreateLink(chkCustomLogs, g_bEnableCustomLogs);
+
+ m_list.OnSelChange = Callback(this, &CSrmmLogOptionsDlg::onChange_List);
+ }
+
+ bool OnInitDialog() override
+ {
+ pDialog = this;
+ ptrA szCurr(db_get_sa(0, "SRMM", "Logger", "built-in"));
+
+ for (auto &it : g_arLogClasses) {
+ int idx = m_list.AddString(TranslateW_LP(it->wszScreenName, it->pPlugin), LPARAM(it));
+ if (!mir_strcmp(szCurr, it->szShortName))
+ m_list.SetCurSel(idx);
+ }
+
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ int idx = m_list.GetCurSel();
+ if (idx == -1)
+ return false;
+
+ auto *pLogger = (LoggerClass *)m_list.GetItemData(idx);
+ db_set_s(0, "SRMM", "Logger", pLogger->szShortName);
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ pDialog = nullptr;
+ }
+
+ void Rebuild()
+ {
+ m_list.ResetContent();
+ OnInitDialog();
+ }
+
+ void onChange_List(CCtrlListBox *)
+ {
+ NotifyChange();
+ }
+};
+
+void SrmmLogOptionsInit(WPARAM wParam)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.position = 910000000;
+ odp.szGroup.a = LPGEN("Message sessions");
+ odp.szTitle.a = LPGEN("Log viewer");
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pDialog = new CSrmmLogOptionsDlg();
+ g_plugin.addOptions(wParam, &odp);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(HANDLE) RegisterSrmmLog(CMPlugin *pPlugin, const char *pszShortName, const wchar_t *pwszScreenName, pfnSrmmLogCreator fnBuilder)
+{
+ if (!pszShortName || !pwszScreenName || !fnBuilder)
+ return nullptr;
+
+ auto *p = new LoggerClass(pPlugin, pszShortName, pwszScreenName, fnBuilder);
+ g_arLogClasses.insert(p);
+
+ if (pDialog)
+ pDialog->Rebuild();
+ return p;
+}
+
+MIR_APP_DLL(void) UnregisterSrmmLog(HANDLE pLogger)
+{
+ g_arLogClasses.remove((LoggerClass *)pLogger);
+
+ if (pDialog)
+ pDialog->Rebuild();
+}
diff --git a/src/mir_app/src/srmm_log_hpp.cpp b/src/mir_app/src/srmm_log_hpp.cpp index 09fe9b3f4b..584ed539f9 100644 --- a/src/mir_app/src/srmm_log_hpp.cpp +++ b/src/mir_app/src/srmm_log_hpp.cpp @@ -1,227 +1,227 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -///////////////////////////////////////////////////////////////////////////////////////// -// CHppLogWindow class - -#include "stdafx.h" -#include "chat.h" - -#define EVENTTYPE_STATUSCHANGE 25368 -#define EVENTTYPE_ERRMSG 25366 - -class CHppLogWindow : public CSrmmLogWindow -{ - HWND m_hwnd = nullptr; - -public: - CHppLogWindow(CMsgDialog &pDlg) : - CSrmmLogWindow(pDlg) - { - } - - void Attach() override - { - IEVIEWWINDOW ieWindow = {}; - ieWindow.iType = IEW_CREATE; - ieWindow.dwMode = IEWM_TABSRMM; - ieWindow.parent = m_pDlg.GetHwnd(); - ieWindow.cx = 10; - ieWindow.cy = 10; - CallService(MS_HPP_EG_WINDOW, 0, (LPARAM)&ieWindow); - m_hwnd = ieWindow.hwnd; - } - - void Detach() override - { - IEVIEWWINDOW ieWindow = {}; - ieWindow.iType = IEW_DESTROY; - ieWindow.hwnd = m_hwnd; - CallService(MS_HPP_EG_WINDOW, 0, (LPARAM)&ieWindow); - } - - ////////////////////////////////////////////////////////////////////////////////////// - - bool AtBottom() override - { - return false; - } - - void Clear() override - { - IEVIEWEVENT event = {}; - event.iType = IEE_CLEAR_LOG; - event.hwnd = m_hwnd; - CallService(MS_HPP_EG_EVENT, 0, (LPARAM)&event); - } - - HWND GetHwnd() override - { - return m_hwnd; - } - - int GetType() override - { - return 2; - } - - wchar_t* GetSelection() override - { - IEVIEWEVENT event = {}; - event.hwnd = m_hwnd; - event.iType = IEE_GET_SELECTION; - event.hContact = m_pDlg.m_hContact; - event.dwFlags = 0; - return (wchar_t *)CallService(MS_HPP_EG_EVENT, 0, (LPARAM)&event); - } - - void LogEvents(MEVENT hDbEventFirst, int count, bool bAppend) override - { - if (!bAppend) - Clear(); - - IEVIEWEVENT event = {}; - event.hwnd = m_hwnd; - event.iType = IEE_LOG_DB_EVENTS; - event.hDbEventFirst = hDbEventFirst; - event.hContact = m_pDlg.m_hContact; - event.count = count; - CallService(MS_HPP_EG_EVENT, 0, (LPARAM)&event); - } - - void CHppLogWindow::LogEvents(LOGINFO *pLog, bool) - { - IEVIEWEVENTDATA ied = {}; - ied.dwFlags = IEEDF_UNICODE_NICK | IEEDF_UNICODE_TEXT; - - IEVIEWEVENT event = {}; - event.hwnd = m_hwnd; - event.hContact = m_pDlg.m_hContact; - event.codepage = CP_ACP; - event.iType = IEE_LOG_MEM_EVENTS; - event.eventData = &ied; - event.count = 1; - - while (pLog) { - if (pLog->ptszText) { - ied.szNick.w = pLog->ptszNick; - ied.szText.w = pLog->ptszText; - ied.time = pLog->time; - ied.bIsMe = pLog->bIsMe; - - switch (pLog->iType) { - case GC_EVENT_MESSAGE: - ied.iType = IEED_GC_EVENT_MESSAGE; - ied.dwData = IEEDD_GC_SHOW_NICK; - break; - case GC_EVENT_ACTION: - ied.iType = IEED_GC_EVENT_ACTION; - break; - case GC_EVENT_JOIN: - ied.iType = IEED_GC_EVENT_JOIN; - break; - case GC_EVENT_PART: - ied.iType = IEED_GC_EVENT_PART; - break; - case GC_EVENT_QUIT: - ied.iType = IEED_GC_EVENT_QUIT; - break; - case GC_EVENT_NICK: - ied.iType = IEED_GC_EVENT_NICK; - break; - case GC_EVENT_KICK: - ied.iType = IEED_GC_EVENT_KICK; - break; - case GC_EVENT_NOTICE: - ied.iType = IEED_GC_EVENT_NOTICE; - break; - case GC_EVENT_TOPIC: - ied.iType = IEED_GC_EVENT_TOPIC; - break; - case GC_EVENT_INFORMATION: - ied.iType = IEED_GC_EVENT_INFORMATION; - break; - case GC_EVENT_ADDSTATUS: - ied.iType = IEED_GC_EVENT_ADDSTATUS; - break; - case GC_EVENT_REMOVESTATUS: - ied.iType = IEED_GC_EVENT_REMOVESTATUS; - break; - } - ied.dwData |= g_Settings->bShowTime ? IEEDD_GC_SHOW_TIME : 0; - ied.dwData |= IEEDD_GC_SHOW_ICON; - ied.dwFlags = IEEDF_UNICODE_TEXT | IEEDF_UNICODE_NICK; - CallService(MS_HPP_EG_EVENT, 0, (LPARAM) & event); - } - - pLog = pLog->prev; - } - } - - void Resize() override - { - RECT rcRichEdit; - GetWindowRect(GetDlgItem(m_pDlg.GetHwnd(), IDC_SRMM_LOG), &rcRichEdit); - - POINT pt = { rcRichEdit.left, rcRichEdit.top }; - ScreenToClient(GetParent(m_hwnd), &pt); - - IEVIEWWINDOW ieWindow = { sizeof(ieWindow) }; - ieWindow.iType = IEW_SETPOS; - ieWindow.parent = m_hwnd; - ieWindow.hwnd = m_hwnd; - ieWindow.x = pt.x; - ieWindow.y = pt.y; - ieWindow.cx = rcRichEdit.right - rcRichEdit.left; - ieWindow.cy = rcRichEdit.bottom - rcRichEdit.top; - if (ieWindow.cx != 0 && ieWindow.cy != 0) - CallService(MS_HPP_EG_WINDOW, 0, (LPARAM)&ieWindow); - } - - void ScrollToBottom() override - { - IEVIEWWINDOW iew = { sizeof(iew) }; - iew.iType = IEW_SCROLLBOTTOM; - iew.hwnd = m_hwnd; - CallService(MS_HPP_EG_WINDOW, 0, (LPARAM)&iew); - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// - -static HANDLE hLogger; - -static CSrmmLogWindow *logBuilder(CMsgDialog &pDlg) -{ - return new CHppLogWindow(pDlg); -} - -MIR_APP_DLL(void) RegisterHppLogger() -{ - hLogger = RegisterSrmmLog(&g_plugin, "hpp", L"History++", &logBuilder); -} - -MIR_APP_DLL(void) UnregisterHppLogger() -{ - UnregisterSrmmLog(hLogger); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// CHppLogWindow class
+
+#include "stdafx.h"
+#include "chat.h"
+
+#define EVENTTYPE_STATUSCHANGE 25368
+#define EVENTTYPE_ERRMSG 25366
+
+class CHppLogWindow : public CSrmmLogWindow
+{
+ HWND m_hwnd = nullptr;
+
+public:
+ CHppLogWindow(CMsgDialog &pDlg) :
+ CSrmmLogWindow(pDlg)
+ {
+ }
+
+ void Attach() override
+ {
+ IEVIEWWINDOW ieWindow = {};
+ ieWindow.iType = IEW_CREATE;
+ ieWindow.dwMode = IEWM_TABSRMM;
+ ieWindow.parent = m_pDlg.GetHwnd();
+ ieWindow.cx = 10;
+ ieWindow.cy = 10;
+ CallService(MS_HPP_EG_WINDOW, 0, (LPARAM)&ieWindow);
+ m_hwnd = ieWindow.hwnd;
+ }
+
+ void Detach() override
+ {
+ IEVIEWWINDOW ieWindow = {};
+ ieWindow.iType = IEW_DESTROY;
+ ieWindow.hwnd = m_hwnd;
+ CallService(MS_HPP_EG_WINDOW, 0, (LPARAM)&ieWindow);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ bool AtBottom() override
+ {
+ return false;
+ }
+
+ void Clear() override
+ {
+ IEVIEWEVENT event = {};
+ event.iType = IEE_CLEAR_LOG;
+ event.hwnd = m_hwnd;
+ CallService(MS_HPP_EG_EVENT, 0, (LPARAM)&event);
+ }
+
+ HWND GetHwnd() override
+ {
+ return m_hwnd;
+ }
+
+ int GetType() override
+ {
+ return 2;
+ }
+
+ wchar_t* GetSelection() override
+ {
+ IEVIEWEVENT event = {};
+ event.hwnd = m_hwnd;
+ event.iType = IEE_GET_SELECTION;
+ event.hContact = m_pDlg.m_hContact;
+ event.dwFlags = 0;
+ return (wchar_t *)CallService(MS_HPP_EG_EVENT, 0, (LPARAM)&event);
+ }
+
+ void LogEvents(MEVENT hDbEventFirst, int count, bool bAppend) override
+ {
+ if (!bAppend)
+ Clear();
+
+ IEVIEWEVENT event = {};
+ event.hwnd = m_hwnd;
+ event.iType = IEE_LOG_DB_EVENTS;
+ event.hDbEventFirst = hDbEventFirst;
+ event.hContact = m_pDlg.m_hContact;
+ event.count = count;
+ CallService(MS_HPP_EG_EVENT, 0, (LPARAM)&event);
+ }
+
+ void CHppLogWindow::LogEvents(LOGINFO *pLog, bool)
+ {
+ IEVIEWEVENTDATA ied = {};
+ ied.dwFlags = IEEDF_UNICODE_NICK | IEEDF_UNICODE_TEXT;
+
+ IEVIEWEVENT event = {};
+ event.hwnd = m_hwnd;
+ event.hContact = m_pDlg.m_hContact;
+ event.codepage = CP_ACP;
+ event.iType = IEE_LOG_MEM_EVENTS;
+ event.eventData = &ied;
+ event.count = 1;
+
+ while (pLog) {
+ if (pLog->ptszText) {
+ ied.szNick.w = pLog->ptszNick;
+ ied.szText.w = pLog->ptszText;
+ ied.time = pLog->time;
+ ied.bIsMe = pLog->bIsMe;
+
+ switch (pLog->iType) {
+ case GC_EVENT_MESSAGE:
+ ied.iType = IEED_GC_EVENT_MESSAGE;
+ ied.dwData = IEEDD_GC_SHOW_NICK;
+ break;
+ case GC_EVENT_ACTION:
+ ied.iType = IEED_GC_EVENT_ACTION;
+ break;
+ case GC_EVENT_JOIN:
+ ied.iType = IEED_GC_EVENT_JOIN;
+ break;
+ case GC_EVENT_PART:
+ ied.iType = IEED_GC_EVENT_PART;
+ break;
+ case GC_EVENT_QUIT:
+ ied.iType = IEED_GC_EVENT_QUIT;
+ break;
+ case GC_EVENT_NICK:
+ ied.iType = IEED_GC_EVENT_NICK;
+ break;
+ case GC_EVENT_KICK:
+ ied.iType = IEED_GC_EVENT_KICK;
+ break;
+ case GC_EVENT_NOTICE:
+ ied.iType = IEED_GC_EVENT_NOTICE;
+ break;
+ case GC_EVENT_TOPIC:
+ ied.iType = IEED_GC_EVENT_TOPIC;
+ break;
+ case GC_EVENT_INFORMATION:
+ ied.iType = IEED_GC_EVENT_INFORMATION;
+ break;
+ case GC_EVENT_ADDSTATUS:
+ ied.iType = IEED_GC_EVENT_ADDSTATUS;
+ break;
+ case GC_EVENT_REMOVESTATUS:
+ ied.iType = IEED_GC_EVENT_REMOVESTATUS;
+ break;
+ }
+ ied.dwData |= g_Settings->bShowTime ? IEEDD_GC_SHOW_TIME : 0;
+ ied.dwData |= IEEDD_GC_SHOW_ICON;
+ ied.dwFlags = IEEDF_UNICODE_TEXT | IEEDF_UNICODE_NICK;
+ CallService(MS_HPP_EG_EVENT, 0, (LPARAM) & event);
+ }
+
+ pLog = pLog->prev;
+ }
+ }
+
+ void Resize() override
+ {
+ RECT rcRichEdit;
+ GetWindowRect(GetDlgItem(m_pDlg.GetHwnd(), IDC_SRMM_LOG), &rcRichEdit);
+
+ POINT pt = { rcRichEdit.left, rcRichEdit.top };
+ ScreenToClient(GetParent(m_hwnd), &pt);
+
+ IEVIEWWINDOW ieWindow = { sizeof(ieWindow) };
+ ieWindow.iType = IEW_SETPOS;
+ ieWindow.parent = m_hwnd;
+ ieWindow.hwnd = m_hwnd;
+ ieWindow.x = pt.x;
+ ieWindow.y = pt.y;
+ ieWindow.cx = rcRichEdit.right - rcRichEdit.left;
+ ieWindow.cy = rcRichEdit.bottom - rcRichEdit.top;
+ if (ieWindow.cx != 0 && ieWindow.cy != 0)
+ CallService(MS_HPP_EG_WINDOW, 0, (LPARAM)&ieWindow);
+ }
+
+ void ScrollToBottom() override
+ {
+ IEVIEWWINDOW iew = { sizeof(iew) };
+ iew.iType = IEW_SCROLLBOTTOM;
+ iew.hwnd = m_hwnd;
+ CallService(MS_HPP_EG_WINDOW, 0, (LPARAM)&iew);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static HANDLE hLogger;
+
+static CSrmmLogWindow *logBuilder(CMsgDialog &pDlg)
+{
+ return new CHppLogWindow(pDlg);
+}
+
+MIR_APP_DLL(void) RegisterHppLogger()
+{
+ hLogger = RegisterSrmmLog(&g_plugin, "hpp", L"History++", &logBuilder);
+}
+
+MIR_APP_DLL(void) UnregisterHppLogger()
+{
+ UnregisterSrmmLog(hLogger);
+}
diff --git a/src/mir_app/src/srmm_log_rtf.cpp b/src/mir_app/src/srmm_log_rtf.cpp index 01d8b7c5c8..c6bf86af64 100644 --- a/src/mir_app/src/srmm_log_rtf.cpp +++ b/src/mir_app/src/srmm_log_rtf.cpp @@ -1,408 +1,408 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -///////////////////////////////////////////////////////////////////////////////////////// -// SRMM log container - -#include "stdafx.h" -#include "chat.h" - -#define EVENTTYPE_STATUSCHANGE 25368 -#define EVENTTYPE_ERRMSG 25366 - -CRtfLogWindow::CRtfLogWindow(CMsgDialog &pDlg) : - CSrmmLogWindow(pDlg), - m_rtf(*(CCtrlRichEdit*)pDlg.FindControl(IDC_SRMM_LOG)) -{ -} - -CRtfLogWindow::~CRtfLogWindow() -{ -} - -///////////////////////////////////////////////////////////////////////////////////////// - -EXTERN_C MIR_APP_DLL(LRESULT) CALLBACK stubLogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - CRtfLogWindow *pLog = (CRtfLogWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA); - if (pLog != nullptr) - return pLog->WndProc(msg, wParam, lParam); - - return mir_callNextSubclass(hwnd, stubLogProc, msg, wParam, lParam); -} - -void CRtfLogWindow::Attach() -{ - SetWindowLongPtr(m_rtf.GetHwnd(), GWLP_USERDATA, LPARAM(this)); - m_rtf.SetReadOnly(true); - - mir_subclassWindow(m_rtf.GetHwnd(), stubLogProc); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CRtfLogWindow::Detach() -{ - mir_unsubclassWindow(m_rtf.GetHwnd(), stubLogProc); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -bool CRtfLogWindow::AtBottom() -{ - if (!(GetWindowLongPtr(m_rtf.GetHwnd(), GWL_STYLE) & WS_VSCROLL)) - return false; - - SCROLLINFO si = {}; - si.cbSize = sizeof(si); - si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS; - GetScrollInfo(m_rtf.GetHwnd(), SB_VERT, &si); - return (si.nPos + (int)si.nPage + 5) >= si.nMax; -} - -void CRtfLogWindow::Clear() -{ - m_rtf.SetText(L""); -} - -HWND CRtfLogWindow::GetHwnd() -{ - return m_rtf.GetHwnd(); -} - -int CRtfLogWindow::GetType() -{ - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static DWORD CALLBACK StreamOutCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) -{ - CMStringW *str = (CMStringW *)dwCookie; - str->Append((wchar_t*)pbBuff, cb / 2); - *pcb = cb; - return 0; -} - -wchar_t* CRtfLogWindow::GetSelection() -{ - CHARRANGE sel; - SendMessage(m_rtf.GetHwnd(), EM_EXGETSEL, 0, (LPARAM)&sel); - if (sel.cpMin == sel.cpMax) - return nullptr; - - CMStringW result; - - EDITSTREAM stream; - memset(&stream, 0, sizeof(stream)); - stream.pfnCallback = StreamOutCallback; - stream.dwCookie = (DWORD_PTR)&result; - SendMessage(m_rtf.GetHwnd(), EM_STREAMOUT, SF_TEXT | SF_UNICODE | SFF_SELECTION, (LPARAM)&stream); - return result.Detach(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -INT_PTR CRtfLogWindow::Notify(WPARAM, LPARAM lParam) -{ - LPNMHDR hdr = (LPNMHDR)lParam; - if (hdr->code != EN_LINK) - return FALSE; - - ENLINK *pLink = (ENLINK *)lParam; - switch (pLink->msg) { - case WM_SETCURSOR: - SetCursor(g_hCurHyperlinkHand); - SetWindowLongPtr(m_pDlg.m_hwnd, DWLP_MSGRESULT, TRUE); - return TRUE; - - case WM_RBUTTONDOWN: - case WM_LBUTTONUP: - case WM_LBUTTONDBLCLK: - CHARRANGE sel; - m_rtf.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel); - if (sel.cpMin != sel.cpMax) - break; - - CMStringW wszText(' ', pLink->chrg.cpMax - pLink->chrg.cpMin + 1); - - TEXTRANGE tr; - tr.chrg = pLink->chrg; - tr.lpstrText = wszText.GetBuffer(); - m_rtf.SendMsg(EM_GETTEXTRANGE, 0, (LPARAM)&tr); - if (wcschr(tr.lpstrText, '@') != nullptr && wcschr(tr.lpstrText, ':') == nullptr && wcschr(tr.lpstrText, '/') == nullptr) - wszText.Insert(0, L"mailto:"); - - if (pLink->msg == WM_RBUTTONDOWN) { - HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_CONTEXT)); - HMENU hSubMenu = GetSubMenu(hMenu, 6); - TranslateMenu(hSubMenu); - - POINT pt = { GET_X_LPARAM(pLink->lParam), GET_Y_LPARAM(pLink->lParam) }; - ClientToScreen(((NMHDR *)lParam)->hwndFrom, &pt); - - switch (TrackPopupMenu(hSubMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_pDlg.m_hwnd, nullptr)) { - case IDM_OPENLINK: - Utils_OpenUrlW(wszText); - break; - - case IDM_COPYLINK: - Utils_ClipboardCopy(wszText); - break; - } - - DestroyMenu(hMenu); - SetWindowLongPtr(m_pDlg.m_hwnd, DWLP_MSGRESULT, TRUE); - return TRUE; - } - - Utils_OpenUrlW(wszText); - SetFocus(m_pDlg.m_message.GetHwnd()); - } - - return FALSE; -} - -void CRtfLogWindow::Resize() -{ - bool bottomScroll = !m_pDlg.isChat(); - if (AtBottom()) - bottomScroll = true; - - // ::MoveWindow(m_rtf.GetHwnd(), x, y, cx, cy, true); - - if (bottomScroll) - ScrollToBottom(); -} - -void CRtfLogWindow::ScrollToBottom() -{ - if (!(GetWindowLongPtr(m_rtf.GetHwnd(), GWL_STYLE) & WS_VSCROLL)) - return; - - SCROLLINFO si = {}; - si.cbSize = sizeof(si); - si.fMask = SIF_PAGE | SIF_RANGE; - GetScrollInfo(m_rtf.GetHwnd(), SB_VERT, &si); - - si.fMask = SIF_POS; - si.nPos = si.nMax - si.nPage; - SetScrollInfo(m_rtf.GetHwnd(), SB_VERT, &si, TRUE); - m_rtf.SendMsg(WM_VSCROLL, MAKEWPARAM(SB_BOTTOM, 0), 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static wchar_t szTrimString[] = L":;,.!?\'\"><()[]- \r\n"; - -INT_PTR CRtfLogWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam) -{ - CHARRANGE sel; - - switch (msg) { - case WM_ACTIVATE: - if (LOWORD(wParam) == WA_INACTIVE) { - m_rtf.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel); - if (sel.cpMin != sel.cpMax) { - sel.cpMin = sel.cpMax; - m_rtf.SendMsg(EM_EXSETSEL, 0, (LPARAM)&sel); - } - } - break; - - case WM_SETCURSOR: - if (m_pDlg.m_bInMenu) { - SetCursor(LoadCursor(nullptr, IDC_ARROW)); - return TRUE; - } - break; - - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - if (!(GetKeyState(VK_RMENU) & 0x8000)) { - MSG message = { m_pDlg.m_hwnd, msg, wParam, lParam }; - LRESULT iButtonFrom = Hotkey_Check(&message, BB_HK_SECTION); - if (iButtonFrom) { - Srmm_ProcessToolbarHotkey(m_pDlg.m_hContact, iButtonFrom, m_pDlg.m_hwnd); - return TRUE; - } - } - break; - - case WM_CHAR: - if (wParam >= ' ') { - SetFocus(m_pDlg.m_message.GetHwnd()); - m_pDlg.m_message.SendMsg(WM_CHAR, wParam, lParam); - } - else if (wParam == '\t') - SetFocus(m_pDlg.m_message.GetHwnd()); - break; - - case WM_CONTEXTMENU: - POINT pt, ptl; - m_rtf.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel); - if (lParam == 0xFFFFFFFF) { - m_rtf.SendMsg(EM_POSFROMCHAR, (WPARAM)&pt, (LPARAM)sel.cpMax); - ClientToScreen(m_rtf.GetHwnd(), &pt); - } - else { - pt.x = GET_X_LPARAM(lParam); - pt.y = GET_Y_LPARAM(lParam); - } - ptl = pt; - ScreenToClient(m_rtf.GetHwnd(), &ptl); - { - wchar_t *pszWord = (wchar_t *)_alloca(8192); - pszWord[0] = '\0'; - - // get a word under cursor - if (sel.cpMin == sel.cpMax) { - int iCharIndex = m_rtf.SendMsg(EM_CHARFROMPOS, 0, (LPARAM)&ptl); - if (iCharIndex < 0) - break; - - sel.cpMin = m_rtf.SendMsg(EM_FINDWORDBREAK, WB_LEFT, iCharIndex); - sel.cpMax = m_rtf.SendMsg(EM_FINDWORDBREAK, WB_RIGHT, iCharIndex); - } - - if (sel.cpMax > sel.cpMin) { - TEXTRANGE tr = { 0 }; - tr.chrg = sel; - tr.lpstrText = pszWord; - int iRes = m_rtf.SendMsg(EM_GETTEXTRANGE, 0, (LPARAM)&tr); - if (iRes > 0) { - wchar_t *p = wcschr(pszWord, '\r'); - if (p) - *p = 0; - - size_t iLen = mir_wstrlen(pszWord) - 1; - while (wcschr(szTrimString, pszWord[iLen])) { - pszWord[iLen] = '\0'; - iLen--; - } - } - } - - CHARRANGE all = { 0, -1 }; - HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_LOGMENU)); - HMENU hSubMenu = GetSubMenu(hMenu, 0); - TranslateMenu(hSubMenu); - m_pDlg.m_bInMenu = true; - - int flags = MF_BYPOSITION | (GetRichTextLength(m_rtf.GetHwnd()) == 0 ? MF_GRAYED : MF_ENABLED); - EnableMenuItem(hSubMenu, 0, flags); - EnableMenuItem(hSubMenu, 2, flags); - - if (pszWord && pszWord[0]) { - CMStringW wszText(FORMAT, TranslateT("Look up '%s':"), pszWord); - if (wszText.GetLength() > 30) { - wszText.Truncate(30); - wszText.AppendChar('\''); - } - ModifyMenu(hSubMenu, 4, MF_STRING | MF_BYPOSITION, 4, wszText); - } - else ModifyMenu(hSubMenu, 4, MF_STRING | MF_GRAYED | MF_BYPOSITION, 4, TranslateT("No word to look up")); - - UINT uID = Chat_CreateMenu(m_rtf.GetHwnd(), hSubMenu, pt, m_pDlg.m_si, nullptr); - m_pDlg.m_bInMenu = false; - DestroyMenu(hMenu); - - switch (uID) { - case 0: - PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0); - break; - - case IDM_COPYALL: - m_rtf.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel); - m_rtf.SendMsg(EM_EXSETSEL, 0, (LPARAM)&all); - m_rtf.SendMsg(WM_COPY, 0, 0); - m_rtf.SendMsg(EM_EXSETSEL, 0, (LPARAM)&sel); - PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0); - break; - - case IDM_CLEAR: - m_rtf.SetText(L""); - if (auto *si = m_pDlg.m_si) { - g_chatApi.LM_RemoveAll(&si->pLog, &si->pLogEnd); - si->iEventCount = 0; - si->LastTime = 0; - } - PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0); - break; - - case IDM_SEARCH_GOOGLE: - case IDM_SEARCH_BING: - case IDM_SEARCH_YANDEX: - case IDM_SEARCH_YAHOO: - case IDM_SEARCH_WIKIPEDIA: - case IDM_SEARCH_FOODNETWORK: - case IDM_SEARCH_GOOGLE_MAPS: - case IDM_SEARCH_GOOGLE_TRANSLATE: - { - CMStringW szURL; - switch (uID) { - case IDM_SEARCH_WIKIPEDIA: - szURL.Format(L"http://en.wikipedia.org/wiki/%s", pszWord); - break; - case IDM_SEARCH_YAHOO: - szURL.Format(L"http://search.yahoo.com/search?p=%s&ei=UTF-8", pszWord); - break; - case IDM_SEARCH_FOODNETWORK: - szURL.Format(L"http://search.foodnetwork.com/search/delegate.do?fnSearchString=%s", pszWord); - break; - case IDM_SEARCH_BING: - szURL.Format(L"http://www.bing.com/search?q=%s&form=OSDSRC", pszWord); - break; - case IDM_SEARCH_GOOGLE_MAPS: - szURL.Format(L"http://maps.google.com/maps?q=%s&ie=utf-8&oe=utf-8", pszWord); - break; - case IDM_SEARCH_GOOGLE_TRANSLATE: - szURL.Format(L"http://translate.google.com/?q=%s&ie=utf-8&oe=utf-8", pszWord); - break; - case IDM_SEARCH_YANDEX: - szURL.Format(L"http://yandex.ru/yandsearch?text=%s", pszWord); - break; - case IDM_SEARCH_GOOGLE: - szURL.Format(L"http://www.google.com/search?q=%s&ie=utf-8&oe=utf-8", pszWord); - break; - } - Utils_OpenUrlW(szURL); - } - PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0); - break; - - default: - PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0); - Chat_DoEventHook(m_pDlg.m_si, GC_USER_LOGMENU, nullptr, nullptr, uID); - break; - } - } - return 0; - } - - LRESULT res = mir_callNextSubclass(m_rtf.GetHwnd(), stubLogProc, msg, wParam, lParam); - if (msg == WM_GETDLGCODE) - return res & ~DLGC_HASSETSEL; - return res; -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// SRMM log container
+
+#include "stdafx.h"
+#include "chat.h"
+
+#define EVENTTYPE_STATUSCHANGE 25368
+#define EVENTTYPE_ERRMSG 25366
+
+CRtfLogWindow::CRtfLogWindow(CMsgDialog &pDlg) :
+ CSrmmLogWindow(pDlg),
+ m_rtf(*(CCtrlRichEdit*)pDlg.FindControl(IDC_SRMM_LOG))
+{
+}
+
+CRtfLogWindow::~CRtfLogWindow()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+EXTERN_C MIR_APP_DLL(LRESULT) CALLBACK stubLogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CRtfLogWindow *pLog = (CRtfLogWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ if (pLog != nullptr)
+ return pLog->WndProc(msg, wParam, lParam);
+
+ return mir_callNextSubclass(hwnd, stubLogProc, msg, wParam, lParam);
+}
+
+void CRtfLogWindow::Attach()
+{
+ SetWindowLongPtr(m_rtf.GetHwnd(), GWLP_USERDATA, LPARAM(this));
+ m_rtf.SetReadOnly(true);
+
+ mir_subclassWindow(m_rtf.GetHwnd(), stubLogProc);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CRtfLogWindow::Detach()
+{
+ mir_unsubclassWindow(m_rtf.GetHwnd(), stubLogProc);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool CRtfLogWindow::AtBottom()
+{
+ if (!(GetWindowLongPtr(m_rtf.GetHwnd(), GWL_STYLE) & WS_VSCROLL))
+ return false;
+
+ SCROLLINFO si = {};
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
+ GetScrollInfo(m_rtf.GetHwnd(), SB_VERT, &si);
+ return (si.nPos + (int)si.nPage + 5) >= si.nMax;
+}
+
+void CRtfLogWindow::Clear()
+{
+ m_rtf.SetText(L"");
+}
+
+HWND CRtfLogWindow::GetHwnd()
+{
+ return m_rtf.GetHwnd();
+}
+
+int CRtfLogWindow::GetType()
+{
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static DWORD CALLBACK StreamOutCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb)
+{
+ CMStringW *str = (CMStringW *)dwCookie;
+ str->Append((wchar_t*)pbBuff, cb / 2);
+ *pcb = cb;
+ return 0;
+}
+
+wchar_t* CRtfLogWindow::GetSelection()
+{
+ CHARRANGE sel;
+ SendMessage(m_rtf.GetHwnd(), EM_EXGETSEL, 0, (LPARAM)&sel);
+ if (sel.cpMin == sel.cpMax)
+ return nullptr;
+
+ CMStringW result;
+
+ EDITSTREAM stream;
+ memset(&stream, 0, sizeof(stream));
+ stream.pfnCallback = StreamOutCallback;
+ stream.dwCookie = (DWORD_PTR)&result;
+ SendMessage(m_rtf.GetHwnd(), EM_STREAMOUT, SF_TEXT | SF_UNICODE | SFF_SELECTION, (LPARAM)&stream);
+ return result.Detach();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR CRtfLogWindow::Notify(WPARAM, LPARAM lParam)
+{
+ LPNMHDR hdr = (LPNMHDR)lParam;
+ if (hdr->code != EN_LINK)
+ return FALSE;
+
+ ENLINK *pLink = (ENLINK *)lParam;
+ switch (pLink->msg) {
+ case WM_SETCURSOR:
+ SetCursor(g_hCurHyperlinkHand);
+ SetWindowLongPtr(m_pDlg.m_hwnd, DWLP_MSGRESULT, TRUE);
+ return TRUE;
+
+ case WM_RBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_LBUTTONDBLCLK:
+ CHARRANGE sel;
+ m_rtf.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel);
+ if (sel.cpMin != sel.cpMax)
+ break;
+
+ CMStringW wszText(' ', pLink->chrg.cpMax - pLink->chrg.cpMin + 1);
+
+ TEXTRANGE tr;
+ tr.chrg = pLink->chrg;
+ tr.lpstrText = wszText.GetBuffer();
+ m_rtf.SendMsg(EM_GETTEXTRANGE, 0, (LPARAM)&tr);
+ if (wcschr(tr.lpstrText, '@') != nullptr && wcschr(tr.lpstrText, ':') == nullptr && wcschr(tr.lpstrText, '/') == nullptr)
+ wszText.Insert(0, L"mailto:");
+
+ if (pLink->msg == WM_RBUTTONDOWN) {
+ HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_CONTEXT));
+ HMENU hSubMenu = GetSubMenu(hMenu, 6);
+ TranslateMenu(hSubMenu);
+
+ POINT pt = { GET_X_LPARAM(pLink->lParam), GET_Y_LPARAM(pLink->lParam) };
+ ClientToScreen(((NMHDR *)lParam)->hwndFrom, &pt);
+
+ switch (TrackPopupMenu(hSubMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_pDlg.m_hwnd, nullptr)) {
+ case IDM_OPENLINK:
+ Utils_OpenUrlW(wszText);
+ break;
+
+ case IDM_COPYLINK:
+ Utils_ClipboardCopy(wszText);
+ break;
+ }
+
+ DestroyMenu(hMenu);
+ SetWindowLongPtr(m_pDlg.m_hwnd, DWLP_MSGRESULT, TRUE);
+ return TRUE;
+ }
+
+ Utils_OpenUrlW(wszText);
+ SetFocus(m_pDlg.m_message.GetHwnd());
+ }
+
+ return FALSE;
+}
+
+void CRtfLogWindow::Resize()
+{
+ bool bottomScroll = !m_pDlg.isChat();
+ if (AtBottom())
+ bottomScroll = true;
+
+ // ::MoveWindow(m_rtf.GetHwnd(), x, y, cx, cy, true);
+
+ if (bottomScroll)
+ ScrollToBottom();
+}
+
+void CRtfLogWindow::ScrollToBottom()
+{
+ if (!(GetWindowLongPtr(m_rtf.GetHwnd(), GWL_STYLE) & WS_VSCROLL))
+ return;
+
+ SCROLLINFO si = {};
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_PAGE | SIF_RANGE;
+ GetScrollInfo(m_rtf.GetHwnd(), SB_VERT, &si);
+
+ si.fMask = SIF_POS;
+ si.nPos = si.nMax - si.nPage;
+ SetScrollInfo(m_rtf.GetHwnd(), SB_VERT, &si, TRUE);
+ m_rtf.SendMsg(WM_VSCROLL, MAKEWPARAM(SB_BOTTOM, 0), 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static wchar_t szTrimString[] = L":;,.!?\'\"><()[]- \r\n";
+
+INT_PTR CRtfLogWindow::WndProc(UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CHARRANGE sel;
+
+ switch (msg) {
+ case WM_ACTIVATE:
+ if (LOWORD(wParam) == WA_INACTIVE) {
+ m_rtf.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel);
+ if (sel.cpMin != sel.cpMax) {
+ sel.cpMin = sel.cpMax;
+ m_rtf.SendMsg(EM_EXSETSEL, 0, (LPARAM)&sel);
+ }
+ }
+ break;
+
+ case WM_SETCURSOR:
+ if (m_pDlg.m_bInMenu) {
+ SetCursor(LoadCursor(nullptr, IDC_ARROW));
+ return TRUE;
+ }
+ break;
+
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ if (!(GetKeyState(VK_RMENU) & 0x8000)) {
+ MSG message = { m_pDlg.m_hwnd, msg, wParam, lParam };
+ LRESULT iButtonFrom = Hotkey_Check(&message, BB_HK_SECTION);
+ if (iButtonFrom) {
+ Srmm_ProcessToolbarHotkey(m_pDlg.m_hContact, iButtonFrom, m_pDlg.m_hwnd);
+ return TRUE;
+ }
+ }
+ break;
+
+ case WM_CHAR:
+ if (wParam >= ' ') {
+ SetFocus(m_pDlg.m_message.GetHwnd());
+ m_pDlg.m_message.SendMsg(WM_CHAR, wParam, lParam);
+ }
+ else if (wParam == '\t')
+ SetFocus(m_pDlg.m_message.GetHwnd());
+ break;
+
+ case WM_CONTEXTMENU:
+ POINT pt, ptl;
+ m_rtf.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel);
+ if (lParam == 0xFFFFFFFF) {
+ m_rtf.SendMsg(EM_POSFROMCHAR, (WPARAM)&pt, (LPARAM)sel.cpMax);
+ ClientToScreen(m_rtf.GetHwnd(), &pt);
+ }
+ else {
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+ }
+ ptl = pt;
+ ScreenToClient(m_rtf.GetHwnd(), &ptl);
+ {
+ wchar_t *pszWord = (wchar_t *)_alloca(8192);
+ pszWord[0] = '\0';
+
+ // get a word under cursor
+ if (sel.cpMin == sel.cpMax) {
+ int iCharIndex = m_rtf.SendMsg(EM_CHARFROMPOS, 0, (LPARAM)&ptl);
+ if (iCharIndex < 0)
+ break;
+
+ sel.cpMin = m_rtf.SendMsg(EM_FINDWORDBREAK, WB_LEFT, iCharIndex);
+ sel.cpMax = m_rtf.SendMsg(EM_FINDWORDBREAK, WB_RIGHT, iCharIndex);
+ }
+
+ if (sel.cpMax > sel.cpMin) {
+ TEXTRANGE tr = { 0 };
+ tr.chrg = sel;
+ tr.lpstrText = pszWord;
+ int iRes = m_rtf.SendMsg(EM_GETTEXTRANGE, 0, (LPARAM)&tr);
+ if (iRes > 0) {
+ wchar_t *p = wcschr(pszWord, '\r');
+ if (p)
+ *p = 0;
+
+ size_t iLen = mir_wstrlen(pszWord) - 1;
+ while (wcschr(szTrimString, pszWord[iLen])) {
+ pszWord[iLen] = '\0';
+ iLen--;
+ }
+ }
+ }
+
+ CHARRANGE all = { 0, -1 };
+ HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_LOGMENU));
+ HMENU hSubMenu = GetSubMenu(hMenu, 0);
+ TranslateMenu(hSubMenu);
+ m_pDlg.m_bInMenu = true;
+
+ int flags = MF_BYPOSITION | (GetRichTextLength(m_rtf.GetHwnd()) == 0 ? MF_GRAYED : MF_ENABLED);
+ EnableMenuItem(hSubMenu, 0, flags);
+ EnableMenuItem(hSubMenu, 2, flags);
+
+ if (pszWord && pszWord[0]) {
+ CMStringW wszText(FORMAT, TranslateT("Look up '%s':"), pszWord);
+ if (wszText.GetLength() > 30) {
+ wszText.Truncate(30);
+ wszText.AppendChar('\'');
+ }
+ ModifyMenu(hSubMenu, 4, MF_STRING | MF_BYPOSITION, 4, wszText);
+ }
+ else ModifyMenu(hSubMenu, 4, MF_STRING | MF_GRAYED | MF_BYPOSITION, 4, TranslateT("No word to look up"));
+
+ UINT uID = Chat_CreateMenu(m_rtf.GetHwnd(), hSubMenu, pt, m_pDlg.m_si, nullptr);
+ m_pDlg.m_bInMenu = false;
+ DestroyMenu(hMenu);
+
+ switch (uID) {
+ case 0:
+ PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0);
+ break;
+
+ case IDM_COPYALL:
+ m_rtf.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel);
+ m_rtf.SendMsg(EM_EXSETSEL, 0, (LPARAM)&all);
+ m_rtf.SendMsg(WM_COPY, 0, 0);
+ m_rtf.SendMsg(EM_EXSETSEL, 0, (LPARAM)&sel);
+ PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0);
+ break;
+
+ case IDM_CLEAR:
+ m_rtf.SetText(L"");
+ if (auto *si = m_pDlg.m_si) {
+ g_chatApi.LM_RemoveAll(&si->pLog, &si->pLogEnd);
+ si->iEventCount = 0;
+ si->LastTime = 0;
+ }
+ PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0);
+ break;
+
+ case IDM_SEARCH_GOOGLE:
+ case IDM_SEARCH_BING:
+ case IDM_SEARCH_YANDEX:
+ case IDM_SEARCH_YAHOO:
+ case IDM_SEARCH_WIKIPEDIA:
+ case IDM_SEARCH_FOODNETWORK:
+ case IDM_SEARCH_GOOGLE_MAPS:
+ case IDM_SEARCH_GOOGLE_TRANSLATE:
+ {
+ CMStringW szURL;
+ switch (uID) {
+ case IDM_SEARCH_WIKIPEDIA:
+ szURL.Format(L"http://en.wikipedia.org/wiki/%s", pszWord);
+ break;
+ case IDM_SEARCH_YAHOO:
+ szURL.Format(L"http://search.yahoo.com/search?p=%s&ei=UTF-8", pszWord);
+ break;
+ case IDM_SEARCH_FOODNETWORK:
+ szURL.Format(L"http://search.foodnetwork.com/search/delegate.do?fnSearchString=%s", pszWord);
+ break;
+ case IDM_SEARCH_BING:
+ szURL.Format(L"http://www.bing.com/search?q=%s&form=OSDSRC", pszWord);
+ break;
+ case IDM_SEARCH_GOOGLE_MAPS:
+ szURL.Format(L"http://maps.google.com/maps?q=%s&ie=utf-8&oe=utf-8", pszWord);
+ break;
+ case IDM_SEARCH_GOOGLE_TRANSLATE:
+ szURL.Format(L"http://translate.google.com/?q=%s&ie=utf-8&oe=utf-8", pszWord);
+ break;
+ case IDM_SEARCH_YANDEX:
+ szURL.Format(L"http://yandex.ru/yandsearch?text=%s", pszWord);
+ break;
+ case IDM_SEARCH_GOOGLE:
+ szURL.Format(L"http://www.google.com/search?q=%s&ie=utf-8&oe=utf-8", pszWord);
+ break;
+ }
+ Utils_OpenUrlW(szURL);
+ }
+ PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0);
+ break;
+
+ default:
+ PostMessage(m_pDlg.m_hwnd, WM_MOUSEACTIVATE, 0, 0);
+ Chat_DoEventHook(m_pDlg.m_si, GC_USER_LOGMENU, nullptr, nullptr, uID);
+ break;
+ }
+ }
+ return 0;
+ }
+
+ LRESULT res = mir_callNextSubclass(m_rtf.GetHwnd(), stubLogProc, msg, wParam, lParam);
+ if (msg == WM_GETDLGCODE)
+ return res & ~DLGC_HASSETSEL;
+ return res;
+}
diff --git a/src/mir_app/src/srmm_statusicon.cpp b/src/mir_app/src/srmm_statusicon.cpp index 1324a30eb7..12a613610e 100644 --- a/src/mir_app/src/srmm_statusicon.cpp +++ b/src/mir_app/src/srmm_statusicon.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team,
+Copyright (C) 2012-23 Miranda NG team,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/srmm_toolbar.cpp b/src/mir_app/src/srmm_toolbar.cpp index f833ea16f9..fb593ea1a4 100644 --- a/src/mir_app/src/srmm_toolbar.cpp +++ b/src/mir_app/src/srmm_toolbar.cpp @@ -1,886 +1,886 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "chat.h" -#include "skin.h" - -#define BB_MODULE_NAME "SRMM_Toolbar" - -#define DPISCALEY_S(argY) ((int)((double)(argY) * g_DPIscaleY)) -#define DPISCALEX_S(argX) ((int)((double)(argX) * g_DPIscaleX)) - -static double g_DPIscaleX, g_DPIscaleY; -static class CSrmmToolbarOptions *g_pDialog = nullptr; - -static CMOption<uint8_t> g_iButtonGap(BB_MODULE_NAME, "ButtonsBarGap", 1); - -static int SortButtons(const CustomButtonData *p1, const CustomButtonData *p2) -{ - if (p1->m_bRSided != p2->m_bRSided) - return (p2->m_bRSided) ? -1 : 1; - if (p1->m_dwPosition != p2->m_dwPosition) - return p1->m_dwPosition - p2->m_dwPosition; - int res = mir_strcmp(p1->m_pszModuleName, p2->m_pszModuleName); - if (res != 0) - return res; - return p1->m_dwButtonID - p2->m_dwButtonID; -} - -static LIST<CustomButtonData> arButtonsList(1, SortButtons); - -int LastCID = MIN_CBUTTONID; -int dwSepCount = 0; - -static mir_cs csToolBar; -static HANDLE hHookToolBarLoadedEvt, hHookButtonPressedEvt; - -static int sstSortButtons(const void *p1, const void *p2) -{ - return SortButtons(*(CustomButtonData**)p1, *(CustomButtonData**)p2); -} - -static void CB_RegisterSeparators() -{ - BBButton bbd = {}; - bbd.pszModuleName = "Tabsrmm_sep"; - for (int i = 0; dwSepCount > i; i++) { - bbd.bbbFlags = BBBF_ISSEPARATOR | BBBF_ISIMBUTTON; - bbd.dwButtonID = i + 1; - bbd.dwDefPos = 410 + i; - Srmm_AddButton(&bbd, &g_plugin); - } -} - -MIR_APP_DLL(CustomButtonData*) Srmm_GetNthButton(int i) -{ - return arButtonsList[i]; -} - -MIR_APP_DLL(int) Srmm_GetButtonCount(void) -{ - return arButtonsList.getCount(); -} - -MIR_APP_DLL(int) Srmm_GetButtonGap() -{ - return g_iButtonGap; -} - -MIR_APP_DLL(int) Srmm_GetButtonState(HWND hwndDlg, BBButton *bbdi) -{ - if (hwndDlg == nullptr || bbdi == nullptr) - return 1; - - uint32_t tempCID = 0; - bbdi->bbbFlags = 0; - for (auto &cbd : arButtonsList) - if (!mir_strcmp(cbd->m_pszModuleName, bbdi->pszModuleName) && (cbd->m_dwButtonID == bbdi->dwButtonID)) { - tempCID = cbd->m_dwButtonCID; - break; - } - - if (!tempCID) - return 1; - - HWND hwndBtn = GetDlgItem(hwndDlg, tempCID); - bbdi->bbbFlags = (IsDlgButtonChecked(hwndDlg, tempCID) ? BBSF_PUSHED : BBSF_RELEASED) | (IsWindowVisible(hwndBtn) ? 0 : BBSF_HIDDEN) | (IsWindowEnabled(hwndBtn) ? 0 : BBSF_DISABLED); - return 0; -} - -MIR_APP_DLL(int) Srmm_SetButtonState(MCONTACT hContact, BBButton *bbdi) -{ - if (hContact == 0 || bbdi == nullptr) - return 1; - - uint32_t tempCID = 0; - for (auto &cbd : arButtonsList) - if (!mir_strcmp(cbd->m_pszModuleName, bbdi->pszModuleName) && (cbd->m_dwButtonID == bbdi->dwButtonID)) { - tempCID = cbd->m_dwButtonCID; - break; - } - - if (!tempCID) - return 1; - - HWND hwndDlg = WindowList_Find(g_hWindowList, hContact); - if (hwndDlg == nullptr) - return 1; - - HWND hwndBtn = GetDlgItem(hwndDlg, tempCID); - if (hwndBtn == nullptr) - return 1; - - SetWindowTextA(hwndBtn, bbdi->pszModuleName); - if (bbdi->hIcon) - SendMessage(hwndBtn, BM_SETIMAGE, IMAGE_ICON, (LPARAM)IcoLib_GetIconByHandle(bbdi->hIcon)); - if (bbdi->pwszTooltip) - SendMessage(hwndBtn, BUTTONADDTOOLTIP, (WPARAM)bbdi->pwszTooltip, BATF_UNICODE); - if (bbdi->bbbFlags) { - ShowWindow(hwndBtn, (bbdi->bbbFlags & BBSF_HIDDEN) ? SW_HIDE : SW_SHOW); - EnableWindow(hwndBtn, !(bbdi->bbbFlags & BBSF_DISABLED)); - Button_SetCheck(hwndBtn, (bbdi->bbbFlags & BBSF_PUSHED) != 0); - Button_SetCheck(hwndBtn, (bbdi->bbbFlags & BBSF_RELEASED) == 0); - } - return 0; -} - -MIR_APP_DLL(int) Srmm_ModifyButton(BBButton *bbdi) -{ - if (!bbdi) - return 1; - - CustomButtonData *cbd = nullptr; - { - mir_cslock lck(csToolBar); - - for (auto &p : arButtonsList) - if (!mir_strcmp(p->m_pszModuleName, bbdi->pszModuleName) && (p->m_dwButtonID == bbdi->dwButtonID)) { - cbd = p; - break; - } - - if (cbd != nullptr) { - if (bbdi->pwszTooltip) - cbd->m_pwszTooltip = mir_wstrdup(bbdi->pwszTooltip); - if (bbdi->hIcon) - cbd->m_hIcon = bbdi->hIcon; - if (bbdi->bbbFlags) { - cbd->m_bHidden = (bbdi->bbbFlags & BBBF_HIDDEN) != 0; - cbd->m_bRSided = (bbdi->bbbFlags & BBBF_ISRSIDEBUTTON) != 0; - cbd->m_bCanBeHidden = (bbdi->bbbFlags & BBBF_CANBEHIDDEN) != 0; - cbd->m_bChatButton = (bbdi->bbbFlags & BBBF_ISCHATBUTTON) != 0; - cbd->m_bIMButton = (bbdi->bbbFlags & BBBF_ISIMBUTTON) != 0; - cbd->m_bDisabled = (bbdi->bbbFlags & BBBF_DISABLED) != 0; - } - } - } - - if (cbd != nullptr) - WindowList_Broadcast(g_hWindowList, WM_CBD_UPDATED, 0, (LPARAM)cbd); - return 0; -} - -MIR_APP_DLL(void) Srmm_ClickToolbarIcon(MCONTACT hContact, int idFrom, HWND hwndDlg, BOOL code) -{ - bool bFromArrow = false; - HWND hwndFrom = nullptr; - - CustomButtonClickData cbcd = {}; - - for (auto &cbd : arButtonsList) { - if (cbd->m_dwButtonCID == idFrom) { - cbcd.pszModule = cbd->m_pszModuleName; - cbcd.dwButtonId = cbd->m_dwButtonID; - hwndFrom = GetDlgItem(hwndDlg, idFrom); - } - else if (cbd->m_dwArrowCID == idFrom) { - bFromArrow = true; - cbcd.pszModule = cbd->m_pszModuleName; - cbcd.dwButtonId = cbd->m_dwButtonID; - hwndFrom = GetDlgItem(hwndDlg, idFrom-1); - } - } - - if (hwndFrom == nullptr) - return; - - RECT rc; - GetWindowRect(hwndFrom, &rc); - cbcd.pt.x = rc.left; - cbcd.pt.y = rc.bottom; - - cbcd.hwndFrom = GetParent(hwndFrom); - cbcd.hContact = hContact; - cbcd.flags = (code ? BBCF_RIGHTBUTTON : 0) | (GetKeyState(VK_SHIFT) & 0x8000 ? BBCF_SHIFTPRESSED : 0) | (GetKeyState(VK_CONTROL) & 0x8000 ? BBCF_CONTROLPRESSED : 0) | (bFromArrow ? BBCF_ARROWCLICKED : 0); - - NotifyEventHooks(hHookButtonPressedEvt, hContact, (LPARAM)&cbcd); -} - -void Srmm_ProcessToolbarHotkey(MCONTACT hContact, INT_PTR iButtonFrom, HWND hwndDlg) -{ - HWND hwndFrom = nullptr; - - CustomButtonClickData cbcd = {}; - - for (auto &cbd : arButtonsList) { - if (cbd->m_hotkey == nullptr || cbd->m_bDisabled) - continue; - - if (cbd->m_hotkey->lParam == iButtonFrom) { - cbcd.pszModule = cbd->m_pszModuleName; - cbcd.dwButtonId = cbd->m_dwButtonID; - hwndFrom = GetDlgItem(hwndDlg, cbd->m_dwButtonCID); - break; - } - } - - if (hwndFrom == nullptr) - return; - - RECT rc; - GetWindowRect(hwndFrom, &rc); - cbcd.pt.x = rc.left; - cbcd.pt.y = rc.bottom; - - cbcd.hwndFrom = GetParent(hwndFrom); - cbcd.hContact = hContact; - cbcd.flags = (GetKeyState(VK_SHIFT) & 0x8000 ? BBCF_SHIFTPRESSED : 0) | (GetKeyState(VK_CONTROL) & 0x8000 ? BBCF_CONTROLPRESSED : 0); - - NotifyEventHooks(hHookButtonPressedEvt, hContact, (LPARAM)&cbcd); -} - -MIR_APP_DLL(void) Srmm_ResetToolbar() -{ - for (auto &cbd : arButtonsList) { - cbd->m_dwPosition = cbd->m_dwOrigPosition; - cbd->m_bRSided = cbd->m_dwOrigFlags.bit1; - cbd->m_bIMButton = cbd->m_dwOrigFlags.bit2; - cbd->m_bChatButton = cbd->m_dwOrigFlags.bit3; - cbd->m_bCanBeHidden = cbd->m_dwOrigFlags.bit4; - } -} - -void Srmm_CreateToolbarIcons(HWND hwndDlg, int flags) -{ - HINSTANCE hInstance = (HINSTANCE)GetWindowLongPtr(hwndDlg, GWLP_HINSTANCE); - - CDlgBase *pDlg = CDlgBase::Find(hwndDlg); - - for (auto &cbd : arButtonsList) { - if (cbd->m_bSeparator) - continue; - - HWND hwndButton = GetDlgItem(hwndDlg, cbd->m_dwButtonCID); - if ((flags & BBBF_ISIMBUTTON) && cbd->m_bIMButton || (flags & BBBF_ISCHATBUTTON) && cbd->m_bChatButton) { - if (hwndButton == nullptr) { - hwndButton = CreateWindowEx(0, L"MButtonClass", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, 0, cbd->m_iButtonWidth, DPISCALEX_S(22), hwndDlg, (HMENU)cbd->m_dwButtonCID, hInstance, nullptr); - if (hwndButton == nullptr) // smth went wrong - continue; - - // if there's a pre-created button control in a class, initialize it - if (pDlg != nullptr) { - CCtrlBase *pControl = (*pDlg)[cbd->m_dwButtonCID]; - if (pControl) - pControl->OnInit(); - } - } - SendMessage(hwndButton, BUTTONSETASFLATBTN, TRUE, 0); - if (cbd->m_pwszText) - SetWindowTextW(hwndButton, cbd->m_pwszText); - if (cbd->m_pwszTooltip) - SendMessage(hwndButton, BUTTONADDTOOLTIP, LPARAM(cbd->m_pwszTooltip), BATF_UNICODE); - if (cbd->m_hIcon) - SendMessage(hwndButton, BM_SETIMAGE, IMAGE_ICON, (LPARAM)IcoLib_GetIconByHandle(cbd->m_hIcon)); - - if (cbd->m_dwArrowCID) - SendMessage(hwndButton, BUTTONSETARROW, cbd->m_dwArrowCID, 0); - if (cbd->m_bPushButton) - SendMessage(hwndButton, BUTTONSETASPUSHBTN, TRUE, 0); - - if (cbd->m_bDisabled) - EnableWindow(hwndButton, FALSE); - if (cbd->m_bHidden) - ShowWindow(hwndButton, SW_HIDE); - } - else if (hwndButton) - DestroyWindow(hwndButton); - } -} - -MIR_APP_DLL(void) Srmm_UpdateToolbarIcons(HWND hwndDlg) -{ - for (auto &cbd : arButtonsList) { - if (cbd->m_bSeparator || cbd->m_hIcon == nullptr) - continue; - - HWND hwndBtn = GetDlgItem(hwndDlg, cbd->m_dwButtonCID); - if (hwndBtn) - SendMessage(hwndBtn, BM_SETIMAGE, IMAGE_ICON, (LPARAM)IcoLib_GetIconByHandle(cbd->m_hIcon)); - } -} - -MIR_APP_DLL(void) Srmm_RedrawToolbarIcons(HWND hwndDlg) -{ - for (auto &cbd : arButtonsList) { - HWND hwnd = GetDlgItem(hwndDlg, cbd->m_dwButtonCID); - if (hwnd) - InvalidateRect(hwnd, nullptr, TRUE); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static void CB_ReInitCustomButtons() -{ - for (auto &cbd : arButtonsList.rev_iter()) - if (cbd->m_opFlags & (BBSF_NTBSWAPED | BBSF_NTBDESTRUCT)) { - cbd->m_opFlags ^= BBSF_NTBSWAPED; - - if (cbd->m_opFlags & BBSF_NTBDESTRUCT) - arButtonsList.removeItem(&cbd); - } - - qsort(arButtonsList.getArray(), arButtonsList.getCount(), sizeof(void*), sstSortButtons); - - WindowList_Broadcast(g_hWindowList, WM_CBD_RECREATE, 0, 0); - WindowList_Broadcast(g_hWindowList, WM_CBD_UPDATED, 0, 0); - WindowList_Broadcast(g_hWindowList, WM_CBD_LOADICONS, 0, 0); -} - -static void CB_WriteButtonSettings(MCONTACT hContact, CustomButtonData *cbd) -{ - char SettingName[1024]; - char SettingParameter[1024]; - - //modulename_buttonID, position_inIM_inCHAT_isLSide_isRSide_CanBeHidden - - mir_snprintf(SettingName, "%s_%d", cbd->m_pszModuleName.get(), cbd->m_dwButtonID); - mir_snprintf(SettingParameter, "%d_%u_%u_%u_%u_%u", cbd->m_dwPosition, cbd->m_bIMButton, cbd->m_bChatButton, 0, cbd->m_bRSided, cbd->m_bCanBeHidden); - if (!(cbd->m_opFlags & BBSF_NTBDESTRUCT)) - db_set_s(hContact, BB_MODULE_NAME, SettingName, SettingParameter); - else - db_unset(hContact, BB_MODULE_NAME, SettingName); -} - -#define MIDDLE_SEPARATOR L">-------M-------<" - -class CSrmmToolbarOptions : public CDlgBase -{ - CCtrlTreeView m_toolBar; - CCtrlCheck m_btnIM, m_btnChat, m_btnHidden; - CCtrlButton m_btnReset, m_btnSeparator; - CCtrlSpin m_gap; - CTimer timer; - - HIMAGELIST m_hImgl; - - void SaveTree() - { - bool RSide = false; - int count = 10; - uint32_t loc_sepcout = 0; - wchar_t strbuf[128]; - - TVITEMEX tvi; - tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_HANDLE; - tvi.hItem = m_toolBar.GetRoot(); - tvi.pszText = strbuf; - tvi.cchTextMax = _countof(strbuf); - { - mir_cslock lck(csToolBar); - - while (tvi.hItem != nullptr) { - m_toolBar.GetItem(&tvi); - - if (mir_wstrcmp(tvi.pszText, MIDDLE_SEPARATOR) == 0) { - RSide = true; - count = m_toolBar.GetCount() * 10 - count; - tvi.hItem = m_toolBar.GetNextSibling(tvi.hItem); - continue; - } - CustomButtonData *cbd = (CustomButtonData*)tvi.lParam; - if (cbd && arButtonsList.indexOf(cbd) != -1) { - if (cbd->m_opFlags) { - cbd->m_bIMButton = (cbd->m_opFlags & BBSF_IMBUTTON) != 0; - cbd->m_bChatButton = (cbd->m_opFlags & BBSF_CHATBUTTON) != 0; - cbd->m_bCanBeHidden = (cbd->m_opFlags & BBSF_CANBEHIDDEN) != 0; - } - - if (RSide && !cbd->m_bRSided) { - cbd->m_bRSided = true; - cbd->m_opFlags |= BBSF_NTBSWAPED; - } - else if (!RSide && cbd->m_bRSided) { - cbd->m_bRSided = false; - cbd->m_opFlags |= BBSF_NTBSWAPED; - } - - if (!cbd->m_bCantBeHidden && !m_toolBar.GetCheckState(tvi.hItem)) { - cbd->m_bIMButton = false; - cbd->m_bChatButton = false; - - if (cbd->m_bSeparator && !mir_strcmp(cbd->m_pszModuleName, "Tabsrmm_sep")) - cbd->m_opFlags = BBSF_NTBDESTRUCT; - } - else { - if (!cbd->m_bIMButton && !cbd->m_bChatButton) - cbd->m_bIMButton = true; - if (cbd->m_bSeparator && !mir_strcmp(cbd->m_pszModuleName, "Tabsrmm_sep")) { - cbd->m_bHidden = false; - cbd->m_opFlags &= ~BBSF_NTBDESTRUCT; - ++loc_sepcout; - } - } - - cbd->m_dwPosition = (uint32_t)count; - CB_WriteButtonSettings(0, cbd); - - if (!(cbd->m_opFlags & BBSF_NTBDESTRUCT)) - (RSide) ? (count -= 10) : (count += 10); - } - - HTREEITEM hItem = m_toolBar.GetNextSibling(tvi.hItem); - if (cbd->m_opFlags & BBSF_NTBDESTRUCT) - m_toolBar.DeleteItem(tvi.hItem); - tvi.hItem = hItem; - } - - qsort(arButtonsList.getArray(), arButtonsList.getCount(), sizeof(void*), sstSortButtons); - } - db_set_dw(0, BB_MODULE_NAME, "SeparatorsCount", loc_sepcout); - dwSepCount = loc_sepcout; - } - - void BuildMenuObjectsTree() - { - m_toolBar.DeleteAllItems(); - - m_hImgl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 2, 2); - ImageList_AddIcon(m_hImgl, Skin_LoadIcon(SKINICON_OTHER_SMALLDOT)); - ImageList_Destroy(m_toolBar.GetImageList(TVSIL_NORMAL)); - m_toolBar.SetImageList(m_hImgl, TVSIL_NORMAL); - - if (arButtonsList.getCount() == 0) - return; - - bool bPrevSide = false; - - TVINSERTSTRUCT tvis; - tvis.hParent = nullptr; - tvis.hInsertAfter = TVI_LAST; - tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE; - - mir_cslock lck(csToolBar); - for (auto &cbd : arButtonsList) { - if (bPrevSide != cbd->m_bRSided) { - bPrevSide = true; - - TVINSERTSTRUCT tvis2 = {}; - tvis.hInsertAfter = TVI_LAST; - tvis2.itemex.mask = TVIF_PARAM | TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_STATE | TVIF_STATEEX; - tvis2.itemex.pszText = MIDDLE_SEPARATOR; - tvis2.itemex.stateMask = TVIS_BOLD; - tvis2.itemex.state = TVIS_BOLD; - tvis2.itemex.iImage = tvis.item.iSelectedImage = -1; - tvis2.itemex.uStateEx = TVIS_EX_DISABLED; - tvis.hInsertAfter = m_toolBar.InsertItem(&tvis2); - m_toolBar.SetItemState(tvis.hInsertAfter, 0x3000, TVIS_STATEIMAGEMASK); - } - - tvis.item.lParam = (LPARAM)cbd; - - if (cbd->m_bSeparator) { - tvis.item.pszText = TranslateT("<Separator>"); - tvis.item.iImage = tvis.item.iSelectedImage = 0; - } - else { - tvis.item.pszText = TranslateW(cbd->m_pwszTooltip); - tvis.item.iImage = tvis.item.iSelectedImage = ImageList_AddIcon(m_hImgl, IcoLib_GetIconByHandle(cbd->m_hIcon)); - } - cbd->m_opFlags = 0; - HTREEITEM hti = m_toolBar.InsertItem(&tvis); - - m_toolBar.SetCheckState(hti, (cbd->m_bIMButton || cbd->m_bChatButton)); - if (cbd->m_bCantBeHidden) - m_toolBar.SetItemState(hti, 0x3000, TVIS_STATEIMAGEMASK); - } - } - -public: - CSrmmToolbarOptions() : - CDlgBase(g_plugin, IDD_OPT_TOOLBAR), - m_gap(this, IDC_SPIN1, 10), - m_btnIM(this, IDC_IMCHECK), - m_btnChat(this, IDC_CHATCHECK), - m_toolBar(this, IDC_TOOLBARTREE), - m_btnReset(this, IDC_BBRESET), - m_btnHidden(this, IDC_CANBEHIDDEN), - m_btnSeparator(this, IDC_SEPARATOR), - m_hImgl(nullptr), - timer(this, 1) - { - timer.OnEvent = Callback(this, &CSrmmToolbarOptions::OnTimer); - - m_toolBar.SetFlags(MTREE_DND); // enable drag-n-drop - m_toolBar.OnSelChanged = Callback(this, &CSrmmToolbarOptions::OnTreeSelChanged); - m_toolBar.OnSelChanging = Callback(this, &CSrmmToolbarOptions::OnTreeSelChanging); - m_toolBar.OnItemChanged = Callback(this, &CSrmmToolbarOptions::OnTreeItemChanged); - - m_btnReset.OnClick = Callback(this, &CSrmmToolbarOptions::btnResetClicked); - m_btnSeparator.OnClick = Callback(this, &CSrmmToolbarOptions::btnSeparatorClicked); - } - - bool OnInitDialog() override - { - g_pDialog = this; - BuildMenuObjectsTree(); - - m_btnIM.Disable(); - m_btnChat.Disable(); - m_btnHidden.Disable(); - - m_gap.SetPosition(g_iButtonGap); - return true; - } - - void OnDestroy() override - { - g_pDialog = nullptr; - ImageList_Destroy(m_toolBar.GetImageList(TVSIL_NORMAL)); - ImageList_Destroy(m_toolBar.GetImageList(TVSIL_STATE)); - } - - bool OnApply() override - { - OnTreeSelChanging(nullptr); // save latest changes - SaveTree(); // save the whole tree then - CB_ReInitCustomButtons(); - Chat_UpdateOptions(); // also restore chat windows - - uint16_t newGap = m_gap.GetPosition(); - if (newGap != g_iButtonGap) { - g_iButtonGap = newGap; - WindowList_BroadcastAsync(g_hWindowList, WM_SIZE, 0, 0); - } - - BuildMenuObjectsTree(); - - m_btnIM.Disable(); - m_btnChat.Disable(); - m_btnHidden.Disable(); - return true; - } - - virtual void OnReset() override - { - CB_ReInitCustomButtons(); - dwSepCount = db_get_dw(0, BB_MODULE_NAME, "SeparatorsCount", 0); - } - - void btnResetClicked(void*) - { - db_delete_module(0, BB_MODULE_NAME); - - Srmm_ResetToolbar(); - qsort(arButtonsList.getArray(), arButtonsList.getCount(), sizeof(void*), sstSortButtons); - - BuildMenuObjectsTree(); - NotifyChange(); - } - - void btnSeparatorClicked(void*) - { - HTREEITEM hItem = m_toolBar.GetSelection(); - if (!hItem) - hItem = TVI_FIRST; - - BBButton bbd = {}; - bbd.pszModuleName = "Tabsrmm_sep"; - bbd.bbbFlags = BBBF_ISSEPARATOR | BBBF_ISIMBUTTON; - bbd.dwButtonID = ++dwSepCount; - - CustomButtonData *cbd = (CustomButtonData*)Srmm_AddButton(&bbd, &g_plugin); - - TVINSERTSTRUCT tvis; - tvis.hParent = nullptr; - tvis.hInsertAfter = hItem; - tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE; - tvis.item.pszText = TranslateT("<Separator>"); - tvis.item.iImage = tvis.item.iSelectedImage = -1; - tvis.item.lParam = (LPARAM)cbd; - hItem = m_toolBar.InsertItem(&tvis); - - m_toolBar.SetCheckState(hItem, (cbd->m_bIMButton || cbd->m_bChatButton)); - NotifyChange(); - } - - void OnTreeSelChanging(void*) - { - HTREEITEM hItem = m_toolBar.GetSelection(); - if (hItem == nullptr) - return; - - wchar_t strbuf[128]; - TVITEMEX tvi; - tvi.hItem = hItem; - tvi.pszText = strbuf; - tvi.cchTextMax = _countof(strbuf); - tvi.mask = TVIF_TEXT | TVIF_HANDLE | TVIF_PARAM; - m_toolBar.GetItem(&tvi); - - if (tvi.lParam == 0 || !m_toolBar.GetCheckState(tvi.hItem) || !mir_wstrcmp(tvi.pszText, MIDDLE_SEPARATOR)) - return; - - CustomButtonData *cbd = (CustomButtonData*)tvi.lParam; - cbd->m_bIMButton = m_btnIM.GetState() != 0; - cbd->m_bChatButton = m_btnChat.GetState() != 0; - cbd->m_bCanBeHidden = !cbd->m_bCantBeHidden && m_btnHidden.GetState() != 0; - cbd->m_opFlags = (cbd->m_bIMButton ? BBSF_IMBUTTON : 0) + (cbd->m_bChatButton ? BBSF_CHATBUTTON : 0) + (cbd->m_bCanBeHidden ? BBSF_CANBEHIDDEN : 0); - - if (!cbd->m_bChatButton && !cbd->m_bIMButton) - m_toolBar.SetCheckState(tvi.hItem, 0); - } - - void OnTreeSelChanged(void*) - { - HTREEITEM hItem = m_toolBar.GetSelection(); - if (hItem == nullptr) - return; - - wchar_t strbuf[128]; - TVITEMEX tvi; - tvi.pszText = strbuf; - tvi.cchTextMax = _countof(strbuf); - tvi.mask = TVIF_TEXT | TVIF_HANDLE | TVIF_PARAM; - tvi.hItem = hItem; - m_toolBar.GetItem(&tvi); - - if (!m_toolBar.GetCheckState(tvi.hItem) || !mir_wstrcmp(tvi.pszText, MIDDLE_SEPARATOR)) { - m_btnIM.Disable(); - m_btnChat.Disable(); - m_btnHidden.Disable(); - return; - } - - if (tvi.lParam == 0) - return; - - CustomButtonData *cbd = (CustomButtonData*)tvi.lParam; - m_btnIM.Enable(); m_btnIM.SetState(cbd->m_bIMButton); - m_btnChat.Enable(); m_btnChat.SetState(cbd->m_bChatButton); - m_btnHidden.Enable(); m_btnHidden.SetState(cbd->m_bCanBeHidden); - } - - void OnTreeItemChanged(CCtrlTreeView::TEventInfo *evt) - { - bool iNewState = !m_toolBar.GetCheckState(evt->hItem); - m_btnIM.Enable(iNewState); - m_btnChat.Enable(iNewState); - m_btnHidden.Enable(iNewState); - if (iNewState) - m_btnIM.SetState(true); - } - - void OnTimer(CTimer *pTimer) - { - pTimer->Stop(); - BuildMenuObjectsTree(); - } - - static void RereadButtons() - { - if (g_pDialog) - g_pDialog->timer.Start(100); - } -}; - -void SrmmLogOptionsInit(WPARAM wParam); - -static int SrmmOptionsInit(WPARAM wParam, LPARAM) -{ - OPTIONSDIALOGPAGE odp = {}; - odp.position = 910000000; - odp.szGroup.a = LPGEN("Message sessions"); - odp.szTitle.a = LPGEN("Toolbar"); - odp.flags = ODPF_BOLDGROUPS; - odp.pDialog = new CSrmmToolbarOptions(); - g_plugin.addOptions(wParam, &odp); - - ChatOptionsInit(wParam); - SrmmLogOptionsInit(wParam); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(HANDLE) Srmm_AddButton(const BBButton *bbdi, HPLUGIN _hLang) -{ - if (bbdi == nullptr) - return nullptr; - - CustomButtonData *cbd = new CustomButtonData(); - cbd->m_pszModuleName = mir_strdup(bbdi->pszModuleName); - cbd->m_pwszText = mir_wstrdup(bbdi->pwszText); - cbd->m_pwszTooltip = mir_wstrdup(bbdi->pwszTooltip); - - cbd->m_dwButtonID = bbdi->dwButtonID; - cbd->m_hIcon = bbdi->hIcon; - cbd->m_dwPosition = cbd->m_dwOrigPosition = bbdi->dwDefPos; - cbd->m_dwButtonCID = (bbdi->bbbFlags & BBBF_CREATEBYID) ? bbdi->dwButtonID : LastCID; - cbd->m_dwArrowCID = (bbdi->bbbFlags & BBBF_ISARROWBUTTON) ? cbd->m_dwButtonCID + 1 : 0; - cbd->m_bHidden = (bbdi->bbbFlags & BBBF_HIDDEN) != 0; - cbd->m_bSeparator = (bbdi->bbbFlags & BBBF_ISSEPARATOR) != 0; - - cbd->m_bDisabled = (bbdi->bbbFlags & BBBF_DISABLED) != 0; - cbd->m_bPushButton = (bbdi->bbbFlags & BBBF_ISPUSHBUTTON) != 0; - cbd->m_pPlugin = _hLang; - - cbd->m_dwOrigFlags.bit1 = cbd->m_bRSided = (bbdi->bbbFlags & BBBF_ISRSIDEBUTTON) != 0; - cbd->m_dwOrigFlags.bit2 = cbd->m_bIMButton = (bbdi->bbbFlags & BBBF_ISIMBUTTON) != 0; - cbd->m_dwOrigFlags.bit3 = cbd->m_bChatButton = (bbdi->bbbFlags & BBBF_ISCHATBUTTON) != 0; - cbd->m_dwOrigFlags.bit4 = cbd->m_bCanBeHidden = (bbdi->bbbFlags & BBBF_CANBEHIDDEN) != 0; - - if (cbd->m_bSeparator) - cbd->m_iButtonWidth = DPISCALEX_S(10); - else if (bbdi->bbbFlags & BBBF_ISARROWBUTTON) - cbd->m_iButtonWidth = DPISCALEX_S(34); - else - cbd->m_iButtonWidth = DPISCALEX_S(22); - - if (bbdi->pszHotkey) { - for (auto &p : hotkeys) { - if (!mir_strcmp(p->getName(), bbdi->pszHotkey)) { - cbd->m_hotkey = p; - break; - } - } - } - - // download database settings - char SettingName[1024]; - mir_snprintf(SettingName, "%s_%d", cbd->m_pszModuleName.get(), cbd->m_dwButtonID); - - DBVARIANT dbv = { 0 }; - if (!db_get_s(0, BB_MODULE_NAME, SettingName, &dbv)) { - // modulename_buttonID, position_inIM_inCHAT_isLSide_isRSide_CanBeHidden - char *token = strtok(dbv.pszVal, "_"); - cbd->m_dwPosition = (uint32_t)atoi(token); - token = strtok(nullptr, "_"); - cbd->m_bIMButton = atoi(token) != 0; - token = strtok(nullptr, "_"); - cbd->m_bChatButton = atoi(token) != 0; - token = strtok(nullptr, "_"); - token = strtok(nullptr, "_"); - cbd->m_bRSided = atoi(token) != 0; - token = strtok(nullptr, "_"); - cbd->m_bCanBeHidden = atoi(token) != 0; - - db_free(&dbv); - } - - arButtonsList.insert(cbd); - - if (cbd->m_dwButtonCID != cbd->m_dwButtonID) - LastCID++; - if (cbd->m_dwArrowCID == LastCID) - LastCID++; - - WindowList_Broadcast(g_hWindowList, WM_CBD_UPDATED, 0, 0); - CSrmmToolbarOptions::RereadButtons(); - return cbd; -} - -MIR_APP_DLL(int) Srmm_RemoveButton(BBButton *bbdi) -{ - if (!bbdi) - return 1; - - CustomButtonData *pFound = nullptr; - { - mir_cslock lck(csToolBar); - - for (auto &cbd : arButtonsList.rev_iter()) - if (!mir_strcmp(cbd->m_pszModuleName, bbdi->pszModuleName) && cbd->m_dwButtonID == bbdi->dwButtonID) - pFound = arButtonsList.removeItem(&cbd); - } - - if (pFound) { - CSrmmToolbarOptions::RereadButtons(); - WindowList_Broadcast(g_hWindowList, WM_CBD_REMOVED, pFound->m_dwButtonCID, (LPARAM)pFound); - delete pFound; - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void KillModuleToolbarIcons(CMPluginBase *pPlugin) -{ - int oldCount = arButtonsList.getCount(); - - auto T = arButtonsList.rev_iter(); - for (auto &cbd : T) - if (cbd->m_pPlugin == pPlugin) - delete arButtonsList.removeItem(&cbd); - - if (oldCount != arButtonsList.getCount()) - CSrmmToolbarOptions::RereadButtons(); -} - -static INT_PTR BroadcastMessage(WPARAM, LPARAM lParam) -{ - Srmm_Broadcast((UINT)lParam, 0, 0); - return 0; -} - -static void CALLBACK SrmmLoadToolbar() -{ - NotifyEventHooks(hHookToolBarLoadedEvt, 0, 0); - DestroyHookableEvent(hHookToolBarLoadedEvt); - - HookEvent(ME_OPT_INITIALISE, SrmmOptionsInit); -} - -static int ConvertToolbarData(const char *szSetting, void*) -{ - DBVARIANT dbv; - if (!db_get(0, "Tab" BB_MODULE_NAME, szSetting, &dbv)) { - db_set(0, BB_MODULE_NAME, szSetting, &dbv); - db_free(&dbv); - } - return 0; -} - -void LoadSrmmToolbarModule() -{ - CreateServiceFunction("SRMsg/BroadcastMessage", BroadcastMessage); - - Miranda_WaitOnHandle(SrmmLoadToolbar); - - hHookButtonPressedEvt = CreateHookableEvent(ME_MSG_BUTTONPRESSED); - hHookToolBarLoadedEvt = CreateHookableEvent(ME_MSG_TOOLBARLOADED); - - HDC hScrnDC = GetDC(nullptr); - g_DPIscaleX = GetDeviceCaps(hScrnDC, LOGPIXELSX) / 96.0; - g_DPIscaleY = GetDeviceCaps(hScrnDC, LOGPIXELSY) / 96.0; - ReleaseDC(nullptr, hScrnDC); - - // old data? convert them - if (db_get_dw(0, "Tab" BB_MODULE_NAME, "SeparatorsCount", -1) != -1) { - db_enum_settings(0, ConvertToolbarData, "Tab" BB_MODULE_NAME, nullptr); - db_delete_module(0, "Tab" BB_MODULE_NAME); - } - - dwSepCount = db_get_dw(0, BB_MODULE_NAME, "SeparatorsCount", 0); - CB_RegisterSeparators(); -} - -void UnloadSrmmToolbarModule() -{ - DestroyHookableEvent(hHookButtonPressedEvt); - - for (auto &it : arButtonsList) - delete it; - arButtonsList.destroy(); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "chat.h"
+#include "skin.h"
+
+#define BB_MODULE_NAME "SRMM_Toolbar"
+
+#define DPISCALEY_S(argY) ((int)((double)(argY) * g_DPIscaleY))
+#define DPISCALEX_S(argX) ((int)((double)(argX) * g_DPIscaleX))
+
+static double g_DPIscaleX, g_DPIscaleY;
+static class CSrmmToolbarOptions *g_pDialog = nullptr;
+
+static CMOption<uint8_t> g_iButtonGap(BB_MODULE_NAME, "ButtonsBarGap", 1);
+
+static int SortButtons(const CustomButtonData *p1, const CustomButtonData *p2)
+{
+ if (p1->m_bRSided != p2->m_bRSided)
+ return (p2->m_bRSided) ? -1 : 1;
+ if (p1->m_dwPosition != p2->m_dwPosition)
+ return p1->m_dwPosition - p2->m_dwPosition;
+ int res = mir_strcmp(p1->m_pszModuleName, p2->m_pszModuleName);
+ if (res != 0)
+ return res;
+ return p1->m_dwButtonID - p2->m_dwButtonID;
+}
+
+static LIST<CustomButtonData> arButtonsList(1, SortButtons);
+
+int LastCID = MIN_CBUTTONID;
+int dwSepCount = 0;
+
+static mir_cs csToolBar;
+static HANDLE hHookToolBarLoadedEvt, hHookButtonPressedEvt;
+
+static int sstSortButtons(const void *p1, const void *p2)
+{
+ return SortButtons(*(CustomButtonData**)p1, *(CustomButtonData**)p2);
+}
+
+static void CB_RegisterSeparators()
+{
+ BBButton bbd = {};
+ bbd.pszModuleName = "Tabsrmm_sep";
+ for (int i = 0; dwSepCount > i; i++) {
+ bbd.bbbFlags = BBBF_ISSEPARATOR | BBBF_ISIMBUTTON;
+ bbd.dwButtonID = i + 1;
+ bbd.dwDefPos = 410 + i;
+ Srmm_AddButton(&bbd, &g_plugin);
+ }
+}
+
+MIR_APP_DLL(CustomButtonData*) Srmm_GetNthButton(int i)
+{
+ return arButtonsList[i];
+}
+
+MIR_APP_DLL(int) Srmm_GetButtonCount(void)
+{
+ return arButtonsList.getCount();
+}
+
+MIR_APP_DLL(int) Srmm_GetButtonGap()
+{
+ return g_iButtonGap;
+}
+
+MIR_APP_DLL(int) Srmm_GetButtonState(HWND hwndDlg, BBButton *bbdi)
+{
+ if (hwndDlg == nullptr || bbdi == nullptr)
+ return 1;
+
+ uint32_t tempCID = 0;
+ bbdi->bbbFlags = 0;
+ for (auto &cbd : arButtonsList)
+ if (!mir_strcmp(cbd->m_pszModuleName, bbdi->pszModuleName) && (cbd->m_dwButtonID == bbdi->dwButtonID)) {
+ tempCID = cbd->m_dwButtonCID;
+ break;
+ }
+
+ if (!tempCID)
+ return 1;
+
+ HWND hwndBtn = GetDlgItem(hwndDlg, tempCID);
+ bbdi->bbbFlags = (IsDlgButtonChecked(hwndDlg, tempCID) ? BBSF_PUSHED : BBSF_RELEASED) | (IsWindowVisible(hwndBtn) ? 0 : BBSF_HIDDEN) | (IsWindowEnabled(hwndBtn) ? 0 : BBSF_DISABLED);
+ return 0;
+}
+
+MIR_APP_DLL(int) Srmm_SetButtonState(MCONTACT hContact, BBButton *bbdi)
+{
+ if (hContact == 0 || bbdi == nullptr)
+ return 1;
+
+ uint32_t tempCID = 0;
+ for (auto &cbd : arButtonsList)
+ if (!mir_strcmp(cbd->m_pszModuleName, bbdi->pszModuleName) && (cbd->m_dwButtonID == bbdi->dwButtonID)) {
+ tempCID = cbd->m_dwButtonCID;
+ break;
+ }
+
+ if (!tempCID)
+ return 1;
+
+ HWND hwndDlg = WindowList_Find(g_hWindowList, hContact);
+ if (hwndDlg == nullptr)
+ return 1;
+
+ HWND hwndBtn = GetDlgItem(hwndDlg, tempCID);
+ if (hwndBtn == nullptr)
+ return 1;
+
+ SetWindowTextA(hwndBtn, bbdi->pszModuleName);
+ if (bbdi->hIcon)
+ SendMessage(hwndBtn, BM_SETIMAGE, IMAGE_ICON, (LPARAM)IcoLib_GetIconByHandle(bbdi->hIcon));
+ if (bbdi->pwszTooltip)
+ SendMessage(hwndBtn, BUTTONADDTOOLTIP, (WPARAM)bbdi->pwszTooltip, BATF_UNICODE);
+ if (bbdi->bbbFlags) {
+ ShowWindow(hwndBtn, (bbdi->bbbFlags & BBSF_HIDDEN) ? SW_HIDE : SW_SHOW);
+ EnableWindow(hwndBtn, !(bbdi->bbbFlags & BBSF_DISABLED));
+ Button_SetCheck(hwndBtn, (bbdi->bbbFlags & BBSF_PUSHED) != 0);
+ Button_SetCheck(hwndBtn, (bbdi->bbbFlags & BBSF_RELEASED) == 0);
+ }
+ return 0;
+}
+
+MIR_APP_DLL(int) Srmm_ModifyButton(BBButton *bbdi)
+{
+ if (!bbdi)
+ return 1;
+
+ CustomButtonData *cbd = nullptr;
+ {
+ mir_cslock lck(csToolBar);
+
+ for (auto &p : arButtonsList)
+ if (!mir_strcmp(p->m_pszModuleName, bbdi->pszModuleName) && (p->m_dwButtonID == bbdi->dwButtonID)) {
+ cbd = p;
+ break;
+ }
+
+ if (cbd != nullptr) {
+ if (bbdi->pwszTooltip)
+ cbd->m_pwszTooltip = mir_wstrdup(bbdi->pwszTooltip);
+ if (bbdi->hIcon)
+ cbd->m_hIcon = bbdi->hIcon;
+ if (bbdi->bbbFlags) {
+ cbd->m_bHidden = (bbdi->bbbFlags & BBBF_HIDDEN) != 0;
+ cbd->m_bRSided = (bbdi->bbbFlags & BBBF_ISRSIDEBUTTON) != 0;
+ cbd->m_bCanBeHidden = (bbdi->bbbFlags & BBBF_CANBEHIDDEN) != 0;
+ cbd->m_bChatButton = (bbdi->bbbFlags & BBBF_ISCHATBUTTON) != 0;
+ cbd->m_bIMButton = (bbdi->bbbFlags & BBBF_ISIMBUTTON) != 0;
+ cbd->m_bDisabled = (bbdi->bbbFlags & BBBF_DISABLED) != 0;
+ }
+ }
+ }
+
+ if (cbd != nullptr)
+ WindowList_Broadcast(g_hWindowList, WM_CBD_UPDATED, 0, (LPARAM)cbd);
+ return 0;
+}
+
+MIR_APP_DLL(void) Srmm_ClickToolbarIcon(MCONTACT hContact, int idFrom, HWND hwndDlg, BOOL code)
+{
+ bool bFromArrow = false;
+ HWND hwndFrom = nullptr;
+
+ CustomButtonClickData cbcd = {};
+
+ for (auto &cbd : arButtonsList) {
+ if (cbd->m_dwButtonCID == idFrom) {
+ cbcd.pszModule = cbd->m_pszModuleName;
+ cbcd.dwButtonId = cbd->m_dwButtonID;
+ hwndFrom = GetDlgItem(hwndDlg, idFrom);
+ }
+ else if (cbd->m_dwArrowCID == idFrom) {
+ bFromArrow = true;
+ cbcd.pszModule = cbd->m_pszModuleName;
+ cbcd.dwButtonId = cbd->m_dwButtonID;
+ hwndFrom = GetDlgItem(hwndDlg, idFrom-1);
+ }
+ }
+
+ if (hwndFrom == nullptr)
+ return;
+
+ RECT rc;
+ GetWindowRect(hwndFrom, &rc);
+ cbcd.pt.x = rc.left;
+ cbcd.pt.y = rc.bottom;
+
+ cbcd.hwndFrom = GetParent(hwndFrom);
+ cbcd.hContact = hContact;
+ cbcd.flags = (code ? BBCF_RIGHTBUTTON : 0) | (GetKeyState(VK_SHIFT) & 0x8000 ? BBCF_SHIFTPRESSED : 0) | (GetKeyState(VK_CONTROL) & 0x8000 ? BBCF_CONTROLPRESSED : 0) | (bFromArrow ? BBCF_ARROWCLICKED : 0);
+
+ NotifyEventHooks(hHookButtonPressedEvt, hContact, (LPARAM)&cbcd);
+}
+
+void Srmm_ProcessToolbarHotkey(MCONTACT hContact, INT_PTR iButtonFrom, HWND hwndDlg)
+{
+ HWND hwndFrom = nullptr;
+
+ CustomButtonClickData cbcd = {};
+
+ for (auto &cbd : arButtonsList) {
+ if (cbd->m_hotkey == nullptr || cbd->m_bDisabled)
+ continue;
+
+ if (cbd->m_hotkey->lParam == iButtonFrom) {
+ cbcd.pszModule = cbd->m_pszModuleName;
+ cbcd.dwButtonId = cbd->m_dwButtonID;
+ hwndFrom = GetDlgItem(hwndDlg, cbd->m_dwButtonCID);
+ break;
+ }
+ }
+
+ if (hwndFrom == nullptr)
+ return;
+
+ RECT rc;
+ GetWindowRect(hwndFrom, &rc);
+ cbcd.pt.x = rc.left;
+ cbcd.pt.y = rc.bottom;
+
+ cbcd.hwndFrom = GetParent(hwndFrom);
+ cbcd.hContact = hContact;
+ cbcd.flags = (GetKeyState(VK_SHIFT) & 0x8000 ? BBCF_SHIFTPRESSED : 0) | (GetKeyState(VK_CONTROL) & 0x8000 ? BBCF_CONTROLPRESSED : 0);
+
+ NotifyEventHooks(hHookButtonPressedEvt, hContact, (LPARAM)&cbcd);
+}
+
+MIR_APP_DLL(void) Srmm_ResetToolbar()
+{
+ for (auto &cbd : arButtonsList) {
+ cbd->m_dwPosition = cbd->m_dwOrigPosition;
+ cbd->m_bRSided = cbd->m_dwOrigFlags.bit1;
+ cbd->m_bIMButton = cbd->m_dwOrigFlags.bit2;
+ cbd->m_bChatButton = cbd->m_dwOrigFlags.bit3;
+ cbd->m_bCanBeHidden = cbd->m_dwOrigFlags.bit4;
+ }
+}
+
+void Srmm_CreateToolbarIcons(HWND hwndDlg, int flags)
+{
+ HINSTANCE hInstance = (HINSTANCE)GetWindowLongPtr(hwndDlg, GWLP_HINSTANCE);
+
+ CDlgBase *pDlg = CDlgBase::Find(hwndDlg);
+
+ for (auto &cbd : arButtonsList) {
+ if (cbd->m_bSeparator)
+ continue;
+
+ HWND hwndButton = GetDlgItem(hwndDlg, cbd->m_dwButtonCID);
+ if ((flags & BBBF_ISIMBUTTON) && cbd->m_bIMButton || (flags & BBBF_ISCHATBUTTON) && cbd->m_bChatButton) {
+ if (hwndButton == nullptr) {
+ hwndButton = CreateWindowEx(0, L"MButtonClass", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, 0, cbd->m_iButtonWidth, DPISCALEX_S(22), hwndDlg, (HMENU)cbd->m_dwButtonCID, hInstance, nullptr);
+ if (hwndButton == nullptr) // smth went wrong
+ continue;
+
+ // if there's a pre-created button control in a class, initialize it
+ if (pDlg != nullptr) {
+ CCtrlBase *pControl = (*pDlg)[cbd->m_dwButtonCID];
+ if (pControl)
+ pControl->OnInit();
+ }
+ }
+ SendMessage(hwndButton, BUTTONSETASFLATBTN, TRUE, 0);
+ if (cbd->m_pwszText)
+ SetWindowTextW(hwndButton, cbd->m_pwszText);
+ if (cbd->m_pwszTooltip)
+ SendMessage(hwndButton, BUTTONADDTOOLTIP, LPARAM(cbd->m_pwszTooltip), BATF_UNICODE);
+ if (cbd->m_hIcon)
+ SendMessage(hwndButton, BM_SETIMAGE, IMAGE_ICON, (LPARAM)IcoLib_GetIconByHandle(cbd->m_hIcon));
+
+ if (cbd->m_dwArrowCID)
+ SendMessage(hwndButton, BUTTONSETARROW, cbd->m_dwArrowCID, 0);
+ if (cbd->m_bPushButton)
+ SendMessage(hwndButton, BUTTONSETASPUSHBTN, TRUE, 0);
+
+ if (cbd->m_bDisabled)
+ EnableWindow(hwndButton, FALSE);
+ if (cbd->m_bHidden)
+ ShowWindow(hwndButton, SW_HIDE);
+ }
+ else if (hwndButton)
+ DestroyWindow(hwndButton);
+ }
+}
+
+MIR_APP_DLL(void) Srmm_UpdateToolbarIcons(HWND hwndDlg)
+{
+ for (auto &cbd : arButtonsList) {
+ if (cbd->m_bSeparator || cbd->m_hIcon == nullptr)
+ continue;
+
+ HWND hwndBtn = GetDlgItem(hwndDlg, cbd->m_dwButtonCID);
+ if (hwndBtn)
+ SendMessage(hwndBtn, BM_SETIMAGE, IMAGE_ICON, (LPARAM)IcoLib_GetIconByHandle(cbd->m_hIcon));
+ }
+}
+
+MIR_APP_DLL(void) Srmm_RedrawToolbarIcons(HWND hwndDlg)
+{
+ for (auto &cbd : arButtonsList) {
+ HWND hwnd = GetDlgItem(hwndDlg, cbd->m_dwButtonCID);
+ if (hwnd)
+ InvalidateRect(hwnd, nullptr, TRUE);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void CB_ReInitCustomButtons()
+{
+ for (auto &cbd : arButtonsList.rev_iter())
+ if (cbd->m_opFlags & (BBSF_NTBSWAPED | BBSF_NTBDESTRUCT)) {
+ cbd->m_opFlags ^= BBSF_NTBSWAPED;
+
+ if (cbd->m_opFlags & BBSF_NTBDESTRUCT)
+ arButtonsList.removeItem(&cbd);
+ }
+
+ qsort(arButtonsList.getArray(), arButtonsList.getCount(), sizeof(void*), sstSortButtons);
+
+ WindowList_Broadcast(g_hWindowList, WM_CBD_RECREATE, 0, 0);
+ WindowList_Broadcast(g_hWindowList, WM_CBD_UPDATED, 0, 0);
+ WindowList_Broadcast(g_hWindowList, WM_CBD_LOADICONS, 0, 0);
+}
+
+static void CB_WriteButtonSettings(MCONTACT hContact, CustomButtonData *cbd)
+{
+ char SettingName[1024];
+ char SettingParameter[1024];
+
+ //modulename_buttonID, position_inIM_inCHAT_isLSide_isRSide_CanBeHidden
+
+ mir_snprintf(SettingName, "%s_%d", cbd->m_pszModuleName.get(), cbd->m_dwButtonID);
+ mir_snprintf(SettingParameter, "%d_%u_%u_%u_%u_%u", cbd->m_dwPosition, cbd->m_bIMButton, cbd->m_bChatButton, 0, cbd->m_bRSided, cbd->m_bCanBeHidden);
+ if (!(cbd->m_opFlags & BBSF_NTBDESTRUCT))
+ db_set_s(hContact, BB_MODULE_NAME, SettingName, SettingParameter);
+ else
+ db_unset(hContact, BB_MODULE_NAME, SettingName);
+}
+
+#define MIDDLE_SEPARATOR L">-------M-------<"
+
+class CSrmmToolbarOptions : public CDlgBase
+{
+ CCtrlTreeView m_toolBar;
+ CCtrlCheck m_btnIM, m_btnChat, m_btnHidden;
+ CCtrlButton m_btnReset, m_btnSeparator;
+ CCtrlSpin m_gap;
+ CTimer timer;
+
+ HIMAGELIST m_hImgl;
+
+ void SaveTree()
+ {
+ bool RSide = false;
+ int count = 10;
+ uint32_t loc_sepcout = 0;
+ wchar_t strbuf[128];
+
+ TVITEMEX tvi;
+ tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_HANDLE;
+ tvi.hItem = m_toolBar.GetRoot();
+ tvi.pszText = strbuf;
+ tvi.cchTextMax = _countof(strbuf);
+ {
+ mir_cslock lck(csToolBar);
+
+ while (tvi.hItem != nullptr) {
+ m_toolBar.GetItem(&tvi);
+
+ if (mir_wstrcmp(tvi.pszText, MIDDLE_SEPARATOR) == 0) {
+ RSide = true;
+ count = m_toolBar.GetCount() * 10 - count;
+ tvi.hItem = m_toolBar.GetNextSibling(tvi.hItem);
+ continue;
+ }
+ CustomButtonData *cbd = (CustomButtonData*)tvi.lParam;
+ if (cbd && arButtonsList.indexOf(cbd) != -1) {
+ if (cbd->m_opFlags) {
+ cbd->m_bIMButton = (cbd->m_opFlags & BBSF_IMBUTTON) != 0;
+ cbd->m_bChatButton = (cbd->m_opFlags & BBSF_CHATBUTTON) != 0;
+ cbd->m_bCanBeHidden = (cbd->m_opFlags & BBSF_CANBEHIDDEN) != 0;
+ }
+
+ if (RSide && !cbd->m_bRSided) {
+ cbd->m_bRSided = true;
+ cbd->m_opFlags |= BBSF_NTBSWAPED;
+ }
+ else if (!RSide && cbd->m_bRSided) {
+ cbd->m_bRSided = false;
+ cbd->m_opFlags |= BBSF_NTBSWAPED;
+ }
+
+ if (!cbd->m_bCantBeHidden && !m_toolBar.GetCheckState(tvi.hItem)) {
+ cbd->m_bIMButton = false;
+ cbd->m_bChatButton = false;
+
+ if (cbd->m_bSeparator && !mir_strcmp(cbd->m_pszModuleName, "Tabsrmm_sep"))
+ cbd->m_opFlags = BBSF_NTBDESTRUCT;
+ }
+ else {
+ if (!cbd->m_bIMButton && !cbd->m_bChatButton)
+ cbd->m_bIMButton = true;
+ if (cbd->m_bSeparator && !mir_strcmp(cbd->m_pszModuleName, "Tabsrmm_sep")) {
+ cbd->m_bHidden = false;
+ cbd->m_opFlags &= ~BBSF_NTBDESTRUCT;
+ ++loc_sepcout;
+ }
+ }
+
+ cbd->m_dwPosition = (uint32_t)count;
+ CB_WriteButtonSettings(0, cbd);
+
+ if (!(cbd->m_opFlags & BBSF_NTBDESTRUCT))
+ (RSide) ? (count -= 10) : (count += 10);
+ }
+
+ HTREEITEM hItem = m_toolBar.GetNextSibling(tvi.hItem);
+ if (cbd->m_opFlags & BBSF_NTBDESTRUCT)
+ m_toolBar.DeleteItem(tvi.hItem);
+ tvi.hItem = hItem;
+ }
+
+ qsort(arButtonsList.getArray(), arButtonsList.getCount(), sizeof(void*), sstSortButtons);
+ }
+ db_set_dw(0, BB_MODULE_NAME, "SeparatorsCount", loc_sepcout);
+ dwSepCount = loc_sepcout;
+ }
+
+ void BuildMenuObjectsTree()
+ {
+ m_toolBar.DeleteAllItems();
+
+ m_hImgl = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 2, 2);
+ ImageList_AddIcon(m_hImgl, Skin_LoadIcon(SKINICON_OTHER_SMALLDOT));
+ ImageList_Destroy(m_toolBar.GetImageList(TVSIL_NORMAL));
+ m_toolBar.SetImageList(m_hImgl, TVSIL_NORMAL);
+
+ if (arButtonsList.getCount() == 0)
+ return;
+
+ bool bPrevSide = false;
+
+ TVINSERTSTRUCT tvis;
+ tvis.hParent = nullptr;
+ tvis.hInsertAfter = TVI_LAST;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE;
+
+ mir_cslock lck(csToolBar);
+ for (auto &cbd : arButtonsList) {
+ if (bPrevSide != cbd->m_bRSided) {
+ bPrevSide = true;
+
+ TVINSERTSTRUCT tvis2 = {};
+ tvis.hInsertAfter = TVI_LAST;
+ tvis2.itemex.mask = TVIF_PARAM | TVIF_TEXT | TVIF_SELECTEDIMAGE | TVIF_IMAGE | TVIF_STATE | TVIF_STATEEX;
+ tvis2.itemex.pszText = MIDDLE_SEPARATOR;
+ tvis2.itemex.stateMask = TVIS_BOLD;
+ tvis2.itemex.state = TVIS_BOLD;
+ tvis2.itemex.iImage = tvis.item.iSelectedImage = -1;
+ tvis2.itemex.uStateEx = TVIS_EX_DISABLED;
+ tvis.hInsertAfter = m_toolBar.InsertItem(&tvis2);
+ m_toolBar.SetItemState(tvis.hInsertAfter, 0x3000, TVIS_STATEIMAGEMASK);
+ }
+
+ tvis.item.lParam = (LPARAM)cbd;
+
+ if (cbd->m_bSeparator) {
+ tvis.item.pszText = TranslateT("<Separator>");
+ tvis.item.iImage = tvis.item.iSelectedImage = 0;
+ }
+ else {
+ tvis.item.pszText = TranslateW(cbd->m_pwszTooltip);
+ tvis.item.iImage = tvis.item.iSelectedImage = ImageList_AddIcon(m_hImgl, IcoLib_GetIconByHandle(cbd->m_hIcon));
+ }
+ cbd->m_opFlags = 0;
+ HTREEITEM hti = m_toolBar.InsertItem(&tvis);
+
+ m_toolBar.SetCheckState(hti, (cbd->m_bIMButton || cbd->m_bChatButton));
+ if (cbd->m_bCantBeHidden)
+ m_toolBar.SetItemState(hti, 0x3000, TVIS_STATEIMAGEMASK);
+ }
+ }
+
+public:
+ CSrmmToolbarOptions() :
+ CDlgBase(g_plugin, IDD_OPT_TOOLBAR),
+ m_gap(this, IDC_SPIN1, 10),
+ m_btnIM(this, IDC_IMCHECK),
+ m_btnChat(this, IDC_CHATCHECK),
+ m_toolBar(this, IDC_TOOLBARTREE),
+ m_btnReset(this, IDC_BBRESET),
+ m_btnHidden(this, IDC_CANBEHIDDEN),
+ m_btnSeparator(this, IDC_SEPARATOR),
+ m_hImgl(nullptr),
+ timer(this, 1)
+ {
+ timer.OnEvent = Callback(this, &CSrmmToolbarOptions::OnTimer);
+
+ m_toolBar.SetFlags(MTREE_DND); // enable drag-n-drop
+ m_toolBar.OnSelChanged = Callback(this, &CSrmmToolbarOptions::OnTreeSelChanged);
+ m_toolBar.OnSelChanging = Callback(this, &CSrmmToolbarOptions::OnTreeSelChanging);
+ m_toolBar.OnItemChanged = Callback(this, &CSrmmToolbarOptions::OnTreeItemChanged);
+
+ m_btnReset.OnClick = Callback(this, &CSrmmToolbarOptions::btnResetClicked);
+ m_btnSeparator.OnClick = Callback(this, &CSrmmToolbarOptions::btnSeparatorClicked);
+ }
+
+ bool OnInitDialog() override
+ {
+ g_pDialog = this;
+ BuildMenuObjectsTree();
+
+ m_btnIM.Disable();
+ m_btnChat.Disable();
+ m_btnHidden.Disable();
+
+ m_gap.SetPosition(g_iButtonGap);
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ g_pDialog = nullptr;
+ ImageList_Destroy(m_toolBar.GetImageList(TVSIL_NORMAL));
+ ImageList_Destroy(m_toolBar.GetImageList(TVSIL_STATE));
+ }
+
+ bool OnApply() override
+ {
+ OnTreeSelChanging(nullptr); // save latest changes
+ SaveTree(); // save the whole tree then
+ CB_ReInitCustomButtons();
+ Chat_UpdateOptions(); // also restore chat windows
+
+ uint16_t newGap = m_gap.GetPosition();
+ if (newGap != g_iButtonGap) {
+ g_iButtonGap = newGap;
+ WindowList_BroadcastAsync(g_hWindowList, WM_SIZE, 0, 0);
+ }
+
+ BuildMenuObjectsTree();
+
+ m_btnIM.Disable();
+ m_btnChat.Disable();
+ m_btnHidden.Disable();
+ return true;
+ }
+
+ virtual void OnReset() override
+ {
+ CB_ReInitCustomButtons();
+ dwSepCount = db_get_dw(0, BB_MODULE_NAME, "SeparatorsCount", 0);
+ }
+
+ void btnResetClicked(void*)
+ {
+ db_delete_module(0, BB_MODULE_NAME);
+
+ Srmm_ResetToolbar();
+ qsort(arButtonsList.getArray(), arButtonsList.getCount(), sizeof(void*), sstSortButtons);
+
+ BuildMenuObjectsTree();
+ NotifyChange();
+ }
+
+ void btnSeparatorClicked(void*)
+ {
+ HTREEITEM hItem = m_toolBar.GetSelection();
+ if (!hItem)
+ hItem = TVI_FIRST;
+
+ BBButton bbd = {};
+ bbd.pszModuleName = "Tabsrmm_sep";
+ bbd.bbbFlags = BBBF_ISSEPARATOR | BBBF_ISIMBUTTON;
+ bbd.dwButtonID = ++dwSepCount;
+
+ CustomButtonData *cbd = (CustomButtonData*)Srmm_AddButton(&bbd, &g_plugin);
+
+ TVINSERTSTRUCT tvis;
+ tvis.hParent = nullptr;
+ tvis.hInsertAfter = hItem;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ tvis.item.pszText = TranslateT("<Separator>");
+ tvis.item.iImage = tvis.item.iSelectedImage = -1;
+ tvis.item.lParam = (LPARAM)cbd;
+ hItem = m_toolBar.InsertItem(&tvis);
+
+ m_toolBar.SetCheckState(hItem, (cbd->m_bIMButton || cbd->m_bChatButton));
+ NotifyChange();
+ }
+
+ void OnTreeSelChanging(void*)
+ {
+ HTREEITEM hItem = m_toolBar.GetSelection();
+ if (hItem == nullptr)
+ return;
+
+ wchar_t strbuf[128];
+ TVITEMEX tvi;
+ tvi.hItem = hItem;
+ tvi.pszText = strbuf;
+ tvi.cchTextMax = _countof(strbuf);
+ tvi.mask = TVIF_TEXT | TVIF_HANDLE | TVIF_PARAM;
+ m_toolBar.GetItem(&tvi);
+
+ if (tvi.lParam == 0 || !m_toolBar.GetCheckState(tvi.hItem) || !mir_wstrcmp(tvi.pszText, MIDDLE_SEPARATOR))
+ return;
+
+ CustomButtonData *cbd = (CustomButtonData*)tvi.lParam;
+ cbd->m_bIMButton = m_btnIM.GetState() != 0;
+ cbd->m_bChatButton = m_btnChat.GetState() != 0;
+ cbd->m_bCanBeHidden = !cbd->m_bCantBeHidden && m_btnHidden.GetState() != 0;
+ cbd->m_opFlags = (cbd->m_bIMButton ? BBSF_IMBUTTON : 0) + (cbd->m_bChatButton ? BBSF_CHATBUTTON : 0) + (cbd->m_bCanBeHidden ? BBSF_CANBEHIDDEN : 0);
+
+ if (!cbd->m_bChatButton && !cbd->m_bIMButton)
+ m_toolBar.SetCheckState(tvi.hItem, 0);
+ }
+
+ void OnTreeSelChanged(void*)
+ {
+ HTREEITEM hItem = m_toolBar.GetSelection();
+ if (hItem == nullptr)
+ return;
+
+ wchar_t strbuf[128];
+ TVITEMEX tvi;
+ tvi.pszText = strbuf;
+ tvi.cchTextMax = _countof(strbuf);
+ tvi.mask = TVIF_TEXT | TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ m_toolBar.GetItem(&tvi);
+
+ if (!m_toolBar.GetCheckState(tvi.hItem) || !mir_wstrcmp(tvi.pszText, MIDDLE_SEPARATOR)) {
+ m_btnIM.Disable();
+ m_btnChat.Disable();
+ m_btnHidden.Disable();
+ return;
+ }
+
+ if (tvi.lParam == 0)
+ return;
+
+ CustomButtonData *cbd = (CustomButtonData*)tvi.lParam;
+ m_btnIM.Enable(); m_btnIM.SetState(cbd->m_bIMButton);
+ m_btnChat.Enable(); m_btnChat.SetState(cbd->m_bChatButton);
+ m_btnHidden.Enable(); m_btnHidden.SetState(cbd->m_bCanBeHidden);
+ }
+
+ void OnTreeItemChanged(CCtrlTreeView::TEventInfo *evt)
+ {
+ bool iNewState = !m_toolBar.GetCheckState(evt->hItem);
+ m_btnIM.Enable(iNewState);
+ m_btnChat.Enable(iNewState);
+ m_btnHidden.Enable(iNewState);
+ if (iNewState)
+ m_btnIM.SetState(true);
+ }
+
+ void OnTimer(CTimer *pTimer)
+ {
+ pTimer->Stop();
+ BuildMenuObjectsTree();
+ }
+
+ static void RereadButtons()
+ {
+ if (g_pDialog)
+ g_pDialog->timer.Start(100);
+ }
+};
+
+void SrmmLogOptionsInit(WPARAM wParam);
+
+static int SrmmOptionsInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.position = 910000000;
+ odp.szGroup.a = LPGEN("Message sessions");
+ odp.szTitle.a = LPGEN("Toolbar");
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.pDialog = new CSrmmToolbarOptions();
+ g_plugin.addOptions(wParam, &odp);
+
+ ChatOptionsInit(wParam);
+ SrmmLogOptionsInit(wParam);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(HANDLE) Srmm_AddButton(const BBButton *bbdi, HPLUGIN _hLang)
+{
+ if (bbdi == nullptr)
+ return nullptr;
+
+ CustomButtonData *cbd = new CustomButtonData();
+ cbd->m_pszModuleName = mir_strdup(bbdi->pszModuleName);
+ cbd->m_pwszText = mir_wstrdup(bbdi->pwszText);
+ cbd->m_pwszTooltip = mir_wstrdup(bbdi->pwszTooltip);
+
+ cbd->m_dwButtonID = bbdi->dwButtonID;
+ cbd->m_hIcon = bbdi->hIcon;
+ cbd->m_dwPosition = cbd->m_dwOrigPosition = bbdi->dwDefPos;
+ cbd->m_dwButtonCID = (bbdi->bbbFlags & BBBF_CREATEBYID) ? bbdi->dwButtonID : LastCID;
+ cbd->m_dwArrowCID = (bbdi->bbbFlags & BBBF_ISARROWBUTTON) ? cbd->m_dwButtonCID + 1 : 0;
+ cbd->m_bHidden = (bbdi->bbbFlags & BBBF_HIDDEN) != 0;
+ cbd->m_bSeparator = (bbdi->bbbFlags & BBBF_ISSEPARATOR) != 0;
+
+ cbd->m_bDisabled = (bbdi->bbbFlags & BBBF_DISABLED) != 0;
+ cbd->m_bPushButton = (bbdi->bbbFlags & BBBF_ISPUSHBUTTON) != 0;
+ cbd->m_pPlugin = _hLang;
+
+ cbd->m_dwOrigFlags.bit1 = cbd->m_bRSided = (bbdi->bbbFlags & BBBF_ISRSIDEBUTTON) != 0;
+ cbd->m_dwOrigFlags.bit2 = cbd->m_bIMButton = (bbdi->bbbFlags & BBBF_ISIMBUTTON) != 0;
+ cbd->m_dwOrigFlags.bit3 = cbd->m_bChatButton = (bbdi->bbbFlags & BBBF_ISCHATBUTTON) != 0;
+ cbd->m_dwOrigFlags.bit4 = cbd->m_bCanBeHidden = (bbdi->bbbFlags & BBBF_CANBEHIDDEN) != 0;
+
+ if (cbd->m_bSeparator)
+ cbd->m_iButtonWidth = DPISCALEX_S(10);
+ else if (bbdi->bbbFlags & BBBF_ISARROWBUTTON)
+ cbd->m_iButtonWidth = DPISCALEX_S(34);
+ else
+ cbd->m_iButtonWidth = DPISCALEX_S(22);
+
+ if (bbdi->pszHotkey) {
+ for (auto &p : hotkeys) {
+ if (!mir_strcmp(p->getName(), bbdi->pszHotkey)) {
+ cbd->m_hotkey = p;
+ break;
+ }
+ }
+ }
+
+ // download database settings
+ char SettingName[1024];
+ mir_snprintf(SettingName, "%s_%d", cbd->m_pszModuleName.get(), cbd->m_dwButtonID);
+
+ DBVARIANT dbv = { 0 };
+ if (!db_get_s(0, BB_MODULE_NAME, SettingName, &dbv)) {
+ // modulename_buttonID, position_inIM_inCHAT_isLSide_isRSide_CanBeHidden
+ char *token = strtok(dbv.pszVal, "_");
+ cbd->m_dwPosition = (uint32_t)atoi(token);
+ token = strtok(nullptr, "_");
+ cbd->m_bIMButton = atoi(token) != 0;
+ token = strtok(nullptr, "_");
+ cbd->m_bChatButton = atoi(token) != 0;
+ token = strtok(nullptr, "_");
+ token = strtok(nullptr, "_");
+ cbd->m_bRSided = atoi(token) != 0;
+ token = strtok(nullptr, "_");
+ cbd->m_bCanBeHidden = atoi(token) != 0;
+
+ db_free(&dbv);
+ }
+
+ arButtonsList.insert(cbd);
+
+ if (cbd->m_dwButtonCID != cbd->m_dwButtonID)
+ LastCID++;
+ if (cbd->m_dwArrowCID == LastCID)
+ LastCID++;
+
+ WindowList_Broadcast(g_hWindowList, WM_CBD_UPDATED, 0, 0);
+ CSrmmToolbarOptions::RereadButtons();
+ return cbd;
+}
+
+MIR_APP_DLL(int) Srmm_RemoveButton(BBButton *bbdi)
+{
+ if (!bbdi)
+ return 1;
+
+ CustomButtonData *pFound = nullptr;
+ {
+ mir_cslock lck(csToolBar);
+
+ for (auto &cbd : arButtonsList.rev_iter())
+ if (!mir_strcmp(cbd->m_pszModuleName, bbdi->pszModuleName) && cbd->m_dwButtonID == bbdi->dwButtonID)
+ pFound = arButtonsList.removeItem(&cbd);
+ }
+
+ if (pFound) {
+ CSrmmToolbarOptions::RereadButtons();
+ WindowList_Broadcast(g_hWindowList, WM_CBD_REMOVED, pFound->m_dwButtonCID, (LPARAM)pFound);
+ delete pFound;
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void KillModuleToolbarIcons(CMPluginBase *pPlugin)
+{
+ int oldCount = arButtonsList.getCount();
+
+ auto T = arButtonsList.rev_iter();
+ for (auto &cbd : T)
+ if (cbd->m_pPlugin == pPlugin)
+ delete arButtonsList.removeItem(&cbd);
+
+ if (oldCount != arButtonsList.getCount())
+ CSrmmToolbarOptions::RereadButtons();
+}
+
+static INT_PTR BroadcastMessage(WPARAM, LPARAM lParam)
+{
+ Srmm_Broadcast((UINT)lParam, 0, 0);
+ return 0;
+}
+
+static void CALLBACK SrmmLoadToolbar()
+{
+ NotifyEventHooks(hHookToolBarLoadedEvt, 0, 0);
+ DestroyHookableEvent(hHookToolBarLoadedEvt);
+
+ HookEvent(ME_OPT_INITIALISE, SrmmOptionsInit);
+}
+
+static int ConvertToolbarData(const char *szSetting, void*)
+{
+ DBVARIANT dbv;
+ if (!db_get(0, "Tab" BB_MODULE_NAME, szSetting, &dbv)) {
+ db_set(0, BB_MODULE_NAME, szSetting, &dbv);
+ db_free(&dbv);
+ }
+ return 0;
+}
+
+void LoadSrmmToolbarModule()
+{
+ CreateServiceFunction("SRMsg/BroadcastMessage", BroadcastMessage);
+
+ Miranda_WaitOnHandle(SrmmLoadToolbar);
+
+ hHookButtonPressedEvt = CreateHookableEvent(ME_MSG_BUTTONPRESSED);
+ hHookToolBarLoadedEvt = CreateHookableEvent(ME_MSG_TOOLBARLOADED);
+
+ HDC hScrnDC = GetDC(nullptr);
+ g_DPIscaleX = GetDeviceCaps(hScrnDC, LOGPIXELSX) / 96.0;
+ g_DPIscaleY = GetDeviceCaps(hScrnDC, LOGPIXELSY) / 96.0;
+ ReleaseDC(nullptr, hScrnDC);
+
+ // old data? convert them
+ if (db_get_dw(0, "Tab" BB_MODULE_NAME, "SeparatorsCount", -1) != -1) {
+ db_enum_settings(0, ConvertToolbarData, "Tab" BB_MODULE_NAME, nullptr);
+ db_delete_module(0, "Tab" BB_MODULE_NAME);
+ }
+
+ dwSepCount = db_get_dw(0, BB_MODULE_NAME, "SeparatorsCount", 0);
+ CB_RegisterSeparators();
+}
+
+void UnloadSrmmToolbarModule()
+{
+ DestroyHookableEvent(hHookButtonPressedEvt);
+
+ for (auto &it : arButtonsList)
+ delete it;
+ arButtonsList.destroy();
+}
diff --git a/src/mir_app/src/srmm_util.cpp b/src/mir_app/src/srmm_util.cpp index 4f66b9ffd2..2ca887a54a 100644 --- a/src/mir_app/src/srmm_util.cpp +++ b/src/mir_app/src/srmm_util.cpp @@ -1,147 +1,147 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" -#include "chat.h" - -const char *g_pszHotkeySection; - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(DWORD) CALLBACK Srmm_LogStreamCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb) -{ - LOGSTREAMDATA *lstrdat = (LOGSTREAMDATA*)dwCookie; - if (lstrdat) { - // create the RTF - if (lstrdat->buffer == nullptr) { - lstrdat->bufferOffset = 0; - lstrdat->buffer = g_chatApi.Log_CreateRTF(lstrdat); - lstrdat->bufferLen = (int)mir_strlen(lstrdat->buffer); - } - - // give the RTF to the RE control - *pcb = min(cb, LONG(lstrdat->bufferLen - lstrdat->bufferOffset)); - memcpy(pbBuff, lstrdat->buffer + lstrdat->bufferOffset, *pcb); - lstrdat->bufferOffset += *pcb; - - // free stuff if the streaming operation is complete - if (lstrdat->bufferOffset == lstrdat->bufferLen) { - mir_free(lstrdat->buffer); - lstrdat->buffer = nullptr; - } - } - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(int) Srmm_GetWindowData(MCONTACT hContact, MessageWindowData &mwd) -{ - if (hContact == 0) - return 1; - - HWND hwnd = WindowList_Find(g_hWindowList, hContact); - if (hwnd == nullptr) - return 1; - - mwd.hwndWindow = hwnd; - mwd.pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(hwnd, GWLP_USERDATA); - mwd.uState = MSG_WINDOW_STATE_EXISTS; - if (IsWindowVisible(hwnd)) - mwd.uState |= MSG_WINDOW_STATE_VISIBLE; - if (GetForegroundWindow() == hwnd) - mwd.uState |= MSG_WINDOW_STATE_FOCUS; - if (IsIconic(hwnd)) - mwd.uState |= MSG_WINDOW_STATE_ICONIC; - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(void) Srmm_Broadcast(UINT msg, WPARAM wParam, LPARAM lParam) -{ - WindowList_Broadcast(g_hWindowList, msg, wParam, lParam); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(void) Srmm_CreateHotkey(const char *pszSection, const char *pszDescription) -{ - g_pszHotkeySection = pszSection; - - uint16_t wHotKey = HOTKEYCODE(0, VK_RETURN); - if (db_get_b(0, SRMM_MODULE, "SendOnCtrlEnter")) - wHotKey = HOTKEYCODE(HOTKEYF_CONTROL, VK_RETURN); - - if (db_get_b(0, "Tab_SRMsg", "sendonshiftenter")) - wHotKey = HOTKEYCODE(HOTKEYF_SHIFT, VK_RETURN); - - HOTKEYDESC hd = { "tabsrmm_send", pszDescription, pszSection, 0, wHotKey, 0, 100 }; - Hotkey_Register(&hd, g_pChatPlugin); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -MIR_APP_DLL(HWND) Srmm_FindWindow(MCONTACT hContact) -{ - return WindowList_Find(g_hWindowList, hContact); -} - -MIR_APP_DLL(CMsgDialog*) Srmm_FindDialog(MCONTACT hContact) -{ - HWND hwnd = Srmm_FindWindow(hContact); - return (hwnd) ? (CMsgDialog *)GetWindowLongPtr(hwnd, GWLP_USERDATA) : nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// serializes all thread-unsafe operation to the first thread - -struct SSTParam -{ - HWND hwnd; - const wchar_t *wszText; - HICON hIcon; -}; - -static INT_PTR CALLBACK sttSetStatusText(void *_p) -{ - SSTParam *param = (SSTParam*)_p; - - CSrmmBaseDialog *pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(param->hwnd, GWLP_USERDATA); - if (pDlg != nullptr) - pDlg->SetStatusText(param->wszText, param->hIcon); - return 0; -} - -MIR_APP_DLL(void) Srmm_SetStatusText(MCONTACT hContact, const wchar_t *wszText, HICON hIcon) -{ - HWND hwnd = Srmm_FindWindow(hContact); - if (hwnd == nullptr) - hwnd = Srmm_FindWindow(db_mc_getMeta(hContact)); - if (hwnd == nullptr) - return; - - SSTParam param = { hwnd, wszText, hIcon }; - CallFunctionSync(sttSetStatusText, ¶m); -} +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+#include "chat.h"
+
+const char *g_pszHotkeySection;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(DWORD) CALLBACK Srmm_LogStreamCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, LONG cb, LONG * pcb)
+{
+ LOGSTREAMDATA *lstrdat = (LOGSTREAMDATA*)dwCookie;
+ if (lstrdat) {
+ // create the RTF
+ if (lstrdat->buffer == nullptr) {
+ lstrdat->bufferOffset = 0;
+ lstrdat->buffer = g_chatApi.Log_CreateRTF(lstrdat);
+ lstrdat->bufferLen = (int)mir_strlen(lstrdat->buffer);
+ }
+
+ // give the RTF to the RE control
+ *pcb = min(cb, LONG(lstrdat->bufferLen - lstrdat->bufferOffset));
+ memcpy(pbBuff, lstrdat->buffer + lstrdat->bufferOffset, *pcb);
+ lstrdat->bufferOffset += *pcb;
+
+ // free stuff if the streaming operation is complete
+ if (lstrdat->bufferOffset == lstrdat->bufferLen) {
+ mir_free(lstrdat->buffer);
+ lstrdat->buffer = nullptr;
+ }
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(int) Srmm_GetWindowData(MCONTACT hContact, MessageWindowData &mwd)
+{
+ if (hContact == 0)
+ return 1;
+
+ HWND hwnd = WindowList_Find(g_hWindowList, hContact);
+ if (hwnd == nullptr)
+ return 1;
+
+ mwd.hwndWindow = hwnd;
+ mwd.pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+ mwd.uState = MSG_WINDOW_STATE_EXISTS;
+ if (IsWindowVisible(hwnd))
+ mwd.uState |= MSG_WINDOW_STATE_VISIBLE;
+ if (GetForegroundWindow() == hwnd)
+ mwd.uState |= MSG_WINDOW_STATE_FOCUS;
+ if (IsIconic(hwnd))
+ mwd.uState |= MSG_WINDOW_STATE_ICONIC;
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(void) Srmm_Broadcast(UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ WindowList_Broadcast(g_hWindowList, msg, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(void) Srmm_CreateHotkey(const char *pszSection, const char *pszDescription)
+{
+ g_pszHotkeySection = pszSection;
+
+ uint16_t wHotKey = HOTKEYCODE(0, VK_RETURN);
+ if (db_get_b(0, SRMM_MODULE, "SendOnCtrlEnter"))
+ wHotKey = HOTKEYCODE(HOTKEYF_CONTROL, VK_RETURN);
+
+ if (db_get_b(0, "Tab_SRMsg", "sendonshiftenter"))
+ wHotKey = HOTKEYCODE(HOTKEYF_SHIFT, VK_RETURN);
+
+ HOTKEYDESC hd = { "tabsrmm_send", pszDescription, pszSection, 0, wHotKey, 0, 100 };
+ Hotkey_Register(&hd, g_pChatPlugin);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+MIR_APP_DLL(HWND) Srmm_FindWindow(MCONTACT hContact)
+{
+ return WindowList_Find(g_hWindowList, hContact);
+}
+
+MIR_APP_DLL(CMsgDialog*) Srmm_FindDialog(MCONTACT hContact)
+{
+ HWND hwnd = Srmm_FindWindow(hContact);
+ return (hwnd) ? (CMsgDialog *)GetWindowLongPtr(hwnd, GWLP_USERDATA) : nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// serializes all thread-unsafe operation to the first thread
+
+struct SSTParam
+{
+ HWND hwnd;
+ const wchar_t *wszText;
+ HICON hIcon;
+};
+
+static INT_PTR CALLBACK sttSetStatusText(void *_p)
+{
+ SSTParam *param = (SSTParam*)_p;
+
+ CSrmmBaseDialog *pDlg = (CSrmmBaseDialog*)GetWindowLongPtr(param->hwnd, GWLP_USERDATA);
+ if (pDlg != nullptr)
+ pDlg->SetStatusText(param->wszText, param->hIcon);
+ return 0;
+}
+
+MIR_APP_DLL(void) Srmm_SetStatusText(MCONTACT hContact, const wchar_t *wszText, HICON hIcon)
+{
+ HWND hwnd = Srmm_FindWindow(hContact);
+ if (hwnd == nullptr)
+ hwnd = Srmm_FindWindow(db_mc_getMeta(hContact));
+ if (hwnd == nullptr)
+ return;
+
+ SSTParam param = { hwnd, wszText, hIcon };
+ CallFunctionSync(sttSetStatusText, ¶m);
+}
diff --git a/src/mir_app/src/stdafx.cxx b/src/mir_app/src/stdafx.cxx index 52ec2d6817..e23069a5b8 100644 --- a/src/mir_app/src/stdafx.cxx +++ b/src/mir_app/src/stdafx.cxx @@ -1,6 +1,6 @@ /*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org)
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
diff --git a/src/mir_app/src/stdafx.h b/src/mir_app/src/stdafx.h index e40748c95a..cbb17fac19 100644 --- a/src/mir_app/src/stdafx.h +++ b/src/mir_app/src/stdafx.h @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/usedIcons.cpp b/src/mir_app/src/usedIcons.cpp index dec0672025..fc3274aa60 100644 --- a/src/mir_app/src/usedIcons.cpp +++ b/src/mir_app/src/usedIcons.cpp @@ -1,7 +1,7 @@ /*
Copyright (C) 2009 Ricardo Pescuma Domenecci
-Copyright (C) 2012-22 Miranda NG team
+Copyright (C) 2012-23 Miranda NG team
This is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
diff --git a/src/mir_app/src/usedIcons.h b/src/mir_app/src/usedIcons.h index 62c5539bd7..699f30ea7f 100644 --- a/src/mir_app/src/usedIcons.h +++ b/src/mir_app/src/usedIcons.h @@ -1,7 +1,7 @@ /*
Copyright (C) 2009 Ricardo Pescuma Domenecci
-Copyright (C) 2012-22 Miranda NG team
+Copyright (C) 2012-23 Miranda NG team
This is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
diff --git a/src/mir_app/src/userInfo.cpp b/src/mir_app/src/userInfo.cpp index deffc648a3..196ea0ae0a 100644 --- a/src/mir_app/src/userInfo.cpp +++ b/src/mir_app/src/userInfo.cpp @@ -1,45 +1,45 @@ -/* - -Miranda NG: the free IM client for Microsoft* Windows* - -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org), -Copyright (c) 2000-12 Miranda IM project, -all portions of this codebase are copyrighted to the people -listed in contributors.txt. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -*/ - -#include "stdafx.h" - -///////////////////////////////////////////////////////////////////////////////////////// -// User info page base dialog - -CUserInfoPageDlg::CUserInfoPageDlg(class CMPluginBase &pPlug, int idDialog) : - CDlgBase(pPlug, idDialog) -{ - m_forceResizable = true; -} - -bool CUserInfoPageDlg::IsEmpty() const -{ - return false; -} - -bool CUserInfoPageDlg::OnRefresh() -{ - return false; -} - +/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
+Copyright (c) 2000-12 Miranda IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// User info page base dialog
+
+CUserInfoPageDlg::CUserInfoPageDlg(class CMPluginBase &pPlug, int idDialog) :
+ CDlgBase(pPlug, idDialog)
+{
+ m_forceResizable = true;
+}
+
+bool CUserInfoPageDlg::IsEmpty() const
+{
+ return false;
+}
+
+bool CUserInfoPageDlg::OnRefresh()
+{
+ return false;
+}
+
diff --git a/src/mir_app/src/utils.cpp b/src/mir_app/src/utils.cpp index 36260b3905..42e916d7d7 100644 --- a/src/mir_app/src/utils.cpp +++ b/src/mir_app/src/utils.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
diff --git a/src/mir_app/src/visibility.cpp b/src/mir_app/src/visibility.cpp index 1aaa1421a5..8c116a2497 100644 --- a/src/mir_app/src/visibility.cpp +++ b/src/mir_app/src/visibility.cpp @@ -2,7 +2,7 @@ Miranda NG: the free IM client for Microsoft* Windows*
-Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org),
+Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org),
Copyright (c) 2000-12 Miranda IM project,
all portions of this codebase are copyrighted to the people
listed in contributors.txt.
|