Index: src/engine/NotificationWindow.cpp |
diff --git a/src/engine/NotificationWindow.cpp b/src/engine/NotificationWindow.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a7365e0ef1491879d15aa4534ff69cdbd24509e6 |
--- /dev/null |
+++ b/src/engine/NotificationWindow.cpp |
@@ -0,0 +1,293 @@ |
+#include <cassert> |
+#include "NotificationWindow.h" |
+#include "../shared/Utils.h" |
+#include <algorithm> |
+#include <fstream> |
+#include "../shared/MsHTMLUtils.h" |
+ |
+// it is taken from src/plugin/Resource.h |
+#define IDI_ICON_ENABLED 301 |
+ |
+namespace { |
+ // DIP = device independant pixels, as DPI is 96 |
+ const uint32_t kWindowWidth = 400 /*DIP*/; |
+ const uint32_t kWindowHeight = 120 /*DIP*/; |
+ const uint32_t kIconSize = 64 /*DIP*/; |
+ |
+ // offsets from boundaries of working area of screen |
+ const uint32_t kWindowMarginRight = 50 /*DIP*/; |
+ const uint32_t kWindowMarginBottom = 20 /*DIP*/; |
+ |
+ std::vector<uint16_t> iconSizes = []()->std::vector<uint16_t> |
+ { |
+ std::vector<uint16_t> iconSizes; |
+ iconSizes.emplace_back(16); |
+ iconSizes.emplace_back(19); |
+ iconSizes.emplace_back(48); |
+ iconSizes.emplace_back(128); |
+ iconSizes.emplace_back(256); |
+ return iconSizes; |
+ }(); |
+ |
+ class DCHandle |
+ { |
+ public: |
+ explicit DCHandle(HDC hDC = nullptr) : m_hDC(hDC) |
+ { |
+ } |
+ |
+ ~DCHandle() |
+ { |
+ if(m_hDC != nullptr) |
+ { |
+ ::DeleteDC(m_hDC); |
+ m_hDC = nullptr; |
+ } |
+ } |
+ |
+ operator HDC() |
+ { |
+ return m_hDC; |
+ } |
+ |
+ int GetDeviceCaps(int nIndex) const |
+ { |
+ ATLASSERT(m_hDC != nullptr); |
+ return ::GetDeviceCaps(m_hDC, nIndex); |
+ } |
+ |
+ HBITMAP SelectBitmap(HBITMAP value) |
+ { |
+ return static_cast<HBITMAP>(::SelectObject(m_hDC, value)); |
+ } |
+ private: |
+ DCHandle(const DCHandle&); |
+ DCHandle& operator=(const DCHandle&); |
+ private: |
+ HDC m_hDC; |
+ }; |
+ |
+ /// Case insensitive pattern replacing function. |
+ /// ReplaceMulti("Some TeXt <PlaceHolder>Some link</A> and text", "<placeholder>", ->"<a>")-> |
+ /// "Some TeXt <a>Some link</A> and text". |
+ std::wstring ReplaceMulti(const std::wstring& src, std::wstring placeholder, const std::function<std::wstring()>& replacementGenerator) |
+ { |
+ std::transform(placeholder.begin(), placeholder.end(), placeholder.begin(), ::towlower); |
+ std::wstring srcLowerCase = src; |
+ std::transform(srcLowerCase.begin(), srcLowerCase.end(), srcLowerCase.begin(), ::towlower); |
+ std::wstring retValue; |
+ std::wstring::size_type placeHolderOffset = 0; |
+ std::wstring::size_type nextStringChunkOffset = 0; |
+ while ((placeHolderOffset = srcLowerCase.find(placeholder, nextStringChunkOffset)) != std::wstring::npos) |
+ { |
+ retValue.append(src.substr(nextStringChunkOffset, placeHolderOffset - nextStringChunkOffset)); |
+ retValue.append(replacementGenerator()); |
+ nextStringChunkOffset = placeHolderOffset + placeholder.length(); |
+ } |
+ retValue.append(src.substr(nextStringChunkOffset)); |
+ return retValue; |
+ } |
+ std::wstring ReplaceMulti(const std::wstring& workingString, const std::wstring& templ, const std::wstring& replacement) |
+ { |
+ return ReplaceMulti(workingString, templ, [&replacement]()->std::wstring |
+ { |
+ return replacement; |
+ }); |
+ } |
+} |
+ |
+NotificationWindow::NotificationWindow(const AdblockPlus::Notification& notification, const std::wstring& htmlFileDir) |
+ : m_dpi(96) |
+{ |
+ const std::wstring filePath = htmlFileDir + L"NotificationWindow.html"; |
+ std::wifstream ifs(filePath); |
+ assert(ifs.good() && "Cannot open NotificationWindow.html file"); |
+ if (!ifs.good()) |
+ { |
+ throw std::runtime_error("Cannot read NotificationWindow.html"); |
+ } |
+ m_htmlPage.assign((std::istreambuf_iterator<wchar_t>(ifs)), std::istreambuf_iterator<wchar_t>()); |
+ |
+ m_links = ToUtf16Strings(notification.GetLinks()); |
+ auto body = ToUtf16String(notification.GetMessageString()); |
+ uint32_t linkIDCounter = 0; |
+ body = ReplaceMulti(body, L"<a>", [this, &linkIDCounter]()->std::wstring |
+ { |
+ return L"<a href=\"#\" data-linkID=\"" + std::to_wstring(linkIDCounter++) + L"\">"; |
+ }); |
+ assert(linkIDCounter == m_links.size() && "The amount of links in the text is different from the amount of provided links"); |
+ m_htmlPage = ReplaceMulti(m_htmlPage, L"<!--Title-->", ToUtf16String(notification.GetTitle())); |
+ m_htmlPage = ReplaceMulti(m_htmlPage, L"<!--Body-->", body); |
+} |
+ |
+NotificationWindow::~NotificationWindow() |
+{ |
+} |
+ |
+LRESULT NotificationWindow::OnCreate(const CREATESTRUCT* /*createStruct*/) { |
+ m_bgColor.CreateSolidBrush(RGB(255, 255, 255)); |
+ auto windowCoords = GetWindowCoordinates(); |
+ MoveWindow(windowCoords.x, windowCoords.y, DPIAware(kWindowWidth), DPIAware(kWindowHeight)); |
+ |
+ m_icon.Create(m_hWnd, |
+ DPIAware(CRect(CPoint(0, 0), CSize(kIconSize, kIconSize))), |
+ nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP | SS_CENTERIMAGE); |
+ LoadABPIcon(); |
+ |
+ m_axIE.Create(m_hWnd, DPIAware(CRect(CPoint(kIconSize, 0), CSize(kWindowWidth - kIconSize, kWindowHeight))), |
+ L"", WS_CHILD | WS_VISIBLE, 0, kHTMLDocumentCtrlID); |
+ m_axIE.CreateControl((L"mshtml:" + m_htmlPage).c_str()); |
+ ATL::CComPtr<IAxWinAmbientDispatch> axWinAmbient; |
+ if (SUCCEEDED(m_axIE.QueryHost(&axWinAmbient))) { |
+ // disable web browser context menu |
+ axWinAmbient->put_AllowContextMenu(VARIANT_FALSE); |
+ // make web browser DPI aware, so the browser itself sets zoom level and |
+ // cares about rendering (not zooming) in the proper size. |
+ DWORD docFlags; |
+ axWinAmbient->get_DocHostFlags(&docFlags); |
+ docFlags |= DOCHOSTUIFLAG_DPI_AWARE; |
+ // remove DOCHOSTUIFLAG_SCROLL_NO, so it's scrollable |
+ docFlags &= ~DOCHOSTUIFLAG_SCROLL_NO; |
+ axWinAmbient->put_DocHostFlags(docFlags); |
+ } |
+ // kHTMLDocumentCtrlID works here |
+ AtlAdviseSinkMap(this, true); |
+ |
+ SetMsgHandled(false); |
+ return 0; |
+} |
+ |
+LRESULT NotificationWindow::OnCtlColor(UINT /*msg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& handled) |
+{ |
+ if (reinterpret_cast<HWND>(lParam) != m_icon) |
+ { |
+ handled = FALSE; |
+ } |
+ return reinterpret_cast<LRESULT>(static_cast<HBRUSH>(m_bgColor)); |
+} |
+ |
+LRESULT NotificationWindow::OnClick(UINT /*msg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*handled*/) |
+{ |
+ DestroyWindow(); |
+ return 0; |
+} |
+ |
+void NotificationWindow::OnSize(uint32_t wParam, CSize size) |
+{ |
+ if (m_axIE) |
+ { |
+ size.cx -= kIconSize; |
+ CRect rect(CPoint(0, 0), size); |
+ m_axIE.SetWindowPos(0, &rect, SWP_NOMOVE); |
+ } |
+ SetMsgHandled(false); |
+} |
+ |
+void NotificationWindow::OnDestroy() |
+{ |
+ AtlAdviseSinkMap(this, false); |
+ // and proceed as usual |
+ SetMsgHandled(false); |
+} |
+ |
+void __stdcall NotificationWindow::OnHTMLDocumentClick(IHTMLEventObj* eventObject) |
+{ |
+ // stop propagating the event since it's handled by us and should not cause any other actions. |
+ if (!eventObject) |
+ return; |
+ eventObject->put_cancelBubble(VARIANT_TRUE); |
+ eventObject->put_returnValue(ATL::CComVariant(false)); |
+ ATL::CComPtr<IHTMLElement> htmlElement; |
+ if (FAILED(eventObject->get_srcElement(&htmlElement)) || !htmlElement) { |
+ return; |
+ } |
+ ATL::CComBSTR tag; |
+ htmlElement->get_tagName(&tag); |
+ const wchar_t expectedTag[] = { L"a" }; |
+ if (_wcsnicmp(tag, expectedTag, min(sizeof(expectedTag), tag.Length())) != 0) { |
+ return; |
+ } |
+ auto classAttr = GetHtmlElementAttribute(*htmlElement, ATL::CComBSTR(L"class")); |
+ if (classAttr.attributeValue == L"closeButton") |
+ { |
+ PostMessage(WM_CLOSE); |
+ return; |
+ } |
+ if (!linkClicked) |
+ { |
+ return; |
+ } |
+ auto linkIDAttr = GetHtmlElementAttribute(*htmlElement, ATL::CComBSTR(L"data-linkID")); |
+ uint32_t linkID = 0; |
+ if (!linkIDAttr.attributeValue.empty() && (linkID = std::stoi(linkIDAttr.attributeValue)) < m_links.size()) |
+ { |
+ linkClicked(m_links[linkID]); |
+ PostMessage(WM_CLOSE); |
+ } |
+} |
+ |
+void __stdcall NotificationWindow::OnHTMLDocumentSelectStart(IHTMLEventObj* eventObject) |
+{ |
+ if (!eventObject) |
+ return; |
+ // disable selecting |
+ eventObject->put_cancelBubble(VARIANT_TRUE); |
+ eventObject->put_returnValue(ATL::CComVariant(false)); |
+} |
+ |
+void NotificationWindow::OnFinalMessage(HWND) { |
+ if (!!destroyed) { |
+ destroyed(); |
+ } |
+} |
+ |
+void NotificationWindow::LoadABPIcon() |
+{ |
+ ScopedModule m_adblockPlusDLL; |
+ if (!(m_adblockPlusDLL.Open(L"AdblockPlus32.dll", LOAD_LIBRARY_AS_DATAFILE) || |
+ m_adblockPlusDLL.Open(L"AdblockPlus64.dll", LOAD_LIBRARY_AS_DATAFILE) || |
+ // for debug |
+ m_adblockPlusDLL.Open(L"AdblockPlus.dll", LOAD_LIBRARY_AS_DATAFILE))) |
+ { |
+ return; |
+ } |
+ auto iconSizeIterator = lower_bound(iconSizes.begin(), iconSizes.end(), DPIAware(kIconSize)); |
+ if (iconSizeIterator == iconSizes.end()) |
+ { |
+ iconSizeIterator = iconSizes.rbegin().base(); |
+ } |
+ |
+ auto hIcon = static_cast<HICON>(::LoadImageW(m_adblockPlusDLL, (L"#" + std::to_wstring(IDI_ICON_ENABLED)).c_str(), |
+ IMAGE_ICON, *iconSizeIterator, *iconSizeIterator, LR_SHARED)); |
+ if (hIcon == nullptr) |
+ { |
+ return; |
+ } |
+ |
+ HDC screenDC = m_icon.GetDC(); |
+ DCHandle tmpDC(::CreateCompatibleDC(screenDC)); |
+ ScopedObjectHandle<HBITMAP> bitmap; |
+ bitmap = CreateCompatibleBitmap(screenDC, DPIAware(kIconSize), DPIAware(kIconSize)); |
+ HBITMAP prevBitmap = tmpDC.SelectBitmap(bitmap); |
+ RECT tmpRect = {0, 0, DPIAware(kIconSize), DPIAware(kIconSize)}; |
+ FillRect(tmpDC, &tmpRect, m_bgColor); |
+ ::DrawIconEx(tmpDC, 0, 0, hIcon, DPIAware(kIconSize), DPIAware(kIconSize), 0, nullptr, DI_NORMAL); |
+ m_iconImg.Attach(bitmap.Detach()); |
+ tmpDC.SelectBitmap(prevBitmap); |
+ m_icon.SetBitmap(m_iconImg); |
+} |
+ |
+POINT NotificationWindow::GetWindowCoordinates() { |
+ HMONITOR primaryMonitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTOPRIMARY); |
+ { |
+ DCHandle hdc(GetDC()); |
+ m_dpi = hdc.GetDeviceCaps(LOGPIXELSX); |
+ } |
+ MONITORINFO monitorInfo = {}; |
+ monitorInfo.cbSize = sizeof(monitorInfo); |
+ GetMonitorInfo(primaryMonitor, &monitorInfo); |
+ int windowX = monitorInfo.rcWork.right - DPIAware(kWindowWidth + kWindowMarginRight); |
+ int windowY = monitorInfo.rcWork.bottom - DPIAware(kWindowHeight + kWindowMarginBottom); |
+ POINT coords = {windowX, windowY}; |
+ return coords; |
+} |