Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Is it possible to have time out in VFP? 5

Status
Not open for further replies.

Mandy_crw

Programmer
Jul 23, 2020
578
PH
Hi everyone... I dont know how to ask the question well but is it possible for VFP to do something else when its idle? like in windows, it has a screensaver to show when computer has no activity... I want my app that when it's idle for sometime, it will show something or do something... thanks in advance...
 
Why not just use the Windows screensaver? Or if you want to be professional, have your app automatically shutdown if idle for a period of time. Many companies require this for security purposes.
 
Hi vernpace, Thanks for you suggestion... but my intention is my app would execute another routine if in case its idle... im not after the screensaver, its just an example...Thanks...
 
Take a look at this. But instead of shutting your app down, make it do what you needed it to do.

kilroy [knight]
philippines

"Once a king, always a king. But being a knight is more than enough."
 
Well, I have to say I'm confused. If your app is idle, then your computer is also idle which means that the person using the computer is probable not around. So why do you want your app to "do something" when no one is around to watch it. Maybe you want the computer to mine Bitcoin when no one is around ???

Do not click on torturedmind link - looks suspicious.
 
Vernpace

Its a FAQ from this forum. FYI Thank you.

kilroy [knight]
philippines

"Once a king, always a king. But being a knight is more than enough."
 
Well, torturedmind, I'm sure as hell not going to click on it.
 
Mandy,

Here is the code:

Code:
DECLARE INTEGER GetLastInputInfo IN user32.dll ;
        STRING @plii

DECLARE LONG GetTickCount64 IN kernel32.dll

PUBLIC goTimer
goTimer = CREATEOBJECT("tmrinactivity", 10)  && parameter is ten minutes

DEFINE CLASS tmrinactivity AS Timer
    minutes = 0

    PROCEDURE Timer
        IF GetIdleSeconds() >= 60 * This.Minutes
           This.Enabled = .F.

*!*        DO Something

           This.Enabled = .T.

        ENDIF
    ENDPROC

    PROCEDURE Init
        LPARAMETERS tiMinutes AS Integer

        IF NOT EMPTY(tiMinutes) AND VARTYPE(tiMinutes) = "N"
           This.Minutes = tiMinutes
        ELSE
           This.Minutes = 15
        ENDIF

        This.Interval = 15000

    ENDPROC

ENDDEFINE

FUNCTION GetIdleSeconds() AS Integer
   LOCAL lcBuffer, liLast, liIdle

   liIdle = 0
   lcBuffer = BINTOC(8, "4rs") + BINTOC(0, "4rs")

   IF GetLastInputInfo(@lcBuffer) <> 0
      liLast = CTOBIN(SUBSTR(lcBuffer, 5, 4), "4rs")
      liIdle = (GetTickCount64() - liLast) / 1000
   ENDIF

   RETURN liIdle

ENDFUNC
 
Vernspace,

It's simply faq184-4467
And torturedmind has not logged in for a very long time, it seems, but no reason to distrust a link you can check with hovering.

Good idea of code. GetLastInputInfo also is senstive to the current process, that's good.

I played with a timer you reset when a user activity is detected. Like WM_KEYDOWN or WM_MOUSEMOVE. You can limit that to all windows of your process. GetLastInputInfo is really a better idea.

I know the task manager also tells if processes are deemed idle. That happens far earlier when just short pauses are made. In VFP it's the case when the debugger would show a code pointer to READ EVENTS. But that happens too often. I see you take the last input time difference to tickcount and then only react if IdleSeconds exceeds a limit. Really good use case.

Mandy,
So that's also a good solution. Tamar Granor has an article on Bindevents with a section on monitoring user activity, so there's another idea:

It has some details to it like excluding some actions from application activity.

Chriss
 
Here's what I have so far:

Code:
Public goInactivityTimer
goInactivityTimer = CreateObject('InactivityTimer')

