Can't hold down a key with AutoHotkey - windows

I have a simple script that doesn't seem to behave as expected:
^j::
Send, {Down down}
Sleep, 10000
Send, {Down up}
Return
I would like it to hold the down arrow key for 10 seconds, and then release. Instead, it presses the down key once, and breaks the script until reload. What am I doing wrong?

Send documentation says:
When a key is held down via the method above, it does not begin auto-repeating like it would if you were physically holding it down (this is because auto-repeat is a driver/hardware feature).
Use SetKeyDelay and specify the number of repetitions:
SetKeyDelay, 30
Send {Down 333}
333 is approximately 10000/30
Alternatively you can do it in a loop and check for other keys in order to stop sending Down key.

Found a nice workaround, try some script like this (adjust the Mynumber variable to your liking and the Sleep aswell)
a::
Mynumber = 10
While Mynumber > 0
{
Send {Down DOWN}
Sleep 10
Send {Down UP}
Mynumber--
}

According to documentation this should work:
To hold down or release a key: Enclose in braces the name of the key followed by the word Down or Up. For example:
Send {b down}{b up}
Send {TAB down}{TAB up}
Send {Up down} ; Press down the up-arrow key.
Sleep 1000 ; Keep it down for one second.
Send {Up up} ; Release the up-arrow key.
docs about holding down keys: https://www.autohotkey.com/docs/commands/Send.htm

Related

Why is this if statement forcing zenity's --auto-close to close immediately?

