diff --git a/Source/WatchForMeeting.spoon/docs.json b/Source/WatchForMeeting.spoon/docs.json new file mode 100644 index 00000000..2aea1e5d --- /dev/null +++ b/Source/WatchForMeeting.spoon/docs.json @@ -0,0 +1,1048 @@ +[ + { + "Constant" : [ + + ], + "submodules" : [ + + ], + "Function" : [ + + ], + "Variable" : [ + { + "doc" : "hs.logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", + "stripped_doc" : [ + "hs.logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon." + ], + "def" : "WatchForMeeting.logger", + "name" : "logger", + "notes" : [ + + ], + "signature" : "WatchForMeeting.logger", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "hs.logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", + "parameters" : [ + + ] + }, + { + "doc" : "A Table containing the settings that control sharing.\n\n| Key | Description | Default |\n| --- | ----------- | ------- |\n| enabled | Whether or not sharing is enabled.When false, the spoon will still monitor meeting status to [meetingState](#meetingState), but you will need to write your own automations for what to do with that info. | _true_ |\n| useServer | Do you want to use an external server? (See *Configuration Options* below) | _false_ |\n| | ↓ _required info when `useServer=false`_ | |\n| port | What port to run the self hosted server when WatchForMeeting.sharing.useServer is false. | _8080_ |\n| | ↓ _required info when `useServer=true`_ | |\n| serverURL | The complete url for the external server, including port. IE: `http:\/\/localhost:8080` | _nil_ |\n| key | UUID to identify the room. Value is provided when the room is added on the server side. | _nil_ |\n| maxConnectionAttempts | Maximum number of connection attempts when using an external server. When less than 0, infinite retrys | _-1_ |\n| waitBeforeRetry | Time, in seconds, between connection attempts when using an external server | _5_ |\n\n# Configuration Options\n## Default\nIn order to minimize dependencies, by default this spoon uses a [hs.httpserver](https:\/\/www.hammerspoon.org\/docs\/hs.httpserver.html) to host the status page. This comes with a significant downside of: only the last client to load the page will receive status updates. Any previously connected clients will remain stuck at the last update they received before that client connected.\n\nOnce you are running the spoon, assuming you haven't changed the port (and nothing else is running at that location) you can reach your status page at http:\/\/localhost:8080\n\n## Better - MeetingStatusServer\nFor a better experience I recommend utilizing an external server to receive updates via websockets, and broadcast them to as many clients as you wish to connect.\n\nFor that purpose I've built [http:\/\/github.com\/asp55\/MeetingStatusServer](http:\/\/github.com\/asp55\/MeetingStatusServer) which runs on node.js and can either be run locally as its own thing, or hosted remotely.\n\nIf using the external server, you will to create a key to identify your \"room\" and then provide that information to the spoon. \nIn that case, before `spoon.WatchForMeeting:start()` add the following to your `~\/.hammerspoon\/init.lua`\n\n```\nspoon.WatchForMeeting.sharing.useServer = true\nspoon.WatchForMeeting.sharing.serverURL=\"[YOUR SERVER URL]\"\nspoon.WatchForMeeting.sharing.key=\"[YOUR KEY]\"\n```\n\nor \n\n```\nspoon.WatchForMeeting.sharing = {\n useServer = true,\n serverURL = \"[YOUR SERVER URL]\",\n key=\"[YOUR KEY]\"\n}\n```\n\n## Disable\nIf you don't want to broadcast your status to a webpage, simply disable sharing\n```\n spoon.WatchForMeeting.sharing = {\n enabled = false\n }\n```", + "stripped_doc" : [ + "A Table containing the settings that control sharing.", + "", + "| Key | Description | Default |", + "| --- | ----------- | ------- |", + "| enabled | Whether or not sharing is enabled.When false, the spoon will still monitor meeting status to [meetingState](#meetingState), but you will need to write your own automations for what to do with that info. | _true_ |", + "| useServer | Do you want to use an external server? (See *Configuration Options* below) | _false_ |", + "| | ↓ _required info when `useServer=false`_ | |", + "| port | What port to run the self hosted server when WatchForMeeting.sharing.useServer is false. | _8080_ |", + "| | ↓ _required info when `useServer=true`_ | |", + "| serverURL | The complete url for the external server, including port. IE: `http:\/\/localhost:8080` | _nil_ |", + "| key | UUID to identify the room. Value is provided when the room is added on the server side. | _nil_ |", + "| maxConnectionAttempts | Maximum number of connection attempts when using an external server. When less than 0, infinite retrys | _-1_ |", + "| waitBeforeRetry | Time, in seconds, between connection attempts when using an external server | _5_ |", + "", + "# Configuration Options", + "## Default", + "In order to minimize dependencies, by default this spoon uses a [hs.httpserver](https:\/\/www.hammerspoon.org\/docs\/hs.httpserver.html) to host the status page. This comes with a significant downside of: only the last client to load the page will receive status updates. Any previously connected clients will remain stuck at the last update they received before that client connected.", + "", + "Once you are running the spoon, assuming you haven't changed the port (and nothing else is running at that location) you can reach your status page at http:\/\/localhost:8080", + "", + "## Better - MeetingStatusServer", + "For a better experience I recommend utilizing an external server to receive updates via websockets, and broadcast them to as many clients as you wish to connect.", + "", + "For that purpose I've built [http:\/\/github.com\/asp55\/MeetingStatusServer](http:\/\/github.com\/asp55\/MeetingStatusServer) which runs on node.js and can either be run locally as its own thing, or hosted remotely.", + "", + "If using the external server, you will to create a key to identify your \"room\" and then provide that information to the spoon. ", + "In that case, before `spoon.WatchForMeeting:start()` add the following to your `~\/.hammerspoon\/init.lua`", + "", + "```", + "spoon.WatchForMeeting.sharing.useServer = true", + "spoon.WatchForMeeting.sharing.serverURL=\"[YOUR SERVER URL]\"", + "spoon.WatchForMeeting.sharing.key=\"[YOUR KEY]\"", + "```", + "", + "or ", + "", + "```", + "spoon.WatchForMeeting.sharing = {", + " useServer = true,", + " serverURL = \"[YOUR SERVER URL]\",", + " key=\"[YOUR KEY]\"", + "}", + "```", + "", + "## Disable", + "If you don't want to broadcast your status to a webpage, simply disable sharing", + "```", + " spoon.WatchForMeeting.sharing = {", + " enabled = false", + " }", + "```" + ], + "def" : "WatchForMeeting.sharing", + "name" : "sharing", + "notes" : [ + + ], + "signature" : "WatchForMeeting.sharing", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "A Table containing the settings that control sharing.", + "parameters" : [ + + ] + }, + { + "doc" : "A Table containing the settings that control sharing.\n\n| Key | Description | Default | \n| --- | ----------- | ------- | \n| enabled | Whether or not to show the menu bar. | _true_ | \n| color | Whether or not to use color icons. | _true_ |\n| detailed | Whether or not to use the detailed icon set. | _true_ |\n| showFullState | Whether the menubar icon should represent the full state(IE: Mic On\/Off, Video On\/Off, & Screen Sharing) | _true_ |\n\n\n## Icons\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n WatchForMeeting.menuBar = {...}<\/code> →\n <\/th>\n color=true,<\/code>detailed=true,<\/code><\/th>\n color=true,<\/code>detailed=false,<\/code><\/th>\n color=false,<\/code>detailed=true,<\/code><\/th>\n color=false,<\/code>detailed=false,<\/code><\/th>\n <\/tr>\n
State (See: WatchForMeeting.meetingState<\/a>) ↓\n <\/th>\n showFullState=true<\/code> or showFullState=false<\/code><\/th>\n <\/tr>\n <\/thead>\n
Available<\/td>\n \"Free<\/td>\n\"Free<\/td>\n\"Free<\/td>\n\"Free<\/td>\n <\/tr>\n
Busy<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
<\/td>\n showFullState=true<\/code> only<\/th>\n <\/tr>\n
Busy + Mic On<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Video On<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Screen Sharing<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Mic On + Video On<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Mic On + Screen Sharing<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Video On + Screen Sharing<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Mic On + Video On + Screen Sharing<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n <\/tbody>\n<\/table>", + "stripped_doc" : [ + "A Table containing the settings that control sharing.", + "", + "| Key | Description | Default | ", + "| --- | ----------- | ------- | ", + "| enabled | Whether or not to show the menu bar. | _true_ | ", + "| color | Whether or not to use color icons. | _true_ |", + "| detailed | Whether or not to use the detailed icon set. | _true_ |", + "| showFullState | Whether the menubar icon should represent the full state(IE: Mic On\/Off, Video On\/Off, & Screen Sharing) | _true_ |", + "", + "", + "## Icons", + "", + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "
", + " WatchForMeeting.menuBar = {...}<\/code> →", + " <\/th>", + " color=true,<\/code>detailed=true,<\/code><\/th>", + " color=true,<\/code>detailed=false,<\/code><\/th>", + " color=false,<\/code>detailed=true,<\/code><\/th>", + " color=false,<\/code>detailed=false,<\/code><\/th>", + " <\/tr>", + "
State (See: WatchForMeeting.meetingState<\/a>) ↓", + " <\/th>", + " showFullState=true<\/code> or showFullState=false<\/code><\/th>", + " <\/tr>", + " <\/thead>", + "
Available<\/td>", + " \"Free<\/td>", + "\"Free<\/td>", + "\"Free<\/td>", + "\"Free<\/td>", + " <\/tr>", + "
Busy<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
<\/td>", + " showFullState=true<\/code> only<\/th>", + " <\/tr>", + "
Busy + Mic On<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Video On<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Screen Sharing<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Mic On + Video On<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Mic On + Screen Sharing<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Video On + Screen Sharing<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Mic On + Video On + Screen Sharing<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + " <\/tbody>", + "<\/table>" + ], + "def" : "WatchForMeeting.menubar", + "name" : "menubar", + "notes" : [ + + ], + "signature" : "WatchForMeeting.menubar", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "A Table containing the settings that control sharing.", + "parameters" : [ + + ] + }, + { + "doc" : "Number representing which mode WatchForMeeting should be running\n\n- *0* - Automatic (default)\n-- Monitors configured apps (see [apps](#apps)) and updates status accordingly\n- *1* - Busy\n-- Fakes a meeting. (Marks as in meeting, and signals that the mic is live, camera is on, and screen is sharing.) Useful when meeting type is not supported.", + "stripped_doc" : [ + "Number representing which mode WatchForMeeting should be running", + "", + "- *0* - Automatic (default)", + "-- Monitors configured apps (see [apps](#apps)) and updates status accordingly", + "- *1* - Busy", + "-- Fakes a meeting. (Marks as in meeting, and signals that the mic is live, camera is on, and screen is sharing.) Useful when meeting type is not supported." + ], + "def" : "WatchForMeeting.mode", + "name" : "mode", + "notes" : [ + + ], + "signature" : "WatchForMeeting.mode", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "Number representing which mode WatchForMeeting should be running", + "parameters" : [ + + ] + }, + { + "doc" : "A Table controlling which meeting apps are monitored in automatic mode.\n\n| Key | Description | Default |\n| --- | ----------- | ------- |\n| zoom | Monitor Zoom meetings via menu item polling | _true_ |\n| teams | Monitor Microsoft Teams meetings via local WebSocket API | _false_ |\n\nChanges take effect on the next call to `:start()` or `:restart()`.", + "stripped_doc" : [ + "A Table controlling which meeting apps are monitored in automatic mode.", + "", + "| Key | Description | Default |", + "| --- | ----------- | ------- |", + "| zoom | Monitor Zoom meetings via menu item polling | _true_ |", + "| teams | Monitor Microsoft Teams meetings via local WebSocket API | _false_ |", + "", + "Changes take effect on the next call to `:start()` or `:restart()`." + ], + "def" : "WatchForMeeting.apps", + "name" : "apps", + "notes" : [ + + ], + "signature" : "WatchForMeeting.apps", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "A Table controlling which meeting apps are monitored in automatic mode.", + "parameters" : [ + + ] + }, + { + "doc" : "(Read-only) The hs.application for zoom if it is running, otherwise nil", + "stripped_doc" : [ + "(Read-only) The hs.application for zoom if it is running, otherwise nil" + ], + "def" : "WatchForMeeting.zoom", + "name" : "zoom", + "notes" : [ + + ], + "signature" : "WatchForMeeting.zoom", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "(Read-only) The hs.application for zoom if it is running, otherwise nil", + "parameters" : [ + + ] + }, + { + "doc" : "(Read-only) Either false (when not in a meeting) or a table (when in a meeting)\n\n| Value | Description |\n| ----------------------------------------------------------------------- | ----------- |\n| `false` | Available | \n| `{mic_open = [Boolean], video_on = [Boolean], sharing = [Boolean] }` | Busy |", + "stripped_doc" : [ + "(Read-only) Either false (when not in a meeting) or a table (when in a meeting)", + "", + "| Value | Description |", + "| ----------------------------------------------------------------------- | ----------- |", + "| `false` | Available | ", + "| `{mic_open = [Boolean], video_on = [Boolean], sharing = [Boolean] }` | Busy |" + ], + "def" : "WatchForMeeting.meetingState", + "name" : "meetingState", + "notes" : [ + + ], + "signature" : "WatchForMeeting.meetingState", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "(Read-only) Either false (when not in a meeting) or a table (when in a meeting)", + "parameters" : [ + + ] + }, + { + "doc" : "(Read-only) Boolean representing if the meeting is real or faked", + "stripped_doc" : [ + "(Read-only) Boolean representing if the meeting is real or faked" + ], + "def" : "WatchForMeeting.faking", + "name" : "faking", + "notes" : [ + + ], + "signature" : "WatchForMeeting.faking", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "(Read-only) Boolean representing if the meeting is real or faked", + "parameters" : [ + + ] + } + ], + "stripped_doc" : [ + + ], + "Deprecated" : [ + + ], + "type" : "Module", + "desc" : "A Spoon to answer the question", + "Constructor" : [ + + ], + "items" : [ + { + "doc" : "A Table controlling which meeting apps are monitored in automatic mode.\n\n| Key | Description | Default |\n| --- | ----------- | ------- |\n| zoom | Monitor Zoom meetings via menu item polling | _true_ |\n| teams | Monitor Microsoft Teams meetings via local WebSocket API | _false_ |\n\nChanges take effect on the next call to `:start()` or `:restart()`.", + "stripped_doc" : [ + "A Table controlling which meeting apps are monitored in automatic mode.", + "", + "| Key | Description | Default |", + "| --- | ----------- | ------- |", + "| zoom | Monitor Zoom meetings via menu item polling | _true_ |", + "| teams | Monitor Microsoft Teams meetings via local WebSocket API | _false_ |", + "", + "Changes take effect on the next call to `:start()` or `:restart()`." + ], + "def" : "WatchForMeeting.apps", + "name" : "apps", + "notes" : [ + + ], + "signature" : "WatchForMeeting.apps", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "A Table controlling which meeting apps are monitored in automatic mode.", + "parameters" : [ + + ] + }, + { + "doc" : "(Read-only) Boolean representing if the meeting is real or faked", + "stripped_doc" : [ + "(Read-only) Boolean representing if the meeting is real or faked" + ], + "def" : "WatchForMeeting.faking", + "name" : "faking", + "notes" : [ + + ], + "signature" : "WatchForMeeting.faking", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "(Read-only) Boolean representing if the meeting is real or faked", + "parameters" : [ + + ] + }, + { + "doc" : "hs.logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", + "stripped_doc" : [ + "hs.logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon." + ], + "def" : "WatchForMeeting.logger", + "name" : "logger", + "notes" : [ + + ], + "signature" : "WatchForMeeting.logger", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "hs.logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon.", + "parameters" : [ + + ] + }, + { + "doc" : "(Read-only) Either false (when not in a meeting) or a table (when in a meeting)\n\n| Value | Description |\n| ----------------------------------------------------------------------- | ----------- |\n| `false` | Available | \n| `{mic_open = [Boolean], video_on = [Boolean], sharing = [Boolean] }` | Busy |", + "stripped_doc" : [ + "(Read-only) Either false (when not in a meeting) or a table (when in a meeting)", + "", + "| Value | Description |", + "| ----------------------------------------------------------------------- | ----------- |", + "| `false` | Available | ", + "| `{mic_open = [Boolean], video_on = [Boolean], sharing = [Boolean] }` | Busy |" + ], + "def" : "WatchForMeeting.meetingState", + "name" : "meetingState", + "notes" : [ + + ], + "signature" : "WatchForMeeting.meetingState", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "(Read-only) Either false (when not in a meeting) or a table (when in a meeting)", + "parameters" : [ + + ] + }, + { + "doc" : "A Table containing the settings that control sharing.\n\n| Key | Description | Default | \n| --- | ----------- | ------- | \n| enabled | Whether or not to show the menu bar. | _true_ | \n| color | Whether or not to use color icons. | _true_ |\n| detailed | Whether or not to use the detailed icon set. | _true_ |\n| showFullState | Whether the menubar icon should represent the full state(IE: Mic On\/Off, Video On\/Off, & Screen Sharing) | _true_ |\n\n\n## Icons\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n WatchForMeeting.menuBar = {...}<\/code> →\n <\/th>\n color=true,<\/code>detailed=true,<\/code><\/th>\n color=true,<\/code>detailed=false,<\/code><\/th>\n color=false,<\/code>detailed=true,<\/code><\/th>\n color=false,<\/code>detailed=false,<\/code><\/th>\n <\/tr>\n
State (See: WatchForMeeting.meetingState<\/a>) ↓\n <\/th>\n showFullState=true<\/code> or showFullState=false<\/code><\/th>\n <\/tr>\n <\/thead>\n
Available<\/td>\n \"Free<\/td>\n\"Free<\/td>\n\"Free<\/td>\n\"Free<\/td>\n <\/tr>\n
Busy<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
<\/td>\n showFullState=true<\/code> only<\/th>\n <\/tr>\n
Busy + Mic On<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Video On<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Screen Sharing<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Mic On + Video On<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Mic On + Screen Sharing<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Video On + Screen Sharing<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n
Busy + Mic On + Video On + Screen Sharing<\/td>\n \"In<\/td>\n\"In<\/td>\n\"In<\/td>\n\"In<\/td>\n <\/tr>\n <\/tbody>\n<\/table>", + "stripped_doc" : [ + "A Table containing the settings that control sharing.", + "", + "| Key | Description | Default | ", + "| --- | ----------- | ------- | ", + "| enabled | Whether or not to show the menu bar. | _true_ | ", + "| color | Whether or not to use color icons. | _true_ |", + "| detailed | Whether or not to use the detailed icon set. | _true_ |", + "| showFullState | Whether the menubar icon should represent the full state(IE: Mic On\/Off, Video On\/Off, & Screen Sharing) | _true_ |", + "", + "", + "## Icons", + "", + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "
", + " WatchForMeeting.menuBar = {...}<\/code> →", + " <\/th>", + " color=true,<\/code>detailed=true,<\/code><\/th>", + " color=true,<\/code>detailed=false,<\/code><\/th>", + " color=false,<\/code>detailed=true,<\/code><\/th>", + " color=false,<\/code>detailed=false,<\/code><\/th>", + " <\/tr>", + "
State (See: WatchForMeeting.meetingState<\/a>) ↓", + " <\/th>", + " showFullState=true<\/code> or showFullState=false<\/code><\/th>", + " <\/tr>", + " <\/thead>", + "
Available<\/td>", + " \"Free<\/td>", + "\"Free<\/td>", + "\"Free<\/td>", + "\"Free<\/td>", + " <\/tr>", + "
Busy<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
<\/td>", + " showFullState=true<\/code> only<\/th>", + " <\/tr>", + "
Busy + Mic On<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Video On<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Screen Sharing<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Mic On + Video On<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Mic On + Screen Sharing<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Video On + Screen Sharing<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + "
Busy + Mic On + Video On + Screen Sharing<\/td>", + " \"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + "\"In<\/td>", + " <\/tr>", + " <\/tbody>", + "<\/table>" + ], + "def" : "WatchForMeeting.menubar", + "name" : "menubar", + "notes" : [ + + ], + "signature" : "WatchForMeeting.menubar", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "A Table containing the settings that control sharing.", + "parameters" : [ + + ] + }, + { + "doc" : "Number representing which mode WatchForMeeting should be running\n\n- *0* - Automatic (default)\n-- Monitors configured apps (see [apps](#apps)) and updates status accordingly\n- *1* - Busy\n-- Fakes a meeting. (Marks as in meeting, and signals that the mic is live, camera is on, and screen is sharing.) Useful when meeting type is not supported.", + "stripped_doc" : [ + "Number representing which mode WatchForMeeting should be running", + "", + "- *0* - Automatic (default)", + "-- Monitors configured apps (see [apps](#apps)) and updates status accordingly", + "- *1* - Busy", + "-- Fakes a meeting. (Marks as in meeting, and signals that the mic is live, camera is on, and screen is sharing.) Useful when meeting type is not supported." + ], + "def" : "WatchForMeeting.mode", + "name" : "mode", + "notes" : [ + + ], + "signature" : "WatchForMeeting.mode", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "Number representing which mode WatchForMeeting should be running", + "parameters" : [ + + ] + }, + { + "doc" : "A Table containing the settings that control sharing.\n\n| Key | Description | Default |\n| --- | ----------- | ------- |\n| enabled | Whether or not sharing is enabled.When false, the spoon will still monitor meeting status to [meetingState](#meetingState), but you will need to write your own automations for what to do with that info. | _true_ |\n| useServer | Do you want to use an external server? (See *Configuration Options* below) | _false_ |\n| | ↓ _required info when `useServer=false`_ | |\n| port | What port to run the self hosted server when WatchForMeeting.sharing.useServer is false. | _8080_ |\n| | ↓ _required info when `useServer=true`_ | |\n| serverURL | The complete url for the external server, including port. IE: `http:\/\/localhost:8080` | _nil_ |\n| key | UUID to identify the room. Value is provided when the room is added on the server side. | _nil_ |\n| maxConnectionAttempts | Maximum number of connection attempts when using an external server. When less than 0, infinite retrys | _-1_ |\n| waitBeforeRetry | Time, in seconds, between connection attempts when using an external server | _5_ |\n\n# Configuration Options\n## Default\nIn order to minimize dependencies, by default this spoon uses a [hs.httpserver](https:\/\/www.hammerspoon.org\/docs\/hs.httpserver.html) to host the status page. This comes with a significant downside of: only the last client to load the page will receive status updates. Any previously connected clients will remain stuck at the last update they received before that client connected.\n\nOnce you are running the spoon, assuming you haven't changed the port (and nothing else is running at that location) you can reach your status page at http:\/\/localhost:8080\n\n## Better - MeetingStatusServer\nFor a better experience I recommend utilizing an external server to receive updates via websockets, and broadcast them to as many clients as you wish to connect.\n\nFor that purpose I've built [http:\/\/github.com\/asp55\/MeetingStatusServer](http:\/\/github.com\/asp55\/MeetingStatusServer) which runs on node.js and can either be run locally as its own thing, or hosted remotely.\n\nIf using the external server, you will to create a key to identify your \"room\" and then provide that information to the spoon. \nIn that case, before `spoon.WatchForMeeting:start()` add the following to your `~\/.hammerspoon\/init.lua`\n\n```\nspoon.WatchForMeeting.sharing.useServer = true\nspoon.WatchForMeeting.sharing.serverURL=\"[YOUR SERVER URL]\"\nspoon.WatchForMeeting.sharing.key=\"[YOUR KEY]\"\n```\n\nor \n\n```\nspoon.WatchForMeeting.sharing = {\n useServer = true,\n serverURL = \"[YOUR SERVER URL]\",\n key=\"[YOUR KEY]\"\n}\n```\n\n## Disable\nIf you don't want to broadcast your status to a webpage, simply disable sharing\n```\n spoon.WatchForMeeting.sharing = {\n enabled = false\n }\n```", + "stripped_doc" : [ + "A Table containing the settings that control sharing.", + "", + "| Key | Description | Default |", + "| --- | ----------- | ------- |", + "| enabled | Whether or not sharing is enabled.When false, the spoon will still monitor meeting status to [meetingState](#meetingState), but you will need to write your own automations for what to do with that info. | _true_ |", + "| useServer | Do you want to use an external server? (See *Configuration Options* below) | _false_ |", + "| | ↓ _required info when `useServer=false`_ | |", + "| port | What port to run the self hosted server when WatchForMeeting.sharing.useServer is false. | _8080_ |", + "| | ↓ _required info when `useServer=true`_ | |", + "| serverURL | The complete url for the external server, including port. IE: `http:\/\/localhost:8080` | _nil_ |", + "| key | UUID to identify the room. Value is provided when the room is added on the server side. | _nil_ |", + "| maxConnectionAttempts | Maximum number of connection attempts when using an external server. When less than 0, infinite retrys | _-1_ |", + "| waitBeforeRetry | Time, in seconds, between connection attempts when using an external server | _5_ |", + "", + "# Configuration Options", + "## Default", + "In order to minimize dependencies, by default this spoon uses a [hs.httpserver](https:\/\/www.hammerspoon.org\/docs\/hs.httpserver.html) to host the status page. This comes with a significant downside of: only the last client to load the page will receive status updates. Any previously connected clients will remain stuck at the last update they received before that client connected.", + "", + "Once you are running the spoon, assuming you haven't changed the port (and nothing else is running at that location) you can reach your status page at http:\/\/localhost:8080", + "", + "## Better - MeetingStatusServer", + "For a better experience I recommend utilizing an external server to receive updates via websockets, and broadcast them to as many clients as you wish to connect.", + "", + "For that purpose I've built [http:\/\/github.com\/asp55\/MeetingStatusServer](http:\/\/github.com\/asp55\/MeetingStatusServer) which runs on node.js and can either be run locally as its own thing, or hosted remotely.", + "", + "If using the external server, you will to create a key to identify your \"room\" and then provide that information to the spoon. ", + "In that case, before `spoon.WatchForMeeting:start()` add the following to your `~\/.hammerspoon\/init.lua`", + "", + "```", + "spoon.WatchForMeeting.sharing.useServer = true", + "spoon.WatchForMeeting.sharing.serverURL=\"[YOUR SERVER URL]\"", + "spoon.WatchForMeeting.sharing.key=\"[YOUR KEY]\"", + "```", + "", + "or ", + "", + "```", + "spoon.WatchForMeeting.sharing = {", + " useServer = true,", + " serverURL = \"[YOUR SERVER URL]\",", + " key=\"[YOUR KEY]\"", + "}", + "```", + "", + "## Disable", + "If you don't want to broadcast your status to a webpage, simply disable sharing", + "```", + " spoon.WatchForMeeting.sharing = {", + " enabled = false", + " }", + "```" + ], + "def" : "WatchForMeeting.sharing", + "name" : "sharing", + "notes" : [ + + ], + "signature" : "WatchForMeeting.sharing", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "A Table containing the settings that control sharing.", + "parameters" : [ + + ] + }, + { + "doc" : "(Read-only) The hs.application for zoom if it is running, otherwise nil", + "stripped_doc" : [ + "(Read-only) The hs.application for zoom if it is running, otherwise nil" + ], + "def" : "WatchForMeeting.zoom", + "name" : "zoom", + "notes" : [ + + ], + "signature" : "WatchForMeeting.zoom", + "type" : "Variable", + "returns" : [ + + ], + "desc" : "(Read-only) The hs.application for zoom if it is running, otherwise nil", + "parameters" : [ + + ] + }, + { + "doc" : "Monitors Zoom and updates status accordingly\n\nParameters:\n * None\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Monitors Zoom and updates status accordingly", + "" + ], + "def" : "WatchForMeeting:auto()", + "name" : "auto", + "notes" : [ + + ], + "signature" : "WatchForMeeting:auto()", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Monitors Zoom and updates status accordingly", + "parameters" : [ + " * None", + "" + ] + }, + { + "doc" : "Disables monitoring and reports as being in a meeting. Useful when meeting type is not supported.\n\nParameters:\n * mic_open - A boolean indicating if the mic is open\n * video_on - A boolean indicating if the video camera is on\n * sharing - A boolean indicating if screen sharing is on\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Disables monitoring and reports as being in a meeting. Useful when meeting type is not supported.", + "" + ], + "def" : "WatchForMeeting:fake(mic_open, video_on, sharing)", + "name" : "fake", + "notes" : [ + + ], + "signature" : "WatchForMeeting:fake(mic_open, video_on, sharing)", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Disables monitoring and reports as being in a meeting. Useful when meeting type is not supported.", + "parameters" : [ + " * mic_open - A boolean indicating if the mic is open", + " * video_on - A boolean indicating if the video camera is on", + " * sharing - A boolean indicating if screen sharing is on", + "" + ] + }, + { + "doc" : "Starts a WatchForMeeting object\n\nParameters:\n * None\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Starts a WatchForMeeting object", + "" + ], + "def" : "WatchForMeeting:start() -> WatchForMeeting", + "name" : "start", + "notes" : [ + + ], + "signature" : "WatchForMeeting:start() -> WatchForMeeting", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Starts a WatchForMeeting object", + "parameters" : [ + " * None", + "" + ] + }, + { + "doc" : "Restarts a WatchForMeeting object\n\nParameters:\n * None\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Restarts a WatchForMeeting object", + "" + ], + "def" : "WatchForMeeting:start()", + "name" : "start", + "notes" : [ + + ], + "signature" : "WatchForMeeting:start()", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Restarts a WatchForMeeting object", + "parameters" : [ + " * None", + "" + ] + }, + { + "doc" : "Stops a WatchForMeeting object\n\nParameters:\n * None\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Stops a WatchForMeeting object", + "" + ], + "def" : "WatchForMeeting:stop()", + "name" : "stop", + "notes" : [ + + ], + "signature" : "WatchForMeeting:stop()", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Stops a WatchForMeeting object", + "parameters" : [ + " * None", + "" + ] + }, + { + "doc" : "Subscribe to one event with one or more functions\n\nParameters:\n * event - string of the event to subscribe to (see the `spoon.WatchForMeeting` constants)\n * fn - function or list of functions, the callback(s) to add for the event(s); \n\nReturns:\n * The `spoon.WatchForMeeting` object for method chaining", + "stripped_doc" : [ + "Subscribe to one event with one or more functions", + "" + ], + "def" : "WatchForMeeting:subscribe(event, fn)", + "name" : "subscribe", + "notes" : [ + + ], + "signature" : "WatchForMeeting:subscribe(event, fn)", + "type" : "Method", + "returns" : [ + " * The `spoon.WatchForMeeting` object for method chaining" + ], + "desc" : "Subscribe to one event with one or more functions", + "parameters" : [ + " * event - string of the event to subscribe to (see the `spoon.WatchForMeeting` constants)", + " * fn - function or list of functions, the callback(s) to add for the event(s); ", + "" + ] + }, + { + "doc" : "Removes one or more event subscriptions\n\nParameters:\n * event - string of the event to unsubscribe;\n * fn - function or list of functions, the callback(s) to remove; if omitted, all callbacks will be unsubscribed from `event`(s)\n\nReturns:\n * The `spoon.WatchForMeeting` object for method chaining", + "stripped_doc" : [ + "Removes one or more event subscriptions", + "" + ], + "def" : "WatchForMeeting:unsubscribe(event, fn) -> hs.window.filter object", + "name" : "unsubscribe", + "notes" : [ + + ], + "signature" : "WatchForMeeting:unsubscribe(event, fn) -> hs.window.filter object", + "type" : "Method", + "returns" : [ + " * The `spoon.WatchForMeeting` object for method chaining" + ], + "desc" : "Removes one or more event subscriptions", + "parameters" : [ + " * event - string of the event to unsubscribe;", + " * fn - function or list of functions, the callback(s) to remove; if omitted, all callbacks will be unsubscribed from `event`(s)", + "" + ] + }, + { + "doc" : "Removes all subscriptions from one event\n\nParameters:\n * event - string of the event to unsubscribe; ;\n\nReturns:\n * The `spoon.WatchForMeeting` object for method chaining", + "stripped_doc" : [ + "Removes all subscriptions from one event", + "" + ], + "def" : "WatchForMeeting:unsubscribeEvent(event) -> hs.window.filter object", + "name" : "unsubscribeEvent", + "notes" : [ + + ], + "signature" : "WatchForMeeting:unsubscribeEvent(event) -> hs.window.filter object", + "type" : "Method", + "returns" : [ + " * The `spoon.WatchForMeeting` object for method chaining" + ], + "desc" : "Removes all subscriptions from one event", + "parameters" : [ + " * event - string of the event to unsubscribe; ;", + "" + ] + } + ], + "Method" : [ + { + "doc" : "Starts a WatchForMeeting object\n\nParameters:\n * None\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Starts a WatchForMeeting object", + "" + ], + "def" : "WatchForMeeting:start() -> WatchForMeeting", + "name" : "start", + "notes" : [ + + ], + "signature" : "WatchForMeeting:start() -> WatchForMeeting", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Starts a WatchForMeeting object", + "parameters" : [ + " * None", + "" + ] + }, + { + "doc" : "Stops a WatchForMeeting object\n\nParameters:\n * None\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Stops a WatchForMeeting object", + "" + ], + "def" : "WatchForMeeting:stop()", + "name" : "stop", + "notes" : [ + + ], + "signature" : "WatchForMeeting:stop()", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Stops a WatchForMeeting object", + "parameters" : [ + " * None", + "" + ] + }, + { + "doc" : "Restarts a WatchForMeeting object\n\nParameters:\n * None\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Restarts a WatchForMeeting object", + "" + ], + "def" : "WatchForMeeting:start()", + "name" : "start", + "notes" : [ + + ], + "signature" : "WatchForMeeting:start()", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Restarts a WatchForMeeting object", + "parameters" : [ + " * None", + "" + ] + }, + { + "doc" : "Monitors Zoom and updates status accordingly\n\nParameters:\n * None\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Monitors Zoom and updates status accordingly", + "" + ], + "def" : "WatchForMeeting:auto()", + "name" : "auto", + "notes" : [ + + ], + "signature" : "WatchForMeeting:auto()", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Monitors Zoom and updates status accordingly", + "parameters" : [ + " * None", + "" + ] + }, + { + "doc" : "Disables monitoring and reports as being in a meeting. Useful when meeting type is not supported.\n\nParameters:\n * mic_open - A boolean indicating if the mic is open\n * video_on - A boolean indicating if the video camera is on\n * sharing - A boolean indicating if screen sharing is on\n\nReturns:\n * The spoon.WatchForMeeting object", + "stripped_doc" : [ + "Disables monitoring and reports as being in a meeting. Useful when meeting type is not supported.", + "" + ], + "def" : "WatchForMeeting:fake(mic_open, video_on, sharing)", + "name" : "fake", + "notes" : [ + + ], + "signature" : "WatchForMeeting:fake(mic_open, video_on, sharing)", + "type" : "Method", + "returns" : [ + " * The spoon.WatchForMeeting object" + ], + "desc" : "Disables monitoring and reports as being in a meeting. Useful when meeting type is not supported.", + "parameters" : [ + " * mic_open - A boolean indicating if the mic is open", + " * video_on - A boolean indicating if the video camera is on", + " * sharing - A boolean indicating if screen sharing is on", + "" + ] + }, + { + "doc" : "Subscribe to one event with one or more functions\n\nParameters:\n * event - string of the event to subscribe to (see the `spoon.WatchForMeeting` constants)\n * fn - function or list of functions, the callback(s) to add for the event(s); \n\nReturns:\n * The `spoon.WatchForMeeting` object for method chaining", + "stripped_doc" : [ + "Subscribe to one event with one or more functions", + "" + ], + "def" : "WatchForMeeting:subscribe(event, fn)", + "name" : "subscribe", + "notes" : [ + + ], + "signature" : "WatchForMeeting:subscribe(event, fn)", + "type" : "Method", + "returns" : [ + " * The `spoon.WatchForMeeting` object for method chaining" + ], + "desc" : "Subscribe to one event with one or more functions", + "parameters" : [ + " * event - string of the event to subscribe to (see the `spoon.WatchForMeeting` constants)", + " * fn - function or list of functions, the callback(s) to add for the event(s); ", + "" + ] + }, + { + "doc" : "Removes one or more event subscriptions\n\nParameters:\n * event - string of the event to unsubscribe;\n * fn - function or list of functions, the callback(s) to remove; if omitted, all callbacks will be unsubscribed from `event`(s)\n\nReturns:\n * The `spoon.WatchForMeeting` object for method chaining", + "stripped_doc" : [ + "Removes one or more event subscriptions", + "" + ], + "def" : "WatchForMeeting:unsubscribe(event, fn) -> hs.window.filter object", + "name" : "unsubscribe", + "notes" : [ + + ], + "signature" : "WatchForMeeting:unsubscribe(event, fn) -> hs.window.filter object", + "type" : "Method", + "returns" : [ + " * The `spoon.WatchForMeeting` object for method chaining" + ], + "desc" : "Removes one or more event subscriptions", + "parameters" : [ + " * event - string of the event to unsubscribe;", + " * fn - function or list of functions, the callback(s) to remove; if omitted, all callbacks will be unsubscribed from `event`(s)", + "" + ] + }, + { + "doc" : "Removes all subscriptions from one event\n\nParameters:\n * event - string of the event to unsubscribe; ;\n\nReturns:\n * The `spoon.WatchForMeeting` object for method chaining", + "stripped_doc" : [ + "Removes all subscriptions from one event", + "" + ], + "def" : "WatchForMeeting:unsubscribeEvent(event) -> hs.window.filter object", + "name" : "unsubscribeEvent", + "notes" : [ + + ], + "signature" : "WatchForMeeting:unsubscribeEvent(event) -> hs.window.filter object", + "type" : "Method", + "returns" : [ + " * The `spoon.WatchForMeeting` object for method chaining" + ], + "desc" : "Removes all subscriptions from one event", + "parameters" : [ + " * event - string of the event to unsubscribe; ;", + "" + ] + } + ], + "Command" : [ + + ], + "doc" : "A Spoon to answer the question\n> Are you in a meeting?\n\nWatches to see if:\n1) A supported application is running\n2) Are you on a call\n3) Are you on mute, is your camera on, and\/or are you screen sharing\n\nAnd then lets you share that information.\n\n# Basic Usage\n\nTo get going right out of the box, in your `~\/.hammerspoon\/init.lua` add these lines:\n```\nhs.loadSpoon(\"WatchForMeeting\")\nspoon.WatchForMeeting:start()\n```\n\nThis will start the spoon monitoring for zoom calls, and come with the default status page, and menubar configurations.", + "Field" : [ + + ], + "name" : "WatchForMeeting" + } +] diff --git a/Source/WatchForMeeting.spoon/init.lua b/Source/WatchForMeeting.spoon/init.lua new file mode 100644 index 00000000..1856fcb3 --- /dev/null +++ b/Source/WatchForMeeting.spoon/init.lua @@ -0,0 +1,1042 @@ +--- === WatchForMeeting === +--- +--- A Spoon to answer the question +--- > Are you in a meeting? +--- +--- Watches to see if: +--- 1) A supported application is running +--- 2) Are you on a call +--- 3) Are you on mute, is your camera on, and/or are you screen sharing +--- +--- And then lets you share that information. +--- +--- # Basic Usage +--- +--- To get going right out of the box, in your `~/.hammerspoon/init.lua` add these lines: +--- ``` +--- hs.loadSpoon("WatchForMeeting") +--- spoon.WatchForMeeting:start() +--- ``` +--- +--- This will start the spoon monitoring for zoom calls, and come with the default status page, and menubar configurations. +--- + + +--We'll store some stuff in an internal table + +local _internal = {} + + +-- create a namespace + +local WatchForMeeting={} +WatchForMeeting.__index = WatchForMeeting + + +-- Metadata +WatchForMeeting.name = "WatchForMeeting" +WatchForMeeting.version = "3.0.0" +WatchForMeeting.author = "Andrew Parnell " +WatchForMeeting.homepage = "https://github.com/Hammerspoon/Spoons" +WatchForMeeting.license = "MIT - https://opensource.org/licenses/MIT" + +-- Event callbacks +local events = { + meetingChange=true, + meetingStarted=true, + meetingStopped=true, + micChange=true, + micOn=true, + micOff=true, + videoChange=true, + videoOn=true, + videoOff=true, + screensharingChange=true, + screensharingOn=true, + screensharingOff=true, + +} +_internal.events = {} +for k in pairs(events) do WatchForMeeting[k]=k end + + +------------------------------------------- +-- Declare Variables +------------------------------------------- + + +--- WatchForMeeting.logger +--- Variable +--- hs.logger object used within the Spoon. Can be accessed to set the default log level for the messages coming from the Spoon. +WatchForMeeting.logger = hs.logger.new('WatchForMeeting') + + +-- private variable to track if spoon is already running or not. (Makes it easier to find local variables) +_internal.running = false + + ------------------------------------------- + -- Special Variables (stored in _internal and accessed through metamethods defined below) + ------------------------------------------- + + --- WatchForMeeting.sharing + --- Variable + --- A Table containing the settings that control sharing. + --- + --- | Key | Description | Default | + --- | --- | ----------- | ------- | + --- | enabled | Whether or not sharing is enabled.

