Trigger on changing Focus mode introduced in MacOS Monterey

Hi Andreas,

I updated my BTT and noticed that you added BTT to Shortcuts, thank you. However, I think it would be great if I can set up a trigger on changing focus mode (ex. when I activate/deactivate focus "work" or any others). Though, it's possible to use automation on both iPhone and iPad that allows to set up running any shortcuts actions when any focus is on/off.

Seconding this. I use keyboard shortcuts to activate specific apps. It would be great if I could conditionally set a group of apps based on a focus mode.

1 Like

Bump - I think this would be super awesome!

Bump - Would be a game changer for me

if you just need to trigger if focus mode is enabled/disabled, you can use the
"Trigger on Script Output Change" with this script:

return get_number_variable("SystemDoNotDisturbState");

1 Like

Game changer!

While this is already great, it does not trigger when changing from one focus mode to the other, which would be my intended use case. Thanks anyways!

If you need the focus mode name you can achieve that with some scripting however it

  • requires BTT 4.728 alpha
  • BTT must have full disk access in System Settings => Privacy & Security => Full Disk Access

Then you can use this script (modify the bottom condition to check for your desired focus mode)

let jax = `
var app = Application.currentApplication()
app.includeStandardAdditions = true

function getJSON(path) {
	const fullPath = path.replace(/^~/, app.pathTo('home folder'))
	const contents = app.read(fullPath)
	return JSON.parse(contents)
}

function run() {

	let focus = "No focus" // default
	const assert = getJSON("~/Library/DoNotDisturb/DB/Assertions.json").data[0].storeAssertionRecords
	const config = getJSON("~/Library/DoNotDisturb/DB/ModeConfigurations.json").data[0].modeConfigurations

	if (assert) { // focus set manually

		const modeid = assert[0].assertionDetails.assertionDetailsModeIdentifier
		focus = config[modeid].mode.name

	} else { // focus set by trigger

		const date = new Date
		const now = date.getHours() * 60 + date.getMinutes()

		for (const modeid in config) {

			const triggers = config[modeid].triggers.triggers[0]
			if (triggers && triggers.enabledSetting == 2) {

				const start = triggers.timePeriodStartTimeHour * 60 + triggers.timePeriodStartTimeMinute
				const end = triggers.timePeriodEndTimeHour * 60 + triggers.timePeriodEndTimeMinute
				if (start < end) {
					if (now >= start && now < end) {
						focus = config[modeid].mode.name
					}
				} else if (start > end) { // includes midnight
					if (now >= start || now < end) {
						focus = config[modeid].mode.name
					}
				}
			}
		}
	}
	return focus
}
`

let focusModeName = await runJAX(jax);

// check for the localized focusmode name
if(focusModeName == "Sleep") {
    return true;
} else {
   return false;
}
 

Thanks for the script!

I have not yet gotten it to work reliable enough for me, but I may be doing something wrong :sweat_smile:

Anyway, I think I'm looking for a more integrated solution that basically replicates the automation provided by the Shortcuts app on iOS.

I found myself here because I want to change my background/wallpaper based on the focus mode. Like you can do on iOS. It's a great feature.

I see the post above by Andreas, but I'm not sure how to do what I want. Of course, this as a native feature of BTT would be absolutely awesome.

If we wanted to use this for multiple Focus modes, would the best way be to create multiple scripts – one for each mode where we want a different action depending on the true result?

probably yes, but then all scripts should call the same function to determine the state and that function should do some debouncing / caching. I‘ll give it a try later!

Ah I think a better solution to this use case is to run the script periodically and update a variable if the focus mode has changed. Then you can use the " Variable Value Did Change" trigger:

Example Preset:
exported_triggers.bttpreset (13.3 KB)

Here is a slightly updated script REQUIRES BTT 4.789

async function checkFocusState() {
	let focus = "No focus";
    let assertions = JSON.parse(readFile("~/Library/DoNotDisturb/DB/Assertions.json"));
	let modeConfigurations = JSON.parse(readFile("~/Library/DoNotDisturb/DB/ModeConfigurations.json"))
	let config = modeConfigurations.data[0].modeConfigurations;

	let manualFocus = assertions?.data[0]?.storeAssertionRecords;

	if(manualFocus) {
		const modeid = manualFocus[0]?.assertionDetails?.assertionDetailsModeIdentifier
		if(modeid) {
			focus = config[modeid]?.mode?.name
		}
	} else {
	const date = new Date
		const now = date.getHours() * 60 + date.getMinutes()

		for (const modeid in config) {

			const triggers = config[modeid].triggers.triggers[0]
			if (triggers && triggers.enabledSetting == 2) {

				const start = triggers.timePeriodStartTimeHour * 60 + triggers.timePeriodStartTimeMinute
				const end = triggers.timePeriodEndTimeHour * 60 + triggers.timePeriodEndTimeMinute
				if (start < end) {
					if (now >= start && now < end) {
						focus = config[modeid].mode.name
					}
				} else if (start > end) { // includes midnight
					if (now >= start || now < end) {
						focus = config[modeid].mode.name
					}
				}
			}
		}
	
	}
	let previousFocusMode = await get_string_variable("last_focus_mode");
	
	if(previousFocusMode != focus) {
		await set_string_variable({variableName: "last_focus_mode", to: focus})
	}


	return focus;
}

Then in the variable value did change you can have some if conditions:

in 4.890 I added a advanced trigger condition variable "focus_mode". BTT requires full disk access for this to work. (System Settings => Security & Privacy )

1 Like