Calendar Events

For QLab cues that need to happen at a specific time every day, or just on certain days of the week, a wall clock trigger can be programmed. For more complex scheduling, it can be useful to be able to trigger QLab cues directly from a calendar where cues can be dragged around the entire calendar or set to repeat at irregular intervals such as “the 4th Friday of every month.”

Example Application

Here’s an example project for a nine-day film festival that shows the programming of appropriate background music playlists and ambient pre-show video wall content for six different films in repertoire, each screened at different times on different days of the festival, with a clear overview of the entire festival timeline.

Calendar

Film festival planner

This shows the timeline calendar view of the festival. All the events are in a local calendar named QLab Pre Screen. The brown events are all set to open files when their alarms are triggered, which will start QLab cues.

Calendar event

The films are colored blue and have no alarms. (Although they could if the actual film presentations were going to be automated.)

The events are set to open apps that will start eight QLab Cues numbered “CAL_001” to “CAL_008” when each calendar event triggers its alarm.

QLab workspace

Film festival cue list

Cues “CAL_001” to “CAL_006” are Timeline Group cues containing a Video cue (set to bottom layer) with a film-specific graphic to be displayed on a video wall in the lobby, as well as a looping Playlist Group cue containing Audio cues for background music. Each Timeline Group is set, in the Triggers tab of the inspector, to fade and stop peers when the cue is started.

“CAL_007” is the festival logo and is also set to fade and stop peers. When started, it will crossfade the existing video content to the logo and fade out the currently playing audio.

“CAL_008” is a Timeline Group containing a sequence of Audio cues to sound warning chimes (called “Bar Bells” in the example workspace) at five minutes, two minutes, and one minute before showtime. This Group is not set to fade and stop peers, but each of the Audio cues in the Group is set to duck audio of other cues in this list. The result is that the music will duck down to ensure that the chimes are audible.

Here it is in action

The calendar is set to single day view for our film festival’s closing event, an all-night screening of three films starting at 0200.

The screen recording starts with the festival logo on screen, 10 seconds before 0100 when the lobby opens for pre-screening drinks.

At 0100 the calendar opens “CAL_001.app” which starts QLab cue “CAL_001”. Music for the first film of the night begins and the video crossfades from the logo to a film specific image. The screen recording then winds forward to 10 seconds before 0155 when “CAL_008” is triggered starting the “Bar Bells” sequence.

At 0200, cue “CAL_007” is triggered fading the music and replacing the film graphic with the festival logo.

A similar sequence is shown in the screen recording for each of the following films using the same logo and bar bells cue sequences, cue “CAL_004” for the second film, and cue “CAL_005” for the third.

Calendar vs BusyCal

Apple’s Calendar app, which is included as standard in macOS, would seem to be the obvious choice for this project but unfortunately, design choices and security attitudes adopted by Apple make it unsuitable for this project. Crucially, calendar events that are exported, copy-and-pasted, or even just dragged around will usually lose their “Open file” alerts. This makes Calendar entirely unreliable for our needs.

The examples in this chapter have been programmed and extensively tested over complex weekly schedules using an alternative and extraordinarily powerful calendar app called BusyCal.

BusyCal has a 28-day trial, after which a perpetual license for the app costs $50. Other calendar programs may also be suitable but should be tested thoroughly.

Creating a single calendar event to trigger a cue in QLab

Step 1: Create a numbered cue in QLab:

A cue in QLab

Step 2: Create an AppleScript in Script Editor which starts that cue:

tell application "QLab" to tell front workspace to start cue "CAL1"

and export that script as an application:

Step 3: Open the newly exported app and give it permission to control QLab when prompted:

Step 4: Create an event in BusyCal. Set an alarm in the event to open the exported script application. Set the time (in this screen recording the time is set to just after the current time so you can watch it trigger.)

Setting up a working system

If we want to set up a calendar for multiple events, with events that can be reused for multiple projects, it would be very useful to automate as much of this process as possible.

