1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include "PresenterTimer.hxx"
21
22 #include <com/sun/star/lang/XMultiComponentFactory.hpp>
23 #include <com/sun/star/frame/Desktop.hpp>
24 #include <com/sun/star/frame/XTerminateListener.hpp>
25
26 #include <osl/thread.hxx>
27 #include <osl/conditn.hxx>
28
29 #include <algorithm>
30 #include <memory>
31 #include <mutex>
32 #include <set>
33
34 using namespace ::com::sun::star;
35 using namespace ::com::sun::star::uno;
36
37 namespace sdext::presenter {
38
39 namespace {
40 class TimerTask
41 {
42 public:
43 TimerTask (
44 const PresenterTimer::Task& rTask,
45 const TimeValue& rDueTime,
46 const sal_Int64 nRepeatInterval,
47 const sal_Int32 nTaskId);
48
49 PresenterTimer::Task maTask;
50 TimeValue maDueTime;
51 const sal_Int64 mnRepeatInterval;
52 const sal_Int32 mnTaskId;
53 bool mbIsCanceled;
54 };
55
56 typedef std::shared_ptr<TimerTask> SharedTimerTask;
57
58 class TimerTaskComparator
59 {
60 public:
operator ()(const SharedTimerTask & rpTask1,const SharedTimerTask & rpTask2) const61 bool operator() (const SharedTimerTask& rpTask1, const SharedTimerTask& rpTask2) const
62 {
63 return rpTask1->maDueTime.Seconds < rpTask2->maDueTime.Seconds
64 || (rpTask1->maDueTime.Seconds == rpTask2->maDueTime.Seconds
65 && rpTask1->maDueTime.Nanosec < rpTask2->maDueTime.Nanosec);
66 }
67 };
68
69 /** Queue all scheduled tasks and process them when their time has come.
70 */
71 class TimerScheduler
72 : public std::enable_shared_from_this<TimerScheduler>,
73 public ::osl::Thread
74 {
75 public:
76 static std::shared_ptr<TimerScheduler> Instance(
77 uno::Reference<uno::XComponentContext> const& xContext);
78 static SharedTimerTask CreateTimerTask (
79 const PresenterTimer::Task& rTask,
80 const TimeValue& rDueTime,
81 const sal_Int64 nRepeatInterval);
82
83 void ScheduleTask (const SharedTimerTask& rpTask);
84 void CancelTask (const sal_Int32 nTaskId);
85
86 static bool GetCurrentTime (TimeValue& rCurrentTime);
87 static sal_Int64 GetTimeDifference (
88 const TimeValue& rTargetTime,
89 const TimeValue& rCurrentTime);
90 static void ConvertToTimeValue (
91 TimeValue& rTimeValue,
92 const sal_Int64 nTimeDifference);
93 static sal_Int64 ConvertFromTimeValue (
94 const TimeValue& rTimeValue);
95
96 static void NotifyTermination();
97 #if !defined NDEBUG
HasInstance()98 static bool HasInstance() { return mpInstance != nullptr; }
99 #endif
100
101 private:
102 static std::shared_ptr<TimerScheduler> mpInstance;
103 static std::mutex maInstanceMutex;
104 std::shared_ptr<TimerScheduler> mpLateDestroy; // for clean exit
105 static sal_Int32 mnTaskId;
106
107 std::mutex maTaskContainerMutex;
108 typedef ::std::set<SharedTimerTask,TimerTaskComparator> TaskContainer;
109 TaskContainer maScheduledTasks;
110 std::mutex maCurrentTaskMutex;
111 SharedTimerTask mpCurrentTask;
112 ::osl::Condition m_Shutdown;
113
114 TimerScheduler(
115 uno::Reference<uno::XComponentContext> const& xContext);
116 public:
117 virtual void SAL_CALL run() override;
onTerminated()118 virtual void SAL_CALL onTerminated() override { mpLateDestroy.reset(); }
119 };
120
121 class TerminateListener
122 : public ::cppu::WeakImplHelper<frame::XTerminateListener>
123 {
~TerminateListener()124 virtual ~TerminateListener() override
125 {
126 assert(!TimerScheduler::HasInstance());
127 }
128
disposing(lang::EventObject const &)129 virtual void SAL_CALL disposing(lang::EventObject const&) override
130 {
131 }
132
queryTermination(lang::EventObject const &)133 virtual void SAL_CALL queryTermination(lang::EventObject const&) override
134 {
135 }
136
notifyTermination(lang::EventObject const &)137 virtual void SAL_CALL notifyTermination(lang::EventObject const&) override
138 {
139 TimerScheduler::NotifyTermination();
140 }
141 };
142
143 } // end of anonymous namespace
144
145 //===== PresenterTimer ========================================================
146
ScheduleRepeatedTask(const uno::Reference<uno::XComponentContext> & xContext,const Task & rTask,const sal_Int64 nDelay,const sal_Int64 nInterval)147 sal_Int32 PresenterTimer::ScheduleRepeatedTask (
148 const uno::Reference<uno::XComponentContext>& xContext,
149 const Task& rTask,
150 const sal_Int64 nDelay,
151 const sal_Int64 nInterval)
152 {
153 assert(xContext.is());
154 TimeValue aCurrentTime;
155 if (TimerScheduler::GetCurrentTime(aCurrentTime))
156 {
157 TimeValue aDueTime;
158 TimerScheduler::ConvertToTimeValue(
159 aDueTime,
160 TimerScheduler::ConvertFromTimeValue (aCurrentTime) + nDelay);
161 SharedTimerTask pTask (TimerScheduler::CreateTimerTask(rTask, aDueTime, nInterval));
162 TimerScheduler::Instance(xContext)->ScheduleTask(pTask);
163 return pTask->mnTaskId;
164 }
165
166 return NotAValidTaskId;
167 }
168
CancelTask(const sal_Int32 nTaskId)169 void PresenterTimer::CancelTask (const sal_Int32 nTaskId)
170 {
171 auto const pInstance(TimerScheduler::Instance(nullptr));
172 if (pInstance)
173 {
174 pInstance->CancelTask(nTaskId);
175 }
176 }
177
178 //===== TimerScheduler ========================================================
179
180 std::shared_ptr<TimerScheduler> TimerScheduler::mpInstance;
181 std::mutex TimerScheduler::maInstanceMutex;
182 sal_Int32 TimerScheduler::mnTaskId = PresenterTimer::NotAValidTaskId;
183
Instance(uno::Reference<uno::XComponentContext> const & xContext)184 std::shared_ptr<TimerScheduler> TimerScheduler::Instance(
185 uno::Reference<uno::XComponentContext> const& xContext)
186 {
187 std::lock_guard aGuard (maInstanceMutex);
188 if (mpInstance == nullptr)
189 {
190 if (!xContext.is())
191 return nullptr;
192 mpInstance.reset(new TimerScheduler(xContext));
193 mpInstance->create();
194 }
195 return mpInstance;
196 }
197
TimerScheduler(uno::Reference<uno::XComponentContext> const & xContext)198 TimerScheduler::TimerScheduler(
199 uno::Reference<uno::XComponentContext> const& xContext)
200 : maTaskContainerMutex(),
201 maScheduledTasks(),
202 maCurrentTaskMutex(),
203 mpCurrentTask()
204 {
205 uno::Reference<frame::XDesktop> const xDesktop(
206 frame::Desktop::create(xContext));
207 uno::Reference<frame::XTerminateListener> const xListener(
208 new TerminateListener);
209 // assuming the desktop can take ownership
210 xDesktop->addTerminateListener(xListener);
211 }
212
CreateTimerTask(const PresenterTimer::Task & rTask,const TimeValue & rDueTime,const sal_Int64 nRepeatInterval)213 SharedTimerTask TimerScheduler::CreateTimerTask (
214 const PresenterTimer::Task& rTask,
215 const TimeValue& rDueTime,
216 const sal_Int64 nRepeatInterval)
217 {
218 return std::make_shared<TimerTask>(rTask, rDueTime, nRepeatInterval, ++mnTaskId);
219 }
220
ScheduleTask(const SharedTimerTask & rpTask)221 void TimerScheduler::ScheduleTask (const SharedTimerTask& rpTask)
222 {
223 if (!rpTask)
224 return;
225 if (rpTask->mbIsCanceled)
226 return;
227
228 {
229 std::lock_guard aTaskGuard (maTaskContainerMutex);
230 maScheduledTasks.insert(rpTask);
231 }
232 }
233
CancelTask(const sal_Int32 nTaskId)234 void TimerScheduler::CancelTask (const sal_Int32 nTaskId)
235 {
236 // Set of scheduled tasks is sorted after their due times, not their
237 // task ids. Therefore we have to do a linear search for the task to
238 // cancel.
239 {
240 std::lock_guard aGuard (maTaskContainerMutex);
241 auto iTask = std::find_if(maScheduledTasks.begin(), maScheduledTasks.end(),
242 [nTaskId](const SharedTimerTask& rxTask) { return rxTask->mnTaskId == nTaskId; });
243 if (iTask != maScheduledTasks.end())
244 maScheduledTasks.erase(iTask);
245 }
246
247 // The task that is to be canceled may be currently about to be
248 // processed. Mark it with a flag that a) prevents a repeating task
249 // from being scheduled again and b) tries to prevent its execution.
250 {
251 std::lock_guard aGuard (maCurrentTaskMutex);
252 if (mpCurrentTask
253 && mpCurrentTask->mnTaskId == nTaskId)
254 mpCurrentTask->mbIsCanceled = true;
255 }
256
257 // Let the main-loop cleanup in its own time
258 }
259
NotifyTermination()260 void TimerScheduler::NotifyTermination()
261 {
262 std::shared_ptr<TimerScheduler> const pInstance(TimerScheduler::mpInstance);
263 if (!pInstance)
264 {
265 return;
266 }
267
268 {
269 std::lock_guard aGuard(pInstance->maTaskContainerMutex);
270 pInstance->maScheduledTasks.clear();
271 }
272
273 {
274 std::lock_guard aGuard(pInstance->maCurrentTaskMutex);
275 if (pInstance->mpCurrentTask)
276 {
277 pInstance->mpCurrentTask->mbIsCanceled = true;
278 }
279 }
280
281 pInstance->m_Shutdown.set();
282
283 // rhbz#1425304 join thread before shutdown
284 pInstance->join();
285 }
286
run()287 void SAL_CALL TimerScheduler::run()
288 {
289 osl_setThreadName("sdext::presenter::TimerScheduler");
290
291 while (true)
292 {
293 // Get the current time.
294 TimeValue aCurrentTime;
295 if ( ! GetCurrentTime(aCurrentTime))
296 {
297 // We can not get the current time and thus can not schedule anything.
298 break;
299 }
300
301 // Restrict access to the maScheduledTasks member to one, mutex
302 // guarded, block.
303 SharedTimerTask pTask;
304 sal_Int64 nDifference = 0;
305 {
306 std::lock_guard aGuard (maTaskContainerMutex);
307
308 // There are no more scheduled task. Leave this loop, function and
309 // live of the TimerScheduler.
310 if (maScheduledTasks.empty())
311 break;
312
313 nDifference = GetTimeDifference(
314 (*maScheduledTasks.begin())->maDueTime,
315 aCurrentTime);
316 if (nDifference <= 0)
317 {
318 pTask = *maScheduledTasks.begin();
319 maScheduledTasks.erase(maScheduledTasks.begin());
320 }
321 }
322
323 // Acquire a reference to the current task.
324 {
325 std::lock_guard aGuard (maCurrentTaskMutex);
326 mpCurrentTask = pTask;
327 }
328
329 if (!pTask)
330 {
331 // Wait until the first task becomes due.
332 TimeValue aTimeValue;
333 ConvertToTimeValue(aTimeValue, nDifference);
334 // wait on condition variable, so the thread can be stopped
335 m_Shutdown.wait(&aTimeValue);
336 }
337 else
338 {
339 // Execute task.
340 if (pTask->maTask && !pTask->mbIsCanceled)
341 {
342 pTask->maTask(aCurrentTime);
343
344 // Re-schedule repeating tasks.
345 if (pTask->mnRepeatInterval > 0)
346 {
347 ConvertToTimeValue(
348 pTask->maDueTime,
349 ConvertFromTimeValue(pTask->maDueTime)
350 + pTask->mnRepeatInterval);
351 ScheduleTask(pTask);
352 }
353 }
354
355 }
356
357 // Release reference to the current task.
358 {
359 std::lock_guard aGuard (maCurrentTaskMutex);
360 mpCurrentTask.reset();
361 }
362 }
363
364 // While holding maInstanceMutex
365 std::lock_guard aInstance( maInstanceMutex );
366 mpLateDestroy = mpInstance;
367 mpInstance.reset();
368 }
369
GetCurrentTime(TimeValue & rCurrentTime)370 bool TimerScheduler::GetCurrentTime (TimeValue& rCurrentTime)
371 {
372 TimeValue aSystemTime;
373 if (osl_getSystemTime(&aSystemTime))
374 return osl_getLocalTimeFromSystemTime(&aSystemTime, &rCurrentTime);
375 return false;
376 }
377
GetTimeDifference(const TimeValue & rTargetTime,const TimeValue & rCurrentTime)378 sal_Int64 TimerScheduler::GetTimeDifference (
379 const TimeValue& rTargetTime,
380 const TimeValue& rCurrentTime)
381 {
382 return ConvertFromTimeValue(rTargetTime) - ConvertFromTimeValue(rCurrentTime);
383 }
384
ConvertToTimeValue(TimeValue & rTimeValue,const sal_Int64 nTimeDifference)385 void TimerScheduler::ConvertToTimeValue (
386 TimeValue& rTimeValue,
387 const sal_Int64 nTimeDifference)
388 {
389 rTimeValue.Seconds = sal::static_int_cast<sal_Int32>(nTimeDifference / 1000000000L);
390 rTimeValue.Nanosec = sal::static_int_cast<sal_Int32>(nTimeDifference % 1000000000L);
391 }
392
ConvertFromTimeValue(const TimeValue & rTimeValue)393 sal_Int64 TimerScheduler::ConvertFromTimeValue (
394 const TimeValue& rTimeValue)
395 {
396 return sal_Int64(rTimeValue.Seconds) * 1000000000L + rTimeValue.Nanosec;
397 }
398
399 //===== TimerTask =============================================================
400
401 namespace {
402
TimerTask(const PresenterTimer::Task & rTask,const TimeValue & rDueTime,const sal_Int64 nRepeatInterval,const sal_Int32 nTaskId)403 TimerTask::TimerTask (
404 const PresenterTimer::Task& rTask,
405 const TimeValue& rDueTime,
406 const sal_Int64 nRepeatInterval,
407 const sal_Int32 nTaskId)
408 : maTask(rTask),
409 maDueTime(rDueTime),
410 mnRepeatInterval(nRepeatInterval),
411 mnTaskId(nTaskId),
412 mbIsCanceled(false)
413 {
414 }
415
416 } // end of anonymous namespace
417
418 //===== PresenterTimer ========================================================
419
420 ::rtl::Reference<PresenterClockTimer> PresenterClockTimer::mpInstance;
421
Instance(const css::uno::Reference<css::uno::XComponentContext> & rxContext)422 ::rtl::Reference<PresenterClockTimer> PresenterClockTimer::Instance (
423 const css::uno::Reference<css::uno::XComponentContext>& rxContext)
424 {
425 ::osl::MutexGuard aSolarGuard (::osl::Mutex::getGlobalMutex());
426
427 ::rtl::Reference<PresenterClockTimer> pTimer;
428 if (mpInstance.is())
429 {
430 pTimer = mpInstance;
431 }
432 if ( ! pTimer.is())
433 {
434 pTimer.set(new PresenterClockTimer(rxContext));
435 mpInstance = pTimer;
436 }
437 return pTimer;
438 }
439
PresenterClockTimer(const Reference<XComponentContext> & rxContext)440 PresenterClockTimer::PresenterClockTimer (const Reference<XComponentContext>& rxContext)
441 : PresenterClockTimerInterfaceBase(m_aMutex),
442 maListeners(),
443 maDateTime(),
444 mnTimerTaskId(PresenterTimer::NotAValidTaskId),
445 mbIsCallbackPending(false),
446 mxRequestCallback()
447 , m_xContext(rxContext)
448 {
449 assert(m_xContext.is());
450 Reference<lang::XMultiComponentFactory> xFactory =
451 rxContext->getServiceManager();
452 if (xFactory.is())
453 mxRequestCallback.set(
454 xFactory->createInstanceWithContext(
455 "com.sun.star.awt.AsyncCallback",
456 rxContext),
457 UNO_QUERY_THROW);
458 }
459
~PresenterClockTimer()460 PresenterClockTimer::~PresenterClockTimer()
461 {
462 if (mnTimerTaskId != PresenterTimer::NotAValidTaskId)
463 {
464 PresenterTimer::CancelTask(mnTimerTaskId);
465 mnTimerTaskId = PresenterTimer::NotAValidTaskId;
466 }
467
468 Reference<lang::XComponent> xComponent (mxRequestCallback, UNO_QUERY);
469 if (xComponent.is())
470 xComponent->dispose();
471 mxRequestCallback = nullptr;
472 }
473
AddListener(const SharedListener & rListener)474 void PresenterClockTimer::AddListener (const SharedListener& rListener)
475 {
476 osl::MutexGuard aGuard (maMutex);
477
478 maListeners.push_back(rListener);
479
480 // Create a timer task when the first listener is added.
481 if (mnTimerTaskId==PresenterTimer::NotAValidTaskId)
482 {
483 mnTimerTaskId = PresenterTimer::ScheduleRepeatedTask(
484 m_xContext,
485 [this] (TimeValue const& rTime) { return this->CheckCurrentTime(rTime); },
486 0,
487 250000000 /*ns*/);
488 }
489 }
490
RemoveListener(const SharedListener & rListener)491 void PresenterClockTimer::RemoveListener (const SharedListener& rListener)
492 {
493 osl::MutexGuard aGuard (maMutex);
494
495 ListenerContainer::iterator iListener (::std::find(
496 maListeners.begin(),
497 maListeners.end(),
498 rListener));
499 if (iListener != maListeners.end())
500 maListeners.erase(iListener);
501 if (maListeners.empty())
502 {
503 // We have no more clients and therefore are not interested in time changes.
504 if (mnTimerTaskId != PresenterTimer::NotAValidTaskId)
505 {
506 PresenterTimer::CancelTask(mnTimerTaskId);
507 mnTimerTaskId = PresenterTimer::NotAValidTaskId;
508 }
509 mpInstance = nullptr;
510 }
511 }
512
GetCurrentTime()513 oslDateTime PresenterClockTimer::GetCurrentTime()
514 {
515 TimeValue aCurrentTime;
516 TimerScheduler::GetCurrentTime(aCurrentTime);
517 oslDateTime aDateTime;
518 osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime);
519 return aDateTime;
520 }
521
CheckCurrentTime(const TimeValue & rCurrentTime)522 void PresenterClockTimer::CheckCurrentTime (const TimeValue& rCurrentTime)
523 {
524 css::uno::Reference<css::awt::XRequestCallback> xRequestCallback;
525 css::uno::Reference<css::awt::XCallback> xCallback;
526 {
527 osl::MutexGuard aGuard (maMutex);
528
529 TimeValue aCurrentTime (rCurrentTime);
530 oslDateTime aDateTime;
531 if (osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime))
532 {
533 if (aDateTime.Seconds != maDateTime.Seconds
534 || aDateTime.Minutes != maDateTime.Minutes
535 || aDateTime.Hours != maDateTime.Hours)
536 {
537 // The displayed part of the current time has changed.
538 // Prepare to call the listeners.
539 maDateTime = aDateTime;
540
541 // Schedule notification of listeners.
542 if (mxRequestCallback.is() && ! mbIsCallbackPending)
543 {
544 mbIsCallbackPending = true;
545 xRequestCallback = mxRequestCallback;
546 xCallback = this;
547 }
548 }
549 }
550 }
551 if (xRequestCallback.is() && xCallback.is())
552 xRequestCallback->addCallback(xCallback, Any());
553 }
554
555 //----- XCallback -------------------------------------------------------------
556
notify(const css::uno::Any &)557 void SAL_CALL PresenterClockTimer::notify (const css::uno::Any&)
558 {
559 ListenerContainer aListenerCopy;
560
561 {
562 osl::MutexGuard aGuard (maMutex);
563
564 mbIsCallbackPending = false;
565
566 aListenerCopy = maListeners;
567 }
568
569 for (const auto& rxListener : aListenerCopy)
570 {
571 rxListener->TimeHasChanged(maDateTime);
572 }
573 }
574
575 } // end of namespace ::sdext::presenter
576
577 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
578