1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3 
4     SPDX-FileCopyrightText: 1999 Martin R. Jones <mjones@kde.org>
5     SPDX-FileCopyrightText: 2008 Eike Hein <hein@kde.org>
6     SPDX-FileCopyrightText: 2010 Martin Blumenstingl <darklight.xdarklight@googlemail.com>
7 */
8 
9 #include "awaymanager.h"
10 #include "application.h"
11 #include "connectionmanager.h"
12 #include "server.h"
13 #include "preferences.h"
14 #include "trayicon.h"
15 
16 #include <KActionCollection>
17 #include <QIcon>
18 #include <KIdleTime>
19 #include <KToggleAction>
20 
AwayManager(QObject * parent)21 AwayManager::AwayManager(QObject* parent) : QObject(parent)
22 {
23     m_connectionManager = Application::instance()->getConnectionManager();
24 
25     connect(KIdleTime::instance(), &KIdleTime::resumingFromIdle, this, &AwayManager::resumeFromIdle);
26     connect(KIdleTime::instance(), QOverload<int, int>::of(&KIdleTime::timeoutReached), this, &AwayManager::idleTimeoutReached);
27 
28     // Catch the first "resume event" (= user input) so we correctly catch the first
29     // resume event in case the user is already idle on startup).
30     KIdleTime::instance()->catchNextResumeEvent();
31 }
32 
~AwayManager()33 AwayManager::~AwayManager()
34 {
35 }
36 
minutesToMilliseconds(int minutes)37 int AwayManager::minutesToMilliseconds(int minutes)
38 {
39     return minutes * 60 * 1000;
40 }
41 
identitiesChanged()42 void AwayManager::identitiesChanged()
43 {
44     QHash<int, int> newIdentityWithIdleTimeMapping;
45 
46     const QList<Server*> serverList = m_connectionManager->getServerList();
47 
48     for (Server* server : serverList) {
49         IdentityPtr identity = server->getIdentity();
50         int identityId = identity->id();
51 
52         // Calculate the auto-away time in milliseconds.
53         int identityIdleTime = minutesToMilliseconds(identity->getAwayInactivity());
54 
55         if (identity && identity->getAutomaticAway() && server->isConnected())
56             newIdentityWithIdleTimeMapping[identityId] = identityIdleTime;
57     }
58 
59     m_identitiesWithIdleTimesOnAutoAway = newIdentityWithIdleTimeMapping;
60 
61     identitiesOnAutoAwayChanged();
62 }
63 
identityOnline(int identityId)64 void AwayManager::identityOnline(int identityId)
65 {
66     IdentityPtr identity = Preferences::identityById(identityId);
67 
68     if (identity && identity->getAutomaticAway() &&
69         !m_identitiesWithIdleTimesOnAutoAway.contains(identityId))
70     {
71         m_identitiesWithIdleTimesOnAutoAway[identityId] = minutesToMilliseconds(identity->getAwayInactivity());
72 
73         // Notify the AwayManager implementation that a user which has auto-away enabled is not online.
74         identityOnAutoAwayWentOnline(identityId);
75     }
76 }
77 
identityOffline(int identityId)78 void AwayManager::identityOffline(int identityId)
79 {
80     if (m_identitiesWithIdleTimesOnAutoAway.remove(identityId))
81         // Notify the AwayManager implementation that a user which has auto-away enabled is now offline.
82         identityOnAutoAwayWentOffline(identityId);
83 }
84 
implementManagedAway(int identityId)85 void AwayManager::implementManagedAway(int identityId)
86 {
87     const QList<Server*> serverList = m_connectionManager->getServerList();
88 
89     for (Server* server : serverList) {
90         if (server->getIdentity()->id() == identityId && server->isConnected() && !server->isAway())
91             server->requestAway();
92     }
93 }
94 
setManagedIdentitiesAway()95 void AwayManager::setManagedIdentitiesAway()
96 {
97     QHash<int, int>::ConstIterator itr = m_identitiesWithIdleTimesOnAutoAway.constBegin();
98 
99     for (; itr != m_identitiesWithIdleTimesOnAutoAway.constEnd(); ++itr)
100         implementManagedAway(itr.key());
101 }
102 
implementManagedUnaway(const QList<int> & identityList)103 void AwayManager::implementManagedUnaway(const QList<int>& identityList)
104 {
105     const QList<Server*> serverList = m_connectionManager->getServerList();
106 
107     for (Server* server : serverList) {
108         IdentityPtr identity = server->getIdentity();
109 
110         if (identityList.contains(identity->id()) && identity->getAutomaticUnaway()
111             && server->isConnected() && server->isAway())
112         {
113             server->requestUnaway();
114         }
115     }
116 }
117 
setManagedIdentitiesUnaway()118 void AwayManager::setManagedIdentitiesUnaway()
119 {
120     // Set the "not away" status for all identities which have
121     // auto-away enabled.
122     implementManagedUnaway(m_identitiesWithIdleTimesOnAutoAway.keys());
123 }
124 
requestAllAway(const QString & reason)125 void AwayManager::requestAllAway(const QString& reason)
126 {
127     const QList<Server*> serverList = m_connectionManager->getServerList();
128 
129     for (Server* server : serverList)
130         if (server->isConnected())
131             server->requestAway(reason);
132 }
133 
requestAllUnaway()134 void AwayManager::requestAllUnaway()
135 {
136     const QList<Server*> serverList = m_connectionManager->getServerList();
137 
138     for (Server* server : serverList)
139         if (server->isConnected() && server->isAway())
140             server->requestUnaway();
141 }
142 
setGlobalAway(bool away)143 void AwayManager::setGlobalAway(bool away)
144 {
145     if (away)
146         requestAllAway();
147     else
148         requestAllUnaway();
149 }
150 
updateGlobalAwayAction(bool away)151 void AwayManager::updateGlobalAwayAction(bool away)
152 {
153     // Regardless of any implementation: If the given parameter indicates
154     // that the user is not away we should simulate user activity to
155     // ensure that the away-status of the user is really reset.
156     if (!away)
157         simulateUserActivity();
158 
159     Application* konvApp = Application::instance();
160     auto* awayAction = qobject_cast<KToggleAction*>(konvApp->getMainWindow()->actionCollection()->action(QStringLiteral("toggle_away")));
161     Konversation::TrayIcon* trayIcon = konvApp->getMainWindow()->systemTrayIcon();
162 
163     if (!awayAction)
164         return;
165 
166     if (away)
167     {
168         const QList<Server*> serverList = m_connectionManager->getServerList();
169         int awayCount = 0;
170 
171         for (Server* server : serverList) {
172             if (server->isAway())
173                 awayCount++;
174         }
175 
176         if (awayCount == serverList.count())
177         {
178             awayAction->setChecked(true);
179             awayAction->setIcon(QIcon::fromTheme(QStringLiteral("im-user")));
180             if (trayIcon) trayIcon->setAway(true);
181         }
182     }
183     else
184     {
185         awayAction->setChecked(false);
186         awayAction->setIcon(QIcon::fromTheme(QStringLiteral("im-user-away")));
187         if (trayIcon) trayIcon->setAway(false);
188     }
189 }
190 
calculateRemainingTime(int identityId)191 int AwayManager::calculateRemainingTime(int identityId)
192 {
193     // Get the idle time for the identity.
194     int identityIdleTimeout = m_identitiesWithIdleTimesOnAutoAway[identityId];
195 
196     // The remaining time until the user will be marked as "auto-away".
197     int remainingTime = identityIdleTimeout - KIdleTime::instance()->idleTime();
198 
199     return remainingTime;
200 }
201 
implementUpdateIdleTimeout(int identityId)202 void AwayManager::implementUpdateIdleTimeout(int identityId)
203 {
204     const QHash<int, int> idleTimeouts = KIdleTime::instance()->idleTimeouts();
205 
206     // calculate the remaining time until the user will be marked as away.
207     int remainingTime = calculateRemainingTime(identityId);
208 
209     // Check if the user should be away right now.
210     if (remainingTime <= 0)
211     {
212         implementMarkIdentityAway(identityId);
213 
214         // Since the user is away right now the next auto-away should occur
215         // in X minutes (where X is the timeout which the user has
216         // configured for the identity).
217         remainingTime = m_identitiesWithIdleTimesOnAutoAway[identityId];
218     }
219 
220     // Get the timer which is currently active for the identity.
221     int timerId = m_timerForIdentity.key(identityId, -1);
222 
223     // Checks if we already have a timer for the given identity.
224     // If we already have a timer we need to make sure that we're always
225     // waiting for the remaining time.
226     if (idleTimeouts[timerId] != remainingTime)
227     {
228         // Remove the idle timeout.
229         implementRemoveIdleTimeout(timerId);
230 
231         // Then also reset the timer ID (as the timer does not exist anymore).
232         timerId = -1;
233     }
234 
235     // Check if we already have a timer.
236     if (timerId == -1)
237         // If not create a new timer.
238         implementAddIdleTimeout(identityId, remainingTime);
239 }
240 
implementAddIdleTimeout(int identityId,int idleTime)241 void AwayManager::implementAddIdleTimeout(int identityId, int idleTime)
242 {
243     // Create a new timer.
244     int newTimerId = KIdleTime::instance()->addIdleTimeout(idleTime);
245 
246     // Make sure we keep track of the identity <-> KIdleTimer mapping.
247     m_timerForIdentity[newTimerId] = identityId;
248 }
249 
implementRemoveIdleTimeout(int timerId)250 void AwayManager::implementRemoveIdleTimeout(int timerId)
251 {
252     // Make sure we got a valid timer ID.
253     if (timerId != -1)
254     {
255         // Remove the idle timeout.
256         KIdleTime::instance()->removeIdleTimeout(timerId);
257 
258         // Also remove the timer/identity mapping from our hashtable.
259         m_timerForIdentity.remove(timerId);
260     }
261 }
262 
implementMarkIdentityAway(int identityId)263 void AwayManager::implementMarkIdentityAway(int identityId)
264 {
265     // Mark the current identity as away.
266     implementManagedAway(identityId);
267 
268     // As at least one identity is away we have to catch the next
269     // resume event.
270     KIdleTime::instance()->catchNextResumeEvent();
271 }
272 
simulateUserActivity()273 void AwayManager::simulateUserActivity()
274 {
275     // Tell KIdleTime that it should reset the user's idle status.
276     KIdleTime::instance()->simulateUserActivity();
277 }
278 
resumeFromIdle()279 void AwayManager::resumeFromIdle()
280 {
281     // We are not idle anymore.
282     setManagedIdentitiesUnaway();
283 
284     QHash<int, int>::ConstIterator itr = m_identitiesWithIdleTimesOnAutoAway.constBegin();
285 
286     for (; itr != m_identitiesWithIdleTimesOnAutoAway.constEnd(); ++itr)
287         // Update the idle timeout for the identity to the configured timeout.
288         // This is needed in case the timer is not set to fire after the full
289         // away time but a shorter time (for example if the user was away on startup
290         // we want the timer to fire after "configured away-time" minus "time the user
291         // is already idle"). Then the timer's interval is wrong.
292         // Now (if needed) we simply remove the old timer and add a new one with the
293         // correct interval.
294         implementUpdateIdleTimeout(itr.key());
295 }
296 
idleTimeoutReached(int timerId)297 void AwayManager::idleTimeoutReached(int timerId)
298 {
299     // Get the identity ID for the given timer ID.
300     int identityId = m_timerForIdentity[timerId];
301 
302     // Mark the identity as away.
303     implementMarkIdentityAway(identityId);
304 }
305 
identitiesOnAutoAwayChanged()306 void AwayManager::identitiesOnAutoAwayChanged()
307 {
308     const QList<Server*> serverList = m_connectionManager->getServerList();
309 
310     // Since the list of identities has changed we want to drop all timers.
311     KIdleTime::instance()->removeAllIdleTimeouts();
312 
313     // Also clear the list of identity <-> timer mappings.
314     m_timerForIdentity.clear();
315 
316     for (Server* server : serverList) {
317         IdentityPtr identity = server->getIdentity();
318         int identityId = identity->id();
319 
320         // Only add idle timeouts for identities which have auto-away
321         // enabled.
322         if (m_identitiesWithIdleTimesOnAutoAway.contains(identityId))
323             // Update the idle timeout for the current identity.
324             implementUpdateIdleTimeout(identityId);
325     }
326 }
327 
identityOnAutoAwayWentOnline(int identityId)328 void AwayManager::identityOnAutoAwayWentOnline(int identityId)
329 {
330     // Simply update the idle timeout for the identity (this will
331     // take care of all necessary calculations).
332     implementUpdateIdleTimeout(identityId);
333 }
334 
identityOnAutoAwayWentOffline(int identityId)335 void AwayManager::identityOnAutoAwayWentOffline(int identityId)
336 {
337     // Get the timer for the given identity.
338     int timerId = m_timerForIdentity.key(identityId, -1);
339 
340     // Then remove the timer.
341     implementRemoveIdleTimeout(timerId);
342 }
343 
344 
345