diff --git a/software/009-teams-for-linux.qd b/software/009-teams-for-linux.qd new file mode 100644 index 0000000..9bae698 --- /dev/null +++ b/software/009-teams-for-linux.qd @@ -0,0 +1,140 @@ +.docname {Teams for Linux} +.include {docs} + +Microsoft does not publish a native Teams client for Linux. [Teams for Linux][teams-for-linux] is an +unofficial [Electron][electron] wrapper around the Teams web app that provides a standalone desktop +client, system tray integration and native notifications. + +Install it with `yay -S teams-for-linux-bin`. + +## Configuration + +The configuration is stored in `~/.config/teams-for-linux/config.json`. + +```json +{ + "notificationMethod": "web", + "electronCLIFlags": [ + ["enable-features", "WebRtcPipeWireCamera"] + ] +} +``` + +- `notificationMethod: web` uses the browser notification API of the embedded web app instead of the + Electron notification API. +- `WebRtcPipeWireCamera` enables the PipeWire camera backend, which is required for the webcam to + work under Wayland. + +## Opening meeting links in the app + +Clicking a Teams meeting link in the [Thunderbird](002-thunderbird.qd) or Outlook web interface opens +the meeting in the browser instead of in Teams for Linux. Getting the handover to work requires a +userscript, because the desktop integration alone is not sufficient. + +### Verify the protocol handler + +The package registers the `msteams:` URI scheme through its desktop entry, so this part usually works +out of the box: + +```sh +gio mime x-scheme-handler/msteams +``` + +The output must name `teams-for-linux.desktop` as the default application. If it does not, register it +with `xdg-mime default teams-for-linux.desktop x-scheme-handler/msteams`. + +A deep link can be tested directly from a shell. This opens the meeting in Teams for Linux: + +```sh +xdg-open 'msteams:/meet/?p=' +``` + +### Why the browser never asks + +Meeting invitations do not contain `msteams:` links. They contain plain HTTPS links in the form +`https://teams.microsoft.com/meet/?p=`. An HTTPS URL belongs to the browser, +so nothing is ever handed to an external application. + +That URL answers with a redirect to a launcher page: + +```txt +HTTP/2 302 +location: /dl/launcher/launcher.html?url=%2F_%23%2Fmeet%2F%3Fp%3D%26anon%3Dtrue&type=meet&directDl=true&msLaunch=true +``` + +Because the redirect happens on the server, no document is ever created for the `/meet/` URL and a +userscript cannot match on it. The launcher page is the first document that actually loads, and its +`url` query parameter carries the deep link path, prefixed with `/_#`. Stripping that prefix yields +exactly the path the `msteams:` scheme expects. + +### Userscript + +Install [Tampermonkey][tampermonkey] in [Librewolf](001-librewolf.qd) and add the following userscript. + +```js +// ==UserScript== +// @name Teams links to teams-for-linux +// @match https://teams.microsoft.com/dl/launcher/launcher.html* +// @match https://teams.microsoft.com/meet/* +// @match https://teams.microsoft.com/l/* +// @run-at document-start +// @grant GM_info +// ==/UserScript== +(() => { + let path; + if (location.pathname.startsWith('/dl/launcher/')) { + const u = new URLSearchParams(location.search).get('url'); + if (!u) return; + path = u.replace(/^\/_#/, ''); + } else { + path = location.pathname + location.search; + } + if (!/^\/(meet|l)\//.test(path)) return; + + window.stop(); + location.href = 'msteams:' + path; + setTimeout(() => { + window.close(); + location.replace('about:blank'); + }, 1500); +})(); +``` + +Three details in this script are not obvious and the script silently stops working without them: + +- `@grant GM_info` forces Tampermonkey to run the script in its sandboxed content script scope. With + `@grant none` the script is injected into the page as an inline `