The method shown here has been tested with 1000 calendar events, but unless you have a need for so many, it is probably sensible to limit programming to no more than 100 calendar events.

These events will trigger 100 compiled AppleScript apps that directly trigger 100 numbered cues in QLab.

These calendar events can be set to repeat or copied and pasted as many times as you like in BusyCal.

Remember that this doesn’t actually limit you to 100 cues. A single calendar event can trigger Timeline and Playlist Group cues. For instance, a calendar event could trigger a Group cue that has cues for announcements at 30 and 15 minutes before a performance, plus bells and announcements for five, three, and one-minute warnings.

The steps required to set up a hundred calendar event system are as follows:

  1. Create a QLab workspace with Timeline Groups numbered using a clear and obvious scheme, such as “CAL_000” through “CAL_099”.
  2. Create 100 apps using Script Editor, one for each numbered cue in QLab.
  3. Create .ics files for 100 calendar events with alarms set to open each of the 100 AppleScript apps.
  4. Import these events into a BusyCal Calendar.

Security permissions

All the above should be quite straightforward, but will be complicated by the fact that the security features of macOS will need to be interacted with at every stage. Every app will require permission to control QLab, and every imported .ics event will need permission to control the specific app its alarm opens.

Generally, this only needs to be done once when performing the initial set-up on a computer, and the aim of this chapter is to provide users with the knowledge and helper scripts to allow a computer to be configured as a 50-event system in less than 10 minutes. (If you need more or fewer than 50, the helper scripts can be edited to create the correct number.)

Once all the permissions have been set, you can rename events, change their start and end times and repeat properties, and copy and paste them as you wish without any need to engage further with the macOS security dialogs unless you want to add the ability to trigger more cues.

One important point to note is that alarms set to open files will only work on the Mac they have been set up on. They can’t be synced over iCloud. However, individual calendar events and entire calendars can be exported and transferred to a different computer as files with their alarms intact, but the security permissions for each app and each calendar event will have to be set again on this new installation.

Detailed instructions

Here are the steps to create a system that will allow fifty Timeline Groups to be triggered from a BusyCal calendar.

Step 1: create a QLab workspace containing Timeline Groups numbered “CAL_001” to “CAL_050”

This can easily be automated in an open workspace (either new or existing) by running the following script (which is included in the Helper Scripts download, entitled “Generate numbered Timeline Groups.scpt”):

set thePrefix to "CAL_"
set theFirstCue to 1
set theLastCue to 50
set theDigitPad to 3
tell application id "com.figure53.QLab.5" to tell front workspace
  repeat with theIndex from theFirstCue to theLastCue
    make type "group"
    set theGroup to last item of (selected as list)
    set the mode of theGroup to timeline --or playlist
    set the q number of theGroup to thePrefix & (get text -1 thru (-theDigitPad) of ("00000000" & theIndex))
  end repeat
end tell

The script starts by setting variables to set the prefix, range, and zero padding of digits for your preferred numbering scheme

It then creates and numbers the Timeline Groups.

You can create Playlist Groups instead of Timeline Groups if you prefer, although Playlist groups can also be put inside a Timeline Groups.

Step 2: Create 50 apps named “CAL_001.app” to “CAL_050.app” from AppleScripts that will start the 50 QLab cues.

The apps are AppleScripts compiled as apps in Script Editor. As an example, the “CAL_001” app should be compiled from this script:

set theCue to "CAL_001"
tell application id "com.figure53.QLab.5" to tell front workspace
  try
    start cue theCue
  end try
end tell

Creating all fifty scripts can be automated using the following script (which is included in the Helper Scripts download, entitled “Generate start apps.scpt”):

do shell script "mkdir -p  ~/applications/QLabCalendarEventScripts"
set theFirstCue to 1 --first 
set theLastCue to 50 --last (99 is a sensible maximum)
set theDigitPad to 3
set theApplication to "com.figure53.QLab.5"
set theCuePrefix to "CAL_"

