added teams-for-linux
This commit is contained in:
@@ -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/<meeting-id>?p=<passcode>'
|
||||
```
|
||||
|
||||
### 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/<meeting-id>?p=<passcode>`. 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<meeting-id>%3Fp%3D<passcode>%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 `<script>` element and is then
|
||||
blocked by the launcher page, which sends a `script-src 'nonce-...'` content security policy.
|
||||
The script still matches, but never executes.
|
||||
- `window.stop()` aborts the launcher page before its own scripts are parsed. Otherwise the page
|
||||
navigates on to `https://teams.microsoft.com/v2/`, which destroys the document and discards the
|
||||
pending timeout, so the tab is never cleaned up. It must be called **before** the `msteams:`
|
||||
assignment, because calling it afterwards cancels the handover itself.
|
||||
- `@run-at document-start` is what makes the above possible in the first place.
|
||||
|
||||
On the first meeting link the browser asks which application should open the link. Select Teams for
|
||||
Linux and enable `Always allow teams.microsoft.com to open links of this type`. The dialog is
|
||||
tab-modal, so if it is still open when the tab closes it disappears together with the tab.
|
||||
|
||||
> Tip: The tab is closed by `window.close()` even though `dom.allow_scripts_to_close_windows` is
|
||||
> `false` by default. Outlook opens meeting links through `window.open()`, and Firefox permits a
|
||||
> script to close windows that were opened by a script. The `location.replace('about:blank')` call
|
||||
> is the fallback for tabs that were opened another way.
|
||||
|
||||
### Maintenance
|
||||
|
||||
Two upstream changes can break this and are worth checking first if meeting links stop working:
|
||||
|
||||
- Microsoft changing the launcher URL layout. The `.replace(/^\/_#/, '')` call encodes the current
|
||||
`/_#/meet/...` format of the `url` query parameter.
|
||||
- Teams for Linux tightening its deep link validation. The accepted schemes are defined by these
|
||||
regular expressions in the application:
|
||||
|
||||
```txt
|
||||
^msteams:/(?:meet/|l/(?:app|call|channel|chat|entity|file|meet(?:ing|up-join)|message|task|team)/)
|
||||
^msteams://teams\.(?:microsoft\.com|live\.com|cloud\.microsoft)/(?:meet/|l/(?:app|call|channel|chat|entity|file|meet(?:ing|up-join)|message|task|team)/)
|
||||
```
|
||||
|
||||
[teams-for-linux]: https://github.com/IsmaelMartinez/teams-for-linux
|
||||
[electron]: https://www.electronjs.org
|
||||
[tampermonkey]: https://www.tampermonkey.net
|
||||
Reference in New Issue
Block a user