Page 1 of 1

Timer + automatic wake up of computer when in standby mode ?

Posted: Mon Mar 07, 2011 3:52 pm
by c4s
I want to start a timer when the computer goes into standby mode and "wake it up" again after 30 seconds. Unfortunately there are some things that don't work:
- Waking the computer up after 30 seconds (see logfile)
- Catching the #WM_POWERBROADCAST message when standby was initiated by the program (instead of system buttons etc.)
- ...

Here is the current code I'm using:

Code: Select all

EnableExplicit

#Title = "TimerTest"

Enumeration
	#Window
	#Text
	#ButtonSuspend
	#ButtonHibernate
	#FileLog
EndEnumeration

#PBT_APMRESUMESUSPEND = $0007
#PBT_APMSUSPEND = $0004

Structure LogListItem
	Text.s
	Time.l
	Level.l
EndStructure

Global NewList LogList.LogListItem()

Define Logfile.s
Logfile.s = GetPathPart(ProgramFilename()) + #Title + "_LOG.txt"


Procedure LogListAdd(Text.s, Level=0)
	If AddElement(LogList())
		With LogList()
			\Text = Text
			\Time = Date()
			\Level = Level
		EndWith
	EndIf
EndProcedure

Procedure LogListAddError(Text.s, Level=2, Info=#True)
	Protected Error, ErrorInfo.s

	If Info = #True
		Error = GetLastError_()
		ErrorInfo = Space(#MAX_PATH)
		FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, 0, Error, 0, @ErrorInfo, Len(ErrorInfo), 0)
		ErrorInfo = " -> (" + RSet(Hex(Error, #PB_Long), 8, "0") + ") " + #DQUOTE$ + ErrorInfo + #DQUOTE$
	EndIf

	LogListAdd(Text + ErrorInfo, Level)
EndProcedure

Procedure LogListSave(Filename.s)
	Protected FileNr
	Protected Result = #False

	FileNr = CreateFile(#PB_Any, Filename)
	If FileNr
		ForEach LogList()
			WriteStringN(FileNr, FormatDate("%hh:%ii:%ss", LogList()\Time) + #TAB$ + LogList()\Text)
		Next

		Result = #True
		CloseFile(FileNr)
	EndIf

	ProcedureReturn Result
EndProcedure

Procedure DateToFiletime(Date, *ft.FILETIME)
	Protected st.SYSTEMTIME

	With st
		\wYear = Year(Date)
		\wMonth = Month(Date)
		\wDay = Day(Date)
		\wDayOfWeek = DayOfWeek(Date)
		\wHour = Hour(Date)
		\wMinute = Minute(Date)
		\wSecond = Second(Date)
		\wMilliseconds = 0
	EndWith

	SystemTimeToFileTime_(@st, *ft)
	LocalFileTimeToFileTime_(@*ft, *ft)
EndProcedure

Procedure TimerCallback(*ArgToCompletionRoutine, TimerLowValue.l, TimerHighValue.l)
	LogListAdd("Timer: Finished")
EndProcedure

Procedure TimerStart()
	Protected hTimer
	Protected ft.FILETIME

	hTimer = CreateWaitableTimer_(#Null, #False, #Title)
	If hTimer
		LogListAdd("Timer: Created")

		DateToFiletime(Date() + 30, @ft)  ; Wait 30 seconds until "waking up"

		If SetWaitableTimer_(hTimer, @ft, 0, @TimerCallback(), 0, #True)
			If GetLastError_() <> #ERROR_NOT_SUPPORTED
				LogListAdd("Timer: Started")
				;SleepEx_(#INFINITE, #True)
			Else
				LogListAddError("Timer: Started but doesn't support waking up! (Success message will appear now)")
			EndIf
		Else
			LogListAddError("Timer: Not started!")
		EndIf
		CloseHandle_(hTimer)
	Else
		LogListAddError("Timer: Not created!")
	EndIf
EndProcedure

Procedure WindowCallback(hWnd, uMsg, wParam, lParam)
	Select uMsg
		Case #WM_POWERBROADCAST
			Select wParam
				Case #PBT_APMSUSPEND
					LogListAdd("Standby: Activated")
					TimerStart()
				Case #PBT_APMRESUMESUSPEND
					LogListAdd("Standby: Finished")
			EndSelect
	EndSelect

	ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

Procedure Suspend(Hibernate, ForceCritical=#False, DisableWakeEvent=#False)
	Protected LibraryID
	Protected Result = #False

	LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
	If LibraryID
		Result = CallFunction(LibraryID, "SetSuspendState", Hibernate, ForceCritical, DisableWakeEvent)
		CloseLibrary(LibraryID)
	EndIf

	ProcedureReturn Result
EndProcedure

Procedure SuspendAllowed()
	Protected LibraryID
	Protected Result = #False

	LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
	If LibraryID
		Result = CallFunction(LibraryID, "IsPwrSuspendAllowed")
		CloseLibrary(LibraryID)
	EndIf

	ProcedureReturn Result
EndProcedure

Procedure HibernateAllowed()
	Protected LibraryID
	Protected Result = #False

	LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
	If LibraryID
		Result = CallFunction(LibraryID, "IsPwrHibernateAllowed")
		CloseLibrary(LibraryID)
	EndIf

	ProcedureReturn Result
EndProcedure



If OpenWindow(#Window, #PB_Ignore, #PB_Ignore, 300, 170, #Title, #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_Invisible)

	TextGadget(#Text, 10, 10, 280, 70, "The timer should start automatically (for 30 secs) when clicking on these buttons or the PC's " + #DQUOTE$ + "standby" + #DQUOTE$ + " button." + #CRLF$ + #CRLF$ + "Warning: These buttons do what they say!")
	ButtonGadget(#ButtonSuspend, 10, 90, 280, 30, "Suspend PC")
	ButtonGadget(#ButtonHibernate, 10, 130, 280, 30, "Hibernate PC")

	If SuspendAllowed() = #False : DisableGadget(#ButtonSuspend, #True) : EndIf
	If HibernateAllowed() = #False : DisableGadget(#ButtonHibernate, #True) : EndIf

	SetWindowCallback(@WindowCallback())

	HideWindow(#Window, #False)

	LogListAdd("Program: Started")


	Repeat
		Select WaitWindowEvent()
			Case #PB_Event_Gadget
				Select EventGadget()
					Case #ButtonSuspend
						LogListAdd("Standby: Start suspending")
						TimerStart()  ; <-- Why isn't #WM_POWERBROADCAST received to do this? (see WindowCallback())
						Suspend(#False)
					Case #ButtonHibernate
						LogListAdd("Standby: Start hibernating")
						TimerStart()  ;
						Suspend(#True)
				EndSelect
			Case #PB_Event_CloseWindow
				LogListAdd("Program: Closed")
				LogListSave(Logfile) ;: RunProgram(Logfile)
				Break
		EndSelect
	ForEver
EndIf
Here is a logfile created on Window Vista. Standby was initiated by pressing "Suspend PC":

Code: Select all

15:25:14	Program: Started
15:25:20	Standby: Start suspending
15:25:20	Timer: Created
15:25:20	Timer: Started
15:25:20	Timer: Finished  <-- Where are the 30 seconds of wait?
15:26:37	Standby: Finished
15:26:45	Program: Closed
Can someone please help me out? I don't get why this all simply doesn't work.

Edit: Changed thread title

Re: Autom. wake PC up when in standby mode -> Code doesn't w

Posted: Tue Mar 08, 2011 12:15 am
by c4s
Please try the code and post what you get, so I can try to understand why this doesn't work. I'm grateful for any help.

Right now I think the problem could be that DateToFiletime() somehow creates a time in the past, SleepEx_() is requiered, security issues (privileges?), my system doesn't support that kind of standby and/or conflicts with PureBasic's message handling...

Re: Timer + automatic wake up of computer when in standby mo

Posted: Sat Mar 12, 2011 11:01 am
by c4s
This code seems to work (I also changed the timer to 60 seconds):

Code: Select all

EnableExplicit

#Title = "TimerTest"

Enumeration
   #Window
   #Text
   #ButtonSuspend
   #ButtonHibernate
EndEnumeration

#PBT_APMRESUMEAUTOMATIC = $0012
#PBT_APMRESUMESUSPEND = $0007
#PBT_APMSUSPEND = $0004

#ES_CONTINUOUS = $80000000
#ES_DISPLAY_REQUIRED = $2
#ES_SYSTEM_REQUIRED = $1

Structure LogListItem
   Text.s
   Time.l
   Level.l
EndStructure

Global NewList LogList.LogListItem()

Define Logfile.s
Logfile.s = GetPathPart(ProgramFilename()) + #Title + "_LOG.txt"


Procedure LogListAdd(Text.s, Level=0)
   If AddElement(LogList())
      With LogList()
         \Text = Text
         \Time = Date()
         \Level = Level
      EndWith
   EndIf
EndProcedure

Procedure LogListAddError(Text.s, Level=2, Info=#True)
   Protected Error, ErrorInfo.s

   If Info = #True
      Error = GetLastError_()
      ErrorInfo = Space(#MAX_PATH)
      FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, 0, Error, 0, @ErrorInfo, Len(ErrorInfo), 0)
      ErrorInfo = " -> (" + RSet(Hex(Error, #PB_Long), 8, "0") + ") " + #DQUOTE$ + ErrorInfo + #DQUOTE$
   EndIf

   LogListAdd(Text + ErrorInfo, Level)
EndProcedure

Procedure LogListSave(Filename.s)
   Protected FileNr
   Protected Result = #False

   FileNr = CreateFile(#PB_Any, Filename)
   If FileNr
      ForEach LogList()
         WriteStringN(FileNr, FormatDate("%hh:%ii:%ss", LogList()\Time) + #TAB$ + LogList()\Text)
      Next

      Result = #True
      CloseFile(FileNr)
   EndIf

   ProcedureReturn Result
EndProcedure

Procedure DateToFiletime(Date, *ft.FILETIME)
   Protected st.SYSTEMTIME
   Protected ftTemp.FILETIME

   With st
      \wYear = Year(Date)
      \wMonth = Month(Date)
      \wDay = Day(Date)
      \wHour = Hour(Date)
      \wMinute = Minute(Date)
      \wSecond = Second(Date)
      \wMilliseconds = 0
   EndWith

   SystemTimeToFileTime_(st, ftTemp)
   LocalFileTimeToFileTime_(ftTemp, *ft)
EndProcedure

Procedure TimerCallback(*ArgToCompletionRoutine, TimerLowValue.l, TimerHighValue.l)
   LogListAdd("Timer: Finished successfully")
EndProcedure

Procedure TimerStart()
   Protected hTimer
   Protected ft.FILETIME

   hTimer = CreateWaitableTimer_(#Null, #False, #Title) ;;
   If hTimer
      LogListAdd("Timer: Created")

      DateToFiletime(Date() + 60, @ft)  ; Wait for 60 secs

      If SetWaitableTimer_(hTimer, @ft, 0, @TimerCallback(), 0, #True)
         If GetLastError_() <> #ERROR_NOT_SUPPORTED
            LogListAdd("Timer: Started")
         Else
            LogListAddError("Timer: Started but doesn't support waking up! (Success message will appear now)")
         EndIf
         SleepEx_(#INFINITE, #True) ;;
      Else
         LogListAddError("Timer: Not started!")
      EndIf
      CancelWaitableTimer_(hTimer)
      CloseHandle_(hTimer)
   Else
      LogListAddError("Timer: Not created!")
   EndIf
EndProcedure

Procedure WindowCallback(hWnd, uMsg, wParam, lParam)
   Protected Result = #PB_ProcessPureBasicEvents

   Select uMsg
      Case #WM_POWERBROADCAST
         Select wParam
            Case #PBT_APMRESUMEAUTOMATIC  ; Operation is resuming automatically from low-power state. This is sent every time the system resumes.
               LogListAdd("Standby: Finished")
            Case #PBT_APMRESUMESUSPEND  ; Operation is resuming from low-power state. This is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input.
               LogListAdd("Info: Finished by user input")
            Case #PBT_APMSUSPEND
               LogListAdd("Standby: Activated")
               TimerStart()
         EndSelect
         Result = #True
   EndSelect

   ProcedureReturn Result
EndProcedure

Procedure Suspend(Hibernate, ForceCritical=#False, DisableWakeEvent=#False)
   Protected LibraryID
   Protected Result = #False

   LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
   If LibraryID
      Result = CallFunction(LibraryID, "SetSuspendState", Hibernate, ForceCritical, DisableWakeEvent)
      CloseLibrary(LibraryID)
   EndIf

   ProcedureReturn Result
EndProcedure

Procedure SuspendAllowed()
   Protected LibraryID
   Protected Result = #False

   LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
   If LibraryID
      Result = CallFunction(LibraryID, "IsPwrSuspendAllowed")
      CloseLibrary(LibraryID)
   EndIf

   ProcedureReturn Result
EndProcedure

Procedure HibernateAllowed()
   Protected LibraryID
   Protected Result = #False

   LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
   If LibraryID
      Result = CallFunction(LibraryID, "IsPwrHibernateAllowed")
      CloseLibrary(LibraryID)
   EndIf

   ProcedureReturn Result
EndProcedure



If OpenWindow(#Window, #PB_Ignore, #PB_Ignore, 300, 170, #Title, #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_Invisible)

   TextGadget(#Text, 10, 10, 280, 70, "The timer should start automatically (for 60 secs) when clicking on these buttons or the PC's " + #DQUOTE$ + "standby" + #DQUOTE$ + " button." + #CRLF$ + #CRLF$ + "Warning: These buttons do what they say!")
   ButtonGadget(#ButtonSuspend, 10, 90, 280, 30, "Suspend")
   ButtonGadget(#ButtonHibernate, 10, 130, 280, 30, "Hibernate")

   If SuspendAllowed() = #False : DisableGadget(#ButtonSuspend, #True) : EndIf
   If HibernateAllowed() = #False : DisableGadget(#ButtonHibernate, #True) : EndIf

   SetWindowCallback(@WindowCallback())

   HideWindow(#Window, #False)

   LogListAdd("Program: Started")


   Repeat
      Select WaitWindowEvent()
         Case #PB_Event_Gadget
            Select EventGadget()
               Case #ButtonSuspend
                  LogListAdd("Standby: Start suspending")
                  TimerStart()  ; <-- Why isn't #WM_POWERBROADCAST received to do this? (see WindowCallback())
                  Suspend(#False)
               Case #ButtonHibernate
                  LogListAdd("Standby: Start hibernating")
                  TimerStart()  ;
                  Suspend(#True)
            EndSelect
         Case #PB_Event_CloseWindow
            LogListAdd("Program: Closed")
            LogListSave(Logfile) ;: RunProgram(Logfile)
            Break
      EndSelect
   ForEver
EndIf 
I didn't test the hibernation yet. Anyway, the timer itself should work now.

I'm still uncertain if pushing suspend/hibernate in the start menu triggers my timer. Furthermore I don't understand how I can run the timer in the background so that the program event handling doesn't stop - it looks like SleepEx_() is needed.

Re: Timer + automatic wake up of computer when in standby mo

Posted: Sat Mar 12, 2011 10:36 pm
by happer66
I played around with your first code yesterday and discovered a few things, haven't really had time to look closely at your second code though.

If you're targeting anything above XP then it might be difficult to create the waketimer after you discover the system is going down (hibernation, sleep etc). You could abort such actions before but Microsoft changed all that in Vista. Now you're just given a very short window (2 seconds?) to do what you want done (SetThreadExecutionState with #ES_SYSTEM_REQUIRED flag doesn't work anymore).

To be able to receive #PBT_APMSUSPEND you must register your app with RegisterPowerSettingNotification (see code).

Code: Select all

;I made quite a mess of things when playing with this code
;so there might be some letftovers I forgot to remove before posting :)
EnableExplicit

Define Title.s = "TimerTest"

Enumeration
   #Window
   #Text
   #ButtonSuspend
   #ButtonHibernate
   #FileLog
EndEnumeration

#PBT_APMQUERYSUSPEND        = $0000 ;No longer supported on Vista+
#PBT_APMQUERYSTANDBY        = $0001
#PBT_APMQUERYSUSPENDFAILED  = $0002
#PBT_APMQUERYSTANDBYFAILED  = $0003
#PBT_APMSUSPEND             = $0004
#PBT_APMSTANDBY             = $0005
#PBT_APMRESUMECRITICAL      = $0006
#PBT_APMRESUMESUSPEND       = $0007
#PBT_APMRESUMESTANDBY       = $0008
#PBT_APMPOWERSTATUSCHANGE   = $000A
#PBT_APMOEMEVENT            = $000B
#PBT_APMRESUMEAUTOMATIC     = $0012

#BROADCAST_QUERY_DENY       = $424D5144
#WM_POWER                   = $0048
#WM_POWERBROADCAST          = $0218
#PWR_SUSPENDREQUEST         = $0001

#TIMER_MODIFY_STATE         = $0002

#ES_SYSTEM_REQUIRED         = $0001
#ES_DISPLAY_REQUIRED        = $0002
#ES_AWAYMODE_REQUIRED       = $0040
#ES_CONTINUOUS              = $80000000

Structure LogListItem
   Text.s
   Time.l
   Level.l
EndStructure

Global NewList LogList.LogListItem()

Define Logfile.s
Logfile.s = GetPathPart(ProgramFilename()) + Title + "_LOG.txt"


Procedure LogListAdd(Text.s, Level=0)
   If AddElement(LogList())
      With LogList()
         \Text = Text
         \Time = Date()
         \Level = Level
      EndWith
   EndIf
EndProcedure

Procedure LogListAddError(Text.s, Level=2, Info=#True)
   Protected Error, ErrorInfo.s

   If Info = #True
      Error = GetLastError_()
      ErrorInfo = Space(#MAX_PATH)
      FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, 0, Error, 0, @ErrorInfo, Len(ErrorInfo), 0)
      ErrorInfo = " -> (" + RSet(Hex(Error, #PB_Long), 8, "0") + ") " + #DQUOTE$ + ErrorInfo + #DQUOTE$
   EndIf

   LogListAdd(Text + ErrorInfo, Level)
EndProcedure

Procedure LogListSave(Filename.s)
   Protected FileNr
   Protected Result = #False

   FileNr = CreateFile(#PB_Any, Filename)
   If FileNr
      ForEach LogList()
         WriteStringN(FileNr, FormatDate("%hh:%ii:%ss", LogList()\Time) + #TAB$ + LogList()\Text)
      Next

      Result = #True
      CloseFile(FileNr)
   EndIf

   ProcedureReturn Result
EndProcedure

Procedure EnablePWRNotify()
  Protected LibraryID, Result
 
  LibraryID = OpenLibrary(#PB_Any, "user32.dll")
  If LibraryID
      Result = CallFunction(LibraryID, "RegisterPowerSettingNotification", WindowID(#Window),#Null,0)
    CloseLibrary(LibraryID)
  EndIf
  
  ProcedureReturn Result
 EndProcedure
 

Procedure DateToFiletime(Date, *ft.FILETIME)
   Protected st.SYSTEMTIME, tmp.FILETIME

   With st
      \wYear = Year(Date)
      \wMonth = Month(Date)
      \wDay = Day(Date)
      \wHour = Hour(Date)
      \wMinute = Minute(Date)
      \wSecond = Second(Date)
      \wMilliseconds = 0
   EndWith

   SystemTimeToFileTime_(st, tmp)
   LocalFileTimeToFileTime_(tmp, *ft)
EndProcedure

Procedure TimerCallback(*ArgToCompletionRoutine, TimerLowValue.l, TimerHighValue.l)
   LogListAdd("Timer: Finished")
EndProcedure

Procedure TimerStart()
   Protected hTimer
   Protected ft.FILETIME
   Protected mDate
   Shared Title

   hTimer = CreateWaitableTimer_(#Null, #False, Title)
   If hTimer
      LogListAdd("Timer: Created")

      mdate = AddDate(Date(), #PB_Date_Minute, 1)
      DateToFiletime(mDate, @ft)
      
      hTimer = OpenWaitableTimer_(#TIMER_MODIFY_STATE,#Null,@title)

      If SetWaitableTimer_(hTimer, @ft, 0, @TimerCallback(), 0, #True)
         If GetLastError_() <> #ERROR_NOT_SUPPORTED
            LogListAdd("Timer: Started")
         Else
            LogListAddError("Timer: Started but doesn't support waking up! (Success message will appear now)")
         EndIf
      Else
         LogListAddError("Timer: Not started!")
      EndIf
      CloseHandle_(hTimer)
   Else
      LogListAddError("Timer: Not created!")
   EndIf
 EndProcedure
 

 Procedure WindowCallback(hWnd, uMsg, wParam, lParam)
   
   Select wParam
     Case #PBT_APMSUSPEND
      ; Computer is hibernating/sleeping, we have VERY little time to do anything...
      LogListAdd("Standby: Activated")
      ;TimerStart() ; --- not enough time left for that?
  EndSelect
  
  Select uMsg
    Case #WM_POWERBROADCAST
      Select wParam
        Case #PBT_APMRESUMESUSPEND
          LogListAdd("Standby: Finished")
      EndSelect
  EndSelect

  ProcedureReturn #PB_ProcessPureBasicEvents
  
EndProcedure

Procedure Suspend(Hibernate, ForceCritical=#False, DisableWakeEvent=#False)
   Protected LibraryID
   Protected Result = #False

   LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
   If LibraryID
      Result = CallFunction(LibraryID, "SetSuspendState", Hibernate, ForceCritical, DisableWakeEvent)
      CloseLibrary(LibraryID)
   EndIf

   ProcedureReturn Result
EndProcedure

Procedure SuspendAllowed()
   Protected LibraryID
   Protected Result = #False

   LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
   If LibraryID
      Result = CallFunction(LibraryID, "IsPwrSuspendAllowed")
      CloseLibrary(LibraryID)
   EndIf

   ProcedureReturn Result
EndProcedure

Procedure HibernateAllowed()
   Protected LibraryID
   Protected Result = #False

   LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
   If LibraryID
      Result = CallFunction(LibraryID, "IsPwrHibernateAllowed")
      CloseLibrary(LibraryID)
   EndIf

   ProcedureReturn Result
EndProcedure



If OpenWindow(#Window, #PB_Ignore, #PB_Ignore, 300, 170, Title, #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_Invisible)

   TextGadget(#Text, 10, 10, 280, 70, "Timer should start automatically when clicking on these buttons." + #CRLF$ + #CRLF$ + "Warning: These buttons do what they say!")
   ButtonGadget(#ButtonSuspend, 10, 90, 280, 30, "Suspend PC")
   ButtonGadget(#ButtonHibernate, 10, 130, 280, 30, "Hibernate PC")

   If SuspendAllowed() = #False : DisableGadget(#ButtonSuspend, #True) : EndIf
   If HibernateAllowed() = #False : DisableGadget(#ButtonHibernate, #True) : EndIf

   SetWindowCallback(@WindowCallback())

   HideWindow(#Window, #False)

   LogListAdd("Program: Started")


   Repeat
      Select WaitWindowEvent()
         Case #PB_Event_Gadget
            Select EventGadget()
               Case #ButtonSuspend
                  LogListAdd("Standby: Start suspending")
                  TimerStart()
                  Suspend(#False)
               Case #ButtonHibernate
                  LogListAdd("Standby: Start hibernating")
                  TimerStart()
                  Suspend(#True)
            EndSelect
         Case #PB_Event_CloseWindow
            LogListAdd("Program: Closed")
            LogListSave(Logfile)
            Break
      EndSelect
   ForEver
 EndIf
TimerTest_LOG.txt wrote: 21:50:51 Program: Started
21:50:53 Standby: Start suspending
21:50:53 Timer: Created
21:50:53 Timer: Started
21:52:03 Standby: Finished
21:52:13 Program: Closed
powercfg.exe -lastwake wrote: Wake History Count - 1
Wake History [0]
Wake Source Count - 1
Wake Source [0]
Type: Wake Timer
Owner: [PROCESS] \Device\HarddiskVolume3\Users\th\AppData\Local\Temp\PureBasic_Compilation0.exe
If you have an active waketimer then it won't matter how the suspend (startmenu, commands, your app) was done, it can still be woken up by your timer.
You can test it by commenting out the Suspend() command and clicking one of the buttons, optionally check if the timer is running ("powercfg.exe -waketimers"), and running "rundll32 powrprof.dll,SetSuspendState" from a command prompt.

Hopefully I am at error here and there is a way to do whatever you want to achieve :mrgreen:

Re: Timer + automatic wake up of computer when in standby mo

Posted: Sun Mar 13, 2011 1:28 pm
by c4s
Hello happer66, thanks for testing it. I changed a little here and there and it seems to work now just fine. The timer starts automatically when you push the standby button in the start menu or in the window and then the system will be shut down. The system will wake up again when the timer has finished:

Code: Select all

EnableExplicit

Enumeration
	#Window
	#Text
	#ButtonSleep
	#ButtonHibernate
EndEnumeration

#PBT_APMRESUMEAUTOMATIC = $0012
#PBT_APMRESUMESUSPEND = $0007
#PBT_APMSUSPEND = $0004

#TIMER_MODIFY_STATE = $0002

Structure LogListItem
	Text.s
	Time.l
	Level.l
EndStructure

Global NewList LogList.LogListItem()

Global Title.s = "TimerTest"
Define Logfile.s = GetPathPart(ProgramFilename()) + Title + "_LOG.txt"


Procedure LogListAdd(Text.s, Level=0)
	If AddElement(LogList())
		With LogList()
			\Text = Text
			\Time = Date()
			\Level = Level
		EndWith
	EndIf
EndProcedure

Procedure LogListAddError(Text.s, Level=2, Info=#True)
	Protected Error, ErrorInfo.s

	If Info = #True
		Error = GetLastError_()
		ErrorInfo = Space(#MAX_PATH)
		FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM, 0, Error, 0, @ErrorInfo, Len(ErrorInfo), 0)
		ErrorInfo = " -> (" + RSet(Hex(Error, #PB_Long), 8, "0") + ") " + #DQUOTE$ + ErrorInfo + #DQUOTE$
	EndIf

	LogListAdd(Text + ErrorInfo, Level)
EndProcedure

Procedure LogListSave(Filename.s)
	Protected FileNr
	Protected Result = #False

	FileNr = CreateFile(#PB_Any, Filename)
	If FileNr
		ForEach LogList()
			WriteStringN(FileNr, FormatDate("%hh:%ii:%ss", LogList()\Time) + #TAB$ + LogList()\Text)
		Next

		Result = #True
		CloseFile(FileNr)
	EndIf

	ProcedureReturn Result
EndProcedure

Procedure DateToFiletime(Date, *ft.FILETIME)
	Protected st.SYSTEMTIME
	Protected ftTemp.FILETIME

	With st
		\wYear = Year(Date)
		\wMonth = Month(Date)
		\wDay = Day(Date)
		\wHour = Hour(Date)
		\wMinute = Minute(Date)
		\wSecond = Second(Date)
		\wMilliseconds = 0
	EndWith

	SystemTimeToFileTime_(st, ftTemp)
	LocalFileTimeToFileTime_(ftTemp, *ft)
EndProcedure

Procedure TimerCallback(*ArgToCompletionRoutine, TimerLowValue.l, TimerHighValue.l)
	LogListAdd("Timer: Finished successfully")
EndProcedure

Procedure TimerStart(Void)
	Protected hTimer
	Protected ft.FILETIME

	hTimer = CreateWaitableTimer_(#Null, #False, @Title)
	If hTimer
		;hTimer = OpenWaitableTimer_(#TIMER_MODIFY_STATE, #False, @Title)  ; Didn't work with this

		LogListAdd("Timer: Created")

		DateToFiletime(AddDate(Date(), #PB_Date_Minute, 1), @ft)  ; Timer should wait 1 minute

		If SetWaitableTimer_(hTimer, @ft, 0, @TimerCallback(), #Null, #True)
			LogListAdd("Timer: Started")
			If GetLastError_() = #ERROR_NOT_SUPPORTED
				LogListAddError("Info: Waking up not supported")
			EndIf
			SleepEx_(#INFINITE, #True)  ; Wait for timer to finish
		Else
			LogListAddError("Timer: Not started!")
		EndIf

		CloseHandle_(hTimer)
	Else
		LogListAddError("Timer: Not created!")
	EndIf
EndProcedure

Procedure WindowCallback(hWnd, uMsg, wParam, lParam)
	Protected Result = #PB_ProcessPureBasicEvents

	Select uMsg
		Case #WM_POWERBROADCAST
			Select wParam
				Case #PBT_APMRESUMEAUTOMATIC  ; Resuming automatically, sent every time the system resumes.
					LogListAdd("Standby: Finished")
				Case #PBT_APMRESUMESUSPEND  ; Resuming, sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user (or this timer??)
					LogListAdd("Info: Finished by user input")
				Case #PBT_APMSUSPEND  ; System is suspending (hibernate or standby)
					LogListAdd("Standby: Starting")
					CreateThread(@TimerStart(), 0)
			EndSelect
			Result = #True
	EndSelect

	ProcedureReturn Result
EndProcedure

Procedure Suspend(Hibernate, ForceCritical=#False, DisableWakeEvent=#False)
	Protected LibraryID
	Protected Result = #False

	LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
	If LibraryID
		Result = CallFunction(LibraryID, "SetSuspendState", Hibernate, ForceCritical, DisableWakeEvent)
		CloseLibrary(LibraryID)
	EndIf

	ProcedureReturn Result
EndProcedure

Procedure SuspendAllowed()
	Protected LibraryID
	Protected Result = #False

	LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
	If LibraryID
		Result = CallFunction(LibraryID, "IsPwrSuspendAllowed")
		CloseLibrary(LibraryID)
	EndIf

	ProcedureReturn Result
EndProcedure

Procedure HibernateAllowed()
	Protected LibraryID
	Protected Result = #False

	LibraryID = OpenLibrary(#PB_Any, "powrprof.dll")
	If LibraryID
		Result = CallFunction(LibraryID, "IsPwrHibernateAllowed")
		CloseLibrary(LibraryID)
	EndIf

	ProcedureReturn Result
EndProcedure



If OpenWindow(#Window, #PB_Ignore, #PB_Ignore, 300, 170, Title, #PB_Window_SystemMenu | #PB_Window_ScreenCentered | #PB_Window_Invisible)

	TextGadget(#Text, 10, 10, 280, 70, "Timer should start automatically (for 1 minute) when clicking on these buttons or the system's " + #DQUOTE$ + "standby" + #DQUOTE$ + " button." + #CRLF$ + #CRLF$ + "Info: Close this to create the logfile.")
	ButtonGadget(#ButtonSleep, 10, 90, 280, 30, "Sleep")
	ButtonGadget(#ButtonHibernate, 10, 130, 280, 30, "Hibernate")

	If SuspendAllowed() = #False : DisableGadget(#ButtonSleep, #True) : EndIf
	If HibernateAllowed() = #False : DisableGadget(#ButtonHibernate, #True) : EndIf

	SetWindowCallback(@WindowCallback())

	HideWindow(#Window, #False)

	LogListAdd("Program: Started")


	Repeat
		Select WaitWindowEvent()
			Case #PB_Event_Gadget
				Select EventGadget()
					Case #ButtonSleep
						LogListAdd("Standby: Activate sleeping via button")
						CreateThread(@TimerStart(), 0)
						Suspend(#False)
					Case #ButtonHibernate
						LogListAdd("Standby: Activate hibernating via button")
						CreateThread(@TimerStart(), 0)
						Suspend(#True)
				EndSelect
			Case #PB_Event_CloseWindow
				LogListAdd("Program: Closed")
				LogListSave(Logfile) ;: RunProgram(Logfile)
				Break
		EndSelect
	ForEver
EndIf
Here are some of my successful logs I just did...

Vista/XP -> Standby in the start menu:
12:40:53 Program: Started
12:41:00 Standby: Starting
12:41:00 Timer: Created
12:41:00 Timer: Started
12:42:01 Timer: Finished successfully
12:42:01 Standby: Finished
12:42:05 Info: Finished by user input
12:42:28 Program: Closed
Vista -> Standby in the start menu with manually starting the pc before the timer has finished:
12:44:37 Program: Started
12:45:01 Standby: Starting
12:45:01 Timer: Created
12:45:01 Timer: Started
12:45:21 Standby: Finished
12:45:24 Info: Finished by user input
12:46:01 Timer: Finished successfully
12:46:33 Program: Closed
Vista -> Hibernate in the start menu:
12:47:44 Program: Started
12:48:05 Standby: Starting
12:48:05 Timer: Created
12:48:05 Timer: Started
12:49:46 Timer: Finished successfully
12:49:47 Info: Finished by user input
12:49:47 Standby: Finished
12:50:20 Program: Closed