#Define WM_KEYDOWN   0x0100
#Define WM_MOUSEMOVE 0x0200
BindEvent(_screen.hWnd, WM_KEYDOWN, goInactivityTimer,'ResetIdleWaitTime')
BindEvent(_screen.hWnd, WM_MOUSEMOVE, goInactivityTimer,'ResetIdleWaitTime')

#Define GWL_WNDPROC  -4
Define Class InActivityTimer as Timer
    Interval = 15*60000 && 15 minutes inactivity is judged as idle, set this as you like.
    
    Procedure Timer()
        MessageBox(Textmerge('last keyboard or mouse activity <<This.Interval/60000>> minutes ago'))
        * Instead of the messagebox, do whatever you want to do in the idle state
        This.Reset()
    EndProc
    
    Procedure ResetIdleWaitTime()
       Lparameters hWnd, Msg, wParam, lParam
       
       This.Reset()
       * The timer event will only happen after this is NOT called for Interval milliseconds.
       * The timer therefore measures inactivity and will reach it's Timer event
       * only when it's not reset.
       

       * The following is just standard code so windows events/messages are processed normally,
       * ie in short: ...so your app works. So better not forget to do this, ever, when handling events
       LOCAL lnOldProc, lnResult
  
       DECLARE INTEGER GetWindowLong in Win32API;
          INTEGER hWnd, INTEGER nIndex
       lnOldProc = GetWindowLong(hWnd, GWL_WNDPROC)

       DECLARE INTEGER CallWindowProc in Win32API;
          INTEGER lpPrevWndFunc, INTEGER hWnd, INTEGER Msg, INTEGER wParam, INTEGER lParam
       lnResult  = CallWindowProc(lnOldProc, hWnd, Msg, wParam, lParam)
       
       RETURN lnResult
    EndProc 
EndDefine

This would need to be done for all HWNDs of forms of your application, if you want any mouse or keyboard activity to reset. But you can also just call goInactivityTimer.Reset() at any place like using the menu, starting a form. Things like that. If you don't bind to a forms HWND, using that form actively is not recorded. So it would better be put in a base form class init or load to bind to it's HWND.

Chriss
 
Chriss,

Good idea for base class code.

One thing that concerns me is that my code (and code with similar functionality) has the potential to be used for nefarious purposes - particularly if the user is unaware of it. Think about it. Any process can be run in the timer event without the user knowing while out to lunch, in a meeting, taking a smoke break...

Years ago, Anatoliy Mogylevets posted KeyLogger code on news2news.com. As much as I admired him, I thought that he was being irresponsible. Fast forward... I was developing a RTF spell-checker and realized that inline spell checking required the use of a KeyLogger. Then I realized the proliferation of KeyLoggers in GMail, Word, and other popular products. While probably totaly innocuous, it is something that we need to be aware of.

As long as users are aware of things like this, then they are assuming responsibily.
 
One thing about the messages is that you only get those addressed to your windows. Which arrive in controls like the textboxes, the menu, etc.
So there is little you can learn from this that's not arriving in your data or is form usage anyway.

But, that's true, once you do this for all HWNDs, and you can list all windows, not only that of your own process, you have the means to listen to all activity.
That you can bind to events is open knowledge, many other things in the Windows API.

An application can do anything "in the name of" a user as it acts as this user in that users session. Anything you do requires trust. That's something you need to be aware of anyway. UAC and other permissions (in the file system and networks) and group policies are the safeties of Windows. Your usual desktop application has many rights by default, that's true, but Windows defends itself quite rigorous.

What you are mainly responsible for is data privacy and security, and that belongs to it, true. But it's not only a topic of binding to windows messages.

Chriss
 
Regarding what an app does while it's idle (and knows its idle): When you would want to do something nefarious, then you can also do it when the user is active. It doesn't need to be displayed, does it?

Any process you start means trusting it only does what it tells it does. You have to trust the security of windows to prevent the worst actions.

