Three gaps caused messages received via SSE to be invisible when the conversation buffer wasn't already open: 1. Echo-area notification (noise-receive.el): when an SSE message arrives and the conversation buffer isn't in a visible window, show "[Noise] <name>" in the echo area. 2. Modeline unread counter (noise.el): the noise-mode lighter now shows Noise[N] for N unread messages via a lightweight SUM aggregation across all conversations. 3. Auto-refresh hook (noise-conversation.el): a window-buffer-change-functions hook re-renders *signal:* buffers when they become visible, covering direct C-x b switches. Also adds regression test: noise-receive-sse-stores-then-open-shows-message — store via SSE handler with no buffer open, then open and verify.
Noise
Noise is a Signal Messenger client in Elisp for Emacs powered by https://github.com/AsamK/signal-cli
Its ultimate goal is to provide a fast and efficient user interface for Signal chats.
Tech stack
The tech stack is Elisp and SQLite (for storing conversations)
Setup
The project assumes a working installation of signal-cli and interfaces with that via JSON-RPC. Noise uses Server-Sent Events (SSE) to receive messages in real-time.
Dependencies
For SQLite access we use this library - https://github.com/pekingduck/emacs-sqlite3-api
We communicate with signal-cli via JSON-RPC over HTTP on localhost port 8080 by default but this can be configured by specifying noise-signal-cli-address
User Experience
We follow Emacs conventions for keybindings with the addition of evil support out of the box. When evil compatibility is enabled then we provide a modal interface for searching conversations/contacts, creating conversations, sending messages, etc.
User Interface
See the file signal-emacs-ui.html for user interface mockups we're targeting.
Installation
signal-cli
Install signal-cli and start the daemon in JSON-RPC mode:
# Install signal-cli (example using the official tarball)
wget https://github.com/AsamK/signal-cli/releases/latest/download/signal-cli.tar.gz
tar xf signal-cli.tar.gz
# Register or link your device
signal-cli link -n "emacs-noise"
# Start the daemon on default port 9128
signal-cli daemon --receive-mode=manual
emacs-sqlite3-api
Noise requires the emacs-sqlite3-api library for SQLite access. Install it with your
package manager of choice, then set up Noise itself.
Noise Package
Below are recipes for several popular Emacs package managers.
elpaca
(use-package noise
:ensure (noise
:host github
:repo "git.roo.lol/jbrechtel/noise"
:protocol https)
:config
;; Optional: set a custom signal-cli address
;; (setq noise-signal-cli-address "http://localhost:8080")
;; Required when signal-cli has multiple accounts registered:
;; (setq noise-account "+15551234567")
;; Start Noise automatically
(noise-mode 1))
Or, if you prefer the elpaca recipe directly without use-package:
(elpaca (noise :host github
:repo "git.roo.lol/jbrechtel/noise"
:protocol https))
(require 'noise)
(noise-mode 1)
straight.el
(straight-use-package
'(noise :type git :host github
:repo "git.roo.lol/jbrechtel/noise"
:protocol https))
(require 'noise)
(noise-mode 1)
Quelpa
(quelpa
'(noise :fetcher github
:repo "git.roo.lol/jbrechtel/noise"))
(require 'noise)
(noise-mode 1)
Manual / package-vc-install (Emacs 29+)
(package-vc-install "https://git.roo.lol/jbrechtel/noise")
(require 'noise)
(noise-mode 1)
Manual clone
git clone https://git.roo.lol/jbrechtel/noise ~/.emacs.d/manual-packages/noise
Then in your init file:
(add-to-list 'load-path "~/.emacs.d/manual-packages/noise")
(require 'noise)
(noise-mode 1)
Usage
Once signal-cli is running and Noise is installed:
M-x noise-mode— toggle the Noise client (opens the chat list)M-x noise-check-connection— test connectivity to signal-cliM-x noise-select-account— pick which registered signal-cli account to use (setsnoise-account)M-x noise-new-chat— start a new conversation by searching contactsM-x noise-new-chat-sync-contacts— re-sync contacts from signal-cliM-x noise-receive— fetch new messages from signal-cli once
While noise-mode is enabled, Noise connects to signal-cli's SSE
endpoint at /api/v1/events for real-time message reception. Incoming
messages, delivery/read receipts, typing indicators, and messages sent
from your other linked devices are all stored locally and reflected in
open buffers automatically.
The SSE connection automatically reconnects with exponential backoff
if the connection is lost. You can configure the maximum reconnect
delay via noise-sse-max-reconnect-delay (default 60 seconds).
If signal-cli has more than one account registered, set noise-account
to the phone number you want to use (E.164 format), or run
M-x noise-select-account to choose interactively. With a single
account it can stay nil.
Keybindings
Chat list (signal buffer)
| Key | Action |
|---|---|
n |
Next chat |
p |
Previous chat |
RET |
Open selected chat |
c |
New chat (search contacts) |
m |
Mark chat as read |
g |
Refresh chat list |
/ |
Filter chats |
q |
Quit chat list window |
C-x b |
Switch conversation (minibuffer) |
Conversation buffer (signal:NAME)
Compose inline at the > prompt at the bottom of the buffer. Message
history above the prompt is read-only.
| Key | Action |
|---|---|
RET |
Send the composed message |
M-RET |
Insert a newline (multi-line) |
C-c C-g |
Refresh conversation from store |
Sent messages show a ✓ once sent and ✓✓ once delivered or read.
When the contact is typing, the modeline shows NAME is typing….
Minibuffer switcher
M-x noise-switcher (or C-x b from the chat list) opens a fuzzy
completion interface showing all conversations and contacts. Start
typing to narrow, then press RET to open the selected conversation.