1 /*
2 * Copyright (C) 2007 Adam Kropelin
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 2 of the GNU General
6 * Public License as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public
14 * License along with this program; if not, write to the Free
15 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16 * MA 02110-1335, USA.
17 */
18
19 #include "apc.h"
20 #include "balloonmgr.h"
21 #include "resource.h"
22
23 #define MAX_TIMEOUT 10000
24 #define MIN_TIMEOUT 2000
25
26 #define ARRAY_SIZE(x) ( sizeof(x) / sizeof((x)[0]) )
27
BalloonMgr()28 BalloonMgr::BalloonMgr()
29 : _exit(false),
30 _active(false)
31 {
32 _mutex = CreateMutex(NULL, false, NULL);
33 _event = CreateEvent(NULL, false, false, NULL);
34 _timer = CreateWaitableTimer(NULL, false, NULL);
35
36 DWORD tid;
37 _thread = CreateThread(NULL, 0, &BalloonMgr::Thread, this, 0, &tid);
38 }
39
~BalloonMgr()40 BalloonMgr::~BalloonMgr()
41 {
42 // Request thread exit
43 _exit = true;
44 signal();
45
46 // Wait for thread exit and force if necessary
47 if (_thread) {
48 if (WaitForSingleObject(_thread, 5000) == WAIT_TIMEOUT)
49 TerminateThread(_thread, 0);
50 CloseHandle(_thread);
51 }
52
53 CloseHandle(_mutex);
54 CloseHandle(_event);
55 CloseHandle(_timer);
56 }
57
PostBalloon(HWND hwnd,const char * title,const char * text)58 void BalloonMgr::PostBalloon(HWND hwnd, const char *title, const char *text)
59 {
60 lock();
61
62 Balloon balloon = {hwnd, title, text};
63 _pending.append(balloon);
64 signal();
65
66 unlock();
67 }
68
lock()69 void BalloonMgr::lock()
70 {
71 WaitForSingleObject(_mutex, INFINITE);
72 }
73
unlock()74 void BalloonMgr::unlock()
75 {
76 ReleaseMutex(_mutex);
77 }
78
signal()79 void BalloonMgr::signal()
80 {
81 SetEvent(_event);
82 }
83
post()84 void BalloonMgr::post()
85 {
86 if (_pending.empty())
87 return; // No active balloon!?
88
89 // Post balloon tip
90 Balloon &balloon = _pending.front();
91 NOTIFYICONDATA nid;
92 nid.hWnd = balloon.hwnd;
93 nid.cbSize = sizeof(nid);
94 nid.uID = IDI_APCCTRL;
95 nid.uFlags = NIF_INFO;
96 strlcpy(nid.szInfo, balloon.text, sizeof(nid.szInfo));
97 strlcpy(nid.szInfoTitle, balloon.title, sizeof(nid.szInfoTitle));
98 nid.uTimeout = MAX_TIMEOUT;
99 nid.dwInfoFlags = NIIF_INFO;
100 Shell_NotifyIcon(NIM_MODIFY, &nid);
101
102 // Set a timeout to clear the balloon
103 LARGE_INTEGER timeout;
104 if (_pending.size() > 1) // More balloons pending: use minimum timeout
105 timeout.QuadPart = -(MIN_TIMEOUT * 10000);
106 else // No other balloons pending: Use maximum timeout
107 timeout.QuadPart = -(MAX_TIMEOUT * 10000);
108 SetWaitableTimer(_timer, &timeout, 0, NULL, NULL, false);
109
110 // Remember the time at which we started the timer
111 gettimeofday(&_time, NULL);
112 }
113
clear()114 void BalloonMgr::clear()
115 {
116 if (_pending.empty())
117 return; // No active balloon!?
118
119 // Clear active balloon
120 Balloon &balloon = _pending.front();
121 NOTIFYICONDATA nid;
122 nid.hWnd = balloon.hwnd;
123 nid.cbSize = sizeof(nid);
124 nid.uID = IDI_APCCTRL;
125 nid.uFlags = NIF_INFO;
126 nid.uTimeout = 0;
127 nid.szInfoTitle[0] = '\0';
128 nid.szInfo[0] = '\0';
129 nid.dwInfoFlags = 0;
130 Shell_NotifyIcon(NIM_MODIFY, &nid);
131
132 // Remove vector entry for active balloon
133 _pending.remove_first();
134 }
135
Thread(LPVOID param)136 DWORD WINAPI BalloonMgr::Thread(LPVOID param)
137 {
138 BalloonMgr *_this = (BalloonMgr*)param;
139 HANDLE handles[] = {_this->_event, _this->_timer};
140 LARGE_INTEGER timeout;
141 struct timeval now;
142 DWORD index;
143 long diff;
144
145 while (1) {
146 // Wait for timeout or new balloon request
147 index = WaitForMultipleObjects(
148 ARRAY_SIZE(handles), handles, false, INFINITE);
149
150 // Exit if we've been asked to do so
151 if (_this->_exit)
152 break;
153
154 switch (index) {
155 // New balloon request has arrived
156 case WAIT_OBJECT_0 + 0:
157 _this->lock();
158
159 if (!_this->_active) {
160 // No balloon active: Post new balloon immediately
161 if (!_this->_pending.empty()) {
162 _this->post();
163 _this->_active = true;
164 }
165 } else {
166 // A balloon is active: Shorten timer to minimum
167 CancelWaitableTimer(_this->_timer);
168 gettimeofday(&now, NULL);
169 diff = TV_DIFF_MS(_this->_time, now);
170 if (diff >= MIN_TIMEOUT) {
171 // Min timeout already expired
172 timeout.QuadPart = -1;
173 } else {
174 // Wait enough additional time to meet minimum timeout
175 timeout.QuadPart = -((MIN_TIMEOUT - diff) * 10000);
176 }
177 SetWaitableTimer(_this->_timer, &timeout, 0, NULL, NULL, false);
178 }
179
180 _this->unlock();
181 break;
182
183 // Timeout ocurred
184 case WAIT_OBJECT_0 + 1:
185 _this->lock();
186
187 // Clear active balloon
188 _this->clear();
189
190 // Post next balloon if there is one
191 if (!_this->_pending.empty()) {
192 _this->post();
193 _this->_active = true;
194 } else {
195 _this->_active = false;
196 }
197
198 _this->unlock();
199 break;
200
201 default:
202 // Should never happen...but if it does, sleep a bit to prevent
203 // spinning.
204 Sleep(1000);
205 break;
206 }
207 }
208 return 0;
209 }
210