I'd be more concerned talking about things that could hack into this security of Windows. As I said in Mandys thread about EXE protection, even one of the biggest companies as Microsoft is, they have to deal with software piracy that works on the basis of knowing Windows weaknesses. I'd not poke around in that domain, but Windows events are a very simple and fundamental thing, nothing secret.

The messages 0x401 to 0x7fff are even free for your own user defined use, no system message uses them. Messages 0x8000 to 0xbfff, see WM_USER and WM_APP docuemntation.

Chriss
 
Thank you Vernpace and Chriss.... I'll have to study it well... i dont where to put in my app... ill get back to you once i have done it.... Thanks....
 
Regarding the "where to put it"

Well, it should run quite early, and always. So such things are good in main. In my case you could put it into a separate PRG and call that PRG in main.
From then on it will detect actions in _screen, but not in any other forms. So, as I told, there will be further places where you have to have BINDEVENT() calls to further forms HWNDs.

And regarding replacing the messagebox (look at the comments in my code), I would not program what you want to do in idle state directly into the timer event. Let it DO another PRG, or example. the timer should disable itself while your code for theidle state runs.

The next question will be how the user getting back to the computer will wake up your app and get back into the non-idle state. Your code has to be responsive to user interaction and stopping when that happens. It has to return to the timer event, which then should enable itself and end.

That's actually the more pressing question: How do you react to the user starting to use your application again? But I think you were not yet aware of it. That's also the open question in ramanis FAQ code and vernspaces code.

Chriss
 
Thanks for mentioning my paper, Chriss. The situation Mandy describes is exactly what I developed that technique for. I had a client app that sat around waiting for things to happen, and the client wanted it to do some cleanup behind the scenes if the user did nothing for some period of time and then stop that cleanup process as soon as the user was active again.

Tamar
 
I see, you somewhere have

Code:
This.lUserActed = .T.
* So start counting again.
This.Reset()

In a method bound to mouse move.

I think you could profit binding to windows events instead, especially binding to 0, see below.

What I checked, and this should also be interesting to vernspace, you can't just listen to all windows events, your process can only listen to events that come to any of your forms.

If you use Declare Integer GetDesktopWindow in Win32Api to get the desktop HWND you don't get ResetIdleWaitTime calls when you move the mouse on the desktop, outside of _screen. If you use 0 as HWND, you bind to all your own application windows, so that's a generalization for which you won't need additional Bindevents for any form you start. But it's still limited to your own process HWNDs.

So I'd change my own idea to:
Code:
BindEvent([s]_screen.hWnd[/s] 0, WM_KEYDOWN, goInactivityTimer,'ResetIdleWaitTime')
BindEvent([s]_screen.hWnd[/s] 0, WM_MOUSEMOVE, goInactivityTimer,'ResetIdleWaitTime')

And while I'm at improving it, change the Timer event to:
Code:
    Procedure Timer()
        This.Enabled = .F.
        * MessageBox(Textmerge('last keyboard or mouse activity <<This.Interval/60000>> minutes ago'))
        * Instead of the messagebox, do whatever you want to do in the idle state:
        Do idlestate.prg
        This.Reset()
        This.Enabled = .T.
    EndProc
There still is nothing that reactivates this timer from idle mode to non-idle mode, except idlestate.prg returning. So again, I stress out: The code you do in idle mode (in idlestate.prg in this case) has to be able to detect a user action and then quit. How to do that is shown already, by binding to windows events. To cover both things in the same timer is possible, but that would need a change:

In idle state the timer should now not reset the timer interval when a user acts, but instead ResetIdleWaitTime in that mode must become the method that stops idlestate.prg from running. Which in theory is possible with CANCEL. As the help chapter on CANCEL in short says "Ends execution of the current Visual FoxPro program file." that would be possible. It would be idlestate.prg. At the same time, though, using CANCEL in any code means it is the current PRG file. And it also stops executing that method itself. When binding to windows events you have to return the value you get from CallWindowProc and so can't do CANCEL and RETURN. Besides that, the help on CANCEL also tells "If a distributed run-time application is running, CANCEL terminates the application and control returns to Windows." So that's a red herring.

