summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2023-03-15 17:26:13 +0100
committerLibravatar GitHub <noreply@github.com>2023-03-15 17:26:13 +0100
commitf1152d3dbb4c6deefea168d66f15f77b7155a5fe (patch)
tree22c27234b6e3a2dfe47ade037ece47b2533f7039 /src
parent6.2.6-nightly.5 [skip ci] (diff)
downloadferdium-app-f1152d3dbb4c6deefea168d66f15f77b7155a5fe.tar.gz
ferdium-app-f1152d3dbb4c6deefea168d66f15f77b7155a5fe.tar.zst
ferdium-app-f1152d3dbb4c6deefea168d66f15f77b7155a5fe.zip
Basic D-Bus API (#866)
* feat: basic D-Bus API Expose muted state and the number of unread message over D-Bus when running on Linux. This is useful for, e.g., displaying notifications on a window manager status bar. Signed-off-by: Kristóf Marussy <kristof@marussy.com> * docs: create docs directory Move the documentation to a separate directory so that new documentation can be added into one place. We keep the following files still in the repository root by convention: * CHANGELOG.md * CODE_OF_CONDUCT.md * CONTRIBUTING.md * LICENSE.md * README.md * SECURITY.md Signed-off-by: Kristóf Marussy <kristof@marussy.com> * docs: D-Bus usage example Signed-off-by: Kristóf Marussy <kristof@marussy.com> * fix: remove unneeded D-Bus signals Only notify clients that the message counts or the mute status has changed if there actually was a change. Signed-off-by: Kristóf Marussy <kristof@marussy.com> * docs: rewrite sample bar client * docs: better unread --services help * docs: update dbus docs * docs: use ferdium-dbus in dbus bar example * docs: make command argument required in bar example --------- Signed-off-by: Kristóf Marussy <kristof@marussy.com> Co-authored-by: Victor Bonnelle <victor.bonnelle@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/lib/DBus.ts69
-rw-r--r--src/lib/dbus/Ferdium.ts88
-rw-r--r--src/stores/ServicesStore.ts52
3 files changed, 186 insertions, 23 deletions
diff --git a/src/lib/DBus.ts b/src/lib/DBus.ts
index bbff405c4..530e30c85 100644
--- a/src/lib/DBus.ts
+++ b/src/lib/DBus.ts
@@ -1,28 +1,92 @@
1import { ipcMain } from 'electron';
2import { comparer } from 'mobx';
3
1import { MessageBus, sessionBus } from 'dbus-next'; 4import { MessageBus, sessionBus } from 'dbus-next';
2import { isLinux } from '../environment'; 5import { isLinux } from '../environment';
3import TrayIcon from './Tray'; 6import TrayIcon from './Tray';
7import Ferdium, { type UnreadServices } from './dbus/Ferdium';
4 8
5export default class DBus { 9export default class DBus {
6 bus: MessageBus | null = null; 10 private bus: MessageBus | null = null;
7 11
8 trayIcon: TrayIcon; 12 trayIcon: TrayIcon;
9 13
14 private ferdium: Ferdium | null = null;
15
16 muted = false;
17
18 unreadDirectMessageCount = 0;
19
20 unreadIndirectMessageCount = 0;
21
22 unreadServices: UnreadServices = [];
23
10 constructor(trayIcon: TrayIcon) { 24 constructor(trayIcon: TrayIcon) {
11 this.trayIcon = trayIcon; 25 this.trayIcon = trayIcon;
26 ipcMain.on('initialAppSettings', (_, appSettings) => {
27 this.updateSettings(appSettings);
28 });
29 ipcMain.on('updateAppSettings', (_, appSettings) => {
30 this.updateSettings(appSettings);
31 });
32 ipcMain.on(
33 'updateDBusUnread',
34 (
35 _,
36 unreadDirectMessageCount,
37 unreadIndirectMessageCount,
38 unreadServices,
39 ) => {
40 this.setUnread(
41 unreadDirectMessageCount,
42 unreadIndirectMessageCount,
43 unreadServices,
44 );
45 },
46 );
47 }
48
49 private updateSettings(appSettings): void {
50 const muted = !!appSettings.data.isAppMuted;
51 if (this.muted !== muted) {
52 this.muted = muted;
53 this.ferdium?.emitMutedChanged();
54 }
12 } 55 }
13 56
14 start() { 57 private setUnread(
58 unreadDirectMessageCount: number,
59 unreadIndirectMessageCount: number,
60 unreadServices: UnreadServices,
61 ): void {
62 if (
63 this.unreadDirectMessageCount !== unreadDirectMessageCount ||
64 this.unreadIndirectMessageCount !== unreadIndirectMessageCount ||
65 !comparer.structural(this.unreadServices, unreadServices)
66 ) {
67 this.unreadDirectMessageCount = unreadDirectMessageCount;
68 this.unreadIndirectMessageCount = unreadIndirectMessageCount;
69 this.unreadServices = unreadServices;
70 this.ferdium?.emitUnreadChanged();
71 }
72 }
73
74 async start() {
15 if (!isLinux || this.bus) { 75 if (!isLinux || this.bus) {
16 return; 76 return;
17 } 77 }
18 78
19 try { 79 try {
20 this.bus = sessionBus(); 80 this.bus = sessionBus();
81 await this.bus.requestName('org.ferdium.Ferdium', 0);
21 } catch { 82 } catch {
22 // Error connecting to the bus. 83 // Error connecting to the bus.
23 return; 84 return;
24 } 85 }
25 86
87 this.ferdium = new Ferdium(this);
88 this.bus.export('/org/ferdium', this.ferdium);
89
26 // HACK Hook onto the MessageBus to track StatusNotifierWatchers 90 // HACK Hook onto the MessageBus to track StatusNotifierWatchers
27 // @ts-expect-error Property '_addMatch' does not exist on type 'MessageBus'. 91 // @ts-expect-error Property '_addMatch' does not exist on type 'MessageBus'.
28 this.bus._addMatch( 92 this.bus._addMatch(
@@ -56,5 +120,6 @@ export default class DBus {
56 120
57 this.bus.disconnect(); 121 this.bus.disconnect();
58 this.bus = null; 122 this.bus = null;
123 this.ferdium = null;
59 } 124 }
60} 125}
diff --git a/src/lib/dbus/Ferdium.ts b/src/lib/dbus/Ferdium.ts
new file mode 100644
index 000000000..b2a9105f4
--- /dev/null
+++ b/src/lib/dbus/Ferdium.ts
@@ -0,0 +1,88 @@
1import * as dbus from 'dbus-next';
2
3import type DBus from '../DBus';
4
5export type UnreadServices = [string, number, number][];
6
7export default class Ferdium extends dbus.interface.Interface {
8 constructor(private readonly dbus: DBus) {
9 super('org.ferdium.Ferdium');
10 }
11
12 emitMutedChanged(): void {
13 Ferdium.emitPropertiesChanged(this, { Muted: this.dbus.muted }, []);
14 }
15
16 get Muted(): boolean {
17 return this.dbus.muted;
18 }
19
20 set Muted(muted: boolean) {
21 if (this.dbus.muted !== muted) {
22 this.ToggleMute();
23 }
24 }
25
26 ToggleMute(): void {
27 this.dbus.trayIcon.mainWindow?.webContents.send('muteApp');
28 }
29
30 ToggleWindow(): void {
31 this.dbus.trayIcon._toggleWindow();
32 }
33
34 emitUnreadChanged(): void {
35 Ferdium.emitPropertiesChanged(
36 this,
37 {
38 UnreadDirectMessageCount: this.dbus.unreadDirectMessageCount,
39 UnreadIndirectMessageCount: this.dbus.unreadIndirectMessageCount,
40 UnreadServices: this.dbus.unreadServices,
41 },
42 [],
43 );
44 }
45
46 get UnreadDirectMessageCount(): number {
47 return this.dbus.unreadDirectMessageCount;
48 }
49
50 get UnreadIndirectMessageCount(): number {
51 return this.dbus.unreadIndirectMessageCount;
52 }
53
54 get UnreadServices(): UnreadServices {
55 return this.dbus.unreadServices;
56 }
57}
58
59Ferdium.configureMembers({
60 methods: {
61 ToggleMute: {
62 inSignature: '',
63 outSignature: '',
64 },
65 ToggleWindow: {
66 inSignature: '',
67 outSignature: '',
68 },
69 },
70 properties: {
71 Muted: {
72 signature: 'b',
73 access: dbus.interface.ACCESS_READWRITE,
74 },
75 UnreadDirectMessageCount: {
76 signature: 'u',
77 access: dbus.interface.ACCESS_READ,
78 },
79 UnreadIndirectMessageCount: {
80 signature: 'u',
81 access: dbus.interface.ACCESS_READ,
82 },
83 UnreadServices: {
84 signature: 'a(suu)',
85 access: dbus.interface.ACCESS_READ,
86 },
87 },
88});
diff --git a/src/stores/ServicesStore.ts b/src/stores/ServicesStore.ts
index 0ab4dbc5b..829c64d76 100644
--- a/src/stores/ServicesStore.ts
+++ b/src/stores/ServicesStore.ts
@@ -1,4 +1,4 @@
1import { shell } from 'electron'; 1import { ipcRenderer, shell } from 'electron';
2import { action, reaction, computed, observable, makeObservable } from 'mobx'; 2import { action, reaction, computed, observable, makeObservable } from 'mobx';
3import { debounce, remove } from 'lodash'; 3import { debounce, remove } from 'lodash';
4import ms from 'ms'; 4import ms from 'ms';
@@ -23,6 +23,7 @@ import { cleanseJSObject } from '../jsUtils';
23import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 23import { SPELLCHECKER_LOCALES } from '../i18n/languages';
24import { ferdiumVersion } from '../environment-remote'; 24import { ferdiumVersion } from '../environment-remote';
25import TypedStore from './lib/TypedStore'; 25import TypedStore from './lib/TypedStore';
26import type { UnreadServices } from '../lib/dbus/Ferdium';
26 27
27const debug = require('../preload-safe-debug')('Ferdium:ServiceStore'); 28const debug = require('../preload-safe-debug')('Ferdium:ServiceStore');
28 29
@@ -1230,26 +1231,29 @@ export default class ServicesStore extends TypedStore {
1230 const { showMessageBadgeWhenMuted } = this.stores.settings.all.app; 1231 const { showMessageBadgeWhenMuted } = this.stores.settings.all.app;
1231 const { showMessageBadgesEvenWhenMuted } = this.stores.ui; 1232 const { showMessageBadgesEvenWhenMuted } = this.stores.ui;
1232 1233
1233 const unreadDirectMessageCount = this.allDisplayed 1234 const unreadServices: UnreadServices = [];
1234 .filter( 1235 let unreadDirectMessageCount = 0;
1235 s => 1236 let unreadIndirectMessageCount = 0;
1236 (showMessageBadgeWhenMuted || s.isNotificationEnabled) && 1237
1237 showMessageBadgesEvenWhenMuted && 1238 if (showMessageBadgesEvenWhenMuted) {
1238 s.isBadgeEnabled, 1239 for (const s of this.allDisplayed) {
1239 ) 1240 if (s.isBadgeEnabled) {
1240 .map(s => s.unreadDirectMessageCount) 1241 const direct =
1241 .reduce((a, b) => a + b, 0); 1242 showMessageBadgeWhenMuted || s.isNotificationEnabled
1242 1243 ? s.unreadDirectMessageCount
1243 const unreadIndirectMessageCount = this.allDisplayed 1244 : 0;
1244 .filter( 1245 const indirect =
1245 s => 1246 showMessageBadgeWhenMuted && s.isIndirectMessageBadgeEnabled
1246 showMessageBadgeWhenMuted && 1247 ? s.unreadIndirectMessageCount
1247 showMessageBadgesEvenWhenMuted && 1248 : 0;
1248 s.isBadgeEnabled && 1249 unreadDirectMessageCount += direct;
1249 s.isIndirectMessageBadgeEnabled, 1250 unreadIndirectMessageCount += indirect;
1250 ) 1251 if (direct > 0 || indirect > 0) {
1251 .map(s => s.unreadIndirectMessageCount) 1252 unreadServices.push([s.name, direct, indirect]);
1252 .reduce((a, b) => a + b, 0); 1253 }
1254 }
1255 }
1256 }
1253 1257
1254 // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases 1258 // We can't just block this earlier, otherwise the mobx reaction won't be aware of the vars to watch in some cases
1255 if (showMessageBadgesEvenWhenMuted) { 1259 if (showMessageBadgesEvenWhenMuted) {
@@ -1257,6 +1261,12 @@ export default class ServicesStore extends TypedStore {
1257 unreadDirectMessageCount, 1261 unreadDirectMessageCount,
1258 unreadIndirectMessageCount, 1262 unreadIndirectMessageCount,
1259 }); 1263 });
1264 ipcRenderer.send(
1265 'updateDBusUnread',
1266 unreadDirectMessageCount,
1267 unreadIndirectMessageCount,
1268 unreadServices,
1269 );
1260 } 1270 }
1261 } 1271 }
1262 1272