How To Call It
Trigger is a global table: call its functions with a dot (Trigger.<method>(...)), not a colon. Requires Macro Handler 1.0.60 or later.
Processes events serially with check(event) and action(event,count). Returning false,value from an action stops the run; errors and normal completion close the subscription.
Documented for app version 1.0.59Updated:
Trigger is a global table: call its functions with a dot (Trigger.<method>(...)), not a colon. Requires Macro Handler 1.0.60 or later.
In automation flow, chain actions with short wait() intervals and use requestStop() or controlled retry on failure paths.
Required: options, handlers. Returns a Lua table.
events contains at least one event. packageName filters apps/notifications and textContains filters notification text. timeoutMs: 0–86400000 (default 60000; 0 until stopped), intervalMs: 50–60000 (250), cooldownMs: 0–86400000 (1000), maxEventAgeMs: 100–86400000 (30000), maxEvents: 0–1000000 (1; 0 unlimited). Combine constraints key/operator/value entries with constraintMode="all", "any" or "none". Unknown state cannot pass even a not-equal constraint. condition requires a check function and fires on false-to-true transitions; fireInitially=true also accepts the first true state. Event names: app_foreground, app_background, network_connected, network_disconnected, network_changed, internet_available, internet_lost, vpn_connected, vpn_disconnected, proxy_changed, screen_on, screen_off, device_unlocked, charger_connected, charger_disconnected, battery_changed, power_save_changed, headset_changed, orientation_changed, notification_posted, notification_removed, interval, condition, wifi_connected, wifi_disconnected, cellular_connected, cellular_disconnected, metered_changed, battery_low, battery_okay, time_changed, timezone_changed, charging_started, charging_stopped, headset_connected, headset_disconnected. State keys: connected, validated, vpn, proxy, screenOn, unlocked, charging, powerSave, battery, foregroundPackage, orientation, wifi, cellular, ethernet, metered, roaming, wiredHeadset, batteryTemperature, localTime, weekday, inputIdle. Operators are eq/ne, plus gt/lt/ge/le for numeric fields. Boolean values are true/false, orientation is portrait/landscape, battery is 0–100. Notification text filter is at most 512 UTF-16 units and is case-insensitive. constraintGroups supports up to four nested ALL/ANY/NONE levels and 64 total groups and conditions. source="event" reads event fields; default source="state" reads device state. String operators match literal case-sensitive text. Numbers accept gt/lt/ge/le/eq/ne; localTime uses inside/outside with HH:mm-HH:mm, including overnight windows. weekday is Monday=1 through Sunday=7; batteryTemperature is Celsius. priority is 0–100, higher first. Trigger.watch defaults omitted timeoutMs and maxEvents to zero. Actions wait until held input is released.
Observation lasts only while the macro runs; this is not a persistent background scheduler. At most 8 subscriptions and 128 events per subscription are allowed; overflow raises an error. Stop and run completion release listeners. App events require connected accessibility; notification events require connected notification access. VPN describes the default network visible to MacroHandler and proxy describes Android HTTP/PAC configuration; neither proves tunnel health or the traffic of other apps. For long-running coded observers put -- @mh:user-infinite-loop at the start of the script; visual blocks add it automatically. Without it the experimental native engine retains a 10-minute hard deadline. The directive preserves stop, heap and continuous active-code protections.
The snippet below is a starter pattern that can be applied directly in runtime flow.
-- @mh:user-infinite-loop
local result = Trigger.run({
events = {"vpn_disconnected", "network_disconnected", "internet_lost"},
timeoutMs = 60000, cooldownMs = 1000, maxEvents = 1
}, {
action = function(event)
local target = "com.example.game"
if System.currentPackage() == target then
assert(System.backgroundApp(target), "Could not send app to background")
end
assert(System.postNotification("Connection changed", event.type), "Notifications are unavailable")
end
})
print(result.reason, result.count)From foundation to combined usage, each level is provided as a separate code block so you can copy the level you need and adapt it directly.
-- @mh:user-infinite-loop
local result = Trigger.run({
events = {"vpn_disconnected", "network_disconnected", "internet_lost"},
timeoutMs = 60000, cooldownMs = 1000, maxEvents = 1
}, {
action = function(event)
local target = "com.example.game"
if System.currentPackage() == target then
assert(System.backgroundApp(target), "Could not send app to background")
end
assert(System.postNotification("Connection changed", event.type), "Notifications are unavailable")
end
})
print(result.reason, result.count)local stepOk = true
-- @mh:user-infinite-loop
local result = Trigger.run({
events = {"vpn_disconnected", "network_disconnected", "internet_lost"},
timeoutMs = 60000, cooldownMs = 1000, maxEvents = 1
}, {
action = function(event)
local target = "com.example.game"
if System.currentPackage() == target then
assert(System.backgroundApp(target), "Could not send app to background")
end
assert(System.postNotification("Connection changed", event.type), "Notifications are unavailable")
end
})
print(result.reason, result.count)
if stepOk then
wait(200)
endlocal ok, err = pcall(function()
-- @mh:user-infinite-loop
local result = Trigger.run({
events = {"vpn_disconnected", "network_disconnected", "internet_lost"},
timeoutMs = 60000, cooldownMs = 1000, maxEvents = 1
}, {
action = function(event)
local target = "com.example.game"
if System.currentPackage() == target then
assert(System.backgroundApp(target), "Could not send app to background")
end
assert(System.postNotification("Connection changed", event.type), "Notifications are unavailable")
end
})
print(result.reason, result.count)
end)
if not ok then
print("API step failed: Trigger.run: " .. tostring(err))
requestStop()
end-- In automation flow, chain actions with short wait() intervals and use requestStop() or controlled retry on failure paths.
local function run_run_step()
-- @mh:user-infinite-loop
local result = Trigger.run({
events = {"vpn_disconnected", "network_disconnected", "internet_lost"},
timeoutMs = 60000, cooldownMs = 1000, maxEvents = 1
}, {
action = function(event)
local target = "com.example.game"
if System.currentPackage() == target then
assert(System.backgroundApp(target), "Could not send app to background")
end
assert(System.postNotification("Connection changed", event.type), "Notifications are unavailable")
end
})
print(result.reason, result.count)
end
local ok, err = pcall(run_run_step)
if not ok then
toast("Step failed")
print(err)
end-- @mh:user-infinite-loop
local result = Trigger.run({
events = {"vpn_disconnected", "network_disconnected", "internet_lost"},
timeoutMs = 60000, cooldownMs = 1000, maxEvents = 1
}, {
action = function(event)
local target = "com.example.game"
if System.currentPackage() == target then
assert(System.backgroundApp(target), "Could not send app to background")
end
assert(System.postNotification("Connection changed", event.type), "Notifications are unavailable")
end
})
print(result.reason, result.count)