Skip to content
knagatoPublic

About

Keep several Matrix accounts (Element Web, Cinny, …) side by side in one macOS window

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

mxdeck

English | 日本語

A desktop shell for macOS and Windows that keeps several Matrix accounts in one window. Pick an account in the left sidebar and its Matrix web client (Element Web, Cinny, …) appears on the right.

mxdeck

Element Desktop runs one account per process; the only way to use several is to start separate processes with --profile. Making Element Web itself multi-account is a deep change (MatrixClientPeg and friends are singletons), so mxdeck does it from the outside: one isolated browser environment per account.

mxdeck is a personal project and is not affiliated with Element (New Vector Ltd) or The Matrix.org Foundation.

Features

  • Cookies, localStorage, IndexedDB (including the crypto store) and service workers are isolated per account
  • Loads hosted web clients as they are — a self-hosted Element Web keeps its own config, theme and modules
  • Background accounts keep running, so sync and notifications continue; clicking a notification switches to that account
  • Unread badges in the sidebar and the Dock / taskbar (from the page title for Element, from the favicon for Cinny)
  • Add, edit, remove and reorder accounts inside the app (written back to accounts.json)
  • ⌘1–⌘9 (Ctrl+1–9 on Windows) to switch accounts, and a per-account memory usage view
  • Plugins: add menu items, panels, sign-in windows with their own storage, and code that talks to widgets inside the clients (docs/PLUGINS.md)

The UI is Japanese only for now.

Install

macOS

Download the dmg from Releases (arm64 for Apple Silicon, x64 for Intel) and move it to Applications. It is signed with a Developer ID and notarized by Apple.

Windows

Download mxdeck-<version>-x64-win-setup.exe from Releases (arm64 for ARM machines such as Snapdragon) and run it. It installs for your user only; no admin rights needed. The installer is not code-signed, so SmartScreen shows "Windows protected your PC" the first time — choose "More info → Run anyway".

From source

pnpm install
node node_modules/electron/install.js   # only if pnpm skipped Electron's postinstall
pnpm start

To build an .app for your own Mac and put it in ~/Applications/mxdeck.app (ad-hoc signed, not notarized):

pnpm run install-app

