Implementation of notifications for Windows OS.

This commit is contained in:
Maxim Biro 2013-05-17 15:35:42 -04:00
parent e827728e78
commit 2fc758377a
7 changed files with 297 additions and 3 deletions

View file

@ -1,6 +1,8 @@
Version 1.11.4+dev:
* Language and i18n:
* Updated translations:
* User interface:
* Added notification support for Windows
Version 1.11.4:
* AI:

View file

@ -5,6 +5,8 @@ changelog: https://github.com/wesnoth/wesnoth-old/blob/master/changelog
Version 1.11.4+dev:
* Language and i18n:
* Updated translations:
* User interface:
* Added notification support for Windows
Version 1.11.4:

View file

@ -21,6 +21,9 @@
#include "preferences_display.hpp"
#include "sound.hpp"
#include "video.hpp"
#if defined _WIN32
#include "windows_tray_notification.hpp"
#endif
#include "SDL.h"
@ -362,6 +365,13 @@ void pump()
}
#endif
#if defined _WIN32
case SDL_SYSWMEVENT: {
windows_tray_notification::handle_system_event(event);
break;
}
#endif
case SDL_QUIT: {
throw CVideo::quit();
}

View file

@ -490,7 +490,7 @@ static int do_gameloop(int argc, char** argv)
loadscreen::start_stage("refresh addons");
refresh_addon_version_info_cache();
#if defined(_X11) && !defined(__APPLE__)
#if (defined(_X11) && !defined(__APPLE__)) || defined(_WIN32)
SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);
#endif

View file

@ -44,6 +44,9 @@ Growl_Delegate growl_obj;
#include "tod_manager.hpp"
#include "sound.hpp"
#include "whiteboard/manager.hpp"
#ifdef _WIN32
#include "windows_tray_notification.hpp"
#endif
#include <boost/foreach.hpp>
@ -973,13 +976,13 @@ static uint32_t send_dbus_notification(DBusConnection *connection, uint32_t repl
}
#endif
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL)
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL) || defined(_WIN32)
void game_display::send_notification(const std::string& owner, const std::string& message)
#else
void game_display::send_notification(const std::string& /*owner*/, const std::string& /*message*/)
#endif
{
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL)
#if defined(HAVE_LIBDBUS) || defined(HAVE_GROWL) || defined(_WIN32)
Uint8 app_state = SDL_GetAppState();
// Do not show notifications when the window is visible...
@ -1038,6 +1041,21 @@ void game_display::send_notification(const std::string& /*owner*/, const std::st
CFRelease(cf_message);
CFRelease(cf_note_name);
#endif
#ifdef _WIN32
std::string notification_title;
std::string notification_message;
if (owner == "Turn changed") {
notification_title = owner;
notification_message = message;
} else {
notification_title = "Chat message";
notification_message = owner + ": " + message;
}
windows_tray_notification::show(notification_title, notification_message);
#endif
}
void game_display::set_team(size_t teamindex, bool show_everything)

View file