When false, the spoon will still monitor meeting status to [meetingState](#meetingState), but you will need to write your own automations for what to do with that info. | _true_ | + --- | useServer | Do you want to use an external server? (See *Configuration Options* below) | _false_ | + --- | | ↓ _required info when `useServer=false`_ | | + --- | port | What port to run the self hosted server when WatchForMeeting.sharing.useServer is false. | _8080_ | + --- | | ↓ _required info when `useServer=true`_ | | + --- | serverURL | The complete url for the external server, including port. IE: `http://localhost:8080` | _nil_ | + --- | key | UUID to identify the room. Value is provided when the room is added on the server side. | _nil_ | + --- | maxConnectionAttempts | Maximum number of connection attempts when using an external server. When less than 0, infinite retrys | _-1_ | + --- | waitBeforeRetry | Time, in seconds, between connection attempts when using an external server | _5_ | + --- + --- # Configuration Options + --- ## Default + --- In order to minimize dependencies, by default this spoon uses a [hs.httpserver](https://www.hammerspoon.org/docs/hs.httpserver.html) to host the status page. This comes with a significant downside of: only the last client to load the page will receive status updates. Any previously connected clients will remain stuck at the last update they received before that client connected. + --- + --- Once you are running the spoon, assuming you haven't changed the port (and nothing else is running at that location) you can reach your status page at http://localhost:8080 + --- + --- ## Better - MeetingStatusServer + --- For a better experience I recommend utilizing an external server to receive updates via websockets, and broadcast them to as many clients as you wish to connect. + --- + --- For that purpose I've built [http://github.com/asp55/MeetingStatusServer](http://github.com/asp55/MeetingStatusServer) which runs on node.js and can either be run locally as its own thing, or hosted remotely. + --- + --- If using the external server, you will to create a key to identify your "room" and then provide that information to the spoon. + --- In that case, before `spoon.WatchForMeeting:start()` add the following to your `~/.hammerspoon/init.lua` + --- + --- ``` + --- spoon.WatchForMeeting.sharing.useServer = true + --- spoon.WatchForMeeting.sharing.serverURL="[YOUR SERVER URL]" + --- spoon.WatchForMeeting.sharing.key="[YOUR KEY]" + --- ``` + --- + --- or + --- + --- ``` + --- spoon.WatchForMeeting.sharing = { + --- useServer = true, + --- serverURL = "[YOUR SERVER URL]", + --- key="[YOUR KEY]" + --- } + --- ``` + --- + --- ## Disable + --- If you don't want to broadcast your status to a webpage, simply disable sharing + --- ``` + --- spoon.WatchForMeeting.sharing = { + --- enabled = false + --- } + --- ``` + --- + + _internal.sharingDefaults = { + enabled = true, + useServer = false, + port = 8080, + serverURL = nil, + key = nil, + maxConnectionAttempts = -1, --when less than 0, infinite retrys + waitBeforeRetry = 5, + } + _internal.sharing = setmetatable({}, {__index=_internal.sharingDefaults}) + + + --- WatchForMeeting.menubar + --- Variable + --- A Table containing the settings that control sharing. + --- + --- | Key | Description | Default | + --- | --- | ----------- | ------- | + --- | enabled | Whether or not to show the menu bar. | _true_ | + --- | color | Whether or not to use color icons. | _true_ | + --- | detailed | Whether or not to use the detailed icon set. | _true_ | + --- | showFullState | Whether the menubar icon should represent the full state
(IE: Mic On/Off, Video On/Off, & Screen Sharing) | _true_ | + --- + --- + --- ## Icons + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + --- + ---
+ --- WatchForMeeting.menuBar = {...} → + --- color=true,
detailed=true,
color=true,
detailed=false,
color=false,
detailed=true,
color=false,
detailed=false,
State (See: WatchForMeeting.meetingState) ↓ + --- showFullState=true or showFullState=false
AvailableFree slash AvailableFree slash AvailableFree slash AvailableFree slash Available
BusyIn meeting, no additional statusIn meeting, no additional statusIn meeting, no additional statusIn meeting, no additional status
showFullState=true only
Busy + Mic OnIn meeting, mic:on, video:off, screensharing:offIn meeting, mic:on, video:off, screensharing:offIn meeting, mic:on, video:off, screensharing:offIn meeting, mic:on, video:off, screensharing:off
Busy + Video OnIn meeting, mic:off, video:on, screensharing:offIn meeting, mic:off, video:on, screensharing:offIn meeting, mic:off, video:on, screensharing:offIn meeting, mic:off, video:on, screensharing:off
Busy + Screen SharingIn meeting, mic:off, video:off, screensharing:onIn meeting, mic:off, video:off, screensharing:onIn meeting, mic:off, video:off, screensharing:onIn meeting, mic:off, video:off, screensharing:on
Busy + Mic On + Video OnIn meeting, mic:on, video:on, screensharing:offIn meeting, mic:on, video:on, screensharing:offIn meeting, mic:on, video:on, screensharing:offIn meeting, mic:on, video:on, screensharing:off
Busy + Mic On + Screen SharingIn meeting, mic:on, video:off, screensharing:onIn meeting, mic:on, video:off, screensharing:onIn meeting, mic:on, video:off, screensharing:onIn meeting, mic:on, video:off, screensharing:on
Busy + Video On + Screen SharingIn meeting, mic:off, video:on, screensharing:onIn meeting, mic:off, video:on, screensharing:onIn meeting, mic:off, video:on, screensharing:onIn meeting, mic:off, video:on, screensharing:on
Busy + Mic On + Video On + Screen SharingIn meeting, mic:on, video:on, screensharing:onIn meeting, mic:on, video:on, screensharing:onIn meeting, mic:on, video:on, screensharing:onIn meeting, mic:on, video:on, screensharing:on
+ + + _internal.menubarDefaults = { + enabled = true, + color = true, + detailed = true, + showFullState = true + } + _internal.menubar__newIndex = function (table, key, value) + if(key=="enabled") then + if(value) then + _internal.meetingMenuBar:returnToMenuBar() + _internal.updateMenuIcon(_internal.meetingState, _internal.faking) + else + _internal.meetingMenuBar:removeFromMenuBar() + end + else + _internal.updateMenuIcon(_internal.meetingState, _internal.faking) + end + end + + _internal.menubar = setmetatable({}, {__index=_internal.menubarDefaults, __newindex=_internal.menubar__newIndex}) + + --- WatchForMeeting.mode + --- Variable + --- Number representing which mode WatchForMeeting should be running + --- + --- - *0* - Automatic (default) + --- -- Monitors configured apps (see [apps](#apps)) and updates status accordingly + --- - *1* - Busy + --- -- Fakes a meeting. (Marks as in meeting, and signals that the mic is live, camera is on, and screen is sharing.) Useful when meeting type is not supported. + _internal.mode = 0 + + --- WatchForMeeting.apps + --- Variable + --- A Table controlling which meeting apps are monitored in automatic mode. + --- + --- | Key | Description | Default | + --- | --- | ----------- | ------- | + --- | zoom | Monitor Zoom meetings via menu item polling | _true_ | + --- | teams | Monitor Microsoft Teams meetings via local WebSocket API | _false_ | + --- + --- Changes take effect on the next call to `:start()` or `:restart()`. + + _internal.appsDefaults = { zoom = true, teams = false } + _internal.apps = setmetatable({}, {__index=_internal.appsDefaults}) + + --- WatchForMeeting.zoom + --- Variable + --- (Read-only) The hs.application for zoom if it is running, otherwise nil + _internal.zoom = nil + + --- WatchForMeeting.meetingState + --- Variable + --- (Read-only) Either false (when not in a meeting) or a table (when in a meeting) + --- + --- | Value | Description | + --- | ----------------------------------------------------------------------- | ----------- | + --- | `false` | Available | + --- | `{mic_open = [Boolean], video_on = [Boolean], sharing = [Boolean] }` | Busy | + _internal.meetingState = false + _internal.lastMeetingState = nil; + + + --- WatchForMeeting.faking + --- Variable + --- (Read-only) Boolean representing if the meeting is real or faked + + + -- MetaMethods + WatchForMeeting = setmetatable(WatchForMeeting, { + --GET + __index = function (table, key) + if(key=="zoom" or key=="meetingState" or key=="faking" or key=="menubar" or key=="mode" or key=="sharing" or key=="apps") then + return _internal[key] + else + return rawget( table, key ) + end + end, + --SET + __newindex = function (table, key, value) + if(key=="zoom" or key=="meetingState" or key=="faking") then + --skip writing zoom or meeting state to watchformeeting as they are read-only fields + elseif(key=="menubar") then + _internal.menubar = setmetatable(value, {__index=_internal.menubarDefaults, __newindex=_internal.menubar__newIndex}) + if(_internal.menubar.enabled) then + _internal.meetingMenuBar:returnToMenuBar() + _internal.updateMenuIcon(_internal.meetingState, _internal.faking) + else + _internal.meetingMenuBar:removeFromMenuBar() + end + elseif(key=="mode") then + if(value == 1) then + table:fake() + else + table:auto() + end + elseif(key=="sharing") then + _internal.sharing = setmetatable(value, {__index=_internal.sharingDefaults}) + elseif(key=="apps") then + _internal.apps = setmetatable(value, {__index=_internal.appsDefaults}) + else + return rawset(table, key, value) + end + end + }) + +------------------------------------------- +-- End of Declare Variables +------------------------------------------- + +------------------------------------------- +-- Menu Bar +------------------------------------------- + +_internal.meetingMenuBar = hs.menubar.new(false) + + +function _internal.updateMenuIcon(status, faking) + if(_internal.menubar.enabled) then + local iconPath = 'menubar-icons/' + + if(_internal.menubar.color) then + iconPath = iconPath..'Color/' + else + iconPath = iconPath..'Template/' + end + + if(_internal.menubar.detailed) then + iconPath = iconPath..'Detailed/' + else + iconPath = iconPath..'Minimal/' + end + + local iconFile = "" + if(status) then + iconFile = "Meeting" + if(_internal.menubar.showFullState and (status.mic_open or status.video_on or status.sharing)) then + if(status.mic_open) then iconFile = iconFile.."-Mic" end + if(status.video_on) then iconFile = iconFile.."-Vid" end + if(status.sharing) then iconFile = iconFile.."-Screen" end + end + if(faking) then iconFile = iconFile.."-Faking" end + iconFile = iconFile..".pdf" + else + iconFile = "Free.pdf" + end + + _internal.meetingMenuBar:setIcon(hs.spoons.resourcePath(iconPath..iconFile),not _internal.menubar.color) + end +end + +------------------------------------------- +-- End of Menu Bar +------------------------------------------- + + +------------------------------------------- +-- Web Server +------------------------------------------- +_internal.server = nil +_internal.websocketStatus = "closed" + +local function composeJsonUpdate(meetingState) + local message = {action="update", inMeeting=meetingState} + return hs.json.encode(message) +end + +local monitorfile = io.open(hs.spoons.resourcePath("monitor.html"), "r") +local htmlContent = monitorfile:read("*a") +monitorfile:close() + +local function selfhostHttpCallback() + local websocketPath = "ws://"..hs.network.interfaceDetails(hs.network.primaryInterfaces())["IPv4"]["Addresses"][1]..":"..WatchForMeeting.sharing.port.."/ws" + htmlContent = string.gsub(htmlContent,"%%websocketpath%%",websocketPath) + return htmlContent, 200, {} +end + +local function selfhostWebsocketCallback(msg) + return composeJsonUpdate(_internal.meetingState) +end +------------------------------------------- +-- End Web Server +------------------------------------------- + +------------------------------------------- +-- Zoom Monitor +------------------------------------------- + + +local function emit(event) + local fns=_internal.events[event] + if fns then + for fn in pairs(fns) do fn() end + end + end + +local function updateCallbacks() + if(_internal.server and _internal.websocketStatus == "open") then _internal.server:send(composeJsonUpdate(_internal.meetingState)) end + + -- Emit appropriate events + if type(_internal.meetingState)~=type(_internal.lastMeetingState) then + emit(WatchForMeeting.meetingChange) + if _internal.meetingState==false then + if type(_internal.lastMeetingState)=="table" then + emit(WatchForMeeting.micChange) + emit(WatchForMeeting.videoChange) + emit(WatchForMeeting.screensharingChange) + if _internal.lastMeetingState.mic_open then + emit(WatchForMeeting.micOff) + end + if _internal.lastMeetingState.video_on then + emit(WatchForMeeting.videoOff) + end + if _internal.lastMeetingState.sharing then + emit(WatchForMeeting.screensharingOff) + end + end + + emit(WatchForMeeting.meetingStopped) + else + emit(WatchForMeeting.meetingStarted) + end + end + + if type(_internal.meetingState)=="table" then + + if not _internal.lastMeetingState or _internal.lastMeetingState.mic_open~=_internal.meetingState.mic_open then + emit(WatchForMeeting.micChange) + if _internal.meetingState.mic_open then + emit(WatchForMeeting.micOn) + else + emit(WatchForMeeting.micOff) + end + end + + if not _internal.lastMeetingState or _internal.lastMeetingState.video_on~=_internal.meetingState.video_on then + emit(WatchForMeeting.videoChange) + if _internal.meetingState.video_on then + emit(WatchForMeeting.videoOn) + else + emit(WatchForMeeting.videoOff) + end + end + + if not _internal.lastMeetingState or _internal.lastMeetingState.sharing~=_internal.meetingState.sharing then + emit(WatchForMeeting.screensharingChange) + if _internal.meetingState.sharing then + emit(WatchForMeeting.screensharingOn) + else + emit(WatchForMeeting.screensharingOff) + end + end + end + + _internal.lastMeetingState = _internal.meetingState + +end + +local function currentlyInMeeting() + --If zoom is running and the second menu in zoom's menu bar is "Meeting" then we're in a meeting + local inMeetingState = (_internal.zoom ~= nil and _internal.zoom:getMenuItems()[2].AXTitle == "Meeting") + return inMeetingState +end + +--declare startStopWatchMeeting before watchMeeting, define it after. +local startStopWatchMeeting = function() end + +local watchMeeting = hs.timer.new(0.5, function() + + if(currentlyInMeeting() == false) then + _internal.updateMenuIcon(false) + -- No longer in a meeting, stop watching the meeting + startStopWatchMeeting() + + updateCallbacks() + return + else + _internal.updateMenuIcon(_internal.meetingState, _internal.faking) + --Watch for zoom menu items + local _mic_open = _internal.zoom:findMenuItem({"Meeting", "Unmute audio"})==nil + local _video_on = _internal.zoom:findMenuItem({"Meeting", "Start video"})==nil + local _sharing = _internal.zoom:findMenuItem({"Meeting", "Start share"})==nil + if((_internal.meetingState.mic_open ~= _mic_open) or (_internal.meetingState.video_on ~= _video_on) or (_internal.meetingState.sharing ~= _sharing)) then + _internal.meetingState = {mic_open = _mic_open, video_on = _video_on, sharing = _sharing} + WatchForMeeting.logger.d("In Meeting: ", (_internal.meetingState and true)," Open Mic: ",_internal.meetingState.mic_open," Video-ing:",_internal.meetingState.video_on," Sharing",_internal.meetingState.sharing) + updateCallbacks() + end + end +end) + +startStopWatchMeeting = function() + if(not _internal.faking) then + if(_internal.meetingState == false and currentlyInMeeting() == true) then + _internal.updateMenuIcon(_internal.meetingState, _internal.faking) + WatchForMeeting.logger.d("Start Meeting") + _internal.meetingState = {} + watchMeeting:start() + watchMeeting:fire() + elseif(_internal.meetingState and currentlyInMeeting() == false) then + _internal.updateMenuIcon(false) + WatchForMeeting.logger.d("End Meeting") + watchMeeting:stop() + _internal.meetingState = false + updateCallbacks() + end + else + --If we're faking the meeting we don't need watchMeeting to be regularly checking the status of the meeting elements + watchMeeting:stop() + end +end + + +local function checkMeetingStatus(window, name, event) + WatchForMeeting.logger.d("Check Meeting Status",window,name,event) + _internal.zoom = window:application() + startStopWatchMeeting() +end + +-- Monitor zoom for running meeting +hs.application.enableSpotlightForNameSearches(true) +_internal.zoomWindowFilter = hs.window.filter.new(false,"ZoomWindowFilterLog",0):setAppFilter('zoom.us') +_internal.zoomWindowFilter:subscribe(hs.window.filter.hasWindow,checkMeetingStatus,true) +_internal.zoomWindowFilter:subscribe(hs.window.filter.hasNoWindows,checkMeetingStatus) +_internal.zoomWindowFilter:subscribe(hs.window.filter.windowDestroyed,checkMeetingStatus) +_internal.zoomWindowFilter:subscribe(hs.window.filter.windowTitleChanged,checkMeetingStatus) +_internal.zoomWindowFilter:pause() + +------------------------------------------- +-- End of Zoom Monitor +------------------------------------------- + + +------------------------------------------- +-- Teams Monitor +------------------------------------------- + +_internal.teamsInMeeting = false +_internal.teamsWebsocket = nil +_internal.teamsConnectionId = 0 + +local function disconnectFromTeams() + if _internal.teamsWebsocket then + _internal.teamsWebsocket:close() + _internal.teamsWebsocket = nil + end +end + +-- forward declare connectToTeams so onTeamsMessage can reference it for reconnects +local connectToTeams = function() end + +local function onTeamsMessage(wsType, message) + WatchForMeeting.logger.d("Teams WebSocket "..wsType, message) + + if wsType == "open" then + WatchForMeeting.logger.d("Connected to Teams local API") + + elseif wsType == "received" then + local ok, parsed = pcall(hs.json.decode, message) + if not ok then + WatchForMeeting.logger.w("Failed to parse Teams message: "..message) + return + end + + if parsed.tokenRefresh then + WatchForMeeting.logger.d("Teams token refreshed") + hs.settings.set("WatchForMeeting.teamsToken", parsed.tokenRefresh) + end + + if parsed.meetingUpdate and parsed.meetingUpdate.meetingPermissions and parsed.meetingUpdate.meetingPermissions.canPair and not _internal.teamsPairing then + + WatchForMeeting.logger.d("Sending pairing request") + _internal.teamsPairing = true + _internal.teamsWebsocket:send('{"action":"toggle-mute","parameters":{},"requestId":1}') + end + + if parsed.response and parsed.response == "Pairing response resulted in no action" then + WatchForMeeting.logger.d("Didn't pair. Will try again next meeting.") + _internal.teamsPairing = false + end + + if parsed.meetingUpdate and parsed.meetingUpdate.meetingState and not _internal.faking then + local ms = parsed.meetingUpdate.meetingState + if ms.isInMeeting then + local newState = { + mic_open = not ms.isMuted, + video_on = ms.isVideoOn, + sharing = ms.isSharing + } + if (not _internal.teamsInMeeting) or + (_internal.meetingState.mic_open ~= newState.mic_open) or + (_internal.meetingState.video_on ~= newState.video_on) or + (_internal.meetingState.sharing ~= newState.sharing) then + _internal.teamsInMeeting = true + _internal.meetingState = newState + _internal.updateMenuIcon(_internal.meetingState, _internal.faking) + updateCallbacks() + end + else + if _internal.teamsInMeeting then + _internal.teamsInMeeting = false + _internal.meetingState = false + _internal.updateMenuIcon(false) + updateCallbacks() + end + end + end + + elseif wsType == "closed" then + _internal.teamsWebsocket = nil + _internal.teamsPairing = false + if _internal.running and WatchForMeeting.apps.teams then + WatchForMeeting.logger.d("Teams WebSocket closed, probably because this app was blocked from the Third-party app API in teams.") + WatchForMeeting.logger.d("Go to Settings > Privacy > Third-party app API > Manage API and remove the application from block.") + end + + elseif wsType == "fail" then + _internal.teamsWebsocket = nil + if _internal.running and WatchForMeeting.apps.teams then + WatchForMeeting.logger.d("Teams not available, retrying in 30 seconds") + hs.timer.doAfter(30, connectToTeams) + end + end +end + +connectToTeams = function() + -- Increment the connection ID before closing, so any callbacks from the + -- previous connection are ignored even if close() fires synchronously. + _internal.teamsConnectionId = _internal.teamsConnectionId + 1 + local myId = _internal.teamsConnectionId + if _internal.teamsWebsocket then + _internal.teamsWebsocket:close() + _internal.teamsWebsocket = nil + end + local token = hs.settings.get("WatchForMeeting.teamsToken") or "" + local url = "ws://localhost:8124?token="..token.."&protocol-version=2.0.0&manufacturer=Hammerspoon&device=WatchForMeeting&app=WatchForMeeting&app-version="..WatchForMeeting.version + WatchForMeeting.logger.d("Connecting to Teams") + _internal.teamsWebsocket = hs.websocket.new(url, function(wsType, message) + if myId == _internal.teamsConnectionId then + onTeamsMessage(wsType, message) + end + end) +end + +------------------------------------------- +-- End of Teams Monitor +------------------------------------------- + + +_internal.connectionAttempts = 0 +_internal.connectionError = false + + +-- forward declare reconnectToSharing so onSharingMessage can reference it for reconnects +local reconnectToSharing = function() end + +local function disconnectFromSharing() + if(_internal.server) then + if(getmetatable(_internal.server).stop) then _internal.server:stop() end + if(getmetatable(_internal.server).close) then _internal.server:close() end + end +end + +local function onSharingMessage(type, message) + if(type=="open") then + _internal.websocketStatus = "open" + _internal.connectionAttempts = 0 + + local draft = {action="identify", key=WatchForMeeting.sharing.key, type="room", status={inMeeting=_internal.meetingState}} + _internal.server:send(hs.json.encode(draft)) + elseif(type == "closed" and _internal.running) then + _internal.websocketStatus = "closed" + if(_internal.connectionError) then + WatchForMeeting.logger.d("Lost connection to sharing websocket, will not reattempt due to error") + else + WatchForMeeting.logger.d("Lost connection to sharing websocket, attempting to reconnect in "..WatchForMeeting.sharing.waitBeforeRetry.." seconds") + reconnectToSharing() + end + elseif(type == "fail") then + _internal.websocketStatus = "fail" + if(WatchForMeeting.sharing.maxConnectionAttempts > 0) then + WatchForMeeting.logger.d("Could not connect to sharing websocket server. attempting to reconnect in "..WatchForMeeting.sharing.waitBeforeRetry.." seconds. (Attempt ".._internal.connectionAttempts.."/"..WatchForMeeting.sharing.maxConnectionAttempts..")") + else + WatchForMeeting.logger.d("Could not connect to sharing websocket server. attempting to reconnect in "..WatchForMeeting.sharing.waitBeforeRetry.." seconds. (Attempt ".._internal.connectionAttempts..")") + end + reconnectToSharing() + elseif(type == "received") then + local parsed = hs.json.decode(message); + if(parsed.error) then + _internal.connectionError = true; + if(parsed.errorType == "badkey") then + disconnectFromSharing() + hs.showError("") + WatchForMeeting.logger.e("WatchForMeeting.sharing.key not valid. Make sure that key has been established on the server.") + end + else + WatchForMeeting.logger.d("Sharing Websocket Message received: ", hs.inspect.inspect(parsed)); + end + + else + WatchForMeeting.logger.d("Sharing Websocket Callback "..type, message) + end +end + + +local function connectToSharing() + if(WatchForMeeting.sharing) then + if(WatchForMeeting.sharing.useServer) then + WatchForMeeting.logger.d("Connecting to server at "..WatchForMeeting.sharing.serverURL) + _internal.connectionAttempts = _internal.connectionAttempts + 1 + _internal.websocketStatus = "connecting" + _internal.server = hs.websocket.new(WatchForMeeting.sharing.serverURL, onSharingMessage); + else + WatchForMeeting.logger.d("Starting Self Hosted Server on port "..WatchForMeeting.sharing.port) + _internal.server = hs.httpserver.new() + _internal.server:websocket("/ws", selfhostWebsocketCallback) + _internal.websocketStatus = "open" + _internal.server:setPort(WatchForMeeting.sharing.port) + _internal.server:setCallback(selfhostHttpCallback) + _internal.server:start() + end + end +end + +--redefine reconnectToSharing now that connectToSharing & disconnectFromSharing exist. +reconnectToSharing = function() + if(WatchForMeeting.sharing.maxConnectionAttempts > 0 and _internal.connectionAttempts >= WatchForMeeting.sharing.maxConnectionAttempts) then + WatchForMeeting.logger.e("Maximum Connection Attempts failed") + disconnectFromSharing() + elseif(_internal.connectionError) then + disconnectFromSharing() + else + hs.timer.doAfter(WatchForMeeting.sharing.waitBeforeRetry, connectToSharing) + end +end + + +function validateShareSettings() + WatchForMeeting.logger.d("validateShareSettings") + if(WatchForMeeting.sharing.useServer and (WatchForMeeting.sharing.serverURL==nil or WatchForMeeting.sharing.key==nil)) then + hs.showError("") + if(WatchForMeeting.sharing.serverURL==nil) then WatchForMeeting.logger.e("WatchForMeeting.sharing.serverURL required when using a server") end + if(WatchForMeeting.sharing.key==nil) then WatchForMeeting.logger.e("WatchForMeeting.sharing.key required when using a server") end + return false + elseif(not WatchForMeeting.sharing.useServer and WatchForMeeting.sharing.port==nil) then + hs.showError("") + WatchForMeeting.logger.e("WatchForMeeting.sharing.port required when self hosting") + return false + else + return true + end +end + + +------------------------------------------- +-- Methods +------------------------------------------- + + +--- WatchForMeeting:start() -> WatchForMeeting +--- Method +--- Starts a WatchForMeeting object +--- +--- Parameters: +--- * None +--- +--- Returns: +--- * The spoon.WatchForMeeting object +function WatchForMeeting:start() + if(not _internal.running) then + _internal.running = true + if(self.sharing.enabled and validateShareSettings()) then + connectToSharing() + end + + if(self.menubar.enabled) then + _internal.meetingMenuBar:returnToMenuBar() + end + + if(_internal.mode == 1 ) then + self:fake() + else + self:auto() + end + end + + return self +end + +--- WatchForMeeting:stop() +--- Method +--- Stops a WatchForMeeting object +--- +--- Parameters: +--- * None +--- +--- Returns: +--- * The spoon.WatchForMeeting object +function WatchForMeeting:stop() + _internal.running = false + disconnectFromSharing() + + _internal.lastMeetingState = nil + + _internal.meetingMenuBar:removeFromMenuBar() + _internal.zoomWindowFilter:pause() + disconnectFromTeams() + return self +end + +--- WatchForMeeting:start() +--- Method +--- Restarts a WatchForMeeting object +--- +--- Parameters: +--- * None +--- +--- Returns: +--- * The spoon.WatchForMeeting object +function WatchForMeeting:restart() + self:stop() + return self:start() +end + + + +--- WatchForMeeting:auto() +--- Method +--- Monitors Zoom and updates status accordingly +--- +--- Parameters: +--- * None +--- +--- Returns: +--- * The spoon.WatchForMeeting object +function WatchForMeeting:auto() + _internal.mode = 0 + + if(_internal.running) then + _internal.faking = false + _internal.meetingState = false + _internal.teamsInMeeting = false + + if(WatchForMeeting.apps.zoom) then + startStopWatchMeeting() + end + + _internal.meetingMenuBar:setMenu({ + { title = "Meeting Status:", disabled = true }, + { title = "Automatic", checked = true }, + { title = "Busy", checked = false, fn=function() WatchForMeeting:fake() end } + }) + + --Update everything + _internal.updateMenuIcon(_internal.meetingState, _internal.faking) + updateCallbacks() + + if(WatchForMeeting.apps.zoom) then + _internal.zoomWindowFilter:resume() + else + _internal.zoomWindowFilter:pause() + end + + if(WatchForMeeting.apps.teams and not _internal.teamsWebsocket) then + connectToTeams() + end + end + + return self +end + + +--- WatchForMeeting:fake(mic_open, video_on, sharing) +--- Method +--- Disables monitoring and reports as being in a meeting. Useful when meeting type is not supported. +--- +--- Parameters: +--- * mic_open - A boolean indicating if the mic is open +--- * video_on - A boolean indicating if the video camera is on +--- * sharing - A boolean indicating if screen sharing is on +--- +--- Returns: +--- * The spoon.WatchForMeeting object +function WatchForMeeting:fake(_mic_open, _video_on, _sharing) + _internal.mode = 1 + + if(_internal.running) then + _internal.faking = true + _internal.meetingState = {mic_open = _mic_open, video_on = _video_on, sharing = _sharing} + startStopWatchMeeting() + + local meetingMenu = { + { title = "Meeting Status:", disabled = true }, + { title = "Automatic", checked = false, fn=function() WatchForMeeting:auto() end }, + { title = "Busy", checked = true }, + { title = "-"} + } + if(not (_mic_open and _video_on and _sharing)) then + table.insert(meetingMenu, { title = "Select All", fn=function() WatchForMeeting:fake(true, true, true) end }) + else + table.insert(meetingMenu, { title = "Select None", fn=function() WatchForMeeting:fake(false, false, false) end }) + end + + table.insert(meetingMenu, { title = "Mic On", indent=1, checked = _internal.meetingState.mic_open, fn=function() WatchForMeeting:fake(not _mic_open, _video_on, _sharing) end}) + table.insert(meetingMenu, { title = "Video On", indent=1, checked = _internal.meetingState.video_on, fn=function() WatchForMeeting:fake(_mic_open, not _video_on, _sharing) end }) + table.insert(meetingMenu, { title = "Sharing Screen", indent=1, checked = _internal.meetingState.sharing, fn=function() WatchForMeeting:fake(_mic_open, _video_on, not _sharing) end }) + + + if(_mic_open or _video_on or _sharing) then + table.insert(meetingMenu, { title = "Clear", fn=function() WatchForMeeting:fake(false, false, false) end }) + end + _internal.meetingMenuBar:setMenu(meetingMenu) + + _internal.zoomWindowFilter:pause() + + updateCallbacks() + _internal.updateMenuIcon(_internal.meetingState, _internal.faking) + end + + return self +end + + + +--- WatchForMeeting:subscribe(event, fn) +--- Method +--- Subscribe to one event with one or more functions +--- +--- Parameters: +--- * event - string of the event to subscribe to (see the `spoon.WatchForMeeting` constants) +--- * fn - function or list of functions, the callback(s) to add for the event(s); +--- +--- Returns: +--- * The `spoon.WatchForMeeting` object for method chaining + +function WatchForMeeting:subscribe(event, fns) + if not events[event] then error('invalid event: '..event,3) end + if type(fns)=='function' then fns = {fns} end + if type(fns)~='table' then error('fn must be a function or table of functions',3) end + for _,fn in pairs(fns) do + if type(fn)~='function' then error('fn must be a function or table of functions',3) end + if not _internal.events[event] then _internal.events[event]={} end + if not _internal.events[event][fn] then + _internal.events[event][fn]=true + WatchForMeeting.logger.df('added callback for event %s',event) + end + end + return self +end + +--- WatchForMeeting:unsubscribe(event, fn) -> hs.window.filter object +--- Method +--- Removes one or more event subscriptions +--- +--- Parameters: +--- * event - string of the event to unsubscribe; +--- * fn - function or list of functions, the callback(s) to remove; if omitted, all callbacks will be unsubscribed from `event`(s) +--- +--- Returns: +--- * The `spoon.WatchForMeeting` object for method chaining +--- +function WatchForMeeting:unsubscribe(event,fn) + if _internal.events[event] and _internal.events[event][fn] then + WatchForMeeting.logger.df('removed callback for event %s',event) + _internal.events[event][fn]=nil + if not next(_internal.events[event]) then + WatchForMeeting.logger.df('no more callbacks for event %s',event) + _internal.events[event]=nil + end + end +end + +--- WatchForMeeting:unsubscribeEvent(event) -> hs.window.filter object +--- Method +--- Removes all subscriptions from one event +--- +--- Parameters: +--- * event - string of the event to unsubscribe; ; +--- +--- Returns: +--- * The `spoon.WatchForMeeting` object for method chaining +--- +function WatchForMeeting:unsubscribeEvent(event) + if not events[event] then error('invalid event: '..event,3) end + if _internal.events[event] then WatchForMeeting.logger.df('removed all callbacks for event %s',event) end + _internal.events[event]=nil + end + + +------------------------------------------- +-- End of Methods +------------------------------------------- + +return WatchForMeeting diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Free.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Free.pdf new file mode 100644 index 00000000..ca50ecbb Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Free.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Free.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Free.png new file mode 100644 index 00000000..e3a998be Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Free.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Faking.pdf new file mode 100644 index 00000000..f80ecaa7 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Faking.png new file mode 100644 index 00000000..1d0acf45 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Faking.pdf new file mode 100644 index 00000000..bc90d4d9 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Faking.png new file mode 100644 index 00000000..b42a35fe Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen-Faking.pdf new file mode 100644 index 00000000..7cdebf14 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen-Faking.png new file mode 100644 index 00000000..d4d71130 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen.pdf new file mode 100644 index 00000000..abfe439b Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen.png new file mode 100644 index 00000000..b42b0438 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Faking.pdf new file mode 100644 index 00000000..28c4e800 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Faking.png new file mode 100644 index 00000000..db61c66c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen-Faking.pdf new file mode 100644 index 00000000..369fda4a Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen-Faking.png new file mode 100644 index 00000000..398d7e19 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen.pdf new file mode 100644 index 00000000..efcbd4ff Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen.png new file mode 100644 index 00000000..cc21d26c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid.pdf new file mode 100644 index 00000000..5aca4ded Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid.png new file mode 100644 index 00000000..cd9c2077 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic-Vid.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic.pdf new file mode 100644 index 00000000..1eeeacaf Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic.png new file mode 100644 index 00000000..759a5a0f Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Mic.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen-Faking.pdf new file mode 100644 index 00000000..91e9edac Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen-Faking.png new file mode 100644 index 00000000..7d967c3e Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen.pdf new file mode 100644 index 00000000..ea1fcf31 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen.png new file mode 100644 index 00000000..a1f3186d Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Faking.pdf new file mode 100644 index 00000000..f374c7d0 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Faking.png new file mode 100644 index 00000000..b9ec48e7 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen-Faking.pdf new file mode 100644 index 00000000..c3fbc5bf Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen-Faking.png new file mode 100644 index 00000000..80a03d51 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen.pdf new file mode 100644 index 00000000..93377e0b Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen.png new file mode 100644 index 00000000..9f174eb9 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid.pdf new file mode 100644 index 00000000..c29d59a2 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid.png new file mode 100644 index 00000000..9e438e78 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting-Vid.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting.pdf new file mode 100644 index 00000000..1cd0feb2 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting.png new file mode 100644 index 00000000..2f138e2d Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Detailed/Meeting.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Free.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Free.pdf new file mode 100644 index 00000000..33423ea2 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Free.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Free.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Free.png new file mode 100644 index 00000000..af02b107 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Free.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Faking.pdf new file mode 100644 index 00000000..3811fcd5 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Faking.png new file mode 100644 index 00000000..0deeb0eb Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Faking.pdf new file mode 100644 index 00000000..9c923c65 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Faking.png new file mode 100644 index 00000000..448aff0d Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen-Faking.pdf new file mode 100644 index 00000000..7146b10e Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen-Faking.png new file mode 100644 index 00000000..3d17ff26 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen.pdf new file mode 100644 index 00000000..333dad9e Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen.png new file mode 100644 index 00000000..fa661050 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Faking.pdf new file mode 100644 index 00000000..7a535c93 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Faking.png new file mode 100644 index 00000000..962feeab Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen-Faking.pdf new file mode 100644 index 00000000..be142e47 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen-Faking.png new file mode 100644 index 00000000..7919c65c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen.pdf new file mode 100644 index 00000000..4c30b01d Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen.png new file mode 100644 index 00000000..2cc8a84f Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid.pdf new file mode 100644 index 00000000..7b8dc2a1 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid.png new file mode 100644 index 00000000..341d06ed Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic-Vid.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic.pdf new file mode 100644 index 00000000..061926ed Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic.png new file mode 100644 index 00000000..c1193b3a Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Mic.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen-Faking.pdf new file mode 100644 index 00000000..e013187c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen-Faking.png new file mode 100644 index 00000000..b6e6144e Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen.pdf new file mode 100644 index 00000000..63490a3b Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen.png new file mode 100644 index 00000000..4cde1a70 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Faking.pdf new file mode 100644 index 00000000..c7282e02 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Faking.png new file mode 100644 index 00000000..89bd87e2 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen-Faking.pdf new file mode 100644 index 00000000..a967b151 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen-Faking.png new file mode 100644 index 00000000..cbac2836 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen.pdf new file mode 100644 index 00000000..b206d656 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen.png new file mode 100644 index 00000000..2d074cbb Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid.pdf new file mode 100644 index 00000000..817961bc Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid.png new file mode 100644 index 00000000..57761080 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting-Vid.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting.pdf new file mode 100644 index 00000000..443484ef Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting.png b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting.png new file mode 100644 index 00000000..9dcd6089 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Color/Minimal/Meeting.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Free.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Free.pdf new file mode 100644 index 00000000..6ba7beab Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Free.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Free.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Free.png new file mode 100644 index 00000000..a8f418c7 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Free.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Faking.pdf new file mode 100644 index 00000000..f5f2d4eb Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Faking.png new file mode 100644 index 00000000..868cd7c3 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Faking.pdf new file mode 100644 index 00000000..c5eec598 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Faking.png new file mode 100644 index 00000000..b567168a Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen-Faking.pdf new file mode 100644 index 00000000..7ec0ba2d Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen-Faking.png new file mode 100644 index 00000000..04c96c61 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen.pdf new file mode 100644 index 00000000..a75baabe Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen.png new file mode 100644 index 00000000..9c16a44e Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Faking.pdf new file mode 100644 index 00000000..82209422 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Faking.png new file mode 100644 index 00000000..149d9c32 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen-Faking.pdf new file mode 100644 index 00000000..bc5a8d7c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen-Faking.png new file mode 100644 index 00000000..1b96e570 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen.pdf new file mode 100644 index 00000000..a5412ec9 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen.png new file mode 100644 index 00000000..466334ef Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid.pdf new file mode 100644 index 00000000..af39236f Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid.png new file mode 100644 index 00000000..bb3c02ef Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic-Vid.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic.pdf new file mode 100644 index 00000000..8e67fa54 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic.png new file mode 100644 index 00000000..c4b28472 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Mic.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen-Faking.pdf new file mode 100644 index 00000000..af8e7c24 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen-Faking.png new file mode 100644 index 00000000..26f2d583 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen.pdf new file mode 100644 index 00000000..edecadd4 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen.png new file mode 100644 index 00000000..82ac43ff Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Faking.pdf new file mode 100644 index 00000000..0513e277 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Faking.png new file mode 100644 index 00000000..ff8054d5 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen-Faking.pdf new file mode 100644 index 00000000..5a6bb831 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen-Faking.png new file mode 100644 index 00000000..49cc63e5 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen.pdf new file mode 100644 index 00000000..1ec6450c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen.png new file mode 100644 index 00000000..c46cf046 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid.pdf new file mode 100644 index 00000000..9a1cd449 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid.png new file mode 100644 index 00000000..81257b7c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting-Vid.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting.pdf new file mode 100644 index 00000000..8255e5e7 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting.png new file mode 100644 index 00000000..42a9adc8 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Detailed/Meeting.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Free.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Free.pdf new file mode 100644 index 00000000..ead3a84f Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Free.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Free.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Free.png new file mode 100644 index 00000000..0b721e8a Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Free.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Faking.pdf new file mode 100644 index 00000000..2a8be9e6 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Faking.png new file mode 100644 index 00000000..a6835652 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Faking.pdf new file mode 100644 index 00000000..3e3e4355 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Faking.png new file mode 100644 index 00000000..2ec93619 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen-Faking.pdf new file mode 100644 index 00000000..03171ee1 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen-Faking.png new file mode 100644 index 00000000..c3f9182d Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen.pdf new file mode 100644 index 00000000..06de993e Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen.png new file mode 100644 index 00000000..0e4cf192 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Faking.pdf new file mode 100644 index 00000000..cee32115 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Faking.png new file mode 100644 index 00000000..86c46a9f Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen-Faking.pdf new file mode 100644 index 00000000..894db3d2 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen-Faking.png new file mode 100644 index 00000000..0dff260c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen.pdf new file mode 100644 index 00000000..272a6c51 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen.png new file mode 100644 index 00000000..bcecfe06 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid.pdf new file mode 100644 index 00000000..5ab86cc6 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid.png new file mode 100644 index 00000000..33a0d180 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic-Vid.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic.pdf new file mode 100644 index 00000000..163126eb Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic.png new file mode 100644 index 00000000..72f99e26 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Mic.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen-Faking.pdf new file mode 100644 index 00000000..06abc7d7 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen-Faking.png new file mode 100644 index 00000000..62eb6324 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen.pdf new file mode 100644 index 00000000..08da50f5 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen.png new file mode 100644 index 00000000..3f718cdf Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Faking.pdf new file mode 100644 index 00000000..692d3cda Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Faking.png new file mode 100644 index 00000000..d89c65cb Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen-Faking.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen-Faking.pdf new file mode 100644 index 00000000..604e620e Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen-Faking.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen-Faking.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen-Faking.png new file mode 100644 index 00000000..337e035c Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen-Faking.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen.pdf new file mode 100644 index 00000000..487982e6 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen.png new file mode 100644 index 00000000..f6014d66 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid-Screen.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid.pdf new file mode 100644 index 00000000..b532140b Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid.png new file mode 100644 index 00000000..483fdbd8 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting-Vid.png differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting.pdf b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting.pdf new file mode 100644 index 00000000..f89685b7 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting.pdf differ diff --git a/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting.png b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting.png new file mode 100644 index 00000000..bc976c30 Binary files /dev/null and b/Source/WatchForMeeting.spoon/menubar-icons/Template/Minimal/Meeting.png differ diff --git a/Source/WatchForMeeting.spoon/monitor.html b/Source/WatchForMeeting.spoon/monitor.html new file mode 100644 index 00000000..a7bb5bd0 --- /dev/null +++ b/Source/WatchForMeeting.spoon/monitor.html @@ -0,0 +1,271 @@ + + + + Zoom Panel + + + + + + +
Initializing...
+ + \ No newline at end of file