/* Copyright (C) 2004 Kimmo Pekkola 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "StdAfx.h" #include "TrayWindow.h" #include "Measure.h" #include "resource.h" #include "Litestep.h" #include "Rainmeter.h" #include "DialogAbout.h" #include "DialogManage.h" #include "System.h" #include "Error.h" #include "RainmeterQuery.h" #include "resource.h" #include "../Version.h" #define RAINMETER_OFFICIAL L"http://rainmeter.net/cms/" #define RAINMETER_HELP L"http://docs.rainmeter.net/" #define ZPOS_FLAGS (SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSENDCHANGING) enum TIMER { TIMER_ADDTRAYICON = 1, TIMER_TRAYMEASURE = 3 }; enum INTERVAL { INTERVAL_ADDTRAYICON = 3000, INTERVAL_TRAYMEASURE = 1000 }; const UINT WM_TASKBARCREATED = ::RegisterWindowMessage(L"TaskbarCreated"); using namespace Gdiplus; TrayWindow::TrayWindow() : m_Icon(), m_Measure(), m_MeterType(TRAY_METER_TYPE_HISTOGRAM), m_Color1(0, 100, 0), m_Color2(0, 255, 0), m_Bitmap(), m_Values(), m_Pos(), m_Notification(TRAY_NOTIFICATION_NONE), m_TrayContextMenuEnabled(true), m_IconEnabled(true) { } TrayWindow::~TrayWindow() { KillTimer(m_Window, TIMER_ADDTRAYICON); KillTimer(m_Window, TIMER_TRAYMEASURE); RemoveTrayIcon(); delete m_Bitmap; delete m_Measure; for (size_t i = 0, isize = m_Icons.size(); i < isize; ++i) { DestroyIcon(m_Icons[i]); } m_Icons.clear(); if (m_Window) DestroyWindow(m_Window); } void TrayWindow::Initialize() { WNDCLASS wc = {0}; wc.lpfnWndProc = (WNDPROC)WndProc; wc.hInstance = GetRainmeter().GetModuleInstance(); wc.lpszClassName = L"RainmeterTrayClass"; wc.hIcon = GetIcon(IDI_RAINMETER); RegisterClass(&wc); m_Window = CreateWindowEx( WS_EX_TOOLWINDOW, L"RainmeterTrayClass", nullptr, WS_POPUP | WS_DISABLED, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, wc.hInstance, this); SetWindowPos(m_Window, HWND_BOTTOM, 0, 0, 0, 0, ZPOS_FLAGS); } bool TrayWindow::AddTrayIcon() { NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; tnid.hWnd = m_Window; tnid.uID = IDI_TRAY; tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; tnid.uCallbackMessage = WM_TRAY_NOTIFYICON; tnid.hIcon = m_Icon; wcsncpy_s(tnid.szTip, APPNAME, _TRUNCATE); return (Shell_NotifyIcon(NIM_ADD, &tnid) || GetLastError() != ERROR_TIMEOUT); } bool TrayWindow::IsTrayIconReady() { NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; tnid.hWnd = m_Window; tnid.uID = IDI_TRAY; return Shell_NotifyIcon(NIM_MODIFY, &tnid); } void TrayWindow::TryAddTrayIcon() { if (IsTrayIconReady()) { ModifyTrayIcon(0); return; } if (m_Icon) { DestroyIcon(m_Icon); m_Icon = nullptr; } m_Icon = CreateTrayIcon(0); if (!AddTrayIcon()) { SetTimer(m_Window, TIMER_ADDTRAYICON, INTERVAL_ADDTRAYICON, nullptr); } } void TrayWindow::CheckTrayIcon() { if (IsTrayIconReady() || AddTrayIcon()) { KillTimer(m_Window, TIMER_ADDTRAYICON); } } void TrayWindow::RemoveTrayIcon() { NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; tnid.hWnd = m_Window; tnid.uID = IDI_TRAY; tnid.uFlags = 0; Shell_NotifyIcon(NIM_DELETE, &tnid); if (m_Icon) { DestroyIcon(m_Icon); m_Icon = nullptr; } } void TrayWindow::ModifyTrayIcon(double value) { if (m_Icon) { DestroyIcon(m_Icon); m_Icon = nullptr; } m_Icon = CreateTrayIcon(value); NOTIFYICONDATA tnid = {sizeof(NOTIFYICONDATA)}; tnid.hWnd = m_Window; tnid.uID = IDI_TRAY; tnid.uFlags = NIF_ICON; tnid.hIcon = m_Icon; Shell_NotifyIcon(NIM_MODIFY, &tnid); } HICON TrayWindow::CreateTrayIcon(double value) { if (m_Measure != nullptr) { if (m_MeterType == TRAY_METER_TYPE_HISTOGRAM) { m_Values[m_Pos] = value; m_Pos = (m_Pos + 1) % TRAYICON_SIZE; Bitmap trayBitmap(TRAYICON_SIZE, TRAYICON_SIZE); Graphics graphics(&trayBitmap); graphics.SetSmoothingMode(SmoothingModeAntiAlias); Point points[TRAYICON_SIZE + 2]; points[0].X = 0; points[0].Y = TRAYICON_SIZE; points[TRAYICON_SIZE + 1].X = TRAYICON_SIZE - 1; points[TRAYICON_SIZE + 1].Y = TRAYICON_SIZE; for (int i = 0; i < TRAYICON_SIZE; ++i) { points[i + 1].X = i; points[i + 1].Y = (int)(TRAYICON_SIZE * (1.0 - m_Values[(m_Pos + i) % TRAYICON_SIZE])); } SolidBrush brush(m_Color1); graphics.FillRectangle(&brush, 0, 0, TRAYICON_SIZE, TRAYICON_SIZE); SolidBrush brush2(m_Color2); graphics.FillPolygon(&brush2, points, TRAYICON_SIZE + 2); HICON icon = nullptr; trayBitmap.GetHICON(&icon); return icon; } else if (m_MeterType == TRAY_METER_TYPE_BITMAP && (m_Bitmap || !m_Icons.empty())) { if (!m_Icons.empty()) { size_t frame = 0; size_t frameCount = m_Icons.size(); // Select the correct frame linearly frame = (size_t)(value * frameCount); frame = min((frameCount - 1), frame); return CopyIcon(m_Icons[frame]); } else { int frame = 0; int frameCount = 0; int newX, newY; if (m_Bitmap->GetWidth() > m_Bitmap->GetHeight()) { frameCount = m_Bitmap->GetWidth() / TRAYICON_SIZE; } else { frameCount = m_Bitmap->GetHeight() / TRAYICON_SIZE; } // Select the correct frame linearly frame = (int)(value * frameCount); frame = min((frameCount - 1), frame); if (m_Bitmap->GetWidth() > m_Bitmap->GetHeight()) { newX = frame * TRAYICON_SIZE; newY = 0; } else { newX = 0; newY = frame * TRAYICON_SIZE; } Bitmap trayBitmap(TRAYICON_SIZE, TRAYICON_SIZE); Graphics graphics(&trayBitmap); graphics.SetSmoothingMode(SmoothingModeAntiAlias); // Blit the image Rect r(0, 0, TRAYICON_SIZE, TRAYICON_SIZE); graphics.DrawImage(m_Bitmap, r, newX, newY, TRAYICON_SIZE, TRAYICON_SIZE, UnitPixel); HICON icon = nullptr; trayBitmap.GetHICON(&icon); return icon; } } } // Return the default icon if there is no valid measure return GetIcon(IDI_TRAY); } void TrayWindow::ShowNotification(TRAY_NOTIFICATION id, const WCHAR* title, const WCHAR* text) { if (m_Notification == TRAY_NOTIFICATION_NONE) { NOTIFYICONDATA nid = {sizeof(NOTIFYICONDATA)}; nid.hWnd = m_Window; nid.uID = IDI_TRAY; nid.uFlags = NIF_INFO; nid.uTimeout = 30000; nid.dwInfoFlags = NIIF_USER; wcsncpy_s(nid.szInfoTitle, title, _TRUNCATE); wcsncpy_s(nid.szInfo, text, _TRUNCATE); if (Platform::IsAtLeastWin7()) { nid.dwInfoFlags |= NIIF_LARGE_ICON; nid.hBalloonIcon = GetIcon(IDI_RAINMETER, true); } if (Shell_NotifyIcon(NIM_MODIFY, &nid)) { m_Notification = id; } } } void TrayWindow::ShowWelcomeNotification() { ShowNotification(TRAY_NOTIFICATION_WELCOME, GetString(ID_STR_WELCOME), GetString(ID_STR_CLICKTOMANAGE)); } void TrayWindow::ShowUpdateNotification(const WCHAR* newVersion) { std::wstring text = GetFormattedString(ID_STR_CLICKTODOWNLOAD, newVersion); ShowNotification(TRAY_NOTIFICATION_UPDATE, GetString(ID_STR_UPDATEAVAILABLE), text.c_str()); } void TrayWindow::SetTrayIcon(bool enabled) { enabled ? TryAddTrayIcon() : RemoveTrayIcon(); m_IconEnabled = enabled; // Save to Rainmeter.ini. const std::wstring& iniFile = GetRainmeter().GetIniFile(); WritePrivateProfileString(L"Rainmeter", L"TrayIcon", enabled ? nullptr : L"0", iniFile.c_str()); } void TrayWindow::ReadOptions(ConfigParser& parser) { // Clear old Settings KillTimer(m_Window, TIMER_ADDTRAYICON); KillTimer(m_Window, TIMER_TRAYMEASURE); delete m_Measure; m_Measure = nullptr; delete m_Bitmap; m_Bitmap = nullptr; std::vector::const_iterator iter = m_Icons.begin(); for ( ; iter != m_Icons.end(); ++iter) { DestroyIcon((*iter)); } m_Icons.clear(); m_MeterType = TRAY_METER_TYPE_NONE; // Read tray settings m_IconEnabled = 0!=parser.ReadInt(L"Rainmeter", L"TrayIcon", 1); if (m_IconEnabled) { const std::wstring& measureName = parser.ReadString(L"TrayMeasure", L"Measure", L""); if (!measureName.empty()) { ConfigParser* oldParser = GetRainmeter().GetCurrentParser(); GetRainmeter().SetCurrentParser(&parser); m_Measure = Measure::Create(measureName.c_str(), nullptr, L"TrayMeasure"); if (m_Measure) { m_Measure->ReadOptions(parser); } GetRainmeter().SetCurrentParser(oldParser); } const WCHAR* type = parser.ReadString(L"TrayMeasure", L"TrayMeter", m_Measure ? L"HISTOGRAM" : L"NONE").c_str(); if (_wcsicmp(type, L"NONE") == 0) { // Use main icon } else if (_wcsicmp(type, L"HISTOGRAM") == 0) { m_MeterType = TRAY_METER_TYPE_HISTOGRAM; m_Color1 = parser.ReadColor(L"TrayMeasure", L"TrayColor1", Color::MakeARGB(255, 0, 100, 0)); m_Color2 = parser.ReadColor(L"TrayMeasure", L"TrayColor2", Color::MakeARGB(255, 0, 255, 0)); } else if (_wcsicmp(type, L"BITMAP") == 0) { m_MeterType = TRAY_METER_TYPE_BITMAP; std::wstring imageName = parser.ReadString(L"TrayMeasure", L"TrayBitmap", L""); // Load the bitmaps if defined if (!imageName.empty()) { imageName.insert(0, GetRainmeter().GetSkinPath()); const WCHAR* imagePath = imageName.c_str(); if (_wcsicmp(imagePath + (imageName.size() - 4), L".ico") == 0) { int count = 1; HICON hIcon = nullptr; // Load the icons do { WCHAR buffer[MAX_PATH]; _snwprintf_s(buffer, _TRUNCATE, imagePath, count++); hIcon = (HICON)LoadImage(nullptr, buffer, IMAGE_ICON, TRAYICON_SIZE, TRAYICON_SIZE, LR_LOADFROMFILE); if (hIcon) m_Icons.push_back(hIcon); if (wcscmp(imagePath, buffer) == 0) break; } while(hIcon != nullptr); } if (m_Icons.empty()) { // No icons found so load as bitmap delete m_Bitmap; m_Bitmap = new Bitmap(imagePath); Status status = m_Bitmap->GetLastStatus(); if (Ok != status) { delete m_Bitmap; m_Bitmap = nullptr; LogWarningF(L"Bitmap image not found: %s", imagePath); } } } } else { LogErrorF(L"No such TrayMeter: %s", type); } TryAddTrayIcon(); if (m_Measure) { SetTimer(m_Window, TIMER_TRAYMEASURE, INTERVAL_TRAYMEASURE, nullptr); // Update the tray once per sec } } else { RemoveTrayIcon(); } } LRESULT CALLBACK TrayWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { TrayWindow* tray = GetRainmeter().GetTrayWindow(); switch (uMsg) { case WM_COMMAND: switch (wParam) { case IDM_MANAGE: DialogManage::Open(); break; case IDM_ABOUT: DialogAbout::Open(); break; case IDM_SHOW_HELP: CommandHandler::RunFile(RAINMETER_HELP); break; case IDM_NEW_VERSION: CommandHandler::RunFile(RAINMETER_OFFICIAL); break; case IDM_REFRESH: PostMessage(GetRainmeter().GetWindow(), WM_RAINMETER_DELAYED_REFRESH_ALL, (WPARAM)nullptr, (LPARAM)nullptr); break; case IDM_SHOWLOGFILE: GetRainmeter().ShowLogFile(); break; case IDM_STARTLOG: GetLogger().StartLogFile(); break; case IDM_STOPLOG: GetLogger().StopLogFile(); break; case IDM_DELETELOGFILE: GetLogger().DeleteLogFile(); break; case IDM_DEBUGLOG: GetRainmeter().SetDebug(!GetRainmeter().GetDebug()); break; case IDM_DISABLEDRAG: GetRainmeter().SetDisableDragging(!GetRainmeter().GetDisableDragging()); break; case IDM_EDITCONFIG: GetRainmeter().EditSettings(); break; case IDM_QUIT: PostQuitMessage(0); break; case IDM_OPENSKINSFOLDER: GetRainmeter().OpenSkinFolder(); break; default: { UINT mID = wParam & 0x0FFFF; if (mID >= ID_THEME_FIRST && mID <= ID_THEME_LAST) { int pos = mID - ID_THEME_FIRST; const std::vector& layouts = GetRainmeter().GetAllLayouts(); if (pos >= 0 && pos < (int)layouts.size()) { GetRainmeter().LoadLayout(layouts[pos]); } } else if (mID >= ID_CONFIG_FIRST && mID <= ID_CONFIG_LAST) { std::pair indexes = GetRainmeter().GetMeterWindowIndex(mID); if (indexes.first != -1 && indexes.second != -1) { GetRainmeter().ToggleSkin(indexes.first, indexes.second); } } else { // Forward the message to correct window int index = (int)(wParam >> 16); const std::map& windows = GetRainmeter().GetAllMeterWindows(); if (index < (int)windows.size()) { std::map::const_iterator iter = windows.begin(); for ( ; iter != windows.end(); ++iter) { --index; if (index < 0) { MeterWindow* meterWindow = (*iter).second; SendMessage(meterWindow->GetWindow(), WM_COMMAND, mID, 0); break; } } } } } break; } break; // Don't send WM_COMMANDS any further case WM_TRAY_NOTIFYICON: { UINT uMouseMsg = (UINT)lParam; LPCWSTR bang; // Check TrayExecute actions switch (uMouseMsg) { case WM_MBUTTONDOWN: bang = GetRainmeter().GetTrayExecuteM().c_str(); break; case WM_RBUTTONDOWN: bang = GetRainmeter().GetTrayExecuteR().c_str(); break; case WM_MBUTTONDBLCLK: bang = GetRainmeter().GetTrayExecuteDM().c_str(); break; case WM_RBUTTONDBLCLK: bang = GetRainmeter().GetTrayExecuteDR().c_str(); break; default: bang = L""; break; } if (*bang && !IsCtrlKeyDown()) // Ctrl is pressed, so only run default action { GetRainmeter().ExecuteCommand(bang, nullptr); tray->m_TrayContextMenuEnabled = (uMouseMsg != WM_RBUTTONDOWN); break; } // Run default UI action switch (uMouseMsg) { case WM_RBUTTONDOWN: tray->m_TrayContextMenuEnabled = true; break; case WM_RBUTTONUP: if (tray->m_TrayContextMenuEnabled) { POINT pos = System::GetCursorPosition(); GetRainmeter().ShowContextMenu(pos, nullptr); } break; case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: DialogManage::Open(); break; case NIN_BALLOONUSERCLICK: if (tray->m_Notification == TRAY_NOTIFICATION_WELCOME) { DialogManage::Open(); } else if (tray->m_Notification == TRAY_NOTIFICATION_UPDATE) { CommandHandler::RunFile(RAINMETER_OFFICIAL); } tray->m_Notification = TRAY_NOTIFICATION_NONE; break; case NIN_BALLOONHIDE: case NIN_BALLOONTIMEOUT: tray->m_Notification = TRAY_NOTIFICATION_NONE; break; } } break; case WM_QUERY_RAINMETER: if (IsWindow((HWND)lParam)) { auto sendCopyData = [&](const std::wstring& data) { COPYDATASTRUCT cds; cds.dwData = wParam; cds.cbData = (DWORD)((data.length() + 1) * sizeof(WCHAR)); cds.lpData = (PVOID)data.c_str(); SendMessage((HWND)lParam, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds); }; switch (wParam) { case RAINMETER_QUERY_ID_SKINS_PATH: sendCopyData(GetRainmeter().GetSkinPath()); return 0; case RAINMETER_QUERY_ID_SETTINGS_PATH: sendCopyData(GetRainmeter().GetSettingsPath()); return 0; case RAINMETER_QUERY_ID_PLUGINS_PATH: sendCopyData(GetRainmeter().GetPluginPath()); return 0; case RAINMETER_QUERY_ID_PROGRAM_PATH: sendCopyData(GetRainmeter().GetPath()); return 0; case RAINMETER_QUERY_ID_LOG_PATH: sendCopyData(GetLogger().GetLogFilePath()); return 0; case RAINMETER_QUERY_ID_CONFIG_EDITOR: sendCopyData(GetRainmeter().GetSkinEditor()); return 0; case RAINMETER_QUERY_ID_IS_DEBUGGING: { BOOL debug = GetRainmeter().GetDebug(); SendMessage((HWND)lParam, WM_QUERY_RAINMETER_RETURN, (WPARAM)hWnd, (LPARAM)debug); } return 0; } } return 1; case WM_COPYDATA: { COPYDATASTRUCT* cds = (COPYDATASTRUCT*)lParam; if (cds->dwData == RAINMETER_QUERY_ID_SKIN_WINDOWHANDLE) { LPCWSTR folderPath = (LPCWSTR)cds->lpData; MeterWindow* mw = GetRainmeter().GetMeterWindow(folderPath); return (mw) ? (LRESULT)mw->GetWindow() : 0; } } return 1; case WM_TIMER: if (wParam == TIMER_TRAYMEASURE) { if (tray->m_Measure) { tray->m_Measure->Update(); tray->ModifyTrayIcon(tray->m_Measure->GetRelativeValue()); } } else if (wParam == TIMER_ADDTRAYICON) { tray->CheckTrayIcon(); } break; case WM_DESTROY: PostQuitMessage(0); break; default: if (uMsg == WM_TASKBARCREATED) { if (tray->IsTrayIconEnabled()) { tray->RemoveTrayIcon(); tray->TryAddTrayIcon(); } } return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; }