How to replace sleep timer when hotkey used again

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 08 Dec 2022, 12:17

I'm trying to create a script to mute my laptop and automatically unmute after a specified amount of time.
Simple: The script mutes the sound by sending {Volume_Mute}, then sleeps for the specified time, then sends {Volume_Mute} to unmute the sound.

All that works fine, but if I press the hotkey again, I need it to abandon the previous sleep timer and start again. (I might need to extend or change how long my laptop is muted for). I had hoped that #SingleInstance Force would do that, but it doesn't work.

What happens is that both instances seem to run concurrently, and then wait until the final sleep timer has completed, and then both instances issue the keystroke {Volume_Mute}.
So the sound is unmuted and immediately muted again.

How can I make this work correctly? Note - the final code will mute for up to 60 minutes, not just a few seconds.

Code: Select all

#SingleInstance Force

^m::
Gui, destroy
Gui, font,bold
Gui,Add,Text,,Mute volume for:
Gui, font
Gui, Add, Radio, altsubmit gSetMute vOption, 20 seconds
Gui, Add, Radio, altsubmit gSetMute, 30 seconds
Gui, Show
return

SetMute:
GuiClose:
GuiEscape:	
Gui, Submit  ; Save each control's contents to its associated variable.

SoundGet, master_mute,, Mute
if (master_mute = "Off")
   Send {Volume_Mute} 

if (Option = 1){
MsgBox, , Audio Volume, Muted for 20 seconds, 2
Sleep, 20000
}	
if (Option = 2){
MsgBox, , Audio Volume, Muted for 30 seconds, 2
Sleep, 30000
}
SoundGet, master_mute,, Mute
if (master_mute = "On")
    msgbox, unmute ;instead of  "send {Volume_Mute}" to see whats happening

return

User avatar
mikeyww
Posts: 26939
Joined: 09 Sep 2014, 18:38

Re: How to replace sleep timer when hotkey used again

Post by mikeyww » 08 Dec 2022, 13:38

Welcome to this AutoHotkey forum!

You can change your Sleep to a :arrow: SetTimer. That "fixes" a lot of issues like this, because you don't have to worry about a hotkey interrupting the subroutine.

If a timer is already running when you set it, it resets. It sounds like that is what you want here, too.

By default, pressing a hotkey will interrupt another running subroutine. When the interrupting routine finishes, the interrupted routine resumes. Thread priorities can be adjusted when needed, and Critical is occasionally useful.

User avatar
flyingDman
Posts: 2817
Joined: 29 Sep 2013, 19:01

Re: How to replace sleep timer when hotkey used again

Post by flyingDman » 08 Dec 2022, 14:01

try:

Code: Select all

^m::
Gui, destroy
Gui, font,bold
Gui, Add,Text,,Mute volume for:
Gui, font
Gui, Add, Radio, altsubmit gSetMute vOption, 20 seconds
Gui, Add, Radio, altsubmit gSetMute, 30 seconds
Gui, Show
return

SetMute:
GuiClose:
GuiEscape:
Gui, Submit
SoundGet, master
SoundSet, -100
settimer, lbl, % option = 1 ? 20000 : 30000          ; use switch in case there more than 2 options
return

lbl:
SoundSet, % master
return
14.3 & 1.3.7

adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

Re: How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 10 Dec 2022, 05:25

flyingDman wrote:
08 Dec 2022, 14:01
try:
That works, except I needed to add "SetTimer, lbl, Off" to stop the timer repeating. (See my new code below).

I do have a few questions:

1. I think flyingDman's code uses "forced expressions". But, I don't understand them:
a. How does this work? settimer, lbl, % option = 1 ? 20000 : 30000
b. How does this set the volume? SoundSet, % master

2. My new code uses switch, because I need more than 2 options. Is there a better way to do this?

3. flyingDman's code provides a more succinct way to set the volume than my original code. But, is there a succinct way to actually use mute ? It's important that mute is set "on" or "off" and not merely toggled.

Here's my new code:

Code: Select all

^m::
Gui, destroy
Gui, font,bold
Gui,Add,Text,,Mute volume for:
Gui, font
Gui, Add, Radio, altsubmit gSetMute vOption, 10 seconds
Gui, Add, Radio, altsubmit gSetMute, 20 seconds
Gui, Add, Radio, altsubmit gSetMute, 30 seconds
Gui, Show
return

SetMute:
GuiClose:
GuiEscape:	
Gui, Submit  ; Save each control's contents to its associated variable.
SoundGet, master
SoundSet, -100