Got a Debian package set up for a little application I was working on, this isn't really relevant for the question but for some context, the application is a simple bash script that deploys some docker containers on the local machine. But I wanted to add a dependency check to make sure the system had docker before it attempted to do anything. If it doesn't, download it, if it does, ignore it. Figured it be nice to have a little zenity dialog alongside it to show what was going on.
In that process, I check for internet before starting for obvious reasons and for some reason, the way I check if there is internet if zenity has the --auto-close flag, will instantly close the entire progress block.
Here is a little dummy example, that if statement is a straight copy-paste from my code, everything else is filler. :
#!/bin/bash
condition=0
if [[ $condition ]]; then
(
echo "0"
# Check for internet
if ping -c 3 -W 3 gcr.io; then
echo "# Internet detected, starting updates..."; sleep 1
echo "10"
else
err_msg="# No internet detected. You may be missing some dependencies.
Services may not function as expected until they are installed."
echo $err_msg
zenity --error --text="$err_msg"
echo "100"
exit 1
fi
echo "15"
echo "# Downloading a thing" ; sleep 1
echo "50"
if zenity --question --text="Do you want to download a special thing?"; then
echo "# Downloading special thing" ; sleep 1
else
echo "# Not downloading special thing" ; sleep 1
fi
echo "75"
echo "# downloading big thing" ; sleep 3
echo "90"
echo "# Downloading last thing" ; sleep 1
echo "100"
) |
zenity --progress --title="Dependency Management" --text="downloading dependencies, please wait..." \
--percentage=0 --auto-close
fi
So im really just wondering why this is making zenity freak-out. If you comment out that if statement, everything works as you expect and zenity progress screen closes once it hits 100.
If you keep the if statement but remove the auto-close flag, it will execute as expected. It's like its initializing at 100 and then going to 0 to progress normally. But if that was the case, --auto-close would never work but in the little example they give you in the help section, it works just fine. https://help.gnome.org/users/zenity/stable/progress.html.en
Thank you for a fun puzzle! Spoiler is at the end, but I thought it might be helpful to look over my shoulder while I poked at the problem. 😀️ If you're more interested in the answer than the journey, feel free to scroll. I'll never know, anyway.
Following my own advice (see 1st comment beneath the question), I set out to create a small, self-contained, complete example. But, as they say in tech support: Before you can debug the problem, you need to debug the customer. (No offense; I'm a terrible witness myself unless I know ahead of time that someone's going to need to reproduce a problem I've found.)
I interpreted your comment about checking for Internet to mean "it worked before I added the ping and failed afterward," so the most sensible course of action seemed to be commenting out that part of the code... and then it worked! So what happens differently when the ping is added?
Changes in timing wouldn't make sense, so the problem must be that ping generates output that gets piped to zenity. So I changed the command to redirect its output to the bit bucket:
ping -c 3 -W 3 gcr.io &>/dev/null;
...and that worked, too! Interesting!
I explored what turned out to be a few ratholes:
I ran ping from the command line and piped its output through od -xa to check for weird control characters, but nope.
Instead of enclosing the contents of the if block in parentheses (()), which executes the commands in a sub-shell, I tried braces ({}) to execute them in the same shell. Nope, again.
I tried a bunch of other embarrassingly useless and time-consuming ideas. Nope, nope, and nope.
Then I realized I could just do
ping -c 3 -W 3 gcr.io | zenity --progress --auto-close
directly from the command line. That failed with the --auto-close flag but worked normally without it. Boy, did that simplify things! That's about as "smallest" as you can get. But it's not, actually: I used up all of my remaining intelligence points for the day by redirecting the output from ping into a file, so I could just
(cat output; sleep 1) | zenity --progress --auto-close
and not keep poking at poor gcr.io until I finally figured this thing out. (The sleep gave me enough time to see the pop-up when it worked, because zenity exits when the pipe closes at the end of the input. So, what's in that output file?
PING gcr.io (172.253.122.82) 56(84) bytes of data.
64 bytes from bh-in-f82.1e100.net (172.253.122.82): icmp_seq=1 ttl=59 time=18.5 ms
64 bytes from bh-in-f82.1e100.net (172.253.122.82): icmp_seq=2 ttl=59 time=21.8 ms
64 bytes from bh-in-f82.1e100.net (172.253.122.82): icmp_seq=3 ttl=59 time=21.4 ms
--- gcr.io ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 18.537/20.572/21.799/1.449 ms
The magic zenity-killer must be in there somewhere! All that was left (ha, "all"!) was to make my "smallest" example even smaller by deleting pieces of the file until it stopped breaking. Then I'd put back whatever I'd deleted last, and I deleted something else, da capo, ad nauseam, or at least ad minimus. (Or whatever; I don't speak Latin.) Eventually the file dwindled to
64 bytes from bh-in-f82.1e100.net (172.253.122.82): icmp_seq=1 ttl=59 time=18.5 ms
and I started deleting stuff from the beginning. Eventually I found that it would break regardless of the length of the line, as long as it started with a number that wasn't 0 and had at least 3 digits somewhere within it. Huh. It'd also break if it did start with a 0 and had at least 4 digits within... unless the second digit was also 0! What's more, a period would make it even weirder: none of the digits anywhere after the period would make it break, no matter what they were.
And then, then came the ah-ha! moment. The zenity documentation says:
Zenity reads data from standard input line by line. If a line is
prefixed with #, the text is updated with the text on that line. If a
line contains only a number, the percentage is updated with that
number.
Wow, really? It can't be that ridiculous, can it?
I found the source for zenity, downloaded it, extracted it (with tar -xf zenity-3.42.1.tar.xz), opened progress.c, and found the function that checks to see if "a line contains only a number." The function is called only if the first character in the line is a number.
108 static float
109 stof(const char* s) {
110 float rez = 0, fact = 1;
111 if (*s == '-') {
112 s++;
113 fact = -1;
114 }
115 for (int point_seen = 0; *s; s++) {
116 if (*s == '.' || *s == ',') {
117 point_seen = 1;
118 continue;
119 }
120 int d = *s - '0';
121 if (d >= 0 && d <= 9) {
122 if (point_seen) fact /= 10.0f;
123 rez = rez * 10.0f + (float)d;
124 }
125 }
126 return rez * fact;
127 }
Do you see it yet? Here, I'll give you a sscce, with comments:
// Clear the "found a decimal point" flag and iterate
// through the input in `s`.
115 for (int point_seen = 0; *s; s++) {
// If the next char is a decimal point (or a comma,
// for Europeans), set the "found it" flag and check
// the next character.
116 if (*s == '.' || *s == ',') {
117 point_seen = 1;
118 continue;
119 }
// Sneaky C trick that converts a numeric character
// to its integer value. Ex: char '1' becomes int 1.
120 int d = *s - '0';
// We only care if it's actually an integer; skip anything else.
121 if (d >= 0 && d <= 9) {
// If we saw a decimal point, we're looking at tenths,
// hundredths, thousandths, etc., so we'll need to adjust
// the final result. (Note from the peanut gallery: this is
// just ridiculous. A progress bar doesn't need to be this
// accurate. Just quit at the first decimal point instead
// of trying to be "clever."
122 if (point_seen) fact /= 10.0f;
// Tack the new digit onto the end of the "rez"ult.
// Ex: if rez = 12.0 and d = 5, this is 12.0 * 10.0 + 5. = 125.
123 rez = rez * 10.0f + (float)d;
124 }
125 }
// We've scanned the entire line, so adjust the result to account
// for the decimal point and return the number.
126 return rez * fact;
Now do you see it?
The author decides "[i]f a line contains only a number" by checking (only!) that the first character is a number. If it is, then it plucks out all the digits (and the first decimal, if there is one), mashes them all together, and returns whatever it found, ignoring anything else it may have seen.
So of course it failed if there were 3 digits and the first wasn't 0, or if there were 4 digits and the first 2 weren't 0... because a 3-digit number is always at least 100, and zenity will --auto-close as soon as the progress is 100 or higher.
Spoiler:
The ping statement generates output that confuses zenity into thinking the progress has reached 100%, so it closes the dialog.
By the way, congratulations: you found one of the rookiest kinds of rookie mistakes a programmer can make... and it's not your bug! For whatever reason, the author of zenity decided to roll their own function to convert a line of text to a floating-point number, and it doesn't do at all what the doc says, or what any normal person would expect it to do. (Protip: libraries will do this for you, and they'll actually work most of the time.)
You can score a bunch of karma points if you can figure out how to report the bug, and you'll get a bonus if you submit your report in the form of a fix. 😀️

Remapping minus key on numeric keyboard

I'm new to linux and even after after searching for more than an hour and help from my IT friends I didn't find a "name" for minus key on numeric keyboard. I'm searching for something like
key <PAUS> { [ Home ] };
key <INS> { [ End ] };
specifically that first part out of the brackets. I've already tried
key <MNS>, key <MINS>, key <MNUS>
but it broke my keyboard (I ended up with EN keyboard), which I assume means, there's a mistake in the code.
Does anybody know the "name"? Thanks.
Open a terminal and:
xev -event keyboard
Press the minus key on numeric keyboard and see the key code.
On my system I obtain this output:
KeyRelease event, serial 28, synthetic NO, window 0x1c00001,
root 0x31b, subw 0x0, time 1053670, (75,41), root:(1768,207),
state 0x0, keycode 82 (keysym 0xffad, KP_Subtract), same_screen YES,
XLookupString gives 1 bytes: (2d) "-"
XFilterEvent returns: False
The keycode is 82
I see from your tag that you are using Ubuntu 20.04.
You can see that the file /usr/share/X11/xkb/symbols/pc has the line:
keycode 82 = KP_Subtract KP_Subtract KP_Subtract KP_Subtract KP_Subtract KP_Subtract XF86Prev_VMode

AppleScript Progress Bar with Reboot Button

I've recently been tasked with working on a project to switch antivirus software in the environment as we are changing to a new vendor. Naturally, this kind of effort will require a reboot on our endpoints once the old A/V software has been removed.
What I'm trying to do is create a reboot notification prompt for our Mac customers (since many are high up on the food chain) to let them know what we are doing.
This prompt will let them know they have 3 hours to reboot their Macs to complete the removal of the software if they are logged in. If they do not reboot within 3 hours, their workstation will automatically reboot.
Applescript has a great workflow that allows me to effectively communicate with our users, but I would like to change the Stop button to a reboot, so that our customers can reboot their Macs on demand.
I will work in logic later on to automatically reboot if they are not logged in.
I modified one of the scripts that I, "borrowed," from here
https://macscripter.net/viewtopic.php?id=46572
-- Progress Bar - Reboot Timer
progress_timer("03:00:00", "Reboot Timer") -- call the progress timer with an HMS time and timer label
return result
------------------------------------------
-- subroutines in alphabetical order --
------------------------------------------
-- getTimeConversion converts a time in HMS format (hh:mm:ss) to a time in seconds
on getTimeConversion(HMS_Time)
set HMSlist to the words of HMS_Time -- get {hh, mm, ss} from HMS time (p. 158 Rosenthal)
set theHours to item 1 of HMSlist
set theMinutes to item 2 of HMSlist
set theSeconds to item 3 of HMSlist
return round (theHours * (60 ^ 2) + theMinutes * 60 + theSeconds)
end getTimeConversion
-- progress_timer displays the elapsed time in a progress bar. For information on progress bars see: https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/DisplayProgress.html
on progress_timer(HMS_Time, timerLabel)
set theTimeSec to getTimeConversion(HMS_Time) -- convert the HMS format to seconds
set progress total steps to theTimeSec
set progress completed steps to 0
set startTime to (current date)
repeat with i from 0 to theTimeSec -- begin at i = 0 to start the timer at 00:00:00
set HMS_SoFar to TimetoText(i) -- convert the seconds so far to HMS format for display
set HMS_ToGo to TimetoText(theTimeSec - i) -- convert the seconds to go to HMS format for display
set progress completed steps to 0
set progress description to "
Your IT Department needs to make changes to your Mac.
Your workstation must be rebooted in order for these changes to take effect.
Your workstation will reboot in " & HMS_ToGo
set progress additional description to ¬
""
--"Time Elapsed: " & HMS_SoFar & return & ¬
--"Counting Down: " & HMS_ToGo
set progress completed steps to i
set elapsedTime to (current date) - startTime -- get actual elapsed time for adjusting delay
set lagAdjust to elapsedTime - i -- compute lag adjustment
delay 1 - lagAdjust -- delay 1 second minus any cumulative lag that needs removing
end repeat
--set HMS_Elapsed to TimetoText(elapsedTime) -- convert elapsedTime back to HMS format for display
set dialogText to null
--set dialogText to "Elapsed Time: " & return & ¬
-- "Nominal = " & HMS_Time & return & ¬
-- "Actual = " & HMS_Elapsed
tell me to activate
--display dialog dialogText with title timerLabel & " Timer"
return dialogText
end progress_timer
-- TimetoText converts a time in seconds to a time in HMS format (hh:mm:ss)
on TimetoText(theTime)
-- parameters - TheTime [integer]: the time in seconds
-- returns [text]: the time in the format hh:mm:ss
-- Nigel Garvey points out this script is only valid for parameter values up to 86399 seconds (23:59:59) and offers a solution for longer times here: https://macscripter.net/viewtopic.php?pid=134656#p134656
if (class of theTime) as text is "integer" then
set TimeString to 1000000 + 10000 * (theTime mod days div hours) -- hours
set TimeString to TimeString + 100 * (theTime mod hours div minutes) -- minutes
set TimeString to (TimeString + (theTime mod minutes)) as text -- seconds
tell TimeString to set theTime to (text -6 thru -5) & ":" & (text -4 thru -3) & ":" & (text -2 thru -1)
end if
return theTime
end TimetoText
Here's a screenshot of the resulting output.

How to make ruby loops wait for 15 min after each 300 element?

I want to make ruby script that will print all followers for any account, but twitters API will gife me an error (too many requests) after 300 printed follower, how can i make loop to print the frist 300 then wait for 15 min then to start where its done to another 300?
you can do it like:
some_variable = 0
loop do
#**your code that puts element **
some_variable += 1
sleep(15*60) if (some_variable % 300).zero?
end

Pick up particular lines from a text file and calculate them together

This might be quite a long shot, but I've written an AppleScript for myself that keeps log on how long I work for whatever project. I'd like to create another script that calculates the overall spent time based on the info from a log file.
Generally my log files look like this:
140304 1353 - Start
140304 1459 - End
work time : 0106
break time : 0000
140307 1248 - Start
140307 1353 - End
work time : 0105
break time : 0000
140321 1101 - Start
140321 1306 - Have a break now
140321 1342 - Back to work
140321 1423 - Have a break now - Go eat
140321 1522 - Back to work
140321 1522 - End
work time : 0246
break time : 0135
So I would need to get every "work time" value and calculate them together.
I've tried googling around this, but I'm not sure how to get started.
Applescript's Text Item Delimiters (TIDs) are perfect for this. We just break the text up into a list by setting TIDs to the text just before the number you want to grab, in this case "work time". Then I just grab the next word after the TIDs because that's the number. So it's easy. The result of your example is 457. My script returns the sum plus shows all the values used to calculate the sum: {457, {"0106", "0105", "0246"}}
set workLog to "140304 1353 - Start
140304 1459 - End
work time : 0106
break time : 0000
140307 1248 - Start
140307 1353 - End
work time : 0105
break time : 0000
140321 1101 - Start
140321 1306 - Have a break now
140321 1342 - Back to work
140321 1423 - Have a break now - Go eat
140321 1522 - Back to work
140321 1522 - End
work time : 0246
break time : 0135"
set beforeText to "work time"
set AppleScript's text item delimiters to beforeText
set textItems to text items of workLog
set AppleScript's text item delimiters to ""
set theSum to 0
set sumItems to {}
repeat with i from 2 to count of textItems
set workTime to word 1 of (item i of textItems)
set end of sumItems to workTime
set theSum to theSum + (workTime as number)
end repeat
return {theSum, sumItems}
Now just replace the first line in my code with this and it will work for you on your log file. Good luck.
set workLog to read (choose file)
Here's a do shell script-based alternative to #regulus6633's pure AppleScript answer:
Note: I'm not sure what your work-time numbers such as 0106 represent - here I'm simply assuming that they are decimal integers to be summed.
set logFile to "/Users/jdoe/hours.log" # Replace with POSIX path to your log file
set total to do shell script ¬
"awk '/^work time :/ { total+=$4 } END { print total }' " ¬
& quoted form of logFile
The shell command uses awk, which greatly simplifies parsing.

Resources