Quit the running app first. Data lives in ~/Library/Application Support/mxdeck for both pnpm start and the .app, so they share login state (don't run both at once).

pnpm run dist:win builds the Windows installers (x64 and arm64) into dist/. This works on a Mac too.

Usage

Accounts live in ~/.config/mxdeck/accounts.json (override with MXDECK_ACCOUNTS). If the file is missing, mxdeck starts with no accounts and opens the add sheet (accounts.example.json shows the format). On Windows this is %USERPROFILE%\.config\mxdeck\accounts.json; paths can still be written as ~/… with /.

{
  "accounts": [
    { "id": "work", "name": "Work", "url": "https://element.example.com", "icon": "~/Pictures/work.png" },
    { "id": "personal", "name": "Personal", "url": "https://app.element.io", "color": "#0dbd8b" }
  ]
}
  • id becomes the partition name (persist:acct-<id>). Changing it means a new, empty storage — you will have to log in again.
  • Without icon, the first letter of name is shown on a color tile.
  • ⌘1–⌘9 switch accounts, ⌘R reloads only the visible account, ⌥⌘I opens developer tools. On Windows read ⌘ as Ctrl and ⌥ as Alt (Ctrl+1–9, Ctrl+R, Ctrl+Alt+I).
  • View → メモリ使用量… (Memory usage) shows memory per account (working set, including cross-origin iframe processes) and for Electron itself.
  • New versions are checked shortly after launch and every 6 hours, downloaded in the background, and offered with a restart (if declined, they are installed on the next quit). To see the current version or check now: mxdeck → アップデートを確認… (Check for updates; on Windows under ファイル / File).
  • If the window stays white (seen on Windows after the GPU process restarts or the window was covered), View → 画面を描き直す (Repaint) draws it again. Crashed processes and repaints are logged to mxdeck.log in the data folder (%APPDATA%\mxdeck / ~/Library/Application Support/mxdeck).

Managing accounts in the app

Action How
Add "+" at the bottom of the sidebar, or ⇧⌘N. Enter a name, a URL and an icon (image, the site's own icon, or color)
Edit Right-click an icon → 編集… (Edit). Changing the URL reloads only that account
Remove Right-click an icon → 削除… (Remove). You choose whether to also delete its stored data (login, keys)
Reorder Drag the icons. ⌘1–9 follow the new order
After editing the file by hand アカウント → accounts.json を読み直す (Reload accounts.json)
  • The id is generated from the name (or the host name if the name has no ASCII) when an account is added, and never changes.
  • An account removed with its data kept gets its old storage back when you add the same URL again — no new login.
  • Icons picked in the app are copied to ~/.config/mxdeck/icons/.
  • サイトから取得 (Fetch from site) takes the largest icon the client publishes (<link rel="icon">, apple-touch-icon, manifest.json, then /favicon.ico). When adding, it runs by itself once you enter the URL, unless you picked an image yourself.
  • An unreadable accounts.json is moved to accounts.json.broken-<timestamp> before starting empty, never overwritten.

Plugins

Plugin What it does
mxdeck-plugin-login-helper Sign in to a service (Slack, …) in its own window and build the login command for its Matrix bridge (mautrix-slack, …). Tokens are shown masked and go only to the clipboard

To install one, choose プラグイン (Plugins) → プラグインを追加… (Add plugin…) and pick it from the list of official plugins, or enter any other repository as owner/repo, then restart. To use a local folder instead, pick it in フォルダから追加… (Add from folder…). Plugins run with mxdeck's own rights, so only add ones you trust. To write your own, see docs/PLUGINS.md.

How it works

  • One WebContentsView per account, each with its own Electron partition; the sidebar toggles which one is visible.
  • Each account loads the URL of a hosted web client. mxdeck does not bundle a web app behind a custom scheme (like Element Desktop's vector://) because login through MAS (OIDC) requires an https redirect URI.
  • Hidden accounts stay loaded (backgroundThrottling: false).
  • Clients signal unread state differently, so both signals are read and merged:
    • Element: the page title (<brand> [3] is a count, * <brand> is unread only)
    • Cinny: the favicon swap (green logo = mentions → !, grey logo = unread → dot; no count)
    • A client that matches neither simply gets no badge; everything else still works.
  • Notification clicks are caught by wrapping Notification in a preload script (Element calls window.focus(), Cinny only navigates in-page, so their own click behaviour can't be relied on).
  • Links with target=_blank open in the default browser. Windows that continue a sign-in stay in the app, in a window that shares the account's storage: sized popups (an IdP's), and pages on the auth server it signed in through (approving a cross-signing reset with MAS, say). In the browser the login would be missing and the result would never come back.
  • A new tab on the account's own site (a custom welcome page whose "Sign in" uses target=_blank) opens in the account's view itself, so that the client never runs twice on the same storage.
  • A client that fails to load shows why and how to fix it, instead of a blank page.

Security

  • Notifications, camera, microphone, screen capture and clipboard are granted only while the view shows the account's own origin. Pages reached through SSO or other navigation get nothing.
  • Account views run with contextIsolation and sandbox. The only thing exposed to the page is a function that reports a notification click. IPC meant for the sidebar or the editor sheet checks its sender, so account views can't use it.
  • Login state and keys are stored by each client inside its partition (~/Library/Application Support/mxdeck/Partitions/, %APPDATA%\mxdeck\Partitions\ on Windows), as in a browser. There is no keychain protection like Element Desktop's.
  • Plugins run with mxdeck's own rights. Only folders listed in plugins.json are loaded, and adding one from the menu asks first. Messages from frames reach a plugin only from an account view and from the origins it declared (docs/PLUGINS.md).

Limitations

  • No Element Desktop–specific features (Seshat local search for encrypted rooms, tray, launch at login, …). It is the browser version inside.
  • Passkeys (Touch ID / iCloud Keychain) can't be used to log in. macOS only hands synced passkeys to browsers Apple has approved. For Google SSO, choose "Try another way" and use a password plus 2-step verification. Electron/ is removed from the user agent so identity providers don't reject the login as an embedded browser.
  • Unread badges depend on the title / favicon format and break if a client changes it.
  • The Windows build is not code-signed (SmartScreen asks first). Its window keeps the normal frame and menu bar; the sidebar follows the page's color, but the title bar follows the OS.
  • After a force quit, Element may say it "is open in another window" on the next start (Element Web's session lock is left behind). If nothing else is running, it is safe to continue.

Development

Run with a separate data directory and account list, without touching your everyday login state:

MXDECK_USER_DATA=/tmp/mxdeck-test/ud MXDECK_ACCOUNTS=/tmp/mxdeck-test/accounts.json \
  pnpm exec electron . --remote-debugging-port=19222

With --remote-debugging-port, the sidebar and the editor sheet can be driven over CDP (http://127.0.0.1:19222/json).

pnpm test starts mxdeck on a temporary profile with a test plugin and checks the plugin API.

Releasing

Builds signed and notarized macOS files (dmg / zip, arm64 and x64) and the Windows installers (x64 and arm64) into dist/, then lists the files to attach.

# once: store notarization credentials in the keychain (asks for an app-specific password)
xcrun notarytool store-credentials developer-id-notary --apple-id <Apple ID> --team-id <Team ID>

APPLE_KEYCHAIN_PROFILE=developer-id-notary pnpm run release
# then run the gh release create … line it prints, with a notes file

scripts/release.sh checks for the notarization credentials and a Developer ID certificate first, and verifies the result with codesign, spctl and stapler afterwards — electron-builder only warns and skips notarization when credentials are missing.

In-app updates read latest-mac.yml / latest.yml from the latest release and fetch the zip (macOS) or the installer (Windows), plus the .blockmap files for differential downloads. A release without these files is not offered as an update (nor are drafts and pre-releases).

To try an update locally, build two versions and serve the newer one to the older one (macOS needs a Developer ID signature for this, not notarization):

for v in 0.0.90 0.0.91; do
  pnpm exec electron-builder --mac zip --arm64 --publish never -c.mac.notarize=false \
    -c.extraMetadata.version=$v -c.directories.output=/tmp/mxu/$v
done
(cd /tmp/mxu/0.0.91 && python3 -m http.server 18085 --bind 127.0.0.1) &
MXDECK_USER_DATA=/tmp/mxu/ud MXDECK_UPDATE_URL=http://127.0.0.1:18085/ \
  /tmp/mxu/0.0.90/mac-arm64/mxdeck.app/Contents/MacOS/mxdeck

It checks 30 seconds after launch and offers to restart once the download is done; progress goes to /tmp/mxu/ud/mxdeck.log.

License

MIT

About

Keep several Matrix accounts (Element Web, Cinny, …) side by side in one macOS window

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages