The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
windows_tray_notification.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2013 - 2017 by Maxim Biro <nurupo.contributions@gmail.com>
3  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
16 
17 #include <SDL_syswm.h>
18 
19 #include "gettext.hpp"
22 
23 #include "video.hpp" // CVideo class holds the window -> SDL_Window object which contains the handle of the main window
24 
25 NOTIFYICONDATA* windows_tray_notification::nid = nullptr;
27 
29 {
30  if (nid == nullptr) {
31  return;
32  }
33 
34  if (!message_reset){
35  Shell_NotifyIcon(NIM_DELETE, nid);
36  delete nid;
37  nid = nullptr;
38  } else {
39  message_reset = false;
40  }
41 }
42 
44 {
45  if (event.syswm.msg->msg.win.msg != WM_TRAYNOTIFY) {
46  return;
47  }
48 
49  if (event.syswm.msg->msg.win.lParam == NIN_BALLOONUSERCLICK) {
52  } else if (event.syswm.msg->msg.win.lParam == NIN_BALLOONTIMEOUT) {
54  }
55  // Scenario: More than one notification arrives before the time-out triggers the tray icon destruction.
56  // Problem: Events seem to be triggered differently in SDL 2.0. For the example of two notifications arriving at once:
57  // 1. Balloon created for first notification
58  // 2. Balloon created for second notification (message_reset set to true because of first notification already present)
59  // 3. Balloon time-out for first notification (destroy_tray_icon skips tray icon destruction because of message_reset flag)
60  // 4. SDL 1.2: Balloon time-out for second notification (destroy_tray_icon destroys tray icon)
61  // SDL 2.0: Balloon time-out for second notification event is never received (tray icon remains indefinitely)
62  // This results in the tray icon being 'stuck' until the user quits Wesnoth *and* hovers over the tray icon (and is only then killed off by the OS).
63  // As a less-than-ideal-but-better-than-nothing-solution, call destroy_tray_icon when the user hovers mouse cursor over the tray icon. At least then the tray is 'reset'.
64  // I could not find the matching definition for 0x0200 in the headers, but this message value is received when the mouse cursor is over the tray icon.
65  // Drawback: The tray icon can still get 'stuck' if the user does not move the mouse cursor over the tray icon.
66  // Also, accidental destruction of the tray icon can occur if the user moves the mouse cursor over the tray icon before the balloon for a single notification has expired.
67  else if (event.syswm.msg->msg.win.lParam == 0x0200 && !message_reset) {
69  }
70 }
71 
73 {
74  // getting handle to a 32x32 icon, contained in "WESNOTH_ICON" icon group of wesnoth.exe resources
75  const HMODULE wesnoth_exe = GetModuleHandle(nullptr);
76  if (wesnoth_exe == nullptr) {
77  return false;
78  }
79 
80  const HRSRC group_icon_info = FindResource(wesnoth_exe, TEXT("WESNOTH_ICON"), RT_GROUP_ICON);
81  if (group_icon_info == nullptr) {
82  return false;
83  }
84 
85  HGLOBAL hGlobal = LoadResource(wesnoth_exe, group_icon_info);
86  if (hGlobal == nullptr) {
87  return false;
88  }
89 
90  const PBYTE group_icon_res = static_cast<PBYTE>(LockResource(hGlobal));
91  if (group_icon_res == nullptr) {
92  return false;
93  }
94 
95  const int nID = LookupIconIdFromDirectoryEx(group_icon_res, TRUE, 32, 32, LR_DEFAULTCOLOR);
96  if (nID == 0) {
97  return false;
98  }
99 
100  const HRSRC icon_info = FindResource(wesnoth_exe, MAKEINTRESOURCE(nID), MAKEINTRESOURCE(3));
101  if (icon_info == nullptr) {
102  return false;
103  }
104 
105  hGlobal = LoadResource(wesnoth_exe, icon_info);
106  if (hGlobal == nullptr) {
107  return false;
108  }
109 
110  const PBYTE icon_res = static_cast<PBYTE>(LockResource(hGlobal));
111  if (icon_res == nullptr) {
112  return false;
113  }
114 
115  const HICON icon = CreateIconFromResource(icon_res, SizeofResource(wesnoth_exe, icon_info), TRUE, 0x00030000);
116  if (icon == nullptr) {
117  return false;
118  }
119 
120  const HWND window = get_window_handle();
121  if (window == nullptr) {
122  return false;
123  }
124 
125  const std::wstring& wtip = string_to_wstring(_("The Battle for Wesnoth"), MAX_TITLE_LENGTH);
126 
127  // filling notification structure
128  nid = new NOTIFYICONDATA;
129  memset(nid, 0, sizeof(*nid));
130  nid->cbSize = NOTIFYICONDATA_V2_SIZE;
131  nid->hWnd = window;
132  nid->uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
133  nid->dwInfoFlags = NIIF_USER;
134  nid->uVersion = NOTIFYICON_VERSION;
135  nid->uCallbackMessage = WM_TRAYNOTIFY;
136  nid->uID = ICON_ID;
137  nid->hIcon = icon;
138 #if _WIN32_WINNT >= 0x600
139  nid->hBalloonIcon = icon;
140 #endif
141  lstrcpyW(nid->szTip, wtip.c_str());
142 
143  // creating icon notification
144  return Shell_NotifyIcon(NIM_ADD, nid) != FALSE;
145 }
146 
148 {
149  // prevents deletion of icon when resetting already existing notification
150  message_reset = (nid->uFlags & NIF_INFO) != 0;
151 
152  nid->uFlags |= NIF_INFO;
153  lstrcpyW(nid->szInfoTitle, string_to_wstring(title, MAX_TITLE_LENGTH).c_str());
154  lstrcpyW(nid->szInfo, string_to_wstring(message, MAX_MESSAGE_LENGTH).c_str());
155 
156  // setting notification
157  return Shell_NotifyIcon(NIM_MODIFY, nid) != FALSE;
158 }
159 
161 {
162  static const int ELIPSIS_LENGTH = 3;
163 
164  // limitations set by winapi
165  if (title.length() > MAX_TITLE_LENGTH) {
166  utils::ellipsis_truncate(title, MAX_TITLE_LENGTH - ELIPSIS_LENGTH);
167  }
168  if (message.length() > MAX_MESSAGE_LENGTH) {
169  utils::ellipsis_truncate(message, MAX_MESSAGE_LENGTH - ELIPSIS_LENGTH);
170  }
171 }
172 
174 {
175  SDL_SysWMinfo wmInfo;
176  SDL_VERSION(&wmInfo.version);
178  // SDL 1.2 keeps track of window handles internally whereas SDL 2.0 allows the caller control over which window to use
179  if (!window || SDL_GetWindowWMInfo (static_cast<SDL_Window *> (*window), &wmInfo) != SDL_TRUE) {
180  return nullptr;
181  }
182 
183  return wmInfo.info.win.window;
184 }
185 
187 {
188  const HWND window = get_window_handle();
189  if (window == nullptr) {
190  return;
191  }
192 
193  if (IsIconic(window)) {
194  ShowWindow(window, SW_RESTORE);
195  }
196  SetForegroundWindow(window);
197 }
198 
199 std::wstring windows_tray_notification::string_to_wstring(const std::string& string, size_t maxlength)
200 {
202  if(u16_string.size() > maxlength) {
203  if((u16_string[maxlength-1] & 0xDC00) == 0xD800)
204  u16_string.resize(maxlength - 1);
205  else
206  u16_string.resize(maxlength);
207  }
208  return std::wstring(u16_string.begin(), u16_string.end());
209 }
210 
212 {
213  adjust_length(title, message);
214 
215  const bool tray_icon_exist = nid != nullptr;
216  if (!tray_icon_exist) {
217  const bool tray_icon_created = create_tray_icon();
218  if (!tray_icon_created) {
219  const bool memory_allocated = nid != nullptr;
220  if (memory_allocated) {
222  }
223  return false;
224  }
225  }
226 
227  // at this point tray icon was just created or already existed before, so it's safe to call `set_tray_message`
228 
229  const bool result = set_tray_message(title, message);
230  // the `destroy_tray_icon` will be called by event only if `set_tray_message` succeeded
231  // if it doesn't succeed, we have to call `destroy_tray_icon` manually
232  if (!result) {
234  }
235  return result;
236 }
std::vector< char_t > string
static const unsigned int WM_TRAYNOTIFY
std::vector< char_t > string
#define NIN_BALLOONTIMEOUT
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
static CVideo & get_singleton()
Definition: video.hpp:50
static bool set_tray_message(const std::string &title, const std::string &message)
-file util.hpp
#define NIIF_USER
void ellipsis_truncate(std::string &str, const size_t size)
Truncates a string to a given utf-8 character count and then appends an ellipsis. ...
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
The wrapper class for the SDL_Window class.
Definition: window.hpp:44
#define NIN_BALLOONUSERCLICK
sdl::window * get_window()
Definition: video.cpp:367
window(const window &)=delete
static bool show(std::string title, std::string message)
Displays a tray notification.
static void adjust_length(std::string &title, std::string &message)
static void handle_system_event(const SDL_Event &event)
Frees resources when a notification disappears, switches user to the wesnoth window if the notificati...
static std::wstring string_to_wstring(const std::string &string, size_t maxlength)