From 0faab3af330f49f58f357446cbde96470edcb624 Mon Sep 17 00:00:00 2001 From: Chris Tallon Date: Sun, 4 Mar 2007 01:26:31 +0000 Subject: [PATCH] New timers method --- command.cc | 31 ++--- message.h | 2 +- threadp.cc | 15 +++ threadp.h | 4 + ticonfig.c | 2 +- timers.cc | 293 +++++++++++++++++++++++++++------------------- timers.h | 29 ++++- vchannelselect.cc | 4 +- viewman.cc | 7 ++ vlivebanner.cc | 9 +- vmute.cc | 2 +- vradiorec.cc | 7 +- vtimerlist.cc | 16 ++- vvideorec.cc | 8 +- vvolume.cc | 2 +- vwelcome.cc | 8 +- 16 files changed, 278 insertions(+), 161 deletions(-) diff --git a/command.cc b/command.cc index 1ab1417..b6f0977 100644 --- a/command.cc +++ b/command.cc @@ -214,9 +214,8 @@ void Command::postMessageNoLock(Message* m) bool Command::postMessageIfNotBusy(Message* m) { - // This is for the timers module - // If the masterlock is locked then the timers module wants to - // cancel delivery + // Used for Windows mouse events + //logger->log("Command", Log::DEBUG, "TRY LOCK"); #ifndef WIN32 if (pthread_mutex_trylock(&masterLock) != EBUSY) @@ -295,28 +294,14 @@ void Command::postMessageFromOuterSpace(Message* m) void Command::processMessage(Message* m) { - logger->log("Command", Log::DEBUG, "processing message %i", m->message); - - // Timer handling is very weird at the mo. Take them out here and convert - if (m->message == Message::TIMER) - { - // FIXME - go to one message queue only - then instead of having - // objects deriving from messagequeues, make them derive from - // messagereceiver - then one messagequeue can deliver any message to anywhere - // FIXME - a slight modification - how if messagereceivers were to register // themselves as receivers to avoid the calling-a-deleted-object problem // then only deliver/register/unregister would have to be protected - // deliver timer + logger->log("Command", Log::DEBUG, "processing message %i", m->message); + - logger->log("Command", Log::DEBUG, "sending timer to %p with parameter %u", m->to, m->parameter); - ((TimerReceiver*)m->to)->timercall(m->parameter); -// handleCommand(Remote::NA_NONE); // in case any timer has posted messages to viewman, -// // run viewman message queue here. FIXME improve this! -// break; - } - else if (m->to == this) + if (m->to == this) { switch(m->message) { @@ -790,9 +775,9 @@ void Command::doJustConnected(VConnect* vconnect) viewman->updateView(vw); // Enter pre-keys here -// handleCommand(Remote::THREE); -// handleCommand(Remote::UP); -// handleCommand(Remote::PLAY); + handleCommand(Remote::THREE); + handleCommand(Remote::UP); + handleCommand(Remote::PLAY); // handleCommand(Remote::DOWN); // handleCommand(Remote::DOWN); // handleCommand(Remote::DOWN); diff --git a/message.h b/message.h index 146c899..0c7b701 100644 --- a/message.h +++ b/message.h @@ -57,7 +57,7 @@ class Message const static ULONG ADD_VIEW = 12; const static ULONG STREAM_END = 13; const static ULONG REDRAW_LANG = 14; - const static ULONG TIMER = 15; + const static ULONG REDRAW = 15; const static ULONG EPG = 16; const static ULONG EPG_CLOSE = 17; const static ULONG CHANGED_OPTIONS = 18; diff --git a/threadp.cc b/threadp.cc index 019c54d..f97fb8f 100644 --- a/threadp.cc +++ b/threadp.cc @@ -101,3 +101,18 @@ void ThreadP::threadSetKillable() pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); } + +void ThreadP::threadSuicide() +{ + if(!pthread_detach(pthread_self())) + { + MILLISLEEP(1000); + if(!pthread_detach(pthread_self())) + { + MILLISLEEP(1000); + pthread_detach(pthread_self()); + } + } + + pthread_exit(NULL); +} diff --git a/threadp.h b/threadp.h index 5461f86..419a93e 100644 --- a/threadp.h +++ b/threadp.h @@ -24,6 +24,7 @@ #include #include +#include "defines.h" #include "thread.h" class ThreadP : public Thread @@ -54,6 +55,9 @@ class ThreadP : public Thread pthread_t pthread; pthread_cond_t threadCond; pthread_mutex_t threadCondMutex; + + public: + static void threadSuicide(); // Self termination }; #endif diff --git a/ticonfig.c b/ticonfig.c index 8c9e0e2..deb8296 100644 --- a/ticonfig.c +++ b/ticonfig.c @@ -1,4 +1,4 @@ -/* Adapted for vomp by Chris Tallon by changing "int main" to "int ticonfig_main" +// Adapted for vomp by Chris Tallon by changing "int main" to "int ticonfig_main" /* * Copyright (C) 2006, Jon Gettler diff --git a/timers.cc b/timers.cc index b6674df..9c491cc 100755 --- a/timers.cc +++ b/timers.cc @@ -1,5 +1,5 @@ /* - Copyright 2004-2005 Chris Tallon + Copyright 2004-2007 Chris Tallon This file is part of VOMP. @@ -46,7 +46,6 @@ int Timers::init() logger = Log::getInstance(); threadLock(); // lock here, the thread loop will unlock and wait - //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 1"); if (!threadStart()) { shutdown(); @@ -65,14 +64,19 @@ int Timers::shutdown() threadStop(); - TimerList::iterator i; - UINT numTimers = timerList.size(); - while(numTimers) + TimerEvent* timerEvent = NULL; + TimerReceiver* client = NULL; + int clientReference = 0; + + while(timerList.size()) { - i = timerList.begin(); - delete *i; - timerList.pop_front(); - --numTimers; + threadLock(); + timerEvent = timerList.front(); + client = timerEvent->client; + clientReference = timerEvent->clientReference; + threadUnlock(); + + cancelTimer(client, clientReference); } logger->log("Timers", Log::DEBUG, "Timers shutdown end"); @@ -80,7 +84,7 @@ int Timers::shutdown() return 1; } -int Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSEC) +bool Timers::setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSEC) { if (!initted) return 0; @@ -91,25 +95,41 @@ int Timers::setTimerT(TimerReceiver* client, int clientReference, long int reque // Check that this timer is not already in the list TimerList::iterator i; - Timer* currentTimer = NULL; + TimerEvent* currentTimerEvent = NULL; for(i = timerList.begin(); i != timerList.end(); i++) { - currentTimer = *i; - if ((currentTimer->client == client) && (currentTimer->clientReference == clientReference)) + currentTimerEvent = *i; + + if ((currentTimerEvent->client == client) && (currentTimerEvent->clientReference == clientReference)) { - // Overwrite an existing timer - currentTimer->requestedTime.tv_sec = requestedTime; - currentTimer->requestedTime.tv_nsec = requestedTimeNSEC; - resetThreadFlag = true; - threadSignalNoLock(); + // Timer exists already, either waiting or running + // Update the clocks + currentTimerEvent->requestedTime.tv_sec = requestedTime; + currentTimerEvent->requestedTime.tv_nsec = requestedTimeNSEC; - //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 2 (b)"); - threadUnlock(); - return 0; + if (currentTimerEvent->running) + { + // If this timerEvent is currently running, update the clocks and set the restart flag. + // Instead of being deleted in timerEventFinished it will be restarted + currentTimerEvent->restartAfterFinish = true; + // Don't need to resetThreadFlag because this timer isn't re-live yet + threadUnlock(); + return true; + } + else + { + // A waiting timer has been edited + resetThreadFlag = true; + threadSignalNoLock(); + threadUnlock(); + return true; + } } } - Timer* t = new Timer(); + // Timer did not exist already + + TimerEvent* t = new TimerEvent(); t->client = client; t->clientReference = clientReference; t->requestedTime.tv_sec = requestedTime; @@ -124,10 +144,10 @@ int Timers::setTimerT(TimerReceiver* client, int clientReference, long int reque logger->log("Timers", Log::DEBUG, "Timer set for %p ref %i", client, clientReference); - return 1; + return true; } -int Timers::setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs) +bool Timers::setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs) { struct timespec currentTime; @@ -159,51 +179,125 @@ int Timers::setTimerD(TimerReceiver* client, int clientReference, long int reque return setTimerT(client, clientReference, requestedTime, requestedTimeNSEC); } -int Timers::cancelTimer(TimerReceiver* client, int clientReference) +bool Timers::cancelTimer(TimerReceiver* client, int clientReference) { - if (!initted) return 0; + /* This method locks the timers mutex + Then one of three things can happen: + 1. The TimerEvent is found, running = false. This means it hasn't started yet. + Delete the timer normally, set resetFlag + 2. The TimerEvent is found, running = true. This means the timer is currently firing, + timercall on the client is being called. + In this case, this thread calling cancelTimer needs to unlock and wait for the + timercall thread to get back. (sleeps or signalling) + 3. The TimerEvent is not found. Client error or the thread returned to + the Timers module in between client calling cancelTimer and cancelTimer actually + running. Do nothing, return normally. + + By making sure there is no waiting timerevent, and no running timerevent, this ensures + that the program cannot segfault because a timer fired on a just deleted object. + + */ + + if (!initted) return false; logger->log("Timers", Log::DEBUG, "Starting cancel timer %p %i, list size = %i", client, clientReference, timerList.size()); - //logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 4"); + while(1) + { + threadLock(); + + TimerList::iterator i; + TimerEvent* currentTimerEvent = NULL; + for(i = timerList.begin(); i != timerList.end(); i++) + { + currentTimerEvent = *i; + if ((currentTimerEvent->client == client) && (currentTimerEvent->clientReference == clientReference)) + { + break; + } + } + + if (i == timerList.end()) + { + // Case 3, no timer found + threadUnlock(); + return true; + } + else + { + // Timer found, Case 1 or 2 + + if (currentTimerEvent->running == false) + { + // Case 1. Just delete the timer and reset the thread. + timerList.erase(i); + delete currentTimerEvent; + logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference); + resetThreadFlag = true; + threadSignalNoLock(); + threadUnlock(); + return true; + } + else + { + // Case 2. For now, use polling with a 50ms delay. + // Don't delete a running timer. + // FIXME upgrade me to signalling + + logger->log("Timers", Log::DEBUG, "%p ref %i cancelTimer WAITING", client, clientReference); + + threadUnlock(); + MILLISLEEP(50); + } + } + } // end of the big while loop +} + +void Timers::timerEventFinished(TimerEvent* timerEvent) +{ + // This function takes out the already timercall'd TimerEvent from the list + // Or resets it if restart flag is true + + + if (!initted) return; threadLock(); - //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 4"); - TimerList::iterator i; - Timer* currentTimer = NULL; - for(i = timerList.begin(); i != timerList.end(); i++) + + logger->log("Timers", Log::DEBUG, "timerEventFinished for %p", timerEvent->client); + + for(TimerList::iterator i = timerList.begin(); i != timerList.end(); i++) { - currentTimer = *i; - //logger->log("Timers", Log::DEBUG, "I: %p %i : %p %i", client, clientReference, currentTimer->client, currentTimer->clientReference); - if ((currentTimer->client == client) && (currentTimer->clientReference == clientReference)) + if (timerEvent != *i) continue; + + if (timerEvent->restartAfterFinish) + { + logger->log("Timers", Log::DEBUG, "timerEventFinished RESTART for %p", timerEvent->client); + + timerEvent->restartAfterFinish = false; + timerEvent->running = false; + resetThreadFlag = true; + threadSignalNoLock(); + } + else { + // The removal of a called and non-restart TimerEvent doesn't need the threadMethod to be reset timerList.erase(i); - logger->log("Timers", Log::DEBUG, "Removed timer for %p ref %i", client, clientReference); - break; - // At this point currentTimer is not in the list but might still be nextTimer in the thread + logger->log("Timers", Log::DEBUG, "timerEventFinished for %p %i - remove done", timerEvent->client, timerEvent->clientReference); + delete timerEvent; } - } - if (i == timerList.end()) - { - // no timer found - logger->log("Timers", Log::DEBUG, "No timer found in cancelTimer %p ref %i", client, clientReference); - //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 4"); - threadUnlock(); - return 0; - } - resetThreadFlag = true; - threadSignalNoLock(); - //logger->log("Timers", Log::DEBUG, "about to un-LOCK -TIMERS- MUTEX 4"); + break; + } + // FIXME At this point, this should signal all threads waiting on cancelTimer threadUnlock(); - - return 1; + // Kill this thread, as it's the one started for the timer event + Thread_TYPE::threadSuicide(); } void Timers::threadMethod() { struct timespec nextTime; - Timer* nextTimer = NULL; + TimerEvent* nextTimer = NULL; resetThreadFlag = true; // locked here @@ -221,10 +315,12 @@ void Timers::threadMethod() nextTimer = NULL; TimerList::iterator i; - Timer* currentTimer = NULL; + TimerEvent* currentTimer = NULL; for(i = timerList.begin(); i != timerList.end(); i++) { currentTimer = *i; + if (currentTimer->running) continue; // has already been timercall'd + if (!nextTimer) { nextTime.tv_sec = currentTimer->requestedTime.tv_sec; @@ -271,6 +367,8 @@ void Timers::threadMethod() // unlocks in the wait } + // Mutex locked again here by exit of wait or timedwait above + // ok. we have been signalled or the time has run out // This only gets signalled if it is to reset or die @@ -285,77 +383,34 @@ void Timers::threadMethod() Log::getInstance()->log("Timers", Log::DEBUG, "Timer firing for client %p ref %i", nextTimer->client, nextTimer->clientReference); - // send this timer to the timer receiver, via the command message queue - // so that the gui mutex is locked when it happens - - Message* m = new Message(); // Timer call, must be injected into master mutex (this is generated outside the mutex) - m->from = this; - m->to = nextTimer->client; - m->message = Message::TIMER; - m->parameter = nextTimer->clientReference; - - if (!Command::getInstance()->postMessageIfNotBusy(m)) - { - // GUI mutex was locked - // abort this timer delivery - it might be trying to be deleted! - delete m; - - // now unlock the timers mutex for a fraction of a second - // in case the gui thread is waiting on the timers mutex - threadUnlock(); - //logger->log("Timers", Log::DEBUG, "un-LOCKED -TIMERS- MUTEX (3)"); - //printf("\n\n\n WOOOOO \n\n\n The anti deadlock code is working!!! \n\n\n"); - MILLISLEEP(20); // 10ms - too long? too short? - //logger->log("Timers", Log::DEBUG, "Waiting for LOCK -TIMERS- MUTEX 7"); - threadLock(); - //logger->log("Timers", Log::DEBUG, "LOCKED -TIMERS- MUTEX 7"); - resetThreadFlag = true; - } - else - { - // timer was delivered - timerList.remove(nextTimer); - delete nextTimer; - nextTimer = NULL; - resetThreadFlag = true; - } + nextTimer->run(); // sets timerevent to running and starts it + resetThreadFlag = true; // find a new timer to wait on } } -/* - -Avoiding deadlock using the timer class... - -Situation: -timer condwait finishes -timers is about to fire a timer -timers locks timers-mutex - user presses a button - command locks gui-mutex +// Class TimerEvent -timers tries to get gui-mutex - - view receives button - view wants to delete itself - view tries to deletetimer - goes into delete timer - waits on timers mutex - -- deadlock - - -Solution: +TimerEvent::TimerEvent() +{ + running = false; + restartAfterFinish = false; + client = NULL; + clientReference = 0; + requestedTime.tv_sec = 0; + requestedTime.tv_nsec = 0; +} -timers tries to get gui mutex -if mutex is locked already abort -unlock timers mutex -wait a fraction of time -(allow other thread to lock timers mutex) -lock timers mutex -set reset flag to recalculate -- if timer has been cancelled next timer will be calced -- if timer has not been cancelled it will be called next +void TimerEvent::threadMethod() +{ + Log::getInstance()->log("Timers", Log::DEBUG, "sending timer to %p with parameter %u", client, clientReference); + client->timercall(clientReference); + Timers::getInstance()->timerEventFinished(this); // does not return +} -*/ +void TimerEvent::run() +{ + running = true; + threadStart(); +} diff --git a/timers.h b/timers.h index 09dddc0..3c95e60 100755 --- a/timers.h +++ b/timers.h @@ -57,19 +57,34 @@ You can reset a timer by calling setTimer again. This will not create 2 timers, You must not allow a timer to fire on an object that has been deleted already, unless you want segfaulty hell. +?? + +You must call cancelTimer before deleting object. cancelTimer guarantees that timercall +will not be called again. + */ -class Timer +class TimerEvent : public Thread_TYPE { public: + TimerEvent(); + + virtual void run(); + virtual void threadMethod(); + virtual void threadPostStopCleanup() {}; + TimerReceiver* client; int clientReference; struct timespec requestedTime; + + bool running; + bool restartAfterFinish; }; + using namespace std; -//using namespace __gnu_cxx; -typedef list TimerList; + +typedef list TimerList; class Timers : public Thread_TYPE { @@ -81,14 +96,16 @@ class Timers : public Thread_TYPE int init(); int shutdown(); - int setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSEC=0); - int setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs=0); - int cancelTimer(TimerReceiver* client, int clientReference); + bool setTimerT(TimerReceiver* client, int clientReference, long int requestedTime, long int requestedTimeNSEC=0); + bool setTimerD(TimerReceiver* client, int clientReference, long int requestedSecs, long int requestedNSecs=0); + bool cancelTimer(TimerReceiver* client, int clientReference); // Thread stuff virtual void threadMethod(); virtual void threadPostStopCleanup() {}; + void timerEventFinished(TimerEvent* timerEvent); // internal use only, does not return + private: static Timers* instance; Log* logger; diff --git a/vchannelselect.cc b/vchannelselect.cc index 880309f..8df54f6 100644 --- a/vchannelselect.cc +++ b/vchannelselect.cc @@ -182,7 +182,7 @@ void VChannelSelect::timercall(int clientReference) m->from = this; m->to = ViewMan::getInstance(); m->message = Message::CLOSE_ME; - Command::getInstance()->postMessageNoLock(m); + Command::getInstance()->postMessageFromOuterSpace(m); // Is there valid data? if ((first > 0) || (second > 0) || (third > 0)) @@ -205,6 +205,6 @@ void VChannelSelect::timercall(int clientReference) m->to = videoLive; m->message = Message::CHANNEL_CHANGE; m->parameter = newChannel; - Command::getInstance()->postMessageNoLock(m); + Command::getInstance()->postMessageFromOuterSpace(m); } } diff --git a/viewman.cc b/viewman.cc index c3e03a6..58691d6 100644 --- a/viewman.cc +++ b/viewman.cc @@ -445,5 +445,12 @@ void ViewMan::processMessage(Message* m) updateView(toAdd); break; } + case Message::REDRAW: + { + View* toRedraw = (View*)m->from; + Region* toRedrawRegion = (Region*)m->parameter; + updateView(toRedraw, toRedrawRegion); + break; + } } } diff --git a/vlivebanner.cc b/vlivebanner.cc index 2dc9c3f..ab790cd 100644 --- a/vlivebanner.cc +++ b/vlivebanner.cc @@ -262,13 +262,18 @@ void VLiveBanner::timercall(int clientReference) m->message = Message::CLOSE_ME; m->to = ViewMan::getInstance(); m->from = this; - Command::getInstance()->postMessageNoLock(m); + Command::getInstance()->postMessageFromOuterSpace(m); } else if (clientReference == 2) { // redraw clock drawClock(); - ViewMan::getInstance()->updateView(this, &clockRegion); + Message* m = new Message(); + m->message = Message::REDRAW; + m->to = ViewMan::getInstance(); + m->from = this; + m->parameter = (ULONG)&clockRegion; + Command::getInstance()->postMessageFromOuterSpace(m); } } diff --git a/vmute.cc b/vmute.cc index 921f15a..1f13950 100644 --- a/vmute.cc +++ b/vmute.cc @@ -64,7 +64,7 @@ void VMute::timercall(int clientReference) m->message = Message::CLOSE_ME; m->to = ViewMan::getInstance(); m->from = this; - Command::getInstance()->postMessageNoLock(m); + Command::getInstance()->postMessageFromOuterSpace(m); } int VMute::handleCommand(int command) diff --git a/vradiorec.cc b/vradiorec.cc index 8febeb4..abd68a3 100644 --- a/vradiorec.cc +++ b/vradiorec.cc @@ -398,7 +398,12 @@ void VRadioRec::timercall(int clientReference) // Update clock if (!barShowing) break; drawBarClocks(); - ViewMan::getInstance()->updateView(this, &barRegion); + Message* m = new Message(); + m->message = Message::REDRAW; + m->to = ViewMan::getInstance(); + m->from = this; + m->parameter = (ULONG)&barRegion; + Command::getInstance()->postMessageFromOuterSpace(m); timers->setTimerD(this, 2, 0, 200000000); break; } diff --git a/vtimerlist.cc b/vtimerlist.cc index cccf503..3d28051 100644 --- a/vtimerlist.cc +++ b/vtimerlist.cc @@ -211,11 +211,23 @@ void VTimerList::drawIndicators() void VTimerList::timercall(int clientReference) { drawClock(); - ViewMan::getInstance()->updateView(this, &clockRegion); + + Message* m = new Message(); + m->message = Message::REDRAW; + m->to = ViewMan::getInstance(); + m->from = this; + m->parameter = (ULONG)&clockRegion; + Command::getInstance()->postMessageFromOuterSpace(m); flipflop = !flipflop; drawIndicators(); - ViewMan::getInstance()->updateView(this, &indicatorsRegion); + + m = new Message(); + m->message = Message::REDRAW; + m->to = ViewMan::getInstance(); + m->from = this; + m->parameter = (ULONG)&indicatorsRegion; + Command::getInstance()->postMessageFromOuterSpace(m); } int VTimerList::handleCommand(int command) diff --git a/vvideorec.cc b/vvideorec.cc index 9ce4ca7..1f6fb33 100644 --- a/vvideorec.cc +++ b/vvideorec.cc @@ -206,6 +206,7 @@ int VVideoRec::handleCommand(int command) case Remote::MENU: { if (playing) stopPlay(); + return 4; } case Remote::PAUSE: @@ -651,7 +652,12 @@ void VVideoRec::timercall(int clientReference) // Update clock if (!barShowing) break; drawBarClocks(); - viewman->updateView(this, &barRegion); + Message* m = new Message(); + m->message = Message::REDRAW; + m->to = ViewMan::getInstance(); + m->from = this; + m->parameter = (ULONG)&barRegion; + Command::getInstance()->postMessageFromOuterSpace(m); timers->setTimerD(this, 2, 0, 200000000); break; } diff --git a/vvolume.cc b/vvolume.cc index fd7cbf6..d7e684d 100644 --- a/vvolume.cc +++ b/vvolume.cc @@ -79,7 +79,7 @@ void VVolume::timercall(int clientReference) m->message = Message::CLOSE_ME; m->to = ViewMan::getInstance(); m->from = this; - Command::getInstance()->postMessageNoLock(m); + Command::getInstance()->postMessageFromOuterSpace(m); } int VVolume::handleCommand(int command) diff --git a/vwelcome.cc b/vwelcome.cc index 8ba5d74..dd4d334 100644 --- a/vwelcome.cc +++ b/vwelcome.cc @@ -96,7 +96,13 @@ void VWelcome::drawClock() void VWelcome::timercall(int clientReference) { drawClock(); - viewman->updateView(this, &clockRegion); + // Put updateView through master mutex since viewman is not mutex protected + Message* m = new Message(); + m->message = Message::REDRAW; + m->to = ViewMan::getInstance(); + m->from = this; + m->parameter = (ULONG)&clockRegion; + Command::getInstance()->postMessageFromOuterSpace(m); } int VWelcome::handleCommand(int command) -- 2.39.5