tell application "Finder" to set mypath to ((container of (path to me)) as text)
tell application "Script Editor"
  set tempPath to ((path to temporary items from user domain) as text) & "TempScript"
  repeat with theIndex from theFirstCue to theLastCue
    set doc to make new document
    save document 1 in file tempPath as "text"
    set doc to document 1
    tell doc
      set theIndex to get text -1 thru (-theDigitPad) of ("00000000" & theIndex)
      set selection to insertion point -1
      set contents of selection to ""
      set selection to insertion point -1
      set theScriptSource to "set theCue to " & quote & theCuePrefix & theIndex & quote & "
      tell application id " & quote & theApplication & quote & " to tell front workspace 
      try
      start cue theCue
      end try
      end tell"
      set contents of selection to theScriptSource
      compile
      set myfile to ((path to "apps" from user domain) & "QLabCalendarEventScripts:" & theCuePrefix & theIndex & ".app") as text
      save as "application" in myfile
      
    end tell
    set theDocName to (theCuePrefix & theIndex & ".app") as text
    tell application "Script Editor" to close (first document whose name is theDocName)
  end repeat
end tell

This script first creates a folder for the apps, named QLabCalendarEventScripts, in the user’s Application folder if one doesn’t already exist. This is a different folder than the main Applications folder used by macOS. It can be found in the user’s home folder.

It then sets variables to set the prefix, range, and zero padding of digits for your preferred numbering scheme and the application ID of the version of QLab you are using.

Finally, it creates the AppleScript required for each app in a temporary file, compiles it, saves it as an application, and closes it.

Step 3: Set permissions for these apps to control QLab

In order to allow these apps to control QLab, permissions must be set. The only way to set permission is to open each file and, when prompted by a dialog, grant the permission.

Again, this can be automated with a script (included in the Helper Scripts download, entitled “Event apps permissions.scpt”):

tell application "Finder"
  set myPath to ((path to "apps" from user domain) & "QLabCalendarEventScripts:") as text
  set theToBlessList to (get items of (myPath as alias) as list)
  repeat with eachItem in theToBlessList
    set theFile to eachItem as text
    open file theFile
    delay 1
    try
      tell application "System Events" to tell application process "UserNotificationCenter" to click button 3 of window 1
    end try
  end repeat
end tell

This script opens all the apps in the folder named QLabCalendarEventScripts in the user’s Application folder, one at a time. If permission is required for the app to control QLab, which will be the case if that app hasn’t been opened before, it automatically grants permission by using UI scripting to click the OK button in the permissions dialog box that appears.

Step 4: Create fifty .ics files named “CAL_001.ics” to “CAL_050.ics” with “Open file” alarms for the corresponding app

“CAL_001.ics” will have its alarm set to open “CAL_001.app” and so forth.

An .ics file is just a text file with specific information laid out in a specific way, so it’s not too difficult to automate the creation of one.

Here is the minimum amount of information required to create an .ics file with an “Open file” alarm:

BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20241111T000100
DTEND:20241111T000100
SUMMARY:CAL_001
BEGIN:VALARM
TRIGGER;VALUE=DURATION:PT0S
ACTION:PROCEDURE
ATTACH:file:///Users/micpool/Applications/QLabCalendarEventScripts/CAL_001.app
END:VALARM
END:VEVENT
END:VCALENDAR

The first line opens a VCALENDAR which encloses a VEVENT block. The VEVENT block contains:

  • DTSTART and DTEND are the starting and ending date and time, in the form YYYYMMDDTHHMMSS where the letter “T” is the separator between the date and the time. Calendars ignore the seconds data, i.e. events can only be set to the nearest minute. If you need to set an event to start offset by a number of seconds you can use pre-waits in QLab.
  • SUMMARY is the event name.
  • A VALARM block which contains everything about the alarm.
    • TRIGGER;VALUE=DURATION:PT0S means that the alarm will trigger at a specific time, and that specific time is 0 seconds before the start time of the event.
    • ACTION:PROCEDURE tells the alarm to launch a file.
    • ATTACH stores the path to the file to launch.

The creation of these .ics files can be automated with this script (included in the Helper Scripts download, entitled “Create ICS files.scpt”):

-- Script to generate .ics files for Calendar Events to Trigger QLab cues from BusyCal.app in a folder on the desktop named QLabBusyCalEvents

set theFirstCue to 0 --From this number
set theLastCue to 99 -- To this number
set thePrefix to "CAL_"
set theDate to 20241111 --YYYYMMDD for Calendar events. Should be date after current date
set theInterval to 5 -- interval between events in minutes 
set theOffset to 1 --Hour of time of first event


set thePath to ("//" & POSIX path of (path to "apps" from user domain) as text) & "QLabCalendarEventScripts/"
do shell script "mkdir -p  ~/desktop/QLabBusyCalEvents"

repeat with eachEvent from theFirstCue to theLastCue
  set theIndex to (characters -theDigitPad thru end of ("0000" & eachEvent)) as text
  set theMinuteString to characters -2 thru end of ("0000" & ((eachEvent * theInterval) mod 60)) as text
  set theHourString to characters -2 thru end of ("0000" & ((eachEvent * theInterval) div 60) + theOffset) as text
  set theTimeString to theHourString & theMinuteString & "00"
  set theSummary to thePrefix & theIndex
  set theText to "BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:" & theDate & "T" & theTimeString & "
DTEND:" & theDate & "T" & theTimeString & "
SUMMARY:" & thePrefix & theIndex & "
BEGIN:VALARM
TRIGGER;VALUE=DURATION:PT0S
ACTION:PROCEDURE
ATTACH:file:" & thePath & thePrefix & theIndex & ".app
END:VALARM
END:VEVENT
END:VCALENDAR
"
  set folderpath to (path to desktop) & "QLabBusyCalEvents:" as text as alias
  set textfile to folderpath & theSummary & ".ics" as text
  do shell script "> " & POSIX path of textfile
  set textfile to textfile as alias
  write theText to textfile
end repeat

The script begins by setting variables defining the first and last cues that events are to be created for, the prefix of the cue numbers, the zero padding of the index, the date the .ics files will include, the interval between the events, and the hour of the first event.

It should be noted that the aim of this script is to create fifty named calendar events that, when they are imported to the calendar, will ask for permission to open the files in their “Open file” alarms. So, the date should be set to a date in the future relative to whenever you run the script.

The initial date and time for all the events is within a single day so that they can be located easily when they are imported to BusyCal (or whatever calendar program you’re using.) Once imported, they can be dragged around, copied, pasted, and renamed to suit your needs.

The script creates a folder on the desktop to contain the .ics files (if none exists) and then creates the contents for each .ics file. It then writes this text to a file and names it appropriately.

Step 5: Set up BusyCal

BusyCal has a truly comprehensive feature set, but we don’t need all of those features for our project. So, in the interest of clarity and to minimize the chances of unexpected results, we’re going to walk through the settings of BusyCal and turn off what we don’t need.

In General settings:

BusyCal settings - General

  • View 24 hours in a day (not crucial; you can set this to taste.)
  • Birthdays calendar off.
  • Anniversaries calendar off.
  • Alternate calendar off (although if you are scheduling your events according to an alternate calendar, you may want to switch it on and proceed with some careful assessment.)

In Menu settings:

BusyCal settings - Menubar

  • Show BusyCal Menu in menu bar switched on.

In Accounts settings:

BusyCal settings - Accounts

  • No accounts in use. We want to use BusyCal for this and only this. We don’t want other calendars (your personal schedule, your child’s badminton league schedule, etc…) interfering with either the visual clarity or the functionality of our system.

In Tasks settings:

BusyCal settings - Tasks

  • Disable reminders
  • All checkboxes in the lower half of the window unchecked.

In Alarms settings:

BusyCal settings - Alarms

  • Show alarms switched on. Without this, nothing will work!
  • The rest of the settings aren’t very important to us.

In Info Panel settings:

BusyCal settings - Info Panel

  • Time to event switched on.
  • Repeat switched on.
  • Everything else can be set to suit your taste.

Nothing specific to this project is set in Appearance, Accessibility, Weather, or Backup settings.

In Advanced settings:

BusyCal settings - Advanced

  • Under Other, set Sync Settings to off.
  • Under Other, set Sync Tag History to off.

In Time Zones settings:

BusyCal settings - Time Zones

  • Uncheck everything. Since all the events are local to your computer, time zones are not relevant.

Any settings not mentioned can be left as default, or set as you prefer.

Next, choose New Calendar → On My Mac from the File menu. Name the calendar “QLab”.

New calendar

Step 6: Import the .ics files to create the fifty events in BusyCal

Select the QLab calendar in the sidebar of BusyCal and then drag all the .ics files from the “QLabBusyCalEvents” on your Desktop to the main BusyCal window.

BusyCal will ask for confirmation before adding each event. Because QLab is the selected calendar, all you need to do is keep pressing the OK button.

Each event will also require permission to open the file set for its “Open file” alarm. This is more complicated and time consuming than it should be because you are required to proactively select the file in a file chooser, even though the file required will be the only one that is not greyed out. Fortunately, pressing ⌘A will automatically select the file, and the Allow button can then be clicked.

When importing a number of .ics files, this can get tedious. Luckily, the required button presses can be automated with, you guessed it, another script which is of course included in the Helper Scripts download, entitled “Button Pusher.scpt”.

display alert "Before running this script drag ics files to BusyCal and wait for first dialog to appear"
tell application "System Events" to tell application process "BusyCal"
  --file permission dialog
  repeat
    set frontmost to true
    delay 0.3
    if (exists window "BusyCal: File Access Required") then
      repeat
        keystroke "a" using command down
        set theAllowState to (enabled of button "Allow" of window "BusyCal: File Access Required")
        if theAllowState is true then
          exit repeat
        else
          delay 0.3
        end if
      end repeat
      click button "Allow" of window "BusyCal: File Access Required"
      
      -- folder permission dialog
      if (exists window "BusyCal: Folder Access Required") then
        repeat
          set theAllowState to (enabled of button "Allow" of window "BusyCal: Folder Access Required")
          if theAllowState is true then
            exit repeat
          else
            delay 0.3
          end if
        end repeat
        click button "Allow" of window "BusyCal: Folder Access Required"
      end if
    end if
    
    --calendar select dialog
    if exists (first window whose title contains "BusyCal Import") then
      
      --tell application "System Events"   to tell application process "BusyCal" 
      click (first button whose title is "OK") of (first window whose title contains "BusyCal Import")
      --end
    end if
  end repeat
end tell

This script uses UI scripting to detect when a dialog is presented and determine which dialog it is.

If it is the Calendar Select dialog, it presses OK.

If it is the File Access Permission dialog it types ⌘A, waits for the Allow button to be enabled, then presses it.

If it is the Folder Access Permission dialog it waits for the Allow button to be enabled and presses it.

Once all the .ics files are imported, remember to stop and close the Button Pusher script!

Events can also be created without .ics files by just creating them directly in BusyCal in the usual way and manually setting an “Open file” alert.

Step 7: Rename events and move to required dates and times in calendar

Events can be renamed to provide more information, although it’s probably a good idea to retain the cue number of the cue that they will trigger as part of the name.

Events can be dragged either singly or in multiples to their new positions in the calendar or have their dates edited individually to move them.

Any events that are not currently required can be moved to a date far in the future, where they can be easily found in the list view.

This screen recording uses the film festival example and schedules pre-screening music for the six films in the repertoire.

BusyCal is a trademark of Busy Apps & Beehive Innovations FZE.

All music in the screen recordings by Kevin MacLeod (incompetech.com), licensed under Creative Commons: By Attribution 3.0

Festival images AI-generated in Photoshop