Switch Option
{
Case 1:
    MsgBox, , Audio Volume, Muted for 10 seconds, 2
    settimer, lbl, 10000
Case 2:
    MsgBox, , Audio Volume, Muted for 20 seconds, 2
    settimer, lbl, 20000
Case 3:
    MsgBox, , Audio Volume, Muted for 30 seconds, 2
    settimer, lbl, 30000
Default: 
    msgbox, No match
}
return

lbl:

SetTimer, lbl, Off
SoundSet, % master
SoundSet, +100
return

adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

Re: How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 10 Dec 2022, 05:27

mikeyww wrote:
08 Dec 2022, 13:38
You can change your Sleep to a :arrow: SetTimer. That "fixes" a lot of issues like this, because you don't have to worry about a hotkey interrupting the subroutine.

If a timer is already running when you set it, it resets. It sounds like that is what you want here, too.

By default, pressing a hotkey will interrupt another running subroutine. When the interrupting routine finishes, the interrupted routine resumes. Thread priorities can be adjusted when needed, and Critical is occasionally useful.
Thanks, that explains exactly what was going on with my original code.

adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

Re: How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 10 Dec 2022, 05:57

Was hoping to edit my reply above, but can't seem to do so.
adrianpaulwood wrote:
10 Dec 2022, 05:25
Here's my new code:
Had to also add this to my new code, so the sound wasn't muted when no option is chosen:

Code: Select all

Case default:
	Goto, lbl
Is there any better way?

Also, I needed to use either this:
SoundSet, % master

or, this:
SoundSet, +100

But not both.

Any advice which is better?

adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

Re: How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 10 Dec 2022, 07:16