@ -0,0 +1,193 @@
#include "windows_tray_notification.hpp"
#include <SDL_syswm.h>
#include "serialization/string_utils.hpp"
NOTIFYICONDATA* windows_tray_notification::nid = NULL;
bool windows_tray_notification::message_reset = false;
void windows_tray_notification::destroy_tray_icon()
{
if (nid == NULL) {
return;
}
if (!message_reset){
Shell_NotifyIcon(NIM_DELETE, nid);
delete nid;
nid = NULL;
} else {
message_reset = false;
}
}
void windows_tray_notification::handle_system_event(const SDL_Event& event)
{
if (event.syswm.msg->msg != WM_TRAYNOTIFY) {
return;
}
if (event.syswm.msg->lParam == NIN_BALLOONUSERCLICK) {
switch_to_wesnoth_window();
destroy_tray_icon();
} else if (event.syswm.msg->lParam == NIN_BALLOONTIMEOUT) {
destroy_tray_icon();
}
}
bool windows_tray_notification::create_tray_icon()
{
// getting hanlde to a 32x32 icon, contained in "WESNOTH_ICON" icon group of wesnoth.exe resources
const HMODULE wesnoth_exe = GetModuleHandle(NULL);
if (wesnoth_exe == NULL) {
return false;
}
const HRSRC group_icon_info = FindResource(wesnoth_exe, L"WESNOTH_ICON", RT_GROUP_ICON);
if (group_icon_info == NULL) {
return false;
}
HGLOBAL hGlobal = LoadResource(wesnoth_exe, group_icon_info);
if (hGlobal == NULL) {
return false;
}
const PBYTE group_icon_res = static_cast<PBYTE>(LockResource(hGlobal));
if (group_icon_res == NULL) {
return false;
}
const int nID = LookupIconIdFromDirectoryEx(group_icon_res, TRUE, 32, 32, LR_DEFAULTCOLOR);
if (nID == 0) {
return false;
}
const HRSRC icon_info = FindResource(wesnoth_exe, MAKEINTRESOURCE(nID), MAKEINTRESOURCE(3));
if (icon_info == NULL) {
return false;
}
hGlobal = LoadResource(wesnoth_exe, icon_info);
if (hGlobal == NULL) {
return false;
}
const PBYTE icon_res = static_cast<PBYTE>(LockResource(hGlobal));
if (icon_res == NULL) {
return false;
}
const HICON icon = CreateIconFromResource(icon_res, SizeofResource(wesnoth_exe, icon_info), TRUE, 0x00030000);
if (icon == NULL) {
return false;
}
const HWND window = get_window_hanlde();
if (window == NULL) {
return false;
}
// filling notification structure
nid = new NOTIFYICONDATA;
memset(nid, 0, sizeof(&nid));
nid->cbSize = NOTIFYICONDATA_V2_SIZE;
nid->hWnd = window;
nid->uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
nid->dwInfoFlags = NIIF_USER;
nid->uVersion = NOTIFYICON_VERSION;
nid->uCallbackMessage = WM_TRAYNOTIFY;
nid->uID = ICON_ID;
nid->hIcon = icon;
#if _WIN32_WINNT >= 0x600
nid->hBalloonIcon = icon;
#endif
lstrcpy(nid->szTip, L"The Battle For Wesnoth");
// creating icon notification
return Shell_NotifyIcon(NIM_ADD, nid) != FALSE;
}
bool windows_tray_notification::set_tray_message(const std::string& title, const std::string& message)
{
// prevents deletion of icon when resetting already existing notification
message_reset = (nid->uFlags & NIF_INFO) != 0;
nid->uFlags |= NIF_INFO;
lstrcpy(nid->szInfoTitle, string_to_wstring(title).data());
lstrcpy(nid->szInfo, string_to_wstring(message).data());
// setting notification
return Shell_NotifyIcon(NIM_MODIFY, nid) != FALSE;
}
void windows_tray_notification::adjust_length(std::string& title, std::string& message)
{
static const int ELIPSIS_LENGTH = 3;
// limitations set by winapi
if (title.length() > MAX_TITLE_LENGTH) {
utils::ellipsis_truncate(title, MAX_TITLE_LENGTH - ELIPSIS_LENGTH);
}
if (message.length() > MAX_MESSAGE_LENGTH) {
utils::ellipsis_truncate(message, MAX_MESSAGE_LENGTH - ELIPSIS_LENGTH);
}
}
HWND windows_tray_notification::get_window_hanlde()
{
SDL_SysWMinfo wmInfo;
SDL_VERSION(&wmInfo.version);
if (SDL_GetWMInfo(&wmInfo) != 1) {
return NULL;
}
return wmInfo.window;
}
void windows_tray_notification::switch_to_wesnoth_window()
{
const HWND window = get_window_hanlde();
if (window == NULL) {
return;
}
if (IsIconic(window)) {
ShowWindow(window, SW_RESTORE);
}
SetForegroundWindow(window);
}
std::wstring windows_tray_notification::string_to_wstring(const std::string& string)
{
const std::vector<wchar_t> wide_string = utils::string_to_wstring(string);
return std::wstring(wide_string.begin(), wide_string.end());
}
bool windows_tray_notification::show(std::string title, std::string message)
{
adjust_length(title, message);
const bool tray_icon_exist = nid != NULL;
if (!tray_icon_exist) {
const bool tray_icon_created = create_tray_icon();
if (!tray_icon_created) {
const bool memory_allocated = nid != NULL;
if (memory_allocated) {
destroy_tray_icon();
}
return false;
}
}
// at this point tray icon was just created or already existed before, so it's safe to call `set_tray_message`
const bool result = set_tray_message(title, message);
// the `destroy_tray_icon` will be called by event only if `set_tray_message` succeeded
// if it doesn't succeed, we have to call `destroy_tray_icon` manually
if (!result) {
destroy_tray_icon();
}
return result;
}

View file

@ -0,0 +1,69 @@
#ifndef WINDOWS_TRAY_NOTIFICATION_HPP_INCLUDED
#define WINDOWS_TRAY_NOTIFICATION_HPP_INCLUDED
#include <SDL.h>
#include <string>
//forces to call Unicode winapi functions instead of ASCII (default)
#define UNICODE
//defines that mingw misses
#ifndef _WIN32_IE
#define _WIN32_IE 0x0600 //specifying target platform to be Windows XP and higher
#endif
#ifndef NIIF_USER
#define NIIF_USER 0x00000004
#endif
#ifndef NIN_BALLOONTIMEOUT
#define NIN_BALLOONTIMEOUT (WM_USER + 4)
#endif
#ifndef NIN_BALLOONUSERCLICK
#define NIN_BALLOONUSERCLICK (WM_USER + 5)
#endif
// ShellAPI.h should be included after Windows.h only!
#include <Windows.h>
#include <ShellAPI.h>
class windows_tray_notification {
public:
/**
* Displays a tray notification.
* When user clicks on the notification popup, the user switches to the wesnoth window.
*
* @param title Title of a notification. Gets truncated if longer than 64 characters, including
* the terminating null character.
* @param message Message of a notification. Gets truncated if longer than 256 characters, including
* the terminating null character.
*
* @return True if message was shown successfully, False otherwise.
*/
static bool show(std::string title, std::string message);
/**
* Frees resources when a notification disappears, switches user to the wesnoth
* window if the notification popup was clicked by user.
*
* @param event System event.
*/
static void handle_system_event(const SDL_Event& event);
private:
static NOTIFYICONDATA* nid;
static bool message_reset;
static const int ICON_ID = 1007; // just a random number
static const unsigned int WM_TRAYNOTIFY = 32868; // WM_APP+100
static const size_t MAX_TITLE_LENGTH = 63; // 64 including the terminating null character
static const size_t MAX_MESSAGE_LENGTH = 255; // 256 including the terminating null character
static bool create_tray_icon();
static void destroy_tray_icon();
static bool set_tray_message(const std::string& title, const std::string& message);
static void adjust_length(std::string& title, std::string& message);
static HWND get_window_hanlde();
static void switch_to_wesnoth_window();
static std::wstring string_to_wstring(const std::string& string);
explicit windows_tray_notification();
windows_tray_notification(const windows_tray_notification& w);
windows_tray_notification& operator=(const windows_tray_notification& w);
};
#endif // WINDOWS_TRAY_NOTIFICATION_HPP_INCLUDED