[Linux-only] basically bloxstap for sober
1import { $ } from "bun";
2import { GetProxyInterface, SendNotification } from "../api/linux";
3import { registerPlugin } from "../api/Plugin";
4import { getGameDetails } from "../api/roblox/GameInfo";
5
6registerPlugin(
7 {
8 name: "System Clipboard & D-Bus",
9 id: "dbus",
10 description: "Let games control MPRIS music players with D-Bus and write to the Wayland clipboard",
11 forceEnable: true,
12 configPrio: -9e9
13 },
14 async (plugin) => {
15 plugin.setFFlag("FFlagClientAllowClipboardControl", true);
16 plugin.setFFlag("FFlagClientAllowDBus", true);
17 plugin.setFFlag("FFlagIsLinux", true);
18
19 plugin.on("BLOXSTRAP_RPC", async (a) => {
20 switch (a.type) {
21 case "WaylandCopy": {
22 if (typeof a.data === "string" && a.data.length <= 512) {
23 } else {
24 return;
25 }
26 const gameName = await getGameDetails(
27 plugin.currentState.getPlaceId()!
28 );
29 if (!gameName) return;
30 await SendNotification(
31 "Roblox",
32 `${gameName} wrote to the Wayland clipboard!`,
33 3000
34 );
35 //TODO - fix this. https://roblox.com/games/15918403591 writes to clipboard, but it's not actually written
36 $`echo ${a.data} | wl-copy -n`.quiet().nothrow(); // <-- wayland is wayland (this is broken)
37 break;
38 }
39 case "CallDBus": {
40 if (typeof a.data !== "object") return;
41 if (typeof a.data.busName !== "string") return;
42 if (typeof a.data.action !== "string") return;
43
44 let {
45 busName,
46 action
47 }: { busName: string; action: string } = a.data;
48
49 if (busName.length >= 64) return;
50 if (!busName.startsWith("org.mpris.MediaPlayer2."))
51 busName = "org.mpris.MediaPlayer2." + busName;
52
53 if (
54 ![
55 "Play",
56 "Pause",
57 "PlayPause",
58 "Previous",
59 "Next"
60 ].includes(action)
61 )
62 return;
63
64 try {
65 const i = await GetProxyInterface(
66 busName,
67 "/org/mpris/MediaPlayer2",
68 "org.mpris.MediaPlayer2.Player"
69 );
70 switch (action) {
71 case "Play": {
72 await (i as any).Play();
73 break;
74 }
75 case "Pause": {
76 await (i as any).Pause();
77 break;
78 }
79 case "PlayPause": {
80 await (i as any).PlayPause();
81 break;
82 }
83 case "Previous": {
84 await (i as any).Previous();
85 break;
86 }
87 case "Next": {
88 await (i as any).Next();
89 break;
90 }
91 }
92 } catch {}
93
94 break;
95 }
96 }
97 });
98 }
99);