I could add a property ApplicationIsIdle signaling the idle state in the timer, so that ResetIdleWaitTime could act accordingly, I'd still need to solve stopping idlestate.prg - assuming it still runs. The simplest way could be that idlestate.prg checks this new property of the timer and when it becomes .F. it stops doing something, for example idlestate.prg could run a loop with
Code:
Do While goInactivityTimer.ApplicationIsIdle
* Do something
EdnDo

I'd still not cover cases the "Do something" part takes a long time and will not be interrupted by user activity directly. On the other side cancelling code also has some pitfalls. When code you cancel is within a transaction, you don't let the transaction complete, neither commit nor roll back. If it even doesn't use transactions it could leave data changes in an undefined state. So whatever you do in an interruptible idlestate.prg would require well planned acting on things that can't leave whatever it manipulates in an unwanted intermediate state. From that perspective I'd suggest idlestate.prg in such a while loop should neither do critical things nor take long for each iteration of the loop waiting for the user to become active again. With that restrictions the simple signaling method would work okay, and there would be no need for an interruption.

So all in all, idelstate.prg has some restrictions of what it can do purposefully.

Also be careful with multi user vs. single user applications. In a single user application you might use the time to do PACKs on tables. But if a PACK takes long, you risk the user still sitting at the computer, just had a 20 minute phone call, you start a PACK taking maybe 10 minutes on a large table and start that at 15 minutes into the phone call. When the user gets back to using the application in the middle of the PACK, there's no way of stopping that.

It's no wonder the usual recommendation of data maintenance actions are to be done in a separate administrative tool or a scheduled task running at night.

What you could easily do instead of a pack is find one deleted row, RECALL it, copy the last undeleted record of the DBF over here and DELETE that record near the end of the DBF, so finally all deleted rows of the DBF are at its end, at least. The file then could be truncated after a fix of the reccount in the DBF header. Still even the record swapping isn't as easy as it sounds, as the primary key index will need unique values in all steps of such a record swap.

A completely different matter would be not just starting a PRG but instead call a new EXE: For example, RUN /N idlestatet.exe. There are ways to do this and be able to also stop idlestatet.exe to run, but then there also would be simpler ideas of running a second process all the time and then simply signal it to become active as your own EXE detects its user is inactive. Which also closes the circle in what I said about nefarious actions: Any code could do nefarious actions, it doesn't need to wait for the user to become inactive. So I don't think you teach a bad hacker how to do things, they already know it takes code to be done and they surely put theirs into viruses embedding themselves in your exe or make their own executable. The other side of this, teaching how to react to windows events, also loses it's power when you only can react to events your own process receives anyway. So, a key logger requires more than reactions to WM_KEYDOWN.

Chriss
 
In short, Tamar points out her solution includes the reactivation part, so perhaps go for her solution instead of reading all my chatter that only includes some sketches of ideas about that.

Chriss
 
Thanks Chriss... its exactly i want to happen as describe by Tamar...Thank you Tamar... still studying and testing all your suggestions.... God bless....
 
May I make one small addition to this discussion. (I haven't been following this thread in detail, so apologies if this point has already been covered.)

In Tamar's article, she suggested binding the KeyPress and MouseMove events. So the timeout will occur when the user doesn't press a key or move the mouse for a given period. I would suggest that merely moving the mouse shouldn't prevent the timeout. In other words, if the user moves the mouse without actually clicking or dragging, that shouldn't be considered as "user activity".

If you agree, the solution is to bind to the MouseDown event rather than MouseMove. That would cover both clicking and dragging, but not idly moving the mouse around.

Just a thought.

Mike

__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top