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