Feature Request more control over when widget script is run

I just found BTT and have been creating a lot of custom widgets, it's awesome!

Something I found though is that I often want more control over how often the script for a widget is run. Either to dynamically change how often the script is run or to only run the script when the widget is tapped/long pressed.

Here are the cases I've run into so far, and my current solutions.

  1. Widget that is always visible but only needs to update when tapped/long pressed.

    My use case is a widget that toggles do not disturb and indicates whether DnD is currently on/off. Since I don't turn it on/off not through the widget I don't need it to ever run in the background and can use the actions to update the widget/run the script.

    In this case it would be nice to be able to specify that I never want the script to be run in the background.

    I have worked around this by specifying an absurdly large amount of time for how often to execute widget.

  2. Widget that is sometimes visible, but needs to update regularly when it is.

    I have a battery widget I only want to show in certain cases where I can't see the battery in the status bar (e.g. fullscreen app). I've figured out how to conditionally do that, however since I don't need it very often to be more performant/power efficient I would like to update the widget less often (e.g. 5/10 minutes), but have it update more regularly once it is visible (1 minute).

    I'm pretty sure there is a way to do this with multiple widgets and/or groups but haven't figured it out yet. I'm currently just having the widget run every minute. Even if I am able to find a workaround it would be nice to have a more explicit way to change the execution time, e.g. when returning the json add an extra execution key.

Thanks so much for this tool, I feel like I have a touchbar I can actually use now!

For 1.) you can use the refresh_widget function, see this: http://docs.bettertouchtool.net/docs/1102_apple_script.html
So you could just assign an apple script action that executes on tap / long press and triggers the refresh

2.) The script won't run at all if the conditional activation group is not active (if CAGs are used). Could you post an example on how you do it conditionally?
It would probably also be possible to use the update_trigger function to change the script repeat rate. I can have a look at your script if you post it here.

Thanks for the quick reply!

For 1) That's what I'm doing, however I have to put a value into how often to execute the script, which feels bad. It would be nice if there were a toggle to indicate that the script should never be run in the background instead of having to enter a very large number.

For 2) Here is the relevant portion of my script (checks if there is a window the full size of the screen, e.g. the status bar isn't visible):

tell application "Finder" to set displaySize to bounds of window of desktop

tell application "System Events"
	set appOfInterest to name of application processes whose frontmost is true
	set currentApplication to item 1 of appOfInterest
	tell process currentApplication
		set windowSize to (position of window 1) & (size of window 1)
	end tell
end tell

if windowSize is not displaySize then
	return ""
end if

It then goes on to format and return the json which is really long and isn't relevant for this conversation (and is copied from the GoldenChaos preset).

Conditionally active groups seem interesting, I didn't come across that in any of my searching. From a quick look it doesn't seem to directly support running a script and showing based on it's output but I could probably make it work with another widget that always returns "" but sets a BTT variable. It would be nice if it supported a script directly, but it seems like that would need a setting for how often it runs.

The update_trigger function seems interesting, are you suggesting something like this when the execution time needs to change?

tell application "BetterTouchTool"
    update_trigger "2F34005D-4537-464D-94E9-A7F42DA39DF1" json "{\"BTTTouchBarScriptUpdateInterval\" : 60}"
end tell

If so I could probably use a custom BTT variable to store the current state, and when the state changes tell BTT to change the update interval.

I'm going to give the update_trigger a try and see if it works, thanks for the idea!

For 1): If you enter 0 the script will only run once and all further runs must be triggered manually

For 2): Yep, the update_trigger would currently be the only option, conditional activation groups will probably get too complicated in your case. It would need to look like this:

tell application "BetterTouchTool"
	
	update_trigger "033E04C4-A3EC-4394-AF1E-D9AE145118B2" json "{\"BTTTriggerConfig\" : { \"BTTTouchBarScriptUpdateInterval\" : 10 }}"
end tell

However I'm not sure whether that's a good option and it might not even work at the moment ( I think updating the trigger config would cause an instant re-run and possibly cause an endless loop. Maybe it would indeed be a good idea to allow changing the update interval by returning an additional field in the JSON.
I'll think about that!

By the way, this is probably a better option to detect full screen mode:

tell front window of (first process whose frontmost is true)
   set isFullScreen to get value of attribute "AXFullScreen"
end tell

For 1) Good to know, I've updated my script. I didn't see that in the documentation anywhere, did I miss it or is that not documented?

For 2) I tried AXFullScreen, however it doesn't work for me for a couple reasons:

a) It doesn't work in all cases. One of the cases I care about is Twitch in Firefox where if you press f to make it fullscreen AXFullScreen still returns false (haven't tried other players or browsers).

b) I only want to display it when I can't see the status bar. Usually this is when I have only one monitor connected because I can see the battery on monitor A if there is a fullscreen window on monitor B.

I initially was checking if there was one monitor then checking if a window was fullscreen. But since the only reliable (in my tests) way to check if a window was fullscreen was to check the screen size I removed checking for multiple monitors since the window size effectively checks that too.

Also this more closely matches what I'm really checking: can I see the status bar on one of my monitors. If there is a window that's the full size of all of my monitors (1 or more) it is most likely covering the status bar.

Thanks again for all of the quick replies and information!

With some more testing this is what I ended up with for the script that changes it's execution time:

global batteryUpdateRateVariableName
global batteryUpdateRate

set batteryUpdateRateVariableName to "Battery_Update_Rate"

to set_battery_update_rate to rate
	if batteryUpdateRate is not rate then
		tell application "BetterTouchTool"
			set_persistent_number_variable batteryUpdateRateVariableName to rate
			update_trigger "B6D56F50-EFD1-488D-B02E-6B2855680DD3" json "{\"BTTTriggerConfig\" : { \"BTTTouchBarScriptUpdateInterval\" : " & rate & " }}"
		end tell
	end if
end set_battery_update_rate

tell application "BetterTouchTool"
	try
		set batteryUpdateRate to get_number_variable batteryUpdateRateVariableName
	end try
end tell

if batteryUpdateRate is missing value then
	set batteryUpdateRate to " "
end if

tell application "Finder" to set displaySize to bounds of window of desktop

tell application "System Events"
	set appOfInterest to name of application processes whose frontmost is true
	set currentApplication to item 1 of appOfInterest
	tell process currentApplication
		set windowSize to (position of window 1) & (size of window 1)
	end tell
end tell

if windowSize is not displaySize then
	set_battery_update_rate to 60
	return ""
else
	set_battery_update_rate to 300
end if
<<trimmed part that generates text and icon>>>

Storing the update rate is required because otherwise the refresh call will cause the widget to update even if nothing changed, resulting in it endlessly refreshing (which causes it to flicker forever with the default icon/text).

I'm also not saying this is the best way (or even a good way) to achieve this, but I had fun tinkering with it. I'm considering adding more widgets with the same visibility criteria, e.g. clock, so I'll probably look into alternatives like CAG in the future when I get around to it or someone points me towards a good way to do it.

Thanks again @Andreas_Hegenberg for all of the pointers, I've learned more today than in the last week and cleaned up several of the widgets I previously created.