Here is my latest code which actually works (that goto lbl didnt' work).
Please still answer my questions above, so I can hopefully understand better / improve my code.
Thanks.

Code: Select all

^m::
Gui, destroy
Gui, font,bold
Gui,Add,Text,,Mute volume for:
Gui, font
Gui, Add, Radio, altsubmit gSetMute vOption, 10 seconds
Gui, Add, Radio, altsubmit gSetMute, 20 seconds
Gui, Add, Radio, altsubmit gSetMute, 30 seconds
Gui, Show
return

GuiClose:
GuiEscape:
Gui, destroy
return

SetMute:
Gui, Submit  ; Save each control's contents to its associated variable.

SoundGet, master
SoundSet, -100

Switch Option
{
Case 1:
    MsgBox, , Audio Volume, Muted for 10 seconds, 2
    settimer, lbl, 10000
Case 2:
    MsgBox, , Audio Volume, Muted for 20 seconds, 2
    settimer, lbl, 20000
Case 3:
    MsgBox, , Audio Volume, Muted for 30 seconds, 2
    settimer, lbl, 30000
}
return

lbl:

SetTimer, lbl, Off
SoundSet, % master
return

User avatar
flyingDman
Posts: 2817
Joined: 29 Sep 2013, 19:01

Re: How to replace sleep timer when hotkey used again

Post by flyingDman » 10 Dec 2022, 12:54

See here https://www.autohotkey.com/docs/Variables.htm under "force an expression".
See here https://www.autohotkey.com/docs/Variables.htm#ternary for ternary operations
Mute it is especially handy when you want to toggle the sound off and on like so:

Code: Select all

^+m::
t := (u:=!u) ? "+1" : "-1"
SoundSet, %t%, , mute
But I don't see the benefit otherwise.

SoundSet, +100 and SoundSet, % master are 2 different things. The latter reverts the sound level to the value set when using SoundGet, master. SoundSet, +100 turns sound to 100%.
To have a settimer excute once (i.e. not repeating) use a "-" in front of the time. Then there is no need to call SetTimer, lbl, Off. (my bad; should have used it in the example above)
Re: Goto: there are very few legitimate uses for it. In most case it needs to be replaced with Gosub.
If the mute period corresponds to the value of option, you can use something like:

Code: Select all

^m::
Gui, destroy
Gui, font,bold
Gui, Add,Text,,Mute volume for:
Gui, font
Gui, Add, Radio, gSetMute vOption, 10 seconds
Gui, Add, Radio, gSetMute, 20 seconds
Gui, Add, Radio, gSetMute, 30 seconds
Gui, Show
return

SetMute:
GuiClose:
GuiEscape:
Gui, Submit
SoundGet, master
SoundSet, -100
msgbox,,, % "sound muted for: " option * 10 "sec.",1
settimer, lbl, % option * -10000
return

lbl:
SoundSet, % master
return
14.3 & 1.3.7

adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

Re: How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 10 Dec 2022, 15:04

Thanks, I'll do some reading and coding and try to understand all your help!

adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

Re: How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 11 Dec 2022, 14:50

So I'm documenting what I've learned here to help me and perhaps other newbies - do I have all this correct?

In the following statement:

Code: Select all

settimer, lbl, % option = 1 ? 20000 : 30000
% indicates that what follows is an expression. This expression evaluates to the required parameter for settimer. In this case, the expression is a ternary operator - shorthand for an if else statement. So:
if option =1 , then the expression evaluates to 20000
else, the expression evaluates to 30000

Since I need more than 2 options, I can't use a ternary operator: I need to use either multiple if / else statements, or, better still use a switch statement.

For the following statements:

Code: Select all

SoundGet, master   ; (1)
SoundSet, -100   ; (2)
SoundSet, % master   ; (3)
(1) gets the current soundlevel for the default device and control (master > volume) and saves it in the variable master
(2) reduces the volume by 100 (effectively reducing it to zero). Again this is working on the default (master > volume).
(3) is a forced expression that sets the soundlevel to that saved in the variable master. Again for master > volume.

However:
Am I correct I could simply mute / unmute using the following code snippets instead? At least, it seems to work on my laptop.

Code: Select all

^m::
SoundSet, 1,, Mute ; turns mute on
return
^l::
SoundSet, 0,, Mute ; turns mute off
return

I still don't fully understand this:

Code: Select all

^+m::
t := (u:=!u) ? "+1" : "-1"
SoundSet, %t%, , mute
I can see it is using a ternary operator toggling the value of the variable t to +1 or -1, and then using Soundset to set the mute on or off. But how does the condition (u:=!u) work?

User avatar
boiler
Posts: 16960
Joined: 21 Dec 2014, 02:44

Re: How to replace sleep timer when hotkey used again

Post by boiler » 11 Dec 2022, 14:56

u := !u toggles the value of u between True and False. It s says "u is assigned not u".

You nest ternary operators so you can have it act on multiple possible values. Example:

Code: Select all

loop, 3
	MsgBox, % A_Index = 1 ? "one" : (A_Index = 2 ? "two": "three")

adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

Re: How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 12 Dec 2022, 12:29

I've found a way to avoid using switch or nested ternary operators - using an array for the various options. This makes the code simpler and easier to manage. However, I guess it could be improved further by iterating through the array to set the options in the Gui, so I could then simply change the array to change the options without touching the rest of the code. Can anyone show me the easiest way to do that?

Code: Select all

Options := [10,20,35,50,65] 

^m::
Gui, destroy
Gui, font,s12 bold
Gui, Add,Text,,Mute volume for:
Gui, font,s12 normal
Gui, Add, Radio, altsubmit gSetMute vOption, % Options[1] . " minutes"
Gui, Add, Radio, altsubmit gSetMute, % Options[2] . " minutes"
Gui, Add, Radio, altsubmit gSetMute, % Options[3] . " minutes"
Gui, Add, Radio, altsubmit gSetMute, % Options[4] . " minutes"
Gui, Add, Radio, altsubmit gSetMute, % Options[5] . " minutes"

Gui, Show
return

GuiClose:
GuiEscape:
Gui, destroy
return

SetMute:
Gui, Submit  ; Save each control's contents to its associated variable.
SoundSet, true,, Mute ; turns mute on
endtime := A_now
endtime += Options[Option], minutes
FormatTime, endtime, % endtime, Time 
MsgBox, , % "Audio Volume", % "Muted for " . Options[Option] . " minutes until " . endtime, 1
settimer, lbl, % Options[Option] * -60000
return

lbl:
SoundSet, false,, Mute ; turns mute off
return

User avatar
flyingDman
Posts: 2817
Joined: 29 Sep 2013, 19:01

Re: How to replace sleep timer when hotkey used again

Post by flyingDman » 12 Dec 2022, 12:49

Code: Select all

^m::
Gui, destroy
Gui, font,s12 bold
Gui, Add,Text,,Mute volume for:
Gui, font,s12 normal
for x,y in options
	Gui, Add, Radio,% "altsubmit gSetMute v" (x = 1 ? option : ""), % y . " minutes"
Gui, Show
return
BTW a ternary becomes easier to read when written this way:

Code: Select all

loop, 7
	MsgBox, % A_Index = 1 ? "one"
	: A_Index = 2 ? "two"
	: A_Index = 3 ? "three"
	: A_Index = 4 ? "four"
	: A_Index = 5 ? "five"
	: A_Index = 6 ? "six"
	: A_Index = 7 ? "seven" : ""
14.3 & 1.3.7

adrianpaulwood
Posts: 9
Joined: 08 Dec 2022, 11:53

Re: How to replace sleep timer when hotkey used again

Post by adrianpaulwood » 13 Dec 2022, 16:07

Thanks - both look like excellent ways to code stuff like this. When I get time I'll give your code a go.

Post Reply

Return to “Ask